> ## Documentation Index
> Fetch the complete documentation index at: https://internal.softcrum.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Loyalty — modelo de datos

> Las 37 tablas del schema loyalty, cada una con su forma, su objetivo y el feature spec que la crea. Sin excepciones: los catálogos y las tablas intermedias también están.

Schema `loyalty`. Depende de `core`; **nunca** referencia tablas de `messaging` ni de `crm`
(`standards/data.md` §1). Toda tabla lleva `program_id` NOT NULL desde la primera migración
([ADR-021](/adr/adr-021-multi-program-from-day-one)).

La columna **Forma** es la de [ADR-024](/adr/adr-024-table-naming-and-base-structure) y determina
las columnas base: **A** dominio con alcance de tenant · **B** append-only · **C** catálogo
paramétrico. La definición completa de cada tabla —tipos, restricciones, índices y su registro de
cambios— vive en [Schemas](/schemas/overview).

## Programa y monedas

| Tabla                                                         | Forma | De qué es dueña                                                                                                                     | Spec        |
| ------------------------------------------------------------- | ----- | ----------------------------------------------------------------------------------------------------------------------------------- | ----------- |
| [`program`](/schemas/loyalty/program)                         | A     | El contenedor de todo lo demás. Nombre, ventana de devolución y ventana de reversa de canje. Exactamente uno por defecto por tenant | FS-LOY-0001 |
| [`point_currency`](/schemas/loyalty/point-currency)           | A     | Las monedas de un programa y su política de expiración. Al menos dos: canjeable y estatus                                           | FS-LOY-0001 |
| [`point_currency_kind`](/schemas/loyalty/point-currency-kind) | C     | Los dos tipos de moneda: `redeemable` y `status`. No extensible por el tenant — el tipo gobierna el ledger                          | FS-LOY-0001 |

## El libro de puntos

| Tabla                      | Forma | De qué es dueña                                                                                                                                                       | Spec        |
| -------------------------- | ----- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- |
| `ledger_transaction`       | **B** | El camino del dinero. Toda acumulación, canje, expiración, revocación y ajuste, append-only. Candidata a partición mensual                                            | FS-LOY-0002 |
| `ledger_transaction_type`  | C     | `earn`, `redeem`, `expire`, `revoke`, `adjust`. `revoke` y `adjust` son distintos a propósito: uno dice "esto nunca debió acreditarse", el otro "corregimos un monto" | FS-LOY-0002 |
| `ledger_transaction_state` | C     | `pending`, `available`, `consumed`, `cancelled`. La transición `pending → available` es la única actualización permitida sobre una fila del ledger                    | FS-LOY-0002 |
| `point_lot`                | A     | Los lotes de puntos con su vencimiento. El consumo es FIFO por `expires_at`, que es la única política que se explica a un cliente en una frase                        | FS-LOY-0002 |
| `contact_balance`          | A     | La proyección del saldo, escrita en la **misma transacción** que el ledger. Se puede reconstruir desde el ledger, y la reconciliación nocturna detecta el drift       | FS-LOY-0003 |

## Motor de reglas

| Tabla              | Forma | De qué es dueña                                                                                                                                          | Spec        |
| ------------------ | ----- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- |
| `rule_campaign`    | A     | La campaña que agrupa reglas y les pone vigencia y presupuesto                                                                                           | FS-LOY-0004 |
| `rule`             | A     | La regla versionada: AST de condiciones en JSON más efectos. Publicar invalida la caché de reglas compiladas                                             | FS-LOY-0004 |
| `rule_effect_type` | C     | Qué puede hacer una regla: `award_points`, `issue_coupon`, `send_webhook`, `trigger_campaign`. `apply_discount` entra en F2                              | FS-LOY-0004 |
| `rule_execution`   | **B** | Qué regla se disparó para qué evento. Es lo que permite deduplicar efectos por `(evento, regla)` y explicarle a un tenant por qué alguien recibió puntos | FS-LOY-0004 |
| `campaign_budget`  | A     | El presupuesto consumido por campaña y por contacto. Separado de `rule_campaign` porque se actualiza en cada acumulación y la campaña casi nunca         | FS-LOY-0013 |

## Catálogo y canjes

| Tabla              | Forma | De qué es dueña                                                                                                                                           | Spec        |
| ------------------ | ----- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- |
| `reward`           | A     | El catálogo de recompensas del programa: costo en puntos, límite por contacto y su ventana                                                                | FS-LOY-0005 |
| `reward_type`      | C     | Taxonomía de recompensa. **Sí es extensible por el tenant**: es vocabulario de su negocio, no lógica del sistema                                          | FS-LOY-0005 |
| `redemption`       | A     | El canje, con estructura padre/hijo desde el día 1 para pedidos multi-recompensa. `money_component` reserva la costura de puntos + dinero, sin usar en F1 | FS-LOY-0006 |
| `redemption_state` | C     | Estados del canje. No extensible: gobierna una máquina de estados                                                                                         | FS-LOY-0006 |

## Cupones

| Tabla             | Forma | De qué es dueña                                                                                                              | Spec        |
| ----------------- | ----- | ---------------------------------------------------------------------------------------------------------------------------- | ----------- |
| `coupon_campaign` | A     | La campaña de cupones y su modo: códigos únicos masivos o un código genérico compartido                                      | FS-LOY-0007 |
| `coupon_code`     | A     | El código individual y su estado. 10 caracteres sobre Crockford Base32 — se dicta por teléfono sin ambigüedad y da \~50 bits | FS-LOY-0007 |
| `coupon_state`    | C     | `issued`, `redeemed`, `expired`, `void`. No extensible                                                                       | FS-LOY-0007 |

## Combinabilidad de beneficios

| Tabla                   | Forma | De qué es dueña                                                                                                                                                               | Spec        |
| ----------------------- | ----- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- |
| `stacking_rule`         | A     | Qué beneficios se pueden combinar y cuáles se excluyen, con su precedencia. Al violar un conjunto no combinable se conserva el de mayor precedencia y se reporta la exclusión | FS-LOY-0008 |
| `discount_category`     | C     | Las categorías sobre las que operan las reglas de combinabilidad                                                                                                              | FS-LOY-0008 |
| `qualification_session` | **B** | La evaluación de qué beneficios aplican a un carrito concreto. Append-only porque es evidencia: explica una decisión que ya se tomó en un mostrador                           | FS-LOY-0013 |

## Referidos

| Tabla                 | Forma | De qué es dueña                                                                                                                                                                                                                         | Spec        |
| --------------------- | ----- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- |
| `referral_code`       | A     | El código personal de cada member que refiere                                                                                                                                                                                           | FS-LOY-0009 |
| `referral_conversion` | A     | La conversión y su estado. La recompensa doble se libera con el evento calificante —primera compra **pagada**—, nunca con el registro                                                                                                   | FS-LOY-0009 |
| `referral_fraud_flag` | A     | Las marcas de sospecha: auto-referido, velocidad, heurísticas de dispositivo e IP. **No expiran, escalan**: a los 14 días sin revisar alertan al tenant, porque expirarlas sería perder referidos legítimos por inacción administrativa | FS-LOY-0009 |

## Niveles

| Tabla                       | Forma | De qué es dueña                                                                                                             | Spec        |
| --------------------------- | ----- | --------------------------------------------------------------------------------------------------------------------------- | ----------- |
| `tier_definition`           | A     | La escalera de niveles: umbrales, métrica de calificación y ventana                                                         | FS-LOY-0010 |
| `tier_membership`           | A     | En qué nivel está cada member, desde cuándo, y si está en período de gracia                                                 | FS-LOY-0010 |
| `tier_override`             | A     | Lo que un nivel cambia: multiplicadores de acumulación, precios de recompensa, expiración. Schema desde el día 1, UI en F1b | FS-LOY-0010 |
| `tier_qualification_metric` | C     | `points_earned`, `spend`, `event_count`. No extensible: cada métrica exige código que la calcule                            | FS-LOY-0010 |

## Vencimientos

| Tabla             | Forma | De qué es dueña                                                                                                                                                               | Spec        |
| ----------------- | ----- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- |
| `expiry_scan_run` | **B** | Cada corrida del escaneo de vencimientos: qué encontró, cuánto expiró y a quién se avisó. Es lo que permite responder "¿por qué este member perdió sus puntos?" meses después | FS-LOY-0011 |

## Gamificación (F2)

| Tabla                | Forma | De qué es dueña                                                                                                          | Spec        |
| -------------------- | ----- | ------------------------------------------------------------------------------------------------------------------------ | ----------- |
| `badge`              | A     | La definición de una insignia y su condición                                                                             | FS-LOY-0012 |
| `badge_award`        | **B** | Qué insignia ganó quién y cuándo. Append-only: una insignia otorgada no se quita                                         | FS-LOY-0012 |
| `challenge`          | A     | El desafío con su meta y su ventana                                                                                      | FS-LOY-0012 |
| `challenge_progress` | A     | El avance de cada member. Es de las pocas proyecciones que se actualiza en caliente, y por eso vive separada del desafío | FS-LOY-0012 |

## Wallet passes (F1b)

| Tabla                      | Forma | De qué es dueña                                                                                                                                                                          | Spec        |
| -------------------------- | ----- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- |
| `wallet_pass`              | A     | El pase emitido a un member, con su branding por tenant. Va sobre **nuestro** certificado de proveedor: un pase no tiene ficha en la tienda, así que la directriz 4.3 de Apple no aplica | FS-LOY-0014 |
| `wallet_pass_registration` | A     | El dispositivo registrado para recibir actualizaciones push del pase. Un member puede tener el mismo pase en varios dispositivos                                                         | FS-LOY-0014 |

## Lo que este schema **no** tiene

* **Ninguna tabla de contacto.** El contacto es de `core` (ADR-009). `loyalty` guarda `contact_id`
  y nada más sobre la persona.
* **Ninguna tabla de envío.** Avisar de un vencimiento es un evento que `messaging` consume.
* **Ninguna columna de saldo mutable.** Está prohibida en todo el módulo: el saldo es una proyección
  reconstruible, y `contact_balance` documenta su procedimiento de reconstrucción.
* **Ningún monto de dinero mezclado con puntos.** `points_amount` es INTEGER contra una moneda de
  puntos; el dinero es `amount` BIGINT con su `currency_code` (`standards/data.md` §3).
