From 6d97efc4db2b22b374fc7a92787d9a2ec04bb031 Mon Sep 17 00:00:00 2001 From: Joaquin Arroyo Date: Mon, 4 May 2026 12:30:37 -0300 Subject: [PATCH] [ADD] Add new copilot instructions --- .copier-answers.yml | 2 +- .github/copilot-instructions.md | 2 +- .github/instructions/i18n.instructions.md | 71 ++++++++++++++++ .github/instructions/manifest.instructions.md | 48 +++++++++++ .../instructions/migrations.instructions.md | 59 +++++++++++++ .github/instructions/models.instructions.md | 68 +++++++++++++++ .../instructions/performance.instructions.md | 82 +++++++++++++++++++ .github/instructions/security.instructions.md | 62 ++++++++++++++ .github/instructions/tests.instructions.md | 64 +++++++++++++++ .github/instructions/views.instructions.md | 60 ++++++++++++++ 10 files changed, 516 insertions(+), 2 deletions(-) create mode 100644 .github/instructions/i18n.instructions.md create mode 100644 .github/instructions/manifest.instructions.md create mode 100644 .github/instructions/migrations.instructions.md create mode 100644 .github/instructions/models.instructions.md create mode 100644 .github/instructions/performance.instructions.md create mode 100644 .github/instructions/security.instructions.md create mode 100644 .github/instructions/tests.instructions.md create mode 100644 .github/instructions/views.instructions.md diff --git a/.copier-answers.yml b/.copier-answers.yml index 42e93bdd..27e7b3ee 100644 --- a/.copier-answers.yml +++ b/.copier-answers.yml @@ -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 diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 8d9ee744..fc9d7d5f 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -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 | \ No newline at end of file +| i18n | Textos al usuario envueltos en `_()`; no marcar nombres técnicos ni claves de dict | diff --git a/.github/instructions/i18n.instructions.md b/.github/instructions/i18n.instructions.md new file mode 100644 index 00000000..ed7fc7b0 --- /dev/null +++ b/.github/instructions/i18n.instructions.md @@ -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. diff --git a/.github/instructions/manifest.instructions.md b/.github/instructions/manifest.instructions.md new file mode 100644 index 00000000..4444016c --- /dev/null +++ b/.github/instructions/manifest.instructions.md @@ -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 `...` (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. diff --git a/.github/instructions/migrations.instructions.md b/.github/instructions/migrations.instructions.md new file mode 100644 index 00000000..9557bb29 --- /dev/null +++ b/.github/instructions/migrations.instructions.md @@ -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//`. + +## 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//` (ej. `migrations/19.0.1.0/`). Versión debe coincidir con `__manifest__.py`. +- Nombres: `pre_.py`, `post_.py`, `end_.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. diff --git a/.github/instructions/models.instructions.md b/.github/instructions/models.instructions.md new file mode 100644 index 00000000..7de0ac75 --- /dev/null +++ b/.github/instructions/models.instructions.md @@ -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`). diff --git a/.github/instructions/performance.instructions.md b/.github/instructions/performance.instructions.md new file mode 100644 index 00000000..e2e66e4a --- /dev/null +++ b/.github/instructions/performance.instructions.md @@ -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. diff --git a/.github/instructions/security.instructions.md b/.github/instructions/security.instructions.md new file mode 100644 index 00000000..56f097e8 --- /dev/null +++ b/.github/instructions/security.instructions.md @@ -0,0 +1,62 @@ +--- +applyTo: + - "**/security/**" + - "**/controllers/**/*.py" + - "**/models/**/*.py" + - "**/wizards/**/*.py" + - "**/wizard/**/*.py" +--- + +# Revisión de seguridad + +## ACL y reglas de acceso + +- Modelo nuevo debe tener fila en `security/ir.model.access.csv` con permisos **mínimos necesarios**. No abrir `perm_unlink` o `perm_write` si no se justifica. +- Campos sensibles (datos personales, flags de configuración, credenciales) deben restringirse por `groups="..."`. +- `record rules` (`ir.rule`) nuevas deben cubrir multi-compañía cuando el modelo tiene `company_id`. Verificar reglas globales vs por grupo. +- **Odoo 19**: `res.groups.category_id` fue reemplazado por `privilege_id` + `res.groups.privilege`; al crear grupos usar la nueva estructura. + +## SQL injection + +- **Bloqueante**: `self.env.cr.execute("... '%s' ..." % var)` o con f-string/`.format`. Toda variable debe pasar como parámetro: + ```python + self.env.cr.execute("SELECT id FROM res_partner WHERE name = %s", (name,)) + ``` +- Preferir dominio ORM: `self.env['res.partner'].search([('name', '=', name)])`. +- **Odoo 19**: usar clase `SQL` con `execute_query_dict()` para consultas seguras; marcar si se ve `cr.execute` crudo. + +## Ejecución arbitraria y deserialización + +- `eval()`, `exec()`: nunca sobre input del usuario. +- Dominios construidos como string y pasados por `eval` → bloqueante. Usar lista de tuplas o `Domain(...)`. +- `safe_eval` permitido solo sobre contextos controlados; marcar si viene de parámetros de request. +- `pickle.loads`, `yaml.load` (sin `SafeLoader`), `marshal`: prohibidos con data no confiable. + +## Bypass de reglas + +- `sudo()` en controllers/wizards: cada uso requiere justificación explícita. Evitar `sudo()` amplio a nivel de método. +- `with_user(SUPERUSER_ID)` sólo para operaciones de sistema documentadas. +- Accesos multi-compañía sin `company_id` explícito: riesgo de leakage; exigir scoping. + +## Controllers HTTP + +- `auth='public'` con escritura o acceso a datos sensibles → riesgo. Evaluar si debería ser `auth='user'` o `auth='portal'`. +- `@http.route(..., csrf=False)` solo para endpoints no-UI (webhooks, APIs) y con autenticación alternativa; marcar si se desactiva sin justificación. +- `browse(int(request.params.get('id')))`: validar pertenencia del registro al usuario actual antes de operar. +- Input del usuario que llega a SQL, filesystem o shell → ver secciones específicas. + +## Filesystem y comandos + +- `subprocess.*` con `shell=True` → bloqueante. Pasar args como lista. +- Paths construidos con input del usuario sin validar → path traversal. Usar `werkzeug.utils.secure_filename` o equivalente. +- URLs descargadas con input del usuario → riesgo SSRF; validar esquema y host permitido. + +## Sensibles específicas Odoo 19 + +- Integraciones IA, VOIP, WhatsApp, Equity/ESG: cambios acá pueden requerir migración de tokens/ownership. Revisar con atención y sugerir script si aplica (ver `migrations.instructions.md`). + +## Criterio de severidad + +- **Bloqueante** (BLOCKER): SQL injection, eval sobre input, shell=True, deserialización insegura, `auth='public'` con efectos secundarios graves. +- **Alto** (HIGH): `sudo()` sin justificación, bypass de ACL, record rules faltantes. +- **Medio** (MEDIUM): falta `groups` en campos sensibles, `noupdate` sin considerar consecuencias. diff --git a/.github/instructions/tests.instructions.md b/.github/instructions/tests.instructions.md new file mode 100644 index 00000000..34d81281 --- /dev/null +++ b/.github/instructions/tests.instructions.md @@ -0,0 +1,64 @@ +--- +applyTo: + - "**/tests/**/*.py" + - "**/models/**/*.py" + - "**/wizards/**/*.py" + - "**/wizard/**/*.py" + - "**/controllers/**/*.py" +--- + +# Revisión de cobertura de tests + +## Cuándo sugerir tests + +Sugerir agregar tests cuando el diff introduce **funcionalidad no trivial**: + +- Métodos nuevos con lógica de negocio (cálculos, validaciones, transiciones de estado). +- Nuevos flujos/wizards completos. +- Refactors amplios de código existente (especialmente si cambia firma de métodos públicos). +- Nuevas APIs/endpoints de controladores. +- Cambios en reportes que alteran la salida. +- Overrides de `create`/`write`/`unlink` con side effects. + +## Cuándo NO sugerir + +- Cambios puramente cosméticos (textos, vistas simples, ajustes de estilo). +- Correcciones menores sin cambio de comportamiento. +- Solo traducciones / solo documentación. +- Renombres de variables. + +## Tipo de test apropiado + +- **Unitario de modelo** (`TransactionCase` / `TestCase`): validar métodos, constraints, computes, onchanges. +- **Wizard test**: instanciar wizard, setear campos, disparar acción, assert resultado. +- **HttpCase**: controladores, rutas, autenticación, respuesta. +- **Tour** (`odoo.tests.common.HttpCase` + tour JS): flujos de UI críticos, especialmente en OWL components. +- **Reporte**: generar reporte contra data conocida y comparar output. + +## Calidad del test + +- `setUp` preparando datos mínimos; preferir factory methods o datos de demo. +- Assertions concretas: no `assertTrue(result)` si se puede `assertEqual(result, expected)`. +- Decoradores apropiados: `@tagged('post_install', '-at_install')` para tests que dependen de módulos dependientes. +- Evitar dependencias del orden de ejecución entre tests; cada test debe ser independiente. +- Si el test crea registros con datos predecibles, usar ids/xml_ids estables para poder referenciarlos. + +## Patrones a marcar como issue + +- Test nuevo sin `assertEqual` / `assertRaises` / similar → no valida nada. +- `try: ... except: pass` en tests → oculta fallos. +- Tests que dependen de la hora del sistema sin `freeze_time` / `mute_logger` donde aplica. +- Tests que modifican `noupdate` records sin restaurar estado. + +## Criterio de suficiencia + +- No exigir una suite completa por cada cambio. +- Una sugerencia concreta y breve es suficiente: "Para este método de cálculo, podría agregarse un test unitario que cubra el caso X." (sin diseñar la suite entera). +- Si el módulo ya tiene una carpeta `tests/` con cobertura previa similar, sugerir seguir el mismo estilo. + +## En PRs que SÍ agregan tests + +- Verificar que el test realmente cubra el diff (no solo código alrededor). +- Que no haga mocks innecesarios del ORM (regla del equipo: preferir tests de integración sobre mocks de BD). +- Que se ejecute: nombre `test_*.py`, clase `Test*`, método `test_*`. +- `__init__.py` en `tests/` importa el nuevo archivo. diff --git a/.github/instructions/views.instructions.md b/.github/instructions/views.instructions.md new file mode 100644 index 00000000..304409d1 --- /dev/null +++ b/.github/instructions/views.instructions.md @@ -0,0 +1,60 @@ +--- +applyTo: + - "**/views/**/*.xml" + - "**/reports/**/*.xml" + - "**/data/**/*.xml" +--- + +# Revisión de vistas XML y QWeb + +## Herencia + +- Usar `inherit_id` + `xpath` específico en vez de redefinir la vista entera. +- `xpath` debe apuntar a un elemento único y estable: preferir `//field[@name='...']` o `//group[@name='...']` antes que índices de `child::`. +- Evitar `position="replace"` cuando `position="attributes"` o `position="after"/"before"/"inside"` alcanza. +- No duplicar grandes bloques de `arch`: heredar y sobreescribir lo mínimo necesario. + +## Campos referenciados + +- Todo `` debe existir en el modelo correspondiente (y ser accesible por el usuario). +- Campos usados en atributos como `invisible="..."`, `readonly="..."`, `required="..."` también deben estar declarados en la vista (si no, agregar con `invisible="1"`). + +## Atributos dinámicos (Odoo 17+) + +- `attrs="{'invisible': [...]}"` **deprecado**. Usar atributos directos: `invisible="field == 'done'"`, `readonly="state in ['done','cancel']"`, `required="type_id"`. +- Expresiones en atributos usan sintaxis Python sobre los campos disponibles del registro actual. +- En listas (``): para campos que nunca se muestran, usar `column_invisible="1"` en vez de `invisible="1"` (evita cargar valores innecesariamente). + +## `` vs `` (Odoo 19) + +- Odoo 19 usa `` en vez de `` como tag de lista. +- Atributos frecuentes: `editable="bottom"`, `multi_edit="1"`, `decoration-*`, `optional="show|hide"` en fields. +- Si el diff introduce `` en módulo v19 → marcar como cambio obligatorio a ``. + +## Kanban y QWeb + +- **Odoo 19+**: templates kanban usan `t-name="card"` (antes `t-name="kanban-box"`). +- `t-esc` deprecado → usar `t-out` para escribir valores (aplica a todas las versiones recientes). +- `t-options-widget` sólo sobre campos; no abusar. + +## Búsquedas y filtros + +- Filtros de búsqueda sobre campos no indexados en datasets grandes → sugerir `index=True` en el field o filtro alternativo. +- `` debe tener `name` único para poder heredarse. + +## Acciones y menús (cuando vengan en el mismo diff) + +- `ir.actions.act_window` debe declarar `res_model`; `view_mode` consistente con vistas existentes. +- Menús heredados con `parent_id` correcto; evitar duplicación de `sequence`. +- Nuevos menús deben tener permisos coherentes (grupo o reglas ACL). + +## Datos XML + +- `` nuevos deben tener `id` con convención `module__`. +- Usar `noupdate="1"` con cuidado: si más adelante cambia el contenido lógico, requiere script de migración forzando el update por `xml_id`. +- No mezclar datos de demo con datos funcionales (carpetas `data/` vs `demo/` y declaración en manifest). + +## Reportes QWeb + +- Templates deben heredar estilos base (`web.external_layout` o similar) en vez de duplicar CSS inline. +- `t-call` para layouts; `t-field` para renderizar valores con su widget; `t-out`/`t-esc` ya no es necesario si se usa `t-field`.