Catálogo de productos
Modela productos y servicios con unidades UNECE, presentaciones, variantes, ofertas de proveedor y snapshots documentales inmutables.
El catálogo modela qué vendes y, por separado, cómo se presenta, identifica,
almacena y compra. Empieza con una clave fact_test_ y concede solo
products:read y/o products:write según necesites.
Producto o servicio
Cada ítem declara un item_kind explícito:
productpuede gestionar stock decimal y tener variantes con stock propio.servicesiempre tienemanage_stock: false, stock0.0000y umbral de stock bajo nulo. Un servicio no admite variantes con stock propio.
La unidad base pertenece al catálogo UNECE cerrado: C62, KGM, GRM, LTR,
MLT, MTR, MTK, HUR o DAY. Cantidades y stock admiten cuatro decimales;
los factores de conversión, seis. La unidad base no se puede cambiar cuando
reinterpretaría stock, presentaciones, variantes, ofertas o histórico existente.
{
"name": "Aceite de oliva virgen extra",
"sku": "AOVE-GRANEL",
"price": "8.75",
"item_kind": "product",
"base_unit": "LTR",
"manage_stock": true,
"stock": "125.5000",
"low_stock_threshold": "20.0000",
"specifications": { "origen": "Jaén", "cosecha": 2026 }
}Semántica de creación y actualización
POST /v1/products exige name y price. Si external_id ya identifica un
producto de la misma empresa, la creación actúa como upsert idempotente en vez
de duplicarlo. sku también es único por empresa, pero una colisión con otro
producto es un error, no un upsert.
PUT /v1/products/{product} tiene semántica parcial: cada campo omitido
conserva su valor actual. Dos campos aplican sustitución explícita:
specificationssustituye el objeto clave-valor completo;nulllo vacía. Las listas JSON no vacías se rechazan, mientras que{}y[]siguen siendo representaciones vacías compatibles.stockes una cantidad absoluta, no un delta.nulllo pone a cero. Una diferencia real registra un movimiento manual; enviar el valor actual no registra nada. Usa el endpoint dedicado de stock conincreaseodecreasepara deltas explícitos.
stock y low_stock_threshold admiten hasta cuatro decimales. metadata de
Producto conserva su frontera retrocompatible: hasta 50 claves, los valores
escalares se convierten a strings y las listas JSON se aceptan y persisten con
claves numéricas. Esto es deliberadamente distinto de specifications.
Presentaciones
Una presentación es una forma comercial de vender el producto; nunca tiene stock propio.
| Modo | Significado |
|---|---|
fixed | cantidad base = cantidad comercial × conversion_factor. Una caja de 12 usa 12.000000. |
variable_measure | La medida nominal es informativa. Cada línea debe confirmar su cantidad base real. |
POST /v1/products/{product}/presentations{
"name": "Caja de 12",
"mode": "fixed",
"unit": "C62",
"conversion_factor": "12.000000",
"active": true
}Usa GET/POST sobre la colección y PUT/DELETE sobre cada ítem. Una
presentación inactiva no se puede seleccionar en líneas nuevas, pero los
documentos antiguos siguen renderizando desde su snapshot.
Variantes
Una variante conserva identidad comercial propia: nombre, SKU/código de barras opcionales, overrides de precio y coste y, opcionalmente, stock propio.
{
"name": "Azul / M",
"sku": "CAM-AZU-M",
"price_override": "24.5000",
"manage_stock": true,
"stock": "8.5000",
"active": true
}Con manage_stock: true, los movimientos afectan solo a la variante; en caso
contrario delegan en el producto base sin perder la identidad de variante en la
línea ni en el ledger. Una presentación puede combinarse con una variante: la
variante elige el destino de stock y la presentación convierte la cantidad.
Opciones configurables y combinaciones vendibles
Los grupos de opciones modelan elecciones del comprador —acabado, sabor o
nivel de servicio—, no specifications informativas. Cada grupo contiene
valores de selección única; un valor puede añadir un price_adjustment por
unidad comercial. Los grupos y valores inactivos siguen siendo legibles para
conservar snapshots históricos, pero no se pueden elegir en una línea nueva.
Las configuraciones comerciales combinan una variante opcional, una
presentación opcional y un conjunto canónico de valores de opción. Cada
configuración tiene una signature estable, puede declarar un precio final por
unidad comercial y puede estar activa o inactiva.
catalog_availability_mode controla qué se puede vender:
| Modo | Disponibilidad |
|---|---|
open | El producto cartesiano de los ejes activos es vendible. Una configuración solo restringe la disponibilidad con restricts_availability: true: limita su variante nombrada y trata presentación/opciones omitidas como comodines. |
closed | Las configuraciones activas son la lista exacta permitida. Sin ninguna configuración activa, no se puede vender nada. |
La creación usa open por defecto; omitir el campo al actualizar conserva el
modo actual. Una fila con restricts_availability: false todavía puede aportar
identidad o un precio exacto sin estrechar un catálogo abierto.
Usa GET /v1/products/{product}/options y
GET /v1/products/{product}/configurations para inspeccionar el catálogo. Ambos
usan products:read y paginación por cursor. El detalle del producto puede
incluirlos con include=configurable_catalog.
POST /v1/products/{product}/resolve-selection también es una lectura pese a
ser POST: acepta una selección parcial anidada y devuelve resolved,
incomplete, ambiguous o incompatible, además de los ejes compatibles y
grupos pendientes. Dos valores del mismo grupo forman una petición inválida.
Antes de una escritura que reduzca la disponibilidad, llama a
POST /v1/products/{product}/configurations/impact-preview con
products:write. No persiste nada y devuelve las combinaciones afectadas, los
precios negociados que se retirarían y un impact_token que identifica ese
estado exacto del catálogo. Devuelve el token en la actualización destructiva;
si el catálogo o los precios afectados cambian, solicita otra previsualización.
Ledger de stock
GET /v1/products/{product}/stock-movements expone el libro mayor inmutable de
stock de más reciente a más antiguo. Cada fila lleva el delta firmado en
unidades base, el saldo stock_after calculado sobre el libro completo, el
motivo, el documento origen, el usuario y la variante gestora. La paginación
usa limit y starting_after; direction=in|out filtra las filas visibles sin
cambiar los valores históricos de stock_after.
Ofertas de proveedor
Una oferta representa condiciones de compra sin cambiar el precio de venta. Apunta al producto o a una variante y registra proveedor, unidad de compra, factor, disponibilidad, coste y plazo.
{
"supplier_id": "01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a10",
"variant_id": null,
"supplier_sku": "PROV-AOVE-20L",
"purchase_unit": "LTR",
"conversion_factor": "1.000000",
"unit_cost": "5.1250",
"availability": "available",
"minimum_quantity": "20.0000",
"lead_time_days": 3,
"preferred": true,
"active": true
}available exige unit_cost no negativo. unavailable, unknown, seasonal
y store_dependent exigen unit_cost: null. Solo puede haber una oferta activa
preferida por destino; la acción preferred la cambia atómicamente.
Cuando una línea de factura de compra compra contra una oferta,
purchase_measurement registra los bultos recibidos y la unidad en la que se
expresa el coste (offer_unit, base_unit o presentation_unit); el
servidor deriva la cantidad facturada y congela el coste unitario. unit_price
omitido en una línea de catálogo significa «coste no confirmado»: la API aplica
el coste de la oferta o rechaza la línea si el proveedor no tiene oferta activa.
Consulta Líneas de compra medidas.
Los documentos guardan snapshots
Las líneas de catálogo devuelven product_id, variant_id, presentation_id,
configuración/firma comercial y valores de opción, item_kind, unidad
comercial y base, factor, cantidad base, origen y semántica del precio, ajustes
de opción, identidad de tarifa y unidad de precio. Son snapshots: cambiar o
retirar el catálogo vivo no reescribe documentos emitidos ni históricos.
Para una presentación variable_measure, envía confirmed_base_quantity al
crear la línea. Deja que la API derive totales y cantidades de movimiento.
Permisos, borrado y MCP
- Las lecturas usan
products:read; altas, ediciones y borrados de los nuevos subrecursos usanproducts:write. El producto base se borra conproducts:delete. - Los borrados de presentación, variante y oferta llevan
x-irreversible: trueen OpenAPI. - Un producto referenciado por un ítem de tarifa u oferta vigente no se puede borrar físicamente. Retira antes la referencia; el histórico queda en snapshots.
- El dominio Product de MCP tiene ahora 38 tools. Además de las 13 tools de presentaciones, variantes y ofertas, cuatro lecturas nuevas exponen opciones, configuraciones, movimientos de stock y resolución de selecciones.
Consulta Tarifas, Scopes e irreversibilidad y los esquemas exactos en la Referencia de la API.
Descripciones y consultas de documentos
Las líneas admiten additional_description nullable de hasta 5000 caracteres junto a description. Está disponible en facturas de venta y rectificativas, presupuestos, proformas, albaranes, facturas de compra y plantillas recurrentes. Mantén ese detalle separado de cantidades, precios, campos fiscales e identificadores del catálogo; añadir descripción no cambia el cálculo del importe. Consulta GET /v1/products/low-stock-report para la colección y total_count, y GET /v1/products/{product}/sales-analytics para las métricas de ventas; estas respuestas no son recursos de producto individuales. Sus ejemplos completos están en la referencia de la API.