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.
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.
| Estado | Qué significa | Banderas que deriva Smartfleet |
|---|---|---|
ABIERTO | El pedido llegó a bodega y nadie lo alistó todavía | pendiente de alisto, no despachable |
PARCIAL | Bodega alistó una parte | pendiente de alisto, no despachable |
COMPLETO | Bodega terminó de alistar | alistado, despachable |
CERRADO | Bodega 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).
| Campo | Tipo | Descripción |
|---|---|---|
receipt_code | string | Código de la factura en Smartfleet. Es la forma recomendada. |
receipt_source_id | string | source_id con el que el ERP sincronizó la factura. |
receipt_source_system | string | Sistema del receipt_source_id. Opcional; hace falta sólo si el mismo source_id existe en dos sistemas de origen. |
Estado del alisto
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
source_system | string (≤30) | sí | 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_state | ABIERTO | PARCIAL | COMPLETO | CERRADO | sí | Estado del alisto. |
progress_pct | number 0..100 | depende | Avance. 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_at | string ISO 8601 | sí | Instante de la medición, con offset UTC explícito (Z o ±HH:MM). |
blocked_reason | string (≤500) | no | Por qué el alisto está detenido. Se muestra en el panel. |
priority | integer | no | Prioridad del alisto. Omitirlo no borra la que ya haya. |
lines | array | no | Cantidades alistadas por producto. Omitirlo no toca ninguna línea. |
lines[].product_code | string | sí (dentro de lines) | Producto, tal como está en la factura. |
lines[].quantity_prepared | number ≥ 0 | sí (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.
| Caso | Respuesta |
|---|---|
reported_at posterior al guardado | 200, meta.applied: true. Se escribe. |
reported_at igual al guardado | 200, meta.applied: true. Reenviar el mismo reporte deja el mismo estado: es idempotente. |
reported_at anterior al guardado | 200, meta.applied: false, meta.reason: "STALE_REPORT". No se escribe nada y data trae el estado real. |
{
"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
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ón | Código | HTTP |
|---|---|---|
| La factura no existe, está inactiva o es de otro cliente | SYNC_MISSING_DEPENDENCIES | 422 |
receipt_source_id coincide con 2 facturas de distintos sistemas | SYNC_CONFLICT | 409 |
fulfillment_state fuera de los 4 valores | SYNC_VALIDATION_ERROR | 400 |
PARCIAL sin progress_pct | SYNC_VALIDATION_ERROR | 400 |
reported_at sin offset UTC explícito | SYNC_VALIDATION_ERROR | 400 |
reported_at con una fecha o una hora que no existe (2026-02-31T10:00:00Z, 2026-08-19T25:00:00Z) | SYNC_VALIDATION_ERROR | 400 |
| Más de 200 ítems en el lote | SYNC_VALIDATION_ERROR | 400 |
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:
| Sede | Quié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 WMS | Esta 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.