Líneas de compra medidas
Las líneas de factura de compra registran ya los bultos recibidos, la unidad a la que se aplica el coste del proveedor y la medida real. El contrato añade un bloque de medida, persiste el descuento de línea, resuelve el coste desde la oferta del proveedor y duplica una compra desde un origen autorizado.
Las facturas de compra separan ya la parte física de una compra de la parte
facturada. Una línea puede indicar cuántos bultos llegaron, en qué unidad se
expresa el coste del proveedor y la medida real; el servidor deriva la cantidad
facturada, el factor de conversión a la unidad base y el coste unitario
congelado. Una línea sin el bloque nuevo conserva su semántica anterior de
quantity × unit_price.
Nuevos campos de la petición
POST /v1/purchase_invoices y PUT /v1/purchase_invoices/{purchase_invoice}
aceptan estos campos aditivos. Omitirlos todos sigue siendo una petición válida.
| Campo | Significado |
|---|---|
source_purchase_invoice_id | UUID de una factura de compra de tu empresa cuyas líneas históricas quieres duplicar. Solo al crear. La key necesita purchase_invoices:read además de purchase_invoices:write; si no, 403 insufficient_scope. Un origen desconocido o de otra empresa devuelve 404 purchase_invoice_not_found. |
lines[].purchase_measurement | Objeto cerrado con package_quantity (string decimal o null) y cost_basis (offer_unit, base_unit o presentation_unit). Omitido o null conserva el contrato legacy. Los campos derivados de lectura (billing_*, unit_cost) se rechazan en escritura, aunque procedan de una lectura anterior. |
lines[].discount_percent | Descuento porcentual, 0–100 con hasta cuatro decimales, aplicado antes de impuestos. La API lo aceptaba y lo descartaba en silencio; ahora se persiste y se devuelve. |
lines[].source_line_index | Ordinal base cero de la línea en el documento origen autorizado. Mantiene la línea duplicada emparejada con sus medidas y su coste congelado históricos. Nunca una clave interna. |
lines[].price_source | manual o supplier_offer. Nuevo en la API pública; cambia cómo se lee unit_price (ver más abajo). |
metadata.purchase_source_review | Clave reservada: string JSON compacto de 500 caracteres como máximo con el estado de la conciliación OCR (pending, reconciled o accepted_difference), totales de origen y calculado, diferencia y motivo. La metadata sigue siendo plana; los objetos anidados se rechazan. |
Ejemplo: tres cajas de 2 kg compradas con una oferta por kilo
El proveedor cotiza 7,40 por kg. Tres cajas de 2 kg se facturan como 6 kg; el servidor deriva el mismo importe bruto, 44,40, elijas la base que elijas.
{
"external_invoice_number": "F-2026-0918",
"issued_on": "2026-09-18",
"supplier_id": "01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a10",
"lines": [
{
"product_id": "01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a01",
"presentation_id": "01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a02",
"supplier_offer_id": "01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a03",
"quantity": "6.0000",
"unit_price": "7.4000",
"price_source": "supplier_offer",
"tax_rate": 10,
"purchase_measurement": { "package_quantity": "3.0000", "cost_basis": "offer_unit" }
}
]
}La misma compra a precio por caja usa "quantity": "3.0000",
"unit_price": "14.8000" y "cost_basis": "presentation_unit" con coste
manual. Una presentación pesada (medida variable) exige además
confirmed_base_quantity, aunque aplique una oferta.
Cómo se resuelve el coste
Cambian dos comportamientos en las líneas de catálogo (las que llevan
product_id) en la API pública y en MCP, igual que en la aplicación web:
unit_priceomitido significa «coste no confirmado», no cero. El servidor propone el coste de la oferta del proveedor aplicable. Si el proveedor no tiene una oferta activa para el producto o la variante elegidos, la línea se rechaza con422 parameter_invalid_valueyparam: unit_price. Antes ese silencio se guardaba como0.00y el stock entraba valorado a cero. Las líneas manuales sinproduct_idsiguen exigiendounit_price.price_source: "supplier_offer"deja de ser decorativo. Enviado junto aunit_price, el importe se lee como el eco del coste que resolvió el servidor y se vuelve a congelar el coste de la oferta. Para imponer un coste, envíaprice_source: "manual"u omítelo: el importe que envías se respeta.
El coste supplier_offer se aplica a la unidad de compra de la oferta. Una
compra de medida variable sin confirmed_base_quantity se rechaza incluso con
oferta. Los documentos históricos no se recalculan.
Nuevos campos de la respuesta
PurchaseInvoiceLine, devuelto al listar, consultar, crear y actualizar:
| Campo | Significado |
|---|---|
purchase_measurement | Bloque derivado o null: package_quantity, billing_quantity, billing_unit_code, billing_conversion_factor, unit_cost y cost_basis. |
confirmed_base_quantity | Medida real en la unidad base como string decimal; null cuando la línea no la exige. |
discount_percent | Aplicado antes de impuestos. subtotal ya es neto de él, así que unit_price × quantity no es igual a subtotal en una línea con descuento. |
source_line_index | Ordinal base cero de la línea dentro de su documento, en orden estable de lectura. |
quantity, unit_price | El tipo pasa a ser number | string. Una línea medida devuelve strings de 4 decimales para no perder precisión; una línea legacy conserva el número que siempre ha publicado. Parsea ambos. |
Errores
Los errores de medida son 422 con code: parameter_invalid_value y
subcode: catalog_line_assembly_invalid; param señala el campo que corregir.
param | Causa |
|---|---|
purchase_measurement.cost_basis | La base no es válida, falta la oferta o presentación que requiere, o la unidad de compra y la unidad base son de familias distintas. |
purchase_measurement.package_quantity | Los bultos deben ser positivos, con cuatro decimales como máximo. |
quantity | La cantidad facturada no coincide con los bultos ni con la medida confirmada. |
confirmed_base_quantity | Obligatoria en una presentación de medida variable, o la medida no puede expresarse con cuatro decimales en la unidad de facturación. |
unit_price | No hay coste que aplicar: el proveedor no tiene oferta activa para la selección, o una conversión legacy perdería precisión. |
Un bloque de medida almacenado que no supera la validación se notifica con
subcode: catalog_line_snapshot_invalid. Consulta Errores.
MCP
create_purchase_invoice y update_purchase_invoice publican el mismo schema
de línea, incluidos purchase_measurement, discount_percent, price_source,
source_line_index y source_purchase_invoice_id, y aplican la misma regla
adicional de purchase_invoices:read al duplicar. get_purchase_invoice
devuelve los nuevos campos de línea. Consulta el
catálogo de tools MCP.
Lee Catálogo de productos para las ofertas de proveedor e Importes y fechas para los campos de línea de compra.
Endpoints actualizados4
| Endpoint | Descripción |
|---|---|
POST/v1/purchase_invoices | Crea una factura de compra |
PUT/v1/purchase_invoices/{purchase_invoice} | Actualizar una factura de compra |
GET/v1/purchase_invoices/{purchase_invoice} | Obtener una factura de compra |
GET/v1/purchase_invoices | Listar todas las facturas de compra |