Google Tag Manager — Fundamentos
1. Qué es
Antes de explicar el concepto, tenemos que tener en cuenta que vamos a necesitar una web para poder entender la parte práctica — GitHub Pages, WordPress son las más comunes. En otros temas del blog explico con más detalle cómo tener tu propia web en GitHub Pages. Por ahora pasemos con la definición.
Google Tag Manager (GTM) es una plataforma que te permite gestionar fragmentos de código en tu sitio web sin tener que tocar el código fuente cada vez.
Esos fragmentos de código se llaman etiquetas. Cada etiqueta es el código de una herramienta externa corriendo dentro de tu web — GA4 tiene la suya, Facebook Pixel tiene la suya, Google Ads tiene la suya. Sin GTM, tendrías que pegar cada uno de esos códigos directamente en tu HTML. Con GTM, los configuras desde su interfaz y el sitio no se toca.
Pero una etiqueta sola no hace nada. Necesita saber cuándo ejecutarse — y ahí entra el activador. El activador es la condición que le dice a GTM en qué momento debe correr una etiqueta: cuando alguien carga una página, cuando hace click en un botón, cuando envía un formulario. Sin activador, la etiqueta existe pero nunca se dispara.
Etiqueta + activador es la unidad mínima que funciona en GTM. Una sin la otra no sirve.
Un contenedor GTM agrupa todas las etiquetas y activadores de un sitio web. Cada contenedor tiene un ID único (GTM-XXXXXXX).
2. El punto de entrada: el snippet
GTM llega a un sitio web a través de un snippet — un fragmento de código que se pega una sola vez en el HTML. Ese snippet es un script de JavaScript.
<!-- en <head> -->
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;
f.parentNode.insertBefore(j,f);})(window,document,'script','dataLayer','GTM-XXXXXXX');
</script>
<!-- en <body> -->
<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-XXXXXXX"
height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
3. Dónde va el snippet según la arquitectura del sitio
"Se pega una vez en el HTML" es una simplificación. Lo que significa en realidad es: se pega una vez en la fuente que genera todos los HTMLs. Cómo se hace eso depende de la arquitectura del sitio.
WordPress (lo más común)
WordPress tiene un archivo header.php que se incluye automáticamente en todas las páginas del sitio. El snippet va ahí una sola vez y WordPress lo inyecta en cada página al renderizarla.
header.php ← snippet aquí una vez
↓
WordPress lo inyecta en cada página al servirse
Por eso la mayoría de tutoriales dicen "pégalo una vez" — están pensando en WordPress.
SPA — Single Page Application (React, Vue, Angular)
Una SPA tiene un único index.html. El framework renderiza todas las "páginas" dinámicamente con JavaScript sin recargar el navegador. El snippet va en ese único index.html.
index.html ← snippet aquí, una sola vez, un solo archivo
Pero hay un gotcha importante: como el navegador nunca recarga la página al navegar entre secciones, los activadores de pageview de GTM no se disparan solos. Hay que configurar un activador especial de "cambio de historial" o hacer pushes manuales al dataLayer en cada cambio de ruta.
Sitio estático en GitHub Pages con flujo de publicación por post
En este caso el sitio vive en GitHub Pages — un hosting estático sin servidor. Cada post tiene su propia carpeta con su propio index.html:
web/
index.html ← homepage
aprende-sql-facil-y-rapido/
index.html ← post generado
como-llevar-un-script-a-cloud-run/
index.html ← post generado
El contenido de los posts se escribe como Markdown en una carpeta separada. Un script Python (publish.py) lee cada Markdown, lo convierte a HTML usando una plantilla (post_template.html), y genera el index.html de ese post.
El snippet de GTM va en post_template.html — la plantilla que publish.py usa para todos los posts. La homepage tiene su propio index.html donde el snippet también está.
post_template.html ← snippet aquí una vez (fuente)
↓
publish.py lee cada .md y genera un index.html por post
↓
cada post generado tiene el snippet — repetido, pero correcto
El snippet está repetido en cada HTML generado — eso es el output esperado, no un problema. Si mañana cambia el ID de GTM, se edita en un solo lugar (post_template.html) y se relanza publish.py.
Resumen
| Arquitectura | Fuente del snippet | HTMLs con snippet |
|---|---|---|
| WordPress | header.php |
Todos (inyectado al servir) |
| SPA | index.html |
Uno solo |
| Estático en GitHub Pages con publish.py | post_template.html |
Todos (generados por publish.py) |
4. Qué hace el snippet exactamente
Al ejecutarse, el snippet hace tres cosas en orden:
-
Crea el dataLayer — inicializa
window.dataLayer = []si no existe. Es el buzón de comunicación entre el sitio y GTM. -
Hace un push al dataLayer — mete la marca de tiempo y el evento
gtm.jspara marcar el momento en que GTM arrancó:javascript window.dataLayer.push({ event: 'gtm.js', 'gtm.start': 1234567890 }) -
Va a buscar el motor real de GTM — el snippet que pegas en el HTML no es GTM completo, es solo el arranque. El snippet descarga el script completo de GTM desde los servidores de Google. Hasta que ese script llega, GTM no existe en la página.
El snippet es la llave que abre la puerta. Lo que entra por esa puerta es GTM de verdad. Una vez que llega, empieza a escuchar el dataLayer para disparar etiquetas.
5. El dataLayer
Imagina que tu sitio web es una oficina y las herramientas externas son departamentos distintos que necesitan ser informados cuando algo importante sucede. El dataLayer es el tablón de anuncios de esa oficina — un lugar central y estandarizado donde se publican los mensajes.
Técnicamente es un array en la memoria del navegador (window.dataLayer).
6.1 Quién puede crearlo:
Cuando en la sección anterior decíamos que el snippet "crea el dataLayer", hablábamos de la inicialización técnica: el snippet escribe window.dataLayer = [] en el navegador — el tablón vacío.
Pero hay dos actores que pueden meter información en ese tablón:
-
El desarrollador del sitio — escribe pushes personalizados directamente en el código: en la plantilla HTML, en el JavaScript de la página, o generados por un script como
publish.py. Estos pushes llevan datos que GTM no puede inferir solo: la categoría de un post, el ID de usuario, el precio de un producto. -
GTM — una vez cargado, también hace sus propios pushes automáticos (pageview, DOM listo, window cargado). Pero solo puede pushear lo que ya es visible en el navegador.
Si el único actor fuera el snippet, el dataLayer contendría únicamente la marca de tiempo de arranque — y GTM solo podría trabajar con lo que él mismo detecta: URLs y clicks. Todo lo que importa en analytics (categorías, usuarios, productos, estados de sesión) lo tienen que poner los desarrolladores de forma explícita.
6.2 Por qué existe:
Con dataLayer, el sitio y las herramientas no se hablan directamente. El sitio solo publica información en el tablón. Las herramientas la leen a través de GTM. Añadir o quitar una herramienta no requiere tocar el código del sitio — solo configurarlo en GTM.
6.3 Qué herramientas lo consumen:
Solo herramientas client-side — las que corren en el navegador:
- Analítica: Google Analytics, Adobe Analytics, Matomo
- Publicidad y retargeting: Facebook Pixel, Google Ads, TikTok Pixel, LinkedIn Insight Tag
- Mapas de calor: Hotjar, Crazy Egg
- Chat y soporte: Intercom, Drift
- CRM con componente client-side: HubSpot tracking code, Salesforce Pardot
7. push()
push() es la acción de meter un objeto dentro del dataLayer. Es la única forma de agregar información al array.
7.1 Quién hace los pushes
El desarrollador del sitio — no el snippet, no GTM. Los pushes son código JavaScript que el desarrollador escribe explícitamente: en la plantilla HTML, en scripts de la página, o generados por herramientas como publish.py.
GTM también hace algunos pushes internos automáticos (pageview, DOM listo, window cargado), pero los pushes que llevan datos de negocio son siempre responsabilidad del desarrollador.
7.2 Cuántos pushes puede tener una página
Tantos como necesite el desarrollador. No hay límite técnico. Lo habitual es tener:
- Al cargar la página — datos estáticos: categoría, slug, tipo de contenido.
- Por acción del usuario — click en botón, envío de formulario, scroll hasta el final.
- Por evento de negocio — compra completada, producto añadido al carrito.
Una página compleja puede tener decenas de pushes distintos.
7.3 Cuándo se ejecuta un push
Un push se ejecuta cuando el JavaScript que lo contiene se ejecuta — no antes, no después. Si el push está en el HTML de la página, se lanza al cargar. Si está dentro de un listener de click, se lanza cuando el usuario hace click.
Si un push está en la plantilla de todos los posts, se lanza en cada post, siempre — aunque la información que contiene esté vacía. El push no evalúa si los datos son válidos antes de ejecutarse. GTM recibe el push con los campos que tenga; si algún campo no tiene valor, llega vacío o undefined. No hay error visible, solo dato vacío.
window.dataLayer.push({
'categoria': 'sql',
'slug': 'aprende-sql-desde-cero'
})
Cada vez que se llama, añade un objeto nuevo al final del array. El dataLayer acumula todos los objetos que se han pusheado desde que se cargó la página.
8. Diferencia entre dataLayer y push()
Son dos cosas distintas que se confunden porque siempre aparecen juntas:
- dataLayer — el contenedor. El tablón de anuncios. Un array que vive en la memoria del navegador y acumula información.
- push() — la acción. Colgar una nota en ese tablón. El método que añade un objeto al array.
No son intercambiables: el dataLayer existe aunque nunca se haga un push. Y push() no tiene sentido sin un dataLayer donde meter la información.
9. Los eventos — un push con el campo event
No todos los pushes tienen el mismo efecto. La diferencia clave está en si el objeto incluye o no el campo event:
- Sin
event— lleva información al dataLayer sin disparar nada. GTM la acumula. - Con
event— es la señal que GTM usa para actuar.
En GTM, un "evento" es específicamente un push que incluye ese campo:
window.dataLayer.push({
'event': 'login', // esto convierte el push en un evento
'new_client': 'true'
})
El campo event es la señal que GTM usa para actuar. Cuando GTM detecta un push con event, revisa todos sus activadores buscando uno que coincida con ese nombre. Si lo encuentra, dispara la etiqueta asociada (esto lo explicaremos cuando veamos más a fondo etiquetas).
Sin el campo event, GTM recibe los datos y los acumula en el dataLayer — pero no dispara nada.
| Tipo de push | Tiene event |
GTM actúa |
|---|---|---|
| Push de datos | No | No — solo acumula |
| Push de evento | Sí | Sí — revisa activadores |
10. Lo que GTM puede medir solo — y dónde aparece el límite
Con GTM instalado y una configuración básica ya puedes medir cuántas personas vieron cada página. GTM hace sus propios pushes automáticos al cargar — el desarrollador no tiene que escribir nada. Esos pushes internos son suficientes para saber cuántas visitas tuvo cada post individualmente.
Eso funciona bien hasta que suceden ciertos casos específicos:
11. El problema: quiero medir por categoría
Supón que tienes 12 posts organizados en 3 categorías: Google Cloud, Google Analytics y Power BI. Quieres saber cuántas personas vieron posts de cada categoría — no post por post, sino agrupado.
Ahí GTM se queda sin respuesta. Sus pushes automáticos al cargar la página capturan lo que el navegador expone: la URL, el título, el referrer. Cuando alguien visita un post, GTM solo sabe la URL — por ejemplo /aprende-sql-facil-y-rapido-desde-cero/. No sabe a qué categoría pertenece ese post. Esa información no está en la URL, no está en el título, no está en ningún lugar del HTML que GTM pueda leer solo.
La categoría vive en el frontmatter del Markdown (los metadatos) — una fuente que existe antes de que el HTML se genere, pero que no llega al navegador a menos que alguien la ponga ahí explícitamente.
Ese es el límite: GTM puede leer lo que está en el navegador. Lo que no llega al navegador, no puede leerlo.
Para que GTM pueda leerlo, alguien tiene que ponerlo en el navegador explícitamente — y ese alguien es el desarrollador, a través de un push al dataLayer.
| Qué trackear | GTM lo lee solo | Por qué |
|---|---|---|
| Visitas por página | Sí | La URL siempre está disponible |
| Clicks en links | Sí | El navegador expone los clicks nativamente |
| Profundidad de scroll | Sí | El navegador expone la posición de scroll |
| Categoría del post | No | Vive en los metadatos, no llega al navegador por defecto |
| Slug o título del post | No | Mismo motivo |
12. La solución: pasar los datos del frontmatter al dataLayer
El frontmatter es una capa de etiquetas que utilizo en mi blog para parametrizar un post, por ejemplo:
---
titulo: "Aprende SQL fácil y rápido desde cero"
categoria: sql
slug: aprende-sql-facil-y-rapido-desde-cero
sistema-operativo: mac-windows
---
publish.py , que es el script de python que utilizo para publicar mis post, lee ese frontmatter al generar el HTML de cada post — tiene toda esa información disponible en el momento de construcción. El problema es que no llega al navegador por defecto. La solución es que publish.py la embeba directamente en el HTML como un push al dataLayer, antes del snippet de GTM:
window.dataLayer.push({
'titulo': 'Aprende SQL fácil y rápido desde cero', // título del post
'categoria': 'sql', // categoría — lo que queremos agrupar
'slug': 'aprende-sql-facil-y-rapido-desde-cero', // identificador único del post
'sistema_operativo': 'mac-windows' // OS del contenido
})
Cuando GTM se inicializa, ya encuentra esos datos en el dataLayer. Puede leerlos como variables y adjuntarlos como parámetros a cualquier evento — incluyendo el pageview.
Resultado: ahora sí puedes medir cuántas personas vieron posts de cada categoría, sin tocar el código del sitio manualmente. publish.py genera ese bloque automáticamente para cada post desde el frontmatter.
13. Gotchas
-
Olvidar publicar el contenedor — los cambios en la interfaz de GTM no van en vivo hasta que presionas "Publicar". Puedes pasar horas configurando etiquetas en borrador y creer que están activas — no lo están.
-
El orden del dataLayer importa — el push de metadatos de
publish.pytiene que ir antes del snippet en el HTML. Si va después, GTM ya procesó el pageview sin esos datos y no los incluye en el evento. No hay error visible, solo datos incompletos. -
Los bloqueadores de anuncios bloquean GTM — Brave, uBlock, Firefox en modo estricto tratan GTM como herramienta de tracking y lo bloquean antes de que cargue. Tus datos de analytics nunca van a ser 100% completos. No hay solución sencilla, solo hay que saberlo.
-
Preview mode no es live — GTM tiene un modo de previsualización para probar cambios. Lo que ves en preview solo lo ves tú — los visitantes reales siguen viendo la versión publicada anterior. Mucha gente prueba en preview y asume que ya está en producción.
-
El dataLayer se reinicia al navegar a otra página — el dataLayer vive en la memoria del navegador. Cada vez que el usuario carga una URL nueva, esa memoria se borra. En un sitio estático (GitHub Pages, HTML independientes) cada página empieza con un dataLayer limpio. En una SPA (React, Vue) el dataLayer persiste porque el navegador nunca recarga. Esto afecta cómo diseñas tus pushes — en un sitio estático no puedes asumir que los datos de la página anterior siguen disponibles.
14. Referencia oficial
- https://support.google.com/tagmanager