Actualizado el 18-oct-2026. Primera versión de la arquitectura de esta épica.
Qué responde. Cómo funciona por dentro FT-CAT-01: 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-01.json, generada con generar.py; la ficha de la épica es trazabilidad/features/FT-CAT-01-administracion-arbol-categorias-web.md.
En 30 segundos
El árbol de categorías web es el menú de la tienda: los productos cuelgan de sus hojas. Hoy mover o dar de baja una rama exige exportar, borrar y recargar archivos. Esta épica deja a Category administrarlo en el portal: crear con un asistente que valida nivel y mínimos, aprobar antes de que viaje, mover ramas, desligar sin borrar ni dejar productos huérfanos, cargar en masa con revisión previa y traer el árbol vivo de P360.
- Módulo dueño: MS-01 Catálogo. También participan: MS-02 Producto, MS-03 Migración y convivencia, MS-05 Auditoría, MS-09 Publicador de categorías, MS-13 Identidad y acceso.
- Etapa C · Configura el catálogo. Recibe «PE-T · Usuario con permisos y bitácora» y entrega «PE-C · Catálogo vigente».
- APIs: construye 11 y usa 14 que construyen otras épicas.
- Eventos: publica 3 y consume 2.
1. Dónde encaja
Recibe
| De | Qué |
|---|---|
| PE-T · Usuario con permisos y bitácora | Quien entra ya se identificó, MS-13 dice si puede crear, mover o aprobar categorías y cada cambio queda en la bitácora de MS-05. |
| FT-USR-02 · Matriz de roles y permisos por módulo, acción, negocio y… | Los perfiles de Category con sus capacidades: crear y cambiar, mover y desligar, y aprobar. |
| FT-ARQ-02 · Bitácora inmutable y versionado de producto, plantilla y… | La bitácora por campo con versión, de la que sale la historia de cada categoría. |
| FT-REF-01 · Datos de referencia administrables | El catálogo único de sitios (los 14 y el reservado UNLINKED) con su código de departamento y su ID de catálogo (API-01-046 a API-01-049). |
| FT-CAT-02 · Publicación de categorías a la tienda | El acuse de la tienda por categoría y versión, con el que MS-01 actualiza el indicador de publicada. |
Entrega
| A | Qué |
|---|---|
| PE-C · Catálogo vigente | El árbol vigente: cada categoría con su ID que no cambia, nivel calculado, sitio, estatus (Creada, Aprobada o Desligada), indicador de publicada y versión, legible por la API de MS-01. |
| FT-CAT-02 · Publicación de categorías a la tienda | Cada categoría aprobada, cambiada, movida o desligada, con su objeto completo y su versión, por catalog.governance.changed. |
| FT-CAT-03 · Categorización de productos | Las hojas activas y publicadas, la búsqueda por nombre y qué categorías son restringidas, con sus documentos, requisitos y si bloquean o avisan. |
| FT-CAT-06 · Agrupadores y corrección masiva de categorías con… | Los productos que perdieron una categoría desligada, para corregirlos y republicarlos. |
| FT-RAC-07 · Reportes, exportes, importes y búsquedas por atributos | El árbol con niveles calculados y sin las desligadas, para los reportes y las descargas por nivel. |
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 | Administración del catálogo | Entrada Única · Vendor Center | Abre el árbol de su sitio y elige el padre | — |
| 2 | Entrada Única · Vendor Center | MS-01 Catálogo (por el BFF) | Pide la rama con su estatus y si está publicada | API-01-038 |
| 3 | Entrada Única · Vendor Center | MS-01 Catálogo (por el BFF) | Valida la categoría nueva mientras la captura | API-01-040 |
| 4 | MS-01 Catálogo | MS-13 Identidad y acceso | Revisa que quien llama pueda crear categorías | API-13-001 |
| 5 | Entrada Única · Vendor Center | MS-01 Catálogo (por el BFF) | Guarda la categoría en estado Creada | API-01-040 |
| 6 | MS-01 Catálogo | Base pim | Genera el ID, calcula el nivel y guarda versión 1 | — |
| 7 | Administración del catálogo | Entrada Única · Vendor Center | Revisa la rama nueva y la aprueba | — |
| 8 | Entrada Única · Vendor Center | MS-01 Catálogo (por el BFF) | Pide la vista previa de la aprobación | API-01-201 |
| 9 | Entrada Única · Vendor Center | MS-01 Catálogo (por el BFF) | Aprueba las categorías de la rama | API-01-201 |
| 10 | MS-01 Catálogo | Base pim | Pasa a Aprobada y guarda el aviso juntos | — |
| 11 | MS-01 Catálogo | Eventos | Avisa la categoría aprobada con su objeto completo | catalog.governance.changed/CATEGORY_UPSERTED |
| 12 | Eventos | MS-05 Auditoría | Entrega el cambio a la bitácora | catalog.governance.changed/CATEGORY_UPSERTED |
| 13 | Eventos | MS-09 Publicador de categorías | Entrega la categoría aprobada al publicador | catalog.governance.changed/CATEGORY_UPSERTED |
3.1 Mover una rama o desligarla sin dejar huérfanos
3.2 Carga masiva con revisión previa
3.3 Carga inicial del árbol desde P360
4. Si algo sale mal
- Se cuelga un nivel 3 de un nivel 1, se pasa del nivel 5 o un nivel 1 no trae isBrand, rootIndex o fechas. La validación responde con el campo que falla y nada se guarda; el asistente lo marca en la pantalla mientras se captura.
- El ID manual ya existe, aunque sólo cambien mayúsculas. Responde 409 con CATEGORY_ID_ALREADY_EXISTS; un ID nunca se cambia ni se reutiliza, porque para la tienda sería otra categoría.
- Desligar o mover a «unlinked» dejaría productos sin ninguna categoría web activa. La confirmación se bloquea y la vista previa lista esos productos; Category los reasigna (o Repoblamiento con FT-CAT-06) y vuelve a confirmar.
- MS-02 no responde la revisión de productos. El movimiento o el desligue responde 503 y no se hace a ciegas; Category lo reintenta más tarde y nada queda a medias.
- Otra persona cambió la rama entre la vista previa y la confirmación. La confirmación responde 409 VERSION_CONFLICT; la pantalla recarga la rama y se vuelve a revisar.
5. APIs
Las que construye esta épica (11)
GET /bff/v1/catalog/categories/{id} · Ficha de una categoría
| Campo | Valor |
|---|---|
| API | API-BFF-111 · BFF del portal |
| Para qué | Arma la ficha de una categoría: sus datos, versión, número de modificaciones e indicador de publicada (MS-01), su historia por campo (MS-05) y el detalle de su último envío a la tienda, con intentos y último error (MS-09). Reemplaza la bitácora y el diagnóstico que hoy el portal pide al servicio de categorías y a M4. |
| En esta épica | La ficha de una categoría con su versión, su historia por campo y el detalle de su último envío |
| Quién la llama | Entrada Única · Vendor Center (por el BFF) |
| Permiso | Pantalla de categorías |
| Cabeceras | Authorization, X-Request-Id |
| Parámetros | id = catst10045, historySize = 20 |
Request
null
Response · 200
{
"success": true,
"data": {
"category": {
"id": "catst10045",
"name": "Camisas de vestir",
"level": 3,
"brandSite": "LIVERPOOL",
"status": "APROBADA",
"version": 8,
"modifications": 7
},
"history": [
{
"at": "2026-10-18T15:04:05Z",
"actor": "Category (usuario interno)",
"version": 8,
"changes": [
{
"field": "name.es",
"before": "Camisas",
"after": "Camisas de vestir"
}
]
}
],
"store": {
"status": "PUBLICADO",
"sentVersion": 8,
"acknowledgedAt": "2026-10-18T15:04:06Z",
"attempts": 1,
"lastError": null
}
}
}
Errores
| HTTP | Código | Cuándo |
|---|---|---|
| 404 | NOT_FOUND | La categoría no existe |
GET /api/v2/categories · Buscar categorías
| Campo | Valor |
|---|---|
| API | API-01-035 · MS-01 Catálogo |
| Para qué | Busca categorías del árbol web por sitio, padre, nombre, nivel, estatus o si ya están publicadas en la tienda, o sólo las raíces, con su nivel calculado, sus ancestros, si es hoja y si es restringida. Las desligadas sólo salen si se piden. |
| En esta épica | Buscar categorías por sitio, padre, nombre, nivel, estatus o si están publicadas |
| Quién la llama | BFF del portal, MS-02 Producto, MS-04 Reportes, MS-15 IA y enriquecimiento |
| Permiso | ENT-L; los módulos con su cuenta de servicio |
| Cabeceras | Authorization, X-Request-Id |
| Parámetros | brandSite = LIVERPOOL, parentId = catst10040, q = camisas, level = 3, status = APROBADA, published = True, leavesOnly = True, rootsOnly = False, page = 1, size = 50 |
| También la usan | FT-CAT-03 |
Request
null
Response · 200
{
"success": true,
"data": {
"items": [
{
"id": "catst10045",
"name": {
"es": "Camisas de vestir"
},
"parentId": "catst10040",
"ancestorIds": [
"catst10001",
"catst10040"
],
"level": 3,
"brandSite": "LIVERPOOL",
"status": "APROBADA",
"isLeaf": true,
"restricted": false,
"version": 8,
"modifications": 7,
"publication": {
"status": "PUBLICADO",
"version": 8,
"at": "2026-10-18T15:04:06Z"
},
"startDate": "2026-01-01T06:00:00Z",
"endDate": "2099-12-31T06:00:00Z"
}
],
"page": 1,
"size": 50,
"total": 1
}
}
Errores
| HTTP | Código | Cuándo |
|---|---|---|
| 400 | VALIDATION_ERROR | Un filtro trae un valor que no existe (sitio, estado o nivel fuera de 1 a 5) |
GET /api/v2/categories/{id} · Ver una categoría
| Campo | Valor |
|---|---|
| API | API-01-036 · MS-01 Catálogo |
| Para qué | Devuelve una categoría completa: los campos que viajan a la tienda, su nivel, su sitio con su ID de catálogo, su estatus (Creada, Aprobada o Desligada), su versión y su número de modificaciones, su indicador de publicada (qué versión confirmó la tienda) y, si es restringida, sus documentos, sus requisitos y si bloquea o avisa. |
| En esta épica | Ver una categoría completa, con su restricción y su indicador de publicada |
| Quién la llama | BFF del portal, MS-02 Producto |
| Permiso | ENT-L |
| Cabeceras | Authorization, X-Request-Id |
| Parámetros | id = catst10045 |
| También la usan | FT-CAT-03 |
Request
null
Response · 200
{
"success": true,
"data": {
"id": "catst10045",
"name": {
"es": "Camisas de vestir",
"en": "Dress shirts"
},
"parentId": "catst10040",
"ancestorIds": [
"catst10001",
"catst10040"
],
"level": 3,
"brandSite": "LIVERPOOL",
"catalogId": "ICatalog1000",
"status": "APROBADA",
"isLeaf": true,
"startDate": "2026-01-01T06:00:00Z",
"endDate": "2099-12-31T06:00:00Z",
"groupType": "NOT_SPECIFIED",
"sentToFA": true,
"categoryImage": "/imagenes/categorias/catst10045.jpg",
"imageUrl": "",
"googleCategory": "Apparel & Accessories > Clothing > Shirts",
"megaMenuContent": null,
"properties": {
"kioskId": null
},
"restriction": null,
"publication": {
"status": "PUBLICADO",
"version": 8,
"at": "2026-10-18T15:04:06Z"
},
"version": 8,
"modifications": 7,
"approvedAt": "2026-10-17T18:20:00Z",
"updatedAt": "2026-10-18T15:04:05Z"
}
}
Errores
| HTTP | Código | Cuándo |
|---|---|---|
| 404 | NOT_FOUND | La categoría no existe |
GET /api/v2/categories/{id}/children · Hijas de una categoría
| Campo | Valor |
|---|---|
| API | API-01-037 · MS-01 Catálogo |
| Para qué | Lista las hijas directas de una categoría, en el orden del menú, para abrir el árbol nivel por nivel. |
| En esta épica | Abrir el árbol nivel por nivel |
| Quién la llama | BFF del portal |
| Permiso | ENT-L |
| Cabeceras | Authorization, X-Request-Id |
| Parámetros | id = catst10040, includeUnlinked = False, page = 1, size = 100 |
| También la usan | FT-CAT-03 |
Request
null
Response · 200
{
"success": true,
"data": {
"items": [
{
"id": "catst10045",
"name": {
"es": "Camisas de vestir"
},
"level": 3,
"status": "APROBADA",
"isLeaf": true,
"children": 0,
"publication": {
"status": "PUBLICADO",
"version": 8
}
},
{
"id": "catst10046",
"name": {
"es": "Camisas de lino"
},
"level": 3,
"status": "CREADA",
"isLeaf": true,
"children": 0,
"publication": null
}
],
"page": 1,
"size": 100,
"total": 2
}
}
Errores
| HTTP | Código | Cuándo |
|---|---|---|
| 404 | NOT_FOUND | La categoría padre no existe |
GET /api/v2/categories/{id}/tree · Una rama del árbol
| Campo | Valor |
|---|---|
| API | API-01-038 · MS-01 Catálogo |
| Para qué | Devuelve una categoría con toda su rama hasta la profundidad pedida, validada (sin ciclos ni saltos de nivel) y sin truncar, con el estatus y el indicador de publicada de cada nodo para pintar el árbol. |
| En esta épica | Ver una rama completa con el estatus de cada nodo |
| Quién la llama | BFF del portal, MS-04 Reportes |
| Permiso | ENT-L |
| Cabeceras | Authorization, X-Request-Id |
| Parámetros | id = catst10040, depth = 2, includeUnlinked = False |
Request
null
Response · 200
{
"success": true,
"data": {
"id": "catst10040",
"name": {
"es": "Camisas"
},
"level": 2,
"status": "APROBADA",
"publication": {
"status": "PUBLICADO",
"version": 5
},
"children": [
{
"id": "catst10045",
"name": {
"es": "Camisas de vestir"
},
"level": 3,
"status": "APROBADA",
"publication": {
"status": "PUBLICADO",
"version": 8
},
"children": []
},
{
"id": "catst10046",
"name": {
"es": "Camisas de lino"
},
"level": 3,
"status": "CREADA",
"publication": null,
"children": []
}
]
}
}
Errores
| HTTP | Código | Cuándo |
|---|---|---|
| 404 | NOT_FOUND | La categoría no existe |
| 400 | VALIDATION_ERROR | La rama pasa del límite de nodos de una respuesta: se pide por partes, nunca se trunca |
GET /api/v2/categories/tree · Árbol de un sitio
| Campo | Valor |
|---|---|
| API | API-01-039 · MS-01 Catálogo |
| Para qué | Devuelve el árbol web de un sitio desde su nivel 1 hasta la profundidad pedida; con depth=1 da sólo los niveles 1, como la pantalla de hoy. |
| En esta épica | Ver el árbol de un sitio |
| Quién la llama | BFF del portal, MS-04 Reportes, MS-15 IA y enriquecimiento |
| Permiso | ENT-L |
| Cabeceras | Authorization, X-Request-Id |
| Parámetros | brandSite = LIVERPOOL, depth = 1, includeUnlinked = False |
| También la usan | FT-CAT-06, FT-RAC-07 |
Request
null
Response · 200
{
"success": true,
"data": {
"brandSite": "LIVERPOOL",
"catalogId": "ICatalog1000",
"items": [
{
"id": "catst10001",
"name": {
"es": "Ropa"
},
"level": 1,
"status": "APROBADA",
"children": 14,
"publication": {
"status": "PUBLICADO",
"version": 12
}
},
{
"id": "catst10002",
"name": {
"es": "Hogar"
},
"level": 1,
"status": "APROBADA",
"children": 22,
"publication": {
"status": "ERROR",
"version": 9
}
}
]
}
}
Errores
| HTTP | Código | Cuándo |
|---|---|---|
| 400 | VALIDATION_ERROR | El sitio no existe o la profundidad pasa de 5 |
POST /api/v2/categories · Crear una categoría
| Campo | Valor |
|---|---|
| API | API-01-040 · MS-01 Catálogo |
| Para qué | Crea una categoría en estado Creada bajo su padre: si no se manda el ID, el sistema lo genera (catst y un consecutivo), el nivel se calcula del padre y se revisan los mínimos de su nivel. Con ?dryRun=true sólo valida, para el asistente. No viaja a la tienda hasta que se aprueba. |
| En esta épica | Validar con dryRun y crear categorías en Creada |
| Quién la llama | BFF del portal |
| Permiso | CAT-E (Category manager) |
| Cabeceras | Authorization, X-Request-Id, Idempotency-Key |
| Parámetros | dryRun = False |
| Escribe en | categories, id_sequences, governance_outbox |
| Publica | catalog.governance.changed/CATEGORY_UPSERTED |
Request
{
"parentId": "catst10040",
"name": {
"es": "Camisas de lino"
},
"startDate": "2026-11-01T06:00:00Z",
"endDate": "2099-12-31T06:00:00Z",
"groupType": "NOT_SPECIFIED",
"sentToFA": true,
"categoryImage": "/imagenes/categorias/camisas-lino.jpg"
}
Response · 201
{
"success": true,
"data": {
"id": "catst10046",
"parentId": "catst10040",
"level": 3,
"brandSite": "LIVERPOOL",
"status": "CREADA",
"isLeaf": true,
"version": 1,
"modifications": 0,
"createdAt": "2026-10-18T15:04:05Z"
}
}
Errores
| HTTP | Código | Cuándo |
|---|---|---|
| 400 | VALIDATION_ERROR | Falta un mínimo de su nivel (en nivel 1: isBrand, isBrandLanding, rootIndex y fechas), la fecha de fin es anterior a la de inicio o sentToFA viene fuera de los niveles 2 a 5 |
| 409 | ID_ALREADY_EXISTS | El ID manual ya existe, aunque sólo cambien mayúsculas (detalle CATEGORY_ID_ALREADY_EXISTS) |
| 422 | BUSINESS_ERROR | Quedaría en un nivel mayor que 5 o saltaría un nivel (un nivel 3 bajo un nivel 1) |
| 404 | NOT_FOUND | El padre no existe o está Desligado |
| 403 | AUTHORIZATION_ERROR | Quien llama no tiene la capacidad de crear categorías |
PUT /api/v2/categories/{id} · Cambiar una categoría
| Campo | Valor |
|---|---|
| API | API-01-041 · MS-01 Catálogo |
| Para qué | Cambia los campos de una categoría (nombre, fechas, banderas, imágenes, propiedades y la restricción con sus documentos, requisitos y efecto); el ID y el nivel no se capturan. Sube la versión y el número de modificaciones. Si ya está Aprobada, el cambio viaja a la tienda al guardarse; un campo vaciado se guarda vacío para que la tienda lo borre. |
| En esta épica | Cambiar campos, fechas, imágenes y la restricción; vaciar un campo |
| Quién la llama | BFF del portal |
| Permiso | CAT-E (Category manager) |
| Cabeceras | Authorization, X-Request-Id, Idempotency-Key, If-Match |
| Parámetros | id = catst10045, dryRun = False |
| Escribe en | categories, governance_outbox |
| Publica | catalog.governance.changed/CATEGORY_UPSERTED |
Request
{
"name": {
"es": "Camisas de vestir"
},
"categoryImage": "",
"endDate": "2027-01-31T06:00:00Z"
}
Response · 200
{
"success": true,
"data": {
"id": "catst10045",
"status": "APROBADA",
"version": 8,
"modifications": 7,
"travelsToStore": true,
"changes": [
{
"field": "name.es",
"before": "Camisas",
"after": "Camisas de vestir"
},
{
"field": "categoryImage",
"before": "/imagenes/categorias/catst10045.jpg",
"after": ""
},
{
"field": "endDate",
"before": "2099-12-31T06:00:00Z",
"after": "2027-01-31T06:00:00Z"
}
]
}
}
Errores
| HTTP | Código | Cuándo |
|---|---|---|
| 409 | VERSION_CONFLICT | Alguien cambió la categoría desde que se abrió |
| 409 | NOT_EDITABLE_IN_STATUS | Está Desligada: primero se regresa a un árbol activo |
| 400 | VALIDATION_ERROR | Se intenta cambiar el ID o el nivel, la fecha de fin es anterior a la de inicio o falta un mínimo |
PATCH /api/v2/categories/{id}/parent · Mover una rama
| Campo | Valor |
|---|---|
| API | API-01-042 · MS-01 Catálogo |
| Para qué | Mueve una categoría con toda su rama bajo otro padre: conserva hijas y productos, recalcula el nivel de la rama y hereda el sitio del nuevo padre. Moverla al sitio «unlinked» la desliga con su subárbol; moverla de ahí a un padre activo la regresa. Con ?dryRun=true dice qué va a pasar, incluidos los productos que se quedarían sin categoría activa, sin mover nada. |
| En esta épica | Mover una rama, mandarla a «unlinked» o regresarla a un árbol activo |
| Quién la llama | BFF del portal |
| Permiso | CLA-E (Category manager) |
| Cabeceras | Authorization, X-Request-Id, Idempotency-Key, If-Match |
| Parámetros | id = catst10040, dryRun = True |
| Escribe en | categories, governance_outbox |
| Publica | catalog.governance.changed/CATEGORY_MOVED, catalog.governance.changed/CATEGORY_UNLINKED |
Request
{
"parentId": "catst10002"
}
Response · 200
{
"success": true,
"data": {
"dryRun": true,
"categoryId": "catst10040",
"from": {
"parentId": "catst10001",
"level": 2,
"brandSite": "LIVERPOOL"
},
"to": {
"parentId": "catst10002",
"level": 2,
"brandSite": "LIVERPOOL"
},
"descendants": 37,
"levelsRecalculated": 0,
"products": 4210,
"productsWithoutOtherActiveCategory": 0,
"travelsToStore": true,
"canConfirm": true
}
}
Errores
| HTTP | Código | Cuándo |
|---|---|---|
| 422 | BUSINESS_ERROR | El nuevo padre es la misma categoría o una descendiente (ciclo), o la rama quedaría con más de 5 niveles o con un salto |
| 422 | BUSINESS_ERROR | Va a «unlinked» y algún producto se quedaría sin ninguna categoría web activa (se listan) |
| 409 | VERSION_CONFLICT | La categoría cambió desde la vista previa |
| 503 | DEPENDENCY_UNAVAILABLE | MS-02 no respondió la revisión de productos: no se mueve a ciegas |
DELETE /api/v2/categories/{id} · Desligar sin borrar
| Campo | Valor |
|---|---|
| API | API-01-043 · MS-01 Catálogo |
| Para qué | Desliga una categoría sin borrarla: ella y todo su subárbol pasan a Desligada, salen del árbol activo y de las descargas, y la tienda recibe link=false; los productos sólo pierden esa relación. Se bloquea si algún producto se quedaría sin ninguna categoría web activa, hasta reasignarlo. Con ?dryRun=true devuelve esos productos. |
| En esta épica | Desligar sin borrar, con la lista de productos que quedarían huérfanos |
| Quién la llama | BFF del portal |
| Permiso | CLA-E (Category manager) |
| Cabeceras | Authorization, X-Request-Id, Idempotency-Key, If-Match |
| Parámetros | id = catst10045, reason = Categoría de temporada que ya no se usa, dryRun = True |
| Escribe en | categories, governance_outbox |
| Publica | catalog.governance.changed/CATEGORY_UNLINKED |
Request
null
Response · 200
{
"success": true,
"data": {
"dryRun": true,
"categoryId": "catst10045",
"subtree": 3,
"products": 128,
"orphans": {
"total": 2,
"items": [
{
"productId": "PRD-0004812",
"name": "Camisa de lino manga larga",
"business": "MER"
},
{
"productId": "PRD-0004813",
"name": "Camisa de lino manga corta",
"business": "MER"
}
]
},
"canConfirm": false
}
}
Errores
| HTTP | Código | Cuándo |
|---|---|---|
| 422 | BUSINESS_ERROR | Quedarían productos sin ninguna categoría web activa; la respuesta los lista |
| 409 | VERSION_CONFLICT | La categoría cambió desde la vista previa |
| 409 | NOT_EDITABLE_IN_STATUS | Ya está Desligada |
| 503 | DEPENDENCY_UNAVAILABLE | MS-02 no respondió la revisión de productos: no se desliga a ciegas |
POST /api/v2/categories/approvals · Aprobar categorías
| Campo | Valor |
|---|---|
| API | API-01-201 · MS-01 Catálogo |
| Para qué | Aprueba de 1 a 500 categorías Creadas para que viajen a la tienda: revisa mínimos, sitio y que su padre ya esté aprobado o venga en el mismo lote. Con ?dryRun=true dice qué saldría y qué falta, sin aprobar nada. Al confirmar quedan Aprobadas y el aviso a MS-09 se guarda en la misma operación. |
| En esta épica | Aprobar de 1 a 500 categorías para que viajen |
| Quién la llama | BFF del portal |
| Permiso | Category manager con la capacidad de aprobar categorías (MS-13) |
| Cabeceras | Authorization, X-Request-Id, Idempotency-Key |
| Parámetros | dryRun = False |
| Escribe en | categories, governance_outbox |
| Publica | catalog.governance.changed/CATEGORY_UPSERTED |
Request
{
"categoryIds": [
"catst10045",
"catst10046"
],
"comment": "Rama de camisas para primavera"
}
Response · 200
{
"success": true,
"data": {
"approved": 2,
"blocked": 0,
"items": [
{
"categoryId": "catst10045",
"result": "APPROVED",
"status": "APROBADA",
"version": 2,
"travels": "FULL"
},
{
"categoryId": "catst10046",
"result": "APPROVED",
"status": "APROBADA",
"version": 2,
"travels": "FULL"
}
]
}
}
Errores
| HTTP | Código | Cuándo |
|---|---|---|
| 422 | BUSINESS_ERROR | Una categoría no tiene sus mínimos o su sitio, o su padre no está aprobado: no se aprueba nada del lote |
| 409 | NOT_EDITABLE_IN_STATUS | Alguna está Desligada o ya Aprobada sin cambios |
| 403 | AUTHORIZATION_ERROR | Quien llama no tiene la capacidad de aprobar categorías |
| 400 | VALIDATION_ERROR | Más de 500 categorías |
Las que usa y construye otra épica (14)
Su request y su response están en la página de la épica que la construye.
| API | Método y ruta | Para qué, aquí | La construye |
|---|---|---|---|
| API-01-046 | GET /api/v2/brands | Leer los sitios con su código de departamento y su ID de catálogo | FT-REF-01 · Datos de referencia administrables |
| API-01-030 | GET /api/v2/catalog-imports/profiles/{p}/template | Bajar la plantilla del perfil de carga de categorías web | FT-PLT-09 · Carga masiva de configuración de plantillas con… |
| API-01-031 | POST /api/v2/catalog-imports | Subir y revisar sin escribir la carga masiva (lotes de hasta 500) | FT-PLT-09 · Carga masiva de configuración de plantillas con… |
| API-01-032 | POST /api/v2/catalog-imports/{id}/apply | Confirmar la carga masiva | FT-PLT-09 · Carga masiva de configuración de plantillas con… |
| API-01-033 | GET /api/v2/catalog-imports/{id} | Resultado de la carga por renglón | FT-PLT-09 · Carga masiva de configuración de plantillas con… |
| API-01-053 | POST /internal/governance/bulk-load/{coleccion} | Carga inicial por ID sin avisos | FT-PLT-01 · Fundamentos de gobierno de plantillas |
| API-13-001 | GET /internal/identity/permissions?userId= | Permisos efectivos de quien llama | FT-USR-01 · Identidad y autenticación de usuarios internos… |
| API-05-001 | GET /api/v2/audit/entities/{entityType}/{entityId}/events | La historia por campo que compone la ficha de la categoría | FT-ARQ-02 · Bitácora inmutable y versionado de producto, plantilla y… |
| API-02-016 | POST /api/v2/products/classifications/plans | Reasignar la categoría web de los productos que quedarían huérfanos | FT-CAT-03 · Categorización de productos |
| API-02-057 | POST /api/v2/product-plans/{planId}/apply | Confirmar la reasignación | FT-FLU-01 · Motor de estados configurable con dos carreteras y reglas… |
| API-03-001 | POST /api/v2/migration/runs | Corrida de migración de categorías | FT-MIG-01 · Perfilado y limpieza previa |
| API-03-003 | GET /api/v2/migration/reconciliation?runId= | Conciliación contra los conteos de P360 | FT-MIG-02 · Migración de plantillas, atributos y listas de valores |
| API-03-004 | GET /api/v2/migration/quarantine | Cuarentena: nivel 6, IDs que sólo difieren en mayúsculas, huérfanos | FT-MIG-03 · Migración inicial de productos desde P360 |
| API-02-114 | POST /internal/product/classifications/impact | Qué productos quedarían sin categoría web activa | FT-CAT-06 · Agrupadores y corrección masiva de categorías con… |
6. Eventos
Publica
| Evento | Cuándo | Quién lo consume |
|---|---|---|
catalog.governance.changed/CATEGORY_UPSERTED | Se crea, se cambia o se aprueba una categoría (lleva su estatus; sólo viaja la Aprobada) | FT-ARQ-02, FT-CAT-02 |
catalog.governance.changed/CATEGORY_MOVED | Una rama cambia de padre o regresa de «unlinked» a un árbol activo | FT-ARQ-02, FT-CAT-02 |
catalog.governance.changed/CATEGORY_UNLINKED | Se desliga una categoría: uno por cada categoría del subárbol | FT-CAT-02, FT-CAT-06, FT-RAC-07 |
Consume
| Evento | Para qué | Quién lo publica |
|---|---|---|
catalog.governance.changed/CATEGORY_UPSERTED | MS-05 guarda el cambio por campo y MS-09 publica la categoría aprobada | esta misma épica |
catalog.governance.changed/CATEGORY_UNLINKED | MS-02 quita la categoría de sus productos y MS-09 manda link=false | 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 |
|---|---|---|---|
categories | MS-01 | Cada categoría web con su lugar en el árbol y su estado. | escribe |
id_sequences | MS-01 | El siguiente identificador de plantillas y catálogos. | escribe |
import_jobs | MS-01 | Cargas masivas de configuración del catálogo, con revisión previa y resultado por fila. | escribe |
governance_receipts | MS-01 | El recibo de cada escritura confirmada en el catálogo (antes operation_receipts). | escribe |
governance_outbox | MS-01 | Los cambios del catálogo que esperan salir como evento (antes outbox). | escribe |
products | MS-02 | La familia del producto con sus variantes, valores, imágenes (URL), clasificaciones, estado, tareas abiertas y rechazos. | escribe |
brands | MS-01 | Los sitios (banners): Liverpool, Suburbia y los demás. | lee |
taxonomies | MS-01 | Los árboles: plantillas, grupos de artículo y sitios web. | lee |
8. Historias que cubre
| Historia | Prioridad | Cómo la cubre |
|---|---|---|
| FT-CAT-01·H1 · Crear categorías con un asistente que valide nivel y mínimos | P1 | pasos 1, 3, 4, 5, 6; API-01-040 |
| FT-CAT-01·H2 · Desligar sin borrar | P1 | flujo «Mover una rama o desligarla sin dejar huérfanos»; API-01-043 |
| FT-CAT-01·H3 · Mover ramas y reasignar rutas sin exportar ni recargar archivos | P1 | flujo «Mover una rama o desligarla sin dejar huérfanos»; API-01-042 |
| FT-CAT-01·H4 · Crear y modificar en masa con validación previa | P1 | flujo «Carga masiva con revisión previa»; API-01-030, API-01-031, API-01-032, API-01-033 |
| FT-CAT-01·H5 · Cargar el árbol de la revisión 1 sin la deuda que se retire | P1 | flujo «Carga inicial del árbol desde P360»; API-01-053, API-03-001, API-03-003, API-03-004 |
| FT-CAT-01·H6 · Aprobar una categoría antes de que salga, y que sólo perfiles autorizados editen | P2 | pasos 4, 7, 8, 9, 10; API-01-201, API-13-001 |
| FT-CAT-01·H7 · Deslinquear como acción explícita | P1 | flujo «Mover una rama o desligarla sin dejar huérfanos»; API-01-043, API-01-042, API-01-035 |
| FT-CAT-01·H8 · Sitio de categorías retiradas | P1 | flujo «Mover una rama o desligarla sin dejar huérfanos»; API-01-042 |
| FT-CAT-01·H9 · Ningún producto sin categoría | P1 | flujo «Mover una rama o desligarla sin dejar huérfanos»; API-02-114, API-02-016 |
| FT-CAT-01·H10 · Modelo de la categoría web | P1 | pasos 5, 6, 10; API-BFF-111, API-01-036 |
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-01-1. MS-01 es el único dueño del árbol (categories) y de los sitios, incluido el indicador de publicada de cada categoría, que actualiza con el acuse de MS-09; MS-09 no guarda categorías, sólo el detalle de cada envío. Las pantallas de hoy que llaman al servicio de categorías y a M4 pasan una sola vez al BFF en R1 y esas rutas se apagan con tráfico cero; las de /api/v1 no se conservan. Por qué: Hoy dos servicios escriben el mismo documento y se pisan el estado de publicación. Con un dueño, quien necesita «hoja publicada» (FT-CAT-03, FT-CAT-06, FT-INT-07) pregunta en un solo lugar, como ya fijaba contratos-api §3.1. Se apoya en ADR-12, PD-07, DUD-07.
- D-CAT-01-2. Dos estatus, como acordó el taller: Creada (guardada, no viaja) y Aprobada (viaja); Desligada es un estado de vida aparte. Aprobar es una acción explícita, en lotes de hasta 500, con la capacidad de aprobar de MS-13; puede hacerla quien creó. Una categoría ya aprobada viaja con cada cambio que confirma un perfil autorizado: la vista previa de esa confirmación es su revisión. Por qué: Cumple «mientras no se aprueba no viaja» sin un segundo aprobador que nadie ha definido; pedir aprobación de cada cambio duplicaría trabajo y obligaría a guardar dos versiones de cada categoría. Se apoya en RF-CAT-19, DUD-01, DUD-22.
- D-CAT-01-3. El ID lo genera MS-01 como catst y un consecutivo, con una secuencia que arranca arriba del mayor migrado; no cambia, no se reutiliza y se compara sin mayúsculas. El nivel se calcula del padre (1 a 5, sin saltos) y no se captura. Por qué: Un ID distinto es otra categoría para la tienda y uno reutilizado cuelga productos donde no van; el nivel capturado es la deuda de hoy (0 de 7,733 documentos con nivel). Se apoya en PD-20, RF-CAT-19.
- D-CAT-01-4. Desligar, o mover a «unlinked», vale con hijas, con productos y desde el nivel 1: todo el subárbol pasa a Desligada, la tienda recibe link=false y los productos sólo pierden esa relación; nada se borra. Se bloquea sólo si algún producto se quedaría sin ninguna categoría web activa, hasta reasignarlo. Las fechas sólo controlan la visibilidad: una categoría vencida no se desliga sola ni pierde productos. Por qué: El negocio quiere proteger al producto, no al nodo. Desligar por fecha borraría las relaciones de las categorías de temporada que regresan cada año. Se apoya en PD-28, DMS-60.
- D-CAT-01-5. Las categorías conservan la forma de comando directo con ?dryRun=true e If-Match que fija contratos-api §2.1, en vez de plan y confirmación: cada comando toca una categoría o una rama y su vista previa no se guarda. Lo masivo va por la carga de catálogo (API-01-031), que revisa todo antes de escribir y rechaza el lote si un renglón falla. API-01-045 no se construye: la historia la sirve MS-05 y la compone el BFF (API-BFF-111). Por qué: Respeta la ruta base sin duplicar caminos: un solo mecanismo de carga masiva para todo el catálogo y un solo dueño de la bitácora. Se apoya en ADR-12, DMS-61.
- D-CAT-01-6. Carga inicial: se migra el árbol vivo de la revisión 1 con sus ID tal cual, incluidas las cat que tienen productos o están en árboles activos; no se migra lo que cuelga de Unlinked; los nodos de nivel 6, los pares que sólo difieren en mayúsculas y los huérfanos entran a la cuarentena de MS-03 para que Category decida antes del corte. La carga no publica: la tienda ya tiene esas categorías. Por qué:* Cambiar un ID rompería la tienda; la cuarentena deja visible la deuda sin frenar la carga. PD-20 sigue abierta y aquí se propone cómo cerrarla. Se apoya en PD-20, DEC-ARQ-07, RN-MIG-05.
- D-CAT-01-7. Dentro del PIM las fechas son nativas y en UTC; el texto yyyy-MM-dd HH:mm:ss en hora de Ciudad de México sólo existe en el mensaje a la tienda, que arma MS-09. Por qué: El texto en la base es lo que hoy rompe los filtros por fecha de governance; el contrato con la tienda no cambia. Se apoya en DEC-ARQ-11, DUD-20.
- D-CAT-01-8. El evento de categoría lleva el objeto completo (los campos que viajan, sitio e ID de catálogo ya resueltos) y la lista de cambios, como excepción a la regla de eventos sólo con identificadores. Por qué: Así MS-09 arma el mensaje sin leer la base de Catálogo, y una categoría pesa poco. Se apoya en ADR-12, PD-07.
- D-CAT-01-9. La restricción es un dato de la categoría: documentos, requisitos y si bloquea o avisa. La administra Category con API-01-041 y MS-02 la aplica al ligar productos y al aprobarlos (FT-CAT-03). Por qué: Quien liga un producto necesita ver los requisitos en el momento; guardarlos en otro lado obligaría a reconciliar dos listas. Se apoya en FT-CAT-03, ADR-12.
- D-CAT-01-10. La carga masiva de categorías usa la revisión y la confirmación de la carga de catálogo, pero no abre una solicitud de cambio: aplica como los comandos directos (las nuevas quedan Creadas) y Category las aprueba en lote con API-01-201. Por qué: Las categorías ya tienen su aprobación propia (D-CAT-01-2); una segunda aprobación por la solicitud del catálogo sería doble trabajo para lo mismo. Se apoya en RF-CAT-19, DEC-ARQ-12.
Qué falta decidir
| ID | Qué falta |
|---|---|
| PD-20 | Ratificar con Category y Gobierno de Producto qué se migra, se congela o se retira del árbol heredado (propuesta en D-CAT-01-6). |
| PD-28 | Ratificar que la guarda protege al producto y no al nodo (D-CAT-01-4). |
| DUD-01 | Confirmar con Category si un cambio a una categoría ya aprobada necesita otra aprobación y quién aprueba. |
| DUD-22 | Cerrar con FT-USR-02 qué perfiles crean, mueven y aprueban categorías. |
| DUD-20 | Probar en QA el valor de rootIndex vacío y el borrado de un campo vaciado. |
También siguen abiertos: PEN-40, Registrar que las rutas viven en el PIM: MS-01 es su dueño (ADR-12).
Fuentes
- [D]
trazabilidad/features/FT-CAT-01-administracion-arbol-categorias-web.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.