Pruebas A/B de los banners de consentimiento de cookies

El reto

Quieres probar diferentes diseños o mensajes de los banners de cookies para mejorar las tasas de consentimiento sin dejar de cumplir con el RGPD.

El escenario

«Quiero realizar una prueba A/B de nuestro banner de cookies para ver si diferentes formulaciones afectan a las tasas de aceptación. Pero me preocupa el cumplimiento del RGPD si realizo un seguimiento de los usuarios antes de que den su consentimiento».

Enfoque de pruebas A/B conforme a la normativa

El principio clave: puedes probar los banners de cookies SIN herramientas de seguimiento, utilizando únicamente las cookies estrictamente necesarias.

Implementación

// Create variant assignment (strictly necessary cookie)
function assignVariant() {
    let variant = getCookie('banner_test_variant');

    if (!variant) {
        // Randomly assign variant
        variant = Math.random() < 0.5 ? 'A' : 'B';

        // Store in strictly necessary cookie (doesn't require consent)
        document.cookie = banner_test_variant=${variant}; path=/; max-age=2592000; SameSite=Strict;
    }

    return variant;
}

// Configuration for Variant A (Control)
const configA = {
    "notice_banner_type": "simple",
    "website_name": "My Website",
    // ... other config
};

// Configuration for Variant B (Test)
const configB = {
    "notice_banner_type": "headline",
    "website_name": "My Site", // Shorter name
    // ... other config
};

// Initialize based on variant
document.addEventListener('DOMContentLoaded', function () {
    const variant = assignVariant();
    const config = variant === 'A' ? configA : configB;

    // Add callback to track results (only after consent)
    config.callbacks = {
        "i_agree_button_clicked": () => {
            // User accepted - log this (now they've consented to tracking)
            logConsentEvent(variant, 'accepted');
        },
        "i_decline_button_clicked": () => {
            // User declined - still log the action
            logConsentEvent(variant, 'declined');
        }
    };

    cookieconsent.run(config);
});

function logConsentEvent(variant, action) {
    // Send to your analytics (user has now consented)
    if (typeof gtag !== 'undefined') {
        gtag('event', 'cookie_consent', {
            'variant': variant,
            'action': action
        });
    }

    // Or send to backend
    fetch('/api/log-consent', {
        method: 'POST',
        headers: {'Content-Type': 'application/json'},
        body: JSON.stringify({
            variant: variant,
            action: action,
            timestamp: new Date().toISOString()
        })
    });
}

function getCookie(name) {
    const value = ; ${document.cookie};
    const parts = value.split(; ${name}=);
    if (parts.length === 2) return parts.pop().split(';').shift();
}

Variables de prueba

Tipo de banner

// Variant A: Simple banner
"notice_banner_type": "simple"

// Variant B: Headline banner
"notice_banner_type": "headline"

Esquema de colores

// Variant A: Light
"palette": "light"

// Variant B: Dark
"palette": "dark"

Texto del botón (mediante CSS personalizado)

// Add CSS to change button text
const style = document.createElement('style');
style.textContent = variant === 'A'
    ? '.cc-nb-okagree::before { content: "Accept All"; }'
    : '.cc-nb-okagree::before { content: "I Agree"; }';
document.head.appendChild(style);

Medición de impresiones de consentimiento

// Backend endpoint to analyze results
// GET /api/consent-analytics

{
    "variant_A": {
        "impressions": 1000,
        "accepted": 650,
        "declined": 250,
        "no_action": 100,
        "acceptance_rate": 0.65
    },
    "variant_B": {
        "impressions": 1000,
        "accepted": 720,
        "declined": 200,
        "no_action": 80,
        "acceptance_rate": 0.72
    }
}

Consentimiento entre dominios

Problema

«Tenemos www.example.com para nuestro sitio web principal y shop.example.com para nuestra tienda. Los usuarios ven el banner de cookies en AMBOS sitios, incluso después de haberlo aceptado en el primero. Es molesto y estamos perdiendo conversiones».

Comprender los retos entre dominios

Por defecto, las cookies se limitan al dominio específico en el que se establecen:

  • Cookie establecida en www.example.com → No accesible en shop.example.com
  • Cookie establecida en shop.example.com → No accesible en www.example.com

Utilice el parámetro cookie_domain para compartir el consentimiento entre subdominios:

cookieconsent.run({
    "consent_type": "express",
    "website_name": "My Website",
    "website_privacy_policy_url": "https://www.example.com/privacy",
    "cookie_domain": ".example.com", // Note the leading dot!
    "page_load_consent_levels": ["strictly-necessary"]
});

¡Importante! El punto inicial (.example.com) hace que la cookie sea accesible para:

  • example.com
  • www.example.com
  • shop.example.com
  • blog.example.com
  • Cualquier subdominio de example.com

Un ejemplo completo entre dominios

En todos los dominios/subdominios, utilice una configuración idéntica:

<!-- On www.example.com -->
<script type="text/javascript" src="//www.termsfeed.com/public/cookie-consent/4.2.0/cookie-consent.js"></script>
<script type="text/javascript">
document.addEventListener('DOMContentLoaded', function () {
    cookieconsent.run({
        "notice_banner_type": "headline",
        "consent_type": "express",
        "palette": "light",
        "language": "en",
        "website_name": "Example",
        "website_privacy_policy_url": "https://www.example.com/privacy",
        "open_preferences_center_selector": "#changePreferences",
        "page_load_consent_levels": ["strictly-necessary"],
        "cookie_domain": ".example.com", // Shared across all subdomains
        "cookie_secure": true // Recommended for production
    });
});
</script>
<!-- On shop.example.com - SAME configuration -->
<script type="text/javascript" src="//www.termsfeed.com/public/cookie-consent/4.2.0/cookie-consent.js"></script>
<script type="text/javascript">
document.addEventListener('DOMContentLoaded', function () {
    cookieconsent.run({
        // EXACT SAME CONFIG as main domain
        "notice_banner_type": "headline",
        "consent_type": "express",
        "palette": "light",
        "language": "en",
        "website_name": "Example",
        "website_privacy_policy_url": "https://www.example.com/privacy",
        "open_preferences_center_selector": "#changePreferences",
        "page_load_consent_levels": ["strictly-necessary"],
        "cookie_domain": ".example.com", // Same domain!
        "cookie_secure": true
    });
});
</script>

Prueba de la configuración entre dominios

  1. Visite www.example.com
  2. Acepta las cookies
  3. Abre DevTools → Aplicación → Cookies
  4. Comprueba que el dominio de la cookie muestre .example.com (con un punto al principio)
  5. Navega a shop.example.com
  6. Comprueba que el banner NO aparece (consentimiento ya otorgado)

Problemas comunes entre dominios

Problema 1: El banner sigue apareciendo en los subdominios

Problema

«He configurado cookie_domain como '.example.com', pero el banner sigue apareciendo en todos los subdominios».

Causa

El navegador está bloqueando las cookies de terceros.

Solución

Asegúrate de que cookie_secure sea true para los sitios HTTPS.

// Make sure cookie_secure is true for HTTPS sites
"cookie_secure": true,

// Also verify in DevTools that cookie is actually being set
// Check: Application → Cookies → Look for cookie_consent_level
// Domain should show: .example.com (not www.example.com)

Problema 2: Consentimiento diferente en distintos subdominios

Problema:

El usuario da su consentimiento en el dominio principal, pero las preferencias difieren en el subdominio.

Causa:

Las configuraciones no son idénticas en todos los dominios.

Solución:

Utilice un archivo de configuración o una variable compartida.

// shared-config.js (include on all domains)
const SHARED_COOKIE_CONFIG = {
    "notice_banner_type": "headline",
    "consent_type": "express",
    "palette": "light",
    "language": "en",
    "website_name": "Example",
    "website_privacy_policy_url": "https://www.example.com/privacy",
    "open_preferences_center_selector": "#changePreferences",
    "page_load_consent_levels": ["strictly-necessary"],
    "cookie_domain": ".example.com",
    "cookie_secure": true
};

// Then on each domain:
cookieconsent.run(SHARED_COOKIE_CONFIG);

Multidominio (diferentes TLD)

Problema:

Tienes example.com Y example.co.uk, es decir, dominios completamente diferentes.

Solución:

Las cookies NO PUEDEN compartirse entre diferentes dominios de nivel superior debido a la seguridad del navegador.

Tienes dos opciones:

Opción 1: Aceptar varios banners.

  • El usuario ve un banner en cada dominio de nivel superior
  • El consentimiento otorgado se aplica individualmente para cada dominio; el consentimiento no se comparte
  • Probablemente sea el enfoque que mejor cumple con la normativa

Opción 2: Utilizar una sincronización en el backend (para usuarios que hayan iniciado sesión)

// On example.com - user accepts
"callbacks": {
    "user_consent_saved": () => {
        if (isUserLoggedIn()) {
            // Sync to backend
            fetch('https://api.example.com/sync-consent', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    'Authorization': 'Bearer ' + getUserToken()
                },
                body: JSON.stringify({
                    consent: getConsentState(),
                    domains: ['example.com', 'example.co.uk']
                })
            });
        }
    }
}

// On example.co.uk - check backend first
async function checkBackendConsent() {
    if (isUserLoggedIn()) {
        const response = await fetch('https://api.example.com/get-consent', {
            headers: {'Authorization': 'Bearer ' + getUserToken()}
        });
        const data = await response.json();
        if (data.hasConsent) {
            // Set cookie locally
            setCookieConsent(data.consentLevel);
        }
    }
}

Recarga de la página tras el consentimiento

Problema

«Después de que el usuario acepta las cookies, recargo la página con window.location.reload() para asegurarme de que se carguen los scripts de seguimiento. Pero esto hace que Google Analytics pierda la fuente de referencia original y muestre todo el tráfico como "Directo"».

Esto ocurre porque, al recargar la página:

  1. El navegador borra la información de referencia
  2. La nueva carga de la página parece provenir del mismo dominio
  3. Google Analytics lo clasifica como tráfico «Directo»
  4. Se pierden los datos de atribución

Solución 1: No recargues la página (opción recomendada)

Utiliza callbacks para cargar scripts de forma dinámica en lugar de recargar:

cookieconsent.run({
    "notice_banner_type": "headline",
    "consent_type": "express",
    "palette": "light",
    "language": "en",
    "website_name": "My Site",
    "website_privacy_policy_url": "https://mysite.com/privacy",
    "page_load_consent_levels": ["strictly-necessary"],

    "callbacks": {
        "scripts_specific_loaded": (level) => {
            // Scripts are NOW loaded - no reload needed!
            console.log("Scripts loaded for: " + level);

            if (level === 'tracking') {
                // Analytics is now active
                // Send initial page view if needed
                if (typeof gtag !== 'undefined') {
                    gtag('event', 'page_view', {
                        'page_location': window.location.href,
                        'page_referrer': document.referrer
                    });
                }
            }
        }
    },
    "callbacks_force": true
});

Solución 2: Conserva el referrer al recargar la página

Si TIENES que recargar, conserva el referrer:

cookieconsent.run({
    // ... config ...
    "callbacks": {
        "i_agree_button_clicked": () => {
            // Store referrer and source before reload
            sessionStorage.setItem('original_referrer', document.referrer);
            sessionStorage.setItem('landing_page', window.location.href);

            // Get URL parameters (utm_source, etc.)
            const urlParams = new URLSearchParams(window.location.search);
            if (urlParams.has('utm_source')) {
                sessionStorage.setItem('utm_source', urlParams.get('utm_source'));
                sessionStorage.setItem('utm_medium', urlParams.get('utm_medium'));
                sessionStorage.setItem('utm_campaign', urlParams.get('utm_campaign'));
            }

            // Now reload
            window.location.reload();
        }
    }
});

// After reload, restore attribution
document.addEventListener('DOMContentLoaded', function() {
    const savedReferrer = sessionStorage.getItem('original_referrer');
    const utmSource = sessionStorage.getItem('utm_source');

    if (savedReferrer && typeof gtag !== 'undefined') {
        // Send page view with preserved referrer
        gtag('event', 'page_view', {
            'page_location': window.location.href,
            'page_referrer': savedReferrer
        });

        // Send campaign data if available
        if (utmSource) {
            gtag('event', 'campaign_details', {
                'campaign_source': utmSource,
                'campaign_medium': sessionStorage.getItem('utm_medium'),
                'campaign_name': sessionStorage.getItem('utm_campaign')
            });
        }

        // Clear storage
        sessionStorage.removeItem('original_referrer');
        sessionStorage.removeItem('utm_source');
        // ... clear others
    }
});

Solución 3: Utiliza el Modo de consentimiento de Google (práctica recomendada)

Con el Modo de consentimiento V2, NO es necesario recargar la página:

<!-- 1. Set default consent to denied -->
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('consent', 'default', {
    'ad_storage': 'denied',
    'ad_user_data': 'denied',
    'ad_personalization': 'denied',
    'analytics_storage': 'denied'
});
</script>

<!-- 2. Load Google Analytics (runs in consent mode) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-XXXXXXXXXX');
</script>

<!-- 3. Cookie Consent with callbacks -->
<script src="//www.termsfeed.com/public/cookie-consent/4.2.0/cookie-consent.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function () {
    cookieconsent.run({
        "consent_type": "express",
        "website_name": "My Site",
        "page_load_consent_levels": ["strictly-necessary"],
        "callbacks": {
            "scripts_specific_loaded": (level) => {
                // Update consent mode - NO RELOAD!
                switch (level) {
                    case 'tracking':
                        gtag('consent', 'update', {
                            'analytics_storage': 'granted'
                        });
                        break;
                    case 'targeting':
                        gtag('consent', 'update', {
                            'ad_storage': 'granted',
                            'ad_user_data': 'granted',
                            'ad_personalization': 'granted'
                        });
                        break;
                }
            }
        },
        "callbacks_force": true
    });
});
  </script>


Contenido incrustado e iFrames

Problema

«Tenemos vídeos de YouTube incrustados en nuestro sitio web. ¿Deberíamos bloquear los iframes hasta que los usuarios acepten las cookies? ¿Cómo implementamos esto con TermsFeed?»

Solución

La solución consiste en integrar un marcador de posición y utilizar la carga dinámica.

Paso 1: Crear el marcador de posición HTML

<!-- Instead of directly embedding iframe -->
<div class="video-placeholder" data-video-id="dQw4w9WgXcQ" data-consent-type="targeting">
    <div class="placeholder-content">
        <img src="/images/video-thumbnail.jpg" alt="Video thumbnail">
        <div class="placeholder-overlay">
            <button class="accept-and-load" onclick="loadVideo(this)">
                 Accept cookies to watch this video
            </button>
            <p>This video is hosted by YouTube and requires marketing cookies.</p>
            <a href="#" onclick="openCookiePreferences(); return false;">
                Change cookie preferences
            </a>
        </div>
    </div>
</div>

<style>
.video-placeholder {
    position: relative;
    width: 100%;
    padding-bottom: 56.25%; // 16:9 aspect ratio
    background: #000;
}

.placeholder-content {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
}

.placeholder-content img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.placeholder-overlay {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: rgba(0,0,0,0.7);
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    color: white;
    text-align: center;
    padding: 20px;
}

.accept-and-load {
    background: #ff0000;
    color: white;
    border: none;
    padding: 15px 30px;
    font-size: 16px;
    border-radius: 5px;
    cursor: pointer;
    margin-bottom: 15px;
}
</style>

Paso 2: Cargar el iframe tras el consentimiento

// Check if user has already consented
function hasConsent(consentType) {
    const consentCookie = getCookie('cookie_consent_level');
    if (!consentCookie) return false;

    // Parse consent levels
    const levels = JSON.parse(decodeURIComponent(consentCookie));
    return levels.includes(consentType);
}

// Load video dynamically
function loadVideo(button) {
    const placeholder = button.closest('.video-placeholder');
    const videoId = placeholder.dataset.videoId;
    const consentType = placeholder.dataset.consentType;

    // Check if user has consent
    if (!hasConsent(consentType)) {
        // Request consent
        if (typeof cookieconsent !== 'undefined' && cookieconsent.openPreferencesCenter) {
            cookieconsent.openPreferencesCenter();
        } else {
            alert('Please accept marketing cookies to view this content');
        }
        return;
    }

    // Create iframe
    const iframe = document.createElement('iframe');
    iframe.src = https://www.youtube.com/embed/${videoId};
    iframe.width = '100%';
    iframe.height = '100%';
    iframe.frameBorder = '0';
    iframe.allow = 'accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture';
    iframe.allowFullscreen = true;

    // Replace placeholder with iframe
    placeholder.innerHTML = '';
    placeholder.appendChild(iframe);
}

// Auto-load videos if consent already given
document.addEventListener('DOMContentLoaded', function() {
    document.querySelectorAll('.video-placeholder').forEach(placeholder => {
        const consentType = placeholder.dataset.consentType;
        if (hasConsent(consentType)) {
            // Auto-load - user already consented
            const videoId = placeholder.dataset.videoId;
            const iframe = document.createElement('iframe');
            iframe.src = https://www.youtube.com/embed/${videoId};
            iframe.width = '100%';
            iframe.height = '100%';
            iframe.frameBorder = '0';
            iframe.allow = 'accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture';
            iframe.allowFullscreen = true;
            placeholder.innerHTML = '';
            placeholder.appendChild(iframe);
        }
    });
});

// Listen for consent updates
document.addEventListener('DOMContentLoaded', function() {
    cookieconsent.run({
        // ... config ...
        "callbacks": {
            "scripts_specific_loaded": (level) => {
                // When user accepts targeting cookies, load all pending videos
                if (level === 'targeting') {
                    document.querySelectorAll('.video-placeholder').forEach(placeholder => {
                        if (placeholder.dataset.consentType === 'targeting') {
                            loadVideo(placeholder.querySelector('.accept-and-load'));
                        }
                    });
                }
            }
        }
    });
});

function getCookie(name) {
    const value = ; ${document.cookie};
    const parts = value.split(; ${name}=);
    if (parts.length === 2) return parts.pop().split(';').shift();
}

function openCookiePreferences() {
    if (typeof cookieconsent !== 'undefined' && cookieconsent.openPreferencesCenter) {
        cookieconsent.openPreferencesCenter();
    }
}

Ejemplo para otras incrustaciones

Google Maps

<div class="map-placeholder" data-consent-type="functionality" data-location="New York, NY">
    <div class="placeholder-overlay">
        <p>️ Accept functional cookies to view map</p>
        <button onclick="loadMap(this)">Load Map</button>
    </div>
</div>

<script>
function loadMap(button) {
    const placeholder = button.closest('.map-placeholder');
    const location = placeholder.dataset.location;

    if (!hasConsent('functionality')) {
        cookieconsent.openPreferencesCenter();
        return;
    }

    const iframe = document.createElement('iframe');
    iframe.src = https://www.google.com/maps/embed/v1/place?key=YOUR_API_KEY&q=${encodeURIComponent(location)};
    iframe.width = '100%';
    iframe.height = '450';
    iframe.style.border = '0';
    iframe.allowFullscreen = true;
    iframe.loading = 'lazy';

    placeholder.innerHTML = '';
    placeholder.appendChild(iframe);
}
</script>

Incrustaciones de redes sociales (X, Instagram, Facebook)

<div class="social-placeholder" data-consent-type="targeting" data-platform="twitter" data-url="https://twitter.com/user/status/123">
    <div class="placeholder-overlay">
        <p> Accept marketing cookies to view this tweet</p>
        <button onclick="loadSocialEmbed(this)">Load Tweet</button>
    </div>
</div>

<script>
function loadSocialEmbed(button) {
    const placeholder = button.closest('.social-placeholder');
    const platform = placeholder.dataset.platform;
    const url = placeholder.dataset.url;

    if (!hasConsent('targeting')) {
        cookieconsent.openPreferencesCenter();
        return;
    }

    // Load platform SDK
    if (platform === 'twitter' && !window.twttr) {
        const script = document.createElement('script');
        script.src = 'https://platform.twitter.com/widgets.js';
        script.onload = () => {
            twttr.widgets.createTweet(
                url.split('/').pop(),
                placeholder,
                { theme: 'light' }
            );
        };
        document.head.appendChild(script);
    }
    // Add other platforms as needed
}
</script>