Los equipos de marketing ya no usan reportes listos de Google Analytics, sino pipelines de datos donde escriben sus propias reglas. En 2026, el modern marketing data stack se compone de tres capas: fuentes raw, modeling layer, semantic layer. Este artículo explica cómo construir estas tres capas con dbt + BigQuery, qué tipo de errores ocurren en cada etapa y cómo mantener una arquitectura sostenible en production.
Source mapping: Cargar datos a BigQuery no es suficiente
Subiste GA4, Meta Ads, eventos de sGTM a BigQuery — pero es solo el comienzo. Source mapping significa transformar tablas raw en contratos significativos. En dbt, las definiciones de source viven en archivos .yml:
sources:
- name: raw_ga4
database: roibase-prod
schema: analytics_123456789
tables:
- name: events_*
identifier: events_*
loaded_at_field: event_timestamp
freshness:
warn_after: {count: 12, period: hour}
Esta definición logra tres cosas: (1) Data lineage — qué modelo usa qué tabla raw, (2) Freshness check — si el evento más reciente es más antiguo que 12 horas, genera alerta, (3) Contract — si falta la columna event_timestamp, el build falla.
Error más común: Usar el schema raw tal cual. Escribir SQL sin aplanar el array event_params de GA4; cada consulta termina siendo 200+ líneas. La lógica de unnest debe vivir en un único lugar en source mapping:
-- models/staging/stg_ga4_events.sql
with source as (
select * from {{ source('raw_ga4', 'events_*') }}
),
flattened as (
select
event_date,
event_timestamp,
user_pseudo_id,
(select value.string_value from unnest(event_params) where key = 'session_id') as session_id,
(select value.int_value from unnest(event_params) where key = 'ga_session_number') as session_number
from source
)
select * from flattened
Ahora este modelo se llama downstream como ref('stg_ga4_events') — la sintaxis raw event_params está aislada arriba. El freshness check corre cada día; si el schema cambia, el error es automático.
Modeling layer: Define la métrica una vez, úsala cien veces
Después de staging viene el modeling layer. Aquí se separan intermediate models (business logic) y mart models (aggregation). En marketing data stack, el modelo más crítico es el join session → transaction:
-- models/marts/mrt_session_metrics.sql
with sessions as (
select * from {{ ref('int_sessions') }}
),
transactions as (
select * from {{ ref('int_transactions') }}
),
joined as (
select
s.session_id,
s.session_date,
s.traffic_source,
s.medium,
s.campaign,
t.transaction_id,
t.revenue,
t.transaction_timestamp
from sessions s
left join transactions t
on s.session_id = t.session_id
)
select
session_date,
traffic_source,
medium,
campaign,
count(distinct session_id) as sessions,
count(distinct transaction_id) as transactions,
sum(revenue) as total_revenue,
safe_divide(count(distinct transaction_id), count(distinct session_id)) as conversion_rate
from joined
group by 1, 2, 3, 4
Este modelo corre cada día a las 03:00 (scheduler dbt Cloud), Looker Studio se conecta directamente a esta tabla. Si necesitas cambios, editas el SQL en un único lugar; todos los dashboards se actualizan automáticamente.
Detalle importante: Uso de safe_divide — si sessions = 0, no genera error de división por cero, devuelve null. En un pipeline production, el exception handling ocurre en este nivel.
dbt tests: Validación automática de calidad
Al definir métricas en el modeling layer, también escribes tests:
# models/marts/schema.yml
models:
- name: mrt_session_metrics
columns:
- name: session_date
tests:
- not_null
- name: sessions
tests:
- not_null
- dbt_utils.expression_is_true:
expression: ">= 0"
- name: conversion_rate
tests:
- dbt_utils.expression_is_true:
expression: "<= 1"
El comando dbt test ejecuta estas reglas. Si conversion_rate > 1 (hay un error en el SQL), el build falla y envía alerta a Slack. En lugar de QA manual, tienes validación automática de datos — el resto del data stack descansa sobre este cimiento.
Semantic layer: Define la métrica, no la consulta
Con dbt v1.6+, el semantic layer salió de beta. Ahora defines métricas en .yml, no en SQL:
# models/semantic/metrics.yml
metrics:
- name: total_revenue
label: Total Revenue
model: ref('mrt_session_metrics')
type: sum
sql: total_revenue
timestamp: session_date
time_grains: [day, week, month]
- name: roas
label: Return on Ad Spend
type: ratio
numerator: total_revenue
denominator: total_ad_spend
Esta definición se usa en tres lugares: (1) Looker Studio, (2) Slack bot consultando dbt Cloud discovery API por métricas, (3) DAG de Airflow alimentando un pipeline ML downstream.
Ventaja: Consumes métricas sin escribir SQL. El analista de marketing ahora escribe "Show me ROAS by campaign, last 7 days" y dbt semantic layer compila la consulta automáticamente. La lógica SQL está en el modeling layer, la definición métrica en semantic layer — completamente separadas, cambios aislados.
Advertencia: El semantic layer aún es nuevo — no todos los BI tools lo integran nativamente. En el stack production de Roibase, usamos enfoque híbrido: métricas críticas en semantic layer, análisis custom vía SQL exposures.
Exposures: Documenta dependencias downstream
Los exposures muestran dónde se consume un modelo de dbt fuera de dbt:
# models/exposures.yml
exposures:
- name: looker_studio_performance_dashboard
type: dashboard
url: https://lookerstudio.google.com/...
depends_on:
- ref('mrt_session_metrics')
- ref('mrt_campaign_performance')
owner:
name: Marketing Analytics Team
email: [email protected]
Esta definición se visualiza en dbt docs — qué dashboard depende de qué modelo. Si cambias el schema, con dbt run --select +mrt_session_metrics+ ves el impacto downstream.
Caso real: GA4 cambió el key page_location a page_url en event_params. Gracias a las exposures, encontramos 3 dashboards y 1 DAG de Airflow afectados; la migración tomó 2 horas. Sin exposures, los dashboards simplemente se hubiesen roto silenciosamente.
Incremental models: No hagas full refresh de 2TB cada día
En marketing data, las particiones diarias alcanzan escala de terabytes. No puedes hacer full refresh en cada dbt run — costo y tiempo en BigQuery son inaceptables. Usas modelos incremental:
-- models/marts/mrt_user_journey.sql
{{
config(
materialized='incremental',
partition_by={'field': 'event_date', 'data_type': 'date'},
cluster_by=['user_pseudo_id', 'traffic_source'],
incremental_strategy='insert_overwrite'
)
}}
select
event_date,
user_pseudo_id,
traffic_source,
-- ...
from {{ ref('stg_ga4_events') }}
{% if is_incremental() %}
where event_date >= date_sub(current_date(), interval 3 day)
{% endif %}
Esta configuración logra tres cosas: (1) Crea particiones en BigQuery — agrega el día nuevo sin tocar datos antiguos, (2) cluster_by mejora performance de consultas, (3) insert_overwrite strategy — reescribe los últimos 3 días (para capturar datos que llegan tarde).
Diferencia de costo: 365 días, full refresh = 2.5 TB scanned ($12.50), incremental = 3 GB scanned ($0.015). Para un pipeline diario, la diferencia anual es ~$4500 vs ~$5. Por eso los modelos incremental son fundamentales en production.
Conectar el data stack al mecanismo de decisión
Construiste la infraestructura dbt + BigQuery, pero el valor real está en decisiones de marketing. El flujo típico es semantic layer → bot de Slack → métrica on-demand:
- Marketing manager escribe en Slack:
/metric roas last_30_days campaign=brand - Slack app llama dbt Cloud semantic layer API
- La API consulta tabla
mrt_session_metrics, calcula ROAS - Resultado vuelve a Slack: "Campaña brand ROAS: 4.2x"
Para este flujo necesitas semantic layer + middleware Python custom. En production de Roibase, un DAG de Airflow toma daily snapshots del semantic layer; Looker Studio y apps internas consumen estos snapshots — sin problemas de rate limit en API.
Enfoque alternativo: Stack híbrido con Estrategia de Datos First-Party & Medición — dbt semantic layer + Cube.js. Cube.js agrega caching, mejora performance en BI. La elección depende de volumen de datos y patrones de consulta.
Checklist production: Antes de deployar el stack dbt
dbt funciona en local — antes de enviar a production, valida esto:
- CI/CD: dbt Cloud o GitHub Actions ejecuta
dbt build --select state:modified+en cada commit - Freshness monitoring: Define
warn_afteryerror_afterpara fuentes críticas - Alerting: Webhooks dbt Cloud → Slack; el equipo se enterea en 5 minutos si un build falla
- Documentation:
dbt docs generatecorre automático, artifacts van a S3/GCS - Cost monitoring: BigQuery slot reservation o alerta on-demand — threshold $500/day para spikes inesperados
- Backup strategy: Snapshot tables en production warehouse — si un modelo se actualiza mal, puedes rollback
Regla crítica: Cero dbt run manual en production. Todo vía scheduler (dbt Cloud, Airflow, Prefect). El run manual rompe data lineage; en error, no hay root cause analysis.
dbt + BigQuery es la columna vertebral del modern marketing data stack — source mapping conectó datos raw a contratos, modeling layer definió métricas en un punto, semantic layer permitió consumirlas sin SQL. En production, incremental models y test coverage hacen el pipeline sostenible. El siguiente nivel: conectar este data a activación real-time — CDP, audience sync, incremental measurement. Pero eso es otra conversación de data stack.