Actualizado el 18-oct-2026. Primera versión de la arquitectura de esta épica.
Qué responde. Cómo funciona por dentro FT-PRD-10: 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-10.json, generada con generar.py; la ficha de la épica es trazabilidad/features/FT-PRD-10-catalogacion-por-api-del-proveedor.md.
En 30 segundos
Un proveedor o seller con integración puede mandar sus productos desde su propio sistema, sin capturarlos a mano. Entra por el gateway de terceros con credenciales propias y límite de llamadas, consulta el árbol y la plantilla en JSON, manda un producto o un lote y recibe avisos firmados de lo que pasa con cada uno. Por dentro es la misma API de producto y son las mismas reglas del portal.
- Módulo dueño: MS-02 Producto. También participan: MS-13 Identidad y acceso, MS-01 Catálogo, MS-06 Evaluación, MS-11 Activos digitales, MS-14 Avisos y ayuda, MS-15 IA y enriquecimiento.
- Etapa 0 · Entrada. Recibe «PE-C · Catálogo vigente» y entrega «PE-1 · Propuesta en Borrador».
- APIs: construye 11 y usa 22 que construyen otras épicas.
- Eventos: publica 3 y consume 7.
1. Dónde encaja
Recibe
| De | Qué |
|---|---|
| FT-USR-03 · Control de acceso por API para terceros | El tercero registrado en MS-13 con sus credenciales de cliente, negocios, tiendas, alcances y límites; una suspensión vale desde la siguiente llamada. |
| FT-FLU-05 · Reglas de negocio en la carga masiva | Las mismas reglas de lista negra, coincidencias y decisión por negocio de la carga masiva, para los lotes. |
| FT-FLU-04 · Carga masiva de productos por archivo | El trabajo de carga asíncrona con resultado por producto, que atiende los lotes. |
| FT-ARQ-01 · Núcleo headless | La API única de producto, con el alta completa en un paso y el envío al confirmar. |
| PE-C · Catálogo vigente | El árbol de plantillas y la vista efectiva por negocio que el gateway publica en JSON. |
Entrega
| A | Qué |
|---|---|
| PE-1 · Propuesta en Borrador | La propuesta nace en Borrador con origen PARTNER_API, su externalRef y su dueño, con la evaluación de MS-06 guardada; si está completa, se envía a revisión en la misma confirmación. |
| FT-INT-05 · Multioferta y stock out | Cuando ya existe: el SKU sobre el que puede ofertar el seller y la acción (multioferta o stock out), sin propuesta viva. |
| FT-IA-05 · Enriquecimiento continuo después de crear el SKU y después… | Los valores que mandó el proveedor, marcados con origen PARTNER_API por campo, para que el enriquecimiento sólo busque los que faltan. |
Cambios de estado que hace
| Transición | De → a | Cuándo |
|---|---|---|
| T01 | Inicio → Borrador | El proveedor o seller guarda por primera vez (pantalla, archivo, plantilla en línea o API). |
| T03 | Borrador → En revisión | Envía con los obligatorios completos. |
Tareas que abre o cierra: Validación automática, Corrección.
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 | Sistemas de proveedores y sellers | Proveedor de identidad | Pide un token con sus credenciales de cliente | — |
| 2 | Sistemas de proveedores y sellers | Gateway de terceros /partner/v1 | Manda el producto completo con su llave de idempotencia | API-GW-001 |
| 3 | Gateway de terceros /partner/v1 | MS-13 Identidad y acceso | Revisa que el tercero esté activo y en su límite | API-13-012 |
| 4 | Gateway de terceros /partner/v1 | MS-02 Producto | Pide identificar el producto antes del alta | API-02-202 |
| 5 | MS-02 Producto | MS-06 Evaluación | Pide la evaluación de lista negra y coincidencias | API-06-001 |
| 6 | Gateway de terceros /partner/v1 | MS-02 Producto | Pide el alta completa con la evaluación y origen PARTNER_API | API-02-006 |
| 7 | MS-02 Producto | MS-01 Catálogo | Valida contra la vista efectiva de la plantilla | API-01-028 |
| 8 | Gateway de terceros /partner/v1 | MS-02 Producto | Confirma con envío si está completa | API-02-057 |
| 9 | MS-02 Producto | Base pim | Crea la propuesta y la envía a revisión | — |
| 10 | MS-02 Producto | Eventos | Avisa la propuesta nueva con origen PARTNER_API | catalog.product.changed/PRODUCT_CREATED |
| 11 | Gateway de terceros /partner/v1 | MS-02 Producto | Lee el recibo y responde 201 al tercero | API-02-025 |
| 12 | Eventos | MS-14 Avisos y ayuda | Entrega el cambio de estado para avisar | catalog.product.changed/STATUS_CHANGED |
| 13 | MS-14 Avisos y ayuda | Sistemas de proveedores y sellers | Entrega el aviso firmado al webhook del tercero | — |
3.1 Prepara la integración: árbol, plantilla y avisos
3.2 Lote de hasta 1,000 productos
3.3 Corrige sólo el campo que falló
4. Si algo sale mal
- El tercero está suspendido o no tiene límites configurados. El gateway responde 403 PARTNER_SUSPENDED o LIMITS_NOT_CONFIGURED desde la siguiente llamada, porque refresca su copia del registro con PARTNER_CHANGED; los demás terceros siguen sin cambio.
- Pasa su límite por minuto o su tope diario. Responde 429 RATE_LIMITED o DAILY_QUOTA_EXCEEDED con Retry-After; el contador es por tercero, así que el PIM sigue atendiendo a los demás y al portal.
- El producto ya existe o la marca está vetada. La identificación lo dice en línea antes de crear nada: 409 ALREADY_EXISTS con la acción y el enlace a offer-targets, o 422 BRAND_BLOCKED, que es final.
- Falta un obligatorio o un valor no está en la lista. El plan lo rechaza antes de crear nada: 422 VALIDATION_ERROR con producto, variante, ruta y atributo. Se corrige y se reenvía con el mismo externalRef.
- El webhook del tercero no responde. MS-14 reintenta con espera creciente, de 1 minuto a 24 horas; después la entrega queda fallida y el aviso sigue disponible en /partner/v1/events.
5. APIs
Las que construye esta épica (11)
POST /partner/v1/proposals · Enviar un producto
| Campo | Valor |
|---|---|
| API | API-GW-001 · Gateway de terceros /partner/v1 |
| Para qué | El sistema de un proveedor o seller manda un producto completo con sus variantes. El gateway pide a MS-02 identificarlo (lista negra y «¿ya existe?»); si es nuevo, lo traduce a la misma alta completa de MS-02 (API-02-006 sin productId, con origen PARTNER_API, el evaluationId y envío al confirmar), lee el recibo y responde en línea: la propuesta quedó creada y enviada a revisión, o no se creó nada y la respuesta dice qué producto y qué campo fallaron. |
| En esta épica | Enviar un producto, en línea hasta la identificación |
| Quién la llama | Sistemas de proveedores y sellers |
| Permiso | Tercero activo en el registro de MS-13, con el alcance proposals.write y el negocio entre los suyos |
| Cabeceras | Authorization, X-Request-Id, Idempotency-Key |
| También la usan | FT-ACT-03, FT-ARQ-01, FT-USR-03 |
Request
{
"business": "MKP",
"externalRef": "SEL-001-PB20",
"templateId": "EU4-0412",
"templateVersion": 7,
"base": {
"title": "Batería externa de 20,000 mAh con carga rápida USB-C",
"brandCode": "MARCA01",
"model": "PB-20",
"description": "Batería portátil con dos salidas"
},
"attributes": [
{
"code": "CAPACIDAD",
"value": 20000,
"unit": "MAH"
},
{
"code": "TIPO_CONECTOR",
"values": [
"USB_C",
"USB_A"
]
},
{
"code": "MATERIAL",
"values": [
"PLASTICO"
]
}
],
"variants": [
{
"externalRef": "SEL-001-PB20-NEG",
"ean": "7501234567930",
"axes": {
"COLOR": "NEGRO"
},
"images": [
{
"url": "<URL pública de la imagen en el servidor del seller>",
"type": "PRINCIPAL",
"order": 1
}
]
},
{
"externalRef": "SEL-001-PB20-BLA",
"ean": "7501234567947",
"axes": {
"COLOR": "BLANCO"
},
"images": [
{
"url": "<URL pública de la imagen en el servidor del seller>",
"type": "PRINCIPAL",
"order": 1
}
]
}
],
"logistics": {
"package": {
"heightCm": 3,
"widthCm": 8,
"lengthCm": 15,
"grossWeightKg": 0.45,
"netWeightKg": 0.4
}
}
}
Response · 201
{
"success": true,
"data": {
"proposalId": "PRD-0004890",
"externalRef": "SEL-001-PB20",
"business": "MKP",
"status": "IN_APPROVAL",
"correctionDueAt": null,
"variants": [
{
"externalRef": "SEL-001-PB20-NEG",
"variantId": "VAR-0004890-01"
},
{
"externalRef": "SEL-001-PB20-BLA",
"variantId": "VAR-0004890-02"
}
],
"links": {
"self": "/partner/v1/proposals/PRD-0004890"
}
}
}
Errores
| HTTP | Código | Cuándo |
|---|---|---|
| 409 | ALREADY_EXISTS | La identificación dice que el producto ya existe: no se crea nada; trae matchType, allowedAction, sku y el enlace a offer-targets |
| 422 | BRAND_BLOCKED | La marca está en la lista negra de ese negocio: no se crea nada y es final |
| 422 | VALIDATION_ERROR | El plan encuentra un obligatorio que falta o un valor fuera de lista; details trae itemRef, variantRef, path, attribute y code. No se crea nada |
| 422 | TEMPLATE_VERSION_OUTDATED | Mandó una versión vieja de la plantilla y la vigente pide algo que no trae |
| 409 | EXTERNAL_REF_EXISTS | Ya tiene otra propuesta con ese externalRef |
| 403 | PARTNER_SUSPENDED | El tercero está suspendido o revocado en el registro de MS-13 |
| 429 | RATE_LIMITED | Pasó su límite por minuto; trae Retry-After |
| 429 | DAILY_QUOTA_EXCEEDED | Pasó su tope diario de productos nuevos |
GET /partner/v1/batches/{batchId}/items · Resultado de un lote por producto
| Campo | Valor |
|---|---|
| API | API-GW-002 · Gateway de terceros /partner/v1 |
| Para qué | Dice el avance de un lote y qué pasó con cada producto, correlacionado por su externalRef y nunca por posición: creado (CREATED), por corregir sin crear nada (CORRECTION_REQUIRED), rechazado (REJECTED) o ya existe (ALREADY_EXISTS) con la acción que le toca. |
| En esta épica | Resultado de un lote por producto, por externalRef |
| Quién la llama | Sistemas de proveedores y sellers |
| Permiso | El tercero dueño del lote, con el alcance proposals.write o catalog.read |
| Cabeceras | Authorization, X-Request-Id |
| Parámetros | batchId = imp-20261018-0043, result = None, page = 1, size = 200 |
Request
null
Response · 200
{
"success": true,
"data": {
"batchId": "imp-20261018-0043",
"status": "DONE_WITH_ERRORS",
"summary": {
"received": 3,
"created": 1,
"correctionRequired": 1,
"rejected": 0,
"alreadyExists": 1
},
"items": [
{
"externalRef": "SEL-001-PB20",
"result": "CREATED",
"proposalId": "PRD-0004891",
"status": "IN_APPROVAL"
},
{
"externalRef": "SEL-001-PB10",
"result": "CORRECTION_REQUIRED",
"errors": [
{
"variantRef": "SEL-001-PB10-NEG",
"path": "/items/1/variants/0/ean",
"code": "EAN_CHECK_DIGIT",
"message": "El dígito verificador no corresponde a un EAN-13"
}
]
},
{
"externalRef": "SEL-001-CB01",
"result": "ALREADY_EXISTS",
"matchType": "EAN",
"allowedAction": "MULTI_OFFER",
"sku": "1000004812",
"links": {
"offerTarget": "/partner/v1/offer-targets/1000004812"
}
}
],
"page": 1,
"size": 200,
"total": 3
}
}
Errores
| HTTP | Código | Cuándo |
|---|---|---|
| 404 | NOT_FOUND | El lote no existe o no es del tercero |
GET /partner/v1/offer-targets/{sku} · Sobre qué SKU ofertar
| Campo | Valor |
|---|---|
| API | API-GW-003 · Gateway de terceros /partner/v1 |
| Para qué | Cuando el producto ya existe, dice sobre qué SKU puede ofertar el seller, si ya tiene sus datos base y si el portal de ofertas ya lo acusó. Es la misma respuesta de API-02-404 en el contrato de terceros; no tiene efectos y la oferta se crea en Mirakl. |
| En esta épica | Sobre qué SKU ofertar cuando ya existe |
| Quién la llama | Sistemas de proveedores y sellers |
| Permiso | Seller con el alcance catalog.read |
| Cabeceras | Authorization, X-Request-Id |
| Parámetros | sku = 1000004812 |
| También la usan | FT-INT-05 |
Request
null
Response · 200
{
"success": true,
"data": {
"sku": "1000004812",
"productName": "Batería externa de 20,000 mAh",
"offerType": "MULTI_OFFER",
"miraklStatus": "ACKNOWLEDGED",
"acknowledgedAt": "2026-10-16T17:05:12Z"
}
}
Errores
| HTTP | Código | Cuándo |
|---|---|---|
| 404 | NOT_FOUND | El SKU no existe |
| 403 | BUSINESS_NOT_ALLOWED | Ese SKU no admite oferta del seller (por ejemplo, sólo existe en Suburbia) |
POST /partner/v1/batches · Enviar un lote
| Campo | Valor |
|---|---|
| API | API-GW-004 · Gateway de terceros /partner/v1 |
| Para qué | Manda hasta 1,000 productos de un mismo negocio en segundo plano: responde al momento con el ID del lote y el resultado por producto llega después; cada producto es su propia unidad, así que uno malo no tumba el lote. |
| En esta épica | Enviar un lote de hasta 1,000 productos |
| Quién la llama | Sistemas de proveedores y sellers |
| Permiso | Tercero activo con el alcance proposals.write y el negocio entre los suyos |
| Cabeceras | Authorization, X-Request-Id, Idempotency-Key |
Request
{
"business": "MKP",
"items": [
{
"externalRef": "SEL-001-PB20",
"templateId": "EU4-0412",
"templateVersion": 7,
"base": {
"title": "Batería externa de 20,000 mAh con carga rápida USB-C",
"brandCode": "MARCA01",
"model": "PB-20"
},
"attributes": [
{
"code": "CAPACIDAD",
"value": 20000,
"unit": "MAH"
}
],
"variants": [
{
"externalRef": "SEL-001-PB20-NEG",
"ean": "7501234567930",
"axes": {
"COLOR": "NEGRO"
}
}
]
}
]
}
Response · 202
{
"success": true,
"data": {
"batchId": "imp-20261018-0043",
"received": 1,
"status": "VALIDATING",
"links": {
"self": "/partner/v1/batches/imp-20261018-0043/items"
}
}
}
Errores
| HTTP | Código | Cuándo |
|---|---|---|
| 400 | VALIDATION_ERROR | El sobre no sirve: JSON mal formado o falta el negocio |
| 413 | LIMIT_EXCEEDED | Más de 1,000 productos: se rechaza completo, nunca se trunca |
| 429 | RATE_LIMITED | Ya tiene dos lotes en proceso o pasó su límite por minuto |
| 403 | PARTNER_SUSPENDED | El tercero está suspendido o revocado |
GET /partner/v1/categories · Árbol de catalogación
| Campo | Valor |
|---|---|
| API | API-GW-005 · Gateway de terceros /partner/v1 |
| Para qué | Da el árbol para ubicar el producto y buscar por palabra: cada categoría con su ID, su nombre, su ruta completa y si es una hoja que admite productos (con su plantilla). No incluye las desligadas ni las retiradas. |
| En esta épica | Árbol de catalogación con búsqueda por palabra |
| Quién la llama | Sistemas de proveedores y sellers |
| Permiso | Tercero activo con el alcance catalog.read |
| Cabeceras | Authorization, X-Request-Id |
| Parámetros | q = teclado, parentId = None, business = MKP, page = 1, size = 50 |
Request
null
Response · 200
{
"success": true,
"data": {
"items": [
{
"categoryId": "EU4-0611",
"name": "Teclados",
"path": "Tecnología > Computación > Accesorios > Teclados",
"level": 4,
"isLeaf": true,
"templateId": "EU4-0611"
},
{
"categoryId": "EU4-0612",
"name": "Teclados musicales",
"path": "Entretenimiento > Instrumentos > Teclados musicales",
"level": 3,
"isLeaf": true,
"templateId": "EU4-0612"
}
],
"page": 1,
"size": 50,
"total": 2
}
}
Errores
| HTTP | Código | Cuándo |
|---|---|---|
| 404 | NOT_FOUND | La categoría padre no existe |
GET /partner/v1/templates/{templateId} · Plantilla de una categoría en JSON
| Campo | Valor |
|---|---|
| API | API-GW-006 · Gateway de terceros /partner/v1 |
| Para qué | Da la vista efectiva vigente de una plantilla para un negocio: cada atributo con su tipo, su lista de valores, si es obligatorio, recomendado u opcional y si sólo se acepta en el alta; los ejes de variante, las reglas de imagen y la versión. |
| En esta épica | Plantilla de una categoría en JSON, con los campos sólo de alta |
| Quién la llama | Sistemas de proveedores y sellers |
| Permiso | Tercero activo con el alcance catalog.read y el negocio entre los suyos |
| Cabeceras | Authorization, X-Request-Id |
| Parámetros | templateId = EU4-0412, business = MKP |
Request
null
Response · 200
{
"success": true,
"data": {
"templateId": "EU4-0412",
"name": "Baterías portátiles",
"version": 7,
"effectiveFrom": "2026-10-01T00:00:00Z",
"business": "MKP",
"attributes": [
{
"code": "CAPACIDAD",
"name": "Capacidad",
"type": "NUMBER",
"unit": "MAH",
"level": "REQUIRED",
"acceptedOnlyAtCreation": false,
"variantLevel": false
},
{
"code": "TIPO_CONECTOR",
"name": "Tipo de conector",
"type": "LIST",
"multivalued": true,
"level": "REQUIRED",
"acceptedOnlyAtCreation": false,
"values": [
{
"code": "USB_C",
"label": "USB-C"
},
{
"code": "USB_A",
"label": "USB-A"
}
]
},
{
"code": "MARCA",
"name": "Marca",
"type": "LIST",
"level": "REQUIRED",
"acceptedOnlyAtCreation": true
},
{
"code": "MATERIAL",
"name": "Material",
"type": "LIST",
"level": "RECOMMENDED",
"acceptedOnlyAtCreation": false,
"values": [
{
"code": "PLASTICO",
"label": "Plástico"
},
{
"code": "ALUMINIO",
"label": "Aluminio"
}
]
}
],
"variantAxes": [
"COLOR"
],
"imageRules": {
"min": 1,
"max": 20,
"principal": {
"background": "BLANCO",
"minSidePx": 1000
}
},
"calculatedFields": [
"TITULO_WEB"
]
}
}
Errores
| HTTP | Código | Cuándo |
|---|---|---|
| 404 | NOT_FOUND | La plantilla no existe o está retirada |
| 422 | VALIDATION_ERROR | La categoría no es una hoja: no admite productos |
| 403 | BUSINESS_NOT_ALLOWED | El negocio no es del tercero |
POST /partner/v1/products/lookup · ¿Ya existe?
| Campo | Valor |
|---|---|
| API | API-GW-007 · Gateway de terceros /partner/v1 |
| Para qué | Antes de mandar, dice por EAN o por marca y modelo si el producto ya existe y qué acción le toca al seller: crear propuesta, multioferta, oferta con stock out o nada. No dice qué seller es dueño. |
| En esta épica | ¿Ya existe? por EAN o por marca y modelo, antes de mandar |
| Quién la llama | Sistemas de proveedores y sellers |
| Permiso | Tercero activo con el alcance catalog.read |
| Cabeceras | Authorization, X-Request-Id |
Request
{
"business": "MKP",
"eans": [
"7501234567893"
],
"items": [
{
"brandCode": "MARCA01",
"model": "PB-20"
}
]
}
Response · 200
{
"success": true,
"data": {
"results": [
{
"key": "7501234567893",
"exists": true,
"matchType": "EAN",
"catalog": "LIVERPOOL",
"sku": "1000004812",
"ownedByYou": false,
"allowedAction": "STOCK_OUT_OFFER",
"links": {
"offerTarget": "/partner/v1/offer-targets/1000004812"
}
},
{
"key": "MARCA01|PB-20",
"exists": false,
"allowedAction": "CREATE_PROPOSAL"
}
]
}
}
Errores
| HTTP | Código | Cuándo |
|---|---|---|
| 400 | VALIDATION_ERROR | Un EAN no tiene de 8 a 14 dígitos o su dígito verificador no corresponde; más de 100 llaves |
PATCH /partner/v1/proposals/{proposalId} · Corregir sólo lo que falló
| Campo | Valor |
|---|---|
| API | API-GW-008 · Gateway de terceros /partner/v1 |
| Para qué | El tercero manda sólo los campos que corrige y el PIM conserva los demás; si la propuesta tenía una corrección pendiente, se reenvía a revisión en la misma confirmación. Un campo que sólo se acepta en el alta se rechaza con el campo y el motivo. |
| En esta épica | Corregir sólo los campos que fallaron |
| Quién la llama | Sistemas de proveedores y sellers |
| Permiso | El tercero dueño de la propuesta, con el alcance proposals.write |
| Cabeceras | Authorization, X-Request-Id, Idempotency-Key, If-Match |
| Parámetros | proposalId = PRD-0004890 |
Request
{
"attributes": [
{
"code": "TIPO_CONECTOR",
"values": [
"USB_C"
]
}
],
"resubmit": true
}
Response · 202
{
"success": true,
"data": {
"proposalId": "PRD-0004890",
"requestId": "req-3c80f1",
"status": "ACCEPTED",
"links": {
"proposal": "/partner/v1/proposals/PRD-0004890"
}
}
}
Errores
| HTTP | Código | Cuándo |
|---|---|---|
| 422 | READ_ONLY_FIELD | El campo sólo se acepta en el alta, o es SKU, EAN o negocio |
| 409 | VERSION_CONFLICT | La propuesta cambió desde la versión que trae If-Match |
| 409 | NOT_EDITABLE_IN_STATUS | La propuesta ya no se puede corregir por API en su estado |
| 423 | LOCKED | Liverpool la está revisando en ese momento |
| 403 | NOT_CONTENT_OWNER | El producto no es del tercero |
GET /partner/v1/proposals/{proposalId} · Estado de una propuesta
| Campo | Valor |
|---|---|
| API | API-GW-009 · Gateway de terceros /partner/v1 |
| Para qué | Da el estado que ve el tercero, los motivos por campo, hasta cuándo puede corregir, el SKU de cada variante cuando ya lo tiene y el estado de entrega en los canales de venta. |
| En esta épica | Estado de una propuesta con motivos y canales |
| Quién la llama | Sistemas de proveedores y sellers |
| Permiso | El tercero dueño de la propuesta, con el alcance catalog.read |
| Cabeceras | Authorization, X-Request-Id |
| Parámetros | proposalId = PRD-0004890 |
Request
null
Response · 200
{
"success": true,
"data": {
"proposalId": "PRD-0004890",
"externalRef": "SEL-001-PB20",
"business": "MKP",
"status": "CORRECTION_REQUIRED",
"version": 4,
"correctionDueAt": "2026-12-17T15:04:05Z",
"reasons": [
{
"attribute": "TIPO_CONECTOR",
"code": "VALUE_NOT_IN_LIST",
"area": "VALIDACION_AUTOMATICA",
"message": "Manda el código de la lista"
}
],
"requiredActions": [
"FIX_FIELDS"
],
"variants": [
{
"externalRef": "SEL-001-PB20-NEG",
"variantId": "VAR-0004890-01",
"sku": null,
"ean": "7501234567930"
}
],
"channels": [
{
"channel": "TIENDA",
"state": "PENDING"
},
{
"channel": "MARKETPLACE",
"state": "PENDING"
}
]
}
}
Errores
| HTTP | Código | Cuándo |
|---|---|---|
| 404 | NOT_FOUND | La propuesta no existe o no es del tercero |
POST /partner/v1/webhooks · Suscribirse a los avisos
| Campo | Valor |
|---|---|
| API | API-GW-010 · Gateway de terceros /partner/v1 |
| Para qué | El tercero registra el punto de su sistema donde quiere recibir avisos firmados y qué eventos le interesan. La suscripción la guarda MS-14 como un canal más de avisos; el secreto para validar la firma se muestra una sola vez. |
| En esta épica | Suscribirse a los avisos por webhook |
| Quién la llama | Sistemas de proveedores y sellers |
| Permiso | Tercero activo con el alcance webhooks.manage |
| Cabeceras | Authorization, X-Request-Id, Idempotency-Key |
Request
{
"endpoint": "<dirección HTTPS del sistema del seller>",
"events": [
"proposal.created",
"proposal.correction_required",
"proposal.rejected",
"proposal.accepted",
"product.sku_created",
"product.published",
"batch.completed"
],
"description": "Orquestador de catálogo"
}
Response · 201
{
"success": true,
"data": {
"webhookId": "whk-31a0",
"status": "ACTIVE",
"events": [
"proposal.created",
"proposal.correction_required",
"proposal.rejected",
"proposal.accepted",
"product.sku_created",
"product.published",
"batch.completed"
],
"secret": "(se muestra una sola vez)"
}
}
Errores
| HTTP | Código | Cuándo |
|---|---|---|
| 400 | VALIDATION_ERROR | La dirección no es HTTPS o un evento no existe |
| 403 | SCOPE_MISSING | El token no trae el alcance webhooks.manage |
GET /partner/v1/events · Avisos por consulta
| Campo | Valor |
|---|---|
| API | API-GW-011 · Gateway de terceros /partner/v1 |
| Para qué | Los mismos avisos que salen por webhook, en orden y por cursor, de los últimos 30 días: es el respaldo para quien no recibe webhooks o perdió alguno. |
| En esta épica | Avisos por consulta, de respaldo del webhook |
| Quién la llama | Sistemas de proveedores y sellers |
| Permiso | Tercero activo con el alcance catalog.read |
| Cabeceras | Authorization, X-Request-Id |
| Parámetros | since = 2026-10-18T00:00:00Z, types = proposal.correction_required,proposal.accepted, cursor = c-00A7 |
Request
null
Response · 200
{
"success": true,
"data": {
"items": [
{
"eventId": "evt-01JA7Q4M2K",
"type": "proposal.correction_required",
"occurredAt": "2026-10-18T17:05:12Z",
"business": "MKP",
"proposalId": "PRD-0004890",
"externalRef": "SEL-001-PB20",
"status": "CORRECTION_REQUIRED",
"version": 4,
"reasons": [
{
"attribute": "TIPO_CONECTOR",
"code": "VALUE_NOT_IN_LIST"
}
]
}
],
"nextCursor": "c-00A8"
}
}
Errores
| HTTP | Código | Cuándo |
|---|---|---|
| 400 | VALIDATION_ERROR | El cursor no es válido o pide más de 30 días |
Las que usa y construye otra épica (22)
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-13-012 | GET /internal/identity/partners/by-client/{clientId} | El tercero de un cliente de API, con su estado y sus límites | FT-USR-03 · Control de acceso por API para terceros |
| API-02-006 | POST /api/v2/products/guided/plans | Alta completa en un paso con origen PARTNER_API y envío al confirmar | FT-PRD-07 · Captura guiada por pasos con los mínimos primero |
| API-01-028 | GET /api/v2/taxonomies/PRODUCT/nodes/{id}/effective | Vista efectiva de la plantilla por negocio | FT-PLT-06 · Configuración de atributos por plantilla y por negocio… |
| API-01-022 | GET /api/v2/structures/{id}/leaves | Hojas del árbol con su ruta legible | FT-PLT-05 · Taxonomía de plantillas por familia, metadatos de… |
| API-02-057 | POST /api/v2/product-plans/{planId}/apply | Confirmar el plan | FT-FLU-01 · Motor de estados configurable con dos carreteras y reglas… |
| API-06-001 | POST /api/v2/evaluations | Evaluación de lista negra, coincidencias y decisión por negocio | FT-REF-02 · Blacklist de Marketplace por marca, negocio y grupo de… |
| API-02-025 | GET /api/v2/product-receipts/{requestId} | Recibo de la confirmación, para responder en línea | FT-FLU-01 · Motor de estados configurable con dos carreteras y reglas… |
| API-02-046 | POST /api/v2/product-imports | Carga asíncrona de los lotes (source PARTNER_BATCH) | FT-FLU-04 · Carga masiva de productos por archivo |
| API-06-002 | POST /api/v2/evaluations/batches | Evaluar los productos del lote por bloque | FT-FLU-05 · Reglas de negocio en la carga masiva |
| API-02-048 | GET /api/v2/product-imports/{id} | Resultado del lote por renglón | FT-FLU-04 · Carga masiva de productos por archivo |
| API-02-012 | POST /api/v2/products/{id}/values/plans | Cambiar sólo los valores corregidos, con reenvío | FT-RAC-01 · Motor de validación en línea y candados de datos… |
| API-02-011 | POST /api/v2/products/{id}/root/plans | Cambiar datos generales; SKU, EAN y negocio son de sólo lectura | FT-PRD-01 · Modelo canónico de producto |
| API-02-042 | GET /api/v2/delivery-status?productId=&sku= | Estado de entrega por canal, para el estado de la propuesta (API-GW-009) | FT-CAT-05 · Sitios, enrutamiento y visibilidad por negocio |
| API-02-002 | GET /api/v2/products/{id} | Ficha de la propuesta para el estado que ve el tercero | FT-PRD-01 · Modelo canónico de producto |
| API-02-029 | GET /api/v2/workflow/comments?productIds= | Motivos de rechazo por campo | FT-PRD-04 · Propuesta del proveedor de Mercaderías |
| API-14-004 | PUT /api/v2/notifications/preferences | Guardar la suscripción de webhook como preferencia de canal | FT-RAC-05 · Estatus del producto y motivos visibles para seller y… |
| API-14-005 | POST /internal/notifications/messages | Avisar que terminó un lote | FT-RAC-05 · Estatus del producto y motivos visibles para seller y… |
| API-14-001 | GET /api/v2/notifications?unread= | Los avisos del tercero, para el feed de respaldo | FT-RAC-05 · Estatus del producto y motivos visibles para seller y… |
| API-11-006 | POST /api/v2/assets/from-url | Registrar cada imagen por URL que mandó el proveedor, hasta 20 por color | FT-ACT-03 · Imágenes del seller por URL con validación y corrección… |
| API-15-004 | POST /api/v2/ai/enrichments | Enriquecer después sólo los atributos vacíos (FT-IA-05) | FT-IA-05 · Enriquecimiento continuo después de crear el SKU y después… |
| API-02-202 | POST /api/v2/products/identifications | Identificación previa al alta, también para el «¿ya existe?» (API-GW-007) | FT-FLU-02 · Alta individual de Marketplace |
| API-02-404 | GET /api/v2/offer-targets/{sku} | Sobre qué SKU ofertar, detrás de offer-targets (API-GW-003) | FT-INT-05 · Multioferta y stock out |
6. Eventos
Publica
| Evento | Cuándo | Quién lo consume |
|---|---|---|
catalog.product.changed/PRODUCT_CREATED | Se confirma una propuesta del tercero | FT-ARQ-01 |
catalog.product.changed/STATUS_CHANGED | La propuesta completa pasa a En revisión | FT-ACT-05, FT-ARQ-02, FT-CAT-05, FT-FLU-01, FT-IA-04, FT-PRD-02, FT-PRD-04, FT-PRD-06, FT-RAC-03, FT-RAC-05, FT-RAC-06 |
catalog.product.changed/PRODUCT_UPDATED | El tercero corrige un campo | FT-ACT-05, FT-ARQ-02, FT-IA-01, FT-PLT-07, FT-PRD-05, FT-RAC-06 |
Consume
| Evento | Para qué | Quién lo publica |
|---|---|---|
catalog.product.changed/STATUS_CHANGED | MS-14 avisa al tercero por webhook: creada, requiere corrección, aceptada o cancelada | esta misma épica |
catalog.product.changed/REJECTION_ADDED | MS-14 manda al tercero el motivo por campo | FT-FLU-01, FT-INT-07, FT-PRD-02, FT-PRD-04, FT-PRD-05, FT-RAC-01, FT-RAC-03, FT-RAC-05 |
catalog.product.changed/SKU_ASSIGNED | MS-14 avisa product.sku_created con el SKU de cada variante | FT-INT-03, FT-PRD-02 |
catalog.delivery.result/DELIVERY_RESULT | MS-14 avisa «publicado» o «falló» por canal de venta | FT-CAT-02, FT-CAT-05, FT-INT-01, FT-INT-02, FT-INT-05, FT-INT-07, FT-PRD-02 |
catalog.identity.changed/PARTNER_CHANGED | El gateway refresca su copia del tercero: una suspensión vale en la siguiente llamada | FT-USR-03 |
catalog.validation.result/EVALUATION_DONE | MS-02 sigue con el lote cuando MS-06 terminó de evaluar un bloque | FT-FLU-03, FT-FLU-05, FT-REF-03 |
catalog.asset.changed/ASSET_VALIDATED | MS-02 liga a su variante la URL de trabajo de cada imagen que mandó el tercero | FT-ACT-01, FT-ACT-02, FT-ACT-03 |
7. Datos
Cada colección tiene un solo dueño, el único que escribe en ella.
| Colección | Dueño | Qué guarda | Esta épica |
|---|---|---|---|
products | MS-02 | La familia del producto con sus variantes, valores, imágenes (URL), clasificaciones, estado, tareas abiertas y rechazos. | escribe |
identifier_registry | MS-02 | Unicidad de SKU y de EAN por negocio. | escribe |
tasks | MS-02 | Todo el trabajo asignado: visto bueno, corrección, Gobierno de Datos, foto, secciones, QA, revisión del cambio y repoblamiento. | escribe |
product_versions | MS-02 | Foto numerada del producto en cada versión. | escribe |
product_jobs | MS-02 | Trabajos asíncronos: cargas masivas de productos, edición masiva y publicación. | escribe |
product_receipts | MS-02 | El recibo de cada escritura confirmada en el producto (antes operation_receipts). | escribe |
product_outbox | MS-02 | Los cambios del producto que esperan salir como evento (antes outbox). | escribe |
partners | MS-13 | El registro de terceros: proveedores y sellers que llaman por API, con su tipo, negocios, tiendas, alcances, límites y clientes (catalogación por API §3.1). | lee |
template_views | MS-01 | La vista efectiva ya resuelta por plantilla y negocio: qué atributos pide y cuáles son obligatorios. | lee |
validation_results | MS-06 | El resultado de cada evaluación: lista negra, coincidencias con su confianza y score. | lee |
notifications | MS-14 | Cada aviso a un usuario, proveedor o seller: canal, texto, si ya lo leyó. | lee |
8. Historias que cubre
| Historia | Prioridad | Cómo la cubre |
|---|---|---|
| FT-PRD-10·H1 · Alta por API con las reglas del portal | P1 | pasos 2, 3, 4, 5, 6, 7, 8, 9, 11; flujo «Lote de hasta 1,000 productos»; API-GW-001, API-GW-004, API-GW-002, API-GW-007, API-GW-003 |
| FT-PRD-10·H2 · Más que los mínimos | P1 | pasos 6, 8; API-11-006, API-GW-001 |
| FT-PRD-10·H3 · Alertas en tiempo real | P2 | pasos 12, 13; flujo «Prepara la integración: árbol, plantilla y avisos», «Lote de hasta 1,000 productos»; API-GW-010, API-GW-011, API-GW-009 |
| FT-PRD-10·H4 · El enriquecimiento completa lo que falta | P2 | API-15-004. Fuera: El enriquecimiento posterior lo hace FT-IA-05; aquí se deja el origen PARTNER_API en cada valor para que sólo busque lo vacío (D-PRD-10-5). |
| FT-PRD-10·H5 · Campos de alta y modificación por API | P2 | flujo «Corrige sólo el campo que falló», «Prepara la integración: árbol, plantilla y avisos»; API-GW-008, API-GW-006 |
| FT-PRD-10·H6 · Consultar por API la plantilla de una categoría | P1 | flujo «Prepara la integración: árbol, plantilla y avisos»; API-GW-006 |
| FT-PRD-10·H7 · Consultar por API el árbol de categorías | P2 | flujo «Prepara la integración: árbol, plantilla y avisos»; API-GW-005 |
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-10-1. El gateway no guarda nada: valida el token, aplica el límite por tercero con la copia en memoria del registro de MS-13 (API-13-012, refrescada con PARTNER_CHANGED) y traduce cada ruta /partner/v1 a la API v2 de los servicios, con las mismas reglas. Los lotes viven en product_jobs de MS-02; las suscripciones y las entregas de avisos, en MS-14. Por qué: Con ADR-12 cada dato tiene un dueño y el borde no tiene base. Se aparta de la propuesta de catalogación por API (T-6), que guardaba lotes, suscripciones y entregas en la API de terceros. Se apoya en ADR-12, PD-26, PD-05, RN-GEN-08.
- D-PRD-10-2. Envío individual en línea hasta la identificación: el gateway pide a MS-02 identificar el producto (API-02-202); si es nuevo, pide el alta completa con origen PARTNER_API, el evaluationId y envío al confirmar, lee el recibo y responde 201 con la propuesta en revisión; si no, 409 ALREADY_EXISTS o 422 BRAND_BLOCKED o VALIDATION_ERROR, sin crear nada. Los lotes van en segundo plano (202) sobre la misma carga de FT-FLU-04, con un negocio por lote y hasta 1,000 productos. Por qué: Es la opción que recomienda DUD-38: el tercero sabe al momento si su producto entró, y los volúmenes grandes no cargan en línea a MS-02 ni a MS-06. Se apoya en DUD-38, RF-MAS-05, RF-ALT-14.
- D-PRD-10-3. Si la identificación dice que ya existe o que la marca está vetada, o el plan encuentra errores de plantilla, no se crea nada: el tercero corrige y reenvía con el mismo externalRef, que es único por tercero. Si una propuesta ya creada resulta repetida al enviarla, se cierra como convertida en oferta (FT-INT-05). Por qué: Es el contrato de catalogación por API (no se guardan propuestas que no existen) y la misma identificación previa que usa Evaluación; los cuatro resultados de lote son CREATED, CORRECTION_REQUIRED, REJECTED y ALREADY_EXISTS. Se apoya en RF-MAT-15, PEN-23, DUD-38.
- D-PRD-10-4. Avisos en tiempo real por webhook firmado (HMAC con el secreto de la suscripción) que entrega MS-14 como un canal más, con reintentos de 1 minuto a 24 horas; de respaldo, el feed /partner/v1/events con los mismos avisos de los últimos 30 días. Para los dos negocios. Sin correo para sistemas. Por qué: RF-ALT-14 y S20-RF-16 piden alertas en tiempo real para ambos negocios (DUD-40); que las entregue MS-14 evita un segundo dueño de los avisos. Se apoya en RF-ALT-14, S20-RF-16, DUD-40, PD-26.
- D-PRD-10-5. Lo que manda el dueño del contenido gana: cada valor guarda su origen PARTNER_API; el enriquecimiento con IA sólo llena atributos vacíos o propone, y sobrescribir un valor del dueño pide la aprobación de un rol con permiso sobre ese campo. Se aceptan atributos opcionales y hasta 20 imágenes por color. Por qué: Lo que el proveedor entrega por API entra en la catalogación inicial y no se le vuelve a pedir (RN-ENR-13); resuelve la parte de PEN-167 que toca a esta épica. Se apoya en RN-ENR-13, PEN-167, DEC-123.
- D-PRD-10-6. La plantilla en JSON es la vista efectiva vigente de MS-01 por plantilla y negocio, con acceptedOnlyAtCreation por atributo; un PATCH a uno de esos campos responde 422 READ_ONLY_FIELD, y SKU, EAN y negocio nunca cambian por API. La categoría web la asigna Liverpool y el tercero sólo la lee. Por qué: Cubre H5 y H6 y separa la plantilla de la categoría web, como propone S20-RF-20. Se apoya en S34-RF-03, S37-RF-03, S20-RF-20, DUD-22.
- D-PRD-10-7. La API productiva abre en R4 (Fase 2), después del corte de producto; antes sólo hay contrato y sandbox, donde toda escritura responde WRITE_DISABLED. Nunca escribe en P360. Por qué: Abrir después del corte evita la doble escritura durante la convivencia y sigue PD-02 en la opción A. Se apoya en PD-02, DEC-ARQ-05, DUD-39.
- D-PRD-10-8. Las imágenes por URL las descarga y valida MS-11 (API-11-006) después de crear la propuesta, ligadas a su variante y posición; la ficha guarda sólo la URL. Una imagen rechazada no tumba la propuesta: llega como aviso. Por qué: Cada archivo tiene un solo dueño, MS-11, y el gateway no descarga nada. Se apoya en ADR-12, RF-IMG-12.
Qué falta decidir
| ID | Qué falta |
|---|---|
| PD-26 | Arquitectura con Seguridad: el proveedor de identidad para tokens de cliente al volumen de sellers y los límites por puerta en la entrada del clúster. |
| DUD-38 | La prueba de carga confirma si el envío individual puede responder en línea; si no, también pasa a 202. |
| DUD-40 | Producto con Marketplace ratifica que las alertas son para los dos negocios. |
| PD-02 | Dirección, PM y Marketplace confirman la opción A: la API productiva va en R4. |
También siguen abiertos: PEN-23, Marketplace ratifica que el PIM no crea ofertas: sólo dice sobre qué SKU ofertar.
Fuentes
- [D]
trazabilidad/features/FT-PRD-10-catalogacion-por-api-del-proveedor.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.