Plan de implementación — Backend¶
Base: 04-diseno-oms.md. Hallazgos: 02, 03.
Datos de prueba en dev: ítems TESTBOM-*, BOM ProductTrees('TESTBOM-PC'), cliente C32857 (02 §8, 03 §5).
Estado (2026-07-17): B1–B5 IMPLEMENTADAS y verificadas. Build ✅, lint de los archivos nuevos ✅, 21 tests unitarios nuevos ✅ (las 3 fallas de
orders.service.specsobre autorización son preexistentes, igual que las 13 deitems.service.spec— les falta el mock de CacheService; no son de esta feature). E2E real contra dev con el combo publicadoPCM-12160: verifynot_found→ sync (creó componente06558, padre06559, BOM) → verifyok→POST Orderscon línea única del padre → explosión correcta (padreiSalesTreeprecio 278.000, componente ×2 en 0,TaxCodepropagado, sin error-10) → pedido 33266 cancelado. Ver "Decisiones in situ" al final.
Etapas en orden de dependencia; cada una compila, pasa lint/tests y es verificable por sí sola.
Etapa B1 — Cliente Algolia de productos¶
Hoy el backend solo consulta el índice de clientes. Se necesita leer el índice de
productos (product_items_view_index / _dev) para que verify/sync usen la fuente de
la verdad sin confiar en el payload del cliente.
backend/src/services/algolia/: agregar acceso search-only al índice de productos. Método principal:findProductBySku(sku)→ busca porsku_sapcon fallback asku(mismo criterio quefrontend/src/utils/algolia/fetchProductByLine.ts).- Config: nombre del índice por env (
ALGOLIA_PRODUCTS_INDEX), siguiendo el patrón deindexing.config.ts. Credenciales search-only ya existentes enalgolia.client.ts. - Tipado del hit según el contrato de 04 §3 (
product_type,components[],price, …).
Verificación: unit test del servicio + llamada real en dev buscando un SKU conocido.
Etapa B2 — Extensión de itemsService.create()¶
items.service.ts:48 / item.mapper.ts:131. Sin romper el uso actual:
- Aceptar overrides de defaults (para el padre:
InventoryItem: 'tNO'). - Soportar
ItemWarehouseInfoCollectionen el payload — siempre enviarlo en las altas de esta funcionalidad (03 §2: sin almacén asignado, el pedido del combo falla conInternal error (-10); no depender de la parametrización de empresa). - Se mantiene: Series 79 (ItemCode autogenerado), SKU externo en
SupplierCatalogNo,ItemPriceslista 1.
Verificación: unit tests del mapper/payload.
Etapa B3 — Endpoints composite (verify / sync)¶
Submódulo en modules/items (p. ej. composite/): controller + service + DTOs.
POST /items/composite/verify{ sku }→ contrato de 04 §4: resuelve la definición en Algolia (B1), resuelve padre y componentes en SAP (ItemCode = sku_sap→ fallbackSupplierCatalogNo = sku), compara receta vsProductTreeLinespor set (ItemCode, Quantity), respondeok | not_found | outdated+diff+componentsToCreate.POST /items/composite/sync{ sku, warehouseCode }→ orquesta: componentes faltantes (B2) → padre (B2,tNO) →POST ProductTreesoPATCHcon lista completa- header
B1S-ReplaceCollectionsOnPatch: true(03 §1) → verify final como respuesta. HideBOMComponentsInPrintout: 'tNO'en BOMs nuevos.- Idempotente: tolerar
-2035(ya existe) en cada POST y continuar; carreras entre vendedores terminan en el mismo estado. warehouseCode: lo manda el frontend (sucursal del pedido) — decisión in situ pendiente (04 §8); mientras tanto parámetro explícito.- Precio de componentes nuevos: opcionalmente buscar cada
skuen Algolia para cargarItemPrices; si no se encuentra, crear con precio 0 (no afecta la receta ni el combo).
Verificación: unit tests del diff; prueba manual completa contra dev vía Swagger:
combo nuevo inventado sobre TESTBOM-* + TESTBOM-SSD como componente a crear/agregar.
Etapa B4 — Pedidos con combos¶
orders.service.ts / interfaces / DTOs:
SapOrderLineData(+ mapeo de respuesta): exponerTreeTypepor línea (iSalesTree/iIngredient/iNotATree) para que la UI arme la jerarquía.processOrderLines()(línea ~66):- Ítem resuelto con
TreeType: iSalesTree→ línea padre de combo: pasa como línea única (los componentes NUNCA se envían — SAP los explota; enviarlos los duplica, 02 §2) y se le fuerzaTaxCodeexplícito (IVA del combo; 02 §5 y 03 §3) junto conPriceAfterVAT. - Ítem inexistente en SAP cuyo hit de Algolia es
product_type: composite→ 409 con mensaje accionable ("verificar/crear el combo primero" — flujo verify/sync). El fallback actual de auto-creación silenciosa NO aplica a compuestos. create()(mapeo de líneas, línea ~255): agregarTaxCodeal payload (solo presente en líneas padre).updateOrder()(línea ~359): si el pedido en SAP contiene alguna línea conTreeType !== 'iNotATree'→ 409 ("los pedidos con productos compuestos no se editan; cancelar y recrear"). Decisión de negocio, 04 §1.
Verificación: unit tests (guard de update, procesamiento de línea padre) + manual en
dev: pedido con TESTBOM-PC desde Swagger → 6 líneas, TaxCode e importes correctos;
intento de PATCH → 409.
Etapa B5 — Facturación (defensivo, opcional)¶
invoices.service.ts → validateDocumentLines() / buildSapPayload() (~612): si las
líneas referencian un pedido que contiene iSalesTree, validar que estén referenciadas
todas las líneas de ese pedido (02 §7: referenciar solo el padre falla en SAP con
error poco claro; mejor 400 propio con mensaje explícito). Recordar el caso stock 0:
la factura falla con negative inventory hasta que haya recepción de mercadería (03 §4)
— no se maneja en backend, solo se documenta para soporte.
Verificación: manual contra dev — factura del pedido de prueba de B4 (con stock) y factura parcial → 400 propio.
Etapa B6 — Cierre¶
npm run lint,npm run test, build.- Swagger: revisar documentación de los endpoints nuevos (
/api-docs). - Actualizar estos docs con lo aprendido (decisiones in situ de 04 §8 que se hayan tomado: precio en oferta, almacén).
- Coordinar despliegue con el frontend (06-requerimiento-frontend.md):
el front depende de verify/sync y del
TreeTypeen el GET del pedido.
Implementación realizada (2026-07-17)¶
Archivos nuevos:
| Archivo | Etapa | Qué hace |
|---|---|---|
src/config/products-search.config.ts |
B1 | Config ALGOLIA_PRODUCTS_APP_ID/SEARCH_KEY/INDEX (opcionales al boot; sin ellas los endpoints composite responden 503) |
src/services/algolia/products-search.service.ts (+module, +interfaz del hit, +spec) |
B1 | findProductBySku(sku) search-only contra el índice del integrador |
src/modules/items/composite/ (controller, service, dtos, interfaces, recipe-diff.ts + spec) |
B3 | POST /items/composite/verify y /sync |
Archivos modificados:
| Archivo | Etapa | Cambio |
|---|---|---|
src/app.module.ts |
B1 | Registra el schema de validación nuevo |
backend/.env |
B1 | Variables ALGOLIA_PRODUCTS_* (espejo de las NEXT_PUBLIC_ALGOLIA_* del frontend) |
items/mappers/item.mapper.ts + items.service.ts |
B2 | create(dto, {sapOverrides, warehouseCodes}) → ItemWarehouseInfoCollection explícito |
items/dtos/ItemDto.ts + interfaces/sap-item-data.interface.ts |
B4 | Expone treeType del ítem |
items/items.module.ts |
B3 | Registra composite + importa ProductsSearchModule |
orders/orders.service.ts |
B4 | Línea padre iSalesTree → TaxCode forzado (salesIva ∥ IVA_10); 409 al alta silenciosa de combo publicado; 409 al editar pedidos con combos |
orders/dtos/CreateOrderLineDto.ts + interfaces/sap-order-data.interface.ts |
B4 | TaxCode en la línea; TreeType en la respuesta (la UI arma la jerarquía) |
orders/orders.module.ts |
B4 | Importa ProductsSearchModule |
invoices/invoices.service.ts |
B5 | validateCompositeKitLines: 400 propio si no se referencian todas las líneas del kit |
Decisiones in situ (cierra 04 §8)¶
- Precio del combo: se usa
hit.pricedel índice tal cual (va comoPriceAfterVATde la línea padre; también como precio de lista 1 del padre al crearlo). El caso oferta (on_sale/special_price) sigue pendiente de confirmar con el integrador — sipriceno reflejara la oferta, el único cambio es encreateParentItem/frontend. - Almacén: parámetro explícito
warehouseCodeen/sync(lo manda el frontend con la sucursal del pedido). Se usa para: ítems nuevos (ItemWarehouseInfoCollection), cabecera y líneas del BOM. findProductBySkuusafilters:exactos por facet (sku_sap:"…"con prioridad,sku:"…"como fallback). Historial: hasta 2026-07-17sku/sku_sapNO eran facets (el filtro devolvía vacío) y se resolvía por query + match en memoria; el 2026-07-20 se agregaron como facets de solo filtro y se migró al filtro directo (verificado contra el índice). Se mantiene la desambiguación: el índice tiene registros duplicados por SKU (la vista composite y un registro simple), se prefiere el que declaraproduct_type.- TaxCode del combo:
salesIvadel ítem con fallbackIVA_10(quirk dev:ArTaxCodeno persiste, 03 §3).
Datos creados en dev por el E2E¶
06558 (componente, SupplierCatalogNo 08969), 06559 (padre del combo
PCM-12160, no inventariable) + su ProductTree; pedido 33266 cancelado.
Pendiente (no bloquea el backend)¶
- Confirmar con el integrador el caso
on_sale(decisión 1). - Frontend: 06-requerimiento-frontend.md.
- Los specs preexistentes rotos (autorización en orders, CacheService en items) — deuda ajena a esta feature.
Propuesta pendiente: optimizar el verify (análisis 2026-07-17)¶
El verify actual es lento porque resuelve cada componente publicado contra
Items (1–2 GETs full-entity por componente, sin $select): un combo de 10
componentes genera ~12–22 llamadas al SL que, al viajar por la misma sesión B1,
se encolan del lado del servidor → varios segundos por verificación.
Rediseño propuesto (sin cambiar el contrato del endpoint):
- Algolia: definición publicada (1 llamada, fuera de SAP).
GET ProductTrees('sku_sap')directo — el TreeCode ES el ItemCode del padre; solo si da 404 (o no haysku_sap) se consultaItemscon$selectmínimo para distinguir "ítem sin BOM" / "no existe" / resolver por SupplierCatalogNo.- Comparar en memoria
components[].sku_sapvsProductTreeLines[](el BOM ya trae ItemCode+Quantity; no hace falta pasar porItems). - SOLO los componentes que no matchean (normalmente ninguno) se resuelven en
una query batcheada:
GET Items?$filter=(ItemCode eq 'a' or SupplierCatalogNo eq 'a' or ...)&$select=ItemCode,SupplierCatalogNo(patrón ya existente:findItemNamesByCodes).
Resultado: caso común ok pasa de ~12–22 llamadas SL a 2; outdated/
not_found a 3–4. El mismo camino acelera sync (hoy repite la resolución
completa dos veces). Cambio acotado a composite.service.ts + un método de
búsqueda batcheada en items.service.ts; tests del diff intactos; re-verificar
con el E2E de PCM-12160.