Saltar al contenido principal

Guía de integración con Stock

Esta guía está dirigida a equipos técnicos externos que necesiten integrar sus sistemas con el módulo de stock de TicTAP a través de la API y webhooks.


Tabla de contenidos

  1. Visión general
  2. Autenticación
  3. Conceptos del dominio
  4. API — Gestión de catálogo
  5. API — Almacenes e inventario
  6. API — Transacciones de stock
  7. API — Reportes y exportación
  8. Webhooks — Eventos salientes
  9. Formatos y convenciones
  10. Roadmap — Funcionalidades pendientes

Visión general

El módulo de stock de TicTAP es un WMS (Warehouse Management System) integrado en la Core API. Permite gestionar productos, almacenes jerárquicos, movimientos de inventario y alertas de reposición.

Casos de uso típicos para una integración externa:

  • Sincronización de catálogo de productos desde un ERP o PIM externo.
  • Registro de movimientos de stock (entradas, salidas, transferencias) desde sistemas de expedición o recepción.
  • Consulta en tiempo real del stock disponible por almacén.
  • Recepción de eventos de movimiento vía webhook para actualizar sistemas externos (ERP, BI, OMS).

URL base de la API:

https://<host>/api/stock/

Autenticación

Mecanismo

La API usa autenticación por API Key mediante cabecera HTTP. Todos los endpoints del módulo de stock requieren autenticación; no existen rutas públicas.

X-AUTH-TOKEN: <api_key>

La API key se obtiene desde el panel de administración de TicTAP. Cada key está asociada a un Team concreto y determina el alcance de todos los datos devueltos — no es posible acceder a datos de otro equipo con la misma key.

Roles relevantes

RolAcceso
ROLE_USEREndpoints backend: catálogo, almacenes, transacciones, webhooks
ROLE_MOBILE_APPEndpoints backend y frontend (apps móviles)
ROLE_FRONT_USEREndpoints frontend únicamente

Para integraciones servidor a servidor se utiliza ROLE_USER.

Cabeceras requeridas

X-AUTH-TOKEN: <api_key>
Content-Type: application/json
Accept: application/json

Conceptos del dominio

Modos de operación

El sistema puede operar en dos modos configurables por equipo:

ModoDescripción
catalogLos productos se gestionan en un catálogo propio de stock. Soporta CRUD completo e importación masiva. Recomendado para integraciones externas.
entitiesLos productos están mapeados a entidades dinámicas de TicTAP. Soporta campos personalizados arbitrarios.

Jerarquía de almacenes

Los almacenes (StoreNode) se organizan en árbol. Un nodo puede representar distintos niveles físicos u organizativos:

Empresa
└── Sitio (Site)
└── Grupo de almacenes (Store Group)
└── Almacén (Store)
└── Categoría interna (Category)
└── Ítem de stock (Stock Item)

Tipos de movimiento

TipoDescripción
inputEntrada de mercancía (recepción, compra)
outputSalida de mercancía (consumo, venta, baja)
transferTraslado entre almacenes
adjustAjuste manual de inventario

Moneda

Los importes se almacenan como enteros en la unidad mínima de la moneda (por ejemplo, 9900 = 99,00 EUR). La moneda se especifica como código ISO 4217 (EUR, USD, etc.). La moneda por defecto es la configurada en el equipo.


Disponible en modo catalog. Requiere ROLE_USER.

Listar productos

GET /api/stock/products

Parámetros de query:

ParámetroTipoDescripción
namestringFiltro por nombre (búsqueda parcial)
categoryIdstring (UUID)Filtro por categoría
querystringBúsqueda por nombre o número de referencia
ownerIdstring (UUID)Filtro por empresa propietaria

Respuesta 200:

[
{
"id": "01HXYZ...",
"name": "Cable HDMI 2m",
"description": "Cable HDMI estándar 4K",
"image": { "id": null, "url": null },
"image_url": null,
"qr_codes": ["QR-001"],
"unit_cost": { "amount": "1299", "currency": "EUR" },
"part_number": "CAB-HDMI-2M",
"category_id": "uuid-categoria",
"manufacturer_id": "uuid-fabricante",
"owner_id": "uuid-empresa"
}
]

Obtener un producto

GET /api/stock/products/{id}

En modo entities, la respuesta incluye además un objeto values con los campos dinámicos asociados.


Crear producto

POST /api/stock/products
Content-Type: application/json

Cuerpo:

{
"name": "Cable HDMI 2m",
"unitCostAmount": 1299,
"unitCostCurrency": "EUR",
"description": "Cable HDMI estándar 4K",
"partNumber": "CAB-HDMI-2M",
"qrCodes": ["QR-001"],
"categoryId": "uuid-categoria",
"manufacturerId": "uuid-fabricante"
}
CampoRequeridoDescripción
nameNombre del producto
unitCostAmountCoste unitario en unidad mínima de moneda
unitCostCurrencyNoCódigo ISO de moneda (por defecto: moneda del equipo)
descriptionNoDescripción
partNumberNoNúmero de referencia / SKU
qrCodesNoArray de códigos QR asociados
categoryIdNoUUID de la categoría
manufacturerIdNoUUID del fabricante
idNoUUID propio (se genera automáticamente si se omite)

Respuesta 201: mensaje de confirmación de creación.


Actualizar producto

PATCH /api/stock/products/{id}
Content-Type: application/json

Acepta cualquier subconjunto de los campos de creación.


Eliminar producto

DELETE /api/stock/products/{id}

Solo disponible en modo catalog. Respuesta 204.


Importación masiva de productos

Endpoint optimizado para sincronización desde ERP/PIM. Realiza upsert basado en partNumber.

POST /api/stock/products/import
Content-Type: application/json

Cuerpo:

{
"products": [
{
"partNumber": "CAB-HDMI-2M",
"name": "Cable HDMI 2m",
"unitCost": 1299,
"description": "Cable HDMI estándar 4K",
"manufacturerName": "Marca X",
"categoryName": "Cables"
}
]
}
CampoRequeridoDescripción
partNumberClave de upsert — identifica el producto
nameNombre del producto
unitCostCoste unitario en unidad mínima de moneda
descriptionNoDescripción
manufacturerNameNoNombre del fabricante (se crea si no existe)
categoryNameNoNombre de la categoría (se crea si no existe)
idNoUUID forzado para el producto
imageNoURL de imagen

Respuesta 200:

{
"created": 5,
"updated": 12,
"processed": 17
}

Si manufacturerName o categoryName no existen, se crean automáticamente. Es el punto de entrada preferido para sincronizaciones periódicas desde catálogos externos.


API — Almacenes e inventario

Listar almacenes del equipo

GET /api/stock/stores

Devuelve todos los nodos de almacén del equipo autenticado.


Detalle de un almacén

GET /api/stock/stores/{id}

Devuelve el resumen del nodo: nombre, jerarquía y totales de inventario.


Productos disponibles en un almacén

GET /api/stock/stores/{id}/available-products

Alias: GET /api/stock/stores/{id}/products

Respuesta 200:

[
{
"id": "uuid-stock-item",
"product_id": "uuid-producto",
"name": "Cable HDMI 2m",
"description": "...",
"quantity": 42.5,
"min_quantity": 10,
"max_quantity": 100,
"qr_codes": ["QR-001"],
"image_url": null,
"image": { "id": null, "url": null },
"node_id": "uuid-nodo-almacen",
"unit_cost": { "amount": "1299", "currency": "EUR" },
"part_number": "CAB-HDMI-2M"
}
]

Nodos hijos de un almacén

GET /api/stock/stores/{id}/children?page=1&limit=25

Devuelve los nodos hijos directos con paginación.


Ítems por debajo del mínimo (reposición)

GET /api/stock/stores/{id}/replenish.{format}

Formatos: json, csv, xlsx.

Devuelve todos los ítems de stock del almacén (y descendientes) cuya cantidad actual está por debajo de min_quantity. Útil para generar órdenes de compra automáticas.


Crear ítem de stock en un almacén

POST /api/stock/stores/{id}/items
Content-Type: application/json

Cuerpo:

{
"productId": "uuid-producto",
"quantity": 0,
"minQuantity": 10,
"maxQuantity": 100
}

Respuesta 201: ítem de stock serializado.


Actualizar ítem de stock

PATCH /api/stock/items/{id}
Content-Type: application/json

Campos actualizables: quantity, minQuantity, maxQuantity.


Resumen de múltiples almacenes

GET /api/stock/stores/{id}/summaries?ids=id1,id2,id3

Devuelve el resumen agregado de los almacenes indicados en el parámetro ids (separados por coma).


API — Transacciones de stock

Las transacciones son el mecanismo central para registrar cualquier movimiento de mercancía. Son inmutables una vez creadas.

Crear una transacción

POST /api-front/stock/transactions
Content-Type: application/json

Cuerpo:

{
"type": "input",
"destination": "uuid-nodo-destino",
"items": [
{
"product_id": "uuid-producto",
"quantity": 10
}
],
"order_number": "ORD-2026-001",
"reason": "Compra mensual",
"comments": "Proveedor X, albarán 123"
}
CampoRequeridoDescripción
typeinput, output, transfer o adjust
itemsArray de líneas {product_id, quantity}
originCondicionalUUID del nodo origen (requerido en transfer y output)
destinationCondicionalUUID del nodo destino (requerido en transfer e input)
order_numberNoNúmero de pedido / referencia externa
reasonNoMotivo del movimiento
commentsNoTexto libre
modeNocatalog o entities (por defecto: configuración del equipo)

Respuesta 201: objeto StockTransaction completo (ver formato más abajo).


Consultar transacciones

GET /api/stock/transactions.{format}

Formatos: json (por defecto), csv, xlsx.

Parámetros de query:

ParámetroTipoDescripción
from_datedatetime Y-m-d H:i:sFecha inicio
to_datedatetime Y-m-d H:i:sFecha fin
product_idUUIDFiltro por producto
origin_storeUUIDFiltro por almacén de origen
destination_storeUUIDFiltro por almacén de destino
origin_nodeUUIDFiltro por nodo de origen
destination_nodeUUIDFiltro por nodo de destino
movement_typestringinput, output, transfer, adjust
order_numberstringBúsqueda parcial (LIKE)
reasonstringBúsqueda parcial (LIKE)
user_idstringFiltro por usuario creador
quantitynumberFiltro por cantidad
pageintegerNúmero de página (por defecto: 1)
limitintegerResultados por página (por defecto: 25)

Requisito: al menos uno de origin_store o destination_store debe especificarse.

Respuesta 200:

{
"results": [
{
"id": "01HXYZ...",
"date_time": "2026-04-24T10:30:00+00:00",
"applied": "2026-04-24T10:35:00+00:00",
"comments": "Albarán 123",
"reason": "Compra mensual",
"order_number": "ORD-2026-001",
"user_id": "uuid-usuario",
"movement": {
"type": "input",
"origin": null,
"destination": "Almacén Central"
},
"lines": [
{
"product_id": "uuid-producto",
"quantity": 10.0,
"product_unit_cost": { "amount": "1299", "currency": "EUR" },
"line_cost": { "amount": "12990", "currency": "EUR" }
}
]
}
],
"total": 85,
"count": 25,
"pages": 4,
"page": 1,
"limit": 25
}

Resumen estadístico de transacciones

GET /api/stock/transactions/summary

Devuelve estadísticas agregadas (totales por tipo de movimiento, valor total, etc.).


Estadísticas diarias

GET /api/stock/transactions/daily-stats

Serie temporal de movimientos agrupados por día. Útil para dashboards de BI.


API — Reportes y exportación

Reporte de inventario por almacén

GET /api/stock/stores/{id}/report.{format}

Formatos: json, csv, xlsx.

ParámetroDescripción
currencyCódigo ISO de moneda para conversión (opcional)

Devuelve una vista consolidada del inventario del almacén y sus descendientes, incluyendo cantidades y valoración económica.


Reporte global del equipo

GET /api/stock/team/report.{format}

Formatos: json, csv, xlsx.

Vista consolidada del inventario de todos los almacenes del equipo.


Webhooks — Eventos salientes

El sistema de webhooks permite recibir notificaciones en tiempo real cuando se producen eventos en el stock. Esto elimina la necesidad de polling desde sistemas externos.

Registro de webhook

POST /api/stock/webhooks
Content-Type: application/json

Cuerpo:

{
"url": "https://erp.miempresa.com/hooks/tictap-stock",
"format": "json",
"headers": {
"X-Secret-Token": "mi-token-secreto",
"X-Source": "TicTAP"
}
}
CampoRequeridoDescripción
urlEndpoint receptor del webhook
formatNojson (application/json) o form (application/x-www-form-urlencoded). Por defecto: form
headersNoCabeceras HTTP personalizadas incluidas en cada llamada

Respuesta 201: webhook registrado.

Los webhooks creados de esta forma se vinculan a las definiciones de almacén e ítem de stock configuradas para el equipo asociado al token de autenticación. La API los suscribe automáticamente a eventos de transacción de stock y a eventos de umbral de los ítems de stock.

El webhook queda asociado al equipo del token de autenticación y se activa para los almacenes y los ítems de stock de ese equipo.


Eventos soportados por el endpoint de registro

stock_transaction_created
entity_stock_depleted
entity_stock_replenished

stock_transaction_created se emite para movimientos de stock que afectan a los almacenes del equipo. entity_stock_depleted y entity_stock_replenished se emiten para ítems de stock cuando su cantidad cruza el umbral mínimo configurado.


Evento: stock_transaction_created

Se dispara cada vez que se crea una nueva transacción de stock.

Payload (formato JSON):

{
"id": "01HXYZ...",
"date_time": "2026-04-24T10:30:00+00:00",
"applied": "2026-04-24T10:35:00+00:00",
"comments": "Albarán 123",
"reason": "Compra mensual",
"order_number": "ORD-2026-001",
"user_id": "uuid-usuario",
"movement": {
"type": "input",
"origin": null,
"destination": "Almacén Central"
},
"lines": [
{
"product_id": "uuid-producto",
"product_name": "Cable HDMI 2m",
"quantity": 10.0,
"product_unit_cost": { "amount": "1299", "currency": "EUR" },
"line_cost": { "amount": "12990", "currency": "EUR" }
}
]
}

product_name se enriquece en el momento del envío. Los campos applied, comments, reason y order_number pueden ser null.

Comportamiento de entrega:

  • El webhook se envía a la URL configurada para el almacén implicado en la transacción (origen y/o destino).
  • Si una transacción involucra dos almacenes con el mismo webhook configurado, el payload se envía una sola vez (deduplicación por webhook ID).
  • Las cabeceras personalizadas definidas en el registro se incluyen en cada request.

Eventos: entity_stock_depleted y entity_stock_replenished

Estos eventos se entregan como webhooks de entidad para la definición de ítem de stock configurada en los ajustes de stock del equipo.

EventoCuándo se dispara
entity_stock_depletedLa cantidad cae por debajo de min_quantity
entity_stock_replenishedLa cantidad sube hasta o por encima de min_quantity

El payload incluye:

  • entity: serialización del ítem de stock afectado.
  • event.name: entity_stock_depleted o entity_stock_replenished.
  • event.current_snapshot: snapshot de la entidad tras el cambio.
  • event.data: datos serializados del evento, incluyendo el id de entidad, id de definición, stock mínimo, stock actual y stock máximo.
  • datetime: fecha de generación del webhook.

En entity_stock_depleted, event.data.replenishment_quantity indica cuántas unidades hay que reponer para alcanzar el stock máximo efectivo. Si el stock máximo no está configurado o es 0, se usa el stock mínimo como máximo efectivo para ese cálculo.


Formatos y convenciones

Identificadores

  • La mayoría de entidades usan UUID v4 o ULID como identificadores.
  • Los identificadores se representan siempre como strings en la API.

Fechas

  • Formato ISO 8601 con offset de zona horaria: 2026-04-24T10:30:00+00:00.
  • Las fechas de filtro en consultas usan el formato Y-m-d H:i:s.

Cantidades

  • quantity en transacciones y stock items es un decimal (hasta 3 decimales), representado como número en JSON.
  • minQuantity y maxQuantity son enteros.

Moneda

  • amount: entero en unidad mínima de la moneda (1299 = 12,99 EUR).
  • currency: código ISO 4217 (EUR, USD, GBP, etc.).

Paginación

Las listas paginadas devuelven siempre:

{
"results": [],
"total": 100,
"count": 25,
"pages": 4,
"page": 1,
"limit": 25
}

Errores

La API devuelve errores estándar HTTP:

CódigoSignificado
400Parámetros inválidos o faltantes
401No autenticado
403Sin acceso al recurso (equipo distinto)
404Entidad no encontrada
422Error de validación de dominio

Roadmap — Funcionalidades pendientes

Esta sección documenta extensiones naturales del sistema actual que aún no están implementadas pero que aportarían valor significativo para integraciones bidireccionales.


Gestión de webhooks (listar, actualizar, eliminar)

Estado actual: Solo existe POST /api/stock/webhooks. No hay endpoints para listar, actualizar ni eliminar webhooks.

Qué falta:

  • GET /api/stock/webhooks — listar webhooks del equipo con su configuración.
  • DELETE /api/stock/webhooks/{id} — eliminar un webhook.
  • PATCH /api/stock/webhooks/{id} — actualizar URL, formato o cabeceras.

Sistema de reintentos de webhook

Estado actual: Los webhooks se despachan por command bus sin mecanismo de reintento ni confirmación de entrega.

Qué falta: Lógica de retry con backoff exponencial (ej. 3 intentos: 1min, 5min, 30min) y registro de intentos fallidos accesible vía API (GET /api/stock/webhooks/{id}/deliveries).


Handshake de verificación de webhook

Estado actual: No se valida que la URL registrada sea alcanzable ni que pertenezca al registrante.

Qué falta: Al crear o actualizar un webhook, enviar un request de verificación con un token challenge que el receptor debe devolver. Patrón estándar usado por Stripe, GitHub y Shopify.


Firma de payload de webhook (HMAC)

Estado actual: No existe mecanismo de firma de los payloads enviados.

Qué falta: Generar un header X-TicTAP-Signature en cada request de webhook usando HMAC-SHA256 sobre el cuerpo con un secreto por webhook, permitiendo al receptor verificar la autenticidad.


API key de solo lectura / con alcance limitado

Estado actual: No existe un tipo de credencial de solo lectura o con alcance restringido.

Qué falta: Un tipo de API key con rol restringido (ROLE_INTEGRATION) con acceso de solo lectura al stock, sin capacidad de crear transacciones ni modificar el catálogo.


Exportación incremental de transacciones

Estado actual: La consulta de transacciones requiere especificar un almacén (origin_store o destination_store). No existe un endpoint tipo cursor/delta orientado a sincronización.

Qué falta: Un endpoint basado en cursor:

GET /api/stock/transactions/changes?since=<timestamp>&cursor=<token>

que devuelva transacciones creadas tras el último sincronismo, sin necesidad de especificar almacenes individuales.


Búsqueda de ítem de stock por código QR en la API backend

Estado actual: GET /api-front/stock/stock-items/by-qr/{qr_code} existe en la API frontend pero no en la API backend (/api/stock/).

Qué falta: Exponer el mismo endpoint en el área backend para que sistemas externos (lectores de pistola, kioscos) puedan consultar stock por código QR sin necesitar credenciales frontend.