Guía: Lectura desde GET · ← Aplicación
rule_effects — referencia completa
Campo en la respuesta GET
En GET /discounts y GET /discounts/{id}, este bloque viene en **rule_effects** (string JSON). Valida rule_group para bundle (no variantes legacy como rule_grouped en samples antiguos).
Define qué beneficio otorga la campaña (descuento monetario, sustitución de precio por producto, precio combo, etc.) y cómo se reparte entre el carrito y las líneas.
Pregunta que responde: «Si la campaña ganó, ¿cuánto descuenta o qué muestra, y sobre qué base del carrito?»
Aquí **rule_group** (agrupación bundle en el efecto) sí importa para integraciones — a diferencia de bundle / grouped en rule_conditions, que son internos y se ignoran.
Índice
- Campos comunes en GET
limits(límite del efecto, no de victoria)- Distribuciones permitidas por tipo de efecto
- Apilamiento (stackability) por efecto
percentagedirect_valueratiostrikethroughcombo_priceformula(efecto promocional externo)- Mecánica bundle (
rule_group) - Configuración vs respuesta de carrito
- Errores frecuentes —
rule_effects
Campos comunes en GET
| Campo | Rol |
|---|---|
key / type | Identifican el efecto; suelen coincidir (percentage, direct_value, …) |
value | Magnitud principal (% o importe). No aplica en strikethrough ni en formula |
unit | Solo **ratio**: unidad monetaria de referencia (p. ej. cada 10 €) |
distribution | Cómo se reparte el efecto (ver tabla por tipo más abajo) |
limits | Veces máximas que el efecto puede aplicar en la misma transacción |
rule_group | true = agrupación / bundle (solo percentage, direct_value, combo_price). Requiere cantidades en condiciones + aplicación — ver Mecánica bundle |
formula | Solo efecto **formula**: expresión fija de cálculo |
strikethrough_data | Solo **strikethrough**: mapa clave producto → precio promocional. La clave puede ser **product_id** o **product_code** según use la compañía (debe coincidir con el identificador de línea en el carrito) |
limits (límite del efecto, no de victoria)
limits en rule_effects acota cuántas veces puede aplicarse el efecto en la misma transacción (mismo ticket). No es el tope de victoria de la campaña (once, presupuesto, etc.) ni un límite de unidades en rule_conditions.
| Valor | Interpretación |
|---|---|
0 | Sin límite: el efecto puede aplicarse todas las veces que correspondan según condiciones y aplicación |
> 0 | Como máximo esa cantidad de aplicaciones del efecto en el carrito |
Qué cuenta como «una aplicación» depende de distribution y de rule_group:
| Configuración | Qué cuenta como 1 aplicación | Qué limita limits: N |
|---|---|---|
distribution: individual_items y rule_group null / false | Una unidad (línea interna de cantidad 1; ver expansión abajo) | Máximo de unidades bonificadas en el carrito |
rule_group: true (bundle / 2x1 / combo) | Un grupo armado según quantity en rule_conditions + rule_application | Máximo de grupos que reciben el beneficio (p. ej. cuántos 2x1) |
Una unidad = una línea en el motor (aunque el carrito traiga quantity > 1)
Para el cálculo de efectos, Loyal Guru trata cada unidad como una línea independiente. Si la integración envía una sola línea con quantity: 10, el motor la descompone internamente en 10 líneas de cantidad 1, ejecuta condiciones, rule_application, efectos y limits sobre ese modelo unitario y, al terminar, reagrupa el resultado en las mismas líneas que llegaron en el carrito (mismo número de líneas y cantidades que envió el retailer).
Implicaciones para integraciones:
- Con
distribution: individual_items,limitscuenta unidades (líneas internas de qty 1), no necesariamente filas del JSON delines[].limits: 10con una línea agregada de qty 15 → como mucho 10 unidades con descuento en esa línea (las 5 restantes de esa línea, al precio pleno para ese efecto). - En
POST /carts,effects[]puede mostrar el descuento consolidado por línea original; la lógica interna siguió el modelo unitario. - Condiciones de victoria y aplicación también se evalúan sobre unidades expandidas cuando el motor lo requiere; la expansión es transparente para quien solo lee el GET de campaña, pero importa al interpretar
limitsy al depurar tickets con líneas multi-cantidad.
Ejemplo: % por producto con tope de unidades (sin bundle)
Objetivo: 5 % sobre determinados productos, como máximo en 10 unidades del carrito.
| Campo | Valor | Por qué |
|---|---|---|
type | percentage | Descuento porcentual |
value | 5 | 5 % |
distribution | individual_items | El % se calcula por unidad (línea interna de qty 1) |
rule_group | null o false | Sin agrupación: cada aplicación = una unidad |
limits | 10 | Como mucho 10 unidades reciben el 5 % en esa compra |
rule_application debe acotar los productos (p. ej. product_id o product_categories en las líneas). Si el carrito trae una línea con quantity: 15 del producto elegible, el motor la trata como 15 unidades; con limits: 10 solo 10 llevan el 5 % (en la respuesta suele verse reflejado en esa misma línea agregada). Si hay varias líneas en lines[], el tope de 10 se reparte entre unidades de todas ellas según prioridad/apilamiento.
{
"type": "percentage",
"value": 5,
"distribution": "individual_items",
"rule_group": null,
"limits": 10
}
Ejemplo: 2x1 con tope de grupos (con rule_group)
Objetivo: mecánica 2x1 pero solo dos 2x1 por ticket (dos grupos bonificados).
| Campo | Valor | Por qué |
|---|---|---|
rule_group | true | Cada aplicación = un grupo (par 2+1), no una línea suelta |
limits | 2 | Como mucho 2 grupos con beneficio en la transacción |
Condiciones de victoria (rule_conditions) definen el tamaño del grupo (p. ej. quantity ≥ 2); rule_application cuántas unidades del grupo llevan dto. (p. ej. ≥ 1). Detalle completo: Mecánica bundle (rule_group) — ahí el mismo carrito con 8 unidades y limits: 2 solo bonifica 2 de 4 grupos posibles.
No confundir con límites de victoria (
once,num_per_day, presupuesto, etc.) en la configuración general de la campaña.
Distribuciones permitidas por tipo de efecto
| Efecto | total | individual_items | individual_items_pro_rata |
|---|---|---|---|
percentage | ✓ | ✓ | ✓ |
direct_value | ✓ | ✓ | ✓ |
ratio | ✓ | ✓ | ✓ |
strikethrough | — | ✓ (única) | — |
combo_price | ✓ | — | ✓ |
formula | — | ✓ (única) | — |
| Distribución | Comportamiento breve |
|---|---|
total | Un solo descuento sobre el conjunto calificado / total de la transacción |
individual_items | El efecto se evalúa por unidad (cada unidad como línea interna de qty 1; ver expansión unitaria) |
individual_items_pro_rata | El beneficio se reparte proporcionalmente entre las líneas calificadas |
Apilamiento (stackability) por efecto
| Efecto | Modo en carrito |
|---|---|
strikethrough | Siempre Highest Discount |
combo_price | Siempre Highest Discount |
percentage, ratio, direct_value, formula | Stackable o Highest Discount (configurable) |
Detalle: Apilamiento.
percentage
Descuento porcentual sobre líneas o total calificado.
{
"key": "percentage",
"type": "percentage",
"value": 20,
"limits": 0,
"formula": null,
"rule_group": null,
"distribution": "individual_items"
}
| Campo | Lectura |
|---|---|
value: 20 | 20 % de descuento |
distribution: individual_items | El % se aplica en cada línea que cumpla aplicación |
limits: 0 | Sin tope de unidades bonificadas; con limits: 10, como mucho 10 unidades — ver ejemplo % con tope |
rule_group: null | Sin agrupación (p. ej. % simple en categoría). Con true, activa grupos según quantity en condiciones y aplicación |
Integración: si el descuento no aparece en las líneas esperadas del carrito, revisar **rule_application** además del % — en carrito, la aplicación suele ser obligatoria para % por línea (Detalles operativos §5).
direct_value
Importe fijo de descuento (moneda de la campaña).
{
"key": "direct_value",
"type": "direct_value",
"value": 5,
"limits": 0,
"formula": null,
"rule_group": null,
"distribution": "individual_items"
}
| Campo | Lectura |
|---|---|
value: 5 | 5 € (u otra moneda) según currency de la campaña |
distribution: individual_items | 5 € por línea calificada; con total, un único bloque de 5 € sobre el conjunto |
rule_group | Igual que en percentage: true para bundles que reparten un importe fijo en grupo |
Caso frecuente: €5 de dto. en cesta → direct_value, distribution: total, condición total_cart >= X.
ratio
Beneficio proporcional al gasto en unidades monetarias: por cada unit € gastados (en base calificada), se otorga value de beneficio, repartido según distribution.
{
"key": "ratio",
"type": "ratio",
"unit": 10,
"value": 1,
"limits": 2,
"formula": null,
"distribution": "individual_items_pro_rata"
}
| Campo | Lectura del ejemplo |
|---|---|
unit: 10, value: 1 | Cada 10 € calificados → 1 de descuento (u otro beneficio según modo de campaña) |
distribution: individual_items_pro_rata | Ese beneficio se prorratea entre las líneas que participan |
limits: 2 | Como máximo 2 aplicaciones del ratio en la misma transacción (p. ej. hasta 20 € de base → 2 × 1 de beneficio si la regla lo permite) |
En campañas de puntos, el mismo tipo
ratiopuede otorgar saldo (puntos o wallet) en lugar de descuento monetario — revisarcurrency/ contexto de la campaña. En descuentos con este JSON, la lectura operativa es descuento escalado al gasto y prorrateo.
strikethrough
Sustituye el precio de la línea en el carrito por el precio promocional definido en strikethrough_data. El descuento aplicado es la diferencia entre el precio enviado en el carrito y el precio promocional del efecto.
{
"key": "strikethrough",
"type": "strikethrough",
"limits": 0,
"formula": null,
"distribution": "individual_items",
"strikethrough_data": {
"233212": 1,
"343434": 9
}
}
| Campo | Lectura |
|---|---|
strikethrough_data | Mapa { "clave_producto": precio_promocional }. La clave es el **product_id** o el **product_code** que use la compañía en catálogo/ticket — debe ser el mismo tipo de identificador que envía la integración en la línea |
Sin value | La magnitud del dto. sale del cálculo por línea (precio ticket − precio promo) |
distribution | Siempre individual_items (una evaluación por línea / producto) |
limits | Misma semántica (0 = sin tope de aplicaciones del efecto) |
Claves del mapa: en una misma campaña, las entradas suelen ser homogéneas (todas por ID numérico o todas por código). Si la compañía opera por SKU, espera claves tipo "12000041"; si opera por ID interno, claves tipo "233212".
Ejemplo numérico (por product_id): strikethrough_data["233212"]: 1, línea en ticket con ese producto y precio 3 → precio efectivo 1, descuento 2 (3 − 1).
Ejemplo (por product_code): strikethrough_data["CC-33CL-LATA"]: 0.99 — la línea del carrito debe traer el mismo product_code para que matchee.
Integración: si no aplica, comprobar tipo de clave (ID vs código) alineado con el carrito, que la clave exista en el mapa y el precio de línea enviado. Apilamiento: siempre Highest Discount.
combo_price
Precio fijo del bundle para un grupo de productos que cumplen condiciones / aplicación.
{
"key": "combo_price",
"type": "combo_price",
"value": 10.9,
"limits": 0,
"formula": null,
"rule_group": true,
"distribution": "individual_items_pro_rata"
}
| Campo | Lectura |
|---|---|
value: 10.9 | Precio total del combo (10,9 €) |
rule_group: true | El motor agrupa líneas que cumplen la mecánica bundle antes de fijar el precio combo |
distribution: individual_items_pro_rata | El ahorro del combo se reparte entre líneas del grupo. Alternativa: total (un solo bloque sobre el conjunto) |
limits: 0 | Sin tope de veces que puede aplicar el combo en el carrito |
Restricción: no usar individual_items en combo_price — solo total o individual_items_pro_rata. Apilamiento: siempre Highest Discount.
formula (efecto promocional externo)
Descuento calculado por línea a partir de un atributo enviado por un sistema externo (precio promo precalculado).
{
"key": "formula",
"type": "formula",
"limits": 0,
"formula": "lines.total - lines.attributes.promoPrice",
"distribution": "individual_items"
}
| Campo | Lectura |
|---|---|
formula | Fija en configuración: lines.total - lines.attributes.promoPrice |
Sin value | El importe sale del cálculo por línea |
distribution | Siempre individual_items |
rule_group | No usa agrupación (formula no admite bundle) |
Flujo de integración: el integrador debe enviar promoPrice (u homólogo acordado) en atributos de línea del carrito. Si falta en una línea, esa línea no recibe dto. por esta campaña. Si el resultado ≤ 0, no aplica descuento en esa línea.
Mecánica bundle (rule_group)
En Owner App, Bundle / grupo (rule_group: true) indica al motor que forme grupos de líneas y aplique el efecto por grupo, no línea a línea de forma independiente.
Dónde aplica
rule_effects.rule_group | Tipos de efecto compatibles |
|---|---|
true | percentage, direct_value, combo_price |
false / null | Cualquier efecto sin agrupación |
| No disponible | ratio, strikethrough, formula |
Los tres bloques que definen el bundle
| Bloque | Pregunta que responde | Qué validar en integración |
|---|---|---|
**rule_conditions** | ¿Cuántas unidades hacen falta para formar un grupo? | Misma cláusula de producto/categoría + subcondición quantity (ej. gte 2 → grupos de 2) |
**rule_application** | ¿Sobre cuántas unidades del grupo cae el efecto? | Misma familia de producto/categoría + subcondición quantity (ej. gte 1 → 1 unidad con beneficio por grupo) |
**rule_effects** | ¿Qué beneficio y cuántas veces? | rule_group: true, value / type, distribution, **limits** |
Los flags bundle y grouped en el JSON de condiciones/aplicación son internos (ignorar valor). Lo operativo son las **quantity** anidadas y **rule_group** en el efecto.
Ejemplo completo: 2x1 del producto 2222 (100 % en 1 de cada 2)
Victoria — armar grupos de 2 unidades (rule_conditions):
{
"conditions": {
"and": [
{
"key": "cart_product_id",
"field": "product_code",
"bundle": 1,
"values": ["2222"],
"grouped": true,
"operator": "in",
"conditions": [
{ "field": "quantity", "value": 2, "operator": "gte" }
],
"array_field": "lines"
}
]
}
}
Aplicación — el dto. va sobre 1 unidad por grupo (rule_application):
{
"conditions": {
"and": [
{
"key": "cart_product_id",
"field": "product_code",
"bundle": 1,
"values": ["2222"],
"grouped": true,
"operator": "in",
"conditions": [
{ "field": "quantity", "value": 1, "operator": "gte" }
],
"array_field": "lines"
}
]
}
}
Efecto — 100 % en esa unidad, con agrupación activa (rule_effects):
{
"key": "percentage",
"type": "percentage",
"value": 100,
"limits": 1,
"formula": null,
"rule_group": true,
"distribution": "individual_items_pro_rata"
}
Interpretación del 2x1
| Paso | Lectura |
|---|---|
Condiciones quantity: 2 | Cada grupo válido = 2 unidades del producto 2222 en el carrito |
Aplicación quantity: 1 | Dentro de cada grupo, el 100 % aplica sobre 1 unidad (la “gratis” o bonificada del 2x1) |
rule_group: true | El motor agrupa antes de calcular; sin esto, el % se interpretaría línea a línea sin mecánica 2x1 |
distribution: individual_items_pro_rata | Reparto del descuento de la unidad bonificada entre líneas del grupo (habitual en 2x1) |
Ticket con 8 unidades 2222 | 8 ÷ 2 = 4 grupos → hasta 4 aplicaciones del efecto (4 unidades bonificadas si no hay tope) |
limits del efecto con bundles
Con rule_group: true, cada aplicación del efecto es un grupo (no una línea suelta). Por eso limits: 2 en un 2x1 significa «como mucho dos 2x1 en el carrito», no «dos unidades en total». Ver también ejemplo 2x1 con tope de grupos.
limits | En el ejemplo 2x1 |
|---|---|
0 | Sin tope: todos los grupos posibles en el carrito reciben beneficio (8 uds. → 4 grupos → 4 bonificaciones) |
1 | Solo 1 grupo recibe el efecto en la transacción (1 producto “gratis”, aunque haya más grupos posibles) |
2 | Máximo 2 grupos (dos 2x1); con 8 uds. en ticket solo se bonifican 2 de los 4 grupos posibles |
**limits** cuenta aplicaciones del efecto por transacción (grupos bonificados conrule_group, o líneas conindividual_itemssin grupo), no unidades totales en el carrito.
Resumen visual
Ticket: 8 × producto 2222
│
rule_conditions (qty ≥ 2) → 4 grupos de 2
│
rule_application (qty ≥ 1) → 1 unidad con dto. por grupo
│
rule_effects (100%, rule_group) + limits
│
limits: 0 → 4 bonificaciones (4 grupos)
limits: 2 → 2 bonificaciones (solo 2 de 4 grupos)
Variantes habituales
| Mecánica | Condiciones (grupo) | Aplicación (beneficio) | Efecto típico |
|---|---|---|---|
| 2x1 mismo producto | quantity gte 2 en producto | quantity gte 1 en mismo producto | percentage: 100 + rule_group: true |
| 3x2 en categoría | quantity gte 3 en categoría | quantity gte 1 en categoría | percentage: 100 + rule_group: true |
| Bundle A + B + C productos por €10 | quantity gte 1 en A + quantity gte 1 en B + quantity gte 1 en C | filtro de líneas del bundle | combo_price + rule_group: true |
| € fijo por grupo | quantity gte N | quantity gte 1 | direct_value + rule_group: true |
Errores frecuentes — bundle
| Síntoma | Causa habitual |
|---|---|
| 2x1 no funciona | rule_group: false o falta subcondición quantity en condiciones o aplicación |
| Bonifica de más | limits: 0 con muchos grupos en ticket; subir limits para capar grupos bonificados |
| Bonifica de menos | limits bajo (ej. 1) con muchas unidades; o condiciones exigen más unidades que las del carrito |
| Producto distinto en aplicación | values / field en aplicación no coinciden con condiciones de victoria |
Confundir flags bundle/grouped | No activan la mecánica solos; hace falta **rule_group: true** y las **quantity** correctas |
Configuración vs respuesta de carrito
| Contexto | Campo | Qué contiene |
|---|---|---|
| Campaña en GET | rule_effects (a menudo string JSON) | Configuración: type, value, distribution, limits, … |
| Ticket / compra evaluada | effects[] | Resultado runtime por línea o bloque |
Campos típicos en **effects[]** del carrito:
| Campo en ticket | Relación con config |
|---|---|
effectType | Corresponde a rule_effects.type (percentage, direct_value, …) |
distribution_type | Corresponde a rule_effects.distribution |
discount_value | Importe ya calculado aplicado en esa línea o bloque |
rule_id | ID de la campaña que generó el efecto |
Ejemplo (descuento % en línea):
{
"effects": [{
"rule_id": 101,
"line": 1,
"type": "discount",
"effectType": "percentage",
"distribution_type": "individual_items",
"discount_value": 1.50
}]
}
Integración: si el carrito no refleja el descuento del GET, comparar rule_effects con effects[] de la respuesta; si effects[] está vacío, revisar elegibilidad (rule_conditions, audiencia, cupón) o rule_application, no solo el tipo de efecto.
Errores frecuentes — rule_effects
| Confusión | Cómo resolverlo |
|---|---|
limits: 0 «desactiva límites» | Correcto: sin tope de aplicaciones del efecto en la transacción |
Una línea con quantity: 20 y limits: 10 | El motor expande a 20 unidades; solo 10 reciben el efecto; la respuesta puede seguir siendo una línea con el descuento agregado — ver expansión unitaria |
limits vs límites de victoria | rule_effects.limits = repeticiones del efecto; once / num_* = cuántas veces puede ganar la campaña el cliente |
| Bundle / 2x1 | Trío: quantity en rule_conditions (tamaño grupo) + quantity en rule_application (uds. con dto.) + rule_group: true + limits |
strikethrough «no descontó lo esperado» | Clave del mapa (product_id vs product_code) debe coincidir con el identificador de la línea en el carrito; dto. = precio ticket − precio promo |
combo_price con individual_items | Inválido en producto; usar total o individual_items_pro_rata |
formula sin promoPrice en línea | Integración incompleta; dto. 0 en esa línea |
| % o ratio sin aplicación en carrito | Revisar rule_application — suele ser obligatoria para % por línea |
| Mismo % en config y distinto en ticket | Revisar stackability (Highest Discount ganó otra campaña) y effects[] |
Volver: Lectura desde GET · Checklist