Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .copier-answers.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Do NOT update manually; changes here will be overwritten by Copier
_commit: 0d084b9
_commit: 95203e6
_src_path: https://github.com/ingadhoc/addons-repo-template.git
description: 'ADHOC Odoo website Modules

Expand Down
2 changes: 1 addition & 1 deletion .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,4 @@ Cambios clave de Odoo 19 a tener en cuenta (detalle en cada `instructions.md` es
| Seguridad | ACL mínimo; sin `cr.execute` con interpolación; sin `eval()` sobre input externo |
| Migraciones | Cambios estructurales → script idempotente en lotes |
| Rendimiento | Sin `search`/`write`/`create` en loop; `mapped`/`filtered`/`search_count`/`_read_group` |
| i18n | Textos al usuario envueltos en `_()`; no marcar nombres técnicos ni claves de dict |
| i18n | Textos al usuario envueltos en `_()`; no marcar nombres técnicos ni claves de dict |
71 changes: 71 additions & 0 deletions .github/instructions/i18n.instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
---
applyTo:
- "**/models/**/*.py"
- "**/wizards/**/*.py"
- "**/wizard/**/*.py"
- "**/controllers/**/*.py"
- "**/report/**/*.py"
- "**/i18n/**/*.po"
- "**/i18n/**/*.pot"
---

# Revisión de internacionalización (i18n)

## Marcar texto traducible

- Todo texto que se muestra al usuario debe estar envuelto en `_()` o `self.env._()` (indistinto):
```python
raise UserError(_("No se puede eliminar un pedido confirmado."))
return {'warning': {'title': _("Atención"), 'message': _("Stock insuficiente.")}}
```
- Import típico: `from odoo import _, _lt` (usar `_lt` cuando el texto se define a nivel módulo/clase y la traducción se resuelve en runtime).

## Qué marcar como issue

- `raise UserError("...")` o `ValidationError("...")` con string literal.
- `return {'warning': {'message': "texto"}}`.
- Mensajes de `raise`, `notifications`, toast, `display_name` calculado, labels en wizards, títulos de acciones construidas dinámicamente.
- Textos en `_message_post` que muestran al usuario.

## Qué NO marcar

- Nombres técnicos de campos (`'partner_id'`, `'name'`).
- Claves de diccionarios (`'state': 'draft'`).
- Logs técnicos (`_logger.info("...")`) — no se traducen.
- Nombres de xml_ids.
- Cadenas en tests, comentarios, docstrings.
- `fields.Char(string="Name")`: el `string=` se recoge para i18n automáticamente, no requiere `_()`.

## Uso correcto de `_()`

- `_()` resuelve traducción **en el momento de la llamada** (runtime del idioma del usuario).
- `_lt()` (lazy translate) para strings definidas a nivel módulo; la traducción se resuelve al serializar, útil en selecciones y listas de constantes.
- No concatenar fragmentos traducibles con `+`: usar `%` o f-string sobre la cadena ya traducida:
```python
# MAL (rompe traducción)
raise UserError(_("Error en ") + record.name)
# BIEN
raise UserError(_("Error en %s") % record.name)
```
- Evitar format con claves traducibles múltiples; preferir placeholders con nombre:
```python
raise UserError(_("Falta %(field)s en %(model)s") % {'field': name, 'model': model})
```

## Archivos `.po` / `.pot`

- No editar manualmente traducciones generadas por `odoo i18n export` salvo correcciones puntuales.
- Commits que solo tocan `.po` / `.pot` de exportación suelen ser benignos; no requieren tests ni script de migración.
- Si se agrega un idioma nuevo, verificar que esté listado en `i18n/` y que las cadenas base existan en `.pot`.

## Convención del equipo (ADHOC)

- Idioma destino principal: **español latinoamericano formal**. Evitar tuteo en mensajes de sistema ("usted" vs "tú").
- Mantener consistencia terminológica: "pedido" (no "orden"), "contacto" (no "partner" en user-facing), "factura", etc.
- Placeholders (`%s`, `%(name)s`) deben mantenerse idénticos entre el mensaje original y la traducción.

## Criterio de severidad

- **Medio**: texto al usuario sin `_()`, aislado.
- **Bajo**: patrón que podría mejorarse (concatenación con `+`, falta de `_lt` en constante de módulo).
- No-issue: archivo `.po` autogenerado con cambios de exportación rutinarios.
48 changes: 48 additions & 0 deletions .github/instructions/manifest.instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
---
applyTo:
- "**/__manifest__.py"
---

# Revisión de `__manifest__.py`

## Archivos referenciados

- Todo archivo usado por el módulo (vistas, seguridad, datos, reportes, wizards, demo) debe estar listado en alguna de las claves del manifest (`data`, `demo`, `assets`).
- Si un archivo XML/CSV se borra del módulo, debe removerse del manifest; si se agrega uno nuevo, debe incluirse.
- Orden relativo importa: datos de seguridad antes de datos que los referencian; vistas después de sus modelos.

## Dependencias (`depends`)

- Deben listarse todos los módulos cuyos modelos/vistas/xml_ids se usan directamente.
- **No** declarar dependencias innecesarias (infla el árbol de instalación).
- Módulos de localización (`l10n_*`) solo cuando el módulo depende funcionalmente; no por conveniencia.

## Versión

- Formato `<serie>.<mayor>.<menor>.<patch>` (ej. `19.0.1.0.0`). La serie (`19.0`, `18.0`) debe coincidir con la rama y la versión de Odoo target.
- **Regla obligatoria de versión**: cualquier cambio estructural que requiera script en `migrations/` debe **bumpear la versión** del módulo, y la carpeta bajo `migrations/` debe coincidir.
- Solo comentar la versión **una vez por revisión**, aunque haya múltiples archivos afectados.

## Metadatos

- `name`, `summary`, `description` deben estar definidos y ser consistentes.
- `author`, `license` presentes. En Adhoc, típicamente `"ADHOC SA"` y licencia según convención del repo.
- `category` coherente con el tipo de módulo.
- `installable: True` salvo que explícitamente esté siendo discontinuado.
- `application: True` solo para módulos que deben aparecer como aplicación raíz (no para sub-módulos).

## Assets (bundles)

- `assets` debe listar bundles correctos (`web.assets_backend`, `web.assets_frontend`, `web.report_assets_common`, `web.assets_tests`, etc.).
- Extensiones coherentes: `.js`, `.scss`, `.css`, `.xml` (OWL templates).
- Archivos borrados deben quitarse también de `assets`.

## Hooks

- `pre_init_hook`, `post_init_hook`, `uninstall_hook`, `post_load`: si están declarados, verificar que apunten a funciones existentes en el módulo (`from . import hooks` o similar).
- Los hooks deben ser idempotentes y no dependientes de datos demo.

## Demo data

- Datos de demo en la key `demo`, **no** mezclados con `data`.
- Al introducir funcionalidad nueva que se beneficia de casos visibles, considerar agregar demo; al introducir módulo de configuración, no es necesario.
59 changes: 59 additions & 0 deletions .github/instructions/migrations.instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
---
applyTo:
- "**/migrations/**/*.py"
- "**/__manifest__.py"
- "**/models/**/*.py"
---

# Revisión de scripts de migración

> Si el diff introduce cambio estructural en un modelo, **siempre** evaluar si corresponde proponer script en `migrations/<version>/`.

## Cuándo proponer script

1. **Rename de campo almacenado** (`Char`, `Many2one`, etc. o `compute` con `store=True`). **No** si es `compute` sin store.
2. **Rename de modelo**: siempre. Toca `ir.model`, `ir.model.data`, tablas relacionales, vistas, acciones.
3. **Cambio de tipo de campo** con cambio real en DB (`Char→Many2one`, `Selection→Many2one`, `Many2one→Many2many`). Cambios compatibles (`Char→Text`, ajustes de `Float`) no requieren script.
4. **Split/merge de campos**.
5. **Nuevo `compute` con `store=True`** que aplique a registros históricos → post-script de backfill en lotes. Advertir si el modelo tiene millones de registros.
6. **Cambio en keys de `selection`**: renombrar/eliminar existentes → script que mapee `old → new`. Agregar nuevas keys **no** requiere script.
7. **Cambio de dominio** en relacional que excluya valores usados históricamente → limpiar/remapear.
8. **Nueva `UNIQUE`/índice** (`_sql_constraints` o `models.Constraint`): pre-script que resuelva duplicados antes de crear la constraint.
9. **Cambios en `ir.model.data` / XML IDs** (rename `module.name → module2.name2`): script para actualizar referencias.
10. **Registros con `noupdate="1"`** cuyo contenido lógico cambia: forzar update por `xml_id`.
11. **Cambios en reglas de acceso / multi-company / multi-website**: rellenar campos obligatorios, recomputar ownership.

> **No** proponer script solo por `required=True` nuevo sin default, salvo que el diff evidencie datos históricos incompatibles.

## Pre / Post / End

- **pre**: antes del update. Preparar datos/esquemas para evitar fallos.
- **post**: después. Recalcular, limpiar, ajustar referencias.
- **end**: al final del upgrade global. Tareas cross-módulo o finales.

Regla: **rompe durante el upgrade → pre**; **recalcula después → post**; **global al final → end**.

## Mapeo cambio → acción

- **Rename campo almacenado** → pre: copiar datos viejo→nuevo. Post: cleanup + recomputes.
- **Rename modelo** → pre: mapear `ir.model`/`ir.model.data`. Post: re-enlazar vistas, acciones, menús, reglas.
- **Split/merge** → pre: copiar a nuevos campos antes de que el schema borre el viejo. Post: normalizar/recompute.
- **`compute` nuevo con `store=True`** → post: backfill en lotes (pre opcional en modelos grandes para preparar columna).
- **Cambio de tipo con conversión** → pre: columna temporal + conversión. Post: swap/rename/borrar vieja.
- **`selection` (remove/rename keys)** → pre: mapeo `old → new` (usar `change_field_selection_values` si aplica). Post: validar consistencia.
- **Nueva `UNIQUE`** → pre: resolver duplicados. Post: crear índice si aplica.
- **`noupdate="1"` con cambio lógico** → post: update por `xml_id`.

## Convenciones

- Ubicación: `migrations/<module_version>/` (ej. `migrations/19.0.1.0/`). Versión debe coincidir con `__manifest__.py`.
- Nombres: `pre_<desc>.py`, `post_<desc>.py`, `end_<desc>.py`.
- **Idempotentes**: seguros ante re-ejecución.
- **En lotes** (`batch_size` razonable) para datasets grandes.
- Logs claros (`_logger.info`); comentario al inicio documentando supuestos y garantías.
- Evitar transacciones muy largas; `env.cr.commit()` controlado o helpers de progreso.

## Versión del manifest

- Al introducir cambio estructural, **bumpear** versión en `__manifest__.py` para que el script corra (ej. `19.0.1.0 → 19.0.2.0`).
- La carpeta bajo `migrations/` debe coincidir con la nueva versión.
68 changes: 68 additions & 0 deletions .github/instructions/models.instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
---
applyTo:
- "**/models/**/*.py"
- "**/wizards/**/*.py"
- "**/wizard/**/*.py"
- "**/report/**/*.py"
---

# Revisión de modelos Python

## Relaciones y campos

- `Many2one`/`One2many`/`Many2many` deben declarar `comodel_name` y `ondelete` apropiado. Evitar `ondelete='cascade'` sin justificación.
- Nombres de campos claros, consistentes, sin conflictos con campos heredados.
- `required=True` sin `default` **solo** si no hay datos históricos que puedan romperse. Si los hay, proponer `default` o migración.
- Campos `compute` con `store=True` que dependen de datos históricos pueden necesitar backfill (ver `migrations.instructions.md`).

## Decoradores `@api.*`

- `@api.depends` debe listar **todas** las dependencias reales, incluidas las dotted (`@api.depends('partner_id.email')`).
- `@api.constrains` **no** acepta dotted paths, solo nombres simples.
- `@api.onchange` no debe escribir a BD ni modificar campos computados.
- Evitar decoradores obsoletos: `@api.one`, `@api.multi` (Odoo 13+ no los acepta).
- **Odoo 18+**: para prevenir borrado usar `@api.ondelete(at_uninstall=False)` en vez de sobreescribir `unlink`.
- `@api.model` solo cuando el método no depende de `self` como recordset.
- `@api.model_create_multi` para métodos `create` que aceptan lista de dicts (obligatorio en Odoo 17+).

## Herencia y `super()`

- Métodos redefinidos deben llamar `super()` salvo que el contrato diga lo contrario. Preservar el tipo/shape del retorno.
- `_name` + `_inherit` juntos solo cuando se busca crear modelo nuevo (multi-table inheritance); marcar si no hay razón clara.
- No sobrescribir `create`/`write`/`unlink` solo para side effects triviales; preferir `@api.depends`, `@api.constrains` o `@api.ondelete`.

## Constraints e índices

- **Odoo 19+**: usar `models.Constraint(...)`, `models.Index(...)`, `models.UniqueIndex(...)` como declarativas a nivel de clase, en vez de `_sql_constraints`. Si el diff ya toca constraints, sugerir migrar a la nueva API.
- Mensajes de constraint deben ser traducibles (`_("...")`).
- Añadir `UNIQUE` sobre tabla con datos existentes puede fallar; ver `migrations.instructions.md`.

## ORM seguro y eficiente

- Evitar `search` dentro de loops → usar dominio con `in` sobre ids o `_read_group`.
- Evitar `write`/`create`/`unlink` uno a uno en loops → vectorizar sobre recordset.
- `create` en Odoo 17+: preferir lista de dicts `create([{...}, {...}])`.
- `mapped`, `filtered`, `search_count`, `search_fetch` antes que recorrer en Python.
- Navegación relacional segura: `rec.partner_id.email` devuelve falso si `partner_id` vacío; no duplicar el check.
- Acceso por índice (`recordset[0]`) puede lanzar `IndexError`; guardar con `if rec: ...` o rediseñar para operar sobre el recordset completo.
- Evitar `sudo()` amplio/innecesario en métodos de negocio; justificar cada uso.
- En Odoo 19, `cr.execute` crudo desaconsejado → usar clase `SQL` con `execute_query_dict()`. Si hay `cr.execute` con interpolación (`%`, f-string, `.format`) → bloqueante, ver `security.instructions.md`.

## Nombres y estilo

- Métodos privados prefijo `_`; en Odoo 19, preferir `@api.private` donde aplica.
- Métodos muy largos (>50 líneas) → sugerir split.
- Comparaciones booleanas: `if x:` / `if not x:` (no `== True` / `== False`).
- `else` después de `return` innecesario.
- Imports no utilizados deben removerse.

## Dominios

- En Odoo 19 es válido `Domain('field', 'op', 'value')` y combinar con `&`, `|`, `~`. No marcar como error.
- `Domain` permite uso en `filtered`: no hace falta convertir a lista.
- Nunca construir dominios como strings y pasarlos por `eval` (ver `security.instructions.md`).

## Selecciones

- Agregar nuevos values a un `selection` **no** requiere migración.
- Renombrar/eliminar keys existentes → proponer script que mapee `old → new` (ver `migrations.instructions.md`).
82 changes: 82 additions & 0 deletions .github/instructions/performance.instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
---
applyTo:
- "**/models/**/*.py"
- "**/wizards/**/*.py"
- "**/wizard/**/*.py"
- "**/controllers/**/*.py"
- "**/report/**/*.py"
---

# Revisión de rendimiento (ORM)

## Anti-patrones que bloquean performance

- **Search en loop** → N+1 queries. Reemplazar por una sola `search` con dominio `in` sobre ids, o `_read_group` / `search_fetch`.
```python
# MAL
for order in orders:
payments = self.env['payment'].search([('order_id', '=', order.id)])
# BIEN
payments = self.env['payment'].search([('order_id', 'in', orders.ids)])
```
- **Create/write/unlink en loop** → múltiples roundtrips a DB. Vectorizar:
```python
# MAL
for vals in data:
self.env['res.partner'].create(vals)
# BIEN (Odoo 17+)
self.env['res.partner'].create(data) # lista de dicts
```
- **`search([])` + filtrado en Python** → traer todos los records. Usar dominio preciso.
- **`mapped` en loop** sobre recordsets grandes → preferir una única `.mapped('field')` fuera del loop.

## `@api.depends` afinado

- Listar todas las dependencias **reales**, incluidas las dotted: `@api.depends('partner_id.email')` para evitar consultas extra.
- No listar campos ajenos al compute (dispara recomputes innecesarios).
- Evitar depender de campos no almacenados en cadenas largas.

## Agregados

- Para sumar/contar preferir `read_group` / `_read_group` / `formatted_read_group` (Odoo 17+) antes que iterar + `sum`/`len`.
- `search_count(domain)` en vez de `len(search(domain))`.
- `browse(ids)` en lugar de re-buscar cuando ya se tienen ids.

## Relacionales

- **N+1 por navegación**: si un `@api.depends` dispara muchas lecturas, ajustar dependencias o prefetch.
- `mapped('campo_relacional.subcampo')` agrupa lecturas y usa prefetch; preferir a loops manuales.
- `filtered_domain(domain)` para filtrados con mismo idioma que `search`.

## Cron y jobs largos

- **Odoo 19**: usar `self.env['ir.cron']._commit_progress(remaining=N)` / `_commit_progress(processed=M)` en crons en lugar de `notify_progress` / commits manuales ad hoc.
- Procesar en **lotes** (`batch_size` razonable, p. ej. 500–1000) y commitear por lote.
- Logs con `_logger.info` para observabilidad.

## Computes y store

- `store=True` sobre `compute` implica backfill en historia → ver `migrations.instructions.md`.
- `compute` sin store se reevalúa por read; si se accede repetidas veces en un loop, cachear localmente.
- `write` dentro de un compute → anti-patrón, genera recursión o recomputes encadenados.

## Transacciones

- `flush()` explícito solo cuando se requiere forzar la orden de escritura antes de leer. No usar en loops.
- `env.cr.commit()` en crons o scripts de migración, pero nunca dentro de lógica transaccional de usuario.
- `invalidate_cache` solo si hay razón concreta (modificación externa por SQL directo).

## Vistas XML relacionadas (cross-reference)

- Filtros en listas grandes sobre campos no indexados → sugerir `index=True` en el modelo.
- Columnas de lista que nunca se muestran: `column_invisible="1"` (evita cargar valores). Ver `views.instructions.md`.

## Cuándo NO optimizar

- Loops sobre recordsets pequeños y acotados (< ~20 elementos) donde la claridad gana a la micro-optimización.
- Código de setup/install que corre una única vez.
- Para diffs chicos y acotados, evitar proponer reescrituras masivas — preferir marcar la regla para futuras iteraciones.

## Beneficios indirectos

- Mantenerse dentro del ORM hereda controles de acceso, auditoría, reglas multi-compañía y prefetch automático. Queries crudas pierden todo eso.
Loading
Loading