Análisis de seis plantillas de correo (RFQ) para evaluar la viabilidad técnica del mapeo automático catálogo ↔ solicitud de cliente.
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.
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).
| Caso | Competidor | Vol. | Tipo input | Hallazgo central |
|---|---|---|---|---|
| 01 Centro de Saúde de Chaves | KATOA + CLIMAR | 245 u | D (email) | 3 gaps críticos de catálogo (downlight 90×90 GU10, lineal encastrado slim) |
| 02 AlpLuz / Philips | Philips (Signify) | 231 u | A/C | Primer competidor con códigos exactos; orçamento expande RFQ 2,4× |
| 03 APPACDM Sabrosa | — (ninguno) | — | D | Cliente especifica productos Aronlight por nombre, sin código; cliente ausente de staging |
| 04 HAM Housing A. Macedo | marca «OH» desconocida | ~700 u | E (caderno) | 6 de 12 ítems sin equivalente; marca «OH» no identificable tras 15+ búsquedas |
| 05 IP Bragança | Soneres / Lucens / Schréder | Lote 1+2 | E (concurso) | Concurso público; MQT en link externo no descargable; ítems fuera de scope |
| 06 San Juan del Puerto | iGuzzini (100%) | 522 u | BOQ español | Premium europeo; DALI obligatorio en 13/16; primer caso Aronlight ES (company_id=2) |
01_catalog_access.py → data/catalog.json02_categorization.py → esquema de categorías03_client_matching.py → data/partners.json04_input_parser.py → parsing email/Excel/imagen05_sku_matching.py → registro de matches por tiers06_order_matching.py → match RFQ ↔ orçamento Odooodoo_client.py → conexión XML-RPC reutilizablescripts/fetch_images.py → descarga de imágenes de productoSalidas order_match_*.json generadas para los casos 02–06.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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).
| Gate | Condición de parada | Acción |
|---|---|---|
| 1 | Match de cliente < 75% | Flag, revisión humana |
| 2 | Cualquier ítem unclear | Nunca auto-crear línea de pedido |
| 3 | Match de SKU < 90% | Mostrar sugerencia, exigir confirmación |
| 4 | Orden final | Aprobación humana antes de escribir en Odoo |
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.
.eml, inventaría adjuntos, clasifica el tipo de input y decide qué skills ya cubren los siguientes pasos vs. qué es trabajo nuevo. Nació de repetir el mismo arranque manual seis veces.res.partner + ranking de sale.orders por solapamiento de cantidades. Codifica los aprendizajes de cliente-intermediario y firma de cantidades — e incorporó el fix del bug de customer_rank descubierto en el caso 04.open-questions.md con el filtro obligatorio de relevancia. Convirtió el banco de 37 preguntas en un proceso disciplinado en lugar de notas sueltas.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.
unclear.customer_rank poco fiable, cobertura parcial del staging, scope multi-empresa.01–05 contra el staging real y registrar las salidas (acceso a catálogo, matching de cliente, parsing de input, matching de SKU).