Actualizado el 18-oct-2026. Primera versión de la arquitectura de esta épica.
Qué responde. Cómo funciona por dentro FT-CAT-02: qué piezas participan, en qué orden hablan, qué APIs hay que construir con su request y su response, y qué le entrega a la épica siguiente.
Para quién. Los equipos que van a construir la épica, QA y arquitectura.
Estado. [Propuesta] del TL del Nuevo PIM, 18-oct-2026, construida sobre ADR-12. La spec de cada épica dice el qué; estas páginas, el cómo.
Fuente. docs/arquitectura/por-epica/epicas/FT-CAT-02.json, generada con generar.py; la ficha de la épica es trazabilidad/features/FT-CAT-02-publicacion-categorias-product-data-service.md.
En 30 segundos
El árbol de categorías tiene que llegar a la tienda tal como lo espera hoy, pero sin el broker de P360. Esta épica hace que el publicador de categorías mande cada categoría aprobada con el mismo mensaje de 14 campos, sólo lo que cambió y los desligues como link=false, sin perder ni duplicar nada, y que el corte empiece con un lote pequeño y con forma de regresar.
- Módulo dueño: MS-09 Publicador de categorías. También participan: MS-01 Catálogo, MS-05 Auditoría, MS-14 Avisos y ayuda.
- Etapa 8 · Publicación. Recibe «PE-C · Catálogo vigente» y entrega «PE-9 · Publicado por canal».
- APIs: construye 4 y usa 0 que construyen otras épicas.
- Eventos: publica 2 y consume 5.
1. Dónde encaja
Recibe
| De | Qué |
|---|---|
| FT-CAT-01 · Administración del árbol de categorías web | Cada categoría aprobada, cambiada, movida o desligada, con su objeto completo y su versión, por catalog.governance.changed. |
| PE-C · Catálogo vigente | El árbol vigente y los sitios con su código de departamento y su ID de catálogo, ya resueltos en el evento. |
Entrega
| A | Qué |
|---|---|
| PE-9 · Publicado por canal | Cada categoría aprobada está en la tienda con su acuse (Publicado, o Error que se republica): sin ella un producto publicado no se ve en el menú. |
| FT-CAT-01 · Administración del árbol de categorías web | El acuse por categoría y versión, con el que MS-01 actualiza el indicador de publicada. |
| FT-INT-01 · Publicación a e-commerce | La categoría ya publicada antes de que llegue un producto que la usa. |
| FT-INT-07 · Repoblamiento masivo de productos hacia la tienda | El indicador de publicada de cada categoría, que el repoblamiento exige antes de mandar un CatID. |
| FT-CAT-05 · Sitios, enrutamiento y visibilidad por negocio | Las categorías publicadas con su sitio y su ID de catálogo. |
| FT-CAT-06 · Agrupadores y corrección masiva de categorías con… | Las categorías publicadas, las únicas que se pueden usar al corregir en masa. |
| FT-RAC-08 · Monitoreo de visibilidad y alertas de caída de SKUs… | El estado de envío por categoría para vigilar que no se caigan productos visibles. |
2. Diagrama de contexto
Quién participa y cómo se conecta. Las pantallas llegan por el BFF del portal, que valida el token y compone; cada módulo vuelve a validar el token y decide sus permisos con los roles de MS-13.
🟩 Verde: nuevo, se construye · 🟦 Azul: ya existe y se queda, se reutiliza su código · 🟧 Naranja: temporal o se retira (se apaga en R3) · ⬜ Gris: sistema externo · ★ módulo dueño
3. Diagrama de flujo
El camino principal, paso por paso. Cuando una pantalla llama a un módulo se ven dos flechas: la pantalla al BFF y el BFF al módulo. Los eventos salen del outbox del módulo después de guardar; quien los consume ignora los repetidos.
Los pasos en texto
| Paso | De | A | Qué hace | API o evento |
|---|---|---|---|---|
| 1 | Eventos | MS-09 Publicador de categorías | Entrega la categoría aprobada con su versión | catalog.governance.changed/CATEGORY_UPSERTED |
| 2 | MS-09 Publicador de categorías | Base pim | Descarta el evento repetido o la versión ya enviada | — |
| 3 | MS-09 Publicador de categorías | Base pim | Revisa aprobación, sitio, mínimos y que el padre ya llegó | — |
| 4 | MS-09 Publicador de categorías | Tienda en línea | Publica el mensaje de 14 campos con source=STIBO | — |
| 5 | MS-09 Publicador de categorías | Base pim | Marca Enviado y, con la confirmación, Publicado | — |
| 6 | MS-09 Publicador de categorías | Eventos | Devuelve el acuse de la tienda por categoría | catalog.delivery.result/DELIVERY_RESULT |
| 7 | Eventos | MS-01 Catálogo | Entrega el acuse a la dueña de la categoría | catalog.delivery.result/DELIVERY_RESULT |
| 8 | MS-01 Catálogo | Base pim | Marca la categoría Publicada con esa versión | — |
3.1 Si el bus de eventos falla o la tienda no confirma
3.2 Cambio sólo de nombre, movimiento, desligue o campo vaciado
3.3 Primer corte con lote pequeño y regreso
4. Si algo sale mal
- Llega el mismo evento dos veces o una versión vieja. MS-09 lo omite por su eventId y por categoría y versión, y lo cuenta como duplicado en el detalle de envío; la tienda no recibe nada repetido.
- el bus de eventos falla o no confirma. Reintenta cada 30 s × 2ⁿ, con tope de 15 minutos; al agotar los intentos marca Error con el motivo, devuelve FAILED, MS-01 marca Error en la categoría y MS-14 avisa a Category, que republica con API-01-044.
- La categoría no tiene sitio o su padre no ha llegado a la tienda. No se manda: queda Pendiente mientras espera al padre, o Error «sin sitio»; Category lo ve en su árbol.
- La tienda conserva el valor de un campo vaciado. Se prueba en QA antes del corte (DUD-20); si la tienda no lo borra con texto vacío, se pide el cambio a su equipo antes de encender.
- El primer lote sale mal en la tienda. Se pone el publicador en apagado (los eventos quedan Pendientes y no se pierden) y se vuelve a encender el productor de P360 mientras se corrige (DUD-07).
5. APIs
Las que construye esta épica (4)
POST /api/v2/categories/publications · Republicar categorías
| Campo | Valor |
|---|---|
| API | API-01-044 · MS-01 Catálogo |
| Para qué | Pide volver a mandar a la tienda una o varias categorías aprobadas, con sus padres o descendientes si se pide, o una lista de IDs por archivo («Enviar nodos web»). No cambia las categorías: avisa CATEGORY_REPUBLISH_REQUESTED con la versión vigente y MS-09 la vuelve a mandar aunque esa versión ya haya salido. |
| En esta épica | Republicar categorías con sus padres o descendientes, o por archivo de IDs |
| Quién la llama | BFF del portal |
| Permiso | Category, Admin o Repoblamiento |
| Cabeceras | Authorization, X-Request-Id, Idempotency-Key |
| Escribe en | governance_receipts, governance_outbox |
| Publica | catalog.governance.changed/CATEGORY_REPUBLISH_REQUESTED |
Request
{
"categoryIds": [
"catst10045"
],
"includeAncestors": true,
"includeDescendants": false,
"reason": "La tienda no muestra la categoría"
}
Response · 202
{
"success": true,
"data": {
"requestId": "req-c4e19a",
"status": "ACCEPTED",
"categories": 3,
"receiptUrl": "/api/v2/catalog-receipts/req-c4e19a"
}
}
Errores
| HTTP | Código | Cuándo |
|---|---|---|
| 400 | VALIDATION_ERROR | Más de 500 categorías, o alguna no está Aprobada ni Desligada |
| 403 | AUTHORIZATION_ERROR | Quien llama no tiene un rol que pueda republicar |
GET /api/v2/category-deliveries · Estado de envío de categorías
| Campo | Valor |
|---|---|
| API | API-09-002 · MS-09 Publicador de categorías |
| Para qué | Da el detalle del envío de cada categoría a la tienda: en qué va su última versión aprobada (Pendiente, Enviado, Publicado, Error o Retirado), qué versión salió, qué identificador dio el bus de eventos, cuántos intentos lleva, cuántos envíos repetidos se omitieron, el último error y a qué padre espera. El indicador de publicada que leen los demás vive en la categoría (MS-01), que lo toma del acuse. |
| En esta épica | Detalle del envío por categoría: intentos, último error y a qué padre espera |
| Quién la llama | BFF del portal |
| Permiso | ENT-L (Category, Admin u Operación) |
| Cabeceras | Authorization, X-Request-Id |
| Parámetros | categoryIds = catst10045,catst10046, status = `, from = 2026-10-18T00:00:00Z, page = 1, size = 50` |
Request
null
Response · 200
{
"success": true,
"data": {
"items": [
{
"categoryId": "catst10045",
"destination": "TIENDA",
"status": "PUBLICADO",
"approvedVersion": 8,
"sentVersion": 8,
"messageId": "16234567890123",
"sentAt": "2026-10-18T15:04:06Z",
"acknowledgedAt": "2026-10-18T15:04:06Z",
"attempts": 1,
"duplicatesSkipped": 1,
"lastError": null,
"waitingFor": null
},
{
"categoryId": "catst10046",
"destination": "TIENDA",
"status": "PENDIENTE",
"approvedVersion": 2,
"sentVersion": null,
"messageId": null,
"sentAt": null,
"acknowledgedAt": null,
"attempts": 0,
"duplicatesSkipped": 0,
"lastError": null,
"waitingFor": "catst10045"
}
],
"page": 1,
"size": 50,
"total": 2
}
}
Errores
| HTTP | Código | Cuándo |
|---|---|---|
| 400 | VALIDATION_ERROR | Más de 500 categorías en la consulta o un estado que no existe |
GET /api/v2/category-deliveries/{categoryId}/message · Mensaje enviado a la tienda
| Campo | Valor |
|---|---|
| API | API-09-003 · MS-09 Publicador de categorías |
| Para qué | Muestra el mensaje exacto que salió a la tienda para una versión de la categoría, con sus atributos (source=STIBO, traceId, objectId, publishTime) y el identificador que dio el bus de eventos. Con preview=true arma, sin mandarlo, el de la versión aprobada vigente. Reemplaza a las rutas de diagnóstico de hoy. |
| En esta épica | El mensaje exacto que salió, o el que saldría, para verificar |
| Quién la llama | BFF del portal |
| Permiso | Category, Admin u Operación |
| Cabeceras | Authorization, X-Request-Id |
| Parámetros | categoryId = catst10045, version = 8, preview = False |
Request
null
Response · 200
{
"success": true,
"data": {
"categoryId": "catst10045",
"version": 8,
"messageId": "16234567890123",
"attributes": {
"source": "STIBO",
"application": "catalog-category-publisher",
"traceId": "4bf92f3577b34da6a3ce929d0e0e4736",
"objectId": "catst10045",
"publishTime": "2026-10-18 09:04:06"
},
"body": [
{
"id": "catst10045",
"name": "Camisas de vestir",
"parent": "catst10040",
"googleCategory": "Apparel & Accessories > Clothing > Shirts",
"categoryImage": "",
"megaMenuContent": null,
"startDate": "2026-01-01 00:00:00",
"endDate": "2027-01-31 00:00:00",
"sentToFA": true,
"imageUrl": "",
"link": true,
"properties": {}
}
],
"omitted": [
"brandSite",
"groupType"
],
"omittedBecause": "No cambiaron en esta versión"
}
}
Errores
| HTTP | Código | Cuándo |
|---|---|---|
| 404 | NOT_FOUND | La categoría o esa versión nunca se enviaron (sin preview) |
GET /api/v2/category-publisher · Estado del publicador
| Campo | Valor |
|---|---|
| API | API-09-004 · MS-09 Publicador de categorías |
| Para qué | Dice en qué modo está el publicador de categorías (apagado, sólo una lista permitida o encendido), cuántos envíos esperan, cuántos fallaron y cuándo salió el último. El modo se cambia en la configuración del despliegue, no por API, para que no se encienda por accidente. |
| En esta épica | Modo del publicador, pendientes y errores |
| Quién la llama | BFF del portal |
| Permiso | Operación, Arquitectura o Category |
| Cabeceras | Authorization, X-Request-Id |
Request
null
Response · 200
{
"success": true,
"data": {
"mode": "ALLOWLIST",
"allowlistSize": 25,
"pending": 3,
"inFlight": 0,
"errors": 0,
"lastSentAt": "2026-10-18T15:04:06Z",
"configVersion": "publicador-2026-10-18-01"
}
}
Errores
| HTTP | Código | Cuándo |
|---|---|---|
| 403 | AUTHORIZATION_ERROR | Quien llama no es de Operación, Arquitectura ni Category |
Las que usa y construye otra épica (0)
Su request y su response están en la página de la épica que la construye.
Ninguna.
6. Eventos
Publica
| Evento | Cuándo | Quién lo consume |
|---|---|---|
catalog.delivery.result/DELIVERY_RESULT | el bus de eventos confirma el mensaje (ACCEPTED) o se agotan los intentos (FAILED), por categoría y versión | FT-ARQ-02, FT-CAT-05, FT-CAT-06, FT-IA-05, FT-INT-01, FT-INT-02, FT-INT-05, FT-INT-07, FT-PRD-02, FT-PRD-10, FT-RAC-06 |
catalog.governance.changed/CATEGORY_REPUBLISH_REQUESTED | Category, Admin o Repoblamiento piden volver a mandar categorías | MS-05 (bitácora) |
Consume
| Evento | Para qué | Quién lo publica |
|---|---|---|
catalog.governance.changed/CATEGORY_UPSERTED | MS-09 manda el alta o el cambio de una categoría aprobada; ignora las Creadas | FT-CAT-01 |
catalog.governance.changed/CATEGORY_MOVED | MS-09 manda el nuevo padre | FT-CAT-01 |
catalog.governance.changed/CATEGORY_UNLINKED | MS-09 manda link=false | FT-CAT-01 |
catalog.governance.changed/CATEGORY_REPUBLISH_REQUESTED | MS-09 vuelve a mandar la versión vigente aunque ya haya salido | esta misma épica |
catalog.delivery.result/DELIVERY_RESULT | MS-01 actualiza el indicador de publicada, MS-05 guarda cada intento y MS-14 avisa a Category si una categoría no llegó | esta misma épica |
7. Datos
Cada colección tiene un solo dueño, el único que escribe en ella.
| Colección | Dueño | Qué guarda | Esta épica |
|---|---|---|---|
category_delivery_status | MS-09 | El último envío de cada categoría a la tienda. | escribe |
governance_outbox | MS-01 | Los cambios del catálogo que esperan salir como evento (antes outbox). | escribe |
8. Historias que cubre
| Historia | Prioridad | Cómo la cubre |
|---|---|---|
| FT-CAT-02·H1 · Que una categoría aprobada llegue a la tienda sin broker con el mismo mensaje… | P1 | pasos 1, 2, 3, 4, 5, 6, 7, 8; API-09-003 |
| FT-CAT-02·H2 · Que cada sitio viaje con el identificador que PDS reconoce | P1 | pasos 3, 4; API-09-003 |
| FT-CAT-02·H3 · Que cambios, desligues y vaciados lleguen | P1 | flujo «Cambio sólo de nombre, movimiento, desligue o campo vaciado» |
| FT-CAT-02·H4 · Que ninguna categoría se pierda ni se duplique | P1 | pasos 2, 5, 6, 7, 8; flujo «Si el bus de eventos falla o la tienda no confirma»; API-01-044, API-09-002 |
| FT-CAT-02·H5 · Cortar con un lote pequeño y poder regresar | P1 | flujo «Primer corte con lote pequeño y regreso»; API-09-004, API-09-003 |
| FT-CAT-02·H6 · El flujo EU → CommerceTools empezando por categorías | P2 | pasos 1, 4, 5, 6, 7, 8 |
9. Decisiones de esta épica
Las tomó el TL como arquitecto; quedan como [Propuesta] hasta que el equipo de la épica las revise.
- D-CAT-02-1. MS-09 se entera sólo por catalog.governance.changed y es el único que escribe al tópico de categorías de la tienda; nadie lo llama por HTTP para publicar. Sus 6 rutas de hoy (API-09-001) no se conservan: se apagan con tráfico cero al encender el suscriptor, en R1. Por qué: Hoy el servicio de categorías llama al publicador sin outbox y, si la llamada falla, la categoría queda guardada pero no llega. Con el evento desde el outbox, guardar y avisar es una sola operación. Se apoya en PD-07, DEC-ARQ-10, ADR-12.
- D-CAT-02-2. Cada quien guarda lo suyo: MS-09 guarda el detalle de cada envío (category_delivery_status: Pendiente, Enviado al publicar, Publicado con la confirmación de el bus de eventos, Error al agotar intentos y Retirado con link=false, más intentos, mensaje y duplicados); MS-01 guarda en la categoría sólo su indicador de publicada (estado y versión), que toma del acuse. Como la confirmación de el bus de eventos no prueba que la tienda lo aplicó, el corte se verifica leyendo el lote en la tienda. Por qué: Hoy dos servicios escriben «publicada» en el mismo documento y uno pisa al otro. Así nadie escribe lo del otro y quien necesita «hoja publicada» pregunta sólo a MS-01, como fija contratos-api §3.1. Se apoya en PD-07, DUD-07.
- D-CAT-02-3. Paridad exacta del mensaje: 14 campos, source=STIBO, sin kioskId en subcategorías, fechas en texto de Ciudad de México y los defectos del broker, que MS-09 reproduce al armar el mensaje (MS-01 guarda valores limpios). La única diferencia a propósito: un campo vaciado viaja vacío para que la tienda lo borre. Por qué: La tienda y su indexador no cambian; el vaciado es lo que la ERF pide y hoy no ocurre. Se apoya en DEC-ARQ-10, DEC-ARQ-11, DUD-20.
- D-CAT-02-4. Sólo lo que cambió, como el broker: department (el sitio) y groupType sólo si cambiaron; un desligue es link=false. El sitio viaja como hoy (LIVERPOOL) y su ID de catálogo (ICatalog1000) va en properties para commercetools, sin cambiar el contrato con la tienda. Por qué: Cumple la petición de mandar el ID de catálogo sin romper a quien hoy filtra por sitio; PEN-148 confirma campo y grafía. Se apoya en PEN-148, RF-CAT-13.
- D-CAT-02-5. Sin pérdidas ni duplicados: descarta por eventId y por categoría más versión ya enviada (y lo cuenta), reintenta cada 30 s × 2ⁿ hasta 15 minutos, y manda a una hija sólo cuando su padre ya está Publicado. La republicación (CATEGORY_REPUBLISH_REQUESTED) sí vuelve a mandar la misma versión. Por qué: La entrega de eventos es al menos una vez; la tienda descarta una categoría cuyo padre no conoce. Se apoya en DUD-07, ADR-12.
- D-CAT-02-6. El corte: el publicador arranca apagado y su modo (apagado, lista permitida, encendido) se fija en la configuración del despliegue, no por API. Apagado no pierde nada: guarda Pendiente y al encender manda la última versión de cada categoría. El productor de P360 se apaga en la misma ventana en que se enciende la lista permitida; regresar es apagar el publicador y volver a encender el de P360. Por qué: Nunca hay dos productores a la vez, como fija DEC-ARQ-10, y un lote pequeño se puede verificar y pausar sin perder cambios. Se apoya en DUD-07, DEC-ARQ-10.
Qué falta decidir
| ID | Qué falta |
|---|---|
| DUD-07 | Firmar el plan de regreso y quién apaga el productor de P360 en la ventana del corte. |
| DUD-20 | Probar en QA que la tienda borre un campo que llega vacío y el valor de rootIndex vacío. |
| DUD-21 | Cerrar la lista oficial de sitios y sus códigos (14 más el reservado). |
También siguen abiertos: PEN-148, Confirmar con el equipo de la tienda el campo y la grafía del ID de catálogo.
Fuentes
- [D]
trazabilidad/features/FT-CAT-02-publicacion-categorias-product-data-service.md: la ficha de la épica, con sus historias, reglas y bloqueos. - [D] ADR-12 · Un solo sistema: backend compartido y una sola base (sección 3.4).
- [D]
docs/arquitectura/por-epica/base/: piezas, colecciones, eventos y el hilo de punta a punta. - [D]
docs/arquitectura/omega-a-pim-headless/contratos-api.md: convenciones y endpoints de partida. - [D]
docs/arquitectura/maquina-de-estados-to-be/propuesta-maquina-de-estados.md§7: estados, tareas y canales. - [I] Las decisiones de la sección 9 son del TL sobre esas fuentes.