Factuarea APIDevelopers

Ofertas y precio anterior

Pon productos y packs en oferta unos días, lee la oferta desde tu web y anuncia la rebaja con el precio anterior que exige la ley.

Una oferta fija un precio más bajo para productos o packs entre dos fechas, para los documentos de la empresa, para su web o para ambos. Esta página explica cómo crearla, cómo la lee una web, cómo se calcula el precio anterior y qué debe mostrar una tienda para anunciar una rebaja. Las ofertas están disponibles en todos los planes que incluyen productos, desde Esencial.

Qué es una oferta

Una oferta tiene un nombre, un inicio y un fin en la zona horaria de la empresa (el fin es inclusivo), los canales documents y web, y uno o más artículos. Cada artículo apunta a un producto, una variante, una presentación, una configuración o un pack, con un modo:

modeSignificado
percent_offUn porcentaje entre 0 y 100, ambos excluidos, con 2 decimales
fixedUn precio mayor que 0 con 2 decimales, sin IVA o con IVA en el régimen de la empresa

Un artículo no puede repetirse dentro de la misma oferta. El estado se deriva de las fechas: scheduled, active o ended, además de cancelled, que es explícito.

  • Antes de empezar, todo se puede editar.
  • Una oferta activa solo admite una nueva fecha de fin y que se cancele. Alargarla la marca con is_extension: true. Cambiar precios o artículos responde 422 promotion_started.
  • Una oferta terminada o cancelada es de solo lectura (422 promotion_read_only) y se puede duplicar.

Si dos ofertas cubren el mismo artículo, canal y día, se aplica el precio más bajo y nunca se suman. Un cliente con tarifa no recibe la oferta por defecto, porque manda la tarifa. La oferta se puede marcar para que también se les aplique, y entonces gana el más bajo de los dos.

API

OperaciónScope
GET /v1/companies/{company}/promotions y GET …/promotions/{promotion}promotions:read
POST /v1/companies/{company}/promotions y PATCH …/promotions/{promotion}promotions:write
POST …/promotions/{promotion}/cancelpromotions:write
DELETE …/promotions/{promotion}promotions:delete

El listado filtra por status, channel, starts_at[gte|lte], ends_at[gte|lte] y updated[gte], con paginación por cursor. El alta responde 201 con Location. Los scopes dependen del módulo de productos, así que están disponibles donde haya productos. Errores:

StatuscodeCuándo
404promotion_not_foundLa oferta no existe
422promotion_startedCambias precios o artículos de una oferta activa, o la borras
422promotion_read_onlyEditas, cancelas o borras una oferta terminada o cancelada
422invalid_promotion_itemEl artículo no existe o es de otra empresa, o el modo o el valor están fuera de rango. param apunta al artículo, por ejemplo items.0.target_id
422duplicate_promotion_itemEl mismo artículo dos veces

Los eventos son promotion.created, promotion.updated, promotion.started, promotion.ended, promotion.cancelled y promotion.deleted (este último solo lleva {id, object: "promotion", deleted: true}). Cuando una oferta empieza o termina, un programador emite además product.updated de sus productos y pack.updated de sus packs, así tu web refresca los precios sin que nadie edite nada. El servidor MCP replica las operaciones con list_promotions, get_promotion, create_promotion, update_promotion, cancel_promotion y delete_promotion.

El objeto promotion en productos y packs

La lectura de un producto (también de cada variante, presentación y configuración) o de un pack, en cualquier vista, incluye siempre promotion: la oferta que se aplica hoy en el canal web, o en la date que pidas, o null. La forma del recurso nunca cambia. Una oferta solo de documentos se lee como null.

{
  "id": "019f1b77-3e90-7c24-8a1d-5f0b2e6c9d48",
  "name": "Olivo arbequina",
  "price": "39.00",
  "sale_price": { "net": "39.00", "tax": "3.90", "gross": "42.90" },
  "promotion": {
    "id": "019f2c40-8a15-7e63-9b07-4d1c5f2a8e90",
    "name": "Semana del jardín",
    "label": "Oferta",
    "price": { "net": "33.15", "tax": "3.32", "gross": "36.47" },
    "regular_price": { "net": "39.00", "tax": "3.90", "gross": "42.90" },
    "previous_price": { "net": "39.00", "tax": "3.90", "gross": "42.90" },
    "reduction_percent": 14.99,
    "announce_reduction": true,
    "starts_at": "2026-10-26T00:00:00+01:00",
    "ends_at": "2026-11-02T23:59:59+01:00",
    "is_extension": false,
    "history_complete": true
  }
}

price y sale_price siguen significando el precio habitual, y el precio de oferta va en promotion.price. El parámetro date admite una fecha ISO 8601 y una mal formada responde 400 parameter_invalid_iso8601. El updated_at de un producto cambia cuando empieza o termina una oferta de cualquiera de sus artículos. POST …/products/{product}/resolve-selection devuelve también la promotion de la combinación que resuelve.

Ofertas sobre packs

Una oferta sobre un pack sustituye el precio objetivo del pack, igual que un precio negociado: con fixed, por su precio, y con percent_off, por el porcentaje sobre el precio habitual del pack. El precio derivado de un pack y su reparto usan siempre los precios habituales de los componentes, mientras que el ahorro «comprando por separado» se mide contra sus precios efectivos, ofertas incluidas. Si los componentes están en oferta y ya no queda ahorro, savings vale null y la web no lo muestra.

Resolver un precio para una fecha y un canal

POST /v1/companies/{company}/price-lists/resolve y …/resolve-many admiten date (la fecha del documento, sold_on u hoy) y channel (documents o web). Sin ellos usan hoy y documents. Las tools MCP resolve_catalog_price y resolve_many_catalog_prices reciben los mismos dos parámetros y devuelven el mismo resultado. La oferta es una capa que se aplica después de la precedencia del catálogo, sobre el precio habitual de ese nivel, y el desglose del preview muestra el precio habitual, la oferta aplicada y su origen. Para el olivo el 28 de octubre de 2026 en el canal web, la respuesta es 33,15 € con «Semana del jardín −15 %» sobre un precio habitual de 39,00 €.

El precio anterior

Para anunciar una rebaja con la ley en la mano, la tienda debe mostrar como precio anterior el más bajo aplicado en los 30 días previos al inicio (art. 20 de la Ley 7/1996, en la redacción del RDL 24/2021, y art. 6 bis de la Directiva 98/6/CE). Factuarea guarda el historial del precio público de cada artículo y canal, sin tarifas, y lo calcula por ti:

  • El precio anterior es el menor precio con IVA en ese canal durante los 30 días previos al inicio, sin contar los intervalos de las ofertas marcadas como producto perecedero cerca de caducar.
  • Se mantiene igual durante toda la oferta y sus prolongaciones.
  • Si el artículo se pone a la venta por primera vez, el precio anterior es null.
  • Durante los 30 días siguientes a que se rellenara el historial, history_complete es false: el precio anterior puede no contemplar rebajas más antiguas.

Si el precio de oferta con IVA no es menor que el precio anterior, o no hay precio anterior, la oferta se aplica igual en documentos y en la web pero no se puede anunciar: la API devuelve announce_reduction: false y reduction_percent: null. Pasa sobre todo al repetir una oferta antes de 30 días, porque el precio de la anterior pasa a ser el precio anterior. Por ejemplo, el olivo estuvo a 36,47 € hasta el 2 de noviembre de 2026 y una oferta nueva del −10 % (38,61 €) empieza el 20 de noviembre: el precio anterior es 36,47 € y la rebaja no se puede anunciar. La aplicación propone el descuento mínimo (−16 %) o la primera fecha de inicio que lo permite (3 de diciembre de 2026).

Es una guía técnica, no asesoramiento jurídico. Estas son las reglas que los datos de la API te permiten cumplir:

  1. Muestra la palabra correcta. El art. 18.2 de la LOCM reserva términos como «Oferta» o «Rebajas», por eso la empresa elige el texto de cada oferta. Muestra promotion.label, no un texto propio.
  2. Anuncia solo cuando announce_reduction es true. Entonces muestra promotion.previous_price como precio anterior, tachado, junto a promotion.price. Cuando es false, muestra el precio sin «antes» y sin tachado. La oferta se sigue aplicando al cobrar.
  3. Prefiere el importe ahorrado. Recomendamos anunciar la rebaja con el importe ahorrado (6,43 € en el olivo), que es exacto. Es previous_price.gross menos price.gross.
  4. Redondea el porcentaje hacia abajo. reduction_percent lleva 2 decimales y se calcula sobre los importes con IVA ya redondeados: el olivo baja de 42,90 € a 36,47 €, que es un 14,99 % y no un 15 %. Si tu web muestra un entero, redondea hacia abajo (14 %) para no anunciar más rebaja de la real.
  5. Indica la fecha de fin. ends_at es el último instante incluido. Usa su fecha como priceValidUntil en schema.org (2026-11-02).
  6. B2B y perecederos. El precio anterior se calcula siempre y tu web decide si lo muestra. Las rebajas de productos perecederos cerca de caducar no bajan el precio anterior de las ofertas futuras.
{
  "@context": "https://schema.org",
  "@type": "Product",
  "name": "Olivo arbequina",
  "offers": {
    "@type": "Offer",
    "price": "36.47",
    "priceCurrency": "EUR",
    "priceValidUntil": "2026-11-02",
    "availability": "https://schema.org/InStock"
  }
}

Factura una venta web con oferta

Las líneas de las operaciones v1 y MCP que crean o editan presupuestos, facturas proforma, albaranes, facturas y facturas recurrentes admiten dos campos:

  • sold_on: la fecha del pedido, con la que se resuelve la oferta vigente. Una web que factura el 3 de noviembre un pedido del 2 de noviembre envía sold_on: "2026-11-02" y la línea aplica «Semana del jardín».
  • channel: documents (por defecto) o web. Una web que factura sus pedidos envía web, así una oferta solo web se aplica en la factura. Cualquier otro valor responde 422 con param en la línea.

Un unit_price enviado en la línea gana siempre a la oferta, porque es el precio que cobró una integración, y la línea queda sin fotografía de oferta. Hay una excepción: si el unit_price coincide con el precio de oferta de su canal y su fecha, la línea guarda la fotografía de la oferta y cuenta como vendida con ella. Si no coincide, es un precio cobrado sin oferta.

Sin unit_price, la línea toma el precio de la oferta con price_source: promotion y guarda la oferta, su nombre y el precio unitario habitual. Cambiar la fecha de un borrador revaloriza sus líneas en oferta y avisa una sola vez. En una factura recurrente, la factura generada después del fin de la oferta vuelve al precio habitual. Los presupuestos convertidos después de la oferta conservan el precio que tenían.

En esta página

¿Te echamos una mano?Contactar con soporte