OW Consent — Documentación
La gestión completa del consentimiento en WordPress: banner multijurisdicción, bloqueo de rastreadores, escáner, documentos legales, registro probatorio y portal de derechos. Autor: OptionWeb — Julien Daniel Página del plugin: https://optionweb.dev/es/addons/ow-consent/ Licencia: GPL-2.0-or-later Versión cubierta por este documento: 1.4.3
Índice
- Visión general
- Instalación
- Inicio rápido
- Los once perfiles de cumplimiento
- Las categorías de cookies
- El banner
- El bloqueo automático
- El escáner de rastreadores
- El generador de documentos legales
- El registro de consentimiento
- El portal de derechos (DSAR)
- La exclusión CCPA «Do Not Sell or Share»
- Google Consent Mode v2
- IAB TCF v2.2
- Global Privacy Control
- La detección regional
- La integración con OW Forms
- El botón flotante
- Shortcodes
- API REST
- Referencia de ajustes
- Solución de problemas
- FAQ
Visión general
OW Consent es una suite de gestión del consentimiento para WordPress. Cubre la cadena entera: mostrar una elección, aplicarla realmente a los rastreadores, conservar la prueba, publicar los documentos que la explican y recibir las solicitudes de los interesados.
Se incluyen once perfiles de cumplimiento — del RGPD a la CCPA, pasando por la Ley 25 de Quebec, la LGPD brasileña o la DPDP india. El perfil activo decide el modelo de consentimiento (opt-in u opt-out), los valores por defecto de Google Consent Mode, el texto de los documentos generados, los derechos publicados y la autoridad de control citada.
Todo se queda en su sitio web. El plugin solo contacta con un servicio externo, la Global Vendor List de IAB Europe, y únicamente si activa el módulo TCF, que viene desactivado por defecto. Sin telemetría, sin cuenta, sin suscripción.
Compatible con una caché de página completa, por construcción
Es el punto de arquitectura que gobierna todo lo demás. Nada de lo que el servidor renderiza depende de la cookie de consentimiento. El HTML es idéntico para todos los visitantes: los rastreadores se reescriben como etiquetas inertes para todo el mundo, y es un runtime JavaScript el que los libera en el navegador, categoría por categoría, leyendo la cookie antes del primer pintado.
Consecuencia práctica: LiteSpeed Cache, WP Rocket, Varnish o un CDN no pueden servir las
decisiones de un visitante a otro. Solo las respuestas realmente personales — la página de
confirmación de identidad de una solicitud de derechos, las respuestas REST que contienen
datos del visitante — se marcan explícitamente como no cacheables. Tampoco se imprime
ningún nonce en HTML cacheable: el banner pide uno fresco a un endpoint no-store justo
antes de cada escritura.
Lo que se incluye
- Banner de consentimiento: cuatro posiciones, tema claro / oscuro / automático, «Rechazar todo» tan visible como «Aceptar todo», panel de preferencias accesible por teclado, totalmente traducible.
- Bloqueo automático de rastreadores: scripts, snippets en línea, iframes, píxeles,
pistas de recursos (
preconnect,dns-prefetch,preload), hojas de estilo de analítica/marketing y medios de terceros, a partir de 175 firmas incluidas y editables. - Escáner de rastreadores: barrido de sus propias páginas mediante WP-Cron, lectura de
las cabeceras
Set-Cookie, sonda de navegador reservada a los administradores, y un panel que siempre dice lo que el barrido ha cubierto realmente. - Generador de documentos legales: política de privacidad y política de cookies para los once perfiles, en el idioma de la jurisdicción; aviso legal y condiciones generales en francés.
- Registro de consentimiento: cada acción se añade a una tabla encadenada por HMAC, con el perfil vigente, la huella de los documentos publicados y la huella del banner realmente mostrado.
- Portal de derechos (DSAR): formulario por shortcode, verificación por email, plazo del artículo 12.3 disparado en la confirmación de identidad, conexión con el exportador y el eliminador nativos de WordPress.
- Google Consent Mode v2, IAB TCF v2.2, Global Privacy Control, detección regional, integración con OW Forms: cada uno activable de forma independiente.
- API REST completa bajo el namespace
owc/v1.
Lo que no hace — léalo antes de comprometerse
En un asunto jurídico, una promesa excesiva le expone a usted tanto como al editor. Estos son los límites, tal y como están escritos en el código.
- Los documentos generados son plantillas, no asesoramiento jurídico. Cada documento termina con una advertencia que lo dice, y esa advertencia está activada por defecto. Haga revisar sus documentos antes de publicarlos.
- El módulo TCF no es una CMP registrada ante IAB Europe. Exige un CMP ID que debe obtener usted mismo, no ofrece ninguna elección a nivel de proveedor, y los proveedores están en su derecho de rechazar su señal. Si los ingresos publicitarios bajo TCF le importan, use una CMP certificada.
- El registro solo es a prueba de manipulación bajo condición. La cadena únicamente es una prueba si su clave de firma vive fuera de la base de datos. El plugin detecta el caso contrario, lo informa él mismo y se lo dice en la administración en lugar de pretender lo opuesto.
- El escáner no ejecuta JavaScript. Ve lo que contiene su HTML y lo que depositan sus
cabeceras
Set-Cookie; lo que un gestor de etiquetas inyecta en tiempo de ejecución solo lo ve una sonda reservada a los administradores conectados. - El bloqueo por defecto no es «todo lo que sea de terceros». Un script de terceros desconocido se permite por defecto; son los iframes desconocidos los que se bloquean por defecto. El bloqueo se apoya en el catálogo de firmas, que usted puede enriquecer.
- Sin JavaScript no se puede registrar ninguna elección. El visitante ve un bloque
<noscript>y no se carga nada que no sea imprescindible, pero tampoco se registra nada. - Sin granularidad estado por estado en EE. UU. ni provincia por provincia en Canadá
dentro de la detección regional: todo el territorio estadounidense recibe el perfil
ccpa, todo Canadá recibequebec. - El aviso legal y las condiciones generales solo existen en francés. Para cualquier jurisdicción cuyo idioma de destino no sea el francés, el generador se niega a producir esos dos documentos en lugar de publicar un texto inadecuado.
- Sin rol dedicado: toda la administración exige la capacidad
manage_options.
Esta lista es una decisión asumida, no una hoja de ruta disimulada. En materia de cumplimiento, una herramienta que enuncia sus agujeros vale más que una que los esconde.
Instalación
Desde el .zip
- Descargue
ow-consent-1.4.3.zipdesde https://optionweb.dev/es/addons/ow-consent/ - Plugins → Añadir nuevo → Subir plugin
- Elija el archivo, pulse Instalar y después Activar
Por FTP
Descomprima el archivo y deposite la carpeta ow-consent en /wp-content/plugins/,
después active el plugin desde Plugins.
Requisitos
- WordPress 6.2 o más reciente — es un rechazo de arranque, no una recomendación (véase más abajo)
- PHP 7.4 o más reciente
- MySQL 5.7+ / MariaDB 10.2+
- WP-Cron operativo si usa el escáner, la retención del registro, los recordatorios de vencimiento DSAR o el módulo TCF
El cortafuegos de WordPress 6.2. Desde la 1.3.0, cada identificador de tabla se pasa por el marcador
%idewpdb::prepare(), que WordPress solo entiende a partir de la 6.2. En un núcleo más antiguo,prepare()devolvería una cadena vacía, el catálogo de rastreadores se resolvería en silencio a un conjunto vacío, y el banner seguiría prometiendo al visitante un bloqueo que no se produciría. Por eso el plugin se niega a arrancar y muestra un mensaje de error que dice explícitamente que no se bloquea nada y que no se registra ningún consentimiento. La cabeceraRequires at leastya impide la activación por debajo de 6.2, pero no cubre ni una degradación del núcleo bajo una instalación en servicio, ni una copia depositada por FTP.
Lo que se instala
Al activarse, OW Consent crea cuatro tablas:
| Tabla | Contenido |
|---|---|
{prefix}owc_ledger | El registro de consentimiento, encadenado por hash |
{prefix}owc_dsar | Las solicitudes de ejercicio de derechos |
{prefix}owc_scanner | Los hallazgos del escáner de rastreadores |
{prefix}owc_scripts | El catálogo de firmas que usa el bloqueo |
Una quinta tabla, {prefix}owc_form_links, la crea por separado la integración con
OW Forms si el plugin hermano está activo (véase La integración con OW
Forms).
La activación añade además:
- la opción
owc_settings, sembrada vacía — deliberadamente: mientras no haya guardado nada, todos los textos vienen de los valores por defecto en inglés traducidos al vuelo al idioma del sitio, en lugar de congelar en la base de datos la configuración regional de quien activó el plugin; - la opción
owc_version; - el catálogo de rastreadores incluido (
data/tracker-catalog.json), insertado en lotes de 100 conINSERT IGNORE, con una marca de siembra por versión (owc_catalog_seeded_1.4.3); - la tarea diaria
owc_daily_maintenance, programada una hora después de la activación.
Otros cinco eventos de cron los arman los módulos que los utilizan. Los seis hooks de
cron del plugin son: owc_scanner_run, owc_scanner_run_batch, owc_ledger_retention,
owc_tcf_refresh_gvl, owc_run_upgrade y owc_daily_maintenance.
En multisitio, las tablas se crean por sitio, nunca compartidas
(wp_2_owc_ledger, etc.). Una activación de red recorre todos los sitios solo si la red
tiene como máximo 200 sitios; por encima de esa cifra, cada sitio se aprovisiona de forma
perezosa en su primera petición. Un subsitio creado después de una activación de red se
aprovisiona mediante el hook wp_initialize_site.
Qué definir en wp-config.php
Ninguna de estas constantes es obligatoria, pero dos de ellas cambian el valor probatorio de lo que produce el plugin.
// Recomendado: saca la clave de firma del registro fuera de la base de datos.
define( 'OWC_LEDGER_KEY', 'una cadena aleatoria larga, propia de este sitio' );
// Recomendado: los salts estándar de WordPress. Sin ellos, WordPress los guarda en la
// base de datos, y el propio registro se señala como no a prueba de manipulación.
define( 'AUTH_KEY', '…' );
define( 'AUTH_SALT', '…' );
// Si el sitio está detrás de un CDN, un balanceador de carga o un proxy inverso.
// Sin esta constante, CF-Connecting-IP, X-Forwarded-For y X-Real-IP se IGNORAN
// y solo se usa REMOTE_ADDR — lo que da la misma identidad a todos sus visitantes
// a efectos de limitación de peticiones.
define( 'OWC_TRUSTED_PROXY', '198.51.100.0/24, 2001:db8::/32' );
// `OWC_TRUSTED_PROXIES` se acepta como alias; sirve una cadena o un array.
// Solo para la detección regional: declara qué cabeceras de país son de confianza.
define( 'OWC_GEO_TRUSTED_HEADERS', 'cloudflare' ); // 'cloudflare'|'cloudfront'|'proxy'|'all'
define( 'OWC_BEHIND_CLOUDFLARE', true );
define( 'OWC_BEHIND_CLOUDFRONT', true );
OWC_LEDGER_KEY sirve también para derivar la clave de la cookie de detección regional. Si
no está definida, la clave del registro cae de nuevo en wp_salt('auth').
Quién tiene acceso
Toda la administración del plugin y todas las rutas REST de administración exigen la
capacidad manage_options. No existe ni un rol dedicado ni una capacidad más fina:
abrir la pantalla del plugin a alguien equivale a darle los ajustes del sitio.
Ruta de actualización
El plugin nunca migra su esquema en caliente en una página anónima. Cuando la versión
cambia, owc_version y una marca owc_pending_upgrade se escriben de inmediato, y después:
- la migración se ejecuta en línea si la petición es una petición de administración fuera de AJAX, una ejecución de cron o un comando WP-CLI;
- si no, se programa un evento
owc_run_upgradecinco segundos más tarde.
admin-ajax.php se trata como una petición anónima: es un punto de entrada público. La
migración se ejecuta bajo bloqueo (owc_upgrade_lock, robado al cabo de 300 segundos), y
una red de seguridad en admin_init recoge los sitios donde WP-Cron está desactivado. En
una versión idéntica, el coste total es un único get_option().
Desactivación y eliminación
Desactivar conserva todos los datos y solo borra los seis eventos de cron. En desactivación de red, se recorren todos los sitios en lotes de 200 — al contrario que en la activación — porque un cron dejado armado no desaparecería nunca.
Eliminar el plugin dispara uninstall.php, que procede en dos tiempos:
- Siempre, sea cual sea su ajuste: se borran los seis crons y se elimina la tabla
{prefix}owc_dsar, con sus transients de limitación de peticiones. Es la única tabla que contiene datos personales directamente identificativos de terceros (dirección de email, nombre, texto libre); una vez el plugin ha desaparecido, ya nada acota su retención y ninguna pantalla permite responderlas, exportarlas ni borrarlas. Exporte las solicitudes que deba conservar antes de eliminar el plugin. - Solo si
delete_data_on_uninstallestá explícitamente activado: eliminación de las tablasowc_ledger,owc_dsar,owc_scanner,owc_scriptsyowc_form_links, de las opciones nombradas, de todas las opciones con el prefijoowc_(transients incluidos) y de lasusermetacon el prefijoowc_. En multisitio, las opciones de red siguen la decisión del sitio principal.
Este ajuste está desactivado por defecto: la prueba de consentimiento, exigida por el artículo 7.1 del RGPD, sobrevive a la eliminación del plugin.
Inicio rápido
Tras la activación, abra la entrada OW Consent en la barra lateral de la administración. Todo el plugin cabe en esa única pantalla, en diez pestañas: Dashboard, Banner, Compliance, Legal identity, Categories, Policies, Scanner, Tracker catalogue, Audit ledger, DSAR requests.
Guardar solo afecta a la pestaña abierta. Es deliberado: cada booleano tiene un campo oculto gemelo, y una clave ausente del formulario significa «este campo está en otra pestaña», nunca «desmarcado». Sin eso, guardar una pestaña machacaría los ajustes de todas las demás.
1. Elija su perfil de cumplimiento
Pestaña Compliance. El perfil decide el modelo de consentimiento, los valores por
defecto de Consent Mode, los documentos generados y los derechos publicados. Por defecto:
gdpr.
compliance_strict está activado por defecto: es lo que extiende el bloqueo más allá
de los scripts registrados por WordPress. Déjelo activo si quiere que se traten los
iframes, los píxeles y los scripts escritos a pelo en su tema.
2. Rellene la identidad legal
Pestaña Legal identity. Estos campos son la materia prima de los documentos generados, y la generación se rechaza mientras haya un campo obligatorio vacío — con la lista de las claves que faltan, no un fallo silencioso.
Mínimo para todos los documentos: legal_company_name y legal_company_email. Para todo
salvo la política de cookies, añada la dirección y el país. Para el aviso legal, añada
además el teléfono, el director de la publicación y los datos completos del proveedor de
alojamiento; en Francia, Bélgica y Luxemburgo, la forma jurídica y el número de registro
pasan a ser obligatorios también.
Deje legal_dpa_authority vacío: la autoridad de control se deriva de su país y de su
perfil. Rellenarla a mano en un sitio multijurisdicción equivale a nombrar al regulador
equivocado.
3. Revise sus categorías
Pestaña Categories. Las seis categorías están disponibles por defecto. Desactive
las que su sitio no use: una categoría ausente de la interfaz no es una ganancia de
cumplimiento, es un recurso bloqueado para siempre sin ningún interruptor que lo libere —
el bloqueo lo vuelca entonces sobre marketing.
Deje las etiquetas vacías mientras le sirvan: así siguen el idioma del sitio. En cuanto personaliza un texto, ese texto deja de seguir al idioma.
4. Ajuste el banner
Pestaña Banner. Posición, tema, botones, etiquetas, plazo de renovación.
Dos puntos que no conviene pasar por alto: deje banner_reject_all activado
(«Rechazar» debe ser tan sencillo y visible como «Aceptar»), y deje la aspa de cierre
desactivada — no está por defecto porque cerrar sin elegir equivale a un rechazo
implícito. Si la activa, un clic en el aspa ejecuta la ruta «Rechazar todo» completa, nunca
un cierre silencioso.
5. Genere sus documentos
Pestaña Policies. Cuatro documentos: política de cookies, política de privacidad, aviso legal, condiciones generales. Cada uno se convierte en una página de WordPress versionada, cuyo enlace se reinyecta en los ajustes.
Genere primero en vista previa, revise, y publique después. Y hágalo revisar por un profesional: la banda de advertencia al pie de cada documento no es decorativa.
6. Abra el portal de derechos
Cree una página y pegue en ella [owc_dsar_form]. Rellene dsar_email en la pestaña
DSAR requests: es la dirección de contacto publicada bajo el formulario y el
destinatario de las notificaciones. Sin ella, el plugin cae en legal_dpo_email y después
en admin_email — pero admin_email nunca se publica en una página pública.
7. Compruébelo antes de abrir al público
El panel de control ejecuta quince comprobaciones de cumplimiento y distingue los errores de las advertencias: banner desactivado, «Rechazar todo» ausente, aspa de cierre activada, identidad legal incompleta, portal de derechos cerrado, registro desactivado, ninguna política enlazada, autoridad de control en contradicción con el perfil activo (con un botón «Fix this» que la reinicia), sitio sin HTTPS, rastreadores sin categorizar, solicitudes DSAR fuera de plazo, reglas de catálogo que no pueden dispararse.
Ponga esas quince líneas en verde antes de anunciar que su sitio cumple.
Los once perfiles de cumplimiento
El perfil de cumplimiento no es cosmético. Pilota el modelo legal, la interfaz, las señales técnicas y el contenido de los documentos publicados.
La lista
| Perfil | Régimen cubierto | Modelo |
|---|---|---|
gdpr | RGPD + ePrivacy (UE/EEE) | Opt-in |
uk_pecr | UK GDPR + PECR (Reino Unido) | Opt-in |
ch_nfadp | nLPD suiza | Opt-in |
quebec | Ley 25 (Quebec) | Opt-in |
lgpd | LGPD (Brasil) | Opt-in |
popia | POPIA (Sudáfrica) | Opt-in |
pipl | PIPL (China) | Opt-in |
dpdp | DPDP Act 2023 (India) | Opt-in |
ccpa | CCPA / CPRA (California) | Opt-out |
us_generic | Leyes estatales estadounidenses genéricas | Opt-out |
au | Privacy Act (Australia) | Opt-out |
Ajuste: compliance_profile, por defecto gdpr.
Opt-in significa que, antes de cualquier elección, solo se conceden las categorías
forzadas. Opt-out significa que todo está concedido hasta el rechazo. Los tres perfiles
opt-out son ccpa, us_generic y au. El runtime JavaScript aplica exactamente la misma
regla que PHP, de modo que servidor y navegador no pueden informar de dos estados
distintos.
Lo que el perfil cambia realmente
| Lo que cambia | Detalle |
|---|---|
| Modelo de consentimiento | Opt-in, salvo ccpa, us_generic, au |
| Valores por defecto de Consent Mode | Las siete señales pasan a granted bajo un perfil opt-out |
| Enlace «Do Not Sell or Share» obligatorio | ccpa y us_generic únicamente |
| GPC jurídicamente vinculante | ccpa y us_generic únicamente — Australia queda excluida explícitamente: régimen opt-out, pero que no reconoce el GPC |
| Alcance de «el RGPD se aplica» para TCF | 27 países de la UE + IS, LI, NO + GB + CH, es decir 31 códigos |
| Idioma del documento generado | quebec → francés; lgpd → portugués; gdpr con país FR, BE o LU → francés; todo lo demás → inglés |
| Autoridad de control citada | Tabla por perfil, afinada por país para el RGPD |
| Lista de derechos publicada | Una lista redactada por perfil, con cita del artículo |
| Cuerpo «régimen de cookies» del documento en inglés | Texto, alcance de transferencia y garantías distintos por perfil |
Los documentos generados por perfil
La política de privacidad y la política de cookies existen para los once perfiles. Las
plantillas se resuelven en este orden, ganando la primera encontrada:
<tipo>_<perfil>_<idioma>, luego <tipo>_<perfil>, luego <tipo>_<idioma>, luego
<tipo>.
Los cuerpos en inglés de la política de cookies son conscientes del perfil: regla
aplicable, alcance de transferencia, garantías y etiqueta del panel difieren para gdpr,
uk_pecr, ch_nfadp, au, pipl, dpdp, ccpa y us_generic, con las citas
correspondientes (art. 5(3) de la directiva 2002/58, PECR reg. 6, art. 45c(b) LTC y
art. 19/6(7)(b) nLPD, APP 8, art. 24 PIPL, secciones 5/6/7/9(3)/16 de la DPDP Act 2023,
§1798.121 y Cal. Code Regs. tit. 11 §7025, VCDPA/CPA/CTDPA/UCPA/TDPSA).
Autoridad de control citada
| Perfil | Autoridad nombrada |
|---|---|
gdpr, país FR | CNIL |
gdpr, país BE | APD-GBA |
gdpr, país LU | CNPD |
gdpr, país DE | BfDI |
gdpr, otro país del EEE | Formulación genérica («la autoridad de control competente») |
uk_pecr | ICO |
ch_nfadp | PFPDT / FDPIC |
quebec | Commission d'accès à l'information |
lgpd | ANPD |
ccpa | California Privacy Protection Agency |
popia | Information Regulator (South Africa) |
pipl | Cyberspace Administration of China |
dpdp | Data Protection Board of India |
au | OAIC |
us_generic | Fiscal General de su estado |
Si ha modificado legal_dpa_authority, lo que usted escriba prevalece sobre esta
derivación.
Límites de la cobertura jurisdiccional
- Solo cuatro países del EEE tienen autoridad nombrada (FR, BE, LU, DE). Un sitio RGPD en España, Italia o los Países Bajos publica una formulación genérica.
- Sin granularidad estado por estado en EE. UU. El perfil
us_genericexiste y lleva sus propios textos VCDPA/CPA/CTDPA/UCPA/TDPSA, pero ninguna detección automática lo asigna: se elige a mano. - Sin granularidad provincial en Canadá.
- Solo tres idiomas de documentos: francés, inglés, portugués. Los cuerpos no pasan por el mecanismo de traducción de WordPress — es deliberado: un documento legal debe ser monolingüe y su idioma sigue a la jurisdicción, nunca a la configuración regional del administrador.
- Aviso legal y condiciones generales: plantillas solo en francés. El generador se
niega en redondo para cualquier otro idioma de destino, salvo que usted aporte su propio
cuerpo mediante el filtro
owc_policy_template.
Las categorías de cookies
La lista canónica
Seis categorías, en este orden de visualización, con necessary siempre a la cabeza:
| Slug | Etiqueta incluida | Descripción incluida |
|---|---|---|
necessary | Necessary | Strictly required for the site to function (cart, login, language preferences). Cannot be disabled. |
functional | Functional | Enhance the experience (chat, embedded videos, maps). Without them some features may not work. |
analytics | Statistics | Help us understand how you use the site (anonymously). No personal data is shared for commercial purposes. |
marketing | Marketing | Enable us to show you ads and content tailored to your interests on other sites. |
preferences | Preferences | Remember your interface choices (layout, saved filters). |
social | Social & embeds | Allow embedded social content (YouTube, Instagram, X) to load. |
Los valores anteriores son los predeterminados en inglés que se pueden almacenar. En un sitio en español, el visitante ve la traducción — véase el mecanismo de traducción más abajo.
Disponibilidad
Ajuste cat_<slug>_available, uno por categoría. Las seis están disponibles por
defecto. La antigua clave cat_<slug>_enabled se sigue leyendo como reserva, sin
migración.
preferences y social se entregan disponibles porque el catálogo semilla clasifica
YouTube, Spotify, SoundCloud, Instagram, X y Facebook en social: sin la categoría en la
interfaz, esas integraciones quedarían bloqueadas para siempre sin manera de optar por
ellas.
necessary está forzada: no se puede desactivar (art. 5.3 ePrivacy). Por eso no tiene
interruptor de disponibilidad en la administración, solo una etiqueta y una descripción.
El mecanismo de traducción
Ajustes cat_<slug>_label y cat_<slug>_desc. El valor almacenado se devuelve literal
únicamente si no está vacío y difiere del valor por defecto en inglés; si no, lo que sale
es la traducción.
Consecuencia que conviene conocer: mientras no personalice nada, cambiar el idioma del sitio cambia el idioma de las categorías. En cuanto escribe su propio texto, ese texto queda congelado en el idioma en que lo escribió. La misma regla se aplica a los textos del banner.
La descripción traducida de necessary se construye a partir de dos cadenas: la segunda
nombra las cookies del propio plugin, owc_consent (hasta 13 meses) y owc_geo
(24 horas, escrita únicamente cuando la detección regional está activada).
Correspondencia con Google Consent Mode v2 — vinculante
Es la fuente de verdad única del plugin: el banner, el bloqueo y el bootstrap leen todos esta tabla, así que no pueden divergir.
| Categoría | Señales de Consent Mode v2 |
|---|---|
necessary | security_storage, functionality_storage |
functional | functionality_storage, personalization_storage |
analytics | analytics_storage |
marketing | ad_storage, ad_user_data, ad_personalization |
preferences | personalization_storage |
social | ad_storage, ad_user_data |
Matiz importante: si su sitio expone la categoría functional,
functionality_storage se retira de la lista de necessary. De lo contrario se
concedería antes de cualquier consentimiento, cuando pertenece a una categoría opcional.
Correspondencia con TCF — solo descriptiva
functional → functional, analytics → measurement, marketing → advertising,
preferences → personalization, social → social_media.
Son etiquetas de visualización. La correspondencia vinculante del lado TCF es la tabla de identificadores numéricos de finalidades de IAB, descrita en la sección IAB TCF v2.2. No conecte nunca un comportamiento TCF a estas cinco etiquetas.
Qué pasa si una categoría desaparece
Si una regla del catálogo apunta a una categoría que su sitio ya no expone — porque ha
desactivado social, por ejemplo — el bloqueo vuelca el recurso sobre marketing, o en su
defecto sobre la primera categoría opcional disponible. Sin esa normalización, el recurso
quedaría bloqueado para siempre sin ningún interruptor que lo liberase.
El banner
Cuándo se muestra
El banner se renderiza en wp_footer con prioridad 5, y sus recursos se encolan en
wp_enqueue_scripts. Sale de inmediato si se cumple alguna de estas condiciones: contexto
de administración, feed RSS, robots.txt, o banner_enabled desactivado. Sale también si
wp_head nunca se ha disparado — un tema que no llama a wp_head() obtendría en otro caso
un marcado inerte y sin estilos.
El HTML es idéntico para todos los visitantes. Todas las casillas del panel se
renderizan en OFF desde el servidor y luego se hidratan en el cliente a partir de la
cookie. Es el JavaScript el que pone <html data-owc="given|none"> de forma síncrona,
antes del primer pintado; el CSS solo muestra el banner con data-owc="none".
Corolario que conviene conocer: un visitante sin JavaScript nunca ve el banner, así que
nunca queda encerrado tras una modal que no puede cerrar. En su lugar ve un bloque
<noscript> que explica que sus preferencias no se pueden registrar y que no se carga
ninguna cookie no imprescindible mientras no se haya tomado una decisión.
Posiciones y tema
| Ajuste | Valores | Por defecto |
|---|---|---|
banner_position | bottom-bar, bottom-card, center-modal, top-bar | bottom-bar |
banner_style | auto, light, dark | auto |
Solo center-modal obtiene role="dialog", aria-modal="true", un fondo oscurecido, una
trampa de foco y la tecla Escape. Las otras tres posiciones son un role="region": Escape
no se intercepta (el tema conserva su propia gestión) y el foco no se roba en el primer
pintado, lo que empujaría a un usuario de teclado más allá de todos los enlaces de salto.
La paleta oscura se aplica bajo prefers-color-scheme: dark con protecciones para no
machacar un tema que declara explícitamente light. El tema del sitio puede pilotar la
paleta mediante variables CSS: --owc-paper-tint, --owc-ink, --owc-smoke, --owc-fog,
--owc-ui, --owc-accent, --owc-accent-strong, --owc-accent-darker.
Detalles de implementación: z-index 99998 para el banner y 99997 para el fondo, soporte
de env(safe-area-inset-*) para los notches de iOS, y desplazamiento automático bajo la
barra de administración de WordPress en posición superior.
Los botones
| Ajuste | Por defecto | Efecto |
|---|---|---|
banner_accept_all | true | Muestra «Aceptar todo» |
banner_reject_all | true | Muestra «Rechazar todo» |
banner_preferences | true | Muestra «Personalizar» |
banner_close_x | false | Muestra el aspa de cierre |
banner_show_logo | true | Muestra el logotipo del sitio |
«Aceptar todo» y «Rechazar todo» comparten la misma clase de estilo: mismo fondo, mismo borde, mismo grosor, mismo relleno. Es la respuesta a la exigencia de la CNIL / EDPB (directrices 03/2022): rechazar debe ser tan sencillo y tan visible como aceptar.
El aspa de cierre
Está desactivada por defecto, porque un aspa de cierre equivale a un rechazo implícito. Tres comportamientos que conviene conocer:
- Cuando
banner_close_xestá en off, el aspa está presente en el marcado pero lleva el atributohidden, y el CSS la retira por completo: ni visible, ni enfocable, ni anunciada a los lectores de pantalla. - Cuando está activada, pulsar el aspa ejecuta la ruta «Rechazar todo» completa, nunca un cierre silencioso.
- El runtime la revela y la reetiqueta en un solo caso: cuando un rechazo no ha podido registrarse en absoluto. Entonces ya no rechaza nada, simplemente aparta el aviso — y ese cierre no cuenta como una elección. Un visitante cuyo servidor rechaza la decisión no se queda ante un banner que no puede cerrar.
La primera capa
- El título (
text_title) y el mensaje (text_message). El mensaje es el único campo con HTML enriquecido del plugin; se renderiza con unwp_kseslimitado a<a href target rel>. - El enlace a la política de privacidad:
link_privacy_policy, con reserva en la página de política declarada en WordPress si el ajuste está vacío. - El enlace a la política de cookies:
link_cookie_policy, sin reserva. - El nombre del responsable del tratamiento:
legal_company_name, y si no, el nombre del sitio, mostrado como «Responsable del tratamiento: …» (art. 13(1)(a)). - El logotipo: el logotipo personalizado del tema en tamaño
medium, y si no, el icono del sitio.
El panel de preferencias
Una fila por categoría disponible, dentro de un role="group" con nombre.
- Una categoría forzada muestra una insignia de texto «Always active», sin interruptor.
- Una categoría opcional muestra un
<button role="switch">conaria-checked,aria-labelledbyyaria-describedby. El estado lo transmitenaria-checked, la posición del cursor y una palabra visible On/Off — nunca el color por sí solo. - Objetivo táctil ampliado a 44 px en pantalla pequeña (WCAG 2.2 AA, criterio 2.5.8).
- La barra de acciones del panel es
stickyabajo, para que «Guardar mis opciones» siga siendo alcanzable mientras la lista se desplaza.
Si el módulo TCF está activo, aparecen dos bloques adicionales: las finalidades TCF alcanzables y las funcionalidades especiales declaradas. Véase IAB TCF v2.2.
Renovación del consentimiento
| Ajuste | Por defecto | Límites |
|---|---|---|
consent_renewal_months | 12 | 0 a 13 |
consent_policy_hash_check | true | — |
0 no significa «no volver a preguntar nunca». El valor 0 se convierte en 13 meses, y
13 meses es el tope duro (deliberación CNIL 2020-091). La duración efectiva está, por
tanto, siempre entre 1 y 13 meses, y es ese mismo valor el que gobierna la duración de la
cookie, la comprobación de caducidad en el servidor y la duración publicada en los
documentos generados. La constante OWC_COOKIE_TTL que se ve en el código fuente no es más
que una reserva.
consent_policy_hash_check vuelve a pedir el consentimiento cuando sus documentos cambian:
la cookie lleva una huella de las páginas de política enlazadas, y una diferencia reabre el
banner. La huella vale la cadena vacía cuando no hay ninguna página enlazada — eso es lo
que permite distinguir «desconocido» de «cambiado» y no volver a molestar a todo un sitio
por una comparación vacía.
Los textos
Siete ranuras, todas vacías por defecto y por tanto traducidas automáticamente:
text_title, text_message, text_accept_all, text_reject_all, text_preferences,
text_save, y floating_button_label para el botón flotante.
En la administración, cada campo muestra su valor por defecto traducido como texto de ayuda: dejarlo vacío conserva el valor por defecto, que sigue el idioma del visitante.
El filtro owc_banner_texts permite sustituir estos textos por código. Se aceptan ranuras
adicionales; los valores no escalares o vacíos se descartan para que una llamada torpe no
pueda vaciar el banner.
Cómo se registra realmente la elección
El runtime JavaScript es ES5 estricto — sin funciones flecha, sin plantillas de cadena — para funcionar en los navegadores integrados en aplicaciones y en los WebViews antiguos. Conviene conocer su secuencia de escritura, porque es la que explica la mayoría de los mensajes de error.
- Reanclaje del origen. Las URL REST vienen de la configuración de WordPress. Si sus
visitantes navegan por otro host (www frente a apex, alias, dominio de vista previa,
proxy que reescribe
Host), esa URL es cross-origin y el navegador se niega a almacenar elSet-Cookiemientras WordPress responde 200. Por eso el runtime reancla la ruta que le da PHP sobre el origen realmente visitado. - Nonce. Se pide fresco a
GET /owc/v1/noncejusto antes de la escritura, nunca va incrustado en HTML cacheable, y se envía en la cabeceraX-OWC-Nonce. La obtención tiene un tope de 4 segundos, y un nonce ausente, vacío o inalcanzable nunca impide la escritura ni se le comunica al visitante. page_urlexplícita en el cuerpo de la petición, para que la fila de registro no dependa de la cabeceraReferer, que un plugin, una meta-referrer o un proxy pueden eliminar.- Guardia antibloqueo de 15 segundos con
AbortController: la petición se cancela, no solo se ignora, para que un POST tardío no escriba una segunda fila. - Definición del éxito. El POST tiene éxito si, y solo si: la respuesta es
ok, el cuerpo es JSON analizable,json.ok === true, y la cookie se relee en el navegador. Una página de caché, un desafío de borde o un WAF servido en 200 no cuentan. - Si el servidor responde
cookie_set: false, el runtime escribe él mismo la cookie con los parámetros devueltos y después la relee. Un fallo sigue siendo un fallo: no se publica nada, no se desbloquea nada. - Un único reintento, y solo ante un 403 cuyo código sea
owc_bad_nonce,rest_cookie_invalid_nonceorest_nonce_invalid, y solo si el nonce obtenido es realmente distinto. - Lo que el runtime publica es lo que el servidor ha guardado: las categorías rechazadas por el servidor se restan del estado local.
Mientras el POST no haya tenido éxito, no se desenmascara nada, no se desbloquea nada, y no
se emite ninguna señal granted de Consent Mode.
La taxonomía de errores
El runtime distingue nueve causas, cada una con su mensaje visible: network, refused,
ratelimit, unexpected, timeout, browser, config, cookie,
owc_cookie_not_persisted. El estado HTTP y el código del servidor se colocan en el
elemento (data-owc-status, data-owc-code) y se registran una vez con console.warn —
nunca se muestran como texto visible. Así, una captura de pantalla de soporte nombra la
causa sin necesidad de una traza de red.
La API JavaScript del banner
window.OWCBanner.show(); // abre en modo banner
window.OWCBanner.hide(); // cierra
window.OWCBanner.openPreferences(); // abre el panel de preferencias
window.OWCBanner.openDnsmpi(); // abre el panel con marketing, social
// y preferences ya en OFF (entrada CCPA)
window.OWCBanner.reset(); // borra la cookie en el cliente, envía una retirada,
// y después recarga la página
window.OWCBanner.acceptCategory( 'social' ); // devuelve una promesa resuelta a true
// solo si el servidor lo ha guardado
Un bootstrap más ligero se imprime en <head> con prioridad 1 y expone
window.OWConsent: .config, .categories, .geo, .profile, .optOut, .state,
.has(cat), .refresh(), .paint(), .gcmSignals(state). Su configuración es filtrable
mediante owc_bootstrap_config — pero todo lo que hay ahí es público y lo comparte la
caché: no ponga nunca ahí un dato propio de un visitante.
Abrir el panel desde sus páginas
Tres maneras, todas equivalentes:
- el fragmento de URL
#owc-preferences(o#owc-dnsmpi), que se tiene en cuenta al cargar y enhashchange; - cualquier elemento con la clase
owc-open-preferenceso el atributodata-owc-open; - el atributo
data-owc-dnsmpi="1"para forzar el comportamiento «Do Not Sell».
Se emite un evento owc:consent-changed en cada cambio, con la carga útil enviada en el
detalle. Lo escuchan el bootstrap, el botón flotante, el runtime CCPA y el módulo TCF;
usted también puede escucharlo.
Límites del banner
- Todo depende de
wp_head()ywp_footer(): un tema que no los llame no obtiene nada. - Sin
window.fetchywindow.Promiseno es posible ninguna escritura y se muestra el errorbrowser. - Un consentimiento puede registrarse en el servidor sin conservarse en el navegador (dirección del sitio distinta del host visitado, almacenamiento bloqueado, jar lleno). El runtime detecta ese caso, se niega a contarlo como un éxito, muestra un mensaje dedicado y transmite el hecho al servidor en la siguiente escritura.
- El banner no tiene shortcode: se muestra en todas partes o en ninguna.
El bloqueo automático
Es el módulo que convierte una elección en un efecto real. Reescribe las etiquetas portadoras de rastreadores antes de cualquier consentimiento, para todo el mundo, y deja que el runtime las libere en el navegador.
Dos modos, un solo ajuste
compliance_strict, activado por defecto.
- Desactivado (modo laxo): solo se conecta el filtro
script_loader_tag. Dicho de otro modo, solo se reescriben los scripts registrados conwp_enqueue_script(). Un<script>escrito a pelo en el tema, un embed, un iframe, un píxel<img>: nada de eso se toca. - Activado (modo estricto): además del filtro, un búfer de salida captura todo el
documento, y el marcado renderizado a través de la API REST también se trata. En
administración, si la petición es un
admin-ajax.php, un búfer aparte cubre las respuestas que sirven también al front («cargar más», archivos filtrados) — pero únicamente si el manejador ha declarado él mismoContent-Type: text/html.
Lo que nunca se bufferiza
El búfer estricto sale de inmediato para: la administración, las peticiones AJAX, los
feeds, robots.txt, los trackbacks, el cron, los favicons, las peticiones REST, las
peticiones JSON, la vista previa del personalizador, los sitemaps, y los puntos de entrada
wp-login.php, wp-register.php, wp-signup.php, xmlrpc.php, wp-cron.php,
wp-trackback.php — comprobados sobre el nombre exacto del script, nunca como subcadena de
la URL. Sale también para las peticiones del escáner, que necesitan el HTML en bruto.
Las cinco pasadas
La reescritura se hace mediante expresiones regulares calibradas, nunca con un analizador DOM. Tres salvaguardas enmarcan el conjunto: un documento de más de 8 MB se devuelve intacto, un fallo del motor PCRE devuelve el documento intacto, y si el resultado ocupa menos de la mitad del tamaño original, se devuelve el original. Dicho de otro modo, el modo de fallo del bloqueo es «página servida sin bloqueo», nunca «página rota».
Pasada 0 — enmascarado. Los comentarios HTML, <style> y <textarea> se sustituyen por
marcadores basados en caracteres de control, para que ningún patrón posterior encaje dentro
de ellos.
Pasada 1 — <script>. Decisión, en este orden:
- ya tratado → intacto;
- script del propio plugin → intacto;
type="text/plain"o tipo no JS (ld+json,importmap,x-template) → intacto;- URL buscada en
src,data-src,data-rocket-src,data-lazy-src,data-litespeed-src,data-cfsrc— los atributos de los plugins de rendimiento quedan por tanto cubiertos; - host en la lista de permitidos → intacto; recurso first-party que no parece un rastreador → intacto;
- coincidencia con el catálogo sobre la forma
host + ruta; una URLdata:ojavascript:se descodifica y se juzga como un cuerpo en línea; - una regla clasificada como
necessarynunca se bloquea (Stripe.js, reCAPTCHA, Turnstile, cdnjs…): bloquearla no gana ningún cumplimiento y rompe el snippet que la llama; - ninguna coincidencia →
blocker_unknown_script_policy, por defectoallow; - si no, reescritura como
type="text/plain"condata-owc-cat,data-owc-vendor,data-owc-srcy, si el tipo original era particular (module, por ejemplo),data-owc-typepara no degradar el script restaurado.
Los cuerpos en línea se comparan primero con las reglas de catálogo de tipo
inline_signature, y después con catorce firmas ancladas escritas en el código: fbq(,
_fbq.push, gtag(, dataLayer.push(, ga('…, _gaq.push, _paq.push, hjid,
clarity(, ttq.load|track|page, snaptr(, twq(, lintrk(, pintrk(. Excepción
explícita: un gtag('consent', …) a solas, es decir, una declaración de Consent Mode por
defecto sin ningún otro marcador, se sigue ejecutando.
Pasada 2 — <iframe>. Los mismos atributos de URL, más data-original y data-srcset.
First-party o lista de permitidos → intacto. necessary → intacto, porque si no un
formulario con captcha quedaría imposible de enviar. Sin coincidencia →
blocker_unknown_iframe_policy, por defecto block. El iframe bloqueado pasa a
src="about:blank" y se envuelve en un sustituto visual que contiene un botón «Aceptar
Pasada 3 — <img>, solo píxeles de seguimiento. Regla importante: una línea de
catálogo por sí sola nunca basta para neutralizar una imagen. Si la imagen coincide con el
catálogo pero su categoría no es ni analytics ni marketing — un CDN de imágenes, un
servicio de gravatares, un alojamiento de fuentes — se deja intacta, porque de lo contrario
se eliminarían los medios del sitio en lugar de un rastreador. Solo la heurística de
etiqueta puede bloquear: once puntos de recogida conocidos (facebook.com/tr,
px.ads.linkedin.com, ct.pinterest.com, bat.bing.com, analytics.twitter.com,
t.co/i/adsct, tr.snapchat.com, analytics.tiktok.com, google-analytics.com/collect,
googleads.g.doubleclick.net, pixel.quantserve.com) o una imagen de terceros de 1×1
píxel. Entonces el src se sustituye por un GIF transparente, y se eliminan todos los
atributos portadores de URL para que un cargador perezoso no restaure el original.
Pasada 4 — <link>. Condicionada a blocker_block_resource_hints, por defecto
true. Trata solo los hosts que ya están en el catálogo, nunca uno desconocido.
- Las pistas de recursos (
preconnect,dns-prefetch,prefetch,prerender,preload,modulepreload) hacia un tercero catalogado se eliminan, no se difieren: una pista abre una conexión TCP+TLS y revela la IP y la huella TLS del visitante, y después no hay nada que restaurar. - Una hoja de estilo clasificada como
analyticsomarketingse neutraliza. - Google Fonts y los demás hosts de tipografías clasificados como
functionalno se tocan nunca: neutralizarlos deja todo el sitio con la fuente de reserva, sin ninguna ganancia de cumplimiento.
Pasada 5 — <object>, <embed>, <source>, <video>, <audio>. Nunca hay bloqueo
por defecto: solo se trata a un tercero ya catalogado. Se retiran los atributos autoplay y
preload.
Qué cuenta como «first party»
La URL del sitio, la URL de WordPress, la URL de contenido, la URL de los includes, sus
equivalentes de red en multisitio, y la base de las subidas. El www. se elimina al
comparar. Toda ruta que empiece por /wp-content/ o /wp-includes/ es first-party sea
cual sea el host, para cubrir las reescrituras de CDN. Los esquemas que no son HTTP
(data:, blob:, javascript:) nunca son first-party.
Contraexcepción: una URL que contenga gtag, gtm.js, analytics, pixel, fbevents,
hotjar, matomo, piwik o clarity se trata incluso en el host del sitio. Eso es lo
que atrapa un GTM o un Matomo autoalojado, y los proxies first-party.
El runtime de desbloqueo
Se imprime en <head> con prioridad 2. Su configuración JSON solo contiene el nombre de la
cookie y la lista de hosts vigilados — nada que dependa del visitante.
- Lectura de estado: la cookie
owc_consentse descodifica hasta estabilizarse, con un máximo de tres pasadas, porque las cookies escritas antes de la 1.4.3 estaban codificadas dos veces. - Guardia de los scripts inyectados dinámicamente: el setter
HTMLScriptElement.prototype.srcysetAttributese envuelven. Un cargador first-party que asignas.src = 'https://www.googletagmanager.com/gtm.js?id=…'queda interceptado y el elemento se marca antes de ejecutarse. La lista vigilada tiene veinte hosts:googletagmanager.com,google-analytics.com,googleadservices.com,googlesyndication.com,doubleclick.net,connect.facebook.net,static.hotjar.com,script.hotjar.com,clarity.ms,cdn.matomo.cloud,analytics.tiktok.com,snap.licdn.com,sc-static.net,static.ads-twitter.com,bat.bing.com,s.pinimg.com,cdn.segment.com,js.hs-scripts.com,cdn.amplitude.com,cdn.mxpnl.com. Todo lo que no figure ahí pasa sin ser molestado. Los hosts de su lista de permitidos se retiran de esa lista antes de imprimirla. - Reinyección en el orden del documento. Un script externo restaurado bloquea la cola
hasta su
onloaduonerror, con un plazo máximo de 5 segundos para que un proveedor inalcanzable no bloquee el resto. El atributoasyncsolo se aplica si estaba presente en el original — sin eso, un script creado concreateElementse forzaría a asíncrono y el snippet de configuración se ejecutaría antes que su biblioteca. Para un script en línea restaurado,document.writese redirige temporalmente para no borrar el documento. - Retirada del consentimiento: si una categoría ya aplicada pasa explícitamente a
false, el runtime recarga la página. Un script ya ejecutado no se puede descargar (art. 7.3 RGPD). Una categoría simplemente ausente de la carga útil no es una retirada. - Un
MutationObservervuelve a escanear el marcado inyectado después de la carga (AJAX, secciones perezosas). - Un clic en el botón de un embed bloqueado llama a
OWCBanner.acceptCategory(); si la API del banner no existe, el embed se desbloquea localmente, sin persistir nada.
El catálogo de firmas
Tabla {prefix}owc_scripts. El archivo incluido, data/tracker-catalog.json, contiene
175 líneas que cubren 71 proveedores distintos:
| Reparto | Detalle |
|---|---|
| Por objetivo | 110 patrones de URL, 65 nombres de cookies |
| Por tipo | 172 fragmentos de texto, 3 expresiones regulares |
| Por categoría | necessary 55, marketing 37, functional 37, analytics 36, social 10 |
Columnas: pattern, pattern_type (host, regex, inline_signature), match_target
(url o cookie), name, vendor, category, privacy_policy_url, gcm_signal,
tcf_vendor_id, retention_days.
Dos comportamientos que conviene conocer:
- Las líneas con
match_target = 'cookie'nunca las usa el bloqueo. Sirven únicamente para la tabla de cookies de los documentos generados. Compararlas como subcadena de una URL es precisamente lo que rompía el JavaScript de los sitios. - Un patrón de tipo
hostdirigido a una URL, de menos de seis caracteres y sin punto, se rechaza: «fr» o «IDE» encajarían con «frame.js» y «provider.js».
El catálogo se cachea en la caché de objetos (1 hora) y en un transient (12 horas). Un
resultado vacío nunca se cachea. Toda escritura dispara la acción owc_catalog_updated, que
vacía esas cachés.
La pestaña Tracker catalogue permite añadir, modificar, eliminar y buscar reglas, con un filtro «solo las reglas que no pueden dispararse»: patrón vacío o invisible, expresión regular que no compila o que retrocede de forma catastrófica, patrón demasiado corto y sin punto para una URL, categoría que ya no existe. Este diagnóstico se hace en PHP, porque solo el motor PCRE puede decir si una expresión compila.
Ajustes del bloqueo
| Ajuste | Por defecto | Valores |
|---|---|---|
compliance_strict | true | booleano |
blocker_unknown_script_policy | allow | allow, block |
blocker_unknown_iframe_policy | block | allow, block |
blocker_block_resource_hints | true | booleano |
blocker_allowlist | '' | un host por línea |
Puntos de extensión
// Hosts que nunca se bloquean, además de blocker_allowlist.
add_filter( 'owc_blocker_allowlist', function ( array $hosts ) {
$hosts[] = 'cdn.mi-socio.example';
return $hosts;
} );
// Filas del catálogo antes de la validación.
add_filter( 'owc_scripts_catalog', function ( array $rows ) {
$rows[] = array(
'pattern' => 'tracker.example.com',
'pattern_type' => 'host',
'match_target' => 'url',
'name' => 'Ejemplo',
'vendor' => 'Ejemplo S. A.',
'category' => 'analytics',
);
return $rows;
} );
El método público OWC_Blocker::block_html_fragment( $html ) aplica el bloqueo a un
fragmento que no ha pasado por el búfer de salida.
Límites del bloqueo
- Sin
compliance_strict, solo se tratan los scripts encolados por WordPress. - Un script de terceros desconocido se permite por defecto. El bloqueo real se apoya en el catálogo y en las firmas en línea — enriquézcalos.
- La guardia de los scripts inyectados por JavaScript solo cubre veinte hosts; un rastreador fuera de esa lista inyectado por código first-party pasa.
- Las hojas de estilo clasificadas como
functional(fuentes web) se dejan pasar deliberadamente. - Un rastreador catalogado en
<img>pero clasificado como algo distinto deanalyticsomarketingno se neutraliza. - Los medios (
object,embed,source,video,audio) nunca se bloquean por defecto: solo se trata a un tercero ya catalogado. - Un documento de más de 8 MB, un fallo de PCRE o una pérdida de más del 50 % del contenido dan una página servida sin ningún bloqueo, en silencio.
- La retirada del consentimiento provoca una recarga completa de la página.
El escáner de rastreadores
El escáner inventaría lo que sus páginas cargan realmente. Viene desactivado por defecto: actívelo en la pestaña Scanner.
Tres fuentes de detección
Cada hallazgo conserva su procedencia y la información «¿puede el bloqueo actuar sobre esto?».
1. El HTML servido. El escáner recupera una muestra de las URL de su propio sitio y
lee el marcado: <script src> y <script data-owc-src> — así que los scripts ya bloqueados
se ven igualmente —, firmas en línea, <iframe>, hojas de estilo externas e imágenes cuya
URL contiene 1x1, pixel, track, beacon o impression. No se ejecuta nada de
JavaScript: lo que un gestor de etiquetas inyecta en tiempo de ejecución es invisible para
esta pasada.
2. Las cabeceras Set-Cookie. Son las únicas cookies que el barrido de servidor puede
demostrar, incluidas las cookies HttpOnly que una sonda de navegador nunca verá. Se
marcan como no bloqueables: nada en el bloqueo del cliente puede retener una cookie
emitida por el servidor. Las cookies del propio plugin se ignoran.
3. La sonda de navegador. Se imprime en el pie de página únicamente para un usuario
conectado que tenga manage_options, y solo si el escáner está activado. Fotografía
document.cookie al cargar, observa las mutaciones del DOM — eso es lo que atrapa lo que
inyecta un gestor de etiquetas —, consulta document.cookie cada 5 segundos para las
cookies escritas en el cliente, y transmite sus observaciones cada 2 segundos y en
beforeunload. La respuesta que lleva la sonda se marca como no cacheable.
Límite reconocido en el código: un administrador suele haberlo aceptado ya todo. Por tanto, la sonda describe el estado posconsentimiento, no el estado preconsentimiento.
El barrido
La cola de URL se construye para cubrir plantillas, no páginas: primero las URL centrales (portada, página de inicio estática, página de entradas, y las cuatro páginas de políticas enlazadas), y después un entrelazado en carrusel por familia — hasta 30 páginas, 15 entradas, 5 por tipo de contenido público personalizado, 3 categorías y 3 etiquetas. Sin ese entrelazado, treinta páginas casi idénticas se comían todo el presupuesto y nunca se llegaba a las plantillas de WooCommerce.
El filtro owc_scanner_urls permite añadir URL, pero el resultado se vuelve a restringir
al host del sitio: el escáner nunca sale de su dominio.
Cada petición se hace siguiendo las redirecciones a mano (dos saltos como máximo, cada salto
vuelto a comprobar contra el host del sitio), con una respuesta limitada a 2 MB, una
cabecera Cache-Control: no-cache, no-store y un parámetro de URL único para no leer una
página de caché, y un user-agent OW-Consent-Scanner/<versión>. Una respuesta que no sea
HTML se cuenta como omitida, no como un fallo.
Se envía un secreto de omisión en la cabecera X-OWC-Scanner para que el bloqueo se
retire y deje ver el HTML en bruto. Se compara en tiempo constante, y la cabecera se
comprueba en primer lugar para que un visitante corriente ni siquiera dispare la lectura de
la opción.
Presupuesto, bloqueo, reanudación
| Restricción | Valor |
|---|---|
| URL por ejecución | scanner_max_urls, por defecto 25, límites 1 a 500 |
| Tiempo de espera por petición | scanner_timeout, por defecto 8 s, límites 1 a 60 |
| Presupuesto de reloj por lote | max_execution_time − 10 s, si no 45 s, acotado entre 5 y 60 s |
| Bloqueo de ejecución | 15 minutos |
| Vida útil de la cola | 6 horas |
Si el presupuesto se agota, se programa una reanudación un minuto más tarde y el barrido continúa donde se había quedado. Un segundo lanzamiento mientras un barrido está en marcha responde «ya hay un escaneo en curso». Cortocircuito: si tres peticiones fallan sin que se haya leído ninguna página, el barrido se detiene en lugar de quemar su tiempo de espera en veintidós URL más — el caso típico de un sitio cuyo loopback HTTP está bloqueado.
Las firmas en línea reconocidas
Veintitrés agujas, agrupadas en veintiuna etiquetas: gtag('config', gtag('event',
gtag('js', gtm.start, ga('create', ga('send', fbq('init', fbq('track',
_paq.push, (h.hj=h.hj, clarity('init', clarity.ms/tag, mixpanel.init,
amplitude.init, amplitude.getInstance, _linkedin_partner_id, snaptr('init',
ttq.load, pintrk("load", pintrk('load', rdt('init', criteo_q.push, _etmc.push.
La clave de deduplicación es la firma, nunca una huella del cuerpo: un snippet real de gtag o de Pixel contiene valores propios de la página, lo que daría una línea por página.
El bucle de realimentación: hallazgo → catálogo → bloqueo
Es lo que hace útil al escáner. Clasificar un rastreador escribe una regla en el catálogo de bloqueo. Un clic en la categoría de un hallazgo hace tres cosas: escribe la categoría en la fila, memoriza su decisión manual, y crea la regla correspondiente. Al final de cada barrido completo, se hace la misma operación en masa para lo que el catálogo aún no sabe clasificar.
Reglas de conversión:
- un hallazgo de tipo
cookiese convierte en una reglamatch_target = cookiesobre el nombre de la cookie; - un hallazgo de tipo
inline_scriptescribe una regla solo si se recupera la aguja original. Sin ella no se escribe ninguna regla: una regla construida a partir de la etiqueta «Google gtag (config)» estaría muerta para siempre y encajaría con el nombre del hallazgo, lo que lo haría pasar por «cubierto» eternamente; - si no, una regla
hostsobre el dominio, rechazada si el dominio está vacío.
Sus decisiones manuales se guardan aparte (500 como máximo) y se vuelven a aplicar en cada nueva observación, porque la marca «confirmado» significa también «una regla de catálogo ha encajado» y por tanto no puede llevar sola la información «el administrador lo ha decidido». Reclasificar como «sin categorizar» elimina la entrada: es una marcha atrás de verdad.
El catálogo se compara por el patrón más largo primero, para que una regla amplia
(google-analytics.com) no machaque la regla más precisa que usted ha escrito
(www.google-analytics.com).
Programación, alertas, retención
scanner_frequency acepta hourly, twicedaily, daily y weekly — pero la
administración solo ofrece las recurrencias que su instalación conoce realmente. Por
defecto: weekly. Desactivar el escáner desprograma los dos eventos.
Dos correos distintos, nunca los dos a la vez:
- Rastreadores sin categorizar — enviado únicamente para identificadores realmente nuevos, con una memoria limitada a 500 entradas.
- «El barrido no ha podido leer este sitio» — limitado a un mensaje por semana y por firma de error. Es el modo de fallo más grave, porque un barrido que no lee nada no produce ningún hallazgo y, por tanto, ninguna alerta del primer tipo.
Destinatario: scanner_alert_email, y si no, la dirección de administración del sitio.
Retención: 90 días. Los hallazgos no revisados en 90 días se eliminan, en cuanto se haya leído al menos una página — no solo cuando termina un barrido.
El panel de cobertura
Es la parte más importante de la pestaña Scanner, y alimenta también el recuadro de advertencia de los documentos generados. Responde siempre a estas preguntas:
- ¿se ha lanzado ya algún barrido?
- ¿el último terminó, o lo interrumpió su presupuesto de tiempo?
- ¿cuántas páginas se han recuperado realmente (no retirado de la cola)?
- ¿cuántas no se han podido recuperar, y cuál fue el primer error?
- ¿algún navegador ha informado ya de algo, o no se ha observado nunca JavaScript?
- ¿cuál es la ventana más allá de la cual un rastreador no revisado desaparece?
Un barrido que no ha alcanzado ninguna página se presenta como un fallo, no como un resultado limpio. Es este panel el que hay que leer antes de publicar una política de cookies construida sobre esos hallazgos.
La pestaña Scanner
Tres tarjetas (rastreadores detectados, sin categorizar, último barrido y próxima
ejecución), el panel de cobertura, un botón «lanzar un escaneo ahora», el formulario de
programación, y después la lista de hallazgos: filtros por categoría, búsqueda de texto,
filtro por tipo de elemento (script, inline_script, iframe, stylesheet, pixel,
cookie, link, preconnect), filtro por estado, exportación CSV, paginación de 25 en 25.
Clasificación individual o en masa — el selector de masa tiene un primer elemento vacío
desactivado, para que un envío accidental no se convierta en una desclasificación general.
Ajustes del escáner
| Ajuste | Por defecto | Límites |
|---|---|---|
scanner_enabled | false | booleano |
scanner_frequency | weekly | hourly, twicedaily, daily, weekly |
scanner_max_urls | 25 | 1 a 500 |
scanner_timeout | 8 | 1 a 60 |
scanner_probe_mode | admins | admins, off |
scanner_alert_email | '' | dirección de email |
A tener en cuenta:
scanner_probe_modeno se consume en esta versión. El código lo señala explícitamente — la condición de impresión de la sonda ya no lee este ajuste y se basa únicamente enscanner_enabled. Guardaroffalmacena el valor sin desactivar la sonda. Para detener realmente la sonda, desactive el escáner.
Límites del escáner
- El barrido de servidor no ejecuta nada de JavaScript. Sin la sonda, lo que inyecta un gestor de etiquetas no aparece en el inventario.
- La sonda está reservada a los administradores conectados y no existe ningún modo que la ejecute para un visitante corriente: eso exigiría su propia información previa.
- Una cookie puesta por la cabecera
Set-Cookiese detecta pero no es bloqueable. - El barrido nunca sale del dominio y está limitado a 25 URL por defecto: un sitio grande nunca queda cubierto por completo.
- En un alojamiento donde el loopback HTTP está bloqueado (autenticación HTTP de preproducción, cortafuegos), el barrido no lee nada.
- Un hallazgo no revisado en 90 días desaparece, y por tanto desaparece también de la política de cookies.
- El escáner depende de WP-Cron: en un sitio con
DISABLE_WP_CRONy sin cron de sistema, el barrido programado no se dispara.
El generador de documentos legales
Los cuatro documentos
| Tipo | Contenido | Idiomas disponibles |
|---|---|---|
cookie_policy | Política de cookies | francés, inglés, portugués |
privacy_policy | Política de privacidad | francés, inglés, portugués |
legal_notice | Aviso legal | solo francés |
terms | Condiciones generales | solo francés |
Cada documento generado es una página de WordPress, versionada mediante metadatos:
_owc_policy_type, _owc_policy_version (incrementada en cada generación),
_owc_policy_hash (huella SHA-256 del HTML), _owc_policy_generated_at,
_owc_policy_profile, _owc_policy_lang, _owc_policy_manual_edit.
Tras la generación, el enlace se reinyecta en los ajustes correspondientes
(link_cookie_policy, link_privacy_policy, link_legal_notice, link_terms), y publicar
una política de privacidad actualiza la página de política declarada en WordPress.
Dos cerrojos antes de publicar
Cerrojo 1 — los campos obligatorios. La generación se rechaza con la lista de las claves vacías, en lugar de publicar cláusulas con huecos.
| Documento | Campos exigidos |
|---|---|
| Todos | legal_company_name, legal_company_email |
| Salvo la política de cookies | + legal_company_address, legal_country |
| Aviso legal | + legal_company_phone, legal_publication_director, legal_host_name, legal_host_address, legal_host_phone |
| Aviso legal, países FR / BE / LU | + legal_company_legal_form, legal_company_reg_number |
Cerrojo 2 — el idioma. El generador se niega a publicar un documento redactado en un idioma que la jurisdicción no utiliza. En vista previa, el documento sale con una banda roja de advertencia; en publicación, es un rechazo en redondo. En la pestaña Policies, el botón de generación queda oculto cuando no existe ninguna plantilla para el tipo y el perfil activos, en lugar de mostrarse y fallar sistemáticamente.
Resolución del idioma
| Perfil | Idioma del documento |
|---|---|
quebec | francés |
lgpd | portugués |
gdpr con legal_country ∈ {FR, BE, LU} | francés |
| Todo lo demás | inglés |
La advertencia obligatoria
Mientras policy_disclaimer esté activado — y lo está por defecto — cada documento
termina con un bloque que dice que es una plantilla generada automáticamente, que debe
revisarla un profesional cualificado antes de publicarla, y que lleva la fecha de generación
y la versión del plugin. Desactívelo solo con pleno conocimiento de causa.
La tabla de rastreadores — las reglas de honestidad
La tabla publicada en la política de cookies viene de su tabla de escáner, no de una base de terceros. Cinco reglas gobiernan lo que muestra.
- Ventana de frescura: solo se publican los hallazgos revisados en los últimos 90 días. Si la columna de última observación aún no existe porque una migración no ha pasado, la ventana se ignora en lugar de publicar «ningún rastreador» en un sitio que sí los tiene — la infradivulgación es la única dirección en la que un documento legal nunca debe fallar.
- Recuadro de advertencia al principio de la tabla, derivado del panel de cobertura del escáner: barrido nunca lanzado, ninguna página leída, barrido interrumpido, N URL fallidas, ninguna observación con JavaScript activo, ventana de N días. Un barrido incompleto se divulga, no se publica como un inventario terminado.
- Los rastreadores sin clasificar no se ocultan: tienen su propia sección. Son los que nadie ha examinado.
- Una cookie observada en una cabecera
Set-Cookiese marca con un signo distintivo, con una nota que explica que la deposita el servidor y que ningún bloqueo de cliente puede retenerla. Por tanto, no debe presentarse como condicionada al consentimiento, y la cláusula que afirma que los rastreadores sin clasificar solo se depositan tras el consentimiento lleva la excepción correspondiente. - El nombre del responsable y el enlace a su política vienen del catálogo, únicamente para las reglas que apuntan a una URL. Los patrones de nombre de cookie quedan excluidos: en un documento legal no se afirma quién trata datos sin pruebas.
Las cookies del sitio y las duraciones publicadas
La sección «cookies depositadas por este sitio» enumera las cookies del propio plugin:
owc_consent (duración derivada del ajuste de renovación), euconsent-v2 si TCF está
activado, owc_geo si la detección regional está activada, owc_gpc (duración de
sesión), owc_gpc_notice (5 minutos), más las cookies de inicio de sesión y de ajustes de
WordPress.
La tabla de plazos de conservación saca sus cifras de los ajustes reales —
ledger_retention_days, retention_form_data_days, retention_dsar_days y la duración de
renovación del consentimiento. No se publica ningún valor decorativo. La duración de la
cookie de consentimiento se deriva del mismo cálculo que la propia cookie, lo que
garantiza que un ajuste de 0 meses publique «13 meses» y no «nunca».
Detección del tipo de actividad (condiciones generales)
Las condiciones generales tienen 21 secciones, con conmutación vendedor/prestador, productos/servicios, presupuesto/pedido, y cláusulas específicas por tipo de actividad.
legal_business_type (auto, vitrine, rental, ecommerce, services, saas,
content) es la fuente de verdad. Cuando vale auto, una heurística examina el sitio —
presencia de WooCommerce, registro abierto, plugins de suscripción, página de tarifas,
páginas de servicios, y después densidad de palabras clave en los últimos cincuenta
contenidos publicados — pero su veredicto nunca se imprime en un documento publicado:
solo sirve para elegir las cláusulas opcionales.
Las citas de artículos franceses solo se insertan si legal_country vale FR. Bélgica tiene
sus propias citas (CDE art. VI.45 §1, art. VI.47, Servicio de mediación para el consumidor);
Luxemburgo y Quebec reciben una formulación neutra. Citar un texto sin verificar sería el
defecto, no el remedio.
Otros comportamientos
- La fecha del documento se formatea en la configuración regional del documento. Si el
paquete de traducción correspondiente no está instalado, el formato pasa a
dd/mm/aaaa: un documento francés no puede abrirse con «4 September 2026». - Edición manual detectada: si el contenido almacenado ya no se corresponde con su huella, se guarda una revisión antes de sobrescribir y la pantalla muestra el distintivo «modificaciones manuales sustituidas», con un enlace a las revisiones.
- El generador nunca degrada: una página publicada sigue publicada aunque se desmarque la casilla «publicar», y un título o un enlace permanente que usted haya renombrado sobrevive a una regeneración.
- Inyección en el menú del pie de página:
auto_footer_menu_inject, desactivado por defecto. El plugin no modifica su sitio público sin petición explícita. Ubicaciones reconocidas cuando lo activa:footer,footer-menu,footer_menu,footer-1,footer_1,secondary,legal.
Puntos de extensión
// Cuerpo en bruto de la plantilla, con las {{variables}} todavía en su sitio.
add_filter( 'owc_policy_template', function ( $html, $type, $profile ) {
return $html;
}, 10, 3 );
// Las variables inyectadas en la plantilla.
add_filter( 'owc_policy_vars', function ( array $vars, $type, $profile ) {
return $vars;
}, 10, 3 );
// El HTML final, con las variables ya sustituidas.
add_filter( 'owc_policy_html', function ( $html, array $vars ) {
return $html;
}, 10, 2 );
Aportar su propio cuerpo mediante owc_policy_template desactiva el cerrojo de idioma:
un sitio que aporta su texto asume su idioma. Es la vía oficial para publicar un aviso legal
que no esté en francés.
Hay cuatro bloques pregenerados disponibles como variables reservadas:
{{__trackers_table__}}, {{__categories_list__}}, {{__retention_table__}},
{{__jurisdictional_rights__}}. Todas las variables están ya escapadas en el contexto de
su uso. Tres métodos públicos son reutilizables por una plantilla de terceros:
OWC_Policies::build_cookie_table(), build_data_retention_table() y
render_first_party_cookies().
Rutas REST
| Método | Ruta | Parámetros | Acceso |
|---|---|---|---|
POST | /owc/v1/policies/generate | type (obligatorio), publish (booleano, por defecto false) | manage_options |
GET | /owc/v1/policies/preview | type (obligatorio) | manage_options |
El HTML de la vista previa pasa por wp_kses_post() antes de devolverse.
Límites del generador
- Son plantillas, no asesoramiento jurídico. Hágalas revisar.
- El aviso legal y las condiciones generales solo existen en francés. Cualquier otra jurisdicción choca con el cerrojo de idioma, salvo que usted aporte su propio cuerpo.
- Solo tres idiomas: francés, inglés, portugués.
- Cuatro países del EEE tienen autoridad nombrada; en el resto, una formulación genérica.
- La tabla de rastreadores vale lo que valga su barrido — y el documento lo dice.
- La clase del generador pesa unos 440 KB de plantillas jurídicas: se carga de forma perezosa, únicamente cuando se solicita realmente uno de los tres puntos de entrada.
El registro de consentimiento
El registro es la respuesta al artículo 7.1 del RGPD: poder demostrar que la persona consintió. Está activado por defecto.
La tabla
{prefix}owc_ledger, con todas las fechas en UTC:
| Columna | Tipo | Contenido |
|---|---|---|
id | bigint | Clave primaria |
created_at | datetime | Marca de tiempo UTC |
visitor_token | char(32) | Token seudónimo del navegador, 32 caracteres hexadecimales |
event | varchar(20) | accept_all, reject_all, save_preferences, withdraw, auto, gpc_opt_out |
categories | varchar(255) | Lista de las categorías concedidas |
profile | varchar(20) | Perfil de cumplimiento vigente en el momento de la acción |
source | varchar(60) | banner, preferences, footer_link, api, auto |
ip_pseudonymous | varchar(45) | IP truncada |
ua_hash | char(64) | Huella salada del user-agent |
page_url | varchar(500) | Página donde tuvo lugar la acción |
prev_hash | varchar(128) | Huella de la fila anterior |
row_hash | varchar(128) | Huella de esta fila |
policies_hash | char(64) | Huella de los documentos vigentes |
banner_revision | varchar(40) | Huella del banner realmente mostrado |
plugin_version | varchar(20) | Versión del plugin en el momento de la escritura |
Las tres últimas columnas permiten reconstruir lo que el visitante vio, no solo lo que marcó.
La cadena
Cada fila se firma mediante HMAC sobre una serialización canónica prefijada por longitud de su contenido y de la huella de la fila anterior. La génesis es una secuencia de 64 ceros. La escritura se hace dentro de una transacción, con un bloqueo de fila sobre la última entrada: es ese bloqueo el que serializa realmente las escrituras concurrentes e impide que la cadena se bifurque. Se toma un bloqueo con nombre de MySQL como segunda barrera, en la medida de lo posible: si se deniega, la anexión continúa y se dispara una acción para que usted pueda trazarlo.
Los valores se truncan antes de firmar, para que el valor firmado sea exactamente el valor almacenado.
La honestidad sobre la resistencia a la manipulación
El plugin determina él mismo la procedencia de su clave de firma:
| Procedencia | Condición | Veredicto |
|---|---|---|
constant | OWC_LEDGER_KEY está definida | A prueba de manipulación |
wp-config | AUTH_KEY y AUTH_SALT están definidas, no vacías, distintas entre sí y sin la frase por defecto | A prueba de manipulación |
database | En cualquier otro caso | No a prueba de manipulación |
En el tercer caso, WordPress guarda los salts en la base de datos: cualquiera con acceso a la base puede volver a firmar la cadena. El plugin lo informa en el resultado de verificación y muestra un aviso de administración que lo dice con todas las letras. Se conserva una huella no reversible de la clave, lo que permite distinguir una rotación de salts de una reescritura.
Esa es la razón por la que la sección Instalación recomienda definir
OWC_LEDGER_KEY.
La verificación
El botón «Verify the chain now» de la pestaña Audit ledger — o la ruta
GET /owc/v1/ledger/verify — recorre toda la cadena en lotes de 500 filas.
Se reconocen tres esquemas de firma: el esquema canónico actual, un esquema histórico anterior, y un esquema sin clave procedente de las primerísimas versiones. Este último produce una huella que cualquiera que tenga la base de datos puede recalcular: nunca se considera válido, se cuenta aparte y se señala como una rotura. Un resumen sin clave no es una prueba.
Roturas informadas: hash_mismatch, chain_break, bad_genesis, unkeyed_rows,
table_emptied, tail_truncated, head_mismatch, count_mismatch.
El resultado incluye, entre otros: ok, total, checked, table_total, broken_at,
breaks, break_count, partial, legacy_rows, unverifiable_rows, anchor,
anchor_ok, key_source, tamper_evident, key_rotated.
El ancla de cabecera
Se conserva un ancla fuera de la tabla: identificador, huella, número de filas, marca de tiempo. Sin ella, eliminar las filas más recientes o vaciar la tabla no dejaría ningún rastro. Se dispara una acción cada vez que el ancla se desplaza, y el código invita explícitamente a replicarla fuera de la base — archivo, syslog, endpoint externo — para hacer detectable una reescritura global.
El plugin se niega a sobrescribir un ancla existente y señala con honestidad que un ancla derivada de la propia tabla solo hace detectables los truncamientos posteriores.
La retención
ledger_retention_days, por defecto 1825 días (5 años), límites 0 a 3650. El valor 0
significa conservación ilimitada.
La poda se ejecuta en su propio cron diario y solo retira un prefijo contiguo: nunca un hueco en mitad de la cadena. La huella de la última fila eliminada se memoriza para que el resto siga siendo verificable. Tope por ejecución: 20 000 filas (40 lotes de 500). Un retraso muy grande de poda se reabsorbe, por tanto, a lo largo de varios días.
Minimización de datos
- Dirección IP: IPv4 con el último octeto puesto a cero (se conserva el /24), IPv6 truncada al /48 con los 80 bits restantes a cero. El enmascarado se hace sobre la forma binaria; una dirección IPv6 que mapea una IPv4 se trata como IPv4.
- User-agent: solo se almacena una huella salada, nunca la cadena.
page_url: validada contra los hosts del sitio. El llamante manda — si aporta el valor, aunque esté vacío, esa es la respuesta; elReferersolo se consulta si no ha dicho nada en absoluto, y se valida de la misma manera.- Token de visitante: 32 caracteres hexadecimales salidos de un generador criptográfico, sin vínculo con ninguna identidad.
El algoritmo
ledger_hash_algo acepta sha256 (por defecto) y sha3-256, intersecados con los
algoritmos que su PHP soporta realmente. El ajuste se muestra pero no se puede modificar
en la administración: cambiar de algoritmo haría fallar la verificación de todas las filas
existentes. Las columnas están dimensionadas con más holgura, pero la elección real se
limita a esos dos algoritmos.
La pestaña Audit ledger
Lista paginada de 25 en 25: identificador, fecha local y marca de tiempo UTC en bruto, token de visitante, evento, categorías, perfil, IP seudonimizada, y el eslabón de la cadena (huella anterior → huella actual, truncadas en pantalla, valor completo en un tooltip).
Filtros: token de visitante (32 hexadecimales), evento, perfil, rango de fechas — que se introducen en la zona horaria del sitio y se comparan con las marcas de tiempo UTC realmente almacenadas.
Exportaciones CSV y JSON. El JSON lleva un sobre (formato, algoritmo, procedencia de la clave) para que una autoridad pueda volver a verificar el extracto sin disponer del resto de la cadena. Todas las celdas CSV están neutralizadas contra la inyección de fórmulas de hoja de cálculo.
Consultar el registro mediante la API
GET /owc/v1/ledger acepta page, per_page (1 a 200, por defecto 50), visitor_token,
from y to. Los tres últimos permiten responder a una solicitud de acceso (art. 15) sin
recorrer toda la cadena. Los límites se leen en UTC, y una fecha con formato AAAA-MM-DD se
amplía al día entero. Los filtros realmente aplicados se devuelven en la respuesta: un
filtro rechazado por las comprobaciones no debe leerse como «aquí está todo el registro».
Qué ocurre cuando el registro rechaza una escritura
Es el comportamiento más importante de todo el plugin. Si el registro está activado y la escritura de la fila falla, la cookie de consentimiento se anula y la respuesta es un 503. No se almacena nada y no se libera ningún rastreador.
El razonamiento es directo: un consentimiento que no se puede demostrar no debe reclamarse. Véase Solución de problemas para saber cómo proceder.
Puntos de extensión
add_action( 'owc_consent_updated', function ( $categories, $event, $source ) {}, 10, 3 );
add_action( 'owc_consent_cookie_not_sent', function ( $cookie, $event ) {}, 10, 2 );
add_action( 'owc_ledger_lock_failed', function ( $event, $token ) {}, 10, 2 );
add_action( 'owc_ledger_write_failed', function ( $error, $event, $token ) {}, 10, 3 );
add_action( 'owc_ledger_anchor', function ( array $anchor ) {} );
add_action( 'owc_ledger_pruned', function ( $deleted, $days ) {}, 10, 2 );
owc_ledger_anchor es el hook que hay que usar para replicar el ancla fuera de la base de
datos.
Límites del registro
- La resistencia a la manipulación es condicional, y el propio plugin lo dice.
- Las filas anteriores a la 1.2.0 llevan un resumen sin clave: nunca se vuelven a firmar — volver a firmarlas permitiría a un atacante hacer que se refirmase un historial falsificado — e impiden el veredicto «cadena intacta» mientras sigan ahí.
- La retención está limitada a 20 000 filas por ejecución diaria.
- El registro no lo elimina el eliminador de datos personales de WordPress: es una cadena de hash, retirarla destruiría la prueba que existe precisamente para sostenerla, y solo contiene un token seudónimo y una IP truncada. Un mensaje explica esta decisión al interesado.
El portal de derechos (DSAR)
El portal permite a una persona ejercer sus derechos desde una página de su sitio. Está activado por defecto, pero solo aparece allí donde usted coloca el shortcode.
El formulario
[owc_dsar_form]
Atributos:
| Atributo | Por defecto | Función |
|---|---|---|
types | access,rectification,erasure,portability,restrict,object,optout | Tipos ofrecidos, separados por comas |
title | «Ejercer mis derechos sobre mis datos personales» | Título del bloque |
submit_label | «Enviar mi solicitud» | Etiqueta del botón |
Si dsar_enabled está desactivado, el shortcode muestra «el portal está desactivado» y
la ruta REST responde 404: la desactivación es realmente del lado del servidor.
La lista que usted pide se interseca con la lista que el servidor acepta: una opción rechazada por el endpoint nunca se le ofrece al visitante.
Los ocho tipos de solicitud
access, rectification, erasure, portability, restrict, object, optout,
withdraw.
withdraw se retira deliberadamente de lo que ofrece el formulario. Retirar el
consentimiento de cookies es instantáneo en el panel de preferencias; hacerlo pasar por un
procedimiento escrito de 30 días haría la retirada más difícil que el consentimiento
(art. 7.3 RGPD). Un enlace bajo el formulario abre directamente el panel de preferencias
para ese caso.
El filtro owc_dsar_types es la fuente de verdad única: de él deriva la lista de permitidos
del endpoint.
El ciclo completo
1. Envío. POST /owc/v1/dsar. Tres protecciones de servidor, en este orden:
- Trampa para bots: un campo oculto relleno provoca un rechazo 400 con el mensaje genérico de un envío mal formado — un bot no aprende nada sobre el motivo de su rechazo. Del lado del navegador, el formulario muestra incluso el mensaje de éxito sin enviar nada.
- Declaración (art. 12.6): la casilla «confirmo que ejerzo un derecho sobre MIS propios datos personales» debe estar marcada, y eso se comprueba en el servidor, no solo en el navegador. Se guarda junto con la solicitud.
- Plazo mínimo de 3 segundos entre la aparición del formulario y el envío, del lado del cliente.
Límites de peticiones: 3 por hora y por dirección IP, 3 por día y por dirección de email afectada — la dirección la elige el atacante, así que es esa la que hay que limitar — y 30 por hora para todo el sitio.
La fila se inserta con el estado pending, un token de 64 caracteres del que solo se
almacena la huella SHA-256, una fecha de caducidad del token y un vencimiento de
respuesta. El identificador no se devuelve: sin enumeración, sin fuga de volumetría. La
respuesta vale {ok, mail_sent, message}, y mail_sent refleja el fallo real de envío.
Al DPD no se le notifica en esta fase. De lo contrario, un llamante anónimo haría salir dos correos por envío desde su dominio.
2. Correo de verificación. El enlace apunta a su página de inicio con el token en claro
como parámetro. Validez: dsar_token_ttl_days, por defecto 7 días, límites 1 a 90. El
correo lleva una cabecera Reply-To pero nunca un From reescrito — reescribir el
sobre es lo que rompe SPF. La dirección de respuesta es dsar_email, y si no
legal_dpo_email; nunca la dirección de administración del sitio, que no debe
publicarse.
3. Confirmación de identidad. El enlace abre una página que no ejecuta nada: la confirmación se hace mediante un POST protegido por un nonce. Eso es lo que impide que un escáner de enlaces de correo (Safe Links, URL Defense, vista previa de bandeja de entrada) confirme una identidad en lugar de la persona.
La página es un documento HTML autónomo, servido fuera del tema, con noindex, nofollow y
cabeceras no cacheables. Los intentos están limitados a 30 por hora y por IP. Las respuestas
son distintas: 404 para un enlace desconocido, 200 para un enlace ya confirmado, 410 para un
enlace caducado, 403 para un nonce vencido, 500 para un fallo de escritura, 200 para una
confirmación.
4. Lo que dispara la confirmación.
- El estado pasa a
verified. - El vencimiento se recalcula a partir de la verificación. Artículo 12.3: el plazo corre desde que la solicitud está completa, no desde un envío nunca confirmado.
- El token se quema. Su huella se conserva deliberadamente, para que quien vuelva a abrir su enlace lea «ya confirmado» en lugar de un 404 que le pida empezar de cero. El uso único lo garantiza la comprobación de estado.
- Se registran: la fecha de confirmación, la IP truncada, el token seudónimo del navegador — el único puente posible entre el registro de consentimiento y la solicitud — y el identificador de la solicitud de WordPress asociada.
- Se abre una solicitud nativa de WordPress con la identidad ya probada, así que
directamente en su cola de Herramientas → Exportar / Borrar datos personales. Eso hace
que la ejecución cubra todos los plugins del sitio, no solo OW Consent.
Correspondencia:
accessyportability→ exportación;erasure→ borrado; los demás tipos no abren solicitud nativa. - Se notifica al responsable en
dsar_emailydsar_notify_email, con reserva en la dirección de administración si ninguna es válida.
5. Ejecución. Un panel encima de la pestaña DSAR requests enumera las 20 solicitudes verificadas, ordenadas por vencimiento, con los días restantes o de retraso.
| Acción | Comportamiento |
|---|---|
| Descargar los datos (JSON) | Rechazo 409 si la identidad nunca se ha confirmado. El archivo se llama dsar-<id>-<AAAAMMDD>.json |
| Borrar los datos | Se ofrece únicamente para una solicitud de tipo erasure, y solo tras la confirmación de identidad. La solicitud se cierra antes del borrado, porque si no el texto libre de la solicitud en curso sobreviviría a su propia ejecución |
| +2 meses | Prórroga del art. 12.3, una sola vez, con un motivo de 500 caracteres como máximo. El vencimiento retrocede 60 días, los recordatorios se rearman, y el interesado recibe un correo con el nuevo vencimiento y los motivos |
| Cerrar | Estado resolved o rejected, nota obligatoria. No se puede reescribir una solicitud ya cerrada, e imposible marcar como «atendida» una identidad nunca confirmada |
| Reenviar el enlace | Emite un nuevo token, lo que invalida el anterior |
Cinco estados: pending, verified, resolved, rejected, expired.
La pantalla nunca carga el token ni su huella, y no muestra el nombre del solicitante.
6. Vigilancia del plazo. En el cron diario: se recorren hasta 200 solicitudes verificadas por vencimiento, un recordatorio siete días antes, un recordatorio al superarse el plazo, una sola vez cada uno. Un aviso de administración señala las solicitudes fuera de plazo y las que están a menos de siete días.
7. Purga. Los envíos nunca verificados se eliminan cuando caduca su token, y las
solicitudes cerradas se eliminan pasados los retention_dsar_days. La cuenta arranca en el
cierre, no en el envío.
El paquete de portabilidad
La exportación produce un documento con formato ow-consent/dsar-export, con la marca de
tiempo, el sitio, el interesado, la solicitud y los grupos de datos. Llama a todos los
exportadores registrados del sitio, pagina hasta 50 páginas por exportador y se detiene a
los 20 segundos. El borrado hace lo mismo con los eliminadores, con los mismos límites.
Filtro: owc_dsar_export_bundle( $bundle, $email, $id ).
Lo que cubren la exportación y el borrado depende, por tanto, de los plugins instalados en su sitio: un plugin que no registre ninguno de estos hooks hay que tratarlo a mano.
Integración con las herramientas nativas de WordPress
- Exportador registrado bajo la clave
ow-consent, con dos grupos: las solicitudes de derechos y el registro de consentimiento. - Eliminador:
- los envíos nunca confirmados se eliminan — ningún valor probatorio, solo datos personales;
- a las solicitudes cerradas se les borran el texto libre y el nombre, y se conserva la
fila mínima (fecha, tipo, estado) como prueba (art. 5.2) hasta
retention_dsar_days; - una solicitud todavía abierta se conserva, con un mensaje que explica que hay que responderla antes de eliminarla;
- el registro de consentimiento no se elimina, por la razón explicada más arriba.
- El plugin alimenta también el borrador de política de privacidad de WordPress.
La información bajo el formulario
Bajo el formulario, un bloque desplegable contiene la información del artículo 13: identidad
y dirección del responsable, contacto de privacidad, finalidad y base jurídica (art. 6.1.c),
datos recogidos, destinatarios y plazo de conservación, plazo de respuesta y posibilidad de
prórroga, autoridad de control competente, enlace a la política. Filtro:
owc_dsar_form_notice( $html, $context ).
La autoridad mostrada viene de la derivación descrita en Los once perfiles de cumplimiento, y solo se acepta si empieza por mayúscula: leer el ajuste en bruto nombraría a la autoridad francesa en un sitio sudafricano, indio, australiano o californiano.
Sin JavaScript, el botón de envío se oculta y un mensaje propone la dirección de contacto — para que el navegador no haga nunca un envío nativo que pondría la dirección del solicitante en la URL, y por tanto en los registros de todos los servidores y proxies atravesados.
Ajustes DSAR
| Ajuste | Por defecto | Límites |
|---|---|---|
dsar_enabled | true | booleano |
dsar_email | '' | contacto público y destinatario de las notificaciones |
dsar_notify_email | '' | destinatario adicional |
dsar_response_days | 30 | 1 a 30 — nunca más de un mes |
dsar_token_ttl_days | 7 | 1 a 90 |
retention_dsar_days | 1095 (3 años) | 1 a 3650 |
Puntos de extensión
add_action( 'owc_dsar_submitted', function ( $type, $email, $message ) {}, 10, 3 );
add_action( 'owc_dsar_verified', function ( $id, array $row ) {}, 10, 2 );
add_action( 'owc_dsar_fulfilled', function ( $id, $what, $trace ) {}, 10, 3 );
add_action( 'owc_dsar_mail_failed', function ( $id, $email, $kind ) {}, 10, 3 );
Límites del portal
- La identidad se apoya en un solo factor: el ida y vuelta por email. Cualquier prueba adicional al amparo del art. 12.6 se anota a mano en las notas de la solicitud.
- Un envío de correo defectuoso bloquea el ciclo: la respuesta expone
mail_sent: falsey el formulario muestra un mensaje que remite a la dirección de contacto, pero la solicitud siguependingy el plazo legal no arranca. - Los recordatorios de vencimiento y la purga dependen de WP-Cron.
- La tabla de solicitudes se elimina siempre al desinstalar el plugin, sea cual sea su ajuste. Exporte antes de eliminar.
La exclusión CCPA «Do Not Sell or Share»
Bajo los perfiles estadounidenses, la ley exige un control con nombre, visible, y que ejecute realmente la exclusión.
Dos formas de colocarlo
Shortcode, donde usted quiera:
[owc_dnsmpi]
[owc_dnsmpi label="No vender ni compartir mi información personal" class="mi-enlace"]
| Atributo | Por defecto |
|---|---|
label | «Do Not Sell or Share My Personal Information» |
class | owc-dnsmpi |
Inyección automática en el pie de página: ajuste ccpa_inject_footer, activado por
defecto, renderizado en wp_footer con prioridad 20.
Qué hace un clic
Un clic ejecuta la exclusión. No abre ningún panel. El runtime pide un nonce fresco y
después envía un reject_all con la fuente footer_link y todas las categorías opcionales
a false. El enlace pasa a aria-busy="true" y una zona role="status" muestra el estado:
«guardando», y después «su exclusión se ha registrado en este navegador», o el mensaje de
error.
El código cita la razón: según los reglamentos de desarrollo de la CCPA (§7026(a)(1)), un enlace que se limita a abrir un panel no es un mecanismo conforme.
Reserva sin fetch ni Promise (WebView antiguo, navegador integrado): el clic abre el
panel de preferencias con las categorías publicitarias ya puestas en off.
La restricción de caché
El HTML servido es idéntico para todos. Por eso el enlace se renderiza visible si el
perfil configurado del sitio es ccpa o us_generic; si no, se renderiza oculto y se
revela en el cliente para los visitantes cuya jurisdicción lo exija, según la cookie de
detección regional.
Si la detección regional está desactivada y su perfil configurado no es estadounidense, el bloque directamente no se imprime: sin marcado muerto.
Ningún dato de visitante viaja en la configuración JavaScript de este módulo — solo las listas de perfiles, nunca el booleano resuelto.
Los recursos de este módulo se imprimen en <head>, no en el pie de página, para que el
rechazo de Consent Mode llegue a gtag antes de que se dispare un gestor de etiquetas. El
JavaScript es únicamente ES5.
Límites
- El control de un clic exige
fetchyPromise. - El enlace solo es obligatorio bajo los perfiles
ccpayus_generic; bajo cualquier otro perfil, ni se muestra ni se revela. - La revelación en el cliente depende de la cookie de detección regional, y por tanto de JavaScript.
Google Consent Mode v2
El módulo está activado por defecto (gcm_enabled).
Las siete señales
Se emiten las siete señales de Consent Mode v2: ad_storage, ad_user_data,
ad_personalization, analytics_storage, functionality_storage,
personalization_storage y security_storage. Las seis categorías del plugin se asocian a
ellas desde una fuente de verdad única, lo que garantiza que el banner, el bloqueo y el
bootstrap no puedan divergir. La tabla está en Las categorías de
cookies.
Los valores por defecto, antes de todo consentimiento
La llamada default se imprime como marcado estático, y el update se calcula en el
navegador: eso es lo que mantiene la página cacheable.
| Señal | Régimen opt-in | Régimen opt-out |
|---|---|---|
ad_storage | denied | granted |
analytics_storage | denied | granted |
ad_user_data | denied | granted |
ad_personalization | denied | granted |
personalization_storage | denied | granted |
functionality_storage | denied si el sitio expone la categoría functional, si no granted | granted |
security_storage | granted siempre | granted |
Los dos juegos se imprimen en la misma página. El runtime elige cuál aplicar según la cookie de detección regional. Sin eso, una página cacheada fijaría la jurisdicción de un visitante estadounidense sobre un visitante europeo.
Los dos ajustes asociados
| Ajuste | Por defecto | Efecto |
|---|---|---|
gcm_ads_data_redaction | true | Oculta los identificadores publicitarios mientras ad_storage esté denegado |
gcm_url_passthrough | true | Hace circular gclid / dclid por las URL mientras las cookies estén denegadas |
A tener en cuenta
security_storage nunca se toca en una actualización: sigue concedido, tal y como prevé la
especificación. El panel de control muestra una tarjeta que indica si Consent Mode está
activo.
IAB TCF v2.2
El módulo TCF está desactivado por defecto, y exige un identificador que el plugin no puede proporcionar. Lea la sección de límites antes de activarlo.
Activación
| Ajuste | Por defecto | Límites |
|---|---|---|
tcf_enabled | false | booleano |
tcf_cmp_id | 0 | 0 a 4095 |
tcf_publisher_country | FR | código ISO de 2 letras |
tcf_publisher_purposes_li | [] | lista de finalidades |
tcf_special_features | [] | lista de funcionalidades especiales |
El tope de 4095 no es arbitrario: el campo CmpId ocupa 12 bits en la cadena TCF.
Tres casos en los que el módulo se niega a emitir nada
- Ningún CMP ID (
tcf_cmp_id < 1): sin__tcfapi, sin cadena TC, sin cookieeuconsent-v2, sin ruta REST. Un aviso de administración lo explica. El CMP ID debe asignárselo IAB Europe; el plugin no proporciona ninguno. Emitir una cadena con un CmpId 0 sería peor que no emitir nada. - CMP ID superior a 4095: mismo rechazo, con un aviso específico. Un valor truncado a 12 bits designaría a otra CMP — una suplantación de identidad.
- Ninguna Global Vendor List en caché: el stub se imprime pero la API responde
cmpStatus: 'error'con una cadena vacía, en lugar de inventarse un número de versión de lista.
Se escribe un diagnóstico en una opción (disabled, missing_cmp_id, no_gvl, active),
y únicamente en contexto de administración o de cron, nunca en una página pública.
La Global Vendor List
- Fuente:
https://vendor-list.consensu.org/v3/vendor-list.json. - Nunca se descarga durante el renderizado de una página. De eso se encarga un cron diario, con una primera ejecución cinco minutos después de la activación; en administración con la caché fría, se encola una única recuperación.
- Petición: 5 segundos de tiempo de espera máximo, 2 redirecciones, respuesta limitada a 4 MB. Un cuerpo que alcanza el tope se considera truncado y se rechaza.
- Solo se conservan los campos útiles por proveedor; las pilas se vacían. Si la serialización sigue siendo demasiado grande, se aplican dos niveles de truncamiento: primero las etiquetas y las URL, y después la reducción a las solas finalidades.
- Conservación: una semana en un transient, con stale-if-error — si falla, se conserva la última copia válida.
- Ruta pública:
GET /owc/v1/tcf/gvl, limitada a 10 peticiones por hora. Sirve la copia en caché con unETagy unCache-Control: public, max-age=86400, y gestiona las peticiones condicionales. Si no hay nada en caché, responde 503 con unRetry-After: 300— nunca una lista sintética. Esta ruta nunca dispara una petición saliente.
Dónde se calcula la cadena TC
En el navegador, no en PHP. Un stub completamente estático se imprime en <head> con
prioridad 0: iframe localizador, implementación de window.__tcfapi, relé postMessage, y
un codificador de segmento Core en JavaScript. La configuración publicada no contiene
ningún dato de visitante — eso es lo que permite seguir siendo compatible con una caché
de página. Filtro: owc_tcf_stub_config.
Existe un espejo PHP del codificador, pero no tiene ningún llamante dentro del plugin: está reservado a integraciones y pruebas, con la advertencia de no imprimirlo nunca en HTML cacheable.
Lo que se codifica
Segmento Core únicamente, en base64url sin relleno. La sección de proveedores se codifica o bien como campo de bits, o bien por rangos: se miden los dos tamaños y gana el más pequeño. El índice de proveedores que se transmite al navegador usa un formato compacto propio, con un tope duro más allá del cual el índice se vacía (los consentimientos de proveedores se pierden entonces, y se conserva la versión de la lista).
Correspondencia categorías → finalidades TCF
| Categoría | Finalidades |
|---|---|
necessary | ninguna — fuera del alcance TCF, lo que garantiza que «Rechazar todo» no pueda producir un consentimiento para la finalidad 1 |
functional | 1 |
analytics | 1, 8, 9, 10 |
marketing | 1, 2, 3, 4, 7 |
preferences | 1, 5, 6, 11 |
social | 1 |
Filtro: owc_tcf_purpose_map.
Interés legítimo del editor: solo se retienen las finalidades 2, 7, 8, 9, 10 y 11,
porque TCF v2.2 prohíbe el interés legítimo para las finalidades 1, 3, 4, 5 y 6. Cualquier
otro valor de tcf_publisher_purposes_li se descarta en silencio.
Un proveedor queda consentido en cuanto se concede al menos una de las finalidades que declara bajo base «consentimiento».
Las funcionalidades especiales
Solo se admiten dos entradas: 1 — uso de datos de geolocalización precisa y 2 —
análisis activo de las características del dispositivo. Declare las que utilice en
tcf_special_features; la lista se reduce a ese catálogo.
Aparecen como casillas de verificación reales en el panel de preferencias. «Aceptar todo» no las marca: exigen un opt-in explícito propio. «Rechazar todo» y «Do Not Sell» las desmarcan. Viven en la propia cadena TC, y se rehidratan al reabrir el panel a partir de la API TCF.
La capa TCF en el panel
Solo se renderiza si el módulo está activo, es decir, tcf_enabled y un CMP ID
utilizable. Dos bloques:
- Finalidades: únicamente las alcanzables mediante una categoría no forzada y nombradas en el catálogo de la lista de proveedores. Un interruptor que volviera solo a su sitio sería peor que ningún interruptor. Cambiar una finalidad escribe en todas las categorías que la declaran, y después la visualización se vuelve a derivar.
- Funcionalidades especiales declaradas.
No se codifica en la cadena nada que no se haya mostrado.
La API JavaScript
window.__tcfapi( command, version, callback, parameter );
// comandos: ping, getTCData, getInAppTCData,
// addEventListener, removeEventListener, getVendorList
window.__owcTcfUpdateState( tcData ); // sustituye los datos y notifica a los oyentes
window.__owcTcfRefresh(); // recalcula y emite 'useractioncomplete'
window.__owcTcfUiShown(); window.__owcTcfUiHidden();
window.__owcTcfSetSpecialFeatures( [ 1, 2 ] );
Versiones aceptadas: ausente, null, 2, '2', 2.2, '2.2'. Cualquier otro valor da
callback(null, false).
gdprApplies
Se decide en el cliente. Si la detección regional está desactivada, el valor es true.
Si no, el país del visitante se compara con la lista de los 31 códigos donde el RGPD se
aplica en el sentido de TCF (27 países de la UE, más IS, LI, NO, GB y CH). Fallo
cerrado: un país desconocido da true.
La cookie euconsent-v2
Se escribe únicamente en el cliente, y únicamente cuando el módulo está listo y el
visitante ha actuado. En caso contrario, la cookie se borra. Su duración es la del
consentimiento, limitada a 13 meses (recomendación IAB / CNIL). SameSite=Lax, Secure
en HTTPS.
Límites del módulo TCF — de lectura obligada
- No es una CMP registrada ante IAB Europe. Exige su propio CMP ID y, aun teniéndolo, la política TCF obliga a una CMP registrada a presentar opciones a nivel de finalidad y a nivel de proveedor. Aquí, los interruptores de finalidades siguen a las categorías y no hay ninguna elección a nivel de proveedor: los proveedores están en su derecho de rechazar esta señal. La pantalla de administración lo dice y habla de «modo compatible con CMP (no oficial)», aceptado por la mayoría de los SSP en desarrollo pero rechazado en producción real dentro del EEE. Si los ingresos publicitarios bajo TCF le importan, use una CMP certificada.
- Segmento Core únicamente: sin segmento
disclosedVendors,allowedVendorsnipublisherTC. - No se emite ninguna publisher restriction: el contador correspondiente vale siempre 0 y el objeto de restricciones está vacío.
purposeOneTreatmentyuseNonStandardTextsvalen siemprefalse,isServiceSpecificvale siempretrue— no hay ámbito global — y la pantalla de consentimiento vale siempre 0.getVendorListignora el parámetro de versión y devuelve siempre la lista en caché.- Los consentimientos de editor copian las finalidades generales: sin finalidad personalizada.
- Solo dos funcionalidades especiales.
- El módulo exige un cron operativo y HTTPS saliente. Sin lista en caché, la API
responde
cmpStatus: 'error'y la ruta pública devuelve 503.
Global Privacy Control
GPC es una señal que envía el navegador — la cabecera Sec-GPC: 1 y la propiedad
navigator.globalPrivacyControl. El plugin la respeta por defecto (gpc_honor), pero su
tratamiento depende del régimen jurídico, y ese es el punto importante.
Bajo los perfiles estadounidenses: vinculante
La señal se trata como una exclusión universal vinculante bajo los perfiles ccpa y
us_generic. Australia queda excluida explícitamente: régimen opt-out, pero que no
reconoce el GPC.
La escritura en el servidor solo se produce tras una cadena completa de comprobaciones, en este orden:
gpc_honorestá activado;- la señal está presente — la cabecera
Sec-GPC, con reserva enX-Sec-GPCporque algunos proxies y CDN renombran la cabecera; solo cuenta el valor exacto1; - el perfil efectivo es
ccpaous_generic— si no, no se escribe nada; - se trata de una simple consulta de página: sin administración, sin cron, sin AJAX, sin
REST, sin XML-RPC, sin WP-CLI, y con método
GET; - las cabeceras aún no se han enviado — una cookie que no se puede poner es una decisión que se volvería a registrar en cada petición;
- el marcador de sesión
owc_gpcestá ausente: una vez por sesión de navegación; - el visitante no parece un bot (user-agent vacío, o que contiene
bot,crawl,spider,slurp,monitor,uptime,pingdom,headless,preview,curl/,wget,python-,java/,go-http,okhttp,httpclient,libwww,facebookexternalhit); - la elección ya almacenada no satisface ya la señal — si la satisface, solo se marca el navegador;
- se respeta el límite de peticiones compartido: 30 por hora y por IP;
- el navegador se marca antes de la escritura, para que un fallo no se convierta en un bucle de reintentos.
El evento se registra como gpc_opt_out con la fuente auto. La URL de la página se
reconstruye a partir de la URL del sitio y de la ruta solicitada — nunca de la cabecera
Host, nunca del Referer.
Alcance: todas las categorías opcionales pasan a false; las categorías forzadas siguen
concedidas. Es una lectura amplia de la noción de venta o de compartición.
Transparencia: si el GPC machaca una elección registrada explícitamente, una cookie de cinco minutos dispara en el cliente un aviso en la parte inferior de la pantalla que lo explica, con un botón «gestionar mis preferencias» que abre el panel y un botón de cierre.
Bajo los perfiles RGPD y asimilados: mero indicio
Fuera de los regímenes opt-out estadounidenses, la señal se trata como una indicación, nunca como un consentimiento:
- se pone el atributo
data-owc-gpc="1"en<html>; - Consent Mode pasa
ad_storage,analytics_storage,ad_user_data,ad_personalizationypersonalization_storageadenied; - todas las categorías opcionales se ponen a
falsesolo en memoria; givensigue siendo falso, el banner sigue mostrándose, y no se registra nada.
Una bandera interna impide que el runtime le anuncie al visitante que ya está «excluido» cuando ni la cookie ni el registro lo dicen. La justificación está escrita en el código: el GPC no es una señal legalmente reconocida bajo el RGPD y ePrivacy, la nLPD suiza, la LGPD, POPIA, PIPL, DPDP ni la Ley 25.
Las dos cookies que pone este módulo
| Cookie | Duración | Función |
|---|---|---|
owc_gpc | sesión | Marca que la señal ya se ha tratado para este navegador |
owc_gpc_notice | 300 s | Dispara el aviso «el GPC ha sustituido su elección» |
Las dos van con path=/, SameSite=Lax, Secure en HTTPS, y son legibles por script.
Punto de extensión
add_action( 'owc_gpc_honored', function ( array $categories ) {} );
La detección regional
La detección regional aplica a cada visitante el perfil de su jurisdicción en lugar del perfil configurado del sitio. Está desactivada por defecto.
| Ajuste | Por defecto |
|---|---|
geo_enabled | false |
geo_default_profile | gdpr |
geo_mmdb_enabled | false |
La cascada de detección
Gana el primer acierto:
- Cloudflare (
CF-IPCountry) — solo si el sitio ha declarado estar detrás de Cloudflare; - AWS CloudFront (
CloudFront-Viewer-Country) — misma condición; GEOIP_COUNTRY_CODE, la variable que escribe el propio servidor (mod_geoip, ngx_http_geoip), y que por tanto el cliente no puede falsificar; después, si hay un proxy declarado, las variantes en cabecera HTTP;- Base MaxMind local, si
geo_mmdb_enabledestá activado; - Nada.
Sin una declaración explícita en wp-config.php, no se cree ninguna cabecera HTTP de
país: el cliente puede falsificarlas. Véase Instalación para las
constantes.
El código de país se valida contra la lista de permitidos ISO-3166-1 alfa-2 oficialmente
asignada (unos 249 códigos enumerados en el código). Los códigos de usuario o reservados —
XX, ZZ, el T1 de las salidas Tor — se rechazan.
El fallo cerrado
Sin país detectado, el plugin lee geo_default_profile pero degrada a gdpr cualquier
perfil opt-out. Un geo_default_profile puesto en ccpa, us_generic o au se ignora,
por tanto, para los visitantes no detectados — y la pantalla de administración solo ofrece
perfiles opt-in en ese selector, en lugar de brindar una opción que se ignoraría en
silencio.
El motivo es directo: sin esa salvaguarda, un sitio configurado en inglés estadounidense pondría a todo visitante del EEE en régimen opt-out, con rastreadores marcados como «concedidos» sin ningún consentimiento.
Sin reserva en la configuración regional del sitio: el idioma de un sitio no dice nada sobre el lugar de su visitante.
La cookie owc_geo
- Duración 24 horas,
path=/,SameSite=Lax,Secureen HTTPS, legible por script — el bootstrap del front la necesita. - Contenido: el país, el perfil, una marca de tiempo y un código de autenticación. Nunca una dirección IP.
- Al leerla: tamaño acotado, país validado contra la lista ISO, marca de tiempo dentro de la ventana, código verificado en tiempo constante. El perfil siempre se recalcula en el servidor a partir del país: un visitante no puede elegir su régimen jurídico.
- Solo se cachea una detección real. La reserva de «fallo cerrado» nunca se memoriza.
- Se escribe únicamente en una petición de front-end, y solo si la detección está activada.
La correspondencia país → perfil
| País | Perfil |
|---|---|
| Los 30 países del EEE | gdpr |
| GB, JE, GG, IM | uk_pecr |
| CH | ch_nfadp |
| BR | lgpd |
| ZA | popia |
| CN, HK | pipl |
| IN | dpdp |
| AU, NZ | au |
| CA | quebec |
| US | ccpa |
| Todo lo demás | La reserva, degradada a opt-in |
Hong Kong se rige en realidad por su propia ordenanza local; se trata como PIPL por
severidad. Nueva Zelanda se trata como Australia. Todo Canadá recibe quebec — gana el
régimen más estricto. Estados Unidos recibe ccpa, que hace las veces de perfil para los
demás estados.
La restricción de caché — lo que la detección no hace
El perfil geolocalizado no cambia el HTML renderizado. La reescritura del perfil solo se
aplica a las dos rutas REST de visitante, /owc/v1/consent y /owc/v1/state. El renderizado
de página, las rutas de administración, wp-admin, el cron y WP-CLI conservan el perfil
configurado. Toda respuesta que haya consumido la reescritura se marca como no cacheable.
Es el runtime del front el que elige el régimen, en el cliente, a partir de la cookie. Consecuencia asumida: el régimen que se aplica a un visitante depende de JavaScript, y un visitante cuya primerísima página viene de la caché se trata bajo el perfil configurado del sitio mientras la cookie no exista.
El lector MaxMind integrado
El plugin incorpora su propio lector MMDB, escrito en PHP puro, sin dependencia de Composer — un plugin publicado en WordPress.org no puede incorporar el paquete oficial.
- Solo lectura, solo país. Sin ciudad, sin ASN.
- Entradas/salidas acotadas: lectura en bloques pequeños, nunca carga en memoria de un archivo de varios megabytes.
- Nunca lanza una excepción: un archivo ausente, ilegible, truncado, corrupto u hostil da
null, y la detección cae en su perfil estricto. - Salvaguardas internas sobre la profundidad de recorrido, el presupuesto de descodificación y el tamaño de carga útil. Los metadatos se cachean en un transient cuya clave integra el tamaño y la fecha del archivo: sustituir su descarga mensual invalida la caché por sí sola.
registered_countryyrepresented_countryse ignoran deliberadamente. Es la forma documentada de los rangos de proxy anónimo, VPN y satélite, donde el país de registro designa el lugar en que el proveedor registró el bloque, no al visitante. Usarlos pondría a un visitante del EEE detrás de un VPN estadounidense en régimen opt-out.- Si otro plugin ya carga un lector GeoIP2, se usa como reserva después del lector propio.
Dónde poner el archivo: wp-content/uploads/ow-geoip/GeoLite2-Country.mmdb. La ruta es
filtrable mediante owc_geo_mmdb_path, con validación contra el recorrido de directorios.
Límite importante: el plugin no descarga la base de datos y no ofrece ninguna pantalla de subida. Debe obtener el archivo en MaxMind, depositarlo usted mismo y actualizarlo usted mismo. A cambio, no se envía nada a MaxMind: la búsqueda es íntegramente local, la dirección IP sirve solo como punto de búsqueda y se destruye explícitamente después — nunca se registra, nunca se almacena, nunca se escribe en la cookie.
Límites de la detección regional
- Desactivada por defecto, y la variante MaxMind exige un archivo que aporta usted.
- Las cabeceras de país se ignoran mientras no haya declarado su proxy.
- Sin granularidad estado por estado en EE. UU., ni provincia por provincia en Canadá.
- El régimen del visitante depende de JavaScript y de la cookie de 24 horas.
- El perfil
us_genericnunca se asigna automáticamente.
La integración con OW Forms
Alcance exacto: esta integración apunta al plugin hermano OW Forms, y a nada más. No hay ninguna integración con Contact Form 7, Gravity Forms o WPForms.
Ajuste: forms_integration_enabled, activado por defecto, pero la integración no hace
nada si OW Forms no está activo.
Lo que aporta
1. El vínculo consentimiento ↔ envío. En cada envío almacenado se escribe una fila en una tabla propia de OW Consent — el esquema de OW Forms nunca se toca — que contiene:
- el token seudónimo del visitante (32 hexadecimales, nunca la dirección de email);
- el identificador de la entrada de registro más reciente para ese token;
- las categorías concedidas y el perfil de cumplimiento vigente;
- la huella completa de los documentos publicados en ese instante;
- la marca de tiempo del consentimiento;
- el estado de la casilla RGPD del formulario y la etiqueta exacta que llevaba.
Este último punto es el más útil en la práctica: puede demostrar no solo que la casilla estaba marcada, sino qué había escrito al lado.
2. La cascada de borrado. Una solicitud de borrado verificada delega la eliminación en la rutina de OW Forms — que elimina también los archivos subidos — y después borra las filas de vínculo. La integración detecta si OW Forms ya ha conectado su propio oyente a la misma acción, en cuyo caso se limita a podar sus vínculos.
3. Las herramientas de privacidad de WordPress. Se registran un exportador y un eliminador, de modo que Herramientas → Exportar / Borrar datos personales cubre los envíos de OW Forms — y el portal de derechos de OW Consent también, ya que recorre esos mismos hooks. La exportación recoge las etiquetas reales de los campos y añade los campos de consentimiento. Todo valor exportado se neutraliza contra la inyección de fórmulas de hoja de cálculo y se trunca.
4. La retención. En el cron diario, los vínculos más antiguos que
retention_form_data_days (por defecto 1095 días, límites 1 a 3650) se eliminan en lotes
de 500, con un barrido de los huérfanos. Solo se tocan las filas de OW Consent: la
retención de los propios envíos pertenece a OW Forms.
Límites
- No se escribe ningún evento de envío en el registro encadenado. La fila de vínculo referencia una entrada existente, no crea ninguna — la cadena no debe escribirse desde fuera.
- Imposible vincular un envío si OW Forms está configurado para no almacenar los envíos: no hay ninguna fila a la que engancharse.
- Sin recuperación retroactiva: el vínculo empieza en el primer envío posterior a la actualización.
- La correlación se hace sobre la huella salada de la dirección que calcula OW Forms, con reserva en la dirección en claro que OW Forms también conserva. No es un esquema de conocimiento cero: la dirección sigue en claro en OW Forms, porque hay que poder responder a la persona.
- La integración nunca modifica el plugin OW Forms, ni sus tablas ni sus opciones.
- Si OW Forms está activo pero es demasiado antiguo para exponer su rutina de borrado, el eliminador responde explícitamente que los envíos no se han tocado.
El botón flotante
Un pequeño botón persistente permite reabrir el panel de preferencias en cualquier momento. Es la exigencia del artículo 7(3) — la retirada debe seguir siendo tan sencilla como el consentimiento — y de la deliberación CNIL 2020-091, que pide un mecanismo accesible desde cada página.
Condiciones de existencia
Exige los dos ajustes: floating_button_enabled y banner_enabled. El panel de
preferencias y la API JavaScript viven en el runtime del banner; sin él, el botón sería un
control que no hace nada.
Sale también en administración, en un feed, en robots.txt, y si wp_head no se ha
disparado.
Opciones
| Ajuste | Valores | Por defecto |
|---|---|---|
floating_button_enabled | booleano | true |
floating_button_position | bottom-left, bottom-right, top-left, top-right | bottom-left |
floating_button_style | pill, icon | pill |
floating_button_label | texto libre | Cookies |
El botón no tiene ajuste de tema propio: toma banner_style, para que las dos
superficies sigan la misma paleta.
Comportamiento y accesibilidad
Es un <button> de verdad, así que es accesible por teclado. Lleva aria-haspopup="dialog"
y un aria-label traducible pero no configurable («Manage my cookie choices»): solo se
ajusta la etiqueta visible. En modo icon, la etiqueta se oculta visualmente pero los
lectores de pantalla la siguen leyendo.
Se renderiza con el atributo hidden; es el JavaScript el que decide mostrarlo. Es
visible en todos los estados salvo cuando el banner o el panel ya está en pantalla —
incluido inmediatamente después de una elección en la página actual.
Un clic abre el panel de preferencias. Si la API del banner no existe, el botón cae en el
fragmento #owc-preferences, volviendo a disparar el evento a mano si ya es el fragmento
actual.
z-index 99990, oculto al imprimir, soporte del modo de contrastes forzados y de
prefers-reduced-motion, y desplazamiento bajo la barra de administración en posición
superior.
Shortcodes
El plugin declara dos shortcodes, y solo dos. Ni el banner ni el botón flotante tienen uno: se muestran en todas partes o en ninguna, pilotados por sus ajustes.
[owc_dsar_form] — el portal de derechos
[owc_dsar_form]
[owc_dsar_form types="access,erasure,portability" title="Mis derechos" submit_label="Enviar"]
| Atributo | Por defecto |
|---|---|
types | access,rectification,erasure,portability,restrict,object,optout |
title | «Ejercer mis derechos sobre mis datos personales» |
submit_label | «Enviar mi solicitud» |
Los tipos que se piden se intersecan con los que el servidor acepta. El tipo withdraw
nunca lo ofrece el formulario; véase El portal de derechos.
[owc_dnsmpi] — la exclusión CCPA
[owc_dnsmpi]
[owc_dnsmpi label="No vender ni compartir mi información personal" class="enlace-pie"]
| Atributo | Por defecto |
|---|---|
label | «Do Not Sell or Share My Personal Information» |
class | owc-dnsmpi |
Un clic ejecuta la exclusión; véase La exclusión CCPA. Si deja
ccpa_inject_footer activado, el enlace ya está inyectado en el pie de página y este
shortcode solo sirve para colocarlo en otro sitio.
Reabrir el panel desde un menú o un enlace
No hay shortcode para eso, porque basta con un atributo de clase:
<a href="#owc-preferences" class="owc-open-preferences">Gestionar mis cookies</a>
<button type="button" data-owc-open>Mis preferencias</button>
<a href="#owc-dnsmpi" data-owc-dnsmpi="1">No vender mi información</a>
Es el método recomendado para añadir una entrada «Cookies» a su menú de pie de página.
API REST
Todas las rutas viven bajo el namespace owc/v1, en la raíz REST habitual
(https://ejemplo.com/wp-json/owc/v1/…). Trece rutas en total: doce registradas por el
módulo REST, más la ruta de la lista de proveedores registrada por el módulo TCF.
Rutas públicas
Sin autenticación. Su seguridad se apoya en la comprobación de origen, un token propio del plugin y los límites de peticiones que se describen más abajo.
| Método | Ruta | Parámetros | Función |
|---|---|---|---|
GET | /nonce | action (por defecto wp_rest) | Devuelve un token fresco. Respuesta: {nonce, action, header: "X-OWC-Nonce", ttl: 43200}, con no-store y Vary: Cookie |
POST | /consent | event (por defecto save_preferences), source (por defecto banner), categories (obligatorio), page_url (por defecto ''), cookie_unreadable (booleano, por defecto false) | Registra una elección, pone la cookie y escribe la fila de registro |
GET | /state | — | El estado del propio llamante: {given, categories, profile, at}. Nunca el token de visitante, nunca la IP, nunca la URL de página. no-store + Vary: Cookie |
GET | /tcf/gvl | — | Sirve la Global Vendor List en caché. ETag, Cache-Control: public, max-age=86400, 304 en petición condicional, 503 + Retry-After: 300 si no hay nada en caché |
POST | /dsar | type (obligatorio), email (obligatorio, formato de email), message (por defecto '', 2000 caracteres máx.) | Deposita una solicitud de ejercicio de derechos. Respuesta {ok, mail_sent, message} — nunca el identificador |
Valores aceptados:
event:accept_all,reject_all,save_preferences,withdraw,auto,gpc_opt_outsource:banner,preferences,footer_link,api,autotype(DSAR):access,rectification,erasure,portability,restrict,object,optout,withdrawcategories: un objeto, 32 entradas como máximo, solo valores escalaresaction(nonce):wp_restúnicamente
Rutas de administración
Todas exigen la capacidad manage_options.
| Método | Ruta | Parámetros | Función |
|---|---|---|---|
GET | /ledger | page (≥1, por defecto 1), per_page (1–200, por defecto 50), visitor_token (32 hex), from, to | Lee el registro. Los filtros realmente aplicados se devuelven en la respuesta |
GET | /ledger/verify | — | Verifica la cadena de extremo a extremo |
GET | /settings | — | Los ajustes efectivos |
POST | /settings | cuerpo JSON libre | Escribe los ajustes. Respuesta {ok, updated, rejected, settings} |
POST | /scanner/run | — | Lanza un barrido |
POST | /scanner/ingest | cuerpo JSON {page, findings[]} | Recepción de las observaciones de la sonda |
GET | /scanner/findings | page, per_page (1–200, por defecto 50), filter (slug de categoría o unknown) | Lista los hallazgos |
POST | /policies/generate | type (obligatorio), publish (booleano, por defecto false) | Genera un documento |
GET | /policies/preview | type (obligatorio) | Previsualiza un documento |
/scanner/ingest exige además un nonce wp_rest válido, en la cabecera X-WP-Nonce o
como parámetro _wpnonce — porque sendBeacon no puede poner cabeceras.
/settings en escritura solo acepta las claves declaradas como ajustes reales: un filtro
puede inyectar claves sintéticas que no lo son. Cada valor se sanea individualmente. Si no
hay ninguna clave conocida, se devuelve un 400.
/ledger vuelve a comprobar la capacidad dentro del manejador, como defensa en
profundidad frente a un permission_callback filtrado en otra parte.
El modelo de seguridad de las escrituras públicas
La constatación de partida es sencilla: un nonce no puede vivir en HTML cacheable. Por
eso /consent y /dsar tienen un control de acceso permisivo, protegido por cuatro capas en
un orden que importa.
- Comprobación de origen del mismo host. Gratuita, determinista, no consume ningún presupuesto.
- Token propio del plugin en la cabecera
X-OWC-Nonce— es una prueba, nunca un veto. La decisión de no usarX-WP-Noncees deliberada: el núcleo de WordPress intercepta esa cabecera antes de cualquier control de acceso de ruta y rechaza la petición entera si no la valida. Un token caducado servido por una caché mataría, por tanto, una escritura que el endpoint habría aceptado sin token alguno. - Fallo cerrado: sin origen y sin token verificado, se devuelve un 403
owc_missing_origin. - Límite de peticiones al final. Si fuera el primero, una mala configuración consumiría dos tokens por clic y acabaría en 429, lo que taparía el error de configuración detrás de un límite de peticiones.
Hosts aceptados: los que WordPress declara (URL del sitio, URL de WordPress, raíz REST)
con su gemelo www./apex, más el host de la cabecera Host de la petición actual y su
gemelo. «Mismo origen» significa «el origen se corresponde con el host al que se ha conectado
el navegador», no «se corresponde con lo que WordPress tiene en la base de datos» — porque si
no, todo sitio al que se llegue por un dominio de vista previa, un alias, un nombre de
preproducción, un dominio mapeado en multisitio o detrás de un proxy que reescribe Host
sería rechazado en silencio, sin posibilidad de autorreparación. Filtro:
owc_allowed_request_hosts.
CORS se deja al núcleo de WordPress, deliberadamente.
/nonce rechaza explícitamente la forma JSONP: sin ese rechazo, una página de terceros
podría cosechar el token vivo de un administrador de paso mediante un simple <script src>,
no sometido a la comprobación de origen.
Los límites de peticiones
| Cubo | Presupuesto | Ventana | Clave |
|---|---|---|---|
nonce | 120 | 5 minutos | dirección IP |
consent | 30 | 5 minutos | dirección IP |
dsar | 3 | 1 hora | dirección IP |
dsar_email | 3 | 24 horas | dirección de email afectada |
dsar_global | 30 | 1 hora | todo el sitio |
gpc | 30 | 1 hora | dirección IP |
tcf_gvl | 10 | 1 hora | dirección IP |
- Ventana fija, no deslizante: un cubo que recibe tráfico acaba siempre por caducar.
- La clave almacenada es una huella salada: no hay ninguna dirección IP ni dirección de email en claro en las opciones ni en la caché de objetos.
- Normalización de IP: IPv4 se conserva, IPv6 truncada al /64 — el cliente controla cada bit del identificador de interfaz, así que una clave completa permitiría crear un presupuesto nuevo en cada petición.
- Identidad compartida: detrás de un CDN o de un proxy no declarado mediante
OWC_TRUSTED_PROXY, todos los visitantes llegan con la misma dirección. El presupuesto se fracciona entonces por visitante, con un contador ampliado sobre la dirección compartida. Es un límite más burdo: declare su proxy. - Filtro
owc_throttle_max( $max, $bucket, $window )— devolver0desactiva el límite. - El limitador falla abierto si la caché de objetos no está disponible: es mejor no limitar que impedir a alguien ejercer sus derechos.
La ruta /consent en detalle
headers_sent()se lee lo primero de todo. Si la salida ya ha empezado, la cookie del servidor no se puede poner: la fila se escribe igualmente y la respuesta sigue siendo 200, concookie_set: falsey un descriptorcookie(name,value,ttl,path,samesite,secure) que el cliente pone él mismo.- Las categorías que el sitio no ofrece se devuelven en
dropped_categories, no se ignoran en silencio. page_urlse valida contra los hosts del sitio, con reserva en unReferervalidado de la misma forma, y si no la cadena vacía. La clave está siempre presente, para que el registro no vaya a buscar elRefereren bruto.- Si el registro está activado y se rechaza la escritura, la cookie se anula y la respuesta
es un 503
owc_ledger_write_refused. No se almacena nada, no se libera ningún rastreador. cookie_unreadablees pura telemetría: no se le aplica ninguna comprobación de validación, precisamente para que un campo de diagnóstico no pueda rechazar jamás una escritura de consentimiento. Su único efecto es una nota en el registro de rechazos.
Respuesta tipo: {ok, state, dropped_categories, logged, cookie_set[, cookie]}. El campo
logged vale null cuando el registro está desactivado.
Códigos de error
| Código | HTTP | Significado |
|---|---|---|
owc_bad_param | 400 | Parámetro inválido |
owc_bad_categories | 400 | Objeto categories mal formado, demasiado grande o no escalar |
owc_bad_email | 400 | Dirección de email inválida |
owc_bad_dsar | 400 | Envío DSAR mal formado (trampa para bots incluida) |
owc_dsar_not_attested | 400 | Falta la declaración del art. 12.6 |
owc_no_settings | 400 | Ninguna clave de ajuste conocida en el cuerpo |
owc_missing_origin | 403 | Sin origen y sin token: fallo cerrado |
owc_bad_origin | 403 | El origen declarado no es un host de este sitio — la respuesta enumera hasta diez hosts aceptados |
owc_bad_nonce | 403 | Nonce inválido en /scanner/ingest |
owc_jsonp_forbidden | 403 | Forma JSONP rechazada en /nonce |
owc_forbidden | 401/403 | Capacidad insuficiente |
owc_dsar_disabled | 404 | El portal de derechos está desactivado en el servidor |
owc_no_template | 404 | Ninguna plantilla para este tipo y este perfil |
owc_rate_limited | 429 | Límite de peticiones alcanzado |
owc_consent_failed | 500 | Fallo al aplicar el consentimiento |
owc_dsar_store_failed | 500 | Fallo al escribir la solicitud |
owc_gen_failed | variable | Generación de documento rechazada (campos ausentes, barrera de idioma) |
owc_ledger_write_refused | 503 | El registro se ha negado a escribir — no se registra nada, no se desbloquea nada |
El registro de rechazos
Se conservan los veinte últimos rechazos de escritura y se muestran en el panel de control — únicamente si el registro no está vacío. Se distinguen dos naturalezas:
- una entrada con un estado HTTP es un rechazo de verdad: no se ha almacenado nada y el visitante ha visto un error;
- una entrada con estado 0 es una advertencia: la elección sí se ha registrado.
El panel muestra uno al lado del otro el host declarado por el origen y la cabecera Host de
la petición. Es exactamente lo que difiere cuando sus visitantes navegan por un host mientras
WordPress está configurado con otro — la causa más frecuente de un consentimiento que «no se
guarda».
Límites de la API
/consenty/dsaraceptan escrituras anónimas por diseño; la protección es el origen más el límite de peticiones, no un nonce.- El limitador de peticiones falla abierto sin caché de objetos.
- La cookie de consentimiento es host-only: una cookie puesta para el apex es ilegible
desde una página
www., y ninguna cabecera CORS puede corregir eso. El remedio es una URL de escritura del mismo origen. /scanner/ingestsolo aceptamanage_options: la sonda no puede venir de un visitante./settingsno tiene nonce propio: esmanage_optionsmás la comprobación de nonce por cookie del núcleo.
Referencia de ajustes
Todos los ajustes caben en una única opción, owc_settings, cargada automáticamente
puesto que se lee en cada página del front.
Lectura y escritura en PHP
$ajustes = OWC_Core::settings(); // ajustes efectivos (defaults + guardados + filtros)
$perfil = OWC_Core::setting( 'compliance_profile' );
$bruto = OWC_Core::stored_setting( 'compliance_profile' ); // ignora la reescritura por visitante
OWC_Core::update_settings( array(
'consent_renewal_months' => 6,
'compliance_strict' => true,
) );
update_settings() fusiona solo las claves enviadas sobre lo existente, nunca el
conjunto de los valores por defecto. Eso es lo que permite que la fila de la opción siga
vacía mientras usted no haya personalizado nada, y por tanto que los textos sigan el idioma
del sitio.
El resultado de settings() se memoiza, con una clave basada en el conjunto de las llamadas
del filtro owc_settings: un módulo que registre su filtro después de la primera lectura
invalida el memo en lugar de ser ignorado. El memo se vacía en cada escritura de la opción.
Valores por defecto
[
// Banner
'banner_enabled' => true,
'banner_position' => 'bottom-bar', // bottom-bar | bottom-card | center-modal | top-bar
'banner_style' => 'auto', // auto | light | dark
'banner_accept_all' => true,
'banner_reject_all' => true,
'banner_preferences' => true,
'banner_close_x' => false, // el aspa = rechazo implícito
'banner_show_logo' => true,
'text_title' => 'We use cookies',
'text_message' => 'We use cookies to personalise content, measure audience, '
. 'and provide social media features. You can accept or reject, '
. 'and change your choice at any time.',
'text_accept_all' => 'Accept all',
'text_reject_all' => 'Reject all',
'text_preferences' => 'Customize',
'text_save' => 'Save my choices',
// Botón flotante
'floating_button_enabled' => true,
'floating_button_position' => 'bottom-left',
'floating_button_style' => 'pill',
'floating_button_label' => 'Cookies',
// Consentimiento
'consent_renewal_months' => 12, // 0 se convierte en 13, no en «nunca»
'consent_policy_hash_check' => true,
// Cumplimiento
'compliance_profile' => 'gdpr',
'compliance_strict' => true,
'geo_enabled' => false,
'geo_default_profile' => 'gdpr',
'geo_mmdb_enabled' => false,
// IAB TCF v2.2
'tcf_enabled' => false,
'tcf_cmp_id' => 0,
'tcf_publisher_country' => 'FR',
'tcf_publisher_purposes_li' => [],
'tcf_special_features' => [],
// CCPA / GPC
'ccpa_inject_footer' => true,
'gpc_honor' => true,
// Google Consent Mode v2
'gcm_enabled' => true,
'gcm_ads_data_redaction' => true,
'gcm_url_passthrough' => true,
// Categorías
'cat_necessary_available' => true,
'cat_functional_available' => true,
'cat_analytics_available' => true,
'cat_marketing_available' => true,
'cat_preferences_available' => true,
'cat_social_available' => true,
'cat_necessary_label' => 'Necessary',
'cat_necessary_desc' => 'Strictly required for the site to function …',
'cat_functional_label' => 'Functional',
'cat_functional_desc' => 'Enhance the experience (chat, embedded videos, maps) …',
'cat_analytics_label' => 'Statistics',
'cat_analytics_desc' => 'Help us understand how you use the site (anonymously) …',
'cat_marketing_label' => 'Marketing',
'cat_marketing_desc' => 'Enable us to show you ads and content tailored …',
'cat_preferences_label' => 'Preferences',
'cat_preferences_desc' => 'Remember your interface choices (layout, saved filters).',
'cat_social_label' => 'Social & embeds',
'cat_social_desc' => 'Allow embedded social content (YouTube, Instagram, X) to load.',
// Registro
'ledger_enabled' => true,
'ledger_retention_days' => 1825, // 5 años; 0 = ilimitado
'ledger_hash_algo' => 'sha256',
// Escáner
'scanner_enabled' => false,
'scanner_frequency' => 'weekly',
'scanner_alert_email' => '',
'scanner_max_urls' => 25,
'scanner_timeout' => 8,
'scanner_probe_mode' => 'admins',
// Portal de derechos
'dsar_enabled' => true,
'dsar_email' => '',
'dsar_response_days' => 30,
'dsar_notify_email' => '',
'dsar_token_ttl_days' => 7,
// Bloqueo
'blocker_unknown_script_policy' => 'allow',
'blocker_unknown_iframe_policy' => 'block',
'blocker_allowlist' => '', // un host por línea
'blocker_block_resource_hints' => true,
// Integración y ciclo de vida
'forms_integration_enabled' => true,
'auto_footer_menu_inject' => false,
'delete_data_on_uninstall' => false,
// Identidad legal
'legal_company_name' => '',
'legal_company_legal_form' => '',
'legal_company_capital' => '',
'legal_company_address' => '',
'legal_company_email' => '',
'legal_company_phone' => '',
'legal_country' => 'FR',
'legal_company_reg_number' => '',
'legal_company_reg_place' => '',
'legal_company_vat' => '',
'legal_publication_director' => '',
'legal_eu_representative' => '',
'legal_host_name' => '',
'legal_host_address' => '',
'legal_host_phone' => '',
'legal_host_url' => '',
'legal_profession_body' => '',
'legal_profession_title' => '',
'legal_profession_state' => '',
'legal_profession_rules' => '',
'legal_mediator_name' => '',
'legal_mediator_address' => '',
'legal_mediator_url' => '',
'legal_dpo_name' => '',
'legal_dpo_email' => '',
'legal_dpa_authority' => '', // vacío a propósito: se deriva del país + perfil
'legal_transfer_countries' => '',
'legal_third_parties_note' => '',
'legal_automated_decisions' => false,
'legal_automated_decisions_desc' => '',
'legal_minor_age' => 15,
'legal_data_provision_note' => '',
'legal_currency' => 'EUR',
'legal_tax_label' => 'TTC',
'legal_business_type' => 'auto',
'retention_form_data_days' => 1095,
'retention_dsar_days' => 1095,
'policy_disclaimer' => true,
// Enlaces
'link_privacy_policy' => '',
'link_cookie_policy' => '',
'link_terms' => '',
'link_legal_notice' => '',
]
Listas cerradas
Un valor fuera de la lista se devuelve al valor por defecto.
| Ajuste | Valores aceptados |
|---|---|
banner_position | bottom-bar, bottom-card, center-modal, top-bar |
banner_style | auto, light, dark |
floating_button_position | bottom-left, bottom-right, top-left, top-right |
floating_button_style | icon, pill |
compliance_profile, geo_default_profile | gdpr, ccpa, lgpd, popia, pipl, dpdp, quebec, uk_pecr, ch_nfadp, au, us_generic |
ledger_hash_algo | sha256, sha3-256 (intersecados con lo que PHP soporta) |
scanner_frequency | hourly, twicedaily, daily, weekly |
scanner_probe_mode | admins, off |
legal_business_type | auto, vitrine, rental, ecommerce, services, saas, content |
blocker_unknown_script_policy, blocker_unknown_iframe_policy | allow, block |
Límites de los enteros
| Ajuste | Límites |
|---|---|
consent_renewal_months | 0 a 13 (0 se trata como 13) |
dsar_response_days | 1 a 30 |
dsar_token_ttl_days | 1 a 90 |
ledger_retention_days | 0 a 3650 (0 = ilimitado) |
scanner_max_urls | 1 a 500 |
scanner_timeout | 1 a 60 |
legal_minor_age | 13 a 18 |
retention_form_data_days | 1 a 3650 |
retention_dsar_days | 1 a 3650 |
tcf_cmp_id | 0 a 4095 |
Saneamiento
text_messagees el único campo con HTML enriquecido; acepta el HTML permitido en una entrada de WordPress, y el banner solo renderiza al final<a href target rel>.- Campos multilínea:
legal_company_address,legal_eu_representative,legal_host_address,legal_mediator_address,legal_third_parties_note,legal_automated_decisions_desc,legal_data_provision_note,blocker_allowlist, y cualquier clave terminada en_desc. - URL: toda clave con el prefijo
link_o el sufijo_url, máslegal_profession_rules. - Direcciones de email: toda clave que contenga
email. - Todo lo demás: texto simple.
- Un valor no escalar enviado a un ajuste escalar se ignora, y la clave no se escribe.
Dónde se configura cada cosa
| Pestaña | Ajustes |
|---|---|
| Banner | banner_*, text_*, floating_button_*, consent_renewal_months, consent_policy_hash_check |
| Compliance | compliance_profile, compliance_strict, gcm_*, blocker_*, ccpa_inject_footer, gpc_honor, tcf_*, geo_*, auto_footer_menu_inject, forms_integration_enabled, delete_data_on_uninstall, link_* |
| Legal identity | legal_*, retention_form_data_days, retention_dsar_days, policy_disclaimer |
| Categories | cat_*_available, cat_*_label, cat_*_desc |
| Audit ledger | ledger_enabled, ledger_retention_days (el algoritmo se muestra, no se puede modificar) |
| Scanner | scanner_* |
| DSAR requests | dsar_* |
Tablas
| Tabla | Contenido |
|---|---|
{prefix}owc_ledger | Registro de consentimiento encadenado |
{prefix}owc_dsar | Solicitudes de ejercicio de derechos |
{prefix}owc_scanner | Hallazgos del escáner |
{prefix}owc_scripts | Catálogo de firmas del bloqueo |
{prefix}owc_form_links | Vínculos consentimiento ↔ envío de OW Forms |
Un ayudante PHP owc_table( 'ledger' | 'dsar' | 'scanner' | 'scripts' ) recalcula el nombre
a partir del prefijo actual: úselo dentro de un switch_to_blog(), ya que las constantes
quedan congeladas mientras dura la petición.
Otras opciones y transients
Opciones: owc_settings, owc_version, owc_pending_upgrade, owc_upgrade_lock,
owc_ledger_head, owc_ledger_trim, owc_ledger_key_source, owc_scanner_secret,
owc_scanner_lock, owc_scanner_queue, owc_scanner_status, owc_scanner_alerted,
owc_scanner_probe_status, owc_scanner_manual, owc_refusal_log, owc_tcf_status,
owc_tcf_gvl_version, owc_forms_db_version, más las marcas de migración
(owc_catalog_sanitized_v2, owc_match_target_migrated_v1, owc_ledger_hmac_migrated_v1,
owc_inline_sig_labels_cleaned_v1, owc_scanner_secret_rotated_v1).
Transients: owc_catalog_<version>, owc_invalid_patterns, owc_gvl_cache,
owc_gvl_stub, owc_gvl_etag, owc_gvl_retry, owc_mmdb_<huella>, owc_policies_hash,
owc_site_analysis, owc_scanner_last_run, owc_scanner_run_result, owc_scanner_pruned,
owc_scanner_fail_alerted, owc_ledger_verify_result, owc_ledger_write_error,
owc_policy_gen_result, owc_dsar_sla_alert, owc_dsar_admin_result, y los contadores de
limitación de peticiones.
Constantes reconocidas en wp-config.php
| Constante | Efecto |
|---|---|
OWC_LEDGER_KEY | Clave HMAC del registro, y clave derivada de la cookie de detección regional |
OWC_TRUSTED_PROXY / OWC_TRUSTED_PROXIES | Rangos CIDR de los proxies de confianza; sin ellas, las cabeceras de IP de cliente se ignoran |
OWC_GEO_TRUSTED_HEADERS | cloudflare, cloudfront, proxy o all |
OWC_BEHIND_CLOUDFLARE | Atajo para la cabecera de país de Cloudflare |
OWC_BEHIND_CLOUDFRONT | Atajo para la cabecera de país de CloudFront |
Los hooks para desarrolladores
Filtros
| Filtro | Función |
|---|---|
owc_settings | Ajustes efectivos. La respuesta debe depender de algo estable durante toda la petición |
owc_banner_texts | Textos del banner |
owc_bootstrap_config | Configuración estática impresa en <head> — nunca un dato por visitante |
owc_scripts_catalog | Filas del catálogo antes de la validación |
owc_blocker_allowlist | Hosts que nunca se bloquean |
owc_scanner_urls | URL que hay que barrer (después se vuelven a restringir al host del sitio) |
owc_throttle_max | Presupuesto de un límite de peticiones; 0 lo desactiva |
owc_allowed_request_hosts | Hosts aceptados para una escritura pública |
owc_allow_headerless_write | Levanta el fallo cerrado en las escrituras sin origen (por defecto false) |
owc_geo_mmdb_path | Ruta de la base de datos MaxMind |
owc_tcf_purpose_map | Correspondencia categorías → finalidades TCF |
owc_tcf_stub_config | Configuración estática del stub TCF |
owc_policy_template | Cuerpo en bruto de una plantilla — desactiva el cerrojo de idioma |
owc_policy_vars | Variables de un documento |
owc_policy_html | HTML final de un documento |
owc_dsar_types | Tipos de solicitud ofrecidos y aceptados |
owc_dsar_form_notice | Información bajo el formulario |
owc_dsar_export_bundle | Contenido del paquete de portabilidad |
owc_dsar_show_fulfilment_panel | Visualización del panel de ejecución |
Acciones
| Acción | Firma |
|---|---|
owc_consent_updated | ( array $categories, string $event, string $source ) |
owc_consent_cookie_not_sent | ( array $cookie, string $event ) |
owc_ledger_lock_failed | ( string $event, string $token ) |
owc_ledger_write_failed | ( $error, string $event, string $token ) |
owc_ledger_anchor | ( array $anchor ) |
owc_ledger_pruned | ( int $deleted, int $days ) |
owc_catalog_updated | — |
owc_gpc_honored | ( array $categories ) |
owc_tcf_inactive | ( string $reason ) |
owc_dsar_submitted | ( string $type, string $email, string $message ) |
owc_dsar_verified | ( int $id, array $row ) |
owc_dsar_fulfilled | ( int $id, string $what, $trace ) |
owc_dsar_mail_failed | ( int $id, string $email, string $kind ) |
Crons: owc_daily_maintenance, owc_ledger_retention, owc_scanner_run,
owc_scanner_run_batch, owc_tcf_refresh_gvl, owc_run_upgrade.
Internacionalización
El dominio de traducción es ow-consent, con la ruta /languages. El idioma fuente es
el inglés. El paquete incluye la plantilla ow-consent.pot y una traducción francesa
completa.
Recordatorio del mecanismo descrito más arriba: mientras una etiqueta o un texto del banner no esté personalizado en los ajustes, sigue el idioma del sitio. En cuanto usted introduce su propio valor, se sirve tal cual, sea cual sea el idioma del visitante.
Los cuerpos de los documentos legales no pasan por ese mecanismo: su idioma sigue a la jurisdicción, nunca a la configuración regional del administrador.
Solución de problemas
El banner no se muestra
Recorra la lista en orden:
- ¿Está activado
banner_enabled? La insignia de la parte superior de la pantalla de administración lo dice. - ¿Su tema llama a
wp_head()y awp_footer()? Sin ellos, el banner se niega a imprimirse en lugar de producir un marcado inerte. - ¿El visitante ya ha elegido? El banner solo se muestra con
<html data-owc="none">. Pruebe en navegación privada o conwindow.OWCBanner.reset(). - ¿El visitante tiene JavaScript? Sin él, el banner permanece oculto — deliberadamente —
y el bloque
<noscript>toma el relevo. - ¿Alguna caché de página sirve una versión anterior a la activación? Vacíela.
El banner vuelve en cada página aunque la elección se haya registrado
Tres causas, por orden de frecuencia.
- El host visitado difiere del host configurado en WordPress (
www.frente a apex, alias, dominio de preproducción). El navegador se niega entonces a almacenar la cookie mientras WordPress responde 200. Abra el panel de control: el registro de rechazos muestra uno al lado del otro el origen y la cabeceraHost. El remedio es servir el sitio en un único host canónico. consent_policy_hash_checkestá activado y sus documentos han cambiado — es el comportamiento buscado: al visitante se le vuelve a preguntar tras una modificación de la política.- Está en una versión anterior a la 1.4.3. La cookie estaba entonces codificada dos veces y el navegador no conseguía releerla, mientras que el servidor sí la leía perfectamente. Actualice: las cookies escritas antes de la corrección siguen siendo legibles.
«Su elección no se ha podido registrar» — error refused o 403
Mire data-owc-code en el elemento del mensaje, o el registro de rechazos del panel de
control.
owc_bad_origin: el origen declarado no es un host reconocido. La respuesta enumera los hosts aceptados. Si a su sitio se llega legítimamente por varios nombres, añádalos con el filtroowc_allowed_request_hosts.owc_missing_origin: sin origen y sin token. Suele ser un plugin de privacidad que elimina las cabeceras, o un proxy que las reescribe.owc_bad_nonce: solo afecta a la sonda del escáner, no a la escritura de consentimiento — el nonce nunca es un veto en/consent.
Error ratelimit — 429
Los límites son deliberadamente bajos en las escrituras públicas. Dos causas:
- Un CDN o un proxy no declarado: todos sus visitantes llegan con la misma dirección y
comparten el mismo presupuesto. Declárelo con
OWC_TRUSTED_PROXYenwp-config.php. - Una prueba automatizada que envía más de 30 escrituras en 5 minutos desde la misma dirección.
Un 503 owc_ledger_write_refused
El registro se ha negado a escribir, así que no se ha registrado nada y no se ha liberado ningún rastreador. Es el comportamiento buscado: un consentimiento que no se puede demostrar no debe reclamarse.
- Compruebe que la tabla
{prefix}owc_ledgerexiste. La pestaña Audit ledger muestra un mensaje legible de «todavía no instalado» en lugar de una pantalla en blanco. - Compruebe los permisos de escritura del usuario de MySQL.
- Como último recurso, desactive temporalmente
ledger_enabledpara restablecer el servicio — sabiendo que mientras tanto pierde la prueba.
Todavía se cargan rastreadores antes del consentimiento
- ¿Está activado
compliance_strict? Sin él, solo se tratan los scripts encolados por WordPress. - ¿Está el rastreador en el catálogo? Un script de terceros desconocido se permite por defecto. Lance un barrido y clasifique el hallazgo: eso escribe la regla de bloqueo.
- ¿Lo inyecta JavaScript first-party? La guardia dinámica solo cubre veinte hosts. Añada una regla de catálogo sobre el dominio del rastreador.
- ¿Es una cookie puesta por la cabecera
Set-Cookie? Ningún bloqueo de cliente puede retenerla. Hay que tratarla en el origen, del lado del servidor. - Ponga
blocker_unknown_script_policyenblock— y después pruebe el sitio con cuidado, porque ese ajuste bloquea a todo tercero no reconocido.
Una integración legítima está bloqueada
Añada su host a blocker_allowlist, uno por línea, o mediante el filtro
owc_blocker_allowlist. Compruebe también blocker_unknown_iframe_policy: los iframes
desconocidos se bloquean por defecto, y es el caso más frecuente.
Si la integración debe seguir condicionada al consentimiento, clasifíquela mejor en la categoría correcta: el sustituto visual lleva un botón «Aceptar…» que la desbloquea con un clic.
El escáner no encuentra nada
- ¿Está activado? Viene desactivado por defecto.
- ¿Funciona WP-Cron? Con
DISABLE_WP_CRONy sin cron de sistema, el barrido programado no sale nunca. Lance uno a mano para comprobarlo. - ¿Es posible el loopback HTTP? Una autenticación HTTP de preproducción o un cortafuegos bloquean el barrido. El panel de cobertura muestra el primer error.
- El barrido de servidor no ejecuta nada de JavaScript. Visite una página del sitio como administrador conectado para que la sonda informe de lo que inyecta un gestor de etiquetas.
En todos los casos, lea el panel «lo que este barrido cubre realmente» antes de sacar conclusiones: distingue «no se ha encontrado nada» de «no se ha leído nada».
La generación de un documento se rechaza
Dos causas posibles, y el mensaje lo dice:
- Hay campos obligatorios vacíos — se muestra la lista de las claves que faltan. Complete la pestaña Legal identity.
- Barrera de idioma — está pidiendo un aviso legal o unas condiciones generales para una
jurisdicción no francófona. En ese caso el botón está oculto. La única vía es aportar su
propio cuerpo mediante el filtro
owc_policy_template.
El correo de verificación DSAR no llega
Casi siempre es la entregabilidad, no el plugin.
- La respuesta de la API lleva
mail_sent: falsecuando el envío ha fallado, y el formulario muestra entonces un mensaje específico que remite a la dirección de contacto. - Instale un plugin SMTP. La función
mail()de PHP la rechazan la mayoría de los servidores de recepción. - Compruebe el SPF, el DKIM y el DMARC de su dominio. El plugin nunca reescribe la
dirección de envío — eso es lo que rompería el SPF — solo pone un
Reply-To. - Compruebe que
dsar_emailes una dirección válida.
Mientras el correo no se reciba y se confirme, la solicitud sigue pending y el plazo
legal no arranca.
El módulo TCF no se activa
Abra la pestaña Compliance: un aviso de administración nombra la causa.
missing_cmp_id:tcf_cmp_idvale 0. Debe obtener un CMP ID en IAB Europe; el plugin no proporciona ninguno y se niega a emitir una cadena con el identificador 0.- CMP ID superior a 4095: el valor no cabe en el campo de 12 bits de la cadena y designaría a otra CMP.
no_gvl: la Global Vendor List aún no se ha descargado. Llega por un cron diario; compruebe que WP-Cron funciona y que su servidor puede alcanzarvendor-list.consensu.orgpor HTTPS saliente.
Todos los visitantes reciben el perfil configurado, pese a la detección regional
- ¿Está activado
geo_enabled? Viene desactivado por defecto. - ¿Ha declarado su proxy? Sin
OWC_BEHIND_CLOUDFLARE,OWC_BEHIND_CLOUDFRONToOWC_GEO_TRUSTED_HEADERSenwp-config.php, las cabeceras de país se ignoran, porque se pueden falsificar. - ¿Está el archivo de MaxMind en su sitio? El plugin no lo descarga y no ofrece pantalla de subida.
- Puede ser normal: la primera visualización de un visitante servida por la caché usa el perfil configurado mientras la cookie de 24 horas no exista. El panel de control muestra un bloque de diagnóstico con el país detectado, el perfil aplicado y la fuente.
La administración avisa de que la clave del registro vive en la base de datos
Es el aviso notice_ledger_key. Significa que AUTH_KEY y AUTH_SALT no están en
wp-config.php — WordPress las guarda entonces en la base de datos — y que OWC_LEDGER_KEY
tampoco está definida. La cadena se sigue construyendo, pero cualquiera con acceso a la
base puede volver a firmarla: la afirmación de resistencia a la manipulación ya no se
sostiene.
El remedio es añadir OWC_LEDGER_KEY (y, ya puestos, AUTH_KEY y AUTH_SALT) a
wp-config.php. La verificación distingue después una rotación de clave de una reescritura,
así que este cambio no invalida su historial.
El plugin no hace absolutamente nada, con un aviso rojo
Su WordPress es anterior a la 6.2. El plugin se niega a arrancar, y el aviso dice explícitamente que no se bloquea nada y que no se registra ningún consentimiento. Actualice WordPress, o desactive el plugin y retire el banner de sus páginas mientras tanto.
Hay reglas de catálogo que «no pueden dispararse»
Un aviso de administración las señala, y la pestaña Tracker catalogue tiene un filtro específico. Cuatro motivos posibles: patrón vacío o compuesto únicamente de caracteres invisibles, expresión regular que no compila o que retrocede de forma catastrófica, patrón demasiado corto y sin punto para apuntar a una URL, o categoría que ya no existe en el sitio. Corrija o elimine esas líneas: dan una falsa impresión de cobertura.
FAQ
¿El plugin funciona detrás de una caché de página? Sí, y toda su arquitectura está construida en torno a esa restricción. El HTML producido es idéntico para todos los visitantes; el consentimiento se lee en el navegador y se aplica antes del primer pintado. No se imprime ningún nonce en HTML cacheable. Solo las respuestas realmente personales se marcan como no cacheables.
¿Sustituye a una CMP de pago? En la mayoría de los sitios cubre el mismo terreno: banner, bloqueo, registro de auditoría, solicitudes de derechos, enrutado geográfico y documentos generados. Dos cosas que no hace: no es una CMP registrada ante IAB Europe, y no ofrece asesoramiento jurídico ni documentos revisados — el generador produce borradores que usted debe hacer validar.
¿El registro es a prueba de manipulación?
Es a prueba de manipulación en el sentido de que la delata, bajo una condición. Cada
fila se firma con HMAC sobre la huella de la anterior: una fila modificada o eliminada rompe
la cadena y la verificación dice dónde. La garantía se apoya en que la clave de firma viva
fuera de la base de datos. Es el caso si AUTH_KEY y AUTH_SALT están en wp-config.php, o
si usted define OWC_LEDGER_KEY. Si no, WordPress guarda los salts en la base y un atacante
que acceda a ella podría volver a firmar la cadena — el plugin detecta esa situación y le
avisa.
¿El escáner ralentiza el sitio? No. Viene desactivado por defecto y, una vez activado, se ejecuta en WP-Cron a la frecuencia que usted elija, recuperando una muestra de sus propias páginas desde el servidor. Nunca se ejecuta durante la visita de un visitante.
¿El escáner encuentra las cookies?
Parcialmente, y es importante entenderlo. El barrido de servidor lee las cabeceras
Set-Cookie de sus páginas: encuentra, por tanto, las cookies puestas por el servidor,
incluidas las HttpOnly. No ejecuta nada de JavaScript, así que las cookies escritas por
scripts en el navegador no le son visibles; esas las recoge una sonda que solo se ejecuta
para un administrador conectado. Trate el resultado como un inventario de lo que se ha visto,
no como una lista exhaustiva — las dos pantallas lo dicen en lugar de dejar que usted suponga
lo contrario.
¿Cómo se ejecuta una solicitud de derechos? La persona envía el formulario y recibe un correo de verificación. Confirmar la solicitud en la página enlazada arranca el plazo del artículo 12.3 y abre una solicitud nativa de WordPress. Desde la pantalla DSAR, usted descarga una exportación JSON producida por todos los exportadores registrados en el sitio, lanza todos los eliminadores, y cierra con una respuesta escrita que queda adjunta a la solicitud. Lo que cubre la exportación depende, por tanto, de los plugins instalados; los que no registren ninguno de estos hooks hay que tratarlos a mano.
¿Puedo usar el módulo TCF para AdSense o Ad Manager? Solo con su propio CMP ID emitido por IAB Europe, y aun así con una reserva. Sin CMP ID, el módulo no se carga en absoluto. Con un CMP ID, emite una cadena TC correctamente codificada y el panel muestra cada finalidad y cada funcionalidad especial que esa cadena puede reclamar — pero la política TCF exige a una CMP registrada opciones a nivel de finalidad y a nivel de proveedor. Aquí los interruptores de finalidades siguen a las categorías y no hay ninguna elección de proveedor: no es una CMP registrada, y los proveedores están en su derecho de rechazar su señal. Si los ingresos publicitarios bajo TCF le importan para su sitio, use una CMP certificada.
¿En qué idioma se generan los documentos? La política de privacidad y la política de cookies existen para los once perfiles, en el idioma de la jurisdicción: inglés, francés para Francia, Bélgica, Luxemburgo y Quebec, y portugués para Brasil. El aviso legal y las condiciones generales solo existen en francés; para cualquier otra jurisdicción, el generador se niega a producirlos en lugar de publicar un documento inadecuado.
¿Qué puedo cambiar en el banner?
Cuatro posiciones, un tema claro, oscuro o automático, un logotipo opcional, las etiquetas, y
un botón flotante de reapertura. Cualquier elemento con la clase owc-open-preferences
reabre el panel. «Rechazar todo» se renderiza con la misma prominencia que «Aceptar todo». El
aspa de cierre viene desactivada; activada, registra un rechazo completo, nunca un cierre
silencioso. El panel se puede usar con el teclado, sus interruptores llevan un estado
visible, y la modal atrapa y después devuelve el foco. Los textos son traducibles y
sustituibles mediante el filtro owc_banner_texts.
¿Qué bloquea exactamente el bloqueo automático? Los scripts de terceros, los snippets de seguimiento en línea, los iframes, los píxeles de medición, las hojas de estilo de analítica y marketing, y las fuentes de medios de terceros se reescriben antes del consentimiento y se liberan después, en el orden del documento. Las pistas de recursos hacia un tercero catalogado se eliminan en lugar de diferirse, ya que una pista abre una conexión y no tiene nada que restaurar. Las hojas y las fuentes servidas por un host clasificado como funcional se dejan intactas. Los atributos de carga perezosa de los plugins de caché se neutralizan para que un cargador no restaure una URL bloqueada. El plugin incluye 175 firmas, editables desde la pantalla Tracker catalogue y ampliables por filtro.
¿Qué se inscribe en el registro? Cada acción de consentimiento — aceptación, rechazo, guardado parcial, exclusión GPC, renovación, retirada — se añade a la tabla encadenada. Una fila contiene una IP seudonimizada, una huella del user-agent, la URL de la página, el perfil de cumplimiento, una huella de los documentos vigentes, una huella del banner realmente mostrado y la versión del plugin — los elementos que permiten reconstruir lo que el visitante vio.
¿Qué protecciones tiene el formulario de derechos? Una trampa para bots, un tiempo mínimo de cumplimentación, una declaración obligatoria comprobada en el servidor, tres límites de peticiones distintos, un token de un solo uso almacenado únicamente como huella, y una confirmación que exige una acción explícita en la página — de modo que un escáner de enlaces de correo no pueda confirmar una identidad en lugar de la persona. Una solicitud cuyo autor nunca ha confirmado su identidad no se puede exportar, ni borrar, ni marcar como atendida, y esa comprobación se aplica en el servidor, no solo ocultando botones.
¿Qué señales de Google Consent Mode v2 se emiten?
Las siete: ad_storage, ad_user_data, ad_personalization, analytics_storage,
functionality_storage, personalization_storage y security_storage. Las seis categorías
se asocian a ellas desde una fuente de verdad única, de modo que el banner, el bloqueo y el
bootstrap no pueden divergir.
¿Cómo se gestionan la exclusión CCPA y el GPC? Un control «Do Not Sell or Share» se inyecta en el pie de página o se coloca por shortcode. El primer clic registra la exclusión, como exigen los reglamentos californianos, en lugar de abrir un panel. La señal Global Privacy Control se trata como una exclusión vinculante bajo los perfiles estadounidenses, una vez por sesión de navegación, y se avisa al visitante si ha sustituido una elección que él había registrado. Bajo los perfiles de la familia RGPD, el GPC se trata como un indicio: las categorías opcionales quedan prerrechazadas en la interfaz, el banner sigue mostrándose, y no se registra nada — porque ahí el consentimiento debe ser un acto positivo.
¿Qué cookies deposita el propio plugin?
Todas son cookies internas, ninguna sirve para el seguimiento, y están declaradas en la
política de cookies generada: owc_consent (las elecciones, el token seudónimo del navegador
y el perfil; duración según el ajuste de renovación, 12 meses por defecto, con tope de 13),
owc_geo (país y perfil detectados, 24 horas, sin ninguna dirección IP), owc_gpc (marcador
de sesión), owc_gpc_notice (5 minutos), y euconsent-v2 (cadena TCF, únicamente si el
módulo está activado y tras una acción del visitante).
¿Funciona en multisitio? Sí. Cada sitio de la red tiene sus propias tablas y sus propios ajustes. Una activación de red recorre todos los sitios solo si la red tiene como máximo 200; por encima de esa cifra, cada sitio se aprovisiona en su primera petición.
¿Y si elimino el plugin? El registro, los hallazgos del escáner, el catálogo, los vínculos con OW Forms, las opciones y los ajustes sobreviven por defecto. Dos cosas se van siempre, sea cual sea su ajuste: las seis tareas programadas, y la tabla de solicitudes de derechos — es la única que contiene datos personales directamente identificativos de terceros, y una vez el plugin ha desaparecido, ya nada acota su retención y ninguna pantalla permite responderlas. Exporte sus solicitudes antes de eliminar el plugin. Para una limpieza completa del resto, active «Delete all data on uninstall» antes de eliminarlo.
¿Dónde encontrar el soporte?
- Portal de soporte: https://optionweb.dev/es/addons/support/
- Email: support@optionweb.dev
- Documentación: https://optionweb.dev/es/addons/ow-consent/