> ## 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.

# Referidos — recompensa doble y antifraude

> Cuando esto se entregue, un member podrá invitar a alguien y ambos recibirán recompensa — cuando la invitación se convierta en un cliente real, no antes.

> Traducción. Autoritativo: [`fs-loy-0009-referrals.md`](/modules/loyalty/features/fs-loy-0009-referrals).

## Contexto

Los referidos son el mecanismo de adquisición de mayor palanca que tiene un programa de fidelización,
y el más fácil de explotar. Toda decisión de diseño aquí es en realidad una decisión antifraude.

La que más importa: **la recompensa se libera por un evento calificante, no por el registro**. Si
registrarse paga, el programa paga por cuentas falsas, y lo hará — de forma confiable, a los pocos
días del lanzamiento. Atar la liberación a una primera compra *pagada* obliga al defraudador a gastar
dinero real para extraer una recompensa que vale menos de lo que gastó.

DEC-H7 también condiciona el evento calificante: se configura por tenant desde su taxonomía
instalada, porque "se convirtió en cliente real" significa una factura pagada en un negocio de
suscripción y una primera orden en retail.

## Alcance *(normativo)*

* `loyalty.referral_codes`: un código durable por member por programa, con enlace compartible.
* `loyalty.referral_conversions`: el contacto referido, su estado y el evento calificante.
* Recompensa doble: montos configurables para referidor y referido, liberados al calificar.
* Antifraude: detección de auto-referido, límites de velocidad y heurísticas de dispositivo/IP
  registradas como flags.
* `loyalty.referral_fraud_flags` para revisión, en vez de rechazo silencioso.
* Ventana de atribución entre el clic y la calificación.

## Fuera de alcance *(normativo)*

* Estructuras multinivel o piramidales. Explícitamente nunca: un referidor gana solo de sus propios
  referidos. Es una decisión de producto, no una fase.
* Enviar la invitación. El enlace se produce aquí; entregarlo es `messaging`.
* Landing pages y la UI para compartir, que pertenecen a `frontend/portal` y al widget.

## Comportamiento *(normativo)*

1. El código de referido de un member es **estable durante toda la vida del programa**. Regenerarlo
   rompería todos los enlaces ya compartidos.
2. Una conversión pasa `pending → qualified → rewarded`, o `pending → rejected`. Las recompensas se
   liberan **solo** con el evento calificante configurado por el tenant, que por defecto es una
   primera compra pagada y nunca un registro.
3. El auto-referido se bloquea al convertir, cruzando las señales de identidad que `core` ya
   resuelve — nunca por igualdad de strings de email, que se burla trivialmente.
4. Los límites de velocidad son por referidor y por ventana, configurables. Excederlos **marca para
   revisión; no rechaza en silencio.** Un falso positivo que se traga un referido legítimo es peor
   que uno que lo encola para una persona.
5. Las heurísticas de dispositivo e IP se registran como flags con su evidencia, nunca como veredicto
   automático. Una IP compartida en un hogar es normal.
6. Ambas recompensas se liberan en **una transacción**. Premiar solo a un lado está PROHIBIDO.
7. La ventana de atribución es configurable por programa. Una conversión fuera de ella se registra y
   se rechaza con esa razón, no se descarta.
8. Una conversión rechazada o marcada nunca se borra: la evidencia es justamente el punto.

## Datos *(normativo)*

| Tabla                          | Invariantes clave                                                                                                                                                     |
| ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `loyalty.referral_codes`       | único por (programa, contacto); `code` único por tenant; inmutable una vez emitido                                                                                    |
| `loyalty.referral_conversions` | FK código; `referred_contact_id`; `state_code`; `qualifying_event_id` nullable; `attributed_at`, `qualified_at`, `rewarded_at`; único por (código, contacto referido) |
| `loyalty.referral_fraud_flags` | FK conversión; `flag_code` paramétrico; evidencia JSONB; `reviewed_by`, `reviewed_at`, `resolution`; append-only                                                      |

## API *(normativo)*

| Endpoint                                 | Clase      | Permiso                       | Presupuesto  |
| ---------------------------------------- | ---------- | ----------------------------- | ------------ |
| `GET /v1/loyalty/members/me/referral`    | Runtime    | token de member               | p95 \<150 ms |
| `POST /v1/loyalty/referrals/attribute`   | Runtime    | `loyalty.referrals.attribute` | p95 \<300 ms |
| `GET /v1/loyalty/referrals`              | Management | `loyalty.referrals.read`      | p95 \<1 s    |
| `POST /v1/loyalty/referrals/{id}/review` | Management | `loyalty.referrals.review`    | p95 \<1 s    |

## Eventos *(normativo)*

`loyalty.referral.link_created` y `loyalty.referral.converted`, ambos disponibles como webhooks
salientes. La liberación de la recompensa emite `loyalty.points.earned` vía el servicio del libro.

## Criterios de aceptación *(normativo)*

1. Un registro sin evento calificante libera **cero** recompensa, y la conversión queda en `pending`.
2. El evento calificante libera ambas recompensas en una transacción; una falla inducida en la
   segunda revierte la primera.
3. El auto-referido con identidad resuelta coincidente se bloquea incluso cuando los strings de email
   difieren.
4. Exceder el límite de velocidad marca para revisión y deja la conversión en `pending` — no se
   rechaza ni se descarta en silencio.
5. Una conversión que llega después de la ventana de atribución se registra con razón
   `WINDOW_EXPIRED`.
6. Reprocesar el evento calificante no libera una segunda recompensa.
7. **Negativo:** ninguna configuración permite que un referidor gane por el referido de un referido.

## Ejecución

Pipeline asíncrono: la atribución es síncrona y barata, la calificación la impulsa el motor de reglas
consumiendo el evento calificante. Las heurísticas de fraude corren en el worker, nunca en el camino
caliente.

## Preguntas abiertas

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

## Changelog

| Versión | Fecha      | Cambio                                                                             | Por qué                                    | Autor                  |
| ------- | ---------- | ---------------------------------------------------------------------------------- | ------------------------------------------ | ---------------------- |
| 0.2.0   | 2026-08-17 | Preguntas abiertas resueltas (OQ-LOY-\*) e incorporadas a las secciones normativas | El owner respondió el registro consolidado | daniel + claude-opus-5 |
| 0.1.0   | 2026-08-17 | Borrador inicial                                                                   | —                                          | daniel + claude-opus-5 |

## Registro de entrega

*Aún no implementado.*
