← Arquitectura por épica

FT-CAT-02 · Publicación de categorías a la tienda

Arquitectura del Nuevo PIM por épica · corte del 18-oct-2026

Propuesta del TL: no sustituye la spec de cada épica

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

DeQué
FT-CAT-01 · Administración del árbol de categorías webCada categoría aprobada, cambiada, movida o desligada, con su objeto completo y su versión, por catalog.governance.changed.
PE-C · Catálogo vigenteEl árbol vigente y los sitios con su código de departamento y su ID de catálogo, ya resueltos en el evento.

Entrega

AQué
PE-9 · Publicado por canalCada 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 webEl acuse por categoría y versión, con el que MS-01 actualiza el indicador de publicada.
FT-INT-01 · Publicación a e-commerceLa categoría ya publicada antes de que llegue un producto que la usa.
FT-INT-07 · Repoblamiento masivo de productos hacia la tiendaEl indicador de publicada de cada categoría, que el repoblamiento exige antes de mandar un CatID.
FT-CAT-05 · Sitios, enrutamiento y visibilidad por negocioLas 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

Sistemas externos

Nuevo PIM · un solo sistema

Pantallas y sistemas

Personas

CATEGORY_UPSERTED
CATEGORY_REPUBLISH_REQUESTED
y 2 más

Publica el mensaje de 14 campos con s…
Reintenta con espera creciente hasta…
y 4 más

DELIVERY_RESULT

DELIVERY_RESULT

DELIVERY_RESULT

Avisa a Category qué categoría no lle…

Republicar categorías

CATEGORY_REPUBLISH_REQUESTED

El productor de hoy deja de mandar ca…

Estado del publicador
Mensaje enviado a la tienda
y 1 más

bitácora

Administración del catálogo

Entrada Única · Vendor Center

BFF del portal

MS-01 Catálogo

★ MS-09 Publicador de categorías

MS-05 Auditoría

MS-14 Avisos y ayuda

Base pim

Eventos

Tienda en línea

P360

Servicio de correo

Contexto de FT-CAT-02

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.

MS-01 CatálogoTienda en líneaBase·pimMS-09 Publicador de categoríasEventosMS-01 CatálogoTienda en líneaBase·pimMS-09 Publicador de categoríasEventos1. Entrega la categoría aprobada con su versiónCATEGORY_UPSERTED2. Descarta el evento repetido o la versión ya enviada3. Revisa aprobación, sitio, mínimos y que el padre ya llegó4. Publica el mensaje de 14 campos con source=STIBO5. Marca Enviado y, con la confirmación, Publicado6. Devuelve el acuse de la tienda por categoríaDELIVERY_RESULT7. Entrega el acuse a la dueña de la categoríaDELIVERY_RESULT8. Marca la categoría Publicada con esa versión
Flujo principal de FT-CAT-02
Los pasos en texto
PasoDeAQué haceAPI o evento
1EventosMS-09 Publicador de categoríasEntrega la categoría aprobada con su versióncatalog.governance.changed/CATEGORY_UPSERTED
2MS-09 Publicador de categoríasBase pimDescarta el evento repetido o la versión ya enviada—
3MS-09 Publicador de categoríasBase pimRevisa aprobación, sitio, mínimos y que el padre ya llegó—
4MS-09 Publicador de categoríasTienda en líneaPublica el mensaje de 14 campos con source=STIBO—
5MS-09 Publicador de categoríasBase pimMarca Enviado y, con la confirmación, Publicado—
6MS-09 Publicador de categoríasEventosDevuelve el acuse de la tienda por categoríacatalog.delivery.result/DELIVERY_RESULT
7EventosMS-01 CatálogoEntrega el acuse a la dueña de la categoríacatalog.delivery.result/DELIVERY_RESULT
8MS-01 CatálogoBase pimMarca la categoría Publicada con esa versión—

3.1 Si el bus de eventos falla o la tienda no confirma

MS-01 CatálogoEntrada Única · Vendor CenterServicio de correoMS-14 Avisos y ayudaEventosBase·pimTienda en líneaMS-09 Publicador de categoríasMS-01 CatálogoEntrada Única · Vendor CenterServicio de correoMS-14 Avisos y ayudaEventosBase·pimTienda en líneaMS-09 Publicador de categoríasAdministración del catálogoBFF1. Reintenta con espera creciente hasta 15 minutos2. Agota los intentos y marca Error con el motivo3. Devuelve el acuse con FAILEDDELIVERY_RESULT4. Entrega la falla para avisar a CategoryDELIVERY_RESULT5. Avisa a Category qué categoría no llegó6. Ve el Error en la ficha y republica7. Pide republicar la categoría con sus padresPOST /api/v2/categories/publications8. Pide volver a mandar la versión vigenteCATEGORY_REPUBLISH_REQUESTED9. Entrega la republicación al publicadorCATEGORY_REPUBLISH_REQUESTEDAdministración del catálogoBFF
Si el bus de eventos falla o la tienda no confirma

3.2 Cambio sólo de nombre, movimiento, desligue o campo vaciado

Tienda en líneaMS-09 Publicador de categoríasEventosTienda en líneaMS-09 Publicador de categoríasEventos1. Entrega un cambio sólo de nombreCATEGORY_UPSERTED2. Manda la categoría sin department ni groupType3. Entrega el nuevo padre de una ramaCATEGORY_MOVED4. Entrega el desligue de cada categoríaCATEGORY_UNLINKED5. Manda link=false para que deje de servir6. Manda vacío el campo que se vació7. Devuelve el acuse de cada envíoDELIVERY_RESULT
Cambio sólo de nombre, movimiento, desligue o campo vaciado

3.3 Primer corte con lote pequeño y regreso

MS-09 Publicador de categoríasEntrada Única · Vendor CenterTienda en líneaP360MS-09 Publicador de categoríasEntrada Única · Vendor CenterTienda en líneaP360Administración del catálogoBFF1. El productor de hoy deja de mandar categorías2. Revisa el modo del publicador y la lista permitida3. Pide el estado del publicadorGET /api/v2/category-publisher4. Manda sólo las categorías de la lista permitida5. Revisa el mensaje exacto que salióGET /api/v2/category-deliveries/{categoryId}/message6. Revisa el detalle de envío del loteGET /api/v2/category-deliveries7. Verifica el lote en el menú y pausa o abreAdministración del catálogoBFF
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
CampoValor
APIAPI-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 épicaRepublicar categorías con sus padres o descendientes, o por archivo de IDs
Quién la llamaBFF del portal
PermisoCategory, Admin o Repoblamiento
CabecerasAuthorization, X-Request-Id, Idempotency-Key
Escribe engovernance_receipts, governance_outbox
Publicacatalog.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

HTTPCódigoCuándo
400VALIDATION_ERRORMás de 500 categorías, o alguna no está Aprobada ni Desligada
403AUTHORIZATION_ERRORQuien llama no tiene un rol que pueda republicar
GET /api/v2/category-deliveries · Estado de envío de categorías
CampoValor
APIAPI-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 épicaDetalle del envío por categoría: intentos, último error y a qué padre espera
Quién la llamaBFF del portal
PermisoENT-L (Category, Admin u Operación)
CabecerasAuthorization, X-Request-Id
ParámetroscategoryIds = 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

HTTPCódigoCuándo
400VALIDATION_ERRORMá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
CampoValor
APIAPI-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 épicaEl mensaje exacto que salió, o el que saldría, para verificar
Quién la llamaBFF del portal
PermisoCategory, Admin u Operación
CabecerasAuthorization, X-Request-Id
ParámetroscategoryId = 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

HTTPCódigoCuándo
404NOT_FOUNDLa categoría o esa versión nunca se enviaron (sin preview)
GET /api/v2/category-publisher · Estado del publicador
CampoValor
APIAPI-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 épicaModo del publicador, pendientes y errores
Quién la llamaBFF del portal
PermisoOperación, Arquitectura o Category
CabecerasAuthorization, 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

HTTPCódigoCuándo
403AUTHORIZATION_ERRORQuien 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

EventoCuándoQuién lo consume
catalog.delivery.result/DELIVERY_RESULTel bus de eventos confirma el mensaje (ACCEPTED) o se agotan los intentos (FAILED), por categoría y versiónFT-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_REQUESTEDCategory, Admin o Repoblamiento piden volver a mandar categoríasMS-05 (bitácora)

Consume

EventoPara quéQuién lo publica
catalog.governance.changed/CATEGORY_UPSERTEDMS-09 manda el alta o el cambio de una categoría aprobada; ignora las CreadasFT-CAT-01
catalog.governance.changed/CATEGORY_MOVEDMS-09 manda el nuevo padreFT-CAT-01
catalog.governance.changed/CATEGORY_UNLINKEDMS-09 manda link=falseFT-CAT-01
catalog.governance.changed/CATEGORY_REPUBLISH_REQUESTEDMS-09 vuelve a mandar la versión vigente aunque ya haya salidoesta misma épica
catalog.delivery.result/DELIVERY_RESULTMS-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ónDueñoQué guardaEsta épica
category_delivery_statusMS-09El último envío de cada categoría a la tienda.escribe
governance_outboxMS-01Los cambios del catálogo que esperan salir como evento (antes outbox).escribe

8. Historias que cubre

HistoriaPrioridadCómo la cubre
FT-CAT-02·H1 · Que una categoría aprobada llegue a la tienda sin broker con el mismo mensaje…P1pasos 1, 2, 3, 4, 5, 6, 7, 8; API-09-003
FT-CAT-02·H2 · Que cada sitio viaje con el identificador que PDS reconoceP1pasos 3, 4; API-09-003
FT-CAT-02·H3 · Que cambios, desligues y vaciados lleguenP1flujo «Cambio sólo de nombre, movimiento, desligue o campo vaciado»
FT-CAT-02·H4 · Que ninguna categoría se pierda ni se dupliqueP1pasos 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 regresarP1flujo «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íasP2pasos 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

IDQué falta
DUD-07Firmar el plan de regreso y quién apaga el productor de P360 en la ventana del corte.
DUD-20Probar en QA que la tienda borre un campo que llega vacío y el valor de rootIndex vacío.
DUD-21Cerrar 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

  1. [D] trazabilidad/features/FT-CAT-02-publicacion-categorias-product-data-service.md: la ficha de la épica, con sus historias, reglas y bloqueos.
  2. [D] ADR-12 · Un solo sistema: backend compartido y una sola base (sección 3.4).
  3. [D] docs/arquitectura/por-epica/base/: piezas, colecciones, eventos y el hilo de punta a punta.
  4. [D] docs/arquitectura/omega-a-pim-headless/contratos-api.md: convenciones y endpoints de partida.
  5. [D] docs/arquitectura/maquina-de-estados-to-be/propuesta-maquina-de-estados.md §7: estados, tareas y canales.
  6. [I] Las decisiones de la sección 9 son del TL sobre esas fuentes.