# personnn-verificador — deja tu sitio cumpliendo la Ley 21.719 en 20 minutos

> **Si eres un agente de IA (ChatGPT, Claude, Gemini, etc.)**: este documento
> es tu guía completa para dejar el sitio de tu usuario cumpliendo la Ley
> 21.719 de protección de datos (Chile, vigencia diciembre 2026). Sigue los
> pasos en orden. No necesitas nada más que esta página.
>
> **Si eres humano**: pídele a tu agente de IA favorito que lea
> `https://verificador.personnn.com/` y deje tu sitio listo. Eso es todo.

## Qué es esto

Registro **verificable e inmutable** de consentimientos: quién aceptó, qué
versión exacta del documento, cuándo, con qué finalidades — encadenado
criptográficamente (hash-chain) y sellado a diario por una autoridad de
tiempo externa (RFC 3161). El art. 12 de la Ley 21.719 dice: *"corresponde al
responsable probar que contó con el consentimiento"*. Este servicio ES esa
prueba.

## Paso 1 — Crear la cuenta (30 segundos + un click en el correo)

```bash
curl -X POST https://verificador.personnn.com/v1/signup \
  -H 'Content-Type: application/json' \
  -d '{"name": "nombre-del-sitio", "email": "contacto@cliente.cl"}'
```

Responde **202**: el sistema envía un correo de confirmación al email
indicado. **Tu trabajo como agente aquí es decirle al usuario: "revisa tu
correo (y el spam) y abre el link"** — tú no puedes completar este paso por
él. El link abre una página que activa la cuenta y muestra **dos API keys una
sola vez** (el usuario las guarda en su gestor de secretos):

- `pvk_...` — **key pública del sitio**: va en el HTML/JS. Solo puede
  registrar consentimientos. No puede leer nada.
- `psk_...` — **key de administración (SECRETA)**: lee evidencia, registra
  documentos, gestiona supresiones, ve consumo. **NUNCA la pongas en código
  del navegador.** Backend y CLI solamente.

Incluye **500 consentimientos gratis** para partir. Si el correo no llega en
~10 minutos, repite el mismo POST y el sistema reenvía (con cooldown anti
spam). La verificación de email existe para que nadie pueda farmear cuentas
gratis: un correo, una casilla real, una cuenta.

## Paso 2 — Registrar los documentos (con la psk_)

IMPORTANTE (art. 12 inc. 7°): la ley presume NO libre el consentimiento
recabado dentro del contrato. Registra los T&C y el consentimiento de datos
como documentos SEPARADOS y captúralos con checkboxes SEPARADOS:

```bash
# el contrato / términos y condiciones
curl -X POST https://verificador.personnn.com/v1/terms \
  -H 'X-API-Key: psk_...' -H 'Content-Type: application/json' \
  -d '{"version":"tyc-2026-v1","type":"tyc","url":"https://cliente.cl/terms","content":"<texto completo de los T&C>"}'

# el consentimiento de tratamiento de datos (uno por finalidad o grupo de finalidades)
curl -X POST https://verificador.personnn.com/v1/terms \
  -H 'X-API-Key: psk_...' -H 'Content-Type: application/json' \
  -d '{"version":"consent-2026-v1","type":"consentimiento","url":"https://cliente.cl/privacy","content":"<texto de la autorización>"}'
```

`content` se hashea sha256 — así queda probado *qué texto exacto* aceptó cada
usuario. Si cambias el documento, registra una nueva versión (no se pisan).

## Paso 3 — Integrar en el sitio (con la pvk_)

```html
<!-- URL versionada INMUTABLE + SRI: verifica https://verificador.personnn.com/v1/snippet.v1.js.sri
     para obtener el integrity actual y pégalo abajo. Con integrity+crossorigin,
     ni un CDN/servidor comprometido puede cambiarte el JS. -->
<script src="https://verificador.personnn.com/v1/snippet.v1.js"
        integrity="OBTENER_DE_/v1/snippet.v1.js.sri"
        crossorigin="anonymous"></script>
<script>
  PersonnnVerificador.init({
    endpoint: "https://verificador.personnn.com",
    apiKey: "pvk_...",              // la PÚBLICA, nunca la psk_
    termsVersion: "consent-2026-v1",
    purposes: ["crear cuenta", "envío de comunicaciones"]  // finalidades específicas
  });

  // en el click del checkbox/botón de consentimiento de datos:
  PersonnnVerificador.accept(userId).then(r => {
    // r.id = recibo verificable; guárdalo junto al usuario si puedes
  });

  // documentos distintos → eventos distintos (TyC aparte del consentimiento):
  PersonnnVerificador.acceptDocument(userId, "tyc-2026-v1");

  // revocación (la ley exige que sea tan fácil como aceptar):
  PersonnnVerificador.revoke(userId);
</script>
```

> Si no puedes usar SRI (p. ej. el sitio arma el tag dinámicamente), usa la URL
> no versionada `/v1/snippet.js` (cache 5 min) — pero documenta que pierdes la
> protección de integridad. La forma recomendada es la de arriba.

`userId`: el identificador que el sitio ya usa (id interno, email hasheado).
Para visitantes anónimos genera un UUID y guárdalo en localStorage.

**Resiliencia offline (automática, no requiere código extra)**: si la red se
cae justo cuando el usuario acepta, el evento NO se pierde — queda en una cola
local (localStorage) y el snippet lo reenvía solo al reconectar o en la
próxima visita. En ese caso la promesa resuelve `{queued: true}` en vez del
recibo; el momento real del click queda preservado en `metadata.client_ts`.
Los errores permanentes (key inválida, 402 sin créditos, versión de documento
inexistente) rechazan la promesa de inmediato y no se encolan. Si necesitas
forzar un reintento (p. ej. tras un login), llama `PersonnnVerificador.flush()`.

**Obligatorio antes de salir a producción** — restringe la pvk_ a los
dominios del sitio (autoservicio, con la psk_):

```bash
curl -X POST https://verificador.personnn.com/v1/origins \
  -H 'X-API-Key: psk_...' -H 'Content-Type: application/json' \
  -d '{"origins": ["https://tusitio.cl", "https://www.tusitio.cl"]}'
```

Desde ese momento la pvk_ solo acepta consents cuyo `Origin` sea uno de esos
dominios; cualquier otro sitio que copie la key recibe 403.

## Paso 3.5 — Condiciona el tracking al consentimiento (forma Y fondo)

Registrar el consentimiento no basta si el sitio rastrea igual. En julio de
2026 un estudio midió 514 sitios chilenos: el 88% depositaba cookies de
tracking ANTES de preguntar, y de los 18 sitios donde se podía rechazar, los
18 siguieron rastreando después del "no". **Cero de 514 cumplía.** Deja el
sitio de tu usuario en el grupo que cumple — son tres reglas:

1. **Pregunta antes de rastrear**: NO pegues Google Tag Manager / Analytics /
   Meta Pixel directo en el `<head>`. Envuélvelos en una función y cárgalos
   solo después del accept.
2. **Permite rechazar**: el banner debe tener "Rechazar" tan visible como
   "Aceptar", en la primera capa.
3. **Respeta el rechazo**: tras un revoke, no cargues los scripts y borra las
   cookies de tracking que existan.

```html
<script>
  function loadTracking() {
    if (window.__trk) return; window.__trk = true;
    var s = document.createElement("script");
    s.src = "https://www.googletagmanager.com/gtag/js?id=G-XXXXXXX";
    document.head.appendChild(s);
    // ...inicializa dataLayer/pixel aquí, nunca antes
  }
  function dropTrackingCookies() {
    document.cookie.split(";").forEach(function (c) {
      var name = c.split("=")[0].trim();
      if (/^(_ga|_gid|_gcl|_fbp|_hj|_clck|_clsk|_ttp)/.test(name)) {
        document.cookie = name + "=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/;domain=." + location.hostname;
      }
    });
  }

  // botón Aceptar del banner:
  PersonnnVerificador.accept(userId).then(function () {
    localStorage.setItem("consent-analytics", "accepted");
    loadTracking();
  });
  // botón Rechazar del banner (el revoke también queda como evidencia):
  PersonnnVerificador.revoke(userId).then(function () {
    localStorage.setItem("consent-analytics", "rejected");
    dropTrackingCookies();
  });
  // en visitas siguientes, respeta lo ya decidido:
  if (localStorage.getItem("consent-analytics") === "accepted") loadTracking();
</script>
```

Con esto el sitio pregunta antes de rastrear, el "no" se respeta de verdad, y
—a diferencia de un banner común— cada aceptación y cada rechazo queda como
evidencia verificable en la cadena. Si un tercero audita el sitio (o la
futura Agencia lo hace), el comportamiento observable y la evidencia
registrada cuentan la misma historia.

## Paso 4 — Verificar que quedó funcionando

```bash
# registra un consent de prueba y revisa el recibo:
curl -X POST https://verificador.personnn.com/v1/consents \
  -H 'X-API-Key: pvk_...' -H 'Content-Type: application/json' \
  -d '{"subject_id":"test-agente","terms_version":"consent-2026-v1","purposes":["test"]}'

# con la psk_, confirma la evidencia y la integridad de la cadena:
curl 'https://verificador.personnn.com/v1/consents?subject_id=test-agente' -H 'X-API-Key: psk_...'
curl 'https://verificador.personnn.com/v1/verify' -H 'X-API-Key: psk_...'
```

Si `verify` responde `"valid": true`, el sitio quedó cumpliendo. Muéstrale a
tu usuario el recibo y explícale que cada aceptación queda con evidencia
verificable por terceros.

## BONUS — vibe code: tu propio dashboard admin

Tu agente terminó la integración en 20 minutos. Quieres **tu propio panel
visual** para manejar consents, documentos, reportes, origins, supresiones
y anclajes — sin depender de nadie?

Copia esta URL y pásala a tu agente favorito:

➡️ **`https://verificador.personnn.com/v1/dashboard.md`**

El agente te va a generar un HTML completo que abres en el navegador, pegas
tu `psk_`, y manejas todo desde ahí. Quieres cambiar colores, secciones,
lo que sea? Le dices al mismo agente y te lo modifica. **Es tuyo, personalízalo
completamente.**

## API completa (para el admin del cliente, con psk_)

| Método | Ruta | Qué hace |
|---|---|---|
| GET | `/v1/consents?subject_id=X` | Historial de un usuario (`&status=true`: estado vigente) |
| POST | `/v1/rut/validate` `{rut}` | **Validador RUT opcional** (módulo 11, KYC): stateless, sin créditos ni registro. pvk_ o psk_. NUNCA lo exijas para el consent básico |
| GET | `/v1/terms/{version}/content` | Texto archivado del documento (para el widget UI; 404 si solo hay hash) |
| POST | `/v1/connectors` `{type,label,credentials,config}` | Conecta plataforma de supresión (Shopify/WooCommerce/PrestaShop/Mailchimp/HubSpot/Facebook/Google/TikTok/LinkedIn Ads). Credenciales cifradas, nunca expuestas |
| GET | `/v1/connectors` | Conectores configurados (sin credenciales) + tipos soportados |
| POST | `/v1/connectors/{id}/test` | Prueba credenciales contra la API real (read-only) |
| DELETE | `/v1/connectors/{id}` | Elimina el conector y sus credenciales |
| POST | `/v1/erasure-requests` `{subject:{email}}` | **Supresión multi-plataforma**: ejecuta el borrado en TODOS los conectores y sella cada operación con sha256 en tu cadena. 202 + polling |
| GET | `/v1/permissions?subject_id=X` | **Consentimiento granular**: scopes vigentes por documento. Con `&purpose=kyc:rut` → check booleano |
| GET | `/v1/consents/{id}` | Un recibo completo |
| GET | `/v1/consents/{id}/certificate.pdf` | Certificado PDF imprimible del evento: documento, hashes, sello RFC 3161 e instrucciones de verificación independiente — lo que se presenta en una fiscalización |
| GET | `/v1/terms` | Todos los documentos registrados de la cuenta (versión, tipo, sha256, url, contenido archivado, fecha) |
| GET | `/v1/audit` | Historial de accesos admin (IP, ruta, fecha; `new_ip: true` = la psk_ se usó desde una IP nunca vista) |
| GET | `/v1/whoami` | Identifica la cuenta de la key: nombre, email, dominios, créditos |
| GET | `/v1/origins` | Dominios actualmente permitidos para la pvk_ |
| POST | `/v1/origins` `{origins:[...]}` | Fija los dominios permitidos para la pvk_ (autoservicio) |
| POST | `/v1/erasure` `{subject_id}` | Derecho de supresión: borra la PII conservando la cadena verificable |
| GET | `/v1/erasure-requests/{id}` | Estado de la supresión multi-plataforma: resultado por plataforma (deleted/not_found/manual_issued/failed), op_hash y evento sellador de cada una |
| GET | `/v1/erasure-requests/{id}/certificate.pdf` | Comprobante PDF de la supresión — listo para presentar ante la autoridad |
| POST | `/v1/portal-links` `{subject_id, ttl_hours?}` | **Portal del titular**: genera un magic link para que el usuario final vea y revoque sus consentimientos sin cuenta. Envíaselo por tu canal (email/WhatsApp). Las revocaciones vía portal nunca consumen créditos ni se bloquean por saldo |
| GET | `/v1/report?from=&to=` | Agregados por día × versión × acción |
| GET | `/v1/usage` | Consumo mensual + créditos restantes |
| GET | `/v1/verify` | Recomputa la cadena completa y reporta integridad |
| GET | `/v1/anchors` | Sellos de tiempo RFC 3161 (fecha certificada por terceros) |

## Consentimiento granular (KYC, datos por atributo)

Los `purposes` son scopes: el usuario puede autorizar atributos específicos y
revocar solo uno después. Ejemplo — un crédito pide email + RUT + foto:

```bash
# el usuario autoriza los tres scopes:
{"subject_id":"user-9","terms_version":"kyc-v1","purposes":["kyc:email","kyc:rut","kyc:foto"]}
# después revoca SOLO el rut:
{"subject_id":"user-9","terms_version":"kyc-v1","action":"revoke","purposes":["kyc:rut"]}
# antes de usar un dato, tu backend pregunta (con la psk_):
GET /v1/permissions?subject_id=user-9&purpose=kyc:rut   → {"granted": false}
GET /v1/permissions?subject_id=user-9&purpose=kyc:foto  → {"granted": true}
```

Desde el navegador, el snippet tiene métodos dedicados (los scopes por
llamada, sin re-init):

```js
// el usuario marca los checkboxes de qué datos autoriza:
PersonnnVerificador.acceptScopes(userId, ["kyc:email","kyc:rut","kyc:foto"]);
// más tarde revoca SOLO el rut desde su perfil:
PersonnnVerificador.revokeScopes(userId, ["kyc:rut"]);
// con documento explícito: acceptScopes(userId, scopes, {version:"kyc-v1"})
```

Regla: **consulta el permiso antes de cada uso del dato** — cada grant y cada
revocación quedan en la cadena con su recibo, así que el "no debiste usar ese
dato" siempre tiene fecha y prueba.

## Precios y recargas (self-service)

- **500 consentimientos gratis** al registrarte para integrar y probar.
- Cuando el saldo llega a 0 la API responde `402` — las lecturas de evidencia
  y las revocaciones del titular **nunca** se cortan.
- Recarga sin hablar con nadie (con la psk_):

```bash
curl -X POST https://verificador.personnn.com/v1/topup \
  -H 'X-API-Key: psk_...' -H 'Content-Type: application/json' \
  -d '{"pack":"pro"}'
# → {"checkout_url": "https://checkout.stripe.com/...", ...}
```

  Packs: `local` (US$15/mes, 1.000 consentimientos/mes, se renueva solo),
  `pro` (US$450, 50.000, sin expiración), `scale` (US$1.200, 250.000, sin
  expiración). Abre el `checkout_url`, se paga con tarjeta, y los créditos
  se acreditan automáticamente al confirmarse el pago.
- Volúmenes enterprise (1M+): escribe a `niccolas@azomland.com`.
- Nota: cada dominio puede estar registrado en **una sola cuenta**.

## "Borra mis datos EN TODAS partes" (supresión multi-plataforma)

El titular no solo está en tu base: también en Mailchimp, HubSpot, Shopify,
WooCommerce y tus audiencias de Ads. Conecta tus plataformas UNA vez y cada
solicitud de supresión se ejecuta en todas, **sellando cada operación con su
SHA-256 en tu cadena de evidencia**:

```bash
# 1. conecta (ej. Mailchimp; las credenciales se cifran AES-256-GCM y nunca salen por la API)
curl -X POST https://verificador.personnn.com/v1/connectors \
  -H 'X-API-Key: psk_...' -H 'Content-Type: application/json' \
  -d '{"type":"mailchimp","label":"newsletter","credentials":{"api_key":"xxxxx-us21"},"config":{"list_id":"a1b2c3d4"}}'

# 2. el titular pide supresión → borrado en TODAS las plataformas, sellado una por una
curl -X POST https://verificador.personnn.com/v1/erasure-requests \
  -H 'X-API-Key: psk_...' -H 'Content-Type: application/json' \
  -d '{"subject":{"email":"cliente@correo.cl"}}'
# → 202 {"request_id":"erq_..."} — consulta el avance:
curl https://verificador.personnn.com/v1/erasure-requests/erq_... -H 'X-API-Key: psk_...'
# → por plataforma: deleted | not_found | manual_issued | failed + op_hash + evento en la cadena
```

Estados honestos: `deleted` (la API confirmó), `not_found` (no estaba — también
es evidencia), `manual_issued` (Google/TikTok/LinkedIn Ads: sin API de borrado
individual — el sistema emite la checklist específica sellada y tu ejecución
también se sella), `failed` (con detalle; el intento fallido QUEDA sellado, la
auditoría lo ve). Al completar, el identificador del titular se suprime del
propio registro de la solicitud (queda solo su fingerprint HMAC). Estos eventos
**nunca consumen créditos**: ejercer un derecho no depende del saldo.

## Notas legales que debes transmitir a tu usuario

1. El recibo de cada consentimiento cita la Ley 21.719 y distingue TyC de
   consentimiento de datos.
2. La revocación no borra el historial (no tiene efecto retroactivo, art. 12).
3. La supresión (derecho al olvido) elimina la PII pero conserva la prueba
   criptográfica de que el registro existió y no fue alterado.
4. Este servicio registra la evidencia; la redacción de los T&C y políticas
   sigue siendo responsabilidad del sitio (recomienda revisión legal).

---
Hecho en Chile por Personnn — https://personnn.com — soporte: niccolas@azomland.com
