Chainlink Developers

Conteos de inventario

Ciclo de conteos físicos (stock reviews): secciones, escaneos y cierre.

URL base

Todas las rutas son relativas a https://tu-empresa.chainlink.mx (el subdominio de tu tenant) y requieren un token Bearer. Consulta Autenticación y Convenciones.
GET /api/v1/stock-reviews

Listar conteos

Lista los conteos de forma paginada, ordenados por fecha del más reciente al más antiguo, con filtros por estado y por 'asignados a mí'.

Parámetros de consulta

assigned_to string Cuando se establece en 'me', limita los resultados a los conteos cuyo assigned_to_id coincide con el id del usuario autenticado. Cualquier otro valor se ignora.
status string Filtra por el estado del conteo. Valores conocidos: 'pending', 'STARTED', 'FINALIZED' (se comparan de forma literal y sensible a mayúsculas).
per_page integer Tamaño de página. Los valores se acotan entre 1 y 100; el valor por defecto es 25. El parámetro estándar 'page' de Laravel selecciona la página.
Las lecturas están abiertas a cualquier token autenticado. Paginado mediante una resource collection de Laravel (data/links/meta). Precarga las relaciones warehouse y assignedTo, e incluye sections_count y scans_count.
cURL
curl -X GET "https://tu-empresa.chainlink.mx/api/v1/stock-reviews" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Accept: application/json"
Respuesta (ejemplo)
{
  "data": [
    {
      "id": 7,
      "warehouse_id": 1,
      "warehouse": {
        "id": 1,
        "whs_code": "DEMO",
        "name": "Almacén Demo Principal"
      },
      "created_by_id": 1,
      "assigned_to_id": 1,
      "assigned_to": {
        "id": 1,
        "name": "Administrador Demo"
      },
      "status": "STARTED",
      "date": "2026-07-07",
      "started_at": "2026-07-08T05:08:03.000000Z",
      "finalized_at": null,
      "sections_count": 0,
      "scans_count": 0,
      "created_at": "2026-07-08T05:06:58.000000Z",
      "updated_at": "2026-07-08T05:08:03.000000Z"
    }
  ],
  "links": {
    "first": "https://demo.chainlink.mx/api/v1/stock-reviews?page=1",
    "last": "https://demo.chainlink.mx/api/v1/stock-reviews?page=1",
    "prev": null,
    "next": null
  },
  "meta": {
    "current_page": 1,
    "per_page": 25,
    "from": 1,
    "to": 7,
    "last_page": 1,
    "total": 7
  }
}
GET /api/v1/stock-reviews/{id}

Obtener conteo

Devuelve un conteo específico con sus secciones (cada una con su responsable y su número de escaneos) y los totales agregados.

Parámetros de ruta

id requerido integer Id del conteo. Se resuelve como el modelo StockReview.
Las lecturas están abiertas a cualquier token autenticado. Carga warehouse, assignedTo, sections (ordenadas por label, con claimedBy y scans_count), además de sections_count y scans_count.
cURL
curl -X GET "https://tu-empresa.chainlink.mx/api/v1/stock-reviews/{id}" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Accept: application/json"
Respuesta (ejemplo)
{
  "data": {
    "id": 7,
    "warehouse_id": 1,
    "warehouse": {
      "id": 1,
      "whs_code": "DEMO",
      "name": "Almacén Demo Principal"
    },
    "created_by_id": 1,
    "assigned_to_id": 1,
    "assigned_to": {
      "id": 1,
      "name": "Administrador Demo"
    },
    "status": "STARTED",
    "date": "2026-07-07",
    "started_at": "2026-07-08T05:08:03.000000Z",
    "finalized_at": null,
    "sections_count": 0,
    "scans_count": 0,
    "sections": [],
    "created_at": "2026-07-08T05:06:58.000000Z",
    "updated_at": "2026-07-08T05:08:03.000000Z"
  }
}
POST /api/v1/stock-reviews/{id}/start

Crear conteo

Cambia un conteo al estado STARTED y registra started_at; es idempotente si ya estaba iniciado.

Parámetros de ruta

id requerido integer Id del conteo.
StartStockReviewRequest no tiene reglas de validación y authorize()=true (cualquier usuario autenticado). Es idempotente: devuelve el conteo sin cambios si ya está en STARTED. Si el conteo está en FINALIZED, canStart() es false y el servicio lanza InvalidStateTransitionException (se renderiza como error). Dispara el webhook 'stock_review.started'. Devuelve el conteo con warehouse y assignedTo cargados (sin secciones ni totales).
cURL
curl -X POST "https://tu-empresa.chainlink.mx/api/v1/stock-reviews/{id}/start" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Accept: application/json"
Respuesta (ejemplo)
{
  "data": {
    "data.status": "string",
    "data.started_at": "string"
  }
}
GET /api/v1/stock-reviews/{id}/sections

Obtener conteo

Lista todas las secciones de un conteo, ordenadas por label, cada una con su responsable y su número de escaneos.

Parámetros de ruta

id requerido integer Id del conteo.
Colección sin paginar (devuelve todas las secciones). Ordenadas por label; cada una incluye la relación claimedBy y scans_count.
cURL
curl -X GET "https://tu-empresa.chainlink.mx/api/v1/stock-reviews/{id}/sections" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Accept: application/json"
Respuesta (ejemplo)
{
  "data": []
}
POST /api/v1/stock-reviews/{id}/sections/{section}/claim

Crear conteo

Reclama una sección de forma atómica para el operador autenticado, de modo que dos operadores no cuenten la misma sección.

Parámetros de ruta

id requerido integer Id del conteo.
section requerido integer Id de la sección a reclamar.
ClaimSectionRequest no tiene reglas, authorize()=true. UPDATE condicional atómico (reclama solo si claimed_by_user_id IS NULL). Es idempotente: si el mismo usuario ya la había reclamado, devuelve la sección. Si otro usuario ya la reclamó, lanza SectionAlreadyClaimedException -> HTTP 409. Si la sección no pertenece al conteo, HTTP 404 {error:'section_not_in_review'}. Dispara el webhook 'stock_review.section_claimed' al reclamarla por primera vez.
cURL
curl -X POST "https://tu-empresa.chainlink.mx/api/v1/stock-reviews/{id}/sections/{section}/claim" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Accept: application/json"
Respuesta (ejemplo)
{
  "data": {
    "data.status": "string",
    "data.claimed_by_user_id": "integer",
    "data.claimed_at": "string",
    "data.claimed_by": "object"
  }
}
POST /api/v1/stock-reviews/{id}/sections/{section}/release

Crear conteo

Libera una sección reclamada y la regresa a pendiente (deja de reclamarla).

Parámetros de ruta

id requerido integer Id del conteo.
section requerido integer Id de la sección a liberar.
La autorización la impone ReleaseSectionRequest::authorize() -> section.canBeReleasedBy(user), es decir, el usuario debe ser el responsable actual O tener permiso para administrar el conteo (creador o asignado). En caso contrario falla con HTTP 403. Si la sección no pertenece al conteo, HTTP 404 {error:'section_not_in_review'}. No lleva cuerpo de solicitud.
cURL
curl -X POST "https://tu-empresa.chainlink.mx/api/v1/stock-reviews/{id}/sections/{section}/release" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Accept: application/json"
Respuesta (ejemplo)
{
  "data": {
    "data.status": "string",
    "data.claimed_by_user_id": "null",
    "data.claimed_at": "null"
  }
}
POST /api/v1/stock-reviews/{id}/scans

Crear conteo

Envía un lote de escaneos de conteo (código de barras/RFID/NFC/manual); idempotente por client_uuid.

Parámetros de ruta

id requerido integer Id del conteo. Debe aceptar escaneos (no estar en FINALIZED) o el servicio lanza ReviewFinalizedException.

Cuerpo (JSON)

scans requerido array Arreglo de 1 a 200 objetos de escaneo a persistir en una sola transacción.
scans[].client_uuid requerido string UUID generado por el cliente para idempotencia; reenviar el mismo UUID devuelve el escaneo existente en lugar de duplicarlo.
scans[].scanned_code requerido string El código crudo escaneado; se resuelve a un producto mediante la búsqueda por item_code/barcode/EPC RFID.
scans[].qty number Cantidad contada; el valor por defecto es 1 cuando se omite.
scans[].source string Canal de origen del escaneo; el valor por defecto es 'barcode'.
scans[].section_id integer Sección a la que pertenece el escaneo.
scans[].serial_number string Número de serie capturado con el escaneo.
scans[].batch_num string Número de lote capturado con el escaneo.
scans[].warehouse_position_id integer Posición del almacén donde se contó el artículo.
scans[].device_id string Identificador del dispositivo/terminal que capturó el escaneo.
scans[].scanned_at requerido string Marca de tiempo en que se capturó el escaneo en el dispositivo (se interpreta con Carbon).
scans[].extra object Metadatos adicionales de formato libre almacenados como JSON.
BatchScanRequest authorize()=true. Se persiste en una sola transacción de base de datos; es idempotente por client_uuid (los registros existentes se devuelven, no se vuelven a crear). scanned_code se resuelve a un producto mediante Product::findByCode (item_code, barcode y luego EPC RFID). Si el conteo está en FINALIZED, se lanza ReviewFinalizedException (se rechaza). Dispara el webhook 'stock_review.scan_batch_received'. Máximo 200 escaneos por solicitud.
Cuerpo del request
{
  "scans": [],
  "scans[].client_uuid": "<scans[].client_uuid>",
  "scans[].scanned_code": "<scans[].scanned_code>",
  "scans[].scanned_at": "<scans[].scanned_at>"
}
cURL
curl -X POST "https://tu-empresa.chainlink.mx/api/v1/stock-reviews/{id}/scans" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
  "scans": [],
  "scans[].client_uuid": "<scans[].client_uuid>",
  "scans[].scanned_code": "<scans[].scanned_code>",
  "scans[].scanned_at": "<scans[].scanned_at>"
}'
Respuesta (ejemplo)
{
  "data": [
    {
      "data[].id": "integer",
      "data[].client_uuid": "string",
      "data[].stock_review_id": "integer",
      "data[].section_id": "integer",
      "data[].user_id": "integer",
      "data[].device_id": "string",
      "data[].scanned_code": "string",
      "data[].source": "string",
      "data[].product_id": "integer",
      "data[].product": "object",
      "data[].qty": "number",
      "data[].serial_number": "string",
      "data[].batch_num": "string",
      "data[].warehouse_position_id": "integer",
      "data[].extra": "object",
      "data[].scanned_at": "string",
      "data[].created_at": "string"
    }
  ]
}
DELETE /api/v1/stock-reviews/{id}/scans/{clientUuid}

Eliminar conteo

Deshace (elimina) un escaneo previamente enviado por su client_uuid; los operadores solo pueden eliminar sus propios escaneos mientras el conteo siga abierto.

Parámetros de ruta

id requerido integer Id del conteo.
clientUuid requerido string El client_uuid del escaneo a eliminar.
Sin FormRequest (usa el Request base). Validaciones, en orden: HTTP 409 {error:'review_finalized'} si el conteo ya no acepta escaneos (FINALIZED); HTTP 404 {error:'scan_not_found'} si no existe ningún escaneo con ese client_uuid en el conteo; HTTP 403 {error:'not_scan_owner'} si el user_id del escaneo no coincide con el usuario autenticado. Solo el dueño del escaneo puede eliminarlo, y únicamente mientras el conteo esté abierto.
cURL
curl -X DELETE "https://tu-empresa.chainlink.mx/api/v1/stock-reviews/{id}/scans/{clientUuid}" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Accept: application/json"
Respuesta (ejemplo)
{
  "data": {
    "ok": "boolean",
    "error": "string"
  }
}
GET /api/v1/stock-reviews/{id}/progress

Obtener conteo

Progreso agregado en tiempo real para la pantalla de conteo multidispositivo: desglose por sección, porcentaje de avance, totales de escaneos y conteos por operador.

Parámetros de ruta

id requerido integer Id del conteo.
La agregación la calcula StockReviewService::progress. operators se agrupa por user_id (COUNT y MAX(scanned_at)) con el nombre del usuario precargado. Pensado para consultarse por polling en la pantalla de conteo en vivo.
cURL
curl -X GET "https://tu-empresa.chainlink.mx/api/v1/stock-reviews/{id}/progress" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Accept: application/json"
Respuesta (ejemplo)
{
  "data": {
    "stock_review_id": 7,
    "status": "STARTED",
    "sections_total": 0,
    "sections_done": 0,
    "sections_in_progress": 0,
    "completion_pct": 0,
    "total_scans": 0,
    "unique_codes": 0,
    "sections": [],
    "operators": [],
    "last_scan_at": null
  }
}
POST /api/v1/stock-reviews/{id}/finalize

Crear conteo

Finaliza un conteo iniciado (lo bloquea contra nuevos escaneos) y registra finalized_at; restringido al creador o al asignado del conteo.

Parámetros de ruta

id requerido integer Id del conteo.
La autorización la impone FinalizeStockReviewRequest::authorize() -> review.isManageableBy(user), es decir, el usuario autenticado debe ser el creador (created_by_id) o el asignado (assigned_to_id) del conteo; de lo contrario HTTP 403. No lleva cuerpo de solicitud. Es idempotente: devuelve el conteo sin cambios si ya está en FINALIZED. Si el conteo aún no está en STARTED, canFinalize() es false y el servicio lanza InvalidStateTransitionException. Dispara el webhook 'stock_review.finalized'. Devuelve el conteo con warehouse y assignedTo cargados.
cURL
curl -X POST "https://tu-empresa.chainlink.mx/api/v1/stock-reviews/{id}/finalize" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Accept: application/json"
Respuesta (ejemplo)
{
  "data": {
    "data.status": "string",
    "data.finalized_at": "string"
  }
}

¿Necesitas habilitar un endpoint o tienes dudas? Contáctanos.

← Volver al inicio de Developers