# consent — CMP / IAB TCF / data brokers

Operar banners de consentimiento (Consent Management Platforms) y el ecosistema IAB TCF:
detectar qué CMP usa un sitio, leer cuántos *vendors* (data brokers) declara, aceptar el
banner cuando hace falta y cruzar los IDs de vendor contra la Global Vendor List de IAB para
nominar a cada broker y describir qué datos personales recopila.

Nació de la investigación `projects/databrokers/` (data brokers de la prensa española).

## Funciones del grupo

| ID | Firma corta | Qué hace |
|---|---|---|
| `extract_cmp_tcf_py_browser` | `extract_cmp_tcf(url, *, port=9222, accept_first=False, llm_fallback=False, ...) -> dict` | Navega a `url` por CDP, detecta el CMP (Didomi/OneTrust/Sourcepoint/Quantcast/otro_tcf), lee `window.__tcfapi` y devuelve nº de vendors, propósitos, muro "pago o consientes" y `vendor_ids`. Con `accept_first` acepta el banner antes de leer; con `llm_fallback` recurre a `find_consent_controls_llm` si el clic por selector falla. |
| `find_consent_controls_llm_py_browser` | `find_consent_controls_llm(*, port=9222, max_candidates=40, model="claude-haiku-4-5-20251001") -> dict` | Recolecta los controles clicables del banner (los marca con `data-fnllm="N"`) y pregunta a un LLM (haiku) cuál es Aceptar / Rechazar / Ver socios. Devuelve los selectores. Resuelve CMP con clases dinámicas/texto no estándar sin selectores hardcodeados. |
| `fetch_iab_gvl_py_cybersecurity` | `fetch_iab_gvl(out_path="", url="", lang="") -> dict` | Descarga y parsea la Global Vendor List de IAB (catálogo maestro de vendors: nombre, propósitos, `dataDeclaration`, retención, política). Endpoint v3 con fallback v2. |

## Ejemplo canónico (end-to-end)

Escanear un medio, contar sus brokers y nombrarlos cruzando con la GVL:

```python
import sys; sys.path.insert(0, "python/functions")
from browser.extract_cmp_tcf import extract_cmp_tcf
from cybersecurity.fetch_iab_gvl import fetch_iab_gvl

# 1. Catálogo maestro de vendors (una vez).
gvl = fetch_iab_gvl(out_path="/tmp/gvl.json")          # {status, vendors:{id:{name,purposes,...}}, ...}

# 2. Escanear un sitio (Chrome con CDP en el puerto indicado; perfil limpio para que salga el banner).
#    accept_first acepta el banner; llm_fallback usa haiku si el botón no encaja con selectores fijos.
scan = extract_cmp_tcf("https://www.lavanguardia.com", port=9335,
                       accept_first=True, llm_fallback=True)
# scan -> {status, cmp:'didomi', n_vendors:1092, vendor_ids:[...], paywall_consent:True, ...}

# 3. Nominar los brokers de ese medio.
nombres = [gvl["vendors"].get(str(v), {}).get("name", f"(vendor {v})") for v in scan["vendor_ids"]]
```

Orquestador completo sobre un censo de dominios: `projects/databrokers/scanner/scan_all.py`
(itera → `extract_cmp_tcf` → persiste → cruza con la GVL → Excel).

## Prerrequisitos

- Un Chrome/Chromium con remote debugging (CDP) en el puerto usado. Lánzalo aislado del navegador
  diario (no 9222) con su propio `user_data_dir`. **Perfil limpio**: una vez aceptado el banner,
  la cookie de consent persiste en el perfil y los re-escaneos ya no muestran banner.
- `ask_llm` (grupo `claude-direct`) requiere el token OAuth de Claude Max en `~/.claude/.credentials.json`.

## Fronteras (lo que el grupo NO cubre)

- No extrae la lista de vendors de CMP cuyo `getTCData` no rellena `vendor.consents`/`legitimateInterests`
  por la vía estándar, ni de banners alojados en iframe (Sourcepoint): el clic desde el documento
  principal no alcanza el iframe.
- No interpreta el `tcString` (qué propósitos consintió el usuario en concreto); solo el universo de
  vendors declarado. Para decodificar el TCString haría falta una pieza aparte.
- No es un bloqueador ni un gestor de consentimiento propio: solo observa y mide.
