Capa de interoperabilidad · Prueba técnica de arquitectura

AndesRetail Interop Events 1.0.0

Eventos y comandos internos de la capa de interoperabilidad sobre Kafka. Sobre CloudEvents 1.0 en modo binario: los atributos viajan como cabeceras y el payload de cada mensaje es el campo data. Cada canal declara en bindings.kafka la configuración de su tópico con los valores de producción; es la única fuente para crearlos (ADR-017). En local la herramienta usa factor de replicación 1. Cada operación de recepción declara su grupo consumidor en bindings.kafka.groupId. Diseño en docs/to-be/06-eventos.md.

Tópicos

TópicoParticionesRéplicasPolíticaMensajesDescripción
raw.erp.changes33deleteRawErpChangeCambios crudos del ERP, sin traducir. Interno del erp-adapter y no estable; ningún otro servicio lo lee. Clave tabla y llave
raw.wms.changes33deleteRawWmsChangeCambios crudos del WMS, sin traducir. Interno del wms-adapter y no estable; ningún otro servicio lo lee. Clave sku y ubicación
andes.catalog.products.v1123compactProductChangedEstado de producto. Compactado. Clave sku
andes.pricing.prices.v1123compactPriceChangedEstado de precio. Compactado. Clave sku|channel|priceList; el valor lleva el calendario completo del alcance, así la compactación no borra el precio vigente cuando llega uno futuro (ADR-020). Conserva al menos una hora de versiones antes de compactar (min.compaction.lag.ms), para que la vista de precios de order-service reconstruya su historial dentro de la ventana (ADR-015)
andes.inventory.stock-levels.v1123compactStockLevelChangedStock físico según WMS. Compactado. Clave sku|locationId
andes.sources.sync.v113compactSourceSyncedMarca de sincronización de cada sistema de origen (ADR-018). Compactado. Clave system.
andes.inventory.reservations.v163deleteStockReserved, ReservationConfirmed, ReservationReleased, ReservationExpiredHechos de reservas. Clave orderId
andes.orders.events.v163deleteOrderCreated, OrderConfirmed, OrderCancelled, OrderDispatched, OrderDeliveredHechos de órdenes. Clave orderId
andes.payments.commands.v133deletePaymentRequested, RefundRequestedComandos a payment-service. Clave orderId
andes.payments.events.v163deletePaymentApproved, PaymentRejected, PaymentRefundedHechos de pagos. Clave orderId
andes.oms.commands.v133deleteCreateOmsOrder, CancelOmsOrderComandos al adaptador del OMS. Clave orderId
andes.oms.events.v163deleteOmsOrderCreated, OmsOrderFailedRespuestas del adaptador del OMS. Clave orderId
andes.fulfillment.events.v163deleteDispatchStarted, DeliveryConfirmedHechos de despacho desde el WMS. Clave orderId
andes.audit.v163deleteAuditRecordedRegistro de auditoría. Clave correlationId. Sin vencimiento en Kafka; el archivo con bloqueo contra modificación vive en almacenamiento de objetos (10-observabilidad.md)
andes.inventory.reservations.v1.order-service.retry33deleteStockReserved, ReservationConfirmed, ReservationReleased, ReservationExpiredReintentos de order-service sobre andes.inventory.reservations.v1. Lleva el mensaje original sin cambios en su payload; order-service lo vuelve a procesar cuando vence la espera. Esperas de 1 minuto, 10 minutos y 1 hora (09-resiliencia.md)
andes.inventory.reservations.v1.order-service.dlq33deleteStockReserved, ReservationConfirmed, ReservationReleased, ReservationExpiredDLQ de order-service sobre andes.inventory.reservations.v1. Mensajes que agotaron los reintentos o fallaron por un error no transitorio, que puede ser no cumplir el esquema. Tiene alerta y se reprocesa con el rol ops-admin (09-resiliencia.md)
andes.orders.events.v1.webhook-service.retry33deleteOrderCreated, OrderConfirmed, OrderCancelled, OrderDispatched, OrderDeliveredReintentos de webhook-service sobre andes.orders.events.v1. Lleva el mensaje original sin cambios en su payload; webhook-service lo vuelve a procesar cuando vence la espera. Esperas de 1 minuto, 10 minutos y 1 hora (09-resiliencia.md)
andes.orders.events.v1.webhook-service.dlq33deleteOrderCreated, OrderConfirmed, OrderCancelled, OrderDispatched, OrderDeliveredDLQ de webhook-service sobre andes.orders.events.v1. Mensajes que agotaron los reintentos o fallaron por un error no transitorio, que puede ser no cumplir el esquema. Tiene alerta y se reprocesa con el rol ops-admin (09-resiliencia.md)
andes.orders.events.v1.oms-adapter.retry33deleteOrderCreated, OrderConfirmed, OrderCancelled, OrderDispatched, OrderDeliveredReintentos de oms-adapter sobre andes.orders.events.v1. Lleva el mensaje original sin cambios en su payload; oms-adapter lo vuelve a procesar cuando vence la espera. Esperas de 1 minuto, 10 minutos y 1 hora (09-resiliencia.md)
andes.orders.events.v1.oms-adapter.dlq33deleteOrderCreated, OrderConfirmed, OrderCancelled, OrderDispatched, OrderDeliveredDLQ de oms-adapter sobre andes.orders.events.v1. Mensajes que agotaron los reintentos o fallaron por un error no transitorio, que puede ser no cumplir el esquema. Tiene alerta y se reprocesa con el rol ops-admin (09-resiliencia.md)
andes.orders.events.v1.crm-adapter.retry33deleteOrderCreated, OrderConfirmed, OrderCancelled, OrderDispatched, OrderDeliveredReintentos de crm-adapter sobre andes.orders.events.v1. Lleva el mensaje original sin cambios en su payload; crm-adapter lo vuelve a procesar cuando vence la espera. Esperas de 1 minuto, 10 minutos y 1 hora (09-resiliencia.md)
andes.orders.events.v1.crm-adapter.dlq33deleteOrderCreated, OrderConfirmed, OrderCancelled, OrderDispatched, OrderDeliveredDLQ de crm-adapter sobre andes.orders.events.v1. Mensajes que agotaron los reintentos o fallaron por un error no transitorio, que puede ser no cumplir el esquema. Tiene alerta y se reprocesa con el rol ops-admin (09-resiliencia.md)
andes.payments.commands.v1.payment-service.retry33deletePaymentRequested, RefundRequestedReintentos de payment-service sobre andes.payments.commands.v1. Lleva el mensaje original sin cambios en su payload; payment-service lo vuelve a procesar cuando vence la espera. Esperas de 1 minuto, 10 minutos y 1 hora (09-resiliencia.md)
andes.payments.commands.v1.payment-service.dlq33deletePaymentRequestedMasked, RefundRequestedDLQ de payment-service sobre andes.payments.commands.v1. Mensajes que agotaron los reintentos o fallaron por un error no transitorio, que puede ser no cumplir el esquema. Tiene alerta y se reprocesa con el rol ops-admin (09-resiliencia.md). Cada mensaje es el original salvo los datos sensibles, que llegan enmascarados con el valor fijo --data_redacted-- y listados en la cabecera andes_payload_redacted (ADR-029). Un PaymentRequested llega con payment.token enmascarado y no sirve para volver a cobrar, porque su orden ya venció
andes.payments.events.v1.order-service.retry33deletePaymentApproved, PaymentRejected, PaymentRefundedReintentos de order-service sobre andes.payments.events.v1. Lleva el mensaje original sin cambios en su payload; order-service lo vuelve a procesar cuando vence la espera. Esperas de 1 minuto, 10 minutos y 1 hora (09-resiliencia.md)
andes.payments.events.v1.order-service.dlq33deletePaymentApproved, PaymentRejected, PaymentRefundedDLQ de order-service sobre andes.payments.events.v1. Mensajes que agotaron los reintentos o fallaron por un error no transitorio, que puede ser no cumplir el esquema. Tiene alerta y se reprocesa con el rol ops-admin (09-resiliencia.md)
andes.payments.events.v1.webhook-service.retry33deletePaymentApproved, PaymentRejected, PaymentRefundedReintentos de webhook-service sobre andes.payments.events.v1. Lleva el mensaje original sin cambios en su payload; webhook-service lo vuelve a procesar cuando vence la espera. Esperas de 1 minuto, 10 minutos y 1 hora (09-resiliencia.md)
andes.payments.events.v1.webhook-service.dlq33deletePaymentApproved, PaymentRejected, PaymentRefundedDLQ de webhook-service sobre andes.payments.events.v1. Mensajes que agotaron los reintentos o fallaron por un error no transitorio, que puede ser no cumplir el esquema. Tiene alerta y se reprocesa con el rol ops-admin (09-resiliencia.md)
andes.oms.commands.v1.oms-adapter.retry33deleteCreateOmsOrder, CancelOmsOrderReintentos de oms-adapter sobre andes.oms.commands.v1. Lleva el mensaje original sin cambios en su payload; oms-adapter lo vuelve a procesar cuando vence la espera. En el registro en OMS los reintentos siguen hasta 24 horas antes de ir a DLQ (09-resiliencia.md)
andes.oms.commands.v1.oms-adapter.dlq33deleteCreateOmsOrder, CancelOmsOrderDLQ de oms-adapter sobre andes.oms.commands.v1. Mensajes que agotaron los reintentos o fallaron por un error no transitorio, que puede ser no cumplir el esquema. Tiene alerta y se reprocesa con el rol ops-admin (09-resiliencia.md)
andes.oms.events.v1.order-service.retry33deleteOmsOrderCreated, OmsOrderFailedReintentos de order-service sobre andes.oms.events.v1. Lleva el mensaje original sin cambios en su payload; order-service lo vuelve a procesar cuando vence la espera. Esperas de 1 minuto, 10 minutos y 1 hora (09-resiliencia.md)
andes.oms.events.v1.order-service.dlq33deleteOmsOrderCreated, OmsOrderFailedDLQ de order-service sobre andes.oms.events.v1. Mensajes que agotaron los reintentos o fallaron por un error no transitorio, que puede ser no cumplir el esquema. Tiene alerta y se reprocesa con el rol ops-admin (09-resiliencia.md)
andes.fulfillment.events.v1.order-service.retry33deleteDispatchStarted, DeliveryConfirmedReintentos de order-service sobre andes.fulfillment.events.v1. Lleva el mensaje original sin cambios en su payload; order-service lo vuelve a procesar cuando vence la espera. Esperas de 1 minuto, 10 minutos y 1 hora (09-resiliencia.md)
andes.fulfillment.events.v1.order-service.dlq33deleteDispatchStarted, DeliveryConfirmedDLQ de order-service sobre andes.fulfillment.events.v1. Mensajes que agotaron los reintentos o fallaron por un error no transitorio, que puede ser no cumplir el esquema. Tiene alerta y se reprocesa con el rol ops-admin (09-resiliencia.md)
andes.fulfillment.events.v1.inventory-service.retry33deleteDispatchStarted, DeliveryConfirmedReintentos de inventory-service sobre andes.fulfillment.events.v1. Lleva el mensaje original sin cambios en su payload; inventory-service lo vuelve a procesar cuando vence la espera. Esperas de 1 minuto, 10 minutos y 1 hora (09-resiliencia.md)
andes.fulfillment.events.v1.inventory-service.dlq33deleteDispatchStarted, DeliveryConfirmedDLQ de inventory-service sobre andes.fulfillment.events.v1. Mensajes que agotaron los reintentos o fallaron por un error no transitorio, que puede ser no cumplir el esquema. Tiene alerta y se reprocesa con el rol ops-admin (09-resiliencia.md)

Mensajes

Cambio crudo del ERP (interno, no estable) RawErpChange

Solo lo escribe y lo lee el erp-adapter. No es parte del modelo canónico

name: RawErpChange
title: Cambio crudo del ERP (interno, no estable)
summary: Solo lo escribe y lo lee el erp-adapter. No es parte del modelo canónico
traits:
  - $ref: "#/components/messageTraits/CloudEventsBinary"
headers:
  type: object
  properties:
    ce_type:
      type: string
      const: com.andesretail.erp.change-captured.v1
payload:
  $ref: "#/components/schemas/RawChange"

Movimiento crudo del WMS (interno, no estable) RawWmsChange

Solo lo escribe y lo lee el wms-adapter. No es parte del modelo canónico

name: RawWmsChange
title: Movimiento crudo del WMS (interno, no estable)
summary: Solo lo escribe y lo lee el wms-adapter. No es parte del modelo canónico
traits:
  - $ref: "#/components/messageTraits/CloudEventsBinary"
headers:
  type: object
  properties:
    ce_type:
      type: string
      const: com.andesretail.wms.change-captured.v1
payload:
  $ref: "#/components/schemas/RawChange"

Producto creado o modificado ProductChanged

name: ProductChanged
title: Producto creado o modificado
traits:
  - $ref: "#/components/messageTraits/CloudEventsBinary"
headers:
  type: object
  properties:
    ce_type:
      type: string
      const: com.andesretail.catalog.product-changed.v1
payload:
  $ref: "#/components/schemas/Product"

Precio creado o modificado PriceChanged

name: PriceChanged
title: Precio creado o modificado
traits:
  - $ref: "#/components/messageTraits/CloudEventsBinary"
headers:
  type: object
  properties:
    ce_type:
      type: string
      const: com.andesretail.pricing.price-changed.v1
payload:
  $ref: "#/components/schemas/Price"

Stock físico modificado StockLevelChanged

name: StockLevelChanged
title: Stock físico modificado
traits:
  - $ref: "#/components/messageTraits/CloudEventsBinary"
headers:
  type: object
  properties:
    ce_type:
      type: string
      const: com.andesretail.inventory.stock-level-changed.v1
payload:
  $ref: "#/components/schemas/StockLevel"

Sistema de origen sincronizado hasta una posición SourceSynced

name: SourceSynced
title: Sistema de origen sincronizado hasta una posición
traits:
  - $ref: "#/components/messageTraits/CloudEventsBinary"
headers:
  type: object
  properties:
    ce_type:
      type: string
      const: com.andesretail.sources.synced.v1
payload:
  $ref: "#/components/schemas/SourceSync"

Reserva creada StockReserved

name: StockReserved
title: Reserva creada
traits:
  - $ref: "#/components/messageTraits/CloudEventsBinary"
headers:
  type: object
  properties:
    ce_type:
      type: string
      const: com.andesretail.inventory.stock-reserved.v1
payload:
  $ref: "#/components/schemas/Reservation"

Reserva confirmada ReservationConfirmed

name: ReservationConfirmed
title: Reserva confirmada
traits:
  - $ref: "#/components/messageTraits/CloudEventsBinary"
headers:
  type: object
  properties:
    ce_type:
      type: string
      const: com.andesretail.inventory.reservation-confirmed.v1
payload:
  $ref: "#/components/schemas/Reservation"

Reserva liberada ReservationReleased

name: ReservationReleased
title: Reserva liberada
traits:
  - $ref: "#/components/messageTraits/CloudEventsBinary"
headers:
  type: object
  properties:
    ce_type:
      type: string
      const: com.andesretail.inventory.reservation-released.v1
payload:
  $ref: "#/components/schemas/Reservation"

Reserva vencida ReservationExpired

name: ReservationExpired
title: Reserva vencida
traits:
  - $ref: "#/components/messageTraits/CloudEventsBinary"
headers:
  type: object
  properties:
    ce_type:
      type: string
      const: com.andesretail.inventory.reservation-expired.v1
payload:
  $ref: "#/components/schemas/Reservation"

Orden aceptada con stock reservado OrderCreated

name: OrderCreated
title: Orden aceptada con stock reservado
traits:
  - $ref: "#/components/messageTraits/CloudEventsBinary"
headers:
  type: object
  properties:
    ce_type:
      type: string
      const: com.andesretail.order.created.v1
payload:
  $ref: "#/components/schemas/OrderEvent"

Orden confirmada OrderConfirmed

name: OrderConfirmed
title: Orden confirmada
traits:
  - $ref: "#/components/messageTraits/CloudEventsBinary"
headers:
  type: object
  properties:
    ce_type:
      type: string
      const: com.andesretail.order.confirmed.v1
payload:
  $ref: "#/components/schemas/OrderEvent"

Orden cancelada OrderCancelled

name: OrderCancelled
title: Orden cancelada
traits:
  - $ref: "#/components/messageTraits/CloudEventsBinary"
headers:
  type: object
  properties:
    ce_type:
      type: string
      const: com.andesretail.order.cancelled.v1
payload:
  $ref: "#/components/schemas/OrderEvent"

Orden despachada OrderDispatched

name: OrderDispatched
title: Orden despachada
traits:
  - $ref: "#/components/messageTraits/CloudEventsBinary"
headers:
  type: object
  properties:
    ce_type:
      type: string
      const: com.andesretail.order.dispatched.v1
payload:
  $ref: "#/components/schemas/OrderEvent"

Orden entregada OrderDelivered

name: OrderDelivered
title: Orden entregada
traits:
  - $ref: "#/components/messageTraits/CloudEventsBinary"
headers:
  type: object
  properties:
    ce_type:
      type: string
      const: com.andesretail.order.delivered.v1
payload:
  $ref: "#/components/schemas/OrderEvent"

Solicitud de cobro PaymentRequested

name: PaymentRequested
title: Solicitud de cobro
traits:
  - $ref: "#/components/messageTraits/CloudEventsBinary"
headers:
  type: object
  properties:
    ce_type:
      type: string
      const: com.andesretail.payment.requested.v1
payload:
  $ref: "#/components/schemas/PaymentCommand"

Solicitud de cobro con el token enmascarado (DLQ de payment-service) PaymentRequestedMasked

El PaymentRequested original con payment.token reemplazado por --data_redacted-- (ADR-029)

name: PaymentRequestedMasked
title: Solicitud de cobro con el token enmascarado (DLQ de payment-service)
summary: El PaymentRequested original con payment.token reemplazado por --data_redacted-- (ADR-029)
description: Mismo sobre que PaymentRequested, con ce_type com.andesretail.payment.requested.v1 sin
  cambios. No fija ce_type como constante para no confundirse con PaymentRequested en los índices
  por tipo; lo distingue la cabecera andes_payload_redacted
traits:
  - $ref: "#/components/messageTraits/CloudEventsBinary"
headers:
  type: object
  required:
    - andes_payload_redacted
  properties:
    andes_payload_redacted:
      type: string
      description: Campos del payload enmascarados al pasar a la DLQ, separados por coma (ADR-029); `*` si
        el payload no era JSON y se enmascaró entero
payload:
  $ref: "#/components/schemas/PaymentCommandMasked"

Solicitud de reembolso RefundRequested

name: RefundRequested
title: Solicitud de reembolso
traits:
  - $ref: "#/components/messageTraits/CloudEventsBinary"
headers:
  type: object
  properties:
    ce_type:
      type: string
      const: com.andesretail.payment.refund-requested.v1
payload:
  $ref: "#/components/schemas/RefundCommand"

Pago aprobado PaymentApproved

name: PaymentApproved
title: Pago aprobado
traits:
  - $ref: "#/components/messageTraits/CloudEventsBinary"
headers:
  type: object
  properties:
    ce_type:
      type: string
      const: com.andesretail.payment.approved.v1
payload:
  $ref: "#/components/schemas/PaymentEvent"

Pago rechazado PaymentRejected

name: PaymentRejected
title: Pago rechazado
traits:
  - $ref: "#/components/messageTraits/CloudEventsBinary"
headers:
  type: object
  properties:
    ce_type:
      type: string
      const: com.andesretail.payment.rejected.v1
payload:
  $ref: "#/components/schemas/PaymentEvent"

Pago reembolsado PaymentRefunded

name: PaymentRefunded
title: Pago reembolsado
traits:
  - $ref: "#/components/messageTraits/CloudEventsBinary"
headers:
  type: object
  properties:
    ce_type:
      type: string
      const: com.andesretail.payment.refunded.v1
payload:
  $ref: "#/components/schemas/PaymentEvent"

Registrar orden confirmada en el OMS CreateOmsOrder

name: CreateOmsOrder
title: Registrar orden confirmada en el OMS
traits:
  - $ref: "#/components/messageTraits/CloudEventsBinary"
headers:
  type: object
  properties:
    ce_type:
      type: string
      const: com.andesretail.oms.create-order.v1
payload:
  $ref: "#/components/schemas/OmsCommand"

Cancelar orden en el OMS CancelOmsOrder

name: CancelOmsOrder
title: Cancelar orden en el OMS
traits:
  - $ref: "#/components/messageTraits/CloudEventsBinary"
headers:
  type: object
  properties:
    ce_type:
      type: string
      const: com.andesretail.oms.cancel-order.v1
payload:
  $ref: "#/components/schemas/OmsCommand"

Orden registrada en el OMS OmsOrderCreated

name: OmsOrderCreated
title: Orden registrada en el OMS
traits:
  - $ref: "#/components/messageTraits/CloudEventsBinary"
headers:
  type: object
  properties:
    ce_type:
      type: string
      const: com.andesretail.oms.order-created.v1
payload:
  $ref: "#/components/schemas/OmsEvent"

El OMS rechazó la orden de forma definitiva OmsOrderFailed

name: OmsOrderFailed
title: El OMS rechazó la orden de forma definitiva
traits:
  - $ref: "#/components/messageTraits/CloudEventsBinary"
headers:
  type: object
  properties:
    ce_type:
      type: string
      const: com.andesretail.oms.order-failed.v1
payload:
  $ref: "#/components/schemas/OmsEvent"

Despacho iniciado DispatchStarted

name: DispatchStarted
title: Despacho iniciado
traits:
  - $ref: "#/components/messageTraits/CloudEventsBinary"
headers:
  type: object
  properties:
    ce_type:
      type: string
      const: com.andesretail.fulfillment.dispatch-started.v1
payload:
  $ref: "#/components/schemas/FulfillmentEvent"

Entrega confirmada DeliveryConfirmed

name: DeliveryConfirmed
title: Entrega confirmada
traits:
  - $ref: "#/components/messageTraits/CloudEventsBinary"
headers:
  type: object
  properties:
    ce_type:
      type: string
      const: com.andesretail.fulfillment.delivered.v1
payload:
  $ref: "#/components/schemas/FulfillmentEvent"

Registro de auditoría AuditRecorded

name: AuditRecorded
title: Registro de auditoría
traits:
  - $ref: "#/components/messageTraits/CloudEventsBinary"
headers:
  type: object
  properties:
    ce_type:
      type: string
      const: com.andesretail.audit.recorded.v1
payload:
  $ref: "#/components/schemas/AuditRecord"

Esquemas

MaskedValue

type: string
const: --data_redacted--
description: Valor fijo de un dato sensible enmascarado al pasar a una DLQ (ADR-029). El campo
  enmascarado queda listado en la cabecera andes_payload_redacted

Money

type: string
pattern: ^\d+\.\d{2}$

RawChange

type: object
description: Cambio del legado tal como lo leyó el adaptador, sin traducir. Interno del adaptador y
  no estable; su forma sigue al legado y puede cambiar sin aviso ni versión nueva del tópico. Ningún
  servicio de dominio lo consume; lo que sale de la capa es el evento canónico. Solo trae datos de
  producto, precio y stock, nunca datos personales
required:
  - system
  - position
  - capturedAt
  - record
properties:
  system:
    type: string
    enum:
      - erp
      - wms
  position:
    type: string
    description: Posición del cambio en el feed del legado (lsn del ERP, seq del WMS), para retomar la
      lectura y deduplicar
  capturedAt:
    type: string
    format: date-time
    description: Momento en que el adaptador leyó el cambio
  record:
    type: object
    additionalProperties: true
    description: Fila o movimiento con los nombres de campo del legado

Product

type: object
required:
  - sku
  - status
  - channels
  - updatedAt
properties:
  sku:
    type: string
  name:
    type: string
  status:
    type: string
    enum:
      - ACTIVE
      - INACTIVE
  channels:
    type: array
    description: Canales en los que se puede vender (surtido). Un SKU fuera de esta lista sale en
      notFound de stock y de precio para ese canal (ADR-019)
    items:
      type: string
  updatedAt:
    type: string
    format: date-time

Price

type: object
required:
  - sku
  - channel
  - priceList
  - currency
  - listPrice
  - salePrice
  - priceVersion
  - validFrom
  - updatedAt
properties:
  sku:
    type: string
  channel:
    type: string
  priceList:
    type: string
  currency:
    type: string
    pattern: ^[A-Z]{3}$
    description: Código ISO 4217
  listPrice:
    $ref: "#/components/schemas/Money"
  salePrice:
    $ref: "#/components/schemas/Money"
  taxIncluded:
    type: boolean
    default: false
  priceVersion:
    type: string
  validFrom:
    type: string
    format: date-time
  validTo:
    type: string
    format: date-time
  updatedAt:
    type: string
    format: date-time
  correction:
    type: boolean
    default: false
    description: true si el cambio corrige un precio mal cargado. Las versiones anteriores de la misma
      llave (sku|channel|priceList) dejan de aceptarse de inmediato al validar una orden, aunque
      estén dentro de la ventana de tolerancia del canal. Lo marca el erp-adapter según el motivo
      del cambio en el ERP. Ausente equivale a false (ADR-015)
  schedule:
    type: array
    maxItems: 10
    description: "Calendario del alcance sku|channel|priceList: todas sus ventanas no vencidas, incluida
      la vigente al publicar (ADR-020). Regla de selección: entre las ventanas con validFrom <=
      ahora y sin validTo o con ahora < validTo, gana la de validFrom más reciente; si ninguna está
      vigente, no hay precio. Hora UTC. Los campos de primer nivel son la ventana vigente al
      publicar; un consumidor que no lee schedule se comporta como antes. Ausente equivale a una
      sola ventana, la de primer nivel. Si al publicar ninguna ventana rige, el primer nivel es la
      próxima futura; si todas vencieron, schedule va vacío y el primer nivel es la más reciente,
      con su validTo ya pasado. Un schedule presente, aunque esté vacío, manda sobre el primer
      nivel"
    items:
      $ref: "#/components/schemas/PriceWindow"

PriceWindow

type: object
required:
  - currency
  - listPrice
  - salePrice
  - priceVersion
  - validFrom
properties:
  currency:
    type: string
    pattern: ^[A-Z]{3}$
    description: Código ISO 4217
  listPrice:
    $ref: "#/components/schemas/Money"
  salePrice:
    $ref: "#/components/schemas/Money"
  taxIncluded:
    type: boolean
    default: false
  priceVersion:
    type: string
  validFrom:
    type: string
    format: date-time
  validTo:
    type: string
    format: date-time
    description: Fin de la ventana, excluido. Ausente es sin fin

SourceSync

type: object
required:
  - system
  - position
  - syncedAt
properties:
  system:
    type: string
    enum:
      - erp
      - wms
    description: Sistema de origen. Es la clave del mensaje
  position:
    type: string
    description: Última posición del feed cuyos cambios ya están publicados y confirmados (lsn del ERP o
      seq del WMS), en texto
  syncedAt:
    type: string
    format: date-time
    description: Momento UTC del último sondeo exitoso al legado. Si el legado no responde o el circuito
      está abierto, no avanza

StockLevel

type: object
required:
  - sku
  - locationId
  - onHand
  - wmsAsOf
properties:
  sku:
    type: string
  locationId:
    type: string
  onHand:
    type: integer
    minimum: 0
  wmsAsOf:
    type: string
    format: date-time
    description: Momento del dato en el WMS. Un evento más viejo que el dato guardado se descarta
  snapshot:
    type: boolean
    description: true si proviene de la conciliación completa

Reservation

type: object
required:
  - reservationId
  - orderId
  - partnerId
  - status
  - lines
properties:
  reservationId:
    type: string
  orderId:
    type: string
  partnerId:
    type: string
  status:
    type: string
    enum:
      - HELD
      - CONFIRMED
      - RELEASED
      - EXPIRED
      - CONSUMED
  reason:
    type: string
  expiresAt:
    type: string
    format: date-time
  lines:
    type: array
    items:
      type: object
      required:
        - sku
        - locationId
        - quantity
      properties:
        sku:
          type: string
        locationId:
          type: string
        quantity:
          type: integer
          minimum: 1

OrderEvent

type: object
description: Sin datos personales. Quien necesite la dirección la pide a order-service con el orderId
required:
  - orderId
  - externalOrderId
  - partnerId
  - channel
  - status
  - occurredAt
properties:
  orderId:
    type: string
  externalOrderId:
    type: string
  partnerId:
    type: string
  channel:
    type: string
  customerId:
    type: string
  status:
    type: string
    enum:
      - PENDING_PAYMENT
      - CONFIRMED
      - CANCELLED
      - DISPATCHED
      - DELIVERED
  reason:
    type: string
  total:
    type: object
    properties:
      amount:
        $ref: "#/components/schemas/Money"
      currency:
        type: string
  lines:
    type: array
    items:
      type: object
      required:
        - sku
        - quantity
        - unitPrice
      properties:
        sku:
          type: string
        locationId:
          type: string
        quantity:
          type: integer
        unitPrice:
          $ref: "#/components/schemas/Money"
  occurredAt:
    type: string
    format: date-time

PaymentCommand

type: object
required:
  - orderId
  - partnerId
  - amount
  - currency
if:
  properties:
    mode:
      const: CAPTURE
  required:
    - mode
then:
  required:
    - token
properties:
  orderId:
    type: string
    description: También es la llave de idempotencia hacia la pasarela
  partnerId:
    type: string
  mode:
    type: string
    enum:
      - CAPTURE
      - PREPAID
  method:
    type: string
  token:
    type: string
    description: Token de la pasarela. Nunca datos de tarjeta. Dato sensible; en la DLQ llega
      enmascarado (MaskedValue, ADR-029)
  amount:
    $ref: "#/components/schemas/Money"
  currency:
    type: string
  expiresAt:
    type: string
    format: date-time
    description: Límite para obtener resultado; coincide con el vencimiento de la reserva

PaymentCommandMasked

description: PaymentCommand tal como queda en la DLQ de payment-service, con el token enmascarado (ADR-029)
allOf:
  - $ref: "#/components/schemas/PaymentCommand"
  - type: object
    properties:
      token:
        $ref: "#/components/schemas/MaskedValue"

RefundCommand

type: object
description: Reembolso de un cobro ya aprobado (compensación de la saga). Sin token ni datos de tarjeta
required:
  - orderId
  - partnerId
  - amount
  - currency
properties:
  orderId:
    type: string
    description: Orden del cobro que se reembolsa
  partnerId:
    type: string
  mode:
    type: string
    enum:
      - CAPTURE
      - PREPAID
    description: El del cobro original. Un PREPAID se registra sin llamar a la pasarela
  amount:
    $ref: "#/components/schemas/Money"
  currency:
    type: string
  reason:
    type: string
    description: Motivo de la compensación
    por ejemplo LATE_APPROVAL u ORDER_CANCELLED: null

PaymentEvent

type: object
required:
  - orderId
  - partnerId
  - status
  - amount
  - currency
  - occurredAt
properties:
  orderId:
    type: string
  partnerId:
    type: string
  status:
    type: string
    enum:
      - APPROVED
      - REJECTED
      - REFUNDED
  gatewayReference:
    type: string
  reasonCode:
    type: string
    enum:
      - CARD_DECLINED
      - DECLINED
      - AUTHENTICATION_REQUIRED
      - PAYMENT_TIMEOUT
      - PAYMENT_EXPIRED
      - INVALID_PAYMENT_REQUEST
      - LATE_APPROVAL
    description: "Causa de un REJECTED, o de un REFUNDED automático (LATE_APPROVAL: la pasarela aprobó
      después de que la capa rechazó por tiempo). Ausente en un APPROVED. order-service lo traduce a
      statusReason PAYMENT_REJECTED"
  amount:
    $ref: "#/components/schemas/Money"
  currency:
    type: string
  occurredAt:
    type: string
    format: date-time

OmsCommand

type: object
description: Sin datos personales. CreateOmsOrder lleva lo que el OMS necesita de la orden, salvo
  destinatario y dirección (ADR-025), para que oms-adapter no guarde estado de negocio.
  CancelOmsOrder solo necesita orderId y reason
required:
  - orderId
properties:
  orderId:
    type: string
  externalOrderId:
    type: string
  partnerId:
    type: string
  channel:
    type: string
  reason:
    type: string
  total:
    type: object
    properties:
      amount:
        $ref: "#/components/schemas/Money"
      currency:
        type: string
  lines:
    type: array
    items:
      type: object
      required:
        - sku
        - quantity
        - unitPrice
      properties:
        sku:
          type: string
        locationId:
          type: string
        quantity:
          type: integer
        unitPrice:
          $ref: "#/components/schemas/Money"

OmsEvent

type: object
required:
  - orderId
  - occurredAt
properties:
  orderId:
    type: string
  omsOrderId:
    type: string
  errorCode:
    type: string
  occurredAt:
    type: string
    format: date-time

FulfillmentEvent

type: object
required:
  - orderId
  - occurredAt
properties:
  orderId:
    type: string
  omsOrderId:
    type: string
  locationId:
    type: string
  carrier:
    type: string
  trackingCode:
    type: string
  lines:
    type: array
    items:
      type: object
      properties:
        sku:
          type: string
        quantity:
          type: integer
  occurredAt:
    type: string
    format: date-time

AuditRecord

type: object
required:
  - correlationId
  - actor
  - action
  - resource
  - result
  - occurredAt
properties:
  correlationId:
    type: string
  actor:
    type: object
    required:
      - type
      - id
    properties:
      type:
        type: string
        enum:
          - PARTNER
          - OPERATOR
          - SERVICE
      id:
        type: string
  action:
    type: string
  resource:
    type: string
  result:
    type: string
    enum:
      - SUCCESS
      - DENIED
      - FAILED
  origin:
    type: string
  occurredAt:
    type: string
    format: date-time

Contrato completo (YAML)