Cómo Implementar el Banner de Cookies (RGPD) Correctamente en Tu Web — Paso a Paso
Guía práctica para implementar un banner de consentimiento de cookies que cumpla con RGPD/LOPD. Sin plugins de pago, con código HTML/JS puro, y con checklist legal real.
Tabla de contenidos
El 90% de los banners de cookies que veo en webs de desarrolladores están mal implementados. Y no hablo de diseño — hablo de cumplimiento legal. El error más común: cargar Google Analytics antes de que el usuario acepte las cookies.
Yo también lo hacía. Ponía el script de GA en el <head>, mostraba un banner bonito, y cuando el usuario hacía clic en “Aceptar” guardaba un flag en localStorage. El problema: GA ya estaba trackeando desde el primer milisegundo.
Esto es lo que hago ahora, y es lo que exige la RGPD.
El principio fundamental
1. Cargar la web SIN cookies no esenciales
2. Mostrar el banner
3. El usuario elige: Aceptar / Rechazar / Configurar
4. SOLO si acepta → cargar Analytics, AdSense, etc.
5. Guardar la preferencia para no preguntar otra vez
No al revés. Primero el consentimiento, después las cookies.
Implementación completa (HTML + JS puro)
El banner HTML
<!-- Poner al final del <body>, antes de </body> -->
<div id="cookie-banner" class="fixed bottom-0 left-0 right-0 z-50 hidden">
<div class="mx-auto max-w-5xl px-4 py-4">
<div class="rounded-2xl border border-gray-200 bg-white p-6 shadow-2xl dark:border-gray-700 dark:bg-gray-900">
<div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
<div class="flex-1">
<p class="text-sm text-gray-700 dark:text-gray-300">
Usamos cookies propias y de terceros para analíticas y personalización.
Puedes aceptar, rechazar o
<a href="/cookies" class="underline hover:text-blue-600">configurar tus preferencias</a>.
</p>
</div>
<div class="flex flex-shrink-0 gap-3">
<button
id="cookie-reject"
class="rounded-lg border border-gray-300 px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-100 dark:border-gray-600 dark:text-gray-300 dark:hover:bg-gray-800"
>
Rechazar
</button>
<button
id="cookie-accept"
class="rounded-lg bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-blue-500"
>
Aceptar
</button>
</div>
</div>
</div>
</div>
</div>
Detalles de diseño que importan legalmente:
- Los botones de “Aceptar” y “Rechazar” tienen el mismo tamaño visual. Si haces el de aceptar gigante y el de rechazar pequeño/gris, es un dark pattern y la AEPD puede multarte.
- El texto es claro y directo. Nada de “utilizamos cookies para mejorar tu experiencia” (demasiado vago).
- Hay enlace a la política de cookies.
El JavaScript
// cookie-consent.js
(function() {
'use strict';
const CONSENT_KEY = 'cookie-consent';
const banner = document.getElementById('cookie-banner');
const acceptBtn = document.getElementById('cookie-accept');
const rejectBtn = document.getElementById('cookie-reject');
// Si ya hay una decisión guardada, no mostrar el banner
const savedConsent = localStorage.getItem(CONSENT_KEY);
if (savedConsent === 'accepted') {
loadAnalytics();
return;
}
if (savedConsent === 'rejected') {
return; // No cargar nada, no mostrar banner
}
// Primera visita: mostrar el banner
if (banner) {
banner.classList.remove('hidden');
}
if (acceptBtn) {
acceptBtn.addEventListener('click', function() {
localStorage.setItem(CONSENT_KEY, 'accepted');
if (banner) banner.classList.add('hidden');
loadAnalytics();
});
}
if (rejectBtn) {
rejectBtn.addEventListener('click', function() {
localStorage.setItem(CONSENT_KEY, 'rejected');
if (banner) banner.classList.add('hidden');
});
}
function loadAnalytics() {
// Google Analytics 4
if (!document.querySelector('script[src*="googletagmanager"]')) {
var gaScript = document.createElement('script');
gaScript.async = true;
gaScript.src = 'https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX';
document.head.appendChild(gaScript);
gaScript.onload = function() {
window.dataLayer = window.dataLayer || [];
function gtag() { window.dataLayer.push(arguments); }
window.gtag = gtag;
gtag('js', new Date());
gtag('config', 'G-XXXXXXXXXX', {
anonymize_ip: true // Anonimizar IP — recomendado por RGPD
});
};
}
// Aquí puedes cargar otros scripts: AdSense, Facebook Pixel, Hotjar, etc.
}
})();
Lo importante:
loadAnalytics()solo se ejecuta si el usuario acepta o si ya aceptó antesanonymize_ip: trueanonimiza la IP del usuario (recomendado por RGPD aunque no es obligatorio desde GA4)- Si el usuario rechaza, no se carga nada. Ni ahora ni en visitas futuras
- El script se ejecuta en un IIFE para no contaminar el scope global
Cómo cargarlo en tu web
<!-- En el <head>, ANTES de cualquier script de tracking -->
<script>
// NO pongas Google Analytics aquí. Solo el cookie-consent.
</script>
<!-- Al final del <body> -->
<script src="/cookie-consent.js"></script>
En mi caso con Astro, lo cargo así:
---
// BlogLayout.astro
---
<html>
<head>
<!-- NO hay GA aquí. Solo se carga si hay consentimiento. -->
</head>
<body>
<slot />
<!-- Banner de cookies -->
<div id="cookie-banner" class="...">
<!-- ... -->
</div>
<script is:inline>
// El script de consentimiento inline
// (misma lógica que arriba)
</script>
</body>
</html>
La política de cookies (página obligatoria)
Necesitas una página /cookies (o /cookies.html) con:
## Política de Cookies
### ¿Qué son las cookies?
Las cookies son pequeños archivos de texto que se almacenan en tu navegador...
### Cookies que utilizamos
| Cookie | Tipo | Proveedor | Propósito | Duración |
|--------|------|-----------|-----------|----------|
| `cookie-consent` | Técnica (propia) | Este sitio | Guardar tu preferencia de cookies | Permanente |
| `_ga` | Analítica (tercero) | Google Analytics | Distinguir usuarios únicos | 2 años |
| `_ga_XXXXXXX` | Analítica (tercero) | Google Analytics | Persistir estado de sesión | 2 años |
| `color-theme` | Técnica (propia) | Este sitio | Guardar preferencia de tema oscuro/claro | Permanente |
### Cómo gestionar las cookies
Puedes cambiar tu preferencia en cualquier momento haciendo clic en [Gestionar cookies].
También puedes configurar tu navegador para bloquear cookies...
### Base legal
El tratamiento se basa en tu consentimiento (art. 6.1.a RGPD).
Las cookies técnicas se basan en interés legítimo (art. 6.1.f RGPD).
### Contacto
Si tienes dudas: [tu email]
Checklist de cumplimiento
Obligatorio
- El banner aparece en la primera visita
- NO se cargan cookies de tracking antes del consentimiento
- Hay opción de rechazar con la misma visibilidad que aceptar
- La preferencia se guarda y no se pregunta en cada página
- Hay un enlace a la política de cookies desde el banner
- Existe una página de política de cookies accesible
- La política lista todas las cookies con su proveedor, propósito y duración
Recomendado
- Opción de “configurar” cookies por categoría (analíticas, marketing, etc.)
- Botón para cambiar la preferencia después (en el footer, por ejemplo)
-
anonymize_ip: trueen Google Analytics - Las cookies se eliminan si el usuario cambia de “aceptar” a “rechazar”
Prohibido (dark patterns que multa la AEPD)
- Botón de “Aceptar” mucho más grande/visible que “Rechazar”
- Solo opción de “Aceptar” y “Más información” (sin rechazar)
- Cookie wall: “Acepta las cookies o no puedes ver la web”
- Pre-marcar casillas de cookies no esenciales
- Requerir más clics para rechazar que para aceptar
Versión avanzada: con categorías
Si quieres ir más allá y permitir que el usuario elija por categorías:
const CATEGORIES = {
essential: true, // Siempre activas, no se pueden desactivar
analytics: false, // Google Analytics, Hotjar
marketing: false, // AdSense, Facebook Pixel
};
function getConsent() {
const saved = localStorage.getItem('cookie-categories');
return saved ? JSON.parse(saved) : null;
}
function setConsent(categories) {
localStorage.setItem('cookie-consent', 'configured');
localStorage.setItem('cookie-categories', JSON.stringify(categories));
if (categories.analytics) loadAnalytics();
if (categories.marketing) loadMarketing();
}
// Aceptar todo
function acceptAll() {
setConsent({ essential: true, analytics: true, marketing: true });
}
// Rechazar todo (solo esenciales)
function rejectAll() {
setConsent({ essential: true, analytics: false, marketing: false });
}
Mi implementación real
En mi portfolio uso una versión simple (aceptar/rechazar) porque solo tengo Google Analytics. No necesito categorías si solo tengo un tipo de cookie no esencial.
El código está en mi portfolio con Vite y Tailwind, y el consentimiento se comparte entre el portfolio y el blog porque ambos están en el mismo dominio (francobosg.netlify.app). El localStorage es compartido, así que si aceptas en el portfolio, el blog ya lo sabe.
Herramientas que NO recomiendo
- CookieBot, OneTrust, Iubenda: Son soluciones SaaS de pago (50-200€/mes). Para una web personal o un blog, es matar moscas a cañonazos. El script que te puse arriba hace exactamente lo mismo.
- Plugins de WordPress: Si no estás en WordPress, no te sirven. Y si estás en WordPress, muchos cargan 100KB+ de JavaScript para algo que se hace con 30 líneas.
- Google Consent Mode: Es un layer adicional que Google recomienda para “medir sin cookies”. En la práctica, sigue necesitando consentimiento para tracking completo. Úsalo solo si tienes ads.
Artículos relacionados
- Configuración definitiva de CORS en Node.js — otra pieza de seguridad web
- Cómo hice mi portfolio con Vite y Tailwind — donde implementé este banner
- Deploy gratis con GitHub Actions + Netlify — donde corre esta web