lección 2

Día 1: Tres fuentes, tres verdades

Explora los datos de Google Ads, Meta Ads y Email Marketing. Descubre las discrepancias de granularidad, naming y definición de conversión.

45 min

### Martes 9:15 AM -- El standup

El standup de AdPulse es rápido: 10 minutos de pie junto a la maquina de cafe. Carmen va directo al grano con cada persona: "Que hiciste ayer, que haces hoy, que te bloquea." Cuando llega tu turno, Carmen dice: "El nuevo. Hoy exploras las fuentes de PharmaLife. Ivan te da acceso a los datos crudos. Para mañana por la mañana quiero un doc de 1 pagina con las discrepancias que encuentres entre las 3 fuentes." Se gira hacía Ivan: "Asegurate de que tiene acceso a todo, incluyendo el CRM."

Ivan te lleva a su escritorio después del standup. "Vamos a lo básico primero. Necesitas entender como llegan los datos de cada plataforma. Cada una tiene su formato, su granularidad temporal y su propia definición de lo que es una conversión. Te voy a dar acceso a los 3 datasets de enero para que los explores. No empieces a construir nada todavía -- solo mira, perfila y documenta."

### Fuente 1: Google Ads (CSV diario)

Google Ads exporta un CSV diario via un script que Raul configuro hace meses con la API de Google Ads. El archivo llega a las 6 AM a un bucket de S3 y tiene granularidad diaria por campaña. Los nombres de campaña siguen la convencion interna de PharmaLife tal como los creo el equipo de paid media: prefijo PL_ seguido de tipo de campaña, producto y segmento.

1-- Estructura: google_ads_weekly.csv
2-- Granularidad: 1 fila por día por campaña
3-- Periodo: enero 2024 (31 días)
4-- Campanas activas: 14
5
6SELECT * FROM google_ads LIMIT 8;
7
8-- date | campaign_name | impressions | clicks | conversions | cost_eur
9-- 2024-01-01 | PL_Search_Vitamins_Brand | 12450 | 890 | 78 | 1245.60
10-- 2024-01-01 | PL_Search_Omega3_Generic | 8920 | 445 | 32 | 892.10
11-- 2024-01-01 | PL_Display_Protein_Retargeting | 45600 | 234 | 12 | 456.00
12-- 2024-01-01 | PL_Shopping_Supplements_All | 23100 | 1560 | 145 | 2310.50
13-- 2024-01-01 | PL_Search_Vitamins_Competitor | 6780 | 312 | 18 | 1017.00
14-- 2024-01-01 | PL_Video_Brand_Awareness | 89000 | 120 | 2 | 890.00
15-- 2024-01-01 | PL_Search_Collagen_Generic | 4500 | 234 | 15 | 567.80
16-- 2024-01-01 | PL_Shopping_Omega3_Premium | 15200 | 890 | 67 | 1520.00
17
18-- Total filas: 434 (31 días x 14 campañas)
19-- Total conversiones enero: 2,340
20-- Total gasto enero: 1,100,000 EUR
21-- Ventana de atribución Google: click 30 días

Google Ads: CSV diario, nombres con prefijo PL_, ventana de atribución de 30 días

Ivan señala algo importante: "Fijate en la ventana de atribución. Google cuenta como conversión cualquier compra que ocurra dentro de los 30 días posteriores a un click. Eso significa que si alguien hace click el 1 de enero y compra el 28 de enero, Google cuenta esa conversión en el día del CLICK (1 de enero), no en el día de la compra. Eso distorsiona los datos diarios."

### Fuente 2: Meta Ads (JSON de la API)

Meta (Facebook + Instagram) expone una Marketing API que Ivan configuro para descargar datos cada noche a las 2 AM. El formato es JSON con estructura anidada -- las conversiones vienen dentro de un array "actions" que mezcla diferentes tipos de evento (purchases, add_to_cart, view_content). Los nombres de campaña siguen una convencion DIFERENTE a Google porque los creo otro equipo dentro de PharmaLife.

Ivan te advierte sobre un detalle crítico del JSON de Meta: "Todos los valores dentro de actions son STRINGS, no números. Meta devuelve todo como texto por un tema de compatibilidad con versiones antiguas de su API. Tienes que castear a int/float explicitamente. Si sumas strings en vez de números, vas a tener bugs silenciosos: 45 + 38 te daria 4538 en vez de 83."

1{
2 "data": [
3 {
4 "campaign_id": "120208345",
5 "campaign_name": "pharmalife_vitamins_broad_fb",
6 "date_start": "2024-01-01",
7 "date_stop": "2024-01-01",
8 "impressions": "34500",
9 "reach": "28900",
10 "clicks": "1230",
11 "actions": [
12 {"action_type": "purchase", "value": "45"},
13 {"action_type": "add_to_cart", "value": "312"},
14 {"action_type": "view_content", "value": "890"},
15 {"action_type": "initiate_checkout", "value": "178"}
16 ],
17 "spend": "678.90",
18 "cost_per_action_type": [
19 {"action_type": "purchase", "value": "15.09"}
20 ]
21 },
22 {
23 "campaign_id": "120208346",
24 "campaign_name": "pharmalife_omega3_retarget_ig",
25 "date_start": "2024-01-01",
26 "date_stop": "2024-01-01",
27 "impressions": "28900",
28 "reach": "22100",
29 "clicks": "890",
30 "actions": [
31 {"action_type": "purchase", "value": "38"},
32 {"action_type": "add_to_cart", "value": "245"},
33 {"action_type": "view_content", "value": "567"}
34 ],
35 "spend": "534.20"
36 }
37 ]
38}

Meta Ads: JSON anidado, conversiones en array "actions", ventana click 7d + view 1d

Notas clave sobre Meta: (1) Los valores dentro de "actions" vienen como strings, no como números. (2) Solo te interesa el action_type "purchase" -- el resto son métricas intermedias del funnel. (3) La ventana de atribución de Meta es MUCHO más corta: click 7 días + view 1 día. Eso significa que Meta es más conservador que Google en lo que cuenta como conversión... pero aun así infla.

Raul te da contexto adicional sobre Meta: "La API de Meta es una pesadilla. Cambian cosas cada trimestre sin avisar. El campo cost_per_action_type desaparecio en la v16, luego volvio en la v17 con otro nombre. Ivan ya ha construido 3 versiones del script de ingesta de Meta en 2 años. Por eso insiste en que el código de parseo sea modular -- cuando Meta cambie algo (que lo hará), solo tienes que tocar una función, no reescribir todo."

En 2021, Meta redujo su ventana de atribución de 28 días a 7 días (por privacidad de iOS 14). Esto hizo que de la noche a la mañana "perdiera" un 30% de conversiones reportadas. Los anunciantes entraron en panico pensando que Meta ya no funcionaba. En realidad las ventas seguian igual -- solo que Meta ya no se las podía atribuir. Este episodio demostro lo fragil que es confiar en los números de una sola plataforma.

### Fuente 3: Email Marketing (CSV semanal)

El proveedor de email marketing de PharmaLife (similar a Klaviyo) exporta un CSV SEMANAL cada viernes con las métricas acumuladas de la semana. Este es un problema de granularidad: Google y Meta reportan diario, pero email solo semanal. Ademas, la atribución del email es distinta: cuenta como conversión si el usuario abre un email y compra dentro de los 7 días siguientes a la apertura.

Hay un detalle operativo que Ivan señala: "El CSV de email lo manda el proveedor por email (ironia) cada viernes a las 18:00. Raul lo descarga manualmente y lo sube a S3 el lunes por la mañana. Eso significa que los datos de email siempre llegan 2-3 días más tarde que Google y Meta. Para el pipeline semanal no importa (se ejecuta los lunes y ya tiene todo), pero para análisis ad-hoc del fin de semana es un problema. En la v2 vamos a conectar la API del proveedor directamente."

1-- Estructura: email_campaigns.csv
2-- Granularidad: 1 fila por semana por campaña
3-- Periodo: enero 2024 (4-5 semanas)
4-- Campanas activas: 6-8 por semana
5
6SELECT * FROM email_campaigns LIMIT 6;
7
8-- week_start | week_end | campaign_name | sent | opened | clicked | converted | revenue_eur
9-- 2024-01-01 | 2024-01-07 | Weekly_Vitamins_Promo | 45000 | 12600 | 3400 | 89 | 8900.00
10-- 2024-01-01 | 2024-01-07 | Abandoned_Cart_Recovery | 12000 | 7200 | 2100 | 156 | 15600.00
11-- 2024-01-01 | 2024-01-07 | VIP_Collagen_Exclusive | 8000 | 5600 | 1800 | 42 | 5040.00
12-- 2024-01-08 | 2024-01-14 | Weekly_Omega3_Launch | 45000 | 11200 | 2890 | 67 | 6700.00
13-- 2024-01-08 | 2024-01-14 | Abandoned_Cart_Recovery | 11500 | 6900 | 1950 | 148 | 14800.00
14-- 2024-01-08 | 2024-01-14 | New_Year_Supplements_Blast | 65000 | 18200 | 4500 | 34 | 3400.00
15
16-- Total conversiones enero: 480
17-- Total revenue reportado: ~48,000 EUR
18-- Ventana de atribución: apertura 7 días
19-- Nota: email reporta REVENUE directamente (a diferencia de Google que solo da conversiones)

Email Marketing: CSV semanal, granularidad diferente, incluye revenue directo

### La tabla comparativa de las 3 fuentes

Despues de 2 horas explorando los 3 datasets, te sientas con Ivan para documentar las diferencias. El te ayuda a estructurar el doc que Carmen pidió:

1DOCUMENTO: Discrepancias entre fuentes - PharmaLife (Enero 2024)
2================================================================
3
4| Dimension | Google Ads | Meta Ads | Email Marketing |
5|------------------|----------------------|----------------------|----------------------|
6| Formato | CSV plano | JSON anidado (API) | CSV plano |
7| Granularidad | Diaria por campaña | Diaria por campaña | SEMANAL por campaña |
8| Naming | PL_Type_Product_Seg | pharmalife_x_y_plat | Texto libre humano |
9| Ventana atrib. | Click 30 días | Click 7d + View 1d | Open 7 días |
10| Tipo conversión | Purchase (implicito) | Purchase (en array) | Converted (directo) |
11| Revenue | NO reporta | NO reporta | SI reporta |
12| Moneda | EUR | EUR | EUR |
13| ID comun | NO | NO | NO |
14| Entrega | S3 6AM automático | API pull 2AM auto | Email viernes manual |
15| Fiabilidad | Alta (automático) | Alta (automático) | Media (manual) |
16
17PROBLEMAS IDENTIFICADOS:
181. No hay ID compartido entre fuentes -> necesitamos mapping manual
192. Granularidad temporal incompatible -> email semanal vs resto diario
203. Ventanas de atribución diferentes -> no miden lo mismo
214. Nombres de campaña inconsistentes -> imposible JOIN directo
225. Email no llega hasta lunes (se envia viernes) -> 2-3 días de retraso

Tu primer entregable: el doc de discrepancias para Carmen

### La mapping table de Raul

A media mañana, Raul se acerca a tu mesa con su portatil. "Carmen me dice que necesitas mi mapping. Te lo pasó por Slack." Raul tiene una Google Sheet donde mapea manualmente los nombres de campaña entre las 3 plataformas. Es su tesoro: lo actualiza cada lunes revisando que campañas nuevas se crearon en cada plataforma durante la semana.

Raul te explica el proceso: "Cada lunes abro las 3 plataformas y miro las campañas activas. Las que son nuevas, las anado al sheet conectando los nombres. El problema es que los equipos de paid de PharmaLife no siguen una convencion uniforme. El que gestiona Google usa PL_Type_Product_Segment, el de Meta usa pharmalife_product_tipo_plataforma en minusculas, y los de email usan nombres legibles tipo Titulo_Con_Mayusculas. Nadie se puso de acuerdo y ahora es un lio."

Tu preguntas: "Y no se puede forzar una convencion única?" Raul suspira: "Llevo 2 años pidiendolo. El problema es que cada plataforma tiene sus restricciones: Google no permite ciertos caracteres, Meta trunca a 60 chars, y el equipo de email usa un editor visual que no respeta convenciones. Carmen ha escalado el tema pero PharmaLife no lo prioriza. Asi que lo resolvemos nosotros con mapping manual y paciencia."

1#canal-pharmalife [11:23]
2
3Raul Merino: Hey! Bienvenido al equipo. Carmen me dice que
4estas con lo de la unificacion. Te pasó mi mapping table:
5
6[campaign_mapping.csv]
7
8Columnas: campaign_group, google_name, meta_name, email_name, product_category
928 filas (28 grupos de campaña mapeados)
10
11Ejemplo:
12 campaign_group: "Vitaminas Brand"
13 google_name: "PL_Search_Vitamins_Brand"
14 meta_name: "pharmalife_vitamins_broad_fb"
15 email_name: "Weekly_Vitamins_Promo"
16 product_category: "vitamins"
17
18Ojo que no todas las campañas están en las 3 plataformas.
19Protein Retargeting solo existe en Google y Meta (no tiene email).
20Cart Recovery solo existe en Email. Etc.
21
22Si encuentras alguna campaña que no está mapeada, dime y la anado.
23Suele haber 3-4 nuevas cada semana que aun no he mapeado.

Raul comparte su Google Sheet -- la pieza clave para la unificacion

1-- campaign_mapping.csv (28 filas) - La "biblia" de Raul
2-- NULL significa que esa campaña no existe en esa plataforma
3
4SELECT * FROM campaign_mapping LIMIT 8;
5
6-- campaign_group | google_name | meta_name | email_name | product_category
7-- Vitaminas Brand | PL_Search_Vitamins_Brand | pharmalife_vitamins_broad_fb | Weekly_Vitamins_Promo | vitamins
8-- Omega3 Generic | PL_Search_Omega3_Generic | pharmalife_omega3_retarget_ig | Weekly_Omega3_Launch | omega3
9-- Protein Retargeting | PL_Display_Protein_Retargeting | pharmalife_protein_retarget_fb | NULL | protein
10-- Supplements Shopping | PL_Shopping_Supplements_All | NULL | NULL | supplements
11-- Cart Recovery | NULL | NULL | Abandoned_Cart_Recovery | cross-sell
12-- Collagen Generic | PL_Search_Collagen_Generic | pharmalife_collagen_awareness_ig | VIP_Collagen_Exclusive | collagen
13-- Brand Awareness Video | PL_Video_Brand_Awareness | pharmalife_brand_video_fb | NULL | brand
14-- Vitaminas Competitor | PL_Search_Vitamins_Competitor | pharmalife_vitamins_competitor_fb | NULL | vitamins

La mapping table: NULL donde no aplica, product_category para agrupar

La mapping table de Raul tiene 28 filas pero en las fuentes hay campañas que NO están en la tabla: campañas nuevas que se crearon está semana, campañas de test que alguien lanzo por error, o campañas de otros clientes que se filtraron por un bug. Parte de tu trabajó será detectar estas "huerfanas" y alertar a Raul.

### Las 4 discrepancias clave

Al final del día, Ivan te pide que le resumas los problemas en una frase cada uno. Es su forma de verificar que has entendido:

  1. 01.NAMING: La misma campaña tiene nombres diferentes en cada plataforma. No hay ID compartido. La única forma de conectarlas es la mapping table manual de Raul.
  2. 02.GRANULARIDAD: Google y Meta son diarios. Email es semanal. Para comparar necesitas o agregar a semanal (perdiendo detalle) o distribuir email a diario (inventando distribución).
  3. 03.DEFINICION: Google cuenta clicks de 30 días, Meta clicks de 7 días + views de 1 día, Email aperturas de 7 días. No miden lo mismo. Una "conversión" en Google no es comparable a una "conversión" en Meta.
  4. 04.OVERLAP: Un usuario puede aparecer en las 3 plataformas antes de comprar. Las 3 se atribuyen la venta completa. Es el origen de la inflacion del 49.5%.

### El impacto de las ventanas de atribución

Ivan te dibuja un diagrama temporal en la pizarra para que entiendas como las ventanas de atribución distorsionan los números:

1TIMELINE DE UN CLIENTE (ejemplo real):
2
3Día 1: Ve anuncio en Instagram (Meta) -> NO hace click
4Día 3: Busca "omega 3 precio" en Google -> CLICK en anuncio PL
5Día 3: Visita web PharmaLife, mira productos, se va sin comprar
6Día 5: Recibe email "Te dejaste algo en el carrito" -> ABRE email
7Día 7: Vuelve a buscar en Google "pharmalife omega 3" -> CLICK
8Día 7: COMPRA (90.48 EUR)
9
10QUIEN SE LA ATRIBUYE?
11
12Google: "Fue mia" (click en día 3 Y día 7, ambos dentro de 30 días)
13Meta: "Fue mia" (view en día 1, dentro de 1 día de view... NO!
14 View fue día 1, compra día 7 = 6 días. Fuera de ventana 1d.)
15 Meta NO se la atribuye en este caso.
16Email: "Fue mia" (open día 5, compra día 7 = 2 días. Dentro de 7d.)
17
18Resultado: Google + Email = 2 atribuciones para 1 venta.
19Inflacion en este caso: 2x (100% inflado).
20
21Si Meta tuviera ventana de 7 días (como antes de 2021):
22Google + Meta + Email = 3 atribuciones. Inflacion 3x.

Las ventanas de atribución crean la inflacion -- entender esto es clave

Ivan concluye: "Esto no es un bug -- es by design. Cada plataforma elige una ventana que la favorece. Google usa 30 días porque Search captura intent muy temprano. Meta reducio a 7 días por presion regulatoria (iOS 14), no por eleccion. Email usa 7 días de apertura porque el email es un canal de cierre rápido. Entender estas ventanas te ayuda a interpretar los números."

Ivan te dice: "Regla número 1 de data engineering en AdTech: NUNCA confies en los números de conversión de una sola plataforma. Siempre son optimistas porque la plataforma tiene incentivo de mostrar buenos resultados para que el anunciante siga gastando. La única fuente de verdad son las ventas reales del CRM del cliente. Todo lo demas es una aproximacion."

## ejercicios

[01]

Perfilar la fuente de Google Ads

Carga el CSV de Google Ads en una tabla y genera un perfil completo: filas totales, rango de fechas, campañas unicas, totales de gasto, conversiones e impresiones, y el top 5 de campañas por conversiones.

Cargando editor...
[02]

Aplanar el JSON de Meta Ads con Python

El JSON de Meta tiene estructura anidada con el array actions[]. Escribe un script que lo aplane a un DataFrame limpio extrayendo solo las purchases.

Cargando editor...
[03]

Detectar campañas huerfanas que faltan en el mapping

Escribe una query que identifique campañas en las fuentes que NO aparecen en la mapping table de Raul. Prioriza por gasto para saber cuales son urgentes de mapear.

Cargando editor...
[04]

Comparar granularidad temporal entre fuentes

Escribe queries que agreguen Google Ads a nivel semanal y lo comparen con Email (ya semanal). Identifica semanas con datos en ambas fuentes y calcula discrepancias.

Cargando editor...

Regístrate para guardar tu progreso.

## comentarios

Reporta erratas, ayuda a otros o comparte tu opinión. Sé constructivo.

Inicia sesión para comentar y responder.

cargando comentarios...