Lotes y Valuación FIFO

Fenicia calcula el costo de tu inventario con capas de costo (lotes) bajo la metodología FIFO (primero en entrar, primero en salir). Cada vez que entra stock — por una recepción de compra, una transferencia recibida o una devolución — se crea internamente un lote con su costo unitario y su cantidad. Cuando el stock se consume (por ejemplo, la venta de una orden), Fenicia agota primero los lotes más antiguos.

Superficie de solo lectura

No existe un endpoint público para crear o cancelar un lote manualmente. Los lotes se generan internamente a partir de recepciones de orden de compra, transferencias y devoluciones — no desde esta API. Todos los endpoints de esta página requieren el permiso inventory:read.

Antes de empezar: paginación distinta al resto de /inventory/*

Trampa de paginación

La mayoría de los listados de /inventory/* (stock, transferencias, ajustes, conteos) pagina 1-indexado (page=1 por defecto). GET /inventory/lots es la excepción dentro del propio dominio de inventario: pagina 0-indexado (page=0 por defecto), igual que /locations/*. Además, su forma de respuesta es plana{ data, total, page, limit, totalPages, hasMore } — distinta del { items, pagination: {...} } que usan stock, transferencias, ajustes y conteos. Si compartes un helper de paginación entre recursos de inventario, esto rompe silenciosamente el conteo de páginas.

Listar lotes

Lista las capas de costo FIFO con filtros. Requiere inventory:read.

pagenumber

Página, base 0. Predeterminado: 0.

limitnumber

Resultados por página. Predeterminado: 50.

skustring

Filtrar por SKU exacto.

locationIdstring

Filtrar por ubicación.

statusstring

active, depleted, expired o cancelled.

sourceTypestring

purchase_order, adjustment, transfer, initial o return.

supplierIdstring

Filtrar por proveedor.

startDatestring

Fecha inicial ISO 8601, filtra por receivedAt.

endDatestring

Fecha final ISO 8601.

200
{
  "data": [
    {
      "id": "lot_01HZK...",
      "tenantId": "69db07c8bce4d49b18c42a49",
      "sku": "CAM-ROJO-M",
      "locationId": "loc_cedis_cdmx",
      "sourceType": "purchase_order",
      "sourceId": "po_4471",
      "supplierId": "sup_221",
      "purchaseOrderNumber": "PO-2026-0441",
      "unitCost": 210.50,
      "currency": "MXN",
      "initialQuantity": 200,
      "currentQuantity": 137,
      "reservedQuantity": 0,
      "receivedAt": "2026-06-02T09:00:00.000Z",
      "status": "active"
    }
  ],
  "total": 34,
  "page": 0,
  "limit": 50,
  "totalPages": 1,
  "hasMore": false
}

Valuación agregada por ubicación

Valuación agregada de lotes activos, opcionalmente filtrada por ubicación. Requiere inventory:read.

locationIdstring

Restringe la agregación a una ubicación.

200
{
  "valuation": [
    { "locationId": "loc_cedis_cdmx", "totalQuantity": 4820, "totalValue": 1014610.00, "lotCount": 63 }
  ]
}

Tip

Esta agregación solo considera lotes con status: 'active'. Es la fuente de valuación correcta del dominio — a diferencia del reporte GET /inventory/reports?reportType=valuation, que hoy está roto (ver Reportes de Inventario).

Consultar un lote

Obtiene un lote por su ID. Requiere inventory:read.

idstringrequired

ID del lote.

200
{
  "id": "lot_01HZK...",
  "sku": "CAM-ROJO-M",
  "locationId": "loc_cedis_cdmx",
  "unitCost": 210.50,
  "currentQuantity": 137,
  "status": "active",
  "receivedAt": "2026-06-02T09:00:00.000Z"
}
404
{ "code": "not-found", "message": "Lot not found" }

Movimientos de un lote

Lista los movimientos históricos de consumo/reversión de un lote. Requiere inventory:read.

idstringrequired

ID del lote.

200
{
  "movements": [
    {
      "id": "mov_01HZ...",
      "lotId": "lot_01HZK...",
      "sku": "CAM-ROJO-M",
      "locationId": "loc_cedis_cdmx",
      "type": "sale",
      "quantity": 2,
      "unitCost": 210.50,
      "referenceType": "order",
      "referenceId": "ord_9931",
      "createdAt": "2026-07-10T18:22:00.000Z"
    }
  ]
}

Costo promedio ponderado por SKU

Costo promedio ponderado sobre lotes activos de un SKU. Requiere inventory:read.

skustringrequired

SKU a consultar (path).

locationIdstring

Restringe el cálculo a una ubicación.

200
{ "averageCost": 208.14, "totalQuantity": 512, "totalValue": 106567.68, "currency": "MXN" }

Información

Si el SKU no tiene lotes activos, la respuesta es 200 con los valores en cero, no un 404.

Valuación detallada por SKU y ubicación

Desglose de valuación lote por lote para un SKU en una ubicación específica. Requiere inventory:read.

skustringrequired

SKU a consultar (path).

locationIdstringrequired

Ubicación a consultar. Obligatorio — la petición falla con 400 si se omite.

200
{
  "sku": "CAM-ROJO-M",
  "locationId": "loc_cedis_cdmx",
  "totalQuantity": 137,
  "totalValue": 28838.50,
  "weightedAverageCost": 210.50,
  "lots": [
    {
      "lotId": "lot_01HZK...",
      "unitCost": 210.50,
      "currentQuantity": 137,
      "value": 28838.50,
      "receivedAt": "2026-06-02T09:00:00.000Z",
      "sourceType": "purchase_order",
      "sourceId": "po_4471"
    }
  ]
}

Lotes por origen

Lista los lotes generados por un origen específico (una orden de compra, un ajuste, etc.). Requiere inventory:read.

typestringrequired

sourceType: purchase_order, adjustment, transfer, initial o return.

idstringrequired

ID del origen (sourceId).

200
{ "lots": [ { "id": "lot_01HZK...", "sku": "CAM-ROJO-M", "sourceType": "purchase_order", "sourceId": "po_4471" } ] }

COGS de una orden

Costo de ventas (COGS) de una orden, derivado de los movimientos de lote de tipo venta. Requiere inventory:read.

orderIdstringrequired

ID de la orden.

200
{
  "totalCOGS": 421.00,
  "items": [
    { "sku": "CAM-ROJO-M", "quantity": 2, "unitCost": 210.50, "totalCost": 421.00 }
  ]
}

Información

Este COGS se calcula a partir de los InventoryLotMovement con referenceType: 'order' y type: 'sale' asociados a la orden — no de una tabla de resumen separada.

Modelo de datos

InventoryLot

tenantId, sku, productSku?, locationId, sourceType (purchase_order|adjustment|transfer|initial|return),
sourceId, supplierId?, supplierName?, purchaseOrderNumber?, unitCost (≥0), currency (default 'MXN'),
initialQuantity (≥0), currentQuantity (≥0), reservedQuantity (default 0, ≥0), receivedAt,
lotNumber?, expirationDate?, notes?, status (active|depleted|expired|cancelled, default active)

InventoryLotMovement

tenantId, lotId, sku, locationId,
type (sale|supply_consumption|transfer_out|transfer_in|adjustment|return|fulfillment_send),
quantity (≥0), unitCost (≥0),
referenceType (order|transfer|adjustment|fulfillment|purchase_order), referenceId,
destinationType? (fba|mercado_full|warehouse|store|other), destinationId?, destinationName?, createdAt

Regla de consumo FIFO

El consumo de lotes sigue estrictamente el orden receivedAt ascendente (el lote más antiguo se agota primero) dentro de {tenantId, sku, locationId}, y solo considera lotes con status: 'active'. Si la cantidad solicitada excede la suma de currentQuantity de los lotes activos disponibles, el consumo se rechaza — este es el guardia real contra sobreventa del subsistema de costeo, independiente del guardia de stock negativo del campo stock base (ver Catálogo de Errores).

No expuesto como endpoint

El consumo FIFO se dispara internamente (venta de una orden, recepción de una transferencia) — no hay un POST público para invocarlo directamente. No lo documentes como una operación que puedas ejecutar tú mismo vía API.

Errores

CódigoHTTPCausa
not-found404No existe un lote con ese ID en tu tenant.
400Falta locationId en GET /inventory/lots/sku/{sku}/valuation (parámetro obligatorio).

Consulta el Catálogo de Errores para los errores comunes a todo el dominio (autenticación, permisos, tenant faltante).

Ejemplos

# Lotes activos de un SKU
curl -G https://api.fenicia.io/inventory/lots \
  -H "Authorization: Bearer fkapi_tu_api_key" \
  --data-urlencode "sku=CAM-ROJO-M" \
  --data-urlencode "status=active"
 
# Valuación detallada por SKU + ubicación
curl -G https://api.fenicia.io/inventory/lots/sku/CAM-ROJO-M/valuation \
  -H "Authorization: Bearer fkapi_tu_api_key" \
  --data-urlencode "locationId=loc_cedis_cdmx"

Siguientes pasos