Requerimiento funcional — Frontend: productos compuestos (combos) en pedidos¶
Audiencia: instancia de Claude / dev que trabaje en frontend/ (Next.js 15, ver frontend/CLAUDE.md).
Contexto de negocio y diseño: 01-requerimiento.md, 04-diseno-oms.md.
Maqueta visual: 07-maqueta-ui.html (en _borradores/, no publicado) — wireframes de baja fidelidad de las 7 pantallas (abrir en el navegador).
Este documento es autocontenido: incluye los contratos necesarios. El backend se
implementa en paralelo según 05-plan-implementacion-backend.md.
1. Resumen¶
El OMS debe permitir vender productos compuestos (combos/kits): productos publicados
en Algolia con product_type: "composite" y una lista components[]. En SAP se
representan como Sales BOM: el pedido lleva una sola línea "padre" y SAP explota los
componentes automáticamente. El combo puede no existir aún en SAP (drop-shipping): el
frontend debe orquestar la verificación y, con confirmación del vendedor, su creación en
SAP antes de agregarlo al pedido.
Reglas de negocio inviolables:
- La receta (lista de componentes) es inmutable en el OMS. No se puede cambiar, quitar ni agregar componentes de un compuesto — ni en el picker ni en el pedido. La fuente de la verdad es Algolia, no SAP.
- Los pedidos que contienen un combo no se editan (el backend rechaza el PATCH con 409). Para cambiar algo: cancelar y recrear el pedido.
- El combo tiene precio propio (campo
pricedel hit, IVA incluido); los componentes van con precio 0.
2. Contratos¶
2.1 Hit de Algolia (índice product_items_view_index / _dev)¶
Campos relevantes (hit real, recortado):
{
"objectID": "17774",
"name": "Notebook Gateway ... + estuche",
"sku": "PCM-12167", // SKU publicado del combo
"sku_sap": "PCM-12167", // ItemCode SAP si se conoce (puede faltar)
"product_type": "composite", // ← discriminador de combo
"components": [
{ "product_item_id": 6205, "sku": "03357", "sku_sap": "03357", "name": "Notebook ...", "quantity": 1 },
{ "product_item_id": 1426, "sku": "00414", "sku_sap": "00414", "name": "MOUSE ...", "quantity": 1 }
],
"price": 3114430, // precio final del combo, Gs. IVA incluido
"total_stock": 1, "in_stock": true,
"images": ["..."], "brand": "...", "category": "..."
}
2.2 Endpoints nuevos del backend¶
POST /items/composite/verify — body { "sku": "PCM-12167" }:
{
"status": "ok" | "not_found" | "outdated",
"sku": "PCM-12167",
"itemCode": "09xxx", // ItemCode SAP del padre (si existe)
"diff": { // solo si outdated
"missing": [{ "sku": "03357", "quantity": 1 }],
"extra": [{ "itemCode": "00999", "quantity": 1 }],
"changed": [{ "itemCode": "00414", "sapQty": 1, "publishedQty": 2 }]
},
"componentsToCreate": ["07110"] // componentes que no existen como ítems en SAP
}
POST /items/composite/sync — body { "sku": "PCM-12167", "warehouseCode": "CEN" }
(warehouseCode = almacén/sucursal del pedido en curso). Crea/actualiza en SAP el ítem
padre, los componentes faltantes y la receta. Respuesta: mismo shape del verify, con
status: "ok" e itemCode definitivo. Errores: 4xx con mensaje mostrable.
Ambos son idempotentes; sync puede tardar varios segundos (crea N ítems en SAP).
2.3 GET del pedido: jerarquía por TreeType¶
El backend expondrá TreeType en cada línea del pedido (GET /orders/:docEntry):
| TreeType | Significado |
|---|---|
iSalesTree |
Línea padre del combo — lleva precio, impuesto y cantidad editable en SAP |
iIngredient |
Componente explotado por SAP — precio 0, pertenece al padre que lo precede |
iNotATree |
Línea normal |
Las líneas iIngredient que siguen (en orden de LineNum) a una iSalesTree son sus
componentes. Un pedido puede tener varios combos y líneas normales mezcladas.
3. Requerimientos funcionales¶
RF-1: Identificar combos en el buscador de productos¶
En ProductPicker (src/app/components/products/ProductPicker.tsx), los hits con
product_type === "composite" se muestran con un distintivo visual (badge/chip "Combo")
y, de ser posible, la lista de componentes visible (nombre × cantidad) en el detalle del
hit antes de agregarlo.
Modelo: extender AlgoliaHit (ProductPicker.tsx:26) y Product
(src/models/products.models.ts) con product_type? y components?.
RF-2: Verificación al seleccionar un combo¶
Al seleccionar un hit composite (hoy: mapHitToDocumentLine + onAdd,
ProductPicker.tsx:187-239), en lugar de agregarlo directo:
- Llamar
itemsApi.verifyComposite(sku)(nuevo método ensrc/utils/omsBackend/omsBackend.ts, patrón deitemsApiexistente, línea ~401). - Según
status: ok→ agregar la línea al pedido (RF-4). Sin fricción para el vendedor.not_found→ modal: "Este combo aún no existe en SAP", lista de componentes (marcando los decomponentsToCreatecomo "se crearán en SAP") y botón [Crear en SAP y agregar].outdated→ modal: "La receta del combo cambió", mostrando el diff (missing/extra/changed) y botón [Actualizar en SAP y agregar].- El botón de confirmación llama
itemsApi.syncComposite(sku, warehouseCode)con estado de carga visible (puede tardar). Si respondeok→ agregar la línea (RF-4). Si falla → mensaje de error del backend, no se agrega nada. - Cancelar el modal = no se agrega nada al pedido.
El vendedor nunca puede editar la lista de componentes en estos modales.
RF-3: Línea de combo en el pedido en edición (pre-guardado)¶
- Se agrega una sola línea al store (
addOrderLine,src/store/order.store.ts:262): la del padre, conItemCode=itemCodedevuelto por verify/sync (NO el sku publicado: SAP autogenera los códigos),PriceAfterVAT = pricedel hit, cantidad 1, flagisComposite: trueycomponents[](informativos, del hit). - En la tabla de líneas (
DesktopRow/MobileList,src/app/components/documents/), la línea padre se muestra expandible con sub-filas de componentes solo lectura (nombre, cantidad × cantidad del padre). Sin acciones de quitar/editar por componente. - Editable en la línea padre: cantidad y precio (como una línea normal). Quitar la línea padre quita el combo completo.
- Los componentes NO se envían al backend:
mapOrderToCreateDto(src/utils/mappers/order.mapper.ts:29) manda solo la línea padre. (SAP explota los componentes; enviarlos los duplicaría.) - Cuidado con
_reassignLineNums(order.store.ts:50): los componentes no son líneas del store, así que no debe romper nada — verificar.
RF-4: Pedido guardado / vista de detalle¶
- Al cargar un pedido (
ordersApi.getByDocEntry), agrupar líneas porTreeType(§2.3): fila padre + sub-filas de componentes solo lectura, indentadas/agrupadas visualmente. LíneasiNotATreecomo siempre. - Mostrar por componente un indicador de stock (stock disponible ya se obtiene hoy
por línea —
getCurrentStockAndWarranty): si algún componente no tiene stock suficiente, señalizarlo (la factura fallará hasta que haya recepción de mercadería — modelo drop-shipping).
RF-5: Bloqueo de edición de pedidos con combos¶
Si un pedido cargado contiene alguna línea iSalesTree/iIngredient, la UI deshabilita
la edición del pedido completo (botón editar/guardar oculto o disabled con tooltip
"Los pedidos con combos no se editan — cancelar y recrear"). El backend igualmente
rechaza el PATCH con 409; la UI debe manejar ese error con mensaje claro.
RF-6: Facturación¶
Al generar la factura desde el pedido (invoicesApi, líneas
{BaseType: 17, BaseEntry, BaseLine}), incluir TODAS las líneas del pedido —
padre + componentes + normales. Nunca filtrar las iIngredient (referenciar solo el
padre falla en SAP). Si la factura falla por stock (negative inventory), mostrar el
mensaje indicando qué componente no tiene stock.
4. Fuera de alcance¶
- Editar/armar combos en el OMS (la receta viene de Algolia y es inmutable).
- Sincronización proactiva de combos con SAP (solo bajo demanda con confirmación).
- Crear componentes que no estén publicados en Algolia.
- Manejo de compra/recepción de mercadería para el drop-shipping.
5. Criterios de aceptación¶
- Un combo publicado que ya existe y coincide en SAP se agrega al pedido sin fricción.
- Un combo inexistente en SAP muestra el modal, se crea con un clic y queda agregado.
- Un combo con receta desactualizada muestra el diff, se actualiza con un clic y queda agregado.
- El pedido guardado muestra el combo como padre + componentes solo lectura, con indicador de stock por componente.
- No existe ninguna vía en la UI para modificar componentes de un combo.
- Un pedido con combo no puede editarse desde la UI (y el 409 del backend se muestra con mensaje claro si ocurre).
- La factura generada desde un pedido con combo referencia todas las líneas.
- Un producto normal (
product_typeausente/distinto) se comporta exactamente igual que hoy — cero regresiones en el flujo actual.
6. Datos de prueba¶
- Backend dev apunta a SAP
ZZ_COMPU_3: ítemsTESTBOM-*, BOMProductTrees('TESTBOM-PC')(4 componentes),TESTBOM-SSDlibre para el caso "componente a crear", clienteC32857. - Hit composite real de referencia: sku
PCM-12167(§2.1). - Swagger del backend:
/api-docs(endpoints composite tras la etapa B3 del plan backend).