18 KiB
Reglas del Proyecto — Hazard (Odoo 16)
Información General
- Empresa: Hazard (marca de ropa urbana / streetwear)
- Plataforma ERP: Odoo 16 Community con módulos Enterprise
- Ecommerce: Shopify (sincronizado con Odoo vía módulo custom
hazard_shopify) - Servidor: VPS en Plesk bajo el dominio
tesscorp.com.mx - Idioma de comunicación: Siempre responder en español
Arquitectura Docker
Contenedores
| Contenedor | Imagen | Puerto Externo | Puerto Interno |
|---|---|---|---|
odoo-16-hazard-new |
hazard-odoo-odoo (custom, basada en odoo:16) |
16001 |
8069 |
odoo-16-db-hazard-new |
postgres:17 |
No expuesto | 5432 |
Volúmenes y Rutas
- Módulos Enterprise:
./Addons/enterprise→/mnt/enterprise(dentro del contenedor) - Módulos Custom:
./Addons/custom→/mnt/extra-addons(dentro del contenedor) - Configuración Odoo:
./Conf/odoo.conf→/etc/odoo/odoo.conf - Docker Compose:
/var/www/vhosts/tesscorp.com.mx/hazard.tesscorp.com.mx/docker-compose.yml - IP interna del contenedor DB:
172.18.0.2(usada por MCP Server PostgreSQL)
Base de Datos
- Base de datos principal (producción):
hazard_new - Usuario DB:
odoo - Contraseña DB:
odoo
Credenciales Odoo (XML-RPC)
- URL:
http://localhost:16001 - Base de datos:
hazard_new - Usuario admin:
admin - Contraseña admin:
admin
Módulos Custom Instalados
| Módulo | Estado | Ubicación |
|---|---|---|
hazard_shopify |
✅ Instalado | Addons/custom/hazard_shopify |
hazard_inventory_report |
✅ Instalado | Addons/custom/hazard_inventory_report |
hazard_website |
❌ No instalado | Addons/custom/hazard_website |
Otros Módulos de Terceros
bi_sql_editor— Editor SQL en interfaz Odooquery_deluxe— Consultas avanzadas desde Odoosql_request_abstract— Abstracción para consultas SQLl10n_mx_sat_sync_itadmin_ee— Sincronización SAT México
Reglas de Desarrollo Obligatorias
Python
- NUNCA usar
print(). Siempre usarloggingcon_logger = logging.getLogger(__name__). - Usar prefijos descriptivos en logs:
_logger.info("[Módulo] Mensaje"). - Siempre agregar docstrings a funciones y clases.
Despliegue de Cambios
- Cambios en archivos
.py(modelos/lógica): Requiere restart del contenedor.docker restart odoo-16-hazard-new - Cambios en estructura de tablas (fields nuevos): Requiere restart + actualización del módulo.
docker restart odoo-16-hazard-new # Luego ejecutar el script de actualización: python3 update_module.py - Cambios solo en XML/vistas/reportes: Solo requiere actualización del módulo (sin restart).
Seguridad y Respaldos
- NUNCA ejecutar
UPDATEoDELETEmasivos sin respaldo previo. - Script de respaldo:
backup_hazard.sh(respalda DB + código, retención 15 días). - Los backups se suben a Google Drive automáticamente vía Plesk.
Código de Barras
- Formato EAN13 estandarizado con estructura
20000...(ej.2000010012749). - NUNCA crear scripts que alteren la secuencia de códigos de barras.
Archivos que NO se deben modificar
- Módulos en
/mnt/enterprise(módulos oficiales de Odoo Enterprise). - Solo modificar código en
/mnt/extra-addons(módulos custom).
Scripts Útiles del Proyecto
| Script | Propósito | Comando |
|---|---|---|
backup_hazard.sh |
Respaldo completo (DB + código) | bash backup_hazard.sh |
update_module.py |
Actualizar módulo hazard_shopify via XML-RPC | python3 update_module.py |
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 PrimayProducto Final Custompertenecen 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éricoSHOPIFY-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()deshopify.configcuando 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 headerX-Shopify-Access-Token - Renovación de Token: OAuth con
client_idyclient_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:
- Trigger:
stock.quant._update_available_quantity()detecta cualquier cambio de stock en Odoo. - Filtro: Solo encola productos con
shopify_inventory_item_id(vinculados a Shopify). - Encolado: Crea registro en
shopify.stock.queuecon estadopending(evita duplicados). - Procesamiento: Cron cada 5 min →
process_queue_batch()→ procesa 15 productos por lote. - Limpieza: Cron diario →
clean_done_queue()→ elimina registrosdonecon 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. DefaultU(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.usersoculta los usuarios de soporte (adminy@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_modeenshopify.configprotege 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 Shopifyse 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_dateenshopify.configprotege 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 directorioscripts/. Crea este directorio si no existe.