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

# Supresiones y centro de preferencias

> Cuando esto se entregue, un clic detiene los mensajes que un member no quiere, y una dirección que rebotó nunca recibe un segundo correo.

> Traducción. Autoritativo: [`fs-msg-0004-suppressions-preferences.md`](/modules/messaging/features/fs-msg-0004-suppressions-preferences).

## Contexto

Dos mecanismos que se parecen y no lo son, y confundirlos es un bug real.

El **consentimiento** es lo que la persona eligió, y vive en `core`. La **supresión** es lo que el
sistema o el proveedor impusieron: un rebote duro, una queja de spam, un clic de baja. Una persona
puede consentir y estar suprimida —la dirección simplemente no funciona— y una dirección suprimida
debe seguir suprimida incluso para transaccionales, porque un rebote no se vuelve entregable porque
el mensaje sea importante.

El centro de preferencias es la tercera capa. Que la baja esté a un clic de cualquier mensaje de
marketing no es cortesía: una baja difícil de encontrar genera quejas de spam, y las quejas dañan la
reputación de envío compartida de todos los tenants.

## Alcance *(normativo)*

* `messaging.suppressions`: global por tenant, por identificador de canal.
* Supresión automática ante rebote duro, queja y baja.
* `messaging.preference_settings`: por member, por canal, por categoría.
* Baja en un clic desde cualquier mensaje de marketing, incluida la cabecera list-unsubscribe.
* Configuración de quiet hours y frequency caps a nivel de tenant y de campaña.
* Supervivencia de la supresión a la supresión de datos, como identificador hasheado.

## Fuera de alcance *(normativo)*

* El consentimiento, que es de `core` y acá se lee.
* Importación manual de supresiones desde un proveedor anterior — una feature posterior.
* Reactivación de direcciones suprimidas. Una supresión se levanta solo si el member actúa.

## Comportamiento *(normativo)*

1. Una supresión se llavea por **(tenant, canal, identificador)** — la dirección o el token de push, no
   el contacto. Si la misma dirección pertenece a dos contactos, ambos quedan suprimidos, porque lo
   que rebotó es la dirección.
2. **Todo envío verifica supresiones, sin excepción**, incluido transaccional (regla 3 de FS-MSG-0001).
3. Los rebotes duros y las quejas suprimen **automáticamente**, en la misma transacción que el estado
   (FS-MSG-0003). Los rebotes suaves no: una casilla llena es temporal.
4. La baja es de **un clic, sin login**, desde un enlace firmado que identifica al member sin
   autenticarlo. Exigir login para darse de baja genera quejas.
5. Las cabeceras `List-Unsubscribe` y `List-Unsubscribe-Post` van en todo mensaje de marketing, para
   que el botón del propio cliente de correo funcione.
6. Una supresión se levanta **solo si el member se resuscribe**, nunca por el tenant. Un tenant
   quitando la supresión de una dirección que se quejó es exactamente la conducta que hace que
   bloqueen un dominio.
7. El centro de preferencias es **por canal y por categoría**. Transaccional aparece como informativo
   y no se puede apagar (DEC-E3).
8. Quiet hours por defecto a nivel de tenant con override por campaña; frequency caps solo a marketing
   (DEC-E5).
9. La supresión de datos borra el perfil pero la supresión de envío **sobrevive como identificador
   hasheado** sin perfil asociado (DEC-J3).
10. PROHIBIDO: un endpoint de cara al tenant que borre una supresión · un mensaje de marketing sin
    baja funcional · contar transaccionales contra un frequency cap.

## Datos *(normativo)*

| Tabla                           | Invariantes clave                                                                                                                                                               |
| ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `messaging.suppressions`        | único (`tenant_id`, `channel_code`, `identifier_hash`); `reason` bounce\|complaint\|unsubscribe\|manual; `source`; `created_at`; append-only, sobrevive a la supresión de datos |
| `messaging.preference_settings` | (`tenant_id`, `contact_id`, `channel_code`, `category`); `is_enabled`; `updated_at`; las filas transaccionales son de solo lectura                                              |

El identificador se guarda hasheado para que una lista de supresiones no sea una lista de contactos
utilizable.

## API *(normativo)*

| Endpoint                                       | Clase      | Permiso                         | Presupuesto  |
| ---------------------------------------------- | ---------- | ------------------------------- | ------------ |
| `GET /v1/messaging/suppressions`               | Management | `messaging.suppressions.read`   | p95 \<1 s    |
| `POST /v1/messaging/suppressions`              | Management | `messaging.suppressions.create` | p95 \<1 s    |
| `GET /unsubscribe/{token}`                     | Runtime    | token firmado, sin login        | p95 \<300 ms |
| `POST /unsubscribe/{token}`                    | Runtime    | token firmado, sin login        | p95 \<300 ms |
| `GET/PUT /v1/messaging/members/me/preferences` | Runtime    | token de member                 | p95 \<300 ms |

La ruta de baja vive en la raíz, fuera de `/v1`, porque es un enlace en un correo que debe ser corto,
estable y legible. Excepción documentada a DEC-D1, como las rutas de OAuth.

## Eventos *(normativo)*

Ninguno más allá de los eventos de estado de FS-MSG-0003.

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

1. Una dirección suprimida no recibe nada, incluido transaccional e incluido otro contacto que
   comparta esa dirección.
2. Un rebote duro suprime en la misma transacción; uno suave no.
3. La baja funciona desde el enlace firmado sin login, en un request.
4. Las cabeceras `List-Unsubscribe` están en todo mensaje de marketing y funcionan en un cliente real.
5. Ningún endpoint de cara al tenant quita una supresión.
6. El centro de preferencias no puede apagar transaccional.
7. Los frequency caps cuentan solo marketing.
8. Tras la supresión de datos, la supresión de envío sigue bloqueando esa dirección, sin perfil.
9. **Negativo:** la lista de supresiones no se puede exportar como datos de contacto utilizables.

## Ejecución

Un solo slice, comando síncrono. La ruta de baja es pública y firmada; las lecturas de preferencias
se sirven desde la misma caché que la configuración de la cascada.

## Preguntas abiertas

| # | Pregunta                                                                            | Decide | Para             |
| - | ----------------------------------------------------------------------------------- | ------ | ---------------- |
| 1 | Frequency cap de marketing por defecto — ¿cuántos por semana antes de ser molestia? | Daniel | antes de aprobar |
| 2 | ¿Un member puede resuscribirse tras una queja, o solo tras un rebote?               | Daniel | antes de aprobar |

## Changelog

| Versión | Fecha      | Cambio           | Por qué | Autor                  |
| ------- | ---------- | ---------------- | ------- | ---------------------- |
| 0.1.0   | 2026-08-17 | Borrador inicial | —       | daniel + claude-opus-5 |

## Registro de entrega

*Aún no implementado.*
