Guía de supervivencia del analista digital moderno - De data collection a data pipelines (2)
Segunda parte (técnica) de la transición del rol tradicional de data collection a uno mucho más técnico y adaptado a los nuevos procesos
Esta entrega va a tratar de realizar un análisis pormenorizado de lo que debería hacerse para un proyecto que me he inventado, pero que puede ser el día a día de algunas empresas (o cosas similares a la que vamos a ver). Lo he hecho de esta manera (llamando a la empresa ficticia dadadata por supuesto), porque creo que va a ser más fácil de seguir y entender el valor de estas “nuevas” técnicas para el toolkit de cualquier analista digital.
Antes de seguir, aquí viene la cuña publicitaria que ya habéis visto donde os recuerdo que:
Vamos a ver cosas algo más complejas y técnicas, no tengáis miedo de preguntar lo que necesitéis en comentarios o decidle a vuestro gpt de turno que os traduzca lo que sea.
Como comprenderéis, esto es una entelequia. Nada de lo que hablamos aquí pasa como está explicado (las personas aquí nombradas no existen, tendría que haber mucha lucha interna para que todo fuera tan sencillo, los eventos de dataLayer serían más voluminosos en cuanto a información, ya me entendéis), pero creo que sirve para entender toda la ruta. Espero que sea así, os espero en la siguiente parada.
Extractos de un futuro pasado
Hay arquitecturas que nacen del miedo a perder datos, y otras del deseo de hacer las cosas bien. Esta es la historia de Dadadata, una empresa que cultiva datos como otros cultivan uvas, con una mezcla de ciencia, arte y una pizca de superstición. Aquí, la tecnología no se improvisa: se debate, se documenta, se versiona y —sobre todo— se piensa para sobrevivir a cualquier moda o rebranding corporativo.
Los lunes en Dadadata suelen arrancar despacio: alguien entra con café, alguien se queja de la VPN, y a los pocos minutos una tabla de Excel aparece en la pantalla de la sala. Pero bajo esa calma, el equipo de datos prepara desde hace semanas su salto más ambicioso: diseñar un pipeline de data collection capaz de resistir todo lo que venga.
La conversación empezó una tarde cualquiera, en la sala con la luz más rara de la oficina. “¿Por qué vamos a hacer lo que hacen todos?”, preguntó Clara, la data architect, mientras dibujaba un embudo en la pizarra.
“Podríamos tirar los datos de ga4 a BigQuery y ya. Pero, ¿y si lo hacemos para que aguante el día que migremos, el día que cambien los vendors, el día que queramos reescribir media stack?”
Un silencio leve —de los buenos— y varios asentimientos. No era una urgencia, ni una reacción a una auditoría. Era la oportunidad de crear algo con vocación de legado.
La visión en la pizarra
El equipo se sienta en semicírculo, con portátiles y botellas de agua medio vacías, y empiezan a escribir el manifiesto Dadadata:
Todo evento es nuestro: lo recogemos, lo controlamos, lo auditamos.
El pipeline debe ser multi-nube y multi-vida: hoy Snowflake, mañana lo que sea.
Nada se pierde, todo se transforma (a lo Drexler).
La privacidad debe ser by-design
De ahí salen las primeras cajas y flechas:
dataLayer (origen de la verdad)
GTM Server-Side (la aduana)
Google Cloud Storage (el archivo universal)
Snowflake (el cerebro analítico)
Dashboards, ML, reporting... (el destino siempre cambia)
“Queremos que si dentro de cinco años llega alguien nuevo, entienda por qué hicimos cada cosa.”
—dice Jorge, el responsable de calidad de datos, mientras fotografía la pizarra.
El acta fundacional: un dataLayer sin miedo
En Dadadata, cada evento del dataLayer es casi una confesión. El equipo sabe que lo que no recojan ahora, nadie lo podrá recuperar luego. Por eso, se reúnen para revisar el “schema” como quien lee un contrato.
dataLayer.push({
event: 'begin_checkout',
cart_id: 'CART_2749',
user_id: 'USR_01911',
items: [ { id: 'SKU201', name: 'Guantes DataPro', price: 19.99 }, { id: 'SKU202', name: 'Gorra Pipeline', price: 15.50 } ],
campaign: 'black_friday',
timestamp: Date.now()
});
Hay bromas internas sobre los nombres de variables (“fart_id”), pero también reglas inamovibles:
Todos los IDs son strings, nunca números.
La fecha es epoch, pero humana en logs.
Cualquier cambio en el schema pasa por revisión y mensaje de Slack.
“El dataLayer es nuestro diario. Y a veces también nuestro testamento.”
—bromea Carla, la product manager.
La frontera Dadadata: GTM Server-Side
Aquí empieza el verdadero viaje.
No hay nada más placentero que interceptar un evento y saber que, antes de que Google o cualquier vendor lo procese, ya es tuyo.
1. Configuración del contenedor
Se crea un contenedor Server-Side en GTM, llamado
dadadata-eu
, desplegado en Cloud Run (“Que escale solo, que nadie se tenga que preocupar los domingos”, apunta Álvaro, devops).El dominio asignado:
https://dadadata-eu.a.run.app
Se agrega a la lista blanca de los firewalls y se testea desde varias oficinas y cafés (“Comprobamos que ni la peor WiFi pueda frenar un evento”, dice Javier, el analista de QA).
2. Redirección de eventos del front
Modifican la config de la etiqueta GA4 en todas las webs y apps (TODOS LOS QUE LÉEIS ESTO SABÉIS CÓMO SE HACE ESTO, AQUÍ DEJO EL CÓDIGO COMO EJEMPLO DIDÁCTICO SÓLO):
<script async src="https://www.googletagmanager.com/gtag/js?id=G-DADADATA"></script> <script> window.dataLayer = window.dataLayer || []; function gtag(){dataLayer.push(arguments);} gtag('js', new Date()); gtag('config', 'G-DADADATA', { 'transport_url': 'https://dadadata-eu.a.run.app/g/collect' }); </script>
Hacen pruebas en Chrome, Firefox, Safari, y con varios adblockers activos.
“El dato sale igual, no lo paran ni los gremlins”, apunta con orgullo Lucía, que lidera QA.
3. El Client universal y la Tag mágica
Añaden un Client HTTP en GTM SS, escuchando todos los POST a
/event
.Generan una template que sea capaz de enviar datos de gtm ss a un bucket de cloud storage (es una template que te he desarrollado yo, ya está disponible dentro de la comunidad aquí)
JS de la template, adaptadla a lo que necesitéis:
const getGoogleAuth = require('getGoogleAuth');
const logToConsole = require('logToConsole');
const sendHttpRequest = require('sendHttpRequest');
const JSON = require('JSON');
const encodeURIComponent = require('encodeUriComponent');
const getEventData = require('getEventData');
const Promise = require('Promise');
let method = 'POST';
// Function to upload data to a Cloud Storage bucket
function uploadDataToBucket(bucketName, fileName, data, options) {
options = options || {};
// Get Google Auth credentials
const auth = getGoogleAuth({
scopes: ['https://www.googleapis.com/auth/cloud-platform', 'https://www.googleapis.com/auth/devstorage.read_write']
});
// Construct the URL for the Cloud Storage API
const url = 'https://storage.googleapis.com/upload/storage/v1/b/' + encodeURIComponent(bucketName) + '/o?uploadType=media&name=' + encodeURIComponent(fileName);
// Make the request to the Cloud Storage API
return sendHttpRequest(url, {method: method, headers: { 'Content-Type': 'application/json' }, authorization: auth}, JSON.stringify(data))
.then((response) => {
if (response.statusCode >= 200 && response.statusCode < 300) {
logToConsole('Uploaded data: ' + response.responseText);
} else {
logToConsole('Network response was not ok: ' + response.statusText);
}
})
.catch((error) => {
logToConsole('Error uploading data to bucket:', error);
});
}
// Example usage of the function
function main() {
const bucketName = data.bucket;
const fileName = data.filename;
const dataToUpload = getEventData(data.targetObject);
uploadDataToBucket(bucketName, fileName, dataToUpload, { requestTimeout: 5000 });
}
// Execute the main function
main();
data.gtmOnSuccess();
Se documenta cada paso en Notion, se guardan capturas de pantalla, y la función se testea con eventos simulados (“Un pipeline que no se prueba antes de dormir nunca te dejará dormir”, dice Luis, el más veterano).
El bucket: bodega de la memoria
El bucket en Google Cloud Storage es el lugar donde los datos reposan antes de su “segunda vida”.
A cada evento le espera su archivo, su nombre calculado, su pequeña eternidad digital.
Bucket:
gs://dadadata-pipeline-archive/events/
Política de retención: 30 días (“Si no lo necesitamos en un mes, nunca lo necesitaremos. O eso creemos”, reconoce Clara).
Ejemplo de archivo:
{ "event": "begin_checkout", "cart_id": "CART_2749", "user_id": "USR_01911", "items": [ { "id": "SKU201", "name": "Guantes DataPro", "price": 19.99 }, { "id": "SKU202", "name": "Gorra Pipeline", "price": 15.50 } ], "campaign": "black_friday", "timestamp": 1718726010000 }
Un script semanal recorre todos los archivos buscando anomalías: eventos huérfanos, campos vacíos, consentimientos raros.
Los cambios en el schema van acompañados de un README dentro del bucket (
/events/schemas/README.md
), con fecha, autor y motivo.
“El bucket es la hemeroteca de Dadadata. Aquí, cada bug y cada eureka quedan documentados, aunque nadie los vuelva a leer nunca.”
—dice Lucía antes de borrar uno de los primeros archivos de prueba.
El salto al reino blanco: Snowflake
El equipo se reúne —esta vez con pizza fría y menos épica— para planear la integración con Snowflake, el warehouse que ahora será el “cerebro” del dato.
1. Configuración del External Stage
Clara y Álvaro configuran la Storage Integration:
2. Primer COPY INTO: la fiesta del dato
Se programa la primera carga:
COPY INTO raw.events_landing FROM @raw.dadadata_gcs_stage FILE_FORMAT = (TYPE = 'JSON') ON_ERROR = 'SKIP_FILE';
El equipo aplaude cuando la tabla se llena de registros VARIANT, cada uno un mini-universo JSON.
Se ejecuta el primer SELECT para ver el contenido, y la sala estalla en chistes sobre los nombres de campaña y los test de Carla.
La alquimia de la transformación
La llegada de los eventos al warehouse no es el final, sino el inicio de una segunda vida.
En Dadadata, cada registro crudo en la tabla raw.events_landing
es solo materia prima. Hace falta pulir, limpiar, transformar, y a veces, pelearse con los esquemas cambiantes y las bromas del front (“quien mete un campo nuevo sin avisar paga los cafés del viernes”). Sí, esto no parece parte de las labores que suele caer dentro de data collection, pero al final se trata de dejar el dato lo mejor posible para que cuando los analistas entren, lo hagan para hacer su trabajo de la mejor forma posible.
1. Primeros SELECTs, primeras sorpresas
Antes de tocar la transformación, el equipo examina la tabla de eventos:
SELECT $1:event::STRING, $1:transaction_id::STRING, $1:user_id::STRING, $1:items FROM raw.events_landing LIMIT 5;
Aparecen eventos de prueba, campañas con typos, y algún emoji perdido.
“Esta tabla es como la nevera después de un cumpleaños: hay de todo, y no todo debería estar ahí.”
—resopla Jorge.
2. El modelo analítico: tabla de compras
Clara se pone manos a la obra con la primera tabla “útil” del pipeline: la tabla de compras limpias y deduplicadas.
Se debate el esquema, y se decide:
transaction_id (único)
user_id
campaign
revenue
currency
num_items
event_time
La consulta para poblarla:
CREATE OR REPLACE TABLE analytics.purchases AS SELECT $1:transaction_id::STRING AS transaction_id, $1:user_id::STRING AS user_id, $1:campaign::STRING AS campaign, $1:value::FLOAT AS revenue, $1:currency::STRING AS currency, ARRAY_SIZE($1:items) AS num_items, $1:timestamp::TIMESTAMP_LTZ AS event_time FROM raw.events_landing WHERE $1:event::STRING = 'purchase';
Si algún campo clave falta, se excluye (“Nada de rows zombis”, insiste Carla).
3. La rutina invisible: tasks y automatización
En Dadadata nadie quiere madrugar para lanzar scripts a mano. Se crea una task en Snowflake:
CREATE OR REPLACE TASK analytics.task_transform_purchases WAREHOUSE = 'TRANSFORM_WH' SCHEDULE = 'USING CRON 5 2 * * * Europe/Madrid' AS INSERT INTO analytics.purchases SELECT $1:transaction_id::STRING, $1:user_id::STRING, $1:campaign::STRING, $1:value::FLOAT, $1:currency::STRING, ARRAY_SIZE($1:items), $1:timestamp::TIMESTAMP_LTZ FROM raw.events_landing WHERE $1:event::STRING = 'purchase' AND $1:transaction_id IS NOT NULL QUALIFY ROW_NUMBER() OVER (PARTITION BY $1:transaction_id ORDER BY $1:timestamp DESC) = 1;
REVISAR LA TASK
La task corre de a primera hora, y si algo falla, salta un aviso a Slack y alguien (casi siempre Álvaro) lo revisa al desayuno.
4. Lo que nunca sale en los tutoriales: problemas reales
Schemas cambiantes: un día el front mete un campo “discount_code”. El pipeline no falla, pero la tabla final lo ignora (“La próxima vez, avisad por Slack antes de innovar”, pide Álvaro).
Eventos duplicados: a veces, los clientes con mala conexión repiten pagos. El filtro por
transaction_id
y timestamp funciona, pero la paranoia nunca duerme.Fugas de consentimiento: algún developer se olvida de mapear el consent y entran eventos “undefined”. El script de calidad los excluye, pero cada vez que pasa alguien trae donuts para compensar.
Ficheros corruptos o vacíos: COPY INTO salta archivos dañados y los mueve a
/events/error/
. Los lunes, Lucía los revisa uno a uno y documenta qué falló (“Esto me recuerda a revisar deberes de instituto”).
5. Aprendizajes, iteraciones y cultura
Con el pipeline ya vivo, Dadadata dedica los viernes a retrospectivas técnicas:
¿Qué falló esta semana?
¿Qué podemos hacer más robusto?
¿Hay algo que simplificar, documentar o dejar preparado para el próximo cambio de vendor?
Surgen ideas:
Versionar la lógica de transformación con dbt.
Incluir metadatos de origen en cada archivo del bucket.
Automatizar la generación de diagramas del pipeline (“Que nadie tenga que preguntar nunca más cómo fluye el dato”).
Cada cambio grande queda registrado en el “Crónica del pájaro que da cuerda al Pipeline” —un Google Doc con más comentarios que líneas de texto.
Epílogo: el dato como legado
Hay pipelines que nacen para salir del paso, y otros para quedarse.
El de Dadadata se piensa, se pule, se documenta y se celebra.
Cada analista nuevo se encuentra con una bienvenida especial:
Un onboarding técnico,
Una lectura guiada de las decisiones de arquitectura,
Y, como tradición, el derecho a dibujar una pequeña mejora en la pizarra del pipeline (“Todo puede cambiar, menos el espíritu de control y respeto por el dato”, les dice Carla).
La historia sigue, porque los datos nunca dejan de moverse.
Hoy, cualquier evento de Dadadata puede viajar de una web perdida al warehouse más avanzado, y el equipo puede dormir tranquilo: el pipeline está vivo, auditable y preparado para todo lo que venga.