Productos
Cada item de /catalog/products es un SKU: la unidad que se stockea y se costea de forma independiente.
- Un producto sin variaciones es un solo SKU.
- Un producto con variaciones (talla, color…) es un conjunto de SKUs que comparten un Producto (modelo); cada SKU apunta a él con
product_id. El modelo se gestiona en Grupos.
Campos
| Campo | Tipo | Req. | Notas |
|---|---|---|---|
id | number | Identificador del SKU (solo en la respuesta) | |
sku | string | req | Código único por tenant |
name | string | req | |
product_type | enum | req | raw_material · finished_good · wip · consumable · service |
base_unit_id | number | req | Unidad de stock |
category_id | number | ||
brand_id | number | ||
product_id | number | Producto (modelo) padre, si es una variante. Vacío = producto simple | |
description | string | ||
standard_cost | number | Costo de referencia | |
barcode | string | ||
image_url | string | ||
track_lots | boolean | Manejo por lotes (default false) | |
min_stock | number | Mínimo para alertas de reposición | |
purchase_unit_id | number | Unidad de compra | |
purchase_to_base_factor | number | Requerido si hay purchase_unit_id | |
is_active | boolean | Default true |
Los id son enteros, únicos por tenant. Los campos que gestiona el sistema (id, fechas) no se envían en el body: van solo en la respuesta.
product_type no cambia una vez que el SKU tiene movimientos de stock.
Endpoints
| Método | Ruta | Descripción |
|---|---|---|
| GET | /catalog/products | Lista de SKUs (con filtros) |
| GET | /catalog/products/{id} | Un SKU |
| POST | /catalog/products | Crea un SKU |
| PATCH | /catalog/products/{id} | Edita campos (no sku ni product_type) |
| DELETE | /catalog/products/{id} | Desactiva (is_active=false, soft delete) |
Filtros de GET /catalog/products (query): category_id, brand_id, product_id (las variantes de un Producto), product_type, is_active, search (sku/nombre), limit (≤1000), offset.
Crear un SKU
Request
{
"sku": "ACE-OLI-500",
"name": "Aceite de Oliva Extra Virgen 500ml",
"product_type": "finished_good",
"base_unit_id": 3,
"category_id": 18,
"brand_id": 7,
"standard_cost": 5000,
"barcode": "7701234567890"
} Response 201
{
"id": 142,
"sku": "ACE-OLI-500",
"name": "Aceite de Oliva Extra Virgen 500ml",
"product_type": "finished_good",
"base_unit_id": 3,
"category_id": 18,
"brand_id": 7,
"product_id": null,
"description": null,
"standard_cost": 5000,
"barcode": "7701234567890",
"image_url": null,
"track_lots": false,
"min_stock": null,
"purchase_unit_id": null,
"purchase_to_base_factor": null,
"is_active": true
} Para crear una variante, envía product_id con el Producto (modelo) al que pertenece:
{ "sku": "CAM-AZ-S", "name": "Camiseta Azul S", "product_type": "finished_good", "base_unit_id": 1, "product_id": 30 } Listar las variantes de un Producto
Devuelve todos los SKUs cuyo product_id es 30.
Editar
Request
{ "standard_cost": 5200, "min_stock": 12 } Actualiza cualquier campo editable. sku y product_type son inmutables.
Errores
Formato: { "error": "<código>", "message": "<texto>" }
| HTTP | error | Cuándo |
|---|---|---|
| 400 | bad_request | Body inválido o un id referenciado no existe |
| 401 | unauthorized | Cabeceras Emdimo-Api-Key/-Token ausentes o inválidas |
| 403 | forbidden | La key no tiene el scope requerido |
| 404 | not_found | SKU de otro tenant o inexistente |
| 409 | conflict | SKU duplicado |
{ "error": "conflict", "message": "El SKU 'ACE-OLI-500' ya existe." }