> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Centro de preferencias: página pública de inclusión/exclusión

> Configure el centro de preferencias alojado y firmado por token — marca, canales, opciones de frecuencia y el conmutador de eliminación GDPR — cree un enlace por contacto y sepa exactamente qué superficies de cumplimiento escribe una exclusión (consentimiento, supresión, valla STOP, auditoría).

# Centro de preferencias: página pública de inclusión/exclusión

El **centro de preferencias** es una página pública donde un contacto gestiona sus propias inclusiones por canal, temas de suscripción, frecuencia de mensajes y (si lo habilita) presenta una solicitud de eliminación de datos — sin cuenta, sin inicio de sesión. Cada contacto llega a ella a través de un **enlace firmado**: la URL lleva un token HMAC-SHA256 (`v1.<payload>.<signature>`) que expira a los 30 días, por lo que la página sigue siendo de autoservicio pero se limita a un solo contacto en una sola organización.

La superficie resumida de puntos de acceso también vive en [Barreras de envío](/compliance/send-gates#preference-center); esta guía es el recorrido completo: cada campo de configuración, dónde colocar el enlace, qué devuelve la API de la página pública y qué superficies de cumplimiento escribe una exclusión o inclusión.

Todos los puntos de acceso siguientes tienen raíz en `https://api.orbit.devotel.io/api/v1/compliance`.

> Original en inglés: [Preference center: the public opt-in/opt-out page](/guides/preference-center-opt-out-page).

<Note>
  El centro de preferencias es un **control perteneciente al inquilino**: usted elige los canales, temas y la marca, y su organización guarda la evidencia del consentimiento. Orbit opera la plataforma; la decisión de consentimiento pertenece al contacto. Esta guía no es asesoramiento legal — confirme sus obligaciones con su asesor jurídico.
</Note>

***

## 1. Configurar una vez: POST/GET /preference-center

Establezca la configuración con `POST /preference-center` (clave API propietario/administrador). El punto de acceso hace upsert de la configuración en los ajustes de su organización y devuelve el objeto guardado — ejecútelo de nuevo para actualizar. `GET /preference-center` lee la configuración actual; antes de la configuración, devuelve `enabled: false` con un mensaje de sugerencia.

### Campos de configuración

Cada campo se valida del lado del servidor — un POST rechazado devuelve un `422` con problemas por campo (`field`, `message`) para que pueda identificar cuál falló.

| Campo                  | Tipo            | Por defecto                                      | Qué controla                                                                                                      |
| ---------------------- | --------------- | ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------- |
| `enabled`              | boolean         | `true`                                           | Interruptor principal. Cuando `false`, la página pública devuelve «no disponible» a los contactos.                |
| `companyName`          | string (1–200)  | **requerido**                                    | Nombre de la empresa mostrado en la página alojada.                                                               |
| `logoUrl`              | string (URL)    | —                                                | Logo recogido por la página. Las URL están limitadas a `http://` o `https://`.                                    |
| `primaryColor`         | hex `#rrggbb`   | `#2563eb`                                        | Color de énfasis de la interfaz de la página.                                                                     |
| `headerText`           | string (≤500)   | `"Communication Preferences"`                    | Título de la página.                                                                                              |
| `footerText`           | string (≤1000)  | `"Respetamos sus preferencias de comunicación."` | Texto del pie de página.                                                                                          |
| `optOutMessage`        | string (≤500)   | `"Administrar sus preferencias"`                 | Etiqueta de pie de página usada al adjuntar el enlace a mensajes salientes automáticamente.                       |
| `channels`             | enum array (≥1) | `["sms","email"]`                                | Canales ofrecidos en la página. Valores permitidos: `sms`, `whatsapp`, `email`, `rcs`, `viber`, `voice`.          |
| `showFrequencyOptions` | boolean         | `true`                                           | Mostrar el selector de frecuencia (`all`, `important_only`, `weekly_digest`, `monthly_digest`).                   |
| `showGdprDelete`       | boolean         | `true`                                           | Mostrar el conmutador de eliminación de datos (ver sección 6).                                                    |
| `customCss`            | string (≤10000) | —                                                | CSS adicional inyectado en la página alojada.                                                                     |
| `redirectUrl`          | string (URL)    | —                                                | A dónde se envía al contacto después de completar una exclusión. Solo esquemas `http(s)`.                         |
| `topics`               | array (≤50)     | `[]`                                             | Grupos de suscripción que un contacto activa o desactiva independientemente del interruptor de canal (ver abajo). |

### Temas de suscripción

Un tema es un grupo con nombre — Boletín, Actualizaciones de producto, Alertas de facturación — que un contacto activa o desactiva **sin** tocar el canal completo. Los `id` de temas deben ser únicos y coincidir con el patrón de slug (`[a-z0-9][a-z0-9_-]{0,63}`); cada entrada tiene:

* `name` (1–120 caracteres) — el nombre mostrado en la página.
* `description` (opcional, ≤500) — una línea de contexto mostrada junto al conmutador.
* `defaultOptIn` (por defecto `false`) — cómo se trata un contacto sin preferencia registrada.
* `archived` (opcional) — los temas archivados permanecen en la pista de auditoría pero ya no se muestran en la página.

Errores a nivel de lint: las URL que no pasan la validación del esquema `http(s)` se rechazan de entrada, y los `id` de temas duplicados fallan con «Los id de temas deben ser únicos» en lugar de sobrescribirse silenciosamente.

***

## 2. Crear un enlace por contacto

Una vez configurado, genere un enlace para un contacto a la vez con `POST /preference-center/link`:

```bash theme={null}
curl -X POST "https://api.orbit.devotel.io/api/v1/compliance/preference-center/link" \
  -H "Authorization: Bearer $ORBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "contactId": "cnt_01H…" }'
```

La respuesta devuelve `link` — una URL de la forma `${DEVOTEL_WEB_URL}/preferences?token=v1…`. Puntos a tener en cuenta:

* **El enlace apunta a la página alojada, no al punto de acceso JSON.** Cómpielo tal cual en sus plantillas de pie de página/remitente; la página misma recupera el punto de acceso a datos en segundo plano.
* **TTL de 30 días.** Después, el token se verifica como expirado y el contacto debe solicitar un nuevo enlace (crear uno nuevo toma una llamada API).
* **La página es agnóstica a la localización en el momento de creación.** La aplicación web resuelve una redirección preservando la consulta `?token=`, por lo que no necesita adivinar la configuración regional del contacto.

### Dónde colocarlo

* **Pie de página de correo electrónico (primario).** Agregue el enlace generado (o la variante corta rastreada que usa su remitente) en la zona de cancelación de suscripción de las plantillas de marketing.
* **Rescate SMS / WhatsApp.** Cuando el mensaje no tiene bloque de pie de página, agregue el enlace en línea: `{optOutMessage}: {link}`. El ayudante que construye cuerpos salientes acepta un enlace corto pre-generado, por lo que su clic de cancelación sigue recibiendo la atribución normal de clics.
* **Re-inclusión impulsada por supresión.** Cuando un contacto se re-incluye a través de otro flujo, puede darle un enlace nuevo para que obtenga la misma página de autoservicio.

Un enlace sin procesar sigue funcionando si falla la generación de enlaces cortos — la reserva es aditiva, nunca de carga crítica para el cumplimiento.

***

## 3. La página pública del token

La página alojada lee y escribe a través de dos puntos de acceso no autenticados protegidos por el token firmado:

* `GET /preferences/:token` — devuelve el payload de la página.
* `PUT /preferences/:token` — aplica actualizaciones.

Los formatos de token inválidos devuelven `400 INVALID_TOKEN`; los tokens expirados o alterados devuelven `401 TOKEN_EXPIRED` con «Solicite un nuevo enlace.»

### Respuesta GET

El payload agrupa el estado actual del contacto y la configuración de la organización:

```json theme={null}
{
  "contactId": "cnt_01H…",
  "displayName": "…",
  "email": "m*****@example.com",
  "phone": "+15551****…",
  "channelPreferences": { "sms": "opted_in", "email": "opted_out" },
  "frequencyPreference": "all",
  "channels": ["sms", "email"],
  "topics": [ { "id": "newsletter", "name": "Newsletter", "defaultOptIn": false } ],
  "topicPreferences": { "newsletter": "opted_in" },
  "consentHistory": [
    { "channel": "all", "state": "opted_out", "topicId": "newsletter", "occurredAt": "2026-09-01T…" }
  ],
  "config": { /* la configuración del centro de preferencias de la organización */ }
}
```

El correo electrónico y el teléfono están **enmascarados** en la respuesta pública — la página nunca muestra el identificador en bruto con el que se la llama. `consentHistory` es la pista de auditoría opt-in/opt-out más reciente del contacto, limitada a 20 filas, extraída del mismo libro de consentimiento que sus operadores ven en el panel.

### Cuerpo de solicitud PUT

```json theme={null}
{
  "channelPreferences": {
    "sms": "opted_in",
    "email": "opted_out"
  },
  "frequencyPreference": "important_only",
  "topicPreferences": { "newsletter": "opted_in" },
  "requestDataDeletion": false
}
```

* `channelPreferences` — mapa parcial permitido (Zod registro parcial); **se requiere al menos un canal**.
* `frequencyPreference` — opcional, uno de `all`, `important_only`, `weekly_digest`, `monthly_digest`.
* `topicPreferences` — mapa `{ topicId: opted_in | opted_out }` opcional validado contra sus temas configurados; los id desconocidos se ignoran.
* `requestDataDeletion` — establece una solicitud de eliminación GDPR junto a la exclusión (ver sección 6).

Una respuesta 422 lleva problemas por campo para que el formulario alojado pueda señalar la elección inválida.

***

## 4. Cómo fluyen las actualizaciones

Un opt-in/opt-out escrito aquí **no es solo una bandera de interfaz de usuario** — las mismas cuatro superficies de cumplimiento que una palabra clave STOP escribe se actualizan:

* **Libro de consentimiento.** Una fila `consent_records` por canal (o por tema) se añade con `source: preference_center` — su pista de auditoría de carga probatoria GDPR Artículo 7.
* **Lista de supresión.** En cualquier canal excluido, el teléfono/correo electrónico canonizado del contacto se inserta con ámbito `all` — un bloqueo entre canales que lee cada puerta de envío.
* **Valla STOP.** Una valla de ruta rápida Redis se establece al opt-out (y se borra en una re-inclusión completa), de modo que los lotes de campañas en vuelo ven el cambio antes de que la propagación de supresión de base de datos más lenta llegue.
* **Registro de auditoría.** `compliance.preference_center_updated` se registra cuando cambia la configuración, y los eventos opt-in/out a nivel de contacto se capturan en el libro de consentimiento.

Re-inclusión simétrica: una inclusión completa (todos los canales `opted_in`) revoca las filas de supresión activas del teléfono del contacto y borra la valla STOP, mientras que el libro de consentimiento gana la entrada inversa.

<Note>
  **Tema vs. canal.** Una exclusión a nivel de canal siempre gana — un conmutador de tema restringe el consentimiento *dentro* de los canales que el contacto aún acepta. Los id de temas desconocidos en un PUT se ignoran en lugar de persistir, por lo que un formulario obsoleto no puede escribir claves de atributo arbitrarias.
</Note>

***

## 5. Semántica del conmutador de eliminación GDPR

Cuando `showGdprDelete` está habilitado y el contacto marca `requestDataDeletion: true` en el PUT, la API registra una **solicitud de eliminación GDPR heredada** — una fila `pending` marcada para su proceso de eliminación de datos — junto a la exclusión. Esa marca es deliberada: el conmutador de eliminación del centro de preferencias marca al contacto, **no** inicia el pipeline DSAR rastreado.

<Warning>
  El conmutador de eliminación del centro de preferencias tiene **sin reloj SLA, sin exportación de datos descifrada y sin certificado de borrado del Artículo 17.** Para una solicitud de derecho al borrado que su DPO pueda rastrear, rútelo a través del punto de acceso DSAR (`POST /compliance/dsar`, propietario/administrador) — vea [Solicitudes de acceso de sujeto de datos (DSAR)](/compliance/dsar) y el [Guía DSAR + registro de violaciones](/guides/compliance-dsar-breach-register).
</Warning>

***

## 6. Probarlo

Dos ejemplos curl elaborados que puede pegar en un script de humo:

**Guardar la configuración:**

```bash theme={null}
curl -X POST "https://api.orbit.devotel.io/api/v1/compliance/preference-center" \
  -H "Authorization: Bearer $ORBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "companyName": "Acme Logistics",
    "primaryColor": "#1d4ed8",
    "channels": ["sms", "email"],
    "headerText": "Manage how Acme contacts you",
    "topics": [
      { "id": "shipping-updates", "name": "Shipping updates", "defaultOptIn": true },
      { "id": "promotions", "name": "Promotions", "defaultOptIn": false }
    ]
  }'
```

Esperado: `201` con la configuración guardada reflejada.

**Crear un enlace y ejercitar los puntos de acceso públicos:**

```bash theme={null}
LINK=$(curl -s -X POST "https://api.orbit.devotel.io/api/v1/compliance/preference-center/link" \
  -H "Authorization: Bearer $ORBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"contactId":"cnt_01H…"}' | jq -r '.link')

TOKEN="${LINK#*token=}"

curl -s "https://api.orbit.devotel.io/api/v1/compliance/preferences/$TOKEN" | jq

curl -s -X PUT "https://api.orbit.devotel.io/api/v1/compliance/preferences/$TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"channelPreferences":{"sms":"opted_out"}}' | jq
```

Esperado: GET devuelve las preferencias actuales del contacto; PUT devuelve `updated: true` más las preferencias aplicadas y, cuando se solicita, una entrada `gdprRequest`.

Fallos comunes a comprobar: `400 INVALID_TOKEN` (token mal formado), `401 TOKEN_EXPIRED` (TTL pasado o incompatibilidad de firma — cree un nuevo enlace), `422 VALIDATION_ERROR` (problemas por campo en la configuración o el cuerpo de actualización) y `404 NOT_FOUND` cuando el centro de preferencias está deshabilitado o el id del contacto no existe.

***

## Relacionado

* [Barreras de envío y protecciones pre-envío](/compliance/send-gates) — dónde vive el resumen del centro de preferencias junto a las horas de silencio, parada de emergencia y limitaciones.
* [Exclusión y Listas de supresión](/compliance/opt-out-suppression) — cómo el ámbito `all` y las importaciones CSV masivas se relacionan con esta superficie.
* [Gestión de consentimiento](/compliance/consent-management) — la API del lado del operador que almacena el mismo libro de consentimiento.
* [Referencia DSAR](/compliance/dsar) — el pipeline de borrado rastreado al que enrutar las solicitudes `requestDataDeletion`.
