---
name: find_consent_controls_llm
kind: function
lang: py
domain: browser
version: "1.0.0"
purity: impure
signature: "def find_consent_controls_llm(*, port: int = 9222, max_candidates: int = 40, model: str = 'claude-haiku-4-5-20251001') -> dict"
description: "Identifica los botones de un banner de cookies/consentimiento usando un LLM en vez de selectores hardcodeados por CMP. Recolecta los controles clicables visibles de la pagina via CDP, los marca con un atributo estable data-fnllm='N' en el DOM, y pregunta a haiku (ask_llm) cual es ACEPTAR TODO, cual RECHAZAR y cual el enlace VER SOCIOS/configurar/mas opciones/finalidades. Resuelve los CMP cuyos botones no encajan con selectores fijos (casos no-button del scanner de databrokers)."
tags: [consent, llm, cdp, browser, navegator, claude-direct, cookies, cmp, tcf, python, automation]
uses_functions: [cdp_eval_py_browser, ask_llm_py_core]
uses_types: []
returns: []
returns_optional: false
error_type: "error_go_core"
imports: ["json", "os", "re", "sys"]
params_schema:
  params:
    - name: port
      desc: "Puerto de remote debugging del Chrome donde esta el banner. Default 9222."
    - name: max_candidates
      desc: "Maximo de controles clicables a recolectar y enviar al LLM. Default 40."
    - name: model
      desc: "Modelo Anthropic a usar via ask_llm para clasificar los controles. Default claude-haiku-4-5-20251001."
  output: "dict {status: 'ok'|'error', candidates: [{idx, tag, text, aria, id, cls}], accept_idx/reject_idx/vendors_idx: int|None, accept_selector/reject_selector/vendors_selector: '[data-fnllm=\"N\"]'|None, reason: str, error?: str}. Los selectores se construyen a partir del idx elegido por el LLM y sirven para clicar el control con cdp_eval. Nunca lanza: errores de CDP/eval/LLM se devuelven en el dict."
tested: false
tests: []
test_file_path: ""
file_path: "python/functions/browser/find_consent_controls_llm.py"
---

## Ejemplo

```python
import sys, os, time
sys.path.insert(0, os.path.join("python", "functions"))
from browser.cdp_eval import cdp_eval
from browser.find_consent_controls_llm import find_consent_controls_llm

# Requiere un Chrome con --remote-debugging-port=9335 y una pestana abierta.
# Navega primero al sitio con banner y espera a que cargue el CMP.
cdp_eval("location.href='https://www.elpuntavui.cat'", port=9335)
time.sleep(6)

res = find_consent_controls_llm(port=9335)
print(res["accept_idx"], res["accept_selector"], res["reason"])

# Clicar el boton de aceptar elegido por el LLM:
sel = res["accept_selector"]
if sel:
    cdp_eval(f"document.querySelector('{sel}').click()", port=9335)
```

O directo por CLI: `python3 python/functions/browser/find_consent_controls_llm.py 9335`.

## Cuando usarla

Cuando un banner de cookies/consentimiento NO se resuelve con selectores fijos por
CMP (los casos "no-button" del scanner de databrokers): textos en otro idioma,
marcas TCF poco comunes, botones renderizados con clases dinamicas. La funcion deja
que un LLM lea los controles visibles y decida cual es aceptar/rechazar/ver-socios,
devolviendo selectores `[data-fnllm="N"]` estables que persisten en el DOM para que
el caller clique con `cdp_eval`. Usala como fallback despues de que los selectores
hardcodeados fallen, no como primer intento (cuesta una llamada al LLM).

## Gotchas

- **El banner debe estar YA en el DOM**: navega al sitio y espera unos segundos
  (`time.sleep(~6)`) ANTES de llamar. Si el CMP aun no se ha renderizado, la lista
  de candidatos no lo incluira y el LLM no podra elegir.
- **El LLM puede equivocarse**: haiku es rapido pero falible. Verifica el `text` del
  candidato en `accept_idx` antes de clicar acciones irreversibles. Sube de modelo
  (`model="claude-opus-4-8"`) si la precision importa mas que el coste.
- **Rate limits de ask_llm**: cada llamada consume cuota de la API directa de
  Anthropic. No la invoques en bucle cerrado sobre muchas pestanas sin throttling.
- **Marca el DOM**: pone `data-fnllm="N"` en hasta `max_candidates` elementos. Si
  re-llamas tras cambiar la pagina, los atributos viejos pueden quedar; los selectores
  solo son fiables sobre el mismo render donde se recolectaron.
- **Requiere remote debugging**: sin un Chrome con `--remote-debugging-port`, `cdp_eval`
  falla y devuelve `{status: "error", error: "cdp_eval: ..."}`.
- Solo recolecta controles **visibles** (`getClientRects().length>0`) y con texto
  corto (<=60 chars). Controles dentro de shadow DOM o iframes cross-origin no se ven.
