Volver al blog

Técnico atribucion utm analytics

Como medir marketing cuando las redes no te dan datos? La atribución va en el enlace!

Por Armando Peña 8 min de lectura 11 de septiembre de 2026

man in black long sleeve shirt using computer

Foto de  Mohammad Rahmani  en  Unsplash

El problema: publiqué en cinco lugares y no sé cuál funciona

Publico la misma oferta —"camisas de lino, 2300 CUP"— en un grupo de Facebook, en el estado de WhatsApp, en un canal de Telegram y en Revolico. Al final de la semana tengo mensajes, pero no puedo responder la única pregunta que importa: ¿qué publicación me los trajo?

El grupo no me dice cuántos clics salieron de mi publicación. El estado de WhatsApp no tiene métricas de origen. Revolico muestra visitas, no procedencia. Ninguna plataforma ofrece un botón que diga "de aquí salió quien te escribió".

Y aquí conviene separar cuatro preguntas que no son lo mismo:

  • qué enlace etiquetado generó la visita;
  • de qué plataforma procedió realmente el usuario;
  • qué visita terminó en una consulta;
  • qué consulta terminó en una venta.

Este artículo es sobre atribución de marketing, no sobre seguridad ni criptografía. Y la tesis es simple:

No necesitas que la plataforma te entregue datos de atribución si tú controlas el enlace que publicas. La información viaja en el enlace; tú la pones antes de publicar.

Voy por niveles, del más simple al más sofisticado. Al final vas a entender por qué casi nadie necesita el último.

Primero mide algo

Antes de pensar en etiquetas de campaña hace falta un lugar donde caigan los datos. Un módulo de analytics diminuto: registra el timestamp, el path, qué evento ocurrió (una vista, un clic) y, cuando existan, los parámetros de fuente.

Esto no es Google Analytics casero. No necesito fingerprinting, ni cookies obligatorias, ni identificar visitas individuales, ni un SDK que pese como un libro de texto. Son unas columnas en una tabla:

marketing_events (
  created_at,
  path,
  event,
  source,
  medium,
  campaign,
  content
)

(o una versión agregada por día si lo que quieres es ahorrar almacenamiento).

Las primeras preguntas que responde son casi tontas, y son exactamente las que faltaban:

  • ¿Cuántas visitas recibí?
  • ¿Cuántas recibió determinada página?
  • ¿De dónde parecen proceder?

Dónde vive esa tabla depende de dónde esté tu sistema. No hay respuesta universal:

  • SQLite tiene todo el sentido en un servidor pequeño y persistente: es un archivo local, no pide permiso a nadie.
  • PostgreSQL/Supabase tiene sentido cuando ya usas esa infraestructura: no añades un servicio, añades una tabla.
  • En funciones serverless (Vercel, Lambda) el filesystem local no es persistente: cada invocación puede empezar de cero, y escribir en un archivo SQLite es perder datos silenciosamente. Ahí la base de datos vive fuera de la función.

Lo importante: para responder "cuántas visitas recibió cada campaña" no necesitas un data warehouse ni mil líneas de tag. Una tabla chica da para todo este artículo.

El truco no está en la plataforma: está en el enlace

Aquí está la pieza central. Facebook no tiene que darme un informe de atribución si yo ya puse la información en el enlace antes de publicarlo.

Si publico:

https://mitienda.com/oferta?utm_source=facebook

ese parámetro viaja hasta mi servidor cuando alguien hace clic. Yo controlo lo que publico, así que yo pongo la etiqueta. La plataforma es el cartero: entrega el enlace entero, parámetros incluidos.

UTM: la solución sencilla

UTM es el estándar para hacer exactamente eso. De los cinco parámetros que define, yo uso cuatro:

  • utm_source — el origen que quiero distinguir (facebook, whatsapp, telegram, revolico).
  • utm_medium — el tipo de canal (social, messaging, classified…).
  • utm_campaign — la campaña (camisas-2026-09).
  • utm_content — la variante concreta de la pieza (grupo-venta, estado, anuncio-1).

La misma campaña, tres lugares:

?utm_source=facebook&utm_medium=social&utm_campaign=camisas-2026-09&utm_content=grupo-venta
?utm_source=whatsapp&utm_medium=messaging&utm_campaign=camisas-2026-09&utm_content=estado
?utm_source=revolico&utm_medium=classified&utm_campaign=camisas-2026-09&utm_content=anuncio-1

No hace falta usar los cuatro desde el día uno. Empezar solo con utm_source ya responde la pregunta más importante, y el resto se añade cuando la necesites.

Pero UTM mide el enlace, no prueba el origen

Precisión importante. UTM no te dice "este usuario vino de Facebook". Te dice algo más modesto y suficiente:

Este visitante usó el enlace etiquetado como Facebook.

Esto pasa todo el tiempo: publico el enlace "de Facebook", alguien lo copia, lo reenvía por WhatsApp, la próxima persona hace clic y mi servidor sigue viendo utm_source=facebook. Medir por enlaces etiquetados tiene esa limitación, y no es un defecto: es la naturaleza del método.

El Referer puede servir como señal secundaria —cuando llega un referer de facebook.com, apoya la intuición—, pero no es una fuente fiable de atribución primaria: puede faltar, venir reducido a solo el origen (los navegadores modernos envían strict-origin-when-cross-origin por defecto) o no existir si el clic vino de otra app. Con el utm_source bien puesto no tengo que pelear con eso.

Cuando tienes diez campañas, necesitas orden

UTM resuelve la medición. El siguiente problema es de higiene: cuando los enlaces se acumulan, aparecen cosas como:

?utm_source=facebook-grupo-1-camisas-septiembre-post-azul

eso no es un valor, es un archivo metido en un parámetro. Por eso se separan dimensiones: source, medium, campaign, content son columnas distintas, y cada una responde una pregunta distinta:

  • source → ¿qué plataforma funciona?
  • medium → ¿qué tipo de canal funciona?
  • campaign → ¿qué campaña funciona?
  • content → ¿qué publicación concreta funciona?

Y lo más útil: la misma campaña vive en varias fuentes.

campaign = camisas-2026-09
  source = facebook
  source = whatsapp
  source = telegram

Así comparo canales sin que la campaña se pierda de vista. GROUP BY source me dice dónde mirar; GROUP BY campaign me dice qué campaña entera funcionó.

Una oferta, muchos adaptadores

Con el tiempo, mantener cinco copias de la misma oferta se vuelve insostenible: cambio un precio y edito cinco lugares, y siempre queda una vieja. La oferta existe una sola vez:

id: oferta-2026-09-camisas
precio_cup: 2300

Y cada canal tiene su configuración y su copia:

canales:
  - id: grupo-venta
    source: facebook
    medium: social
    content: grupo-venta

  - id: estado-wa
    source: whatsapp
    medium: messaging
    content: estado

  - id: revolico
    source: revolico
    medium: classified
    content: anuncio-1

La URL de cada canal se genera desde ahí, y publicar una oferta nueva es escribir un bloque YAML.

Pero publicar no es copiar el mismo texto a cinco sitios: cada canal tiene reglas distintas de texto, imagen, tono y CTA. Por eso cada canal es un adaptador:

def render(oferta, canal):
    adapter = ADAPTERS[canal.id]
    return adapter.render(
        oferta=oferta,
        tracking=tracking_params(canal),
    )

El tracking sale del mismo lugar que la pieza: así no existe la posibilidad de publicar y olvidar etiquetar.

En paralelo llevo cuenta de qué publiqué. Eso es un problema distinto del de analytics:

publicaciones (
  oferta,
  canal,
  pieza_hash,
  publicado_at
)

El analytics responde "qué funciona"; esta tabla responde "qué publiqué". El pieza_hash sirve para saber si la pieza cambió y no republicar lo mismo dos veces. Es deduplicación e idempotencia: una herramienta de higiene, no de seguridad.

Medir clics no significa medir ventas

Aquí el sistema se pone honesto. Un ejemplo realista:

Canal Visitas Consultas Ventas
Facebook 100 10 1
WhatsApp 40 20 8
Revolico 200 5 0

Revolico ganó el tráfico. WhatsApp ganó el negocio. "Más clics" no significa "mejor marketing", y eso solo se ve cuando unes visitas con conversiones.

El salto de publicación → visita se mide solo. El salto visita → consulta → venta no: al principio se registra a mano, con una columna más en la tabla de eventos o una hoja aparte. Veinte apuntes al mes tiran más decisiones que cien dashboards. Y no prometo automatizar eso: si algún día el volumen lo pide, se automatiza; antes no.

¿Cuándo necesito IDs opacos?

Hasta aquí todo es legible: la URL dice utm_source=facebook&utm_campaign=camisas-2026-09 y cualquiera lo lee. Eso es una ventaja —se depura mirando la URL— salvo el día en que quieras lo contrario.

Si aparece una necesidad real de no exponer la estructura interna, los parámetros se sustituyen por un código opaco:

?utm_source=facebook&utm_campaign=camisas-2026-09

se convierte en:

?c=7f3a

donde 7f3a responde a una fila interna que guarda campaña, canal y contenido. Un ID opaco sirve para URLs más cortas, para ocultar el esquema, para controlar el tracking sin romper enlaces viejos y para que el visitante no manipule los nombres de campaña.

Pero que quede claro: un ID opaco no hace la atribución más verdadera. Solo cambia cómo se ve en la URL.

¿Cuándo tendría sentido firmarlos?

La última capa, y la que necesita más cuidado. HMAC (una firma con secreto compartido) hace exactamente una cosa:

Te permite comprobar que un valor fue generado por alguien que conoce el secreto.

En un sistema de atribución puro, ¿para qué la querría? Casi nunca. Pero si el parámetro tiene consecuencias —un cupón, un descuento, un enlace de afiliado, atribución con impacto económico—, entonces sí me interesa:

  • que no se fabrique un cupón válido a mano;
  • que ?campaign=facebook&descuento=50 no se lo crea nadie.

Firmado se vería así:

?campaign=facebook&sig=abc123

y el receptor verifica la firma con el secreto antes de confiar en el parámetro.

Y lo que una firma NO hace, porque se vende mal:

  • No demuestra que la visita "realmente vino de Facebook". Eso lo sigue haciendo, con sus límites, el utm_source.
  • No evita que alguien abra 300 veces un enlace legítimo. Reusar un enlace válido es reusar un enlace válido; la firma no distingue abuso de uso.
  • No vuelve el tracking "confiable" en general: protege la autenticidad del parámetro que firma, y nada más.

Traducción: para el problema de "qué publicación funciona", HMAC casi seguro no hace falta. Para cupones y afiliados, sí. Distinguir esos dos casos vale más que cualquier librería de firma.

La regla: no construyas el nivel 7 si el nivel 2 resuelve el problema

El camino completo, condensado:

1. Analytics mínimo     →  "¿cuántas visitas tengo?"
2. UTM                  →  "¿qué enlace etiquetado genera visitas?"
3. Dimensiones s/m/c/c  →  "¿qué canal, campaña o pieza funciona?"
4. Fuente única + adap. →  "¿cómo evito inconsistencias al publicar?"
5. Conversiones         →  "¿qué canal genera negocio?"
6. ID opaco             →  "¿necesito ocultar o separar la identidad interna?"
7. HMAC                 →  "¿el parámetro tiene consecuencias que exigen integridad?"

La tesis:

Empieza arriba y baja solo cuando tengas un problema real que justifique la complejidad.

No al revés. Para publicar ofertas en grupos, estados y clasificados, la cadena que mueve los números es la de arriba hasta el nivel 5. El ranking de canales sale con un GROUP BY, los datos te pertenecen y nadie pagó por un SDK.

¿Te ayudó este artículo?

Si necesitas ayuda con tu presencia digital, cuéntanos tu idea.

Consultar por WhatsApp

Posts similares

graphs of performance analytics on a laptop screen
  • optimización
  • caché
  • offline-first

¿Cómo diseñas software cuando Internet puede desaparecer en cualquier momento?

Cómo diseñar aplicaciones capaces de seguir siendo útiles bajo una conexión lenta, inestable o inexistente. El artículo explora optimización de red, imágenes, código, caché, LRU, funcionamiento sin conexión, sincronización, idempotencia y resolución de conflictos, utilizando las restricciones reales de conectividad en Cuba como un caso extremo de un problema arquitectónico mucho más amplio.

Técnico1512 palabras · 8 min de lectura

Armando Peña 4 0
person writing on white board
  • low-net
  • low-bandwidth
  • offline-first

Cómo transmitir una pizarra con 120 Kbps y una conexión inestable?

Quería que un dibujo cruzara la conexión de mi clase de inglés, alrededor de 120 kbps y con caídas constantes. Empecé comprimiendo una pizarra y descubrí que comprimir era la parte fácil: el problema era decidir qué información enviar y qué hacer cuando la conexión simplemente no está. Esta es la cadena de descubrimientos que llevó a LOW-NET, el laboratorio donde estoy midiendo esa pregunta.

Caso de estudio1940 palabras · 10 min de lectura

Armando Peña 1 0