Pub/Sub - Envelope JSON y ejemplos de mensajes
Todos los mensajes publicados por Nexus en Pub/Sub usan un envelope estándar que incluye trazabilidad, versionado de schema y metadatos de scoping (partner, company, sourceSystem). El payload de negocio se coloca dentro del campo payload con su propio versionado.
Convenciones
- Todos los IDs (
eventId,requestId, etc.) son UUID v4. - Todos los timestamps son ISO-8601 en UTC (
Z). schemaVersionindica la versión del envelope; su cambio obliga a que el consumer valide soporte.payloadVersionindica la versión del payload de negocio (permite migraciones canónicas sin romper consumidores).- El envelope no incluye credenciales; la autenticación se hace por IAM en Pub/Sub y por secretos del worker.
Topics
| Topic | Publisher | Consumer | Propósito |
|---|---|---|---|
nexus.purchase-order.requested | Outbox Publisher (Purchase Order API) | Fusion Worker | Solicitud de creación de OC validada y persistida |
nexus.purchase-order.retry | Fusion Worker / Retry API | Fusion Worker | Reintento automático o manual |
nexus.purchase-order.dlq | Fusion Worker | Operador / proceso de limpieza | Mensajes fallidos con intentos agotados o no reintentables |
Envelope estándar (JSON schema)
{
"eventId": "uuid v4 - unico por publicacion",
"requestId": "uuid v4 - requestId de la solicitud Nexus",
"eventType": "PURCHASE_ORDER_REQUESTED | PURCHASE_ORDER_RETRY_REQUESTED | PURCHASE_ORDER_DLQ",
"schemaVersion": "1.0",
"payloadVersion": "1.0",
"timestamp": "2026-08-12T18:00:00Z",
"partnerId": "GEPP",
"companyId": "ALPURA_MX",
"sourceSystem": "GEPP_ERP",
"externalReference": "PO-123456",
"idempotencyKey": "abc-123-reintento-1",
"attempt": 1,
"orderingKey": "GEPP_ALPURA_MX_PO-123456",
"correlation": {
"traceparent": "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01",
"tracestate": "nexus=s1",
"baggage": "req.origin=gepp_erp"
},
"security": {
"issuedBy": "service-account:nexus-po-api-prod@project.iam.gserviceaccount.com",
"signatureTbd": "TBD: omitir o delegar a KMS si se requiere integridad por mensaje"
},
"metadata": {
"origin": {
"service": "nexus-po-api",
"version": "1.23.0",
"instance": "nexus-po-api-7c9d-abc"
},
"publisher": {
"component": "OutboxPublisher",
"publishedAt": "2026-08-12T18:00:00Z"
}
},
"payload": {
"purchaseOrder": "objeto canónico CreatePurchaseOrderRequest normalizado (ver OpenAPI)",
"validationSummary": {
"valid": true,
"rulesExecuted": 7,
"rulesFailed": 0,
"fatalErrors": 0
},
"links": {
"status": "/api/v1/purchase-orders/requests/{requestId}"
}
}
}
Atributos Pub/Sub (ordenados por cardinalidad)
Además del cuerpo JSON, se recomienda publicar los siguientes attributes en el mensaje de Pub/Sub para: filtrado en subscriptions, HPA por backlog por partner y trazabilidad sin parsear cuerpo.
| Atributo | Tipo | Obligatorio | Notas |
|---|---|---|---|
eventType | string | si | PURCHASE_ORDER_REQUESTED, etc. |
partnerId | string | si | Cardinalidad baja |
companyId | string | si | Cardinalidad baja |
sourceSystem | string | si | Cardinalidad baja |
requestId | string | si | Para auditoria; NO usar como etiqueta de metricas Prometheus. |
eventId | string | si | |
attempt | number | si | |
schemaVersion | string | si | |
traceparent | string | no | Para propagación W3C trace context |
Ejemplo 1 - PURCHASE_ORDER_REQUESTED
Este es el mensaje de creación exitosa, publicado mediante Transactional Outbox después de validar y persistir la solicitud.
{
"eventId": "e9d74a7b-1a2f-4cc6-9d88-6e0af48cbb60",
"requestId": "bcd5f9a7-4ced-4d25-bf3f-6ae762f05b41",
"eventType": "PURCHASE_ORDER_REQUESTED",
"schemaVersion": "1.0",
"payloadVersion": "1.0",
"timestamp": "2026-08-12T18:00:00Z",
"partnerId": "GEPP",
"companyId": "ALPURA_MX",
"sourceSystem": "GEPP_ERP",
"externalReference": "PO-123456",
"idempotencyKey": "GEPP-2026-08-12-001",
"attempt": 1,
"orderingKey": "GEPP_ALPURA_MX_PO-123456",
"correlation": {
"traceparent": "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01",
"tracestate": "nexus=s1",
"baggage": "req.origin=gepp_erp"
},
"security": {
"issuedBy": "service-account:nexus-po-api-prod@proj.iam.gserviceaccount.com"
},
"metadata": {
"origin": {
"service": "nexus-po-api",
"version": "1.23.0",
"instance": "nexus-po-api-7c9d-v6m4"
},
"publisher": {
"component": "OutboxPublisher",
"publishedAt": "2026-08-12T18:00:00.213Z"
}
},
"payload": {
"purchaseOrder": {
"externalReference": "PO-123456",
"documentType": "PURCHASE_ORDER",
"submittedAt": "2026-08-12T17:59:58Z",
"buyer": {
"personId": "EMP-0987",
"email": "compras.1@alpura.com",
"name": "Compras Alpura",
"employeeNumber": "E1234",
"department": "Abastecimiento"
},
"billTo": {
"name": "Alpura SAPI de CV",
"address1": "Periferico Sur 1234",
"city": "CDMX",
"state": "CDMX",
"zip": "01000",
"countryCode": "MX"
},
"shipTo": {
"name": "CDA Alpura Tultitlan",
"address1": "Carretera Mexico Queretaro Km 48",
"city": "Tultitlan",
"state": "Estado de Mexico",
"zip": "54900",
"countryCode": "MX"
},
"supplier": {
"supplierCode": "S-GEPP-001",
"supplierName": "GEPP SA de CV",
"taxId": "GEP880101ABC",
"site": "CDMX-DF",
"contact": {
"name": "Atención a Clientes GEPP",
"email": "atencion@gepp.com",
"phone": "+525512345678"
}
},
"currency": { "code": "MXN", "decimals": 2 },
"paymentTerms": {
"code": "NET_30",
"description": "Pago neto a 30 dias",
"netDays": 30
},
"lines": [
{
"lineNumber": 1,
"item": {
"sku": "GEPP-SKU-0001",
"description": "Leche Entera Larga Vida 1L",
"commodity": "Dairy",
"category": "Liquido"
},
"quantity": { "value": 1000, "unitOfMeasure": "EA" },
"unitPrice": { "amount": 22.50, "currency": "MXN", "taxIncluded": false },
"requestedDeliveryDate": "2026-08-15T09:00:00Z",
"costCenter": "CC-ABAS-01",
"glCode": "3000-10-100-00"
},
{
"lineNumber": 2,
"item": {
"sku": "GEPP-SKU-0002",
"description": "Yogurt Griego Fresa 500g",
"commodity": "Dairy"
},
"quantity": { "value": 500, "unitOfMeasure": "EA" },
"unitPrice": { "amount": 15.00, "currency": "MXN", "taxIncluded": false },
"requestedDeliveryDate": "2026-08-15T09:00:00Z",
"costCenter": "CC-ABAS-01",
"glCode": "3000-10-100-00"
}
],
"notes": "Entregar en muelle 3 entre 9 y 13 hrs. Usar tarimas paletizada plastificada.",
"customFields": {
"gepp.orderType": "REGULAR",
"gepp.routeCode": "RUTA-MEX-007",
"approvalLevel": "LEVEL_1"
}
},
"validationSummary": {
"valid": true,
"rulesExecuted": 11,
"rulesFailed": 0,
"warnings": 0,
"fatalErrors": 0
},
"links": {
"status": "/api/v1/purchase-orders/requests/bcd5f9a7-4ced-4d25-bf3f-6ae762f05b41"
}
}
}
Ejemplo 2 - RETRY_REQUESTED
Mensaje generado tanto por reintento automático (Worker) como por reproceso manual (Retry API). Mantiene el requestId pero cambia eventId y attempt.
{
"eventId": "a04c9a27-89b3-4a62-b392-13b4f2f67a45",
"requestId": "bcd5f9a7-4ced-4d25-bf3f-6ae762f05b41",
"eventType": "PURCHASE_ORDER_RETRY_REQUESTED",
"schemaVersion": "1.0",
"payloadVersion": "1.0",
"timestamp": "2026-08-12T18:05:00Z",
"partnerId": "GEPP",
"companyId": "ALPURA_MX",
"sourceSystem": "GEPP_ERP",
"externalReference": "PO-123456",
"idempotencyKey": "GEPP-2026-08-12-001",
"attempt": 3,
"orderingKey": "GEPP_ALPURA_MX_PO-123456",
"correlation": {
"traceparent": "00-0af7651916cd43dd8448eb211c80319c-e3ff00aa00112233-01"
},
"retry": {
"reason": "TEMPORARY_ERROR",
"reasonCode": "FUSION_TIMEOUT_504",
"previousErrorAt": "2026-08-12T18:03:10Z",
"retryPolicy": "EXPONENTIAL_BACKOFF",
"maxAttempts": 5,
"backoffMs": 60000,
"triggeredBy": "WORKER_AUTO"
},
"payload": {
"purchaseOrder": "(mismo objeto canonico - inmutable)",
"links": {
"status": "/api/v1/purchase-orders/requests/bcd5f9a7-4ced-4d25-bf3f-6ae762f05b41"
}
}
}
Ejemplo 3 - DLQ (Dead Letter Queue)
Mensajes que no se pudieron procesar y superaron maxAttempts, o errores categorizados como NON_RETRYABLE.
{
"eventId": "77f41d2f-257a-4a00-89a4-00c3f0c4a001",
"requestId": "bcd5f9a7-4ced-4d25-bf3f-6ae762f05b41",
"eventType": "PURCHASE_ORDER_DLQ",
"schemaVersion": "1.0",
"payloadVersion": "1.0",
"timestamp": "2026-08-12T18:30:00Z",
"partnerId": "GEPP",
"companyId": "ALPURA_MX",
"sourceSystem": "GEPP_ERP",
"externalReference": "PO-123456",
"idempotencyKey": "GEPP-2026-08-12-001",
"attempt": 5,
"dlq": {
"reasonCategory": "NON_RETRYABLE",
"reasonCode": "FUSION_SUPPLIER_NOT_FOUND",
"message": "El proveedor S-GEPP-001 no existe en Fusion para la BU MX",
"finalAttemptAt": "2026-08-12T18:29:44Z",
"deadLetteredBy": "FusionWorker/instance=nexus-fusion-worker-6d8b-j7hg",
"actionRequired": "Dar de alta proveedor en Fusion + reproceso manual"
},
"correlation": {
"traceparent": "00-0af7651916cd43dd8448eb211c80319c-ffff000000000001-01"
},
"payload": {
"purchaseOrder": "(copia inmutable del payload original)",
"errorHistory": [
{
"attempt": 1,
"at": "2026-08-12T18:01:00Z",
"code": "FUSION_SUPPLIER_NOT_FOUND"
},
{ "attempt": 2, "at": "2026-08-12T18:06:00Z", "code": "FUSION_SUPPLIER_NOT_FOUND" },
{ "attempt": 3, "at": "2026-08-12T18:13:00Z", "code": "FUSION_SUPPLIER_NOT_FOUND" },
{ "attempt": 4, "at": "2026-08-12T18:21:00Z", "code": "FUSION_SUPPLIER_NOT_FOUND" },
{ "attempt": 5, "at": "2026-08-12T18:29:00Z", "code": "FUSION_SUPPLIER_NOT_FOUND" }
]
}
}
Políticas de retry, ack y ordering
ack deadlineinicial sugerido: 600s (10 min).retry policy: 5 intentos con backoff exponencial mínimo 10s y máximo 300s.max delivery attemptspor subscription: 5; después mover al DLQnexus.purchase-order.dlq.message retention: 7 dias en topics + 31 dias en DLQ.ordering: NO habilitado por defecto. Se activa por partner medianteorderingKeysi el socio requiere estricto orden de OCs (impacta throughput).