Factuarea APIDevelopers
Añadido

Unidades contadas, peso por piezas y sustitución por cambio de unidad base

Vende en paquetes, cajas o piezas un producto medido en kilos o litros, registra las piezas en las líneas de peso variable, sustituye un producto por otro con distinta unidad base con vista previa y ejecución irreversible, y lee la unidad real en FacturaE y UBL. Dos operaciones nuevas, dos tools MCP, el evento product.replaced y campos nuevos en productos y líneas de documento.

15 de octubre de 2026 — Un producto medido en kilos, litros o metros ahora se puede vender en unidades contadas como paquetes, cajas o piezas, y un producto que ya tiene historial puede cambiar de unidad base sustituyéndolo por uno nuevo. Todo es aditivo: ningún campo existente cambia de significado.

Unidades contadas

Hay 22 códigos de unidad en total: 9 unidades de medida y 13 unidades contadas.

ClaseUnidades (código UN/ECE)Dónde se pueden usar
Medidaunidad C62, kilogramo KGM, gramo GRM, litro LTR, mililitro MLT, metro MTR, metro cuadrado MTK, hora HUR, día DAYUnidad base, presentaciones, ofertas de proveedor, tarifas y líneas de documento
Contadapieza H87, paquete XPK, caja XBX, bandeja XDS, bolsa XBG, botella XBO, garrafa XDJ, lata XCA, tarrina XTB, bote XCI, bidón XJY, sobre XEN, estuche XCSPresentaciones, ofertas de proveedor, tarifas y líneas de documento

base_unit solo admite las 9 unidades de medida; las unidades contadas son unidades comerciales.

  • Presentaciones y ofertas de proveedor. Una presentación fija en una unidad contada puede apoyarse en una base medida si declara su equivalencia (conversion_factor o nominal_base_quantity): «1 paquete = 0,5 kg». Una unidad de medida debe pertenecer a la misma familia que la base. Una presentación o una oferta cuya unidad no se puede expresar sobre la unidad base del producto se rechaza con 422 unit_not_expressible.
  • Peso por piezas. Una presentación de peso variable cuya unidad es una unidad contada acepta en counted_quantity las piezas que pidió el cliente. La línea se sigue cobrando, y el stock se sigue descontando, por quantity, el peso real en la unidad base; las piezas son informativas.
  • Documentos. Las líneas de factura, presupuesto, proforma, albarán y factura recurrente llevan counted_quantity y counted_unit, ambos null cuando la línea no tiene piezas contadas. El editor, el PDF, el ticket y el enlace público muestran «4 paquetes = 2 kg» o «3 piezas · 1,520 kg × 4,55 €/kg».
  • FacturaE y UBL. FacturaE declara el UnitOfMeasure real de cada línea y UBL envía su unitCode UN/ECE real.

Sustitución por cambio de unidad base

La unidad base de un producto con historial (stock, movimientos, presentaciones, ofertas de proveedor o variantes con stock) no se edita en el sitio. El producto lo indica ahora en base_unit_change, y el cambio se hace sustituyendo el producto por uno nuevo.

  • Vista previa de la sustitución por cambio de unidad base calcula, sin escribir nada, el plan y un impact_token que confirma ese plan exacto. Envías la unidad base nueva (target_base_unit, una de las 9 unidades de medida), la equivalence entre ambas unidades, un base_price opcional y una decisión keep o retire por presentación.
  • Sustituir un producto cambiando su unidad base la ejecuta y responde 201 con el producto nuevo. Es irreversible, así que Idempotency-Key es obligatoria. El stock, las presentaciones que conservas, las variantes, las ofertas de proveedor, las entradas de tarifa y los componentes de pack pasan al producto nuevo, convertidos a su unidad. El producto anterior se desactiva, conserva todo su historial y no se puede reactivar nunca. No consume cuota del plan.
  • Si la conversión pierde precisión o no hay equivalencia, hay que confirmar base_price (422 base_unit_replacement_price_confirmation_required). Si el catálogo, las tarifas o los packs cambian después de la vista previa, la confirmación se rechaza con 422 base_unit_replacement_preview_stale y se pide una vista previa nueva.
  • Los demás rechazos de la sustitución son 422 business_rule_violation con subcódigo product_already_replaced, base_unit_replacement_conversion_unavailable o base_unit_replacement_blocked_by_packs, y 422 parameter_invalid_value con subcódigo base_unit_replacement_same_unit (el destino ya es la unidad base del producto), base_unit_replacement_equivalence_invalid o base_unit_replacement_decision_invalid.
  • La equivalence es el número de unidades de la base actual que equivalen a una unidad de la nueva. Es obligatoria entre una unidad de recuento y una de medida, en cualquier sentido; dentro de la misma familia de medida se calcula sola, y entre dos familias de medida distintas no existe.
  • Un producto que participa en una sustitución, como producto anterior o como nuevo, no se puede eliminar: Elimina un producto y Elimina varios productos de forma masiva responden 422 con subcódigo product_in_base_unit_replacement, y en la operación en bloque el producto figura en failed[] con ese motivo. Las tools MCP delete_product y bulk_delete_products aplican la misma regla.
  • Editar base_unit en un producto cuyo base_unit_change.mode es replacement responde 422 business_rule_violation con subcódigo product_base_unit_locked.

Campos, eventos y MCP

  • Producto. base_unit_change (mode y los blockers cuantificados), replaces y replaced_by (id, name, base_unit, replaced_at, equivalence y stock_transferred). Se devuelven en las lecturas de un solo producto y se omiten en los listados.
  • Evento. product.replaced se publica con el producto nuevo como objeto. Consulta Eventos.
  • Movimientos de stock. Los motivos base_unit_replacement_out y base_unit_replacement_in y el origen base_unit_replacement registran el traspaso entre ambos productos.
  • Tarifas. El motivo de retirada product_replaced identifica las entradas del producto sustituido.
  • MCP. Dos tools nuevas, preview_product_base_unit_replacement y replace_product_base_unit, ambas con el scope products:write; la segunda es irreversible y acepta idempotency_key. Consulta el catálogo de tools.

Los componentes de un pack no admiten piezas: components.*.counted_quantity responde 422 si llega con valor, porque las piezas pertenecen a las líneas de los documentos de venta.

Las operaciones que leen o escriben productos, presentaciones, ofertas de proveedor, tarifas y documentos de venta conservan sus scopes, y sus respuestas y cuerpos incorporan los campos y códigos de unidad descritos.

Nuevos endpoints2

EndpointDescripción
POST/v1/products/{product}/base_unit_replacement/previewVista previa de la sustitución por cambio de unidad base
POST/v1/products/{product}/base_unit_replacementSustituir un producto cambiando su unidad base

Endpoints actualizados103

EndpointDescripción
POST/v1/proformas/{proforma}/acceptAceptar una proforma
POST/v1/quotes/{quote}/acceptAceptar un presupuesto
POST/v1/recurring_invoices/{recurring_invoice}/activateActivar factura recurrente
POST/v1/invoices/{invoice}/annulAnular una factura
POST/v1/invoices/{invoice}/assign-real-numberAsignar un número de factura real
POST/v1/delivery_notes/{delivery_note}/cancelAnular un albarán
POST/v1/recurring_invoices/{recurring_invoice}/cancelAnular factura recurrente
POST/v1/delivery_notes/{delivery_note}/convertConvertir albarán en factura
POST/v1/proformas/{proforma}/convertConvertir proforma en factura
POST/v1/quotes/{quote}/convertConvertir presupuesto en factura
POST/v1/invoices/{invoice}/correctiveGenerar factura rectificativa
POST/v1/delivery_notesCrear un albarán
GET/v1/delivery_notesListar todos los albaranes
POST/v1/invoicesCrea una factura
GET/v1/invoicesListar todas las facturas
POST/v1/price-listsCrear una tarifa
GET/v1/price-listsListar tarifas
POST/v1/productsCrear un producto
GET/v1/productsListar todos los productos
POST/v1/products/{product}/presentationsCrear una presentación de producto
GET/v1/products/{product}/presentationsListar presentaciones de producto
POST/v1/proformasCrea una proforma
GET/v1/proformasListar todas las proformas
POST/v1/purchase_invoicesCrea un gasto
POST/v1/quotesCrea un presupuesto
GET/v1/quotesListar todos los presupuestos
POST/v1/invoices/{invoice}/create-recurringCrear una factura recurrente a partir de una factura
POST/v1/recurring_invoicesCrea una factura recurrente
GET/v1/recurring_invoicesListar todas las facturas recurrentes
POST/v1/products/{product}/supplier-offersCrear una oferta de proveedor
GET/v1/products/{product}/supplier-offersListar ofertas de proveedor
POST/v1/webhook_endpointsCrea un webhook endpoint
GET/v1/webhook_endpointsListar todos los webhook endpoints
GET/v1/delivery_notes/{delivery_note}Obtener un albarán
PUT/v1/delivery_notes/{delivery_note}Actualizar un albarán
GET/v1/invoices/{invoice}Recupera una factura
PUT/v1/invoices/{invoice}Actualizar una factura
GET/v1/price-lists/{priceList}Consultar una tarifa
PUT/v1/price-lists/{priceList}Actualizar una tarifa
GET/v1/products/{product}Obtener un producto
PUT/v1/products/{product}Actualizar un producto
DEL/v1/products/{product}Elimina un producto
POST/v1/products/bulk-deleteElimina varios productos de forma masiva
PUT/v1/products/{product}/presentations/{presentation}Actualizar una presentación de producto
POST/v1/products/{product}/videoSube un vídeo a un producto
GET/v1/proformas/{proforma}Obtener una proforma
PUT/v1/proformas/{proforma}Actualizar una proforma
PUT/v1/purchase_invoices/{purchase_invoice}Actualizar un gasto
GET/v1/quotes/{quote}Obtener un presupuesto
PUT/v1/quotes/{quote}Actualizar un presupuesto
GET/v1/recurring_invoices/{recurring_invoice}Obtener una factura recurrente
PUT/v1/recurring_invoices/{recurring_invoice}Actualizar una factura recurrente
PUT/v1/products/{product}/supplier-offers/{offer}Actualizar una oferta de proveedor
GET/v1/webhook_endpoints/{webhook_endpoint}Obtener un webhook endpoint
PUT/v1/webhook_endpoints/{webhook_endpoint}Actualizar un webhook endpoint
POST/v1/delivery_notes/{delivery_note}/duplicateDuplicar un albarán
POST/v1/invoices/{invoice}/duplicateDuplicar una factura
POST/v1/proformas/{proforma}/duplicateDuplicar una proforma
POST/v1/quotes/{quote}/duplicateDuplicar un presupuesto
POST/v1/delivery_notes/find-by-external-idBuscar un albarán por external ID
POST/v1/invoices/find-by-external-idBuscar una factura por external ID
POST/v1/invoices/find-by-numberBuscar una factura por número
POST/v1/products/find-by-external-idBuscar un producto por external ID
POST/v1/products/find-by-skuBuscar un producto por SKU
POST/v1/proformas/find-by-external-idBuscar una proforma por external ID
POST/v1/quotes/find-by-external-idBuscar un presupuesto por external ID
POST/v1/recurring_invoices/find-by-external-idBuscar una factura recurrente por external ID
GET/v1/products/low-stock-reportLista los productos por debajo del umbral de stock
POST/v1/invoices/{invoice}/issueEmite una factura
GET/v1/eventsListar todos los eventos
GET/v1/invoices/{invoice}/correctivesListar facturas rectificativas
GET/v1/price-lists/{priceList}/itemsListar ítems de tarifa
POST/v1/price-lists/{priceList}/itemsHacer upsert de un ítem de tarifa
GET/v1/price-lists/optionsListar opciones de tarifas activas
GET/v1/products/{product}/stock-movementsListar movimientos de stock de un producto
GET/v1/webhook_endpoints/{webhook_endpoint}/deliveriesLista las entregas de webhook
POST/v1/delivery_notes/{delivery_note}/mark-deliveredMarca el albarán como entregado
POST/v1/invoices/{invoice}/mark-paidMarca la factura como pagada
POST/v1/invoices/{invoice}/mark-sentMarca una factura como enviada
POST/v1/recurring_invoices/{recurring_invoice}/pausePausar factura recurrente
POST/v1/price-lists/{priceList}/items/{item}/reassignReasignar una entrada de tarifa retirada
POST/v1/proformas/{proforma}/rejectRechazar una proforma
POST/v1/quotes/{quote}/rejectRechazar un presupuesto
PATCH/v1/invoices/{invoice}/rescheduleReprogramar una factura
POST/v1/recurring_invoices/{recurring_invoice}/resumeReanudar factura recurrente
POST/v1/webhook_endpoints/{webhook_endpoint}/rotate_secretRota el secreto del webhook
POST/v1/invoices/{invoice}/scheduleProgramar una factura
GET/v1/products/searchBusca productos
POST/v1/invoices/{invoice}/sendEnvía la factura por email
POST/v1/proformas/{proforma}/sendEnvía la proforma por email
POST/v1/quotes/{quote}/sendEnvía el presupuesto por email
POST/v1/webhook_endpoints/{webhook_endpoint}/test_eventEnviar un evento de prueba
POST/v1/products/{product}/supplier-offers/{offer}/preferredEstablecer la oferta de proveedor preferida
GET/v1/events/{event}Obtener un evento
GET/v1/webhook_endpoints/{webhook_endpoint}/deliveries/{delivery}Recupera la entrega del webhook
POST/v1/delivery_notes/{delivery_note}/signFirmar un albarán
POST/v1/recurring_invoices/{recurring_invoice}/skipOmitir la próxima generación de la factura recurrente
POST/v1/invoices/substitute-simplifiedSustituir facturas simplificadas por factura completa
POST/v1/products/{product}/toggle-activeAlternar el estado activo del producto
POST/v1/invoices/{invoice}/unscheduleDesprogramar una factura
POST/v1/invoices/{invoice}/unsendAnular el envío de una factura
PUT/v1/products/{product}/stockActualizar el stock de un producto
POST/v1/invoices/{invoice}/voidAnular una factura

En esta página

¿Te echamos una mano?Contactar con soporte