Transferencias de inventario

Las transferencias mueven stock de una ubicación de origen a una de destino. Todos los endpoints de este recurso requieren el permiso inventory:transfer.

Lee la sección de semántica de escritura antes de integrar

El momento exacto en que el stock se mueve, y las garantías (o falta de ellas) sobre el estado de una transferencia, son el detalle más importante de este recurso. Están descritos abajo con el comportamiento real de producción — no es el diseño ideal, es lo que el sistema hace hoy.

Listar transferencias

Lista las transferencias del tenant, con filtros opcionales

pagenumber

Página, 1-indexada. Default: 1.

limitnumber

Elementos por página. Default: 20.

statusstring

Filtro por estado: pending, approved, received, cancelled.

originIdstring

Filtro por ubicación de origen.

destinationIdstring

Filtro por ubicación de destino.

200
{
  "transfers": [
    {
      "_id": "66f1a2b3c4d5e6f7a8b9c0d1",
      "originId": "loc_abc123",
      "destinationId": "loc_def456",
      "status": "pending",
      "productsList": [
        { "sku": "CAM-ROJO-M", "reassignAmount": 10, "originAmount": 42 }
      ],
      "createdBy": "usr_789"
    }
  ],
  "pagination": { "page": 1, "limit": 20, "total": 12, "pages": 1 }
}

Consultar una transferencia

Consulta una transferencia por ID

idstringrequired

ID de la transferencia.

200
{ "_id": "66f1a2b3c4d5e6f7a8b9c0d1", "originId": "loc_abc123", "destinationId": "loc_def456", "status": "pending" }
404
{ "code": "not-found" }

Crear una transferencia

Crea una transferencia en estado 'pending'. NO mueve stock todavía.

originIdstringrequired

Ubicación de origen.

destinationIdstringrequired

Ubicación de destino.

productsListarrayrequired

Lista no vacía de { sku, reassignAmount, originAmount }.

{
  "originId": "loc_abc123",
  "destinationId": "loc_def456",
  "productsList": [{ "sku": "CAM-ROJO-M", "reassignAmount": 10, "originAmount": 42 }]
}
201
{
  "_id": "66f1a2b3c4d5e6f7a8b9c0d1",
  "originId": "loc_abc123",
  "destinationId": "loc_def456",
  "productsList": [{ "sku": "CAM-ROJO-M", "reassignAmount": 10, "originAmount": 42 }],
  "status": "pending",
  "createdBy": "usr_789"
}
400
{ "code": "bad-request/missing-origin" }

Crear solo registra intención

POST /inventory/transfers no mueve stock. Únicamente crea el registro con status: 'pending'. El stock se mueve exclusivamente al recibir la transferencia (ver abajo).

Aprobar

Marca la transferencia como aprobada

idstringrequired

ID de la transferencia.

No requiere cuerpo — el handler no lee event.body.

{}
200
{ "_id": "66f1a2b3c4d5e6f7a8b9c0d1", "status": "approved", "approvedBy": "usr_789", "approvedAt": "2026-06-01T10:00:00.000Z" }
404
{ "code": "not-found" }

Recibir (mueve el stock)

Aplica el movimiento de stock: decrementa origen, incrementa destino

idstringrequired

ID de la transferencia.

receivedItemsarray

Lista opcional de { sku, reassignAmount, originAmount } a recibir. Si se omite, se usa la lista original de la transferencia.

{
  "receivedItems": [{ "sku": "CAM-ROJO-M", "reassignAmount": 10, "originAmount": 42 }]
}
200
{ "_id": "66f1a2b3c4d5e6f7a8b9c0d1", "status": "received" }
404
{ "code": "not-found" }

Esto es lo único que mueve stock en el ciclo de vida de una transferencia. Por cada ítem recibido:

  1. El stock de origen se decrementa por reassignAmount, con el resultado forzado a un mínimo de 0si el monto excede el stock disponible en origen, la resta se satura en 0 en lugar de rechazar la operación. No se produce ningún error visible por este sobre-envío.
  2. El stock de destino se incrementa por el mismo reassignAmount.
  3. Internamente se intenta consumir/crear lotes FIFO para mantener la trazabilidad de costos; si esa parte falla, la recepción no falla — el error de lotes se registra como advertencia y la petición responde 200 igual.

La recepción NO emite el evento 'Inventory Updated'

A diferencia de la escritura directa de stock (PUT, bulk-update, ajustes), la recepción de una transferencia mueve el stock por una ruta distinta que no dispara el evento Inventory Updated ni la verificación de stock bajo. Si tu integración escucha ese evento para reaccionar a cambios de stock, no verá los movimientos causados por transferencias recibidas.

Sin guarda de transición de estado — una transferencia puede recibirse dos veces

No existe una verificación de que la transferencia esté en el estado correcto antes de aprobar, recibir o cancelar. Esto significa, en el comportamiento actual de producción:

  • Puedes llamar receive sobre una transferencia que ya está en received — el movimiento de stock se vuelve a aplicar, duplicando el efecto.
  • Puedes cancel una transferencia que ya fue received.
  • La operación receive no es idempotente: reenviar la misma petición reaplica el delta de stock cada vez, porque no hay clave de deduplicación.

Tu integración es responsable de no reintentar receive a ciegas y de rastrear localmente el estado antes de volver a llamarlo.

Cancelar

Marca la transferencia como cancelada

idstringrequired

ID de la transferencia.

cancellationReasonstring

Motivo de la cancelación (o el campo alterno `reason`).

{ "cancellationReason": "Error al capturar destino" }
200
{ "_id": "66f1a2b3c4d5e6f7a8b9c0d1", "status": "cancelled" }
404
{ "code": "not-found" }

Cancelar no revierte ningún movimiento de stock ya aplicado por un receive previo — solo cambia el campo status. Si ya recibiste la transferencia y luego la cancelas, el stock movido permanece movido.

Ejemplo — ciclo completo

# 1. Crear
curl -X POST 'https://api.fenicia.io/inventory/transfers' \
  -H 'Authorization: Bearer fkapi_tu_api_key' \
  -H 'Content-Type: application/json' \
  -d '{
    "originId": "loc_abc123",
    "destinationId": "loc_def456",
    "productsList": [{ "sku": "CAM-ROJO-M", "reassignAmount": 10, "originAmount": 42 }]
  }'
 
# 2. Recibir (mueve el stock)
curl -X POST 'https://api.fenicia.io/inventory/transfers/66f1a2b3c4d5e6f7a8b9c0d1/receive' \
  -H 'Authorization: Bearer fkapi_tu_api_key'

Errores

CódigoStatusDescripción
bad-request/missing-tenant401No se pudo resolver el tenant de la API key.
bad-request/missing-origin400Falta originId.
bad-request/missing-destination400Falta destinationId.
bad-request/missing-products400Falta productsList o está vacía.
not-found404No existe una transferencia con ese ID.
internal-error500Error interno.

Siguientes pasos