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
- Visión general
- Autenticación
- Conceptos del dominio
- API — Gestión de catálogo
- API — Almacenes e inventario
- API — Transacciones de stock
- API — Reportes y exportación
- Webhooks — Eventos salientes
- Formatos y convenciones
- 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
| Rol | Acceso |
|---|---|
ROLE_USER | Endpoints backend: catálogo, almacenes, transacciones, webhooks |
ROLE_MOBILE_APP | Endpoints backend y frontend (apps móviles) |
ROLE_FRONT_USER | Endpoints 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:
| Modo | Descripción |
|---|---|
catalog | Los productos se gestionan en un catálogo propio de stock. Soporta CRUD completo e importación masiva. Recomendado para integraciones externas. |
entities | Los 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
| Tipo | Descripción |
|---|---|
input | Entrada de mercancía (recepción, compra) |
output | Salida de mercancía (consumo, venta, baja) |
transfer | Traslado entre almacenes |
adjust | Ajuste 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.
API — Gestión de catálogo
Disponible en modo
catalog. RequiereROLE_USER.
Listar productos
GET /api/stock/products
Parámetros de query:
| Parámetro | Tipo | Descripción |
|---|---|---|
name | string | Filtro por nombre (búsqueda parcial) |
categoryId | string (UUID) | Filtro por categoría |
query | string | Búsqueda por nombre o número de referencia |
ownerId | string (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"
}
| Campo | Requerido | Descripción |
|---|---|---|
name | Sí | Nombre del producto |
unitCostAmount | Sí | Coste unitario en unidad mínima de moneda |
unitCostCurrency | No | Código ISO de moneda (por defecto: moneda del equipo) |
description | No | Descripción |
partNumber | No | Número de referencia / SKU |
qrCodes | No | Array de códigos QR asociados |
categoryId | No | UUID de la categoría |
manufacturerId | No | UUID del fabricante |
id | No | UUID 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"
}
]
}
| Campo | Requerido | Descripción |
|---|---|---|
partNumber | Sí | Clave de upsert — identifica el producto |
name | Sí | Nombre del producto |
unitCost | Sí | Coste unitario en unidad mínima de moneda |
description | No | Descripción |
manufacturerName | No | Nombre del fabricante (se crea si no existe) |
categoryName | No | Nombre de la categoría (se crea si no existe) |
id | No | UUID forzado para el producto |
image | No | URL de imagen |
Respuesta 200:
{
"created": 5,
"updated": 12,
"processed": 17
}
Si
manufacturerNameocategoryNameno 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"
}
| Campo | Requerido | Descripción |
|---|---|---|
type | Sí | input, output, transfer o adjust |
items | Sí | Array de líneas {product_id, quantity} |
origin | Condicional | UUID del nodo origen (requerido en transfer y output) |
destination | Condicional | UUID del nodo destino (requerido en transfer e input) |
order_number | No | Número de pedido / referencia externa |
reason | No | Motivo del movimiento |
comments | No | Texto libre |
mode | No | catalog 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ámetro | Tipo | Descripción |
|---|---|---|
from_date | datetime Y-m-d H:i:s | Fecha inicio |
to_date | datetime Y-m-d H:i:s | Fecha fin |
product_id | UUID | Filtro por producto |
origin_store | UUID | Filtro por almacén de origen |
destination_store | UUID | Filtro por almacén de destino |
origin_node | UUID | Filtro por nodo de origen |
destination_node | UUID | Filtro por nodo de destino |
movement_type | string | input, output, transfer, adjust |
order_number | string | Búsqueda parcial (LIKE) |
reason | string | Búsqueda parcial (LIKE) |
user_id | string | Filtro por usuario creador |
quantity | number | Filtro por cantidad |
page | integer | Número de página (por defecto: 1) |
limit | integer | Resultados por página (por defecto: 25) |
Requisito: al menos uno de
origin_storeodestination_storedebe 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ámetro | Descripción |
|---|---|
currency | Có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"
}
}
| Campo | Requerido | Descripción |
|---|---|---|
url | Sí | Endpoint receptor del webhook |
format | No | json (application/json) o form (application/x-www-form-urlencoded). Por defecto: form |
headers | No | Cabeceras 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_namese enriquece en el momento del envío. Los camposapplied,comments,reasonyorder_numberpueden sernull.
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.
| Evento | Cuándo se dispara |
|---|---|
entity_stock_depleted | La cantidad cae por debajo de min_quantity |
entity_stock_replenished | La cantidad sube hasta o por encima de min_quantity |
El payload incluye:
entity: serialización del ítem de stock afectado.event.name:entity_stock_depletedoentity_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
quantityen transacciones y stock items es un decimal (hasta 3 decimales), representado como número en JSON.minQuantityymaxQuantityson 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ódigo | Significado |
|---|---|
400 | Parámetros inválidos o faltantes |
401 | No autenticado |
403 | Sin acceso al recurso (equipo distinto) |
404 | Entidad no encontrada |
422 | Error 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.