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:
mode | Significado |
|---|---|
percent_off | Un porcentaje entre 0 y 100, ambos excluidos, con 2 decimales |
fixed | Un 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 responde422 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ón | Scope |
|---|---|
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}/cancel | promotions: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:
| Status | code | Cuándo |
|---|---|---|
| 404 | promotion_not_found | La oferta no existe |
| 422 | promotion_started | Cambias precios o artículos de una oferta activa, o la borras |
| 422 | promotion_read_only | Editas, cancelas o borras una oferta terminada o cancelada |
| 422 | invalid_promotion_item | El 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 |
| 422 | duplicate_promotion_item | El 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_completeesfalse: 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).
Guía legal para webs
Es una guía técnica, no asesoramiento jurídico. Estas son las reglas que los datos de la API te permiten cumplir:
- 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. - Anuncia solo cuando
announce_reductionestrue. Entonces muestrapromotion.previous_pricecomo precio anterior, tachado, junto apromotion.price. Cuando esfalse, muestra el precio sin «antes» y sin tachado. La oferta se sigue aplicando al cobrar. - Prefiere el importe ahorrado. Recomendamos anunciar la rebaja con el
importe ahorrado (6,43 € en el olivo), que es exacto. Es
previous_price.grossmenosprice.gross. - Redondea el porcentaje hacia abajo.
reduction_percentlleva 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. - Indica la fecha de fin.
ends_ates el último instante incluido. Usa su fecha comopriceValidUntilen schema.org (2026-11-02). - 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íasold_on: "2026-11-02"y la línea aplica «Semana del jardín».channel:documents(por defecto) oweb. Una web que factura sus pedidos envíaweb, así una oferta solo web se aplica en la factura. Cualquier otro valor responde422conparamen 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.