Operaciones
Alisto en bodega (WMS)

Alisto en bodega (WMS)

Un WMS reporta acá cómo va el alisto de una factura: si está abierta, parcial, completa o cerrada, qué porcentaje lleva, cuándo se midió y —si aplica— por qué está bloqueada. Con eso Smartfleet decide si la factura puede subir a una guía.

El endpoint es genérico: no asume ningún WMS. El nombre del sistema que reporta entra como el valor de source_system.

POST/transportes/sync/receipts/fulfillment

Reporta el estado de alisto de una factura que ya existe en Smartfleet.

Los cuatro estados

fulfillment_state es un set cerrado. Se normaliza a mayúsculas, así que completo y COMPLETO son el mismo valor; cualquier otro valor es 400.

EstadoQué significaBanderas que deriva Smartfleet
ABIERTOEl pedido llegó a bodega y nadie lo alistó todavíapendiente de alisto, no despachable
PARCIALBodega alistó una partependiente de alisto, no despachable
COMPLETOBodega terminó de alistaralistado, despachable
CERRADOBodega cerró el pedido (no se alista más)alistado, despachable

Las dos banderas internas (is_pending_wms_preparation, is_dispatched_wms) se derivan del estado. Si el payload las trae, se ignoran y la respuesta las declara en meta.ignored_fields — no hay forma de dejar una factura marcada como alistada sin que su estado lo diga.

Campos del payload

Cómo se direcciona la factura

Se manda una de las dos formas. La ruta nunca crea facturas: la factura tiene que existir de antes (la sincroniza el ERP por /transportes/sync/receipts).

CampoTipoDescripción
receipt_codestringCódigo de la factura en Smartfleet. Es la forma recomendada.
receipt_source_idstringsource_id con el que el ERP sincronizó la factura.
receipt_source_systemstringSistema del receipt_source_id. Opcional; hace falta sólo si el mismo source_id existe en dos sistemas de origen.

Estado del alisto

CampoTipoRequeridoDescripción
source_systemstring (≤30)Sistema que reporta. Se guarda como último reportero del alisto de esa factura y vuelve en data.wms_last_reporter. Un reporte posterior de otro sistema lo reemplaza. No reemplaza el sistema de origen del documento (el del ERP que lo sincronizó).
fulfillment_stateABIERTO | PARCIAL | COMPLETO | CERRADOEstado del alisto.
progress_pctnumber 0..100dependeAvance. Omitirlo con ABIERTO guarda 0; con COMPLETO o CERRADO guarda 100. Con PARCIAL es obligatorio: un parcial sin porcentaje no se puede despachar contra nada.
reported_atstring ISO 8601Instante de la medición, con offset UTC explícito (Z o ±HH:MM).
blocked_reasonstring (≤500)noPor qué el alisto está detenido. Se muestra en el panel.
priorityintegernoPrioridad del alisto. Omitirlo no borra la que ya haya.
linesarraynoCantidades alistadas por producto. Omitirlo no toca ninguna línea.
lines[].product_codestringsí (dentro de lines)Producto, tal como está en la factura.
lines[].quantity_preparednumber ≥ 0sí (dentro de lines)Cantidad alistada de ese producto.

Respuesta

{
  "ok": true,
  "code": "SYNC_OK",
  "message": "Sync processed successfully.",
  "data": {
    "receipt_id": 4821,
    "receipt_code": "FAC-000123",
    "wms_fulfillment_state": "PARCIAL",
    "wms_preparation_progress_pct": 55.5,
    "wms_preparation_updated_at": "2026-08-19T16:00:00.000Z",
    "is_pending_wms_preparation": true,
    "is_dispatched_wms": false,
    "wms_blocked_reason": null,
    "wms_last_reporter": "wms-bodega",
    "lines_applied": 2,
    "lines_not_found": 0
  },
  "meta": {
    "route": "/transportes/sync/receipts/fulfillment",
    "method": "PostReceiptFulfillmentSync",
    "applied": true,
    "applied_fields": [
      "wms_fulfillment_state",
      "wms_preparation_progress_pct",
      "wms_preparation_updated_at",
      "is_pending_wms_preparation",
      "is_dispatched_wms",
      "wms_blocked_reason",
      "wms_last_reporter"
    ],
    "ignored_fields": [],
    "source_system": "wms-bodega"
  }
}

meta.applied dice si el reporte cambió algo. meta.applied_fields y meta.ignored_fields dicen exactamente qué se escribió y qué no, con su razón: un 200 no significa por sí solo que lo que mandaste quedó guardado.

lines_not_found cuenta los product_code que la factura no tiene. No es un error: el resto de las líneas se aplica y el estado del documento se escribe igual. Divergir del detalle de la factura es normal (artículos de servicio, sustituciones) y no puede tumbar el reporte.

Reportes desordenados y reenvíos

La comparación es contra el reported_at guardado, no contra el reloj del servidor. Así dos WMS reportando en desorden —o una cola que reintenta— no pueden retroceder el alisto.

CasoRespuesta
reported_at posterior al guardado200, meta.applied: true. Se escribe.
reported_at igual al guardado200, meta.applied: true. Reenviar el mismo reporte deja el mismo estado: es idempotente.
reported_at anterior al guardado200, meta.applied: false, meta.reason: "STALE_REPORT". No se escribe nada y data trae el estado real.
Reporte viejo — no se aplica
{
  "ok": true,
  "code": "SYNC_OK",
  "data": {
    "receipt_code": "FAC-000123",
    "wms_fulfillment_state": "COMPLETO",
    "wms_preparation_progress_pct": 100
  },
  "meta": {
    "applied": false,
    "reason": "STALE_REPORT",
    "applied_fields": [],
    "ignored_fields": [
      { "field": "wms_fulfillment_state", "reason": "STALE_REPORT" }
    ]
  }
}

Lotes

POST/transportes/sync/receipts/fulfillment/batch

Hasta 200 reportes de alisto por request, best-effort: un ítem inválido no aborta el lote.

El lote responde 200 con un resultado por ítem, en el mismo orden en que se enviaron. Más de 200 ítems es 400 y no se procesa ninguno.

{
  "ok": true,
  "code": "SYNC_BATCH_OK",
  "data": {
    "total": 2,
    "succeeded": 1,
    "failed": 1,
    "results": [
      {
        "receipt_code": "FAC-000123",
        "ok": true,
        "code": "SYNC_OK",
        "applied": true,
        "wms_fulfillment_state": "COMPLETO",
        "lines_applied": 0,
        "lines_not_found": 0
      },
      {
        "receipt_code": "FAC-000124",
        "ok": false,
        "code": "SYNC_MISSING_DEPENDENCIES",
        "message": "Missing related records"
      }
    ]
  }
}

Errores

SituaciónCódigoHTTP
La factura no existe, está inactiva o es de otro clienteSYNC_MISSING_DEPENDENCIES422
receipt_source_id coincide con 2 facturas de distintos sistemasSYNC_CONFLICT409
fulfillment_state fuera de los 4 valoresSYNC_VALIDATION_ERROR400
PARCIAL sin progress_pctSYNC_VALIDATION_ERROR400
reported_at sin offset UTC explícitoSYNC_VALIDATION_ERROR400
reported_at con una fecha o una hora que no existe (2026-02-31T10:00:00Z, 2026-08-19T25:00:00Z)SYNC_VALIDATION_ERROR400
Más de 200 ítems en el loteSYNC_VALIDATION_ERROR400

Sobre reported_at: el offset es obligatorio y la fecha-hora se verifica contra el calendario. Un 2026-02-31 no se "corrige" al 3 de marzo — se rechaza. Si tu WMS reporta fecha y hora por separado y sin zona, estámpalas con la zona configurada del cliente (America/Costa_Rica), que es la que nombra el mensaje de error.

Quién escribe el alisto: lo decide la sede

Las cinco columnas de alisto —estado, porcentaje, instante, prioridad y motivo de bloqueo— no tienen un dueño fijo. Lo define la sede de la factura:

SedeQuién puede escribirlas
Con WMS (requires_wms_ready_for_dispatch = true)Sólo esta ruta. Lo que manden por /transportes/sync/receipts se descarta y sale declarado en meta.ignored_fields con reason: "WMS_OWNED_AT_HUB".
Sin WMSEsta ruta y /transportes/sync/receipts, sin importar el estado de la factura.

Lo que nunca se escribe desde ninguna de las dos rutas cuando la factura ya está en una guía: su estado, su sede y sus banderas de despacho. Eso lo maneja la operación de Smartfleet y sale declarado con reason: "RECEIPT_PROTECTED".

En una sede con WMS la regla aplica también al alta: una factura nueva que llega por /transportes/sync/receipts con alisto o con prioridad no los hereda del ERP — el estado del alisto arranca vacío y lo estrena el primer reporte de esta ruta. Es la misma frase de arriba («sólo esta ruta») sin excepción para el primer día del documento.

Un valor malformado no te tumba la factura

Por /transportes/sync/receipts estas columnas son campos de paso: si mandás un wms_preparation_updated_at sin offset, un porcentaje fuera de 0..100 o un estado fuera del set, ese campo se ignora y sale declarado con reason: "INVALID_VALUE", y el resto de la factura se aplica con 200. Nunca un 400 por un campo que no es el asunto de esa llamada.

Por esta ruta es al revés: acá el alisto es el asunto, así que un valor malformado es 400 y no se escribe nada. Si tu WMS manda mal el porcentaje, querés enterarte, no que se guarde a medias.

Las cantidades alistadas por línea (quantity_prepared) son siempre de esta ruta, en las dos configuraciones de sede.