Skip to main content
Traducción. Autoritativo: fs-loy-0011-expiring-points.md.

Contexto

La expiración es el mecanismo que mantiene acotado el pasivo y le da urgencia a los puntos. La expiración silenciosa es la versión que genera reclamos y atención regulatoria; la expiración avisada es una de las campañas de retención de mejor rendimiento que tiene un programa. La feature es aparte del libro (FS-LOY-0002) porque los modos de falla no tienen relación. El libro debe expirar puntos correctamente; esta feature debe encontrar a los members que están por perderlos y avisarles, a una escala donde un escaneo ingenuo sobre todo el libro cada noche deja de funcionar.

Alcance (normativo)

  • Escaneo programado de lotes que vencen dentro de un horizonte configurable.
  • loyalty.points.expiring_soon emitido una vez por member por cohorte de lotes por horizonte.
  • Configuración de horizontes por programa, con varios horizontes permitidos (30 y 7 días, digamos).
  • loyalty.expiry_scan_runs registrando cada corrida, su cohorte y su resultado.
  • Fan-out por tenant para que un tenant grande no deje sin recursos al resto.
  • Idempotencia que garantiza que a un member se le avisa una vez por cohorte, pase lo que pase con el job.

Fuera de alcance (normativo)

  • Expirar los puntos. Eso lo hace el libro (FS-LOY-0002); esta feature solo avisa.
  • La campaña y su contenido — messaging consume el evento.
  • La visualización de vencimientos en el portal, que lee la proyección.

Comportamiento (normativo)

  1. El escaneo corre por tenant y por programa, con fan-out por cron. Nunca como una consulta global.
  2. A un member se le avisa una vez por (cohorte de lotes, horizonte). Reejecutar el escaneo el mismo día, o reprocesar el job, no produce un segundo evento.
  3. Los horizontes son independientes: a un member se le puede avisar a 30 días y de nuevo a 7.
  4. El evento lleva el total por vencer, la moneda, la fecha de vencimiento y el id de cohorte — suficiente para que messaging renderice sin volver a consultar.
  5. Los avisos son de categoría marketing y por lo tanto obedecen consentimiento, supresiones, quiet hours y frequency caps (ADR-019). PROHIBIDO: enviar un aviso de vencimiento como transaccional para saltarse un opt-out. Es un mensaje de retención, y el member dijo que no.
  6. La frecuencia del escaneo sigue el tier de frescura del tenant (DEC-G5); el default es nocturno.
  7. Un member sin canal alcanzable igualmente queda registrado como escaneado. El escaneo reporta lo que encontró, con independencia de la entrega.
  8. El escaneo es de solo lectura contra el libro. Nunca muta lotes.

Datos (normativo)

API (normativo)

Eventos (normativo)

loyalty.points.expiring_soon, disponible como webhook saliente. Payload: contact_id, points_amount, point_currency_id, expires_at, cohort_key, correlation_id.

Criterios de aceptación (normativo)

  1. Un member con un lote que vence en 30 días recibe aviso exactamente una vez cuando corre el horizonte de 30 días.
  2. Reejecutar el mismo escaneo el mismo día emite cero eventos adicionales.
  3. Dos horizontes producen dos eventos en sus días respectivos, y ninguno suprime al otro.
  4. El escaneo sobre 500 000 lotes termina dentro del presupuesto documentado sin exceder el presupuesto de consultas por tenant.
  5. Un member que optó por salir de marketing no recibe mensaje, mientras el evento igual se emite y el escaneo igual lo registra.
  6. Reprocesar el job tras una caída a media corrida se reanuda sin duplicar avisos.
  7. Negativo: el escaneo no realiza ninguna escritura contra point_lots ni ledger_transactions — probado con un rol de base de datos de solo lectura en el test.

Ejecución

Pipeline asíncrono. Fan-out programado en backend/scheduler, el escaneo en backend/workers, idempotente vía core.processed_jobs más la valla de expiry_scan_runs.

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.