Aronlight A2 · Quote Generation Module

Resumen de las sesiones de investigación

Análisis de seis plantillas de correo (RFQ) para evaluar la viabilidad técnica del mapeo automático catálogo ↔ solicitud de cliente.

6 casos analizados Stack objetivo: Django + Vue 3 Odoo 18 · XML-RPC Actualizado 2026-05-28
6
casos RFQ procesados end-to-end
4
skills de proyecto creadas
37
preguntas abiertas para el equipo
~1.750
unidades de luminaria mapeadas
43
fichas de investigación de producto
4.324
productos de catálogo Odoo cacheados

1Documentos que hemos generado

El trabajo dejó un lab de feasibility en marcha: pipeline de código, investigación caso a caso y un banco de preguntas estructurado para el equipo de Aronlight.

Documentos núcleo

Investigación por caso — docs/email_samples/<caso>/

Cada uno de los 6 correos quedó decodificado, clasificado y documentado con tabla de posiciones, campos clave para mapping y fichas de producto individuales (43 fichas .md + 26 imágenes de producto descargadas + 5 PDFs de orçamento).

CasoCompetidorVol.Tipo inputHallazgo central
01 Centro de Saúde de ChavesKATOA + CLIMAR245 uD (email)3 gaps críticos de catálogo (downlight 90×90 GU10, lineal encastrado slim)
02 AlpLuz / PhilipsPhilips (Signify)231 uA/CPrimer competidor con códigos exactos; orçamento expande RFQ 2,4×
03 APPACDM Sabrosa— (ninguno)DCliente especifica productos Aronlight por nombre, sin código; cliente ausente de staging
04 HAM Housing A. Macedomarca «OH» desconocida~700 uE (caderno)6 de 12 ítems sin equivalente; marca «OH» no identificable tras 15+ búsquedas
05 IP BragançaSoneres / Lucens / SchréderLote 1+2E (concurso)Concurso público; MQT en link externo no descargable; ítems fuera de scope
06 San Juan del PuertoiGuzzini (100%)522 uBOQ españolPremium europeo; DALI obligatorio en 13/16; primer caso Aronlight ES (company_id=2)

Pipeline de código Python

  • 01_catalog_access.pydata/catalog.json
  • 02_categorization.py → esquema de categorías
  • 03_client_matching.pydata/partners.json
  • 04_input_parser.py → parsing email/Excel/imagen
  • 05_sku_matching.py → registro de matches por tiers
  • 06_order_matching.py → match RFQ ↔ orçamento Odoo
  • odoo_client.py → conexión XML-RPC reutilizable
  • scripts/fetch_images.py → descarga de imágenes de producto

Salidas order_match_*.json generadas para los casos 02–06.

2Aprendizajes que hemos tenido

El valor real de las seis sesiones no fue cotizar seis correos, sino descubrir las dimensiones en las que el problema varía. Cada caso reveló una arista nueva.

Las solicitudes no son homogéneas: hay (al menos) 5 tipos de input

Excel estructurado (A), imagen/scan (B), lista de referencias de competidor (C), email conversacional (D) y concurso público / caderno de encargos (E). El caso 06 añadió un sexto formato: el BOQ de medición español. Cada tipo exige una estrategia de extracción distinta — un parser único no sirve.

Un mismo email mezcla varias clases de ítem: hace falta pre-clasificar por línea

El caso 04 (HAM) combina en un solo RFQ tipos funcionales genéricos sin marca («Fita LED», «Foco Embutido» → matching por categoría) y referencias de marca con código («OH SCUBA IP65 LED 10W» → matching por nombre exacto/fuzzy). Una sola estrategia no sirve: el A2 necesita una etapa de pre-clasificación por ítem antes de elegir cómo matchear cada línea. Y cuando una etiqueta funcional es ambigua y sin specs (Tipo F «Ponto de Luz no Tecto» vs. Tipo G «Foco Embutido», 278 uds entre ambas), el sistema debe preguntar o proponer con nota — nunca decidir en silencio.

El cliente que pide no siempre es el cliente final

Instaladores y distribuidores (Openline, Macinfor, Pedro Moreira) solicitan en nombre del proyecto. El orçamento en Odoo queda registrado bajo el intermediario, no bajo la obra. El matching de cliente debe buscar por el contacto que envía, no por el nombre del proyecto.

El email exacto es el identificador más fiable; el nombre fuzzy, frágil

AlpLuz y Macinfor se localizaron por coincidencia exacta de email. El NIF/VAT es el mejor fallback para empresas. Donde sólo había nombre, el matching fuzzy no superó el umbral — y clientes reales (APPACDM, Openline) ni siquiera existían en staging.

El staging de Odoo no es fiable como verdad de datos

1.874 de 4.324 productos con categ_id = "All"; customer_rank=0 en clientes con orçamentos reales (CHELTS); specs técnicos embebidos como texto libre en el nombre en lugar de atributos estructurados. Filtrar por customer_rank>0 o por categoría perdería matches válidos — de hecho ese filtro fue un bug real: /match-orcamento no encontró a CHELTS hasta que se quitó el filtro de rank de fetch_partners.

El gap de catálogo es la regla, no la excepción

Caso 01: 3 gaps críticos (23% del volumen en una sola línea). Caso 04: 6 de 12 ítems sin equivalente — la marca «OH» bloquea el 54% de las unidades del RFQ y exige una tabla de mapeo código-abreviado → marca comercial para resolver prefijos de Cadernos de Encargos. Hay además un gap distinto: categorías enteras ausentes del catálogo (Espelho Retroiluminado, 32 uds) que no son un fallo de matching sino producto no representado. El sistema debe tener una política definida ante cada tipo de gap — no puede asumir cobertura total.

El matching por potencia exacta NO es lo que hace el comercial

Confirmado en dos competidores independientes: caso 06 (iGuzzini) desvió la potencia hasta +108%, y caso 02 (Philips) de +11% a +140% (TITAN 36 W por un Philips de 15 W). El criterio real del comercial es categoría + dimensiones + flujo luminoso + IP, con la potencia como tolerancia amplia. Un motor que matchee por W exacta producirá malos resultados.

El orçamento es modular: luminaria + driver + accesorio + emergencia

Cada posición genera 2-3 líneas en Odoo (separadores line_note + etiqueta de posición + producto). Dentro de ese bloque: el driver DALI se elige por potencia, no por familia (un mismo driver sirve a 4 productos distintos); el accesorio decorativo (aro/«Espelho») es línea separada con la misma cantidad — pero por posición, no por familia: en el caso 02 la misma familia RUMU lleva aro en IL05 y no en IL06 (depende del «deco ring» pedido); y las emergencias usan prefijo ILEM-, distinto de los ILAR- de luminaria. El A2 debe conocer estas reglas de expansión y aplicar el accesorio según la combinación pedida, no a ciegas por familia.

El matching por «firma de cantidades» funciona — pero hay que detectar la versión viva

Un RFQ de 7 posiciones se convirtió en un orçamento de 17 líneas (×2,4). En AlpLuz aparecieron 3 versiones con 100% de solapamiento y totales distintos (revisiones de precio): la última es la viva. Además hay que distinguir orçamento «Enviado» de pedido confirmado (state=sale) — el pedido real puede tener menos ítems tras negociación. Sin esto, el sistema propondría duplicados.

Matiz del caso 04: el scoring por firma de cantidades dio 10/12 en lugar de 12/12 porque la Fita LED venía en metros (100 m) pero en Odoo está en rollos de 5 m (qty=20). Comparar enteros sin normalizar la unidad de medida (m → rollo, m² → ud.) produce falsos negativos.

«Sin equivalente» es un output válido, explícito en Odoo

El comercial escribe literalmente "sem equivalencia" en una línea cuando no hay match (Le Perroquet, caso 06), y cuando falta un SKU estándar crea un producto ad-hoc con código [0000] (CLOVER RECESSED, IP65 rectangular). El A2 debe poder emitir «sin equivalente» en lugar de forzar un falso match.

Cuando las specs no hacen match exacto, el comercial propone dos opciones

En el caso 02, para IL04 (Philips IP65) el comercial ofreció Fisher IP54 como principal (mejor formato, más caro) y Noa IP65 como alternativa explícita (marcada con un line_note «alternativa» en Odoo). Dos lecturas: (1) un downgrade de IP es aceptable si el formato encaja — el match exacto de IP se prioriza pero no es eliminatorio; (2) el A2 debe poder generar un par principal + alternativa cuando hay dos candidatos plausibles con specs distintos, no forzar una única respuesta.

Hay ítems que NUNCA deben automatizarse

Luminarias de diseño icónico (Le Perroquet de Renzo Piano), proyectores de campo de fútbol en torres de 20 m, y concursos con requisito de equivalencia luminotécnica probada. El sistema debe detectarlos y rutearlos a revisión humana, no proponer un equivalente técnico.

El scope es multi-empresa

Casos 01–05 → Aronlight PT (company_id=3); caso 06 → Aronlight ES (company_id=2). La búsqueda cross-company genera falsos positivos; restringir por país puede perder matches si el comercial creó el pedido en la entidad equivocada. Decisión de diseño aún abierta (Q-037).

Principio de diseño que emergió: human-in-the-loop con 4 gates

GateCondición de paradaAcción
1Match de cliente < 75%Flag, revisión humana
2Cualquier ítem unclearNunca auto-crear línea de pedido
3Match de SKU < 90%Mostrar sugerencia, exigir confirmación
4Orden finalAprobación humana antes de escribir en Odoo

3Otras cosas derivadas del proceso

Lo más valioso a largo plazo: el trabajo de los seis casos cristalizó en capacidades reutilizables. Todas las skills del proyecto nacieron de necesidades reales detectadas al procesar los correos.

Las 4 skills de proyecto .claude/skills/

El patrón meta que se derivó

El proyecto se volvió auto-documentante y auto-capacitante: cada caso nuevo (a) genera documentación de investigación, (b) alimenta el banco de preguntas, y (c) cuando un paso se repite, se promueve a skill. Procesar el caso 07 será notablemente más rápido que el 01 — no por el código, sino por las capacidades acumuladas.

Otros derivados concretos

Qué falta por hacer