Skip to main content
Traducción. Autoritativo: fs-loy-0008-stacking-rules.md.

Contexto

En cuanto un programa tiene más de un instrumento —puntos, cupones, descuento por nivel— aparece la pregunta “¿estos se pueden usar juntos?”, y es una pregunta de margen. Sin una política explícita la respuesta se decide ad hoc en el mostrador, que es como los programas pierden dinero en silencio. Esto deliberadamente no es parte de cupones ni de canjes. Es una capa de política sobre ambos, y forzarla dentro de cualquiera de los dos produce un spec que no se puede aprobar porque la mitad de su materia vive en otro lado. Mantenerlo separado es también lo que permite que F1a salga sin esto: hasta que un tenant tenga dos instrumentos combinables, la política es trivialmente “sí”.

Alcance (normativo)

  • loyalty.discount_categories: paramétrica, extensible por tenant, con orden de precedencia.
  • loyalty.stacking_rules: política ALL o PARTIAL por programa, más conjuntos no combinables.
  • Evaluación dentro de la validación del canje: dado un conjunto de beneficios pretendidos, devolver el subconjunto permitido y la razón de cada exclusión.
  • Precedencia determinista cuando dos beneficios entran en conflicto.

Fuera de alcance (normativo)

  • Calcular el precio resultante. Eso es apply_discount, F2.
  • Acumulación entre tenants o entre programas. Un beneficio nunca se acumula entre programas.
  • Optimización automática a favor del member. El motor aplica la política; no busca la combinación que más le convendría al cliente. Esa es una pregunta deliberada de F2 en adelante.

Comportamiento (normativo)

  1. La política por defecto de un programa nuevo es PARTIAL sin conjuntos no combinables: todo se acumula hasta que el tenant diga lo contrario. Se elige el default permisivo porque el restrictivo rompe en silencio los programas existentes al actualizar.
  2. La evaluación es una función pura sobre los beneficios pretendidos y la política del programa. Sin I/O, totalmente testeable unitariamente, determinista para una entrada dada.
  3. La precedencia es explícita y total: las categorías llevan sort_order y los empates se rompen por código de categoría. No existe escenario donde el resultado dependa del orden del request.
  4. Toda exclusión devuelve una razón tipada. “No permitido” sin razón es inusable en un mostrador e inusable en soporte.
  5. La evaluación corre dentro de la validación del canje, antes de cualquier escritura. PROHIBIDO: aplicar beneficios y después revertir los que no debieron acumularse.
  6. discount_categories es extensible por tenant — es su vocabulario comercial. La política de acumulación en sí no lo es: ALL y PARTIAL son los únicos modos.

Datos (normativo)

API (normativo)

Eventos (normativo)

Ninguno. Los cambios de política son administrativos y quedan cubiertos por el registro de auditoría.

Criterios de aceptación (normativo)

  1. Un programa nuevo acumula todo por defecto, verificado sobre un tenant recién aprovisionado.
  2. El modo ALL permite toda combinación; PARTIAL con un conjunto no combinable {tier_discount, coupon} rechaza exactamente ese par y permite todos los demás.
  3. La evaluación es independiente del orden: el mismo conjunto de beneficios en seis órdenes distintos arroja el mismo subconjunto permitido.
  4. Toda exclusión lleva una razón tipada que nombra la regla que la excluyó.
  5. 100% de cobertura de ramas sobre la función pura de evaluación.
  6. Negativo: ningún canje aplica un beneficio que la evaluación excluyó — probado por un test que lo intenta directamente contra el servicio.

Ejecución

Un solo slice, comando síncrono. El evaluador vive en packages/core junto a resolveChannels — ambas son funciones puras de política con la misma disciplina de testing.

Preguntas abiertas

Ninguna pendiente. Toda pregunta que llevaba este spec quedó respondida en el registro consolidado (../../../../design/open-questions-v1.md, v1.1) y se incorporó a las secciones normativas de arriba.

Changelog

Registro de entrega

Aún no implementado.