chore: clean up historical scripts, add MCP configurations, update agent rules

This commit is contained in:
2026-07-08 14:13:58 -06:00
parent a2908167e6
commit 52470a44cb
52 changed files with 2181 additions and 2328 deletions
+268 -1
View File
@@ -52,7 +52,7 @@
- **NUNCA** usar `print()`. Siempre usar `logging` con `_logger = logging.getLogger(__name__)`.
- Usar prefijos descriptivos en logs: `_logger.info("[Módulo] Mensaje")`.
- Siempre agregar docstrings a funciones y clases.
- Preferir código legible sobre código compacto.
### Despliegue de Cambios
1. **Cambios en archivos `.py` (modelos/lógica):** Requiere restart del contenedor.
@@ -88,3 +88,270 @@
## Conexión MCP Server PostgreSQL
El agente tiene acceso directo de solo lectura a la base de datos `hazard_new` a través del MCP Server configurado en `mcp_config.json`. Usar la herramienta `query` del servidor `odoo-postgres` para consultas SQL cuando sea necesario inspeccionar datos.
## Flujo Custom y Maquila (B2B)
- **Regla Crítica:** Los productos de las categorías `Insumos / Materia Prima` y `Producto Final Custom` pertenecen al flujo B2B/Maquila.
- Estos productos **NUNCA** deben enviarse ni sincronizarse con Shopify. Están tecnológicamente aislados de la plataforma de Retail.
- Cualquier script, sincronización o lógica de Odoo que interactúe con Shopify debe excluir explícitamente estas categorías.
---
## Arquitectura del Módulo `hazard_shopify`
### Modelos Custom (13 modelos)
| Modelo Técnico | Archivo | Descripción |
|---|---|---|
| `shopify.config` | `models/shopify_config.py` | Configuración central de la tienda Shopify. Contiene TODA la lógica de sincronización (~4500 líneas). |
| `shopify.sku.simulation.line` | `models/sku_simulation_line.py` | Líneas de simulación de SKU para mapeo Shopify ↔ Odoo. |
| `shopify.stock.queue` | `models/shopify_stock_queue.py` | Cola asíncrona para sincronización reactiva de inventario. |
| `shopify.cost.line` | `models/shopify_cost_line.py` | Líneas de sincronización de costos desde Shopify. |
| `shopify.publication` | `models/shopify_publication.py` | Canales de venta de Shopify (Online Store, POS, etc.). |
| `shopify.product.publication` | `models/shopify_product_publication.py` | Tabla pivote: estado de publicación de un producto por canal. |
| `shopify.location.mapping` | `models/shopify_config.py` (final) | Mapeo Sucursal Shopify ↔ Almacén/Ubicación Odoo. |
| `shopify.location.wizard` | `models/shopify_location_wizard.py` | Wizard para seleccionar sucursal de Shopify. |
| `shopify.refund.wizard` | `wizard/shopify_refund_wizard.py` | Asistente para reembolsos manuales hacia Shopify. |
### Modelos Heredados (overrides)
| Modelo | Archivo | Qué agrega |
|---|---|---|
| `product.template` | `models/product_template.py` | Campos `shopify_id`, `shopify_published`, `shopify_sync_status`, lógica de push a Shopify, canales de venta GraphQL, generación de barcodes. |
| `product.product` | `models/product_template.py` (L960+) | Campos **CRÍTICOS**: `shopify_variant_id` (llave de variante), `shopify_inventory_item_id` (llave de inventario). Auto-generación de EAN-13 al crear producto. |
| `sale.order` | `models/sale_order.py` | Campos `shopify_order_id`, `shopify_fulfillment_status`, `shopify_refund_status`, `shopify_shipping_title`. Lógica BOPIS, fulfillment, cancelación de pickings. |
| `stock.quant` | `models/stock_quant.py` | Override de `_update_available_quantity()` para encolar sincronización reactiva al cambiar stock. |
| `res.users` | `models/res_users.py` | Override de `_search()` para ocultar usuarios de soporte (`admin`, `@tesscorp.com.mx`). |
### Campos Shopify Críticos (NUNCA eliminar)
**En `product.product`:**
- `shopify_variant_id` — ID numérico de la variante en Shopify. Es la llave para push de SKU, barcodes y fulfillment.
- `shopify_inventory_item_id` — ID del ítem de inventario en Shopify. Es la llave para TODA la sincronización de stock.
**En `product.template`:**
- `shopify_id` — ID del producto (template) en Shopify.
- `shopify_published` — Flag booleano que indica si el producto existe en Shopify.
**En `sale.order`:**
- `shopify_order_id` — ID numérico del pedido en Shopify. Llave para duplicados y fulfillment.
- `shopify_shipping_title` — Título del método de envío. Se usa para detectar pedidos BOPIS (retiro en tienda).
---
## Flujos de Negocio del Conector
### Flujo 1: Importación de Pedidos (Shopify → Odoo)
```
Cron cada N min → cron_import_orders()
→ API REST: GET /orders.json?status=any&updated_at_min=...
→ Por cada pedido: _import_single_shopify_order(s_order)
→ Si ya existe: actualiza estado financiero, fulfillment, refunds, detecta ediciones
→ Si es nuevo: crea res.partner → crea sale.order → auto-confirma
→ Si POS o Fulfilled: auto-valida albarán de salida
→ Si tiene envío: agrega línea de producto ENVIO
→ Si pagado y auto_workflow_payment: publica factura + registra pago
→ Si fecha < cutoff: cancela albaranes (protección histórica)
→ Ejecuta _process_shopify_refunds_automation_logic()
```
**Reglas importantes:**
- El producto se busca por: 1° SKU (`default_code`), 2° `shopify_variant_id`, 3° nombre (`ilike`), 4° genérico `SHOPIFY-GENERIC`.
- Los impuestos se omiten (`tax_id: []`) porque Shopify ya los incluye en el precio.
- Los descuentos se calculan como porcentaje desde `discount_allocations`.
### Flujo 2: Sincronización de Inventario (Odoo → Shopify)
```
Odoo es la FUENTE DE VERDAD del stock.
Modo Reactivo (por evento):
stock.quant._update_available_quantity() → detecta cambio
→ Si producto tiene shopify_inventory_item_id → encola en shopify.stock.queue
→ Cron cada 5 min: process_queue_batch() → procesa 15 productos por lote
→ Llama a _sync_variants_stock_to_shopify()
Modo Cron (masivo periódico):
cron_sync_inventory_to_shopify() → todas las variantes con shopify_inventory_item_id
→ _sync_variants_stock_to_shopify(variants)
→ API REST: POST /inventory_levels/set.json por cada variante × ubicación
```
**Regla CRÍTICA:** Jamás invertir la dirección. El cron `cron_sync_inventory_from_shopify` está redirigido a `cron_sync_inventory_to_shopify` por seguridad operativa.
### Flujo 3: Fulfillment (Odoo → Shopify)
```
Desde el pedido en Odoo → botón "Enviar a Shopify"
→ action_notify_shopify_fulfillment()
→ Doble fallback:
1° Intento: Endpoint legacy POST /orders/{id}/fulfillments.json
2° Intento: Nuevo endpoint POST /fulfillments.json via fulfillment_orders
→ Escribe shopify_fulfillment_id y shopify_fulfillment_status en sale.order
```
### Flujo 4: BOPIS (Buy Online, Pickup In Store)
```
Detección automática por shopify_shipping_title:
Si contiene: OFICINA, RETIRO, PICKUP, SUCURSAL, TIENDA
→ Al confirmar pedido: _create_pickup_internal_transfer()
→ Crea traslado interno: Bodega ALMA → Oficina HRSGOLF
→ Reserva stock y alerta si hay faltantes
→ Al editar pedido en Shopify: _update_pickup_internal_transfer_qty()
→ Ajusta cantidades en traslados existentes o crea complementarios
```
### Flujo 5: Reembolsos y Devoluciones Automáticas
```
_process_shopify_refunds_automation_logic(sale_order, s_order)
→ Candado Sandbox: Si shopify_sandbox_mode=True, solo procesa SKUs con "PRUEBA"
→ Por cada refund no procesado:
A. Devolución de Stock: Crea stock.picking de entrada + auto-valida
B. Nota de Crédito: Crea account.move tipo out_refund en borrador
→ Registra refund_id en shopify_refund_ids_processed para idempotencia
```
**ADVERTENCIA:** Este flujo ya opera en producción. Cambios aquí impactan contabilidad real y stock físico.
### Flujo 6: Publicación de Productos (Odoo → Shopify)
```
Desde ficha de producto → botón "Publicar en Shopify"
→ action_push_to_shopify()
→ Si shopify_id existe: PUT /products/{id}.json (actualización)
→ Si no existe: POST /products.json (creación)
→ Sincroniza canales de venta vía API GraphQL (publishablePublish mutation)
→ Sube imagen si tiene (POST /products/{id}/images.json)
```
---
## Sistema de Crons (7 Acciones Planificadas)
| # | XML ID | Propósito | Frecuencia Default | Activo | Método |
|---|---|---|---|---|---|
| 1 | `ir_cron_shopify_import_orders` | Importar pedidos Shopify → Odoo | 5 min | ✅ | `cron_import_orders()` |
| 2 | `ir_cron_shopify_sync_inventory` | Stock Odoo → Shopify (masivo) | 12 horas | ❌ | `cron_sync_inventory_to_shopify()` |
| 3 | `ir_cron_shopify_refresh_tokens` | Renovar tokens OAuth (24h Shopify) | 20 horas | ✅ | `cron_refresh_tokens()` |
| 4 | `ir_cron_shopify_download_images` | Descargar imágenes (manual/batch) | 1 min | ❌ | `_cron_download_images_batch()` |
| 5 | `ir_cron_shopify_auto_image_sync` | Auto-descargar imágenes nuevas | 2 horas | ✅ | `cron_auto_download_images()` |
| 6 | `ir_cron_shopify_process_stock_queue` | Procesar cola de stock reactiva | 5 min | ✅ | `process_queue_batch()` |
| 7 | `ir_cron_shopify_clean_stock_queue` | Limpiar cola completada (+7 días) | 1 día | ✅ | `clean_done_queue()` |
**Reglas sobre Crons:**
- Los crons 1, 2 y 5 se actualizan dinámicamente desde el `write()` de `shopify.config` cuando el usuario cambia la frecuencia en la interfaz.
- **NUNCA** desactivar manualmente los crons 3 (tokens) o 6 (cola de stock) — rompe la operación.
- El cron 4 se activa/desactiva automáticamente (botón → se activa, termina → se desactiva).
---
## API de Shopify
### Configuración Técnica
- **Versión API REST:** `2024-01`
- **Versión API GraphQL:** `2024-01`
- **Autenticación:** Token de acceso (`shpat_...`) en header `X-Shopify-Access-Token`
- **Renovación de Token:** OAuth con `client_id` y `client_secret`, renovación automática cada 20h vía cron.
- **Rate Limiting:** `time.sleep(0.5)` entre requests (2 req/s). Reintentos automáticos en HTTP 429 (espera 5s, 3 intentos).
### Endpoints Usados
| Método | Endpoint | Propósito |
|---|---|---|
| GET | `/orders.json` | Importar pedidos |
| GET | `/products.json` | Importar catálogo |
| POST/PUT | `/products/{id}.json` | Crear/actualizar productos |
| POST | `/products/{id}/images.json` | Subir imágenes |
| POST | `/inventory_levels/set.json` | Sincronizar stock |
| GET | `/locations.json` | Obtener sucursales |
| POST | `/orders/{id}/fulfillments.json` | Fulfillment legacy |
| GET | `/orders/{id}/fulfillment_orders.json` | Fulfillment orders (nuevo) |
| POST | `/fulfillments.json` | Fulfillment nuevo API |
| POST | `/orders/{id}/refunds/calculate.json` | Calcular reembolso |
| POST | `/orders/{id}/refunds.json` | Ejecutar reembolso |
| PUT | `/variants/{id}.json` | Actualizar variante (SKU, barcode) |
| GET | `/inventory_levels.json` | Consultar niveles de stock |
| POST (GraphQL) | `/graphql.json` | Costos, canales de venta, publicaciones |
---
## Grupos de Seguridad del Módulo
Definidos en `security/hazard_security.xml`. Categoría: `Hazard / HRSGOLF`.
| XML ID | Nombre | Permisos | Hereda de |
|---|---|---|---|
| `group_hazard_designer` | Diseño | Crea productos, sube fotos. Sin precios ni Shopify. | `base.group_user` |
| `group_hazard_warehouse` | Bodega | Stock y tracking. Sin precios. | `stock.group_stock_user` |
| `group_hazard_sales` | Ventas B2B | Cotizaciones/pedidos. Precios solo lectura. | `sales_team.group_sale_salesman` |
| `group_hazard_finance` | Admin/Finanzas | Control total: precios, catálogo, Shopify. | `sales_team.group_sale_salesman_all_leads` |
**Regla:** Solo `group_hazard_finance` tiene acceso de escritura a `shopify.config`. Los demás tienen solo lectura o sin acceso.
---
## Cola Asíncrona de Stock (`shopify.stock.queue`)
Mecanismo reactivo para sincronizar inventario en tiempo semi-real:
1. **Trigger:** `stock.quant._update_available_quantity()` detecta cualquier cambio de stock en Odoo.
2. **Filtro:** Solo encola productos con `shopify_inventory_item_id` (vinculados a Shopify).
3. **Encolado:** Crea registro en `shopify.stock.queue` con estado `pending` (evita duplicados).
4. **Procesamiento:** Cron cada 5 min → `process_queue_batch()` → procesa 15 productos por lote.
5. **Limpieza:** Cron diario → `clean_done_queue()` → elimina registros `done` con más de 7 días.
**Condición:** Solo procesa si `auto_sync_products = True` en la configuración de Shopify.
---
## Lógica de SKU (`sku_logic.py`)
Formato estándar: `HRS-{TIPO}-{MODELO}-{GENERO}-{COLOR}-{TALLA}`
Ejemplo: `HRS-POL-CLUB-M-BLK-L` = Polo Club, Hombre, Negro, Talla L
### Diccionarios de Mapeo (hardcoded)
- **CATEGORIES:** `hat→HAT`, `polo→POL`, `hoodie→HOD`, `bag→BAG`, `glove→GLV`, `towel→TWL`, etc.
- **GENDERS:** `men→M`, `women→W`, `kids→K`. Default `U` (unisex) para accesorios.
- **COLORS:** `black→BLK`, `white→WHT`, `green→GRN`, `blue→BLU`, `red→RED`, etc.
**Regla:** NUNCA modificar estos diccionarios sin aprobación explícita del cliente. Un cambio rompe la consistencia de los SKUs existentes.
---
## Reglas de Seguridad Adicionales
### Override de Usuarios (`res_users.py`)
- El modelo `res.users` oculta los usuarios de soporte (`admin` y `@tesscorp.com.mx`) de los usuarios del cliente.
- **NUNCA eliminar este override.** Los usuarios del cliente no deben ver las cuentas de desarrollo.
### Sandbox de Reembolsos
- El campo `shopify_sandbox_mode` en `shopify.config` protege la base de datos de producción.
- Cuando está activo (por defecto), la automatización de reembolsos solo procesa pedidos con SKUs que contienen la palabra `PRUEBA`.
- **NUNCA desactivar este modo sin autorización explícita y pruebas exhaustivas.**
### Producto Genérico de Envío
- El producto con SKU `ENVIO` (tipo servicio) se usa para las líneas de costo de envío importadas de Shopify.
- Se crea automáticamente si no existe. **No eliminar.**
### Cliente Genérico POS
- El contacto `Cliente POS Shopify` se reutiliza para ventas POS sin datos de cliente.
- **No eliminar ni renombrar.** Evita crear contactos basura por cada venta POS.
### Log Persistente de Crons
- Ruta dentro del contenedor: `/var/lib/odoo/shopify_cron.log`
- Volumen Docker: `odoo-web-data-hazard-new`
- Máximo 5 MB con rotación automática.
- Para consultar: `docker exec odoo-16-hazard-new cat /var/lib/odoo/shopify_cron.log`
### Fecha de Corte de Importación
- El campo `shopify_import_cutoff_date` en `shopify.config` protege el inventario de pedidos históricos.
- Pedidos creados ANTES de esta fecha se importan pero sus albaranes de salida se cancelan automáticamente.
- **No modificar esta fecha sin entender el impacto en el stock.**
---
## Autorización de Borrado
- NUNCA elimines archivos, directorios o recursos proactivamente sin la autorización explícita y directa del usuario.
- Si el usuario pregunta "¿Puedo borrar esto?", "¿Es seguro borrar esto?" o similar, limítate EXCLUSIVAMENTE a responder a su pregunta proporcionando la información.
- NO asumas que el usuario quiere que tú ejecutes el comando de borrado. Espera a que el usuario diga explícitamente "Bórralo", "Por favor elimínalo tú", o que ejecute la acción él mismo.
## Limpieza y Scripts Temporales
- NUNCA crees scripts temporales de Python (o bash) directamente en la carpeta raíz del proyecto.
- Si necesitas crear un script (`.py`, `.sh`, etc.) para una tarea, colócalo SIEMPRE dentro del directorio `scripts/`. Crea este directorio si no existe.