← Arquitectura por épica

FT-PRD-01 · Modelo canónico de producto

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-PRD-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-PRD-01.json, generada con generar.py; la ficha de la épica es trazabilidad/features/FT-PRD-01-modelo-canonico-de-producto.md.

En 30 segundos

Todo producto del PIM se guarda igual, venga de donde venga: una familia (genérico, individual, set o look) con sus variantes adentro, un tipo explícito, llaves que no se repiten (SKU inmutable y EAN único por negocio), relaciones de ida y vuelta y banderas con vigencia. Esta épica fija ese modelo y las APIs para crear, leer, ligar y comparar productos, para que el portal, Foro, SAP, la tienda y la migración vean la misma ficha.

  • Módulo dueño: MS-02 Producto. También participan: MS-01 Catálogo, MS-03 Migración y convivencia.
  • Etapa T · Transversal. Recibe «PE-C · Catálogo vigente», «PE-T · Usuario con permisos y bitácora» y entrega «PE-1 · Propuesta en Borrador».
  • APIs: construye 17 y usa 6 que construyen otras épicas.
  • Eventos: publica 5 y consume 1.

1. Dónde encaja

Recibe

DeQué
PE-C · Catálogo vigenteLas plantillas y su vista efectiva en Catálogo, para tipar y validar cada valor y saber qué hereda cada variante.
PE-T · Usuario con permisos y bitácoraQuien crea o cambia un producto ya se identificó y tiene permiso; cada cambio queda en la bitácora.

Entrega

AQué
PE-1 · Propuesta en BorradorEl documento de la familia en products, en Borrador, con negocio, tipo, origen y dueño, venga de la puerta que venga.
FT-PRD-03 · EANEl registro de identificadores por negocio, donde cada EAN se reserva en la misma transacción que el producto.
FT-PRD-02 · Creación de SKU con SAPLas variantes con identificador propio, listas para recibir el SKU que manda SAP y que después nadie cambia.
FT-MIG-03 · Migración inicial de productos desde P360La forma del documento, las reglas de integridad y la derivación del tipo con que se carga lo migrado.
FT-INT-01 · Publicación a e-commerceLa familia con relaciones, banderas con vigencia y variantes publicables, de donde sale la foto para la tienda.
FT-ARQ-01 · Núcleo headlessEl modelo canónico y sus APIs de alta y lectura, sobre los que se arma la API única de producto para cualquier origen.
FT-FLU-01 · Motor de estados configurable con dos carreteras y reglas…El documento del producto en products, donde viven el estado, las tareas abiertas y los rechazos que mueve el motor.
FT-ACT-04 · Variantes a partir de imágenes y herencia de imágenes por…La familia con sus variantes y el color de cada una, que es la llave de la herencia.
FT-RAC-07 · Reportes, exportes, importes y búsquedas por atributosEl modelo de producto que se exporta: familia, variantes, valores por código, clasificaciones y dueño.
FT-REF-03 · Servicio de matchingEl modelo de producto y el registro de EAN por negocio contra los que se busca, servidos por MS-02.

Cambios de estado que hace

TransiciónDe → aCuándo
T01Inicio → BorradorEl proveedor o seller guarda por primera vez (pantalla, archivo, plantilla en línea o API).

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

Nuevo PIM · un solo sistema

Pantallas y sistemas

Personas

Alta de un producto
Confirmar un plan de Producto
y 5 más

Vista efectiva de una plantilla por n…

PRODUCT_CREATED
PRODUCT_UPDATED
y 1 más

Carga interna de la migración

Cuarentena de la migración

Proveedor

Gobierno de Datos

QA y Category

Entrada Única · Vendor Center

BFF del portal

MS-01 Catálogo

★ MS-02 Producto

MS-03 Migración y convivencia

Base pim

Eventos

Contexto de FT-PRD-01

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.

EventosBase·pimMS-01 CatálogoMS-02 ProductoEntrada Única · Vendor CenterEventosBase·pimMS-01 CatálogoMS-02 ProductoEntrada Única · Vendor CenterProveedorBFFGobierno de Datos1. Inicia el alta de un producto nuevo2. Pide el plan del alta con negocio, tipo y dueñoPOST /api/v2/products/plans3. Si ya trae plantilla, lee su vista efectivaGET /api/v2/taxonomies/PRODUCT/nodes/{id}/effective4. Confirma el altaPOST /api/v2/product-plans/{planId}/apply5. Guarda la familia en Borrador y reserva sus llaves6. Avisa que nació el productoPRODUCT_CREATED7. Abre la ficha de la familia8. Lee la familia completa como una unidadGET /api/v2/products/{id}9. Lee una variante con lo heredado marcadoGET /api/v2/products/{id}/variants/{variantId}10. Captura un modelo propio en esa variante11. Pide el plan del valor propio de la variantePOST /api/v2/products/{id}/values/plans12. Confirma, las hermanas siguen heredando del genéricoPOST /api/v2/product-plans/{planId}/apply13. Avisa el cambio de la variantePRODUCT_UPDATEDProveedorBFFGobierno de Datos
Flujo principal de FT-PRD-01
Los pasos en texto
PasoDeAQué haceAPI o evento
1ProveedorEntrada Única · Vendor CenterInicia el alta de un producto nuevo—
2Entrada Única · Vendor CenterMS-02 Producto (por el BFF)Pide el plan del alta con negocio, tipo y dueñoAPI-02-005
3MS-02 ProductoMS-01 CatálogoSi ya trae plantilla, lee su vista efectivaAPI-01-028
4Entrada Única · Vendor CenterMS-02 Producto (por el BFF)Confirma el altaAPI-02-057
5MS-02 ProductoBase pimGuarda la familia en Borrador y reserva sus llaves—
6MS-02 ProductoEventosAvisa que nació el productocatalog.product.changed/PRODUCT_CREATED
7Gobierno de DatosEntrada Única · Vendor CenterAbre la ficha de la familia—
8Entrada Única · Vendor CenterMS-02 Producto (por el BFF)Lee la familia completa como una unidadAPI-02-002
9Entrada Única · Vendor CenterMS-02 Producto (por el BFF)Lee una variante con lo heredado marcadoAPI-02-004
10Gobierno de DatosEntrada Única · Vendor CenterCaptura un modelo propio en esa variante—
11Entrada Única · Vendor CenterMS-02 Producto (por el BFF)Pide el plan del valor propio de la varianteAPI-02-012
12Entrada Única · Vendor CenterMS-02 Producto (por el BFF)Confirma; las hermanas siguen heredando del genéricoAPI-02-057
13MS-02 ProductoEventosAvisa el cambio de la variantecatalog.product.changed/PRODUCT_UPDATED

3.1 Una variante que no procede

EventosBase·pimMS-02 ProductoEntrada Única · Vendor CenterEventosBase·pimMS-02 ProductoEntrada Única · Vendor CenterQA y CategoryBFF1. Marca una variante como «no procede» con su motivo2. Pide el plan sobre esa variantePOST /api/v2/products/{id}/root/plans3. Confirma la marcaPOST /api/v2/product-plans/{planId}/apply4. Saca sólo esa variante de lo publicable5. Avisa el cambio de la familiaPRODUCT_UPDATED6. Pide publicar la familia sin esa variantePUBLICATION_REQUESTEDQA y CategoryBFF
Una variante que no procede

3.2 Relaciones y banderas con vigencia

EventosBase·pimMS-02 ProductoEntrada Única · Vendor CenterEventosBase·pimMS-02 ProductoEntrada Única · Vendor CenterQA y CategoryBFF1. Agrega un producto a un grupo de venta cruzada2. Pide el plan del grupoPOST /api/v2/product-groups/plans3. Bloquea al que ya está en otro grupo igual4. Confirma el grupo con los miembros válidosPOST /api/v2/product-plans/{planId}/apply5. Avisa el cambio a cada miembroPRODUCT_UPDATED6. Enciende la preventa con inicio y fin7. Pide el plan de la bandera con su vigenciaPOST /api/v2/products/{id}/root/plans8. Confirma la banderaPOST /api/v2/product-plans/{planId}/applyQA y CategoryBFF
Relaciones y banderas con vigencia

3.3 Producto migrado sin tipo

Entrada Única · Vendor CenterBase·pimMS-02 ProductoMS-03 Migración y convivenciaEntrada Única · Vendor CenterBase·pimMS-02 ProductoMS-03 Migración y convivenciaGobierno de DatosBFF1. Carga un lote de familias desde P360POST /internal/product/bulk-load/products2. Deriva el tipo con tipo SAP, negocio y variantes3. Da al genérico de Marketplace su SKU 999 más identificador4. Manda a cuarentena lo que no tiene tipo o negocio5. Revisa la cuarentena y corrige cada caso6. Pide la lista de cuarentena con su motivoGET /api/v2/migration/quarantineGobierno de DatosBFF
Producto migrado sin tipo

4. Si algo sale mal

  • Alguien intenta cambiar el SKU de una variante. El plan responde 422: el SKU sólo lo escribe la respuesta de SAP (FT-PRD-02) y es inmutable. Quien lo intentó ve el motivo; no se guarda nada.
  • Dos altas guardan el mismo EAN en el mismo negocio al mismo tiempo. El índice único del registro de identificadores deja pasar sólo la primera; la segunda recibe 422 con «El código EAN ya existe en catalogación para este negocio» y la opción de pedir su liberación (FT-PRD-03).
  • Un producto migrado no tiene tipo o negocio. No entra al hilo: Migración lo deja en cuarentena con su motivo y Gobierno de Datos lo corrige desde la lista; una familia en cuarentena no se publica ni se ofrece como aprobable.
  • Se agrega un producto a un segundo grupo de venta cruzada. El plan lo marca bloqueado con el grupo donde ya está; se aplica sólo a los miembros válidos y Category ve cuál quedó fuera.
  • Dos personas cambian la misma familia a la vez. La segunda confirmación responde 409 VERSION_CONFLICT; el portal recarga la ficha y la persona decide sobre la versión nueva.

5. APIs

Las que construye esta épica (17)

POST /api/v2/products/plans · Alta de un producto
CampoValor
APIAPI-02-005 · MS-02 Producto
Para quéPrepara el alta de un producto en Borrador desde cualquier puerta (pantalla, archivo o API) con su negocio, tipo, origen, dueño, datos de identificación y la evaluación de la identificación (evaluationId), y dice qué va a crear y qué EAN reservará, sin escribir nada. Con parentId agrega una variante a una familia que ya existe.
En esta épicaPreparar el alta de un producto en Borrador desde cualquier puerta
Quién la llamaBFF del portal, Gateway de terceros /partner/v1
PermisoENT-E; el proveedor o seller sólo para sí mismo
CabecerasAuthorization, X-Request-Id, Idempotency-Key
Publicacatalog.product.changed/PRODUCT_CREATED
También la usanFT-ARQ-01, FT-FLU-02, FT-FLU-03, FT-PRD-09, FT-REF-02, FT-REF-03, FT-REF-04

Request

{
  "business": "MER",
  "type": "GENERIC",
  "origin": "PORTAL",
  "ownerId": "PROV-0001",
  "name": "Licuadora de 10 velocidades",
  "identification": {
    "brand": "MARCA01",
    "model": "LX-200",
    "eans": [
      "7501234567893"
    ]
  },
  "evaluationId": "evl-5d21c8",
  "templateId": null,
  "parentId": null
}

Response · 200

{
  "success": true,
  "data": {
    "planId": "pln-a41c07",
    "expiresAt": "2026-10-18T16:40:00Z",
    "willCreate": {
      "type": "GENERIC",
      "business": "MER",
      "status": "BORRADOR",
      "variants": 0
    },
    "eans": [
      {
        "ean": "7501234567893",
        "type": "HE",
        "result": "RESERVE"
      }
    ],
    "warnings": [
      {
        "code": "NO_VARIANTS_YET",
        "message": "Un genérico puede guardarse sin variantes; para enviarlo necesitará al menos una"
      }
    ],
    "applyUrl": "/api/v2/product-plans/pln-a41c07/apply"
  }
}

Errores

HTTPCódigoCuándo
422VALIDATION_ERROREl tipo no corresponde al negocio (por ejemplo, GENERIC_MKP en Mercaderías) o falta el dueño
422BUSINESS_ERRORUn EAN ya existe en catalogación para ese negocio
403AUTHORIZATION_ERRORQuien llama no puede crear productos para ese dueño o negocio
GET /api/v2/products/{id} · Ficha del producto
CampoValor
APIAPI-02-002 · MS-02 Producto
Para quéDevuelve la familia completa como una unidad: tipo, negocio, dueño, estado y tareas abiertas, plantilla y categorías, textos con su origen, banderas, relaciones, completitud y el resumen de sus variantes. Con view se pide sólo una parte (overview, attributes, texts, images, classification).
En esta épicaLeer la familia completa como una unidad
Quién la llamaBFF del portal, Gateway de terceros /partner/v1
PermisoENT-L; el proveedor o seller dueño
CabecerasAuthorization, X-Request-Id
Parámetrosid = PRD-0004812, view = overview
También la usanFT-ACT-01, FT-INT-06, FT-PRD-04, FT-PRD-09, FT-PRD-10, FT-RAC-03, FT-USR-02

Request

null

Response · 200

{
  "success": true,
  "data": {
    "productId": "PRD-0004812",
    "type": "GENERIC",
    "business": "MER",
    "origin": "PORTAL",
    "ownerId": "PROV-0001",
    "version": 7,
    "status": {
      "code": "BORRADOR",
      "since": "2026-10-16T18:02:00Z"
    },
    "openTasks": [],
    "classification": {
      "templateId": "EU4-0412",
      "webCategoryIds": [],
      "autoAssignedWebCategoryIds": []
    },
    "master": {
      "brand": "MARCA01",
      "supplierId": "PROV-0001",
      "supplierPartNumber": "LX-200",
      "itemGroup": null
    },
    "texts": {
      "productName": "Licuadora MARCA01 LX-200",
      "productNameSource": "RULE",
      "titleWithoutBrand": "Licuadora LX-200",
      "descriptionLong": null
    },
    "flags": [
      {
        "code": "PRESALE",
        "value": true,
        "from": "2026-11-01T06:00:00Z",
        "to": "2026-11-30T06:00:00Z"
      }
    ],
    "relations": {
      "crossSellGroupId": null,
      "configurableGroupId": null
    },
    "completeness": {
      "mandatory": {
        "present": 18,
        "total": 22,
        "status": "MEASURED"
      }
    },
    "variants": [
      {
        "variantId": "VAR-0004812-01",
        "sku": null,
        "ean": "7501234567893",
        "axes": {
          "COLOR": "NEGRO"
        },
        "publishable": true
      },
      {
        "variantId": "VAR-0004812-02",
        "sku": null,
        "ean": "7501234567909",
        "axes": {
          "COLOR": "ROJO"
        },
        "publishable": true
      }
    ]
  }
}

Errores

HTTPCódigoCuándo
404NOT_FOUNDEl producto no existe o quien llama no lo puede ver
GET /api/v2/products/{id}/variants · Variantes del producto
CampoValor
APIAPI-02-003 · MS-02 Producto
Para quéLista las variantes de una familia con su SKU, EAN y tipo de código, sus ejes (talla, color), si tiene imagen, si se puede publicar y cuántos valores hereda del genérico.
En esta épicaListar las variantes con SKU, EAN y ejes
Quién la llamaBFF del portal, Gateway de terceros /partner/v1
PermisoENT-L; el proveedor o seller dueño
CabecerasAuthorization, X-Request-Id
Parámetrosid = PRD-0004812
También la usanFT-ACT-04

Request

null

Response · 200

{
  "success": true,
  "data": {
    "items": [
      {
        "variantId": "VAR-0004812-01",
        "sku": "1000004812",
        "ean": "7501234567893",
        "eanType": "HE",
        "axes": {
          "COLOR": "NEGRO"
        },
        "hasImage": true,
        "publishable": true,
        "inheritedValues": 14,
        "ownValues": 3
      },
      {
        "variantId": "VAR-0004812-02",
        "sku": "1000004813",
        "ean": "7501234567909",
        "eanType": "HE",
        "axes": {
          "COLOR": "ROJO"
        },
        "hasImage": false,
        "publishable": false,
        "notPublishableReason": "NO_PROCEDE",
        "inheritedValues": 14,
        "ownValues": 4
      }
    ],
    "page": 1,
    "size": 50,
    "total": 2
  }
}

Errores

HTTPCódigoCuándo
404NOT_FOUNDEl producto no existe o quien llama no lo puede ver
GET /api/v2/products/{id}/variants/{variantId} · Una variante con lo propio y lo heredado
CampoValor
APIAPI-02-004 · MS-02 Producto
Para quéDevuelve una variante con cada valor marcado como heredado del genérico o propio; lo propio puede ser un eje (talla, color) o un valor que sobrescribe al genérico (modelo, precio, costo).
En esta épicaLeer una variante con lo propio y lo heredado
Quién la llamaBFF del portal, Gateway de terceros /partner/v1
PermisoENT-L; el proveedor o seller dueño
CabecerasAuthorization, X-Request-Id
Parámetrosid = PRD-0004812, variantId = VAR-0004812-02

Request

null

Response · 200

{
  "success": true,
  "data": {
    "variantId": "VAR-0004812-02",
    "productId": "PRD-0004812",
    "sku": "1000004813",
    "ean": "7501234567909",
    "version": 7,
    "values": [
      {
        "attributeId": "MARCA",
        "value": {
          "code": "MARCA01"
        },
        "inherited": true,
        "from": "PRD-0004812"
      },
      {
        "attributeId": "PROVEEDOR",
        "value": {
          "code": "PROV-0001"
        },
        "inherited": true,
        "from": "PRD-0004812"
      },
      {
        "attributeId": "MODELO_COMERCIAL",
        "value": {
          "text": "LX-200-R"
        },
        "inherited": false,
        "overridesParent": true
      },
      {
        "attributeId": "COLOR",
        "value": {
          "code": "ROJO"
        },
        "inherited": false,
        "axis": true
      }
    ],
    "publishable": true
  }
}

Errores

HTTPCódigoCuándo
404NOT_FOUNDLa variante no existe o no pertenece a ese producto
POST /api/v2/products/{id}/root/plans · Cambiar datos generales
CampoValor
APIAPI-02-011 · MS-02 Producto
Para quéPrepara un cambio a los datos generales de un producto o de una de sus variantes (número de parte, banderas con vigencia, la marca de «no procede» de una variante) y muestra el antes y el después. SKU, EAN y negocio son de sólo lectura aquí.
En esta épicaCambiar datos generales, banderas con vigencia y la marca «no procede»
Quién la llamaBFF del portal
PermisoENT-E; la marca «no procede» sólo QA
CabecerasAuthorization, X-Request-Id, Idempotency-Key, If-Match
Parámetrosid = PRD-0004812
Publicacatalog.product.changed/PRODUCT_UPDATED, catalog.product.publication/PUBLICATION_REQUESTED
También la usanFT-PRD-10

Request

{
  "fields": {
    "flags": [
      {
        "code": "PRESALE",
        "value": true,
        "from": "2026-11-01T06:00:00Z",
        "to": "2026-11-30T06:00:00Z"
      }
    ]
  }
}

Response · 200

{
  "success": true,
  "data": {
    "planId": "pln-61b7d0",
    "expiresAt": "2026-10-18T16:50:00Z",
    "changes": [
      {
        "path": "flags.PRESALE",
        "before": null,
        "after": {
          "value": true,
          "from": "2026-11-01T06:00:00Z",
          "to": "2026-11-30T06:00:00Z"
        }
      }
    ],
    "republish": false,
    "applyUrl": "/api/v2/product-plans/pln-61b7d0/apply"
  }
}

Errores

HTTPCódigoCuándo
422VALIDATION_ERRORSe intenta cambiar el SKU, el EAN o el negocio (el EAN va por su flujo y el negocio por su comando)
422VALIDATION_ERRORUna bandera sin fecha de fin o con el fin antes del inicio
409VERSION_CONFLICTAlguien cambió el producto después de abrirlo
POST /api/v2/product-groups/plans · Grupos de productos
CampoValor
APIAPI-02-106 · MS-02 Producto
Para quéPrepara crear un grupo (venta cruzada, configurable o look) o agregar y quitar miembros; revisa que cada producto esté en un solo grupo de cada tipo y dice a quiénes habrá que republicar.
En esta épicaCrear grupos de venta cruzada, configurables y looks, y cambiar sus miembros
Quién la llamaBFF del portal
PermisoENT-E (Category y Gobierno de Producto)
CabecerasAuthorization, X-Request-Id, Idempotency-Key
Publicacatalog.product.changed/PRODUCT_UPDATED, catalog.product.publication/PUBLICATION_REQUESTED
También la usanFT-PLT-08, FT-PRD-09

Request

{
  "action": "ADD_MEMBERS",
  "groupId": "CROSS_SELL::CS-0091",
  "type": "CROSS_SELL",
  "members": [
    "PRD-0004812",
    "PRD-0004990"
  ]
}

Response · 200

{
  "success": true,
  "data": {
    "planId": "pln-77d0e3",
    "expiresAt": "2026-10-18T17:25:00Z",
    "group": {
      "groupId": "CROSS_SELL::CS-0091",
      "type": "CROSS_SELL",
      "membersBefore": 3,
      "membersAfter": 4
    },
    "items": [
      {
        "productId": "PRD-0004812",
        "result": "OK"
      },
      {
        "productId": "PRD-0004990",
        "result": "BLOCKED",
        "reason": "Ya pertenece a CROSS_SELL::CS-0050: un producto sólo está en un grupo de venta cruzada"
      }
    ],
    "republish": [
      "PRD-0004812"
    ],
    "applyUrl": "/api/v2/product-plans/pln-77d0e3/apply"
  }
}

Errores

HTTPCódigoCuándo
404NOT_FOUNDEl grupo o algún miembro no existe
422BUSINESS_ERRORTodos los miembros quedan bloqueados: no hay nada que aplicar
GET /api/v2/product-groups/{groupId} · Leer un grupo de productos
CampoValor
APIAPI-02-107 · MS-02 Producto
Para quéDevuelve un grupo de venta cruzada, configurable o look con sus miembros y, si es configurable, el orden de su configuración.
En esta épicaLeer un grupo con sus miembros
Quién la llamaBFF del portal
PermisoENT-L
CabecerasAuthorization, X-Request-Id
ParámetrosgroupId = CONFIGURABLE::PRD-0006001

Request

null

Response · 200

{
  "success": true,
  "data": {
    "groupId": "CONFIGURABLE::PRD-0006001",
    "type": "CONFIGURABLE",
    "business": "MER",
    "configurableOrder": [
      "CAPACIDAD",
      "COLOR"
    ],
    "members": [
      {
        "productId": "PRD-0006002",
        "name": "Batidora MARCA01 BX-5 de 1.5 L negra"
      },
      {
        "productId": "PRD-0006003",
        "name": "Batidora MARCA01 BX-5 de 2 L negra"
      }
    ],
    "version": 4
  }
}

Errores

HTTPCódigoCuándo
404NOT_FOUNDEl grupo no existe
POST /api/v2/products/{id}/variants/link/plans · Ligar una variante a su genérico
CampoValor
APIAPI-02-014 · MS-02 Producto
Para quéPrepara ligar a esta familia una variante que hoy está suelta, y dice qué empieza a heredar del genérico y qué cambia en los dos productos.
En esta épicaLigar una variante suelta a su genérico
Quién la llamaBFF del portal
PermisoENT-E
CabecerasAuthorization, X-Request-Id, Idempotency-Key
Parámetrosid = PRD-0004812
Publicacatalog.product.changed/VARIANT_LINKED
También la usanFT-ACT-04, FT-RAC-02, FT-REF-02

Request

{
  "variantId": "VAR-0005120-01",
  "versions": {
    "PRD-0004812": 7,
    "PRD-0005120": 2
  }
}

Response · 200

{
  "success": true,
  "data": {
    "planId": "pln-3e8a21",
    "expiresAt": "2026-10-18T17:00:00Z",
    "from": "PRD-0005120",
    "to": "PRD-0004812",
    "inheritsFromNewParent": [
      "MARCA",
      "PROVEEDOR"
    ],
    "applyUrl": "/api/v2/product-plans/pln-3e8a21/apply"
  }
}

Errores

HTTPCódigoCuándo
422BUSINESS_ERRORLa variante ya pertenece a otro genérico: primero se desliga
422VALIDATION_ERRORLa variante tiene otra plantilla u otro negocio que el genérico
POST /api/v2/products/{id}/variants/unlink/plans · Desligar una variante
CampoValor
APIAPI-02-015 · MS-02 Producto
Para quéPrepara sacar una variante de su familia: queda como producto individual con el mismo identificador y su SKU, y conserva su historial.
En esta épicaDesligar una variante y dejarla como individual
Quién la llamaBFF del portal
PermisoENT-E
CabecerasAuthorization, X-Request-Id, Idempotency-Key
Parámetrosid = PRD-0004812
Publicacatalog.product.changed/VARIANT_UNLINKED

Request

{
  "variantId": "VAR-0004812-02",
  "version": 7
}

Response · 200

{
  "success": true,
  "data": {
    "planId": "pln-91c4f2",
    "expiresAt": "2026-10-18T17:00:00Z",
    "variantId": "VAR-0004812-02",
    "becomes": {
      "type": "INDIVIDUAL",
      "keepsSku": "1000004813"
    },
    "applyUrl": "/api/v2/product-plans/pln-91c4f2/apply"
  }
}

Errores

HTTPCódigoCuándo
422BUSINESS_ERROREs la última variante de un genérico ya enviado: un genérico enviado no se queda sin variantes
POST /api/v2/products/{id}/clone/plans · Clonar un producto
CampoValor
APIAPI-02-017 · MS-02 Producto
Para quéPrepara una propuesta nueva en Borrador copiada de otro producto, sin identificador, SKU, EAN, estados, tareas, historial ni categorías, para no capturar de cero algo parecido.
En esta épicaClonar un producto como propuesta nueva sin llaves ni estados
Quién la llamaBFF del portal
PermisoENT-E y lectura del producto de origen
CabecerasAuthorization, X-Request-Id, Idempotency-Key
Parámetrosid = PRD-0004812
Publicacatalog.product.changed/PRODUCT_CREATED

Request

{
  "business": "MER",
  "ownerId": "PROV-0001",
  "copy": [
    "attributes",
    "texts",
    "classification.templateId"
  ]
}

Response · 200

{
  "success": true,
  "data": {
    "planId": "pln-d40e66",
    "expiresAt": "2026-10-18T17:10:00Z",
    "willCreate": {
      "type": "GENERIC",
      "status": "BORRADOR",
      "variants": 0
    },
    "notCopied": [
      "sku",
      "ean",
      "status",
      "tasks",
      "history",
      "webCategoryIds"
    ],
    "applyUrl": "/api/v2/product-plans/pln-d40e66/apply"
  }
}

Errores

HTTPCódigoCuándo
404NOT_FOUNDEl producto de origen no existe
403AUTHORIZATION_ERRORQuien llama no puede ver el producto de origen
POST /api/v2/products/search · Buscar productos
CampoValor
APIAPI-02-001 · MS-02 Producto
Para quéBusca productos y propuestas con filtros combinados (negocio, estado, plantilla, categoría, dueño, con o sin variantes, valor de un atributo) y cuenta por valor; sirve a la consulta, a las bandejas y a las correcciones masivas.
En esta épicaBuscar productos con filtros combinados
Quién la llamaBFF del portal, Gateway de terceros /partner/v1
PermisoENT-L; el proveedor o seller sólo ve sus productos
CabecerasAuthorization, X-Request-Id
También la usanFT-ARQ-01, FT-CAT-04, FT-CAT-05, FT-FLU-04, FT-FLU-06, FT-PLT-03, FT-PRD-04, FT-PRD-05, FT-PRD-09, FT-RAC-07, FT-USR-01, FT-USR-02

Request

{
  "filters": [
    {
      "field": "business",
      "op": "EQ",
      "value": "MKP"
    },
    {
      "field": "status",
      "op": "IN",
      "values": [
        "APROBADO"
      ]
    },
    {
      "field": "classification.webCategoryIds",
      "op": "IS_EMPTY"
    }
  ],
  "page": 1,
  "size": 50,
  "facets": [
    "ownerId"
  ]
}

Response · 200

{
  "success": true,
  "data": {
    "items": [
      {
        "productId": "PRD-0004812",
        "type": "GENERIC",
        "business": "MKP",
        "name": "Playera MARCA01 deportiva de algodón",
        "status": "APROBADO",
        "templateId": "EU4-0412",
        "ownerId": "SELLER-0001",
        "variantCount": 6,
        "webCategoryIds": [],
        "updatedAt": "2026-10-17T21:40:00Z"
      }
    ],
    "page": 1,
    "size": 50,
    "total": 23104,
    "facets": {
      "ownerId": [
        {
          "value": "SELLER-0001",
          "count": 482
        }
      ]
    }
  }
}

Errores

HTTPCódigoCuándo
400VALIDATION_ERRORUn filtro usa un campo u operador que no existe
400VALIDATION_ERRORLa lista de IDs pasa de 10,000 sin venir como archivo; se rechaza completa, nunca se trunca
GET /api/v2/products/compare?ids= · Comparar productos
CampoValor
APIAPI-02-018 · MS-02 Producto
Para quéCompara lado a lado de 2 a 5 productos y devuelve sólo los campos donde difieren.
En esta épicaComparar productos lado a lado
Quién la llamaBFF del portal
PermisoENT-L
CabecerasAuthorization, X-Request-Id
Parámetrosids = PRD-0004812,PRD-0004990

Request

null

Response · 200

{
  "success": true,
  "data": {
    "items": [
      {
        "productId": "PRD-0004812"
      },
      {
        "productId": "PRD-0004990"
      }
    ],
    "differences": [
      {
        "path": "attributes.CAPACIDAD",
        "values": {
          "PRD-0004812": "1.5 L",
          "PRD-0004990": "2 L"
        }
      }
    ],
    "same": 41
  }
}

Errores

HTTPCódigoCuándo
400VALIDATION_ERRORMenos de 2 o más de 5 productos
404NOT_FOUNDAlgún producto no existe o no es visible para quien llama
GET /api/v2/products/{id}/compare-previous · Comparar con la versión anterior
CampoValor
APIAPI-02-019 · MS-02 Producto
Para quéCompara el producto con su versión anterior o con la última aprobada y lista cada campo que cambió, con el antes y el después.
En esta épicaComparar con la versión anterior o la última aprobada
Quién la llamaBFF del portal
PermisoENT-L
CabecerasAuthorization, X-Request-Id
Parámetrosid = PRD-0004812, against = LAST_APPROVED

Request

null

Response · 200

{
  "success": true,
  "data": {
    "productId": "PRD-0004812",
    "current": 12,
    "previous": 9,
    "previousReason": "APPROVAL",
    "changes": [
      {
        "path": "texts.titleWithoutBrand",
        "before": "Licuadora MARCA01 LX-200",
        "after": "Licuadora LX-200"
      }
    ]
  }
}

Errores

HTTPCódigoCuándo
404NOT_FOUNDEl producto no existe o no tiene versión anterior con qué comparar
GET /api/v2/products/{id}/media · Activos ligados al producto
CampoValor
APIAPI-02-020 · MS-02 Producto
Para quéDevuelve, por tipo y posición, qué activos tiene ligados el producto y cada variante, con su juego de trabajo y su juego congelado para publicar. El archivo y su validación técnica son de Activos digitales (API-11-007).
En esta épicaVer qué activos tiene ligados el producto, por tipo y posición
Quién la llamaBFF del portal
PermisoENT-L
CabecerasAuthorization, X-Request-Id
Parámetrosid = PRD-0004812
También la usanFT-ACT-02

Request

null

Response · 200

{
  "success": true,
  "data": {
    "productId": "PRD-0004812",
    "media": [
      {
        "owner": "PRD-0004812",
        "type": "PRINCIPAL",
        "position": 1,
        "assetId": "ast-7710",
        "isMain": true,
        "working": {
          "name": "PRD-0004812_1.jpg"
        },
        "frozen": {
          "name": "PRD-0004812_1_f.jpg",
          "frozenAt": "2026-10-12T15:00:00Z"
        }
      },
      {
        "owner": "VAR-0004812-02",
        "type": "DETALLE",
        "position": 2,
        "assetId": "ast-7712",
        "inheritedByColor": "ROJO",
        "working": {
          "name": "VAR-0004812-02_2.jpg"
        },
        "frozen": null
      }
    ]
  }
}

Errores

HTTPCódigoCuándo
404NOT_FOUNDEl producto no existe o no es visible para quien llama
GET /api/v2/saved-searches · Mis búsquedas guardadas
CampoValor
APIAPI-02-022 · MS-02 Producto
Para quéLista las búsquedas de productos que guardó quien llama y las que le compartieron.
En esta épicaVer las búsquedas guardadas propias y compartidas
Quién la llamaBFF del portal
PermisoENT-L
CabecerasAuthorization, X-Request-Id
Parámetrospage = 1, size = 50

Request

null

Response · 200

{
  "success": true,
  "data": {
    "items": [
      {
        "searchId": "srh-0042",
        "name": "Marketplace sin categoría",
        "filters": [
          {
            "field": "business",
            "op": "EQ",
            "value": "MKP"
          },
          {
            "field": "classification.webCategoryIds",
            "op": "IS_EMPTY"
          }
        ],
        "sharedWith": [
          "AREA:GOBIERNO-DATOS"
        ],
        "isOwner": true
      }
    ],
    "page": 1,
    "size": 50,
    "total": 1
  }
}

Errores

HTTPCódigoCuándo
403AUTHORIZATION_ERRORQuien llama no tiene lectura de productos
POST /api/v2/saved-searches · Guardar una búsqueda
CampoValor
APIAPI-02-023 · MS-02 Producto
Para quéGuarda una búsqueda de productos con su nombre y con quién se comparte (por área). Necesita la colección saved_searches, que pide FT-PRD-01.
En esta épicaGuardar y compartir una búsqueda
Quién la llamaBFF del portal
PermisoENT-L
CabecerasAuthorization, X-Request-Id, Idempotency-Key

Request

{
  "name": "Marketplace sin categoría",
  "filters": [
    {
      "field": "business",
      "op": "EQ",
      "value": "MKP"
    },
    {
      "field": "classification.webCategoryIds",
      "op": "IS_EMPTY"
    }
  ],
  "sharedWith": [
    "AREA:GOBIERNO-DATOS"
  ]
}

Response · 201

{
  "success": true,
  "data": {
    "searchId": "srh-0042"
  }
}

Errores

HTTPCódigoCuándo
400VALIDATION_ERRORUn filtro no es válido o el nombre ya existe para ese usuario
403AUTHORIZATION_ERRORSe comparte con un área a la que quien llama no pertenece
DELETE /api/v2/saved-searches/{id} · Borrar una búsqueda guardada
CampoValor
APIAPI-02-024 · MS-02 Producto
Para quéBorra una búsqueda guardada; sólo su dueño puede hacerlo.
En esta épicaBorrar una búsqueda guardada
Quién la llamaBFF del portal
PermisoEl dueño de la búsqueda
CabecerasAuthorization, X-Request-Id, Idempotency-Key
Parámetrosid = srh-0042

Request

null

Response · 200

{
  "success": true,
  "data": {
    "searchId": "srh-0042",
    "deleted": true
  }
}

Errores

HTTPCódigoCuándo
403AUTHORIZATION_ERRORQuien llama no es el dueño de la búsqueda
404NOT_FOUNDLa búsqueda no existe

Las que usa y construye otra épica (6)

Su request y su response están en la página de la épica que la construye.

APIMétodo y rutaPara qué, aquíLa construye
API-01-028GET /api/v2/taxonomies/PRODUCT/nodes/{id}/effectiveTipar los valores con la vista efectiva de la plantillaFT-PLT-06 · Configuración de atributos por plantilla y por negocio…
API-02-057POST /api/v2/product-plans/{planId}/applyConfirmar el alta, el valor, la bandera o el grupoFT-FLU-01 · Motor de estados configurable con dos carreteras y reglas…
API-02-012POST /api/v2/products/{id}/values/plansRevisar el valor propio de una variante antes de guardarloFT-RAC-01 · Motor de validación en línea y candados de datos…
API-02-056POST /internal/product/bulk-load/productsCargar lo migrado con las mismas reglas del modeloFT-ARQ-01 · Núcleo headless
API-03-004GET /api/v2/migration/quarantineVer la cuarentena de productos sin tipo o sin negocioFT-MIG-03 · Migración inicial de productos desde P360
API-03-008POST /internal/migration/identifier-blocksTomar bloques de identificadores de la secuencia compartida mientras P360 vivaFT-MIG-04 · Convivencia P360 ↔ nuevo PIM

6. Eventos

Publica

EventoCuándoQuién lo consume
catalog.product.changed/PRODUCT_CREATEDSe confirma un alta o un clonFT-ARQ-01
catalog.product.changed/PRODUCT_UPDATEDCambia un valor, una bandera, la marca «no procede» o la membresía a un grupoFT-ACT-05, FT-ARQ-02, FT-IA-01, FT-PLT-07, FT-PRD-05, FT-RAC-06
catalog.product.changed/VARIANT_LINKEDUna variante se liga a un genéricoFT-ACT-04, FT-PRD-07
catalog.product.changed/VARIANT_UNLINKEDUna variante sale de su familiaFT-ACT-04
catalog.product.publication/PUBLICATION_REQUESTEDUn aprobado cambia de forma que la tienda debe enterarse: variante que no procede o cambio en un configurableFT-CAT-05, FT-CAT-06, FT-INT-01, FT-INT-02, FT-INT-05, FT-INT-07, FT-PLT-08, FT-PRD-05, FT-RAC-03

Consume

EventoPara quéQuién lo publica
catalog.governance.changed/TEMPLATE_VIEW_CHANGEDMS-02 invalida su caché de vistas efectivas con que tipa y valida los valoresFT-IA-03, FT-MIG-02, FT-PLT-04, FT-PLT-05, FT-PLT-06, FT-PLT-09

7. Datos

Cada colección tiene un solo dueño, el único que escribe en ella.

ColecciónDueñoQué guardaEsta épica
productsMS-02La familia del producto con sus variantes, valores, imágenes (URL), clasificaciones, estado, tareas abiertas y rechazos.escribe
identifier_registryMS-02Unicidad de SKU y de EAN por negocio.escribe
product_groupsMS-02Looks, configurables y venta cruzada.escribe
product_sequencesMS-02El siguiente bloque de identificadores de producto.escribe
product_versionsMS-02Foto numerada del producto en cada versión.escribe
saved_searchesMS-02Búsquedas de productos guardadas por usuario, con su nombre, sus filtros y con qué áreas se comparten.escribe
product_receiptsMS-02El recibo de cada escritura confirmada en el producto (antes operation_receipts).escribe
product_outboxMS-02Los cambios del producto que esperan salir como evento (antes outbox).escribe
template_viewsMS-01La vista efectiva ya resuelta por plantilla y negocio: qué atributos pide y cuáles son obligatorios.lee
taxonomy_nodesMS-01Plantillas y nodos de grupo de artículo, con la configuración de atributos de cada plantilla.lee
mig_issuesMS-03Lo que no se pudo migrar y por qué.lee

8. Historias que cubre

HistoriaPrioridadCómo la cubre
FT-PRD-01·H1 · Familia completaP1pasos 2, 4, 5, 8; API-02-002, API-02-003, API-02-014
FT-PRD-01·H2 · Tipo explícitoP1pasos 2; flujo «Producto migrado sin tipo»; API-02-005, API-02-056
FT-PRD-01·H3 · LlavesP1pasos 5; API-02-011
FT-PRD-01·H4 · Variante que no procedeP2flujo «Una variante que no procede»
FT-PRD-01·H5 · Relaciones y banderasP2flujo «Relaciones y banderas con vigencia»; API-02-106, API-02-107
FT-PRD-01·H6 · Herencia del genérico en sus variantesP1pasos 9, 10, 11, 12; API-02-004

9. Decisiones de esta épica

Las tomó el TL como arquitecto; quedan como [Propuesta] hasta que el equipo de la épica las revise.

  • D-PRD-01-1. Un documento por familia en products con sus variantes adentro; cada variante tiene identificador propio, único en toda la colección. Nada del producto vive en otra base ni en los fronts. Por qué: Un solo dueño del dato: la ficha que ven el portal, Foro, SAP y la tienda es la misma y se lee en una consulta. Se apoya en DEC-PRD-02, ADR-12.
  • D-PRD-01-2. El tipo es un campo explícito y obligatorio: GENERIC, GENERIC_MKP, INDIVIDUAL, SET o LOOK; GENERIC_DUMMY y DIGITAL quedan reservados hasta que se firme la tabla. En el alta lo dice quien crea; en la migración se deriva con la regla de P360 y lo que no se resuelve va a cuarentena. Por qué: Hoy el tipo se deduce y unos 110 mil renglones no encajan; decidir atributos, flujo e integraciones necesita un dato, no una deducción. Se apoya en PD-17, DUD-19, RSG-15.
  • D-PRD-01-3. Un genérico puede guardarse en Borrador sin variantes; el candado de envío exige al menos una. La regla «genérico sin variantes se rechaza» se aplica al enviar, no al guardar. Por qué: La captura es por pasos y las variantes son un paso propio; rechazar al guardar obligaría a capturar todo de una vez. Se apoya en PD-29, FT-RAC-01.
  • D-PRD-01-4. El identificador del producto es texto y nunca cambia; mientras P360 viva sale de bloques de la secuencia compartida (API-03-008). El SKU sólo lo escribe la respuesta de SAP y es inmutable. El EAN es único por negocio en identifier_registry, escrito en la misma transacción que el producto; liberar nunca borra. Por qué: Sin regla de llaves se repiten duplicados (en Omega el SKU no es único) y la convivencia con P360 no puede chocar identificadores. Se apoya en PD-16, DUD-18, DEC-PRD-01, DEC-PRD-04.
  • D-PRD-01-5. La herencia se resuelve al leer: la variante muestra los valores comunes del genérico marcados como heredados y sólo guarda lo propio (ejes, modelo, precio y costo). Qué atributo se puede sobrescribir lo dice la configuración de la plantilla en Catálogo. Por qué: Lo común se captura una vez y no se copia a cada variante; un cambio en el genérico llega solo a las hermanas que heredan. Se apoya en DEC-PRD-02, DMS-30.
  • D-PRD-01-6. Las relaciones son grupos de primera clase en product_groups (venta cruzada, configurable y look), con un solo grupo de cada tipo por producto. Un cambio en un configurable pide republicar a todos sus miembros. Por qué: Hoy las relaciones son valores sueltos sin integridad; el grupo da ida y vuelta y evita que un producto quede en dos grupos de venta cruzada. Se apoya en DEC-PRD-07, PD-15.
  • D-PRD-01-7. Las banderas son una lista explícita con vigencia (inicio y fin). Sin bandera significa ausente, nunca un false de relleno. Por qué: El 99.97 % del arreglo de hoy es relleno en false; guardar sólo lo encendido con fechas da semántica y quita ruido. Se apoya en PD-23, DUD-30.
  • D-PRD-01-8. «No procede» es una marca por variante con motivo, que pone QA a mano; la publicación omite esa variante y no frena a sus hermanas. Por qué: La historia pide no frenar a la familia; hacerlo automático sin saber cómo lo decide P360 hoy arriesga sacar variantes buenas. Se apoya en DUD-24.

Qué falta decidir

IDQué falta
PD-17Firmar la tabla de tipos con Gobierno de Producto y Arquitectura, incluidos el genérico Dummy y los digitales.
PD-16Ratificar la política de identificadores (prefijo visible o sólo número) con Arquitectura y Gobierno de Producto.
PD-23Cerrar la lista de banderas y su semántica con Prod E-commerce y Category.
DUD-24Confirmar cómo marca P360 hoy la variante que no procede, para decidir si además se automatiza.
PD-15Definir la fuente del Conjunto Look para su grupo.

Fuentes

  1. [D] trazabilidad/features/FT-PRD-01-modelo-canonico-de-producto.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.