Generador de banner de consentimiento de cookies autoalojado

Responde unas preguntas y obtén un banner de consentimiento de cookies que te pertenece: HTML, CSS y JavaScript nativo puro para pegar directamente en tu sitio. Bloquea de verdad los scripts no esenciales hasta que alguien dé su consentimiento, guarda la elección en localStorage, y coloca un botón «Rechazar todo» igual de visible que «Aceptar todo» - el detalle que reguladores como la CNIL francesa exigen expresamente. Sin plataforma de consentimiento externa, sin cuenta, sin límite de páginas vistas.

Todo se genera en tu navegador. El código que obtienes no realiza ninguna solicitud de red propia - sin CDN, sin servidor de gestión de consentimiento, sin contador de páginas vistas que agotar.

Se muestra en el texto del banner - los visitantes deben saber quién pregunta.

Obligatorio. Sin ella, el consentimiento no está informado.

Categorías de cookies que defines

Siempre

Strictly necessary

Exenta de consentimiento, por lo que siempre está presente y no se puede desactivar.

La CNIL considera unos 6 meses como buena práctica, tanto para un rechazo como para un consentimiento.

Expuesta como window.cookieConsent y almacenada bajo "cookieConsent.v1".

Dos cosas que no puedes desactivar aquí

«Rechazar todo» siempre se muestra junto a «Aceptar todo», con el mismo tamaño e importancia, en el primer nivel del banner - y las categorías opcionales siempre se generan sin marcar. Esos son los dos detalles por los que los reguladores realmente sancionan a los sitios, así que deliberadamente no hay opción para romperlos.

  • CorregirAdd your site or company name - the banner has to say who is asking.

Vista previa

We use cookies

This site uses cookies that are strictly necessary to run the site. With your permission we would also like to set optional cookies. You can accept them all, refuse them all, or choose category by category.

Accept allReject all
<style>
/* Cookie consent banner - generated by usezivo.com.
   Dependency-free: no fonts, no CDN, no network requests of its own. */

.cc-cookieconsent-root { position: fixed; z-index: 2147483000; }

.cc-cookieconsent-backdrop {
  position: fixed; inset: 0; z-index: 2147482999;
  background: rgba(15, 23, 42, 0.45);
}

.cc-cookieconsent-banner {
  position: fixed;
  left: 0; right: 0; bottom: 0; max-width: none; border-radius: 0; border-left: 0; border-right: 0; border-bottom: 0;
  box-sizing: border-box;
  padding: 18px 20px;
  background: #ffffff;
  color: #0f172a;
  border: 1px solid #e2e8f0;
  border-radius: 14px;
  box-shadow: 0 10px 40px rgba(15, 23, 42, 0.18);
  font: 400 14px/1.55 system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}

.cc-cookieconsent-title { margin: 0 0 6px; font-size: 15px; font-weight: 700; }
.cc-cookieconsent-text { margin: 0 0 14px; color: #475569; }
.cc-cookieconsent-text a { color: #2563eb; text-decoration: underline; }

/* Accept and Reject are deliberately the SAME component: same size, same
   weight, same visual prominence, side by side at the first level. */
.cc-cookieconsent-actions { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; }
.cc-cookieconsent-btn {
  appearance: none;
  flex: 0 0 auto;
  min-width: 148px;
  padding: 11px 18px;
  font: inherit;
  font-weight: 600;
  line-height: 1.2;
  border-radius: 10px;
  border: 1px solid #2563eb;
  background: #2563eb;
  color: #ffffff;
  cursor: pointer;
}
.cc-cookieconsent-btn:hover { filter: brightness(0.94); }
.cc-cookieconsent-btn:focus-visible { outline: 3px solid #2563eb; outline-offset: 2px; }
.cc-cookieconsent-btn--secondary { background: transparent; color: #2563eb; }
.cc-cookieconsent-link {
  appearance: none; background: none; border: 0; padding: 8px 4px;
  font: inherit; color: #475569; text-decoration: underline; cursor: pointer;
}
.cc-cookieconsent-link:hover { color: #0f172a; }

.cc-cookieconsent-panel {
  position: fixed; left: 50%; top: 50%; transform: translate(-50%, -50%);
  box-sizing: border-box;
  width: min(620px, calc(100vw - 32px));
  max-height: min(760px, calc(100vh - 32px));
  overflow-y: auto;
  padding: 22px;
  background: #ffffff; color: #0f172a;
  border-radius: 16px;
  box-shadow: 0 20px 60px rgba(15, 23, 42, 0.3);
  font: 400 14px/1.55 system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}
.cc-cookieconsent-panel h2 { margin: 0 0 6px; font-size: 17px; }
.cc-cookieconsent-panel-intro { margin: 0 0 16px; color: #475569; }

.cc-cookieconsent-cat {
  padding: 14px 0;
  border-top: 1px solid #e2e8f0;
}
.cc-cookieconsent-cat-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.cc-cookieconsent-cat-name { font-weight: 600; }
.cc-cookieconsent-cat-desc { margin: 6px 0 0; color: #64748b; font-size: 13px; }
.cc-cookieconsent-always { font-size: 12px; font-weight: 600; color: #16a34a; }

/* Switch */
.cc-cookieconsent-switch { position: relative; display: inline-block; width: 44px; height: 24px; flex: 0 0 auto; }
.cc-cookieconsent-switch input { position: absolute; opacity: 0; width: 100%; height: 100%; margin: 0; cursor: pointer; }
.cc-cookieconsent-slider {
  position: absolute; inset: 0; border-radius: 999px;
  background: #cbd5e1; transition: background 0.15s ease; pointer-events: none;
}
.cc-cookieconsent-slider::after {
  content: ""; position: absolute; top: 3px; left: 3px;
  width: 18px; height: 18px; border-radius: 50%; background: #fff;
  transition: transform 0.15s ease;
}
.cc-cookieconsent-switch input:checked + .cc-cookieconsent-slider { background: #2563eb; }
.cc-cookieconsent-switch input:checked + .cc-cookieconsent-slider::after { transform: translateX(20px); }
.cc-cookieconsent-switch input:focus-visible + .cc-cookieconsent-slider { outline: 3px solid #2563eb; outline-offset: 2px; }

.cc-cookieconsent-panel-actions { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 18px; padding-top: 16px; border-top: 1px solid #e2e8f0; }

@media (max-width: 520px) {
  .cc-cookieconsent-banner { left: 8px; right: 8px; bottom: 8px; max-width: none; border-radius: 14px; border: 1px solid #e2e8f0; transform: none; top: auto; }
  .cc-cookieconsent-btn { flex: 1 1 100%; min-width: 0; }
}

@media (prefers-color-scheme: dark) {
  .cc-cookieconsent-banner, .cc-cookieconsent-panel { background: #0f172a; color: #e2e8f0; border-color: #334155; }
  .cc-cookieconsent-text, .cc-cookieconsent-panel-intro, .cc-cookieconsent-cat-desc { color: #94a3b8; }
  .cc-cookieconsent-cat, .cc-cookieconsent-panel-actions { border-color: #334155; }
  .cc-cookieconsent-link { color: #94a3b8; }
  .cc-cookieconsent-link:hover { color: #e2e8f0; }
  .cc-cookieconsent-slider { background: #475569; }
  @media (max-width: 520px) { .cc-cookieconsent-banner { border-color: #334155; } }
}
</style>

<!-- Cookie consent banner - generated by usezivo.com. Paste before </body>. -->
<div id="cc-cookieconsent-banner" class="cc-cookieconsent-banner" role="dialog" aria-modal="false" aria-labelledby="cc-cookieconsent-title" hidden>
  <p class="cc-cookieconsent-title" id="cc-cookieconsent-title">We use cookies</p>
  <p class="cc-cookieconsent-text">This site uses cookies that are strictly necessary to run the site. With your permission we would also like to set optional cookies. You can accept them all, refuse them all, or choose category by category. <a href="/privacy">Privacy policy</a></p>
  <div class="cc-cookieconsent-actions">
    <button type="button" class="cc-cookieconsent-btn" data-cc-cookieconsent-action="accept">Accept all</button>
    <button type="button" class="cc-cookieconsent-btn" data-cc-cookieconsent-action="reject">Reject all</button>
    <button type="button" class="cc-cookieconsent-link" data-cc-cookieconsent-action="open">Customise</button>
  </div>
</div>

<div id="cc-cookieconsent-panel" class="cc-cookieconsent-panel" role="dialog" aria-modal="true" aria-labelledby="cc-cookieconsent-panel-title" hidden>
  <h2 id="cc-cookieconsent-panel-title">Cookie preferences</h2>
  <p class="cc-cookieconsent-panel-intro">Strictly necessary cookies cannot be switched off. Every other category is off until you turn it on.</p>
      <div class="cc-cookieconsent-cat">
        <div class="cc-cookieconsent-cat-head">
          <span class="cc-cookieconsent-cat-name">Strictly necessary</span>
          <span class="cc-cookieconsent-always">Always active</span>
        </div>
        <p class="cc-cookieconsent-cat-desc">Needed for the site to work: security, load balancing, remembering what is in your basket and storing this cookie choice. These are exempt from consent.</p>
      </div>
      <div class="cc-cookieconsent-cat">
        <div class="cc-cookieconsent-cat-head">
          <span class="cc-cookieconsent-cat-name">Analytics</span>
          <label class="cc-cookieconsent-switch">
              <input type="checkbox" data-cc-cookieconsent-cat="analytics" aria-label="Analytics">
              <span class="cc-cookieconsent-slider"></span>
            </label>
        </div>
        <p class="cc-cookieconsent-cat-desc">Help us understand which pages are visited and where people get stuck, so we can improve the site. Measurement only, never used to target advertising.</p>
      </div>
  <div class="cc-cookieconsent-panel-actions">
    <button type="button" class="cc-cookieconsent-btn" data-cc-cookieconsent-action="save">Save my choices</button>
    <button type="button" class="cc-cookieconsent-btn cc-cookieconsent-btn--secondary" data-cc-cookieconsent-action="accept">Accept all</button>
    <button type="button" class="cc-cookieconsent-btn cc-cookieconsent-btn--secondary" data-cc-cookieconsent-action="reject">Reject all</button>
  </div>
</div>

<script>
/* Cookie consent - generated by usezivo.com. Vanilla JS, no dependencies,
   no network requests. Paste after the markup, or load as an external file. */
(function () {
  "use strict";

  var KEY = "cookieConsent.v1";
  var MAX_AGE_MS = 183 * 24 * 60 * 60 * 1000;
  var OPTIONAL = ["analytics"];
  var PREFIX = "cc-cookieconsent";

  var banner = document.getElementById(PREFIX + "-banner");
  var panel = document.getElementById(PREFIX + "-panel");
  var backdrop = null;
  var lastFocus = null;

  // --- storage ------------------------------------------------------------
  // localStorage can throw outright in some privacy modes, so every access is
  // guarded. If it is unavailable the banner simply reappears next visit,
  // which is the safe direction to fail in: nothing gets unblocked.
  function read() {
    try {
      var raw = localStorage.getItem(KEY);
      if (!raw) return null;
      var saved = JSON.parse(raw);
      if (!saved || typeof saved !== "object") return null;
      if (typeof saved.at !== "number" || Date.now() - saved.at > MAX_AGE_MS) return null;
      var out = { necessary: true };
      for (var i = 0; i < OPTIONAL.length; i++) {
        out[OPTIONAL[i]] = saved.categories && saved.categories[OPTIONAL[i]] === true;
      }
      return out;
    } catch (e) {
      return null;
    }
  }

  function write(categories) {
    try {
      localStorage.setItem(KEY, JSON.stringify({ v: 1, at: Date.now(), categories: categories }));
    } catch (e) {
      /* storage blocked - the choice applies to this page load only */
    }
  }

  var state = read();

  // --- script gating ------------------------------------------------------
  // Third-party tags are written as:
  //   <script type="text/plain" data-cookie-category="analytics" src="..."></script>
  // The browser will not execute type="text/plain", so nothing runs until the
  // matching category is granted, at which point the tag is swapped for a real
  // one. Inline tags work the same way.
  function unblock(granted) {
    var pending = document.querySelectorAll('script[type="text/plain"][data-cookie-category]');
    for (var i = 0; i < pending.length; i++) {
      var node = pending[i];
      var category = node.getAttribute("data-cookie-category");
      if (!granted[category]) continue;
      var real = document.createElement("script");
      for (var a = 0; a < node.attributes.length; a++) {
        var attr = node.attributes[a];
        if (attr.name === "type" || attr.name === "data-cookie-category") continue;
        real.setAttribute(attr.name, attr.value);
      }
      real.type = "text/javascript";
      if (!node.src) real.text = node.text;
      node.parentNode.replaceChild(real, node);
    }
  }

  var listeners = [];
  function emit(granted) {
    unblock(granted);
    for (var i = 0; i < listeners.length; i++) {
      try {
        listeners[i](granted);
      } catch (e) {
        /* one bad listener must not break the rest */
      }
    }
    try {
      document.dispatchEvent(new CustomEvent("cookieConsent:change", { detail: granted }));
    } catch (e) {
      /* CustomEvent unsupported - the callback API still works */
    }
  }

  // --- UI -----------------------------------------------------------------
  function showBanner() {
    if (!banner) return;
    banner.hidden = false;
    /* no backdrop configured */
  }

  function hideBanner() {
    if (banner) banner.hidden = true;
    if (backdrop && backdrop.parentNode) {
      backdrop.parentNode.removeChild(backdrop);
      backdrop = null;
    }
  }

  function boxes() {
    return panel ? panel.querySelectorAll("[data-" + PREFIX + "-cat]") : [];
  }

  function openPanel() {
    if (!panel) return;
    lastFocus = document.activeElement;
    // Reflect the stored choice, or leave every optional box OFF when there is
    // no stored choice yet. Nothing is ever pre-ticked.
    var current = read();
    var list = boxes();
    for (var i = 0; i < list.length; i++) {
      var id = list[i].getAttribute("data-" + PREFIX + "-cat");
      list[i].checked = !!(current && current[id]);
    }
    panel.hidden = false;
    var first = panel.querySelector("button, input");
    if (first) first.focus();
    document.addEventListener("keydown", onKeydown);
  }

  function closePanel() {
    if (!panel) return;
    panel.hidden = true;
    document.removeEventListener("keydown", onKeydown);
    if (lastFocus && lastFocus.focus) lastFocus.focus();
  }

  function onKeydown(e) {
    // Escape closes the PREFERENCES panel only. It never dismisses the banner:
    // dismissing without choosing must not be treated as consent.
    if (e.key === "Escape") closePanel();
  }

  function decide(categories) {
    var granted = { necessary: true };
    for (var i = 0; i < OPTIONAL.length; i++) {
      granted[OPTIONAL[i]] = categories[OPTIONAL[i]] === true;
    }
    state = granted;
    write(granted);
    closePanel();
    hideBanner();
    emit(granted);
  }

  function all(value) {
    var out = {};
    for (var i = 0; i < OPTIONAL.length; i++) out[OPTIONAL[i]] = value;
    return out;
  }

  function fromPanel() {
    var out = {};
    var list = boxes();
    for (var i = 0; i < list.length; i++) {
      out[list[i].getAttribute("data-" + PREFIX + "-cat")] = list[i].checked === true;
    }
    return out;
  }

  document.addEventListener("click", function (e) {
    var target = e.target.closest ? e.target.closest("[data-" + PREFIX + "-action]") : null;
    if (!target) return;
    var action = target.getAttribute("data-" + PREFIX + "-action");
    if (action === "accept") decide(all(true));
    else if (action === "reject") decide(all(false));
    else if (action === "save") decide(fromPanel());
    else if (action === "open") openPanel();
    else if (action === "close") closePanel();
  });

  // --- public API ---------------------------------------------------------
  window["cookieConsent"] = {
    /** true if that category has been granted. Always true for "necessary". */
    has: function (category) {
      if (category === "necessary") return true;
      return !!(state && state[category]);
    },
    /** The full granted map, or null if no choice has been made yet. */
    get: function () {
      return state ? JSON.parse(JSON.stringify(state)) : null;
    },
    /** Run cb now (if a choice exists) and on every later change. Returns an unsubscribe fn. */
    onChange: function (cb) {
      listeners.push(cb);
      if (state) {
        try {
          cb(state);
        } catch (e) {
          /* ignore */
        }
      }
      return function () {
        var i = listeners.indexOf(cb);
        if (i > -1) listeners.splice(i, 1);
      };
    },
    /** Wire this to a permanent "Cookie preferences" link in your footer. */
    openPreferences: openPanel,
    /** Forget the stored choice and ask again. Withdrawal must be as easy as consent. */
    reset: function () {
      try {
        localStorage.removeItem(KEY);
      } catch (e) {
        /* ignore */
      }
      state = null;
      showBanner();
    },
  };

  // --- boot ---------------------------------------------------------------
  if (state) {
    // A stored choice: apply it silently, do not nag.
    emit(state);
  } else {
    // No choice yet -> nothing is unblocked, and the banner asks.
    showBanner();
  }
})();
</script>

El código se actualiza en vivo, pero corrige los elementos anteriores antes de publicarlo.

De dónde vienen las reglas anteriores

Este generador produce la interfaz del banner y el mecanismo de almacenamiento del consentimiento. No es asesoría legal y no puede, por sí solo, hacer que un sitio cumpla la normativa - lo que realmente importa es qué rastreadores usas, si realmente los has conectado a través de la barrera, y qué dice tu política de privacidad. Las normas también difieren entre países y cambian con el tiempo. Si hay mucho en juego, haz que un abogado lo revise.

Por qué autoalojarlo en vez de usar Axeptio o Cookiebot

Las plataformas de consentimiento alojadas son productos excelentes, pero sus planes gratuitos tienen límites: al superar un umbral mensual de páginas vistas, el banner deja de funcionar o empieza a facturarse. Además, cargan su propio JavaScript desde su propio dominio en cada visita de página, lo que añade una solicitud de terceros a tu sitio en nombre de la privacidad, y una dependencia que puede cambiar sin que tú lo controles.

El fragmento de código que genera esta herramienta pesa unos 6 KB, no tiene dependencias, no hace ninguna solicitud de red por su cuenta, y vive en tu propio repositorio, donde puedes leer cada línea. No hay ningún proveedor cuya cuota se pueda agotar, y nada se comunica a escondidas con el exterior.

Qué hace el bannerCómo
Bloquea los scripts antes del consentimientoLos scripts de terceros se escriben como type="text/plain" con un atributo data-cookie-category, que los navegadores se niegan a ejecutar. Al dar el consentimiento, la etiqueta se sustituye por un elemento script real, y solo entonces se ejecuta.
Rechazar es tan fácil como aceptarAceptar todo y Rechazar todo son el mismo componente de botón - mismo ancho, relleno, grosor y color - uno junto al otro en el primer nivel.
Nada viene premarcadoLos interruptores de preferencia generados nunca llevan el atributo checked. Nunca.
El silencio no es consentimientoCerrar o ignorar el banner no guarda nada ni desbloquea nada. Solo un clic explícito registra una elección.
Retirar el consentimiento cuando quieras<ns/>.openPreferences() vuelve a abrir el panel, y .reset() olvida la elección - enlaza cualquiera de los dos a un enlace del pie de página.
Recuerda la respuestalocalStorage, con una marca de tiempo y una caducidad que tú elijas (6 meses por defecto), para que a los visitantes no se les pregunte de nuevo en cada página.

Preguntas frecuentes

¿Cómo creo un banner de cookies gratis conforme sin una plataforma de pago?

Rellena el nombre de tu sitio, el enlace a tu política de privacidad y las categorías de cookies que realmente usas, luego copia el bloque generado en tu página antes de la etiqueta de cierre body. Obtienes HTML, CSS y JavaScript nativos sin dependencia de Axeptio, Cookiebot, Tarteaucitron ni ninguna otra plataforma de consentimiento - así que no hay límite de páginas vistas que alcanzar ni script de terceros cargando en cada página.

¿Realmente bloquea Google Analytics antes del consentimiento, o solo oculta el banner?

Realmente lo bloquea, pero tienes que hacer una cosa: cambiar tu etiqueta de type="text/javascript" a type="text/plain" y añadir data-cookie-category="analytics". Un navegador no ejecuta un script text/plain, así que la etiqueta queda inerte en la página. En el momento en que el visitante concede esa categoría, el script se clona en un elemento script real y se ejecuta.

¿Por qué no hay opción para ocultar el botón Rechazar todo?

Porque es el problema más citado de los banners no conformes. La postura de la CNIL es que debe ser tan fácil rechazar rastreadores como aceptarlos, y considera que un botón «Rechazar todo» en el primer nivel, con la misma apariencia que «Aceptar todo», es la forma clara de lograrlo. Este generador siempre produce ambos botones con la misma clase CSS, así que son idénticos en tamaño y peso.

¿Esto hace que mi sitio sea legalmente conforme con el RGPD?

No, y ten cuidado con cualquier herramienta que afirme lo contrario. Esto genera la interfaz del banner y el mecanismo de almacenamiento del consentimiento. El cumplimiento también depende de qué rastreadores uses realmente, si cada uno pasa de verdad por la barrera, de lo que digan tus políticas de privacidad y cookies, y de normas que varían según el país y cambian con el tiempo.

Herramientas relacionadas