Implementar Analytics con Claude Code: GA4, GSC, Cloudflare y Revenue
Implementa analytics con Claude Code: GA4, GSC, Cloudflare, PV, CTA, ingresos y pruebas.
Analytics empieza antes de instalar la etiqueta
Implementar analytics significa convertir PV, clics, lectura completa, consultas, clics a productos y compras en datos útiles para decidir. PV es page view, una vista de página. Un evento es una acción registrada. Una conversión o key event en GA4 es una acción que el negocio considera resultado. UTM son etiquetas de campaña en la URL. Consentimiento es la regla que decide si el navegador puede enviar medición.
Claude Code ayuda porque puede transformar el plan de medición en código, pruebas y documentación. En la operación del sitio de Masa, el error fue mirar crecimiento de PV sin separar clics a producto, formularios completados y registros a recursos gratuitos. El tráfico parecía sano, pero el camino hacia ingresos era invisible.
La pila práctica de este artículo es: GA4 para campañas y eventos clave, Search Console para demanda por query y página, Cloudflare para señales de borde, Plausible para objetivos ligeros y PostHog para funnels. Puedes conectarlo con optimización SEO, A/B testing, performance y auditoría del funnel.
Plan de medición
Pide a Claude Code una tabla desde decisiones, no desde herramientas.
Crea un plan de implementación analytics para este sitio de contenido.
El objetivo no es solo crecer en PV, sino mejorar lectura completa, clics CTA, consultas, clics a producto y compras.
Usa columnas business_question, event_name, trigger, required_params, provider, decision.
Usa eventos recomendados de GA4 cuando encajen. Los propios van en snake_case.
| business_question | event_name | trigger | required_params | provider | decision |
|---|---|---|---|---|---|
| ¿Se lee el artículo hasta el final? | article_read_complete | Footer visible al 70% | slug, category, reading_time_sec | GA4/PostHog | Reescribir intro, headings e internos |
| ¿Se hace clic en CTA? | cta_click | CTA de producto, formación o PDF | slug, cta_id, cta_type, target_url | GA4/Plausible/PostHog | Cambiar posición y copy |
| ¿La consulta se completa? | generate_lead | Formulario enviado con éxito | form_id, lead_source, value, currency | GA4/PostHog | Mejorar formulario y oferta |
| ¿El producto genera intención? | purchase_link_click | Clic a producto o Gumroad | product_id, price, currency, slug | GA4/PostHog | Ajustar artículo y producto |
| ¿Qué queries valen la pena? | gsc_query_page | Search Console API devuelve page/query | page, query, clicks, impressions, ctr, position | GSC | Priorizar títulos y actualizaciones |
| ¿Faltan tags de navegador? | edge_page_view | Cloudflare Worker recibe request | path, country, status, duration_ms | Cloudflare | Detectar bloqueos y problemas de velocidad |
Los eventos de GA4 deben revisarse en recommended events. Usa generate_lead cuando el significado coincida; deja eventos propios para acciones específicas del artículo.
flowchart LR
Reader["Lector"]
Consent["Consentimiento"]
Browser["browser analytics.js"]
Server["GA4 Measurement Protocol"]
GSC["Search Console API"]
Edge["Cloudflare Worker"]
Dashboard["Dashboards de contenido, ingresos y calidad"]
Reader --> Consent --> Browser
Browser --> Server
GSC --> Dashboard
Edge --> Dashboard
Browser --> Dashboard
Server --> Dashboard
Contrato de eventos en JS
El contrato evita que Claude Code cree ctaClick, cta_click y button_click para la misma acción.
// event-plan.mjs
import { pathToFileURL } from "node:url";
export const eventPlan = {
article_read_complete: { required: ["slug", "category", "reading_time_sec"], providers: ["GA4", "PostHog"] },
cta_click: { required: ["slug", "cta_id", "cta_type", "target_url"], providers: ["GA4", "Plausible", "PostHog"] },
generate_lead: { required: ["form_id", "lead_source", "value", "currency"], providers: ["GA4", "PostHog"] },
purchase_link_click: { required: ["product_id", "price", "currency", "slug"], providers: ["GA4", "PostHog"] },
campaign_landing: { required: ["utm_source", "utm_medium", "utm_campaign"], providers: ["GA4"] },
};
export function validateEvent(name, params = {}) {
const contract = eventPlan[name];
if (!contract) return { ok: false, missing: ["known_event_name"] };
const missing = contract.required.filter((key) => params[key] === undefined || params[key] === "");
return { ok: missing.length === 0, missing };
}
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
console.log(validateEvent("cta_click", { slug: "claude-code-analytics-implementation", cta_id: "products_footer", cta_type: "product", target_url: "/en/products/" }));
}
Separa products de training. El primero mide interés por plantillas o guías; el segundo mide intención de adopción en equipo o consultoría.
Capa del navegador
Esta capa concentra consentimiento, UTM, limpieza de parámetros y envío a GA4, Plausible y PostHog.
// browser-analytics.js
const CONSENT_KEY = "analytics_consent";
const UTM_KEYS = ["utm_source", "utm_medium", "utm_campaign", "utm_term", "utm_content"];
function inBrowser() {
return typeof window !== "undefined" && typeof localStorage !== "undefined";
}
function hasConsent() {
return inBrowser() && localStorage.getItem(CONSENT_KEY) === "granted";
}
function cleanParams(params = {}) {
return Object.fromEntries(Object.entries(params).filter(([, value]) => value !== undefined && value !== null && value !== "").map(([key, value]) => [key, typeof value === "boolean" ? Number(value) : value]));
}
export function setAnalyticsConsent(state) {
if (!inBrowser()) return;
localStorage.setItem(CONSENT_KEY, state);
window.gtag?.("consent", "update", { analytics_storage: state, ad_storage: "denied" });
}
export function readUtmParams() {
if (!inBrowser()) return {};
const current = new URLSearchParams(window.location.search);
const saved = JSON.parse(localStorage.getItem("landing_utm") || "{}");
const next = { ...saved };
for (const key of UTM_KEYS) {
const value = current.get(key);
if (value) next[key] = value;
}
localStorage.setItem("landing_utm", JSON.stringify(next));
return next;
}
export function trackEvent(name, params = {}) {
if (!hasConsent()) return;
const payload = cleanParams({ ...readUtmParams(), ...params });
window.gtag?.("event", name, payload);
window.plausible?.(name, { props: payload });
window.posthog?.capture(name, payload);
}
Envía generate_lead después del éxito del formulario, no en el clic del botón. Para lectura completa usa un único disparo cuando el final del artículo aparece.
GA4, GSC y Cloudflare
Los resultados confirmados en servidor pueden enviarse con GA4 Measurement Protocol y validarse con validation server.
// ga4-server-event.mjs
import { pathToFileURL } from "node:url";
const { GA4_MEASUREMENT_ID, GA4_API_SECRET, GA4_DEBUG } = process.env;
if (!GA4_MEASUREMENT_ID || !GA4_API_SECRET) throw new Error("GA4_MEASUREMENT_ID and GA4_API_SECRET are required");
export async function sendGa4Event({ clientId, name, params = {} }) {
const endpoint = new URL(GA4_DEBUG === "1" ? "https://www.google-analytics.com/debug/mp/collect" : "https://www.google-analytics.com/mp/collect");
endpoint.searchParams.set("measurement_id", GA4_MEASUREMENT_ID);
endpoint.searchParams.set("api_secret", GA4_API_SECRET);
const response = await fetch(endpoint, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ client_id: clientId, events: [{ name, params }] }) });
if (!response.ok) throw new Error("GA4 request failed with status " + response.status);
if (GA4_DEBUG === "1") {
const result = await response.json();
if (result.validationMessages?.length) throw new Error(JSON.stringify(result.validationMessages, null, 2));
}
}
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
await sendGa4Event({ clientId: "555.1234567890", name: "generate_lead", params: { form_id: "training", lead_source: "article_footer", value: 1, currency: "USD" } });
console.log("sent");
}
Search Console se consulta con Search Analytics API.
// gsc-query.mjs
import { pathToFileURL } from "node:url";
const { GSC_ACCESS_TOKEN, GSC_SITE_URL = "https://example.com/" } = process.env;
if (!GSC_ACCESS_TOKEN) throw new Error("GSC_ACCESS_TOKEN is required");
export async function querySearchConsole({ startDate, endDate, pageContains }) {
const endpoint = "https://www.googleapis.com/webmasters/v3/sites/" + encodeURIComponent(GSC_SITE_URL) + "/searchAnalytics/query";
const response = await fetch(endpoint, {
method: "POST",
headers: { authorization: "Bearer " + GSC_ACCESS_TOKEN, "content-type": "application/json" },
body: JSON.stringify({ startDate, endDate, dimensions: ["page", "query"], dimensionFilterGroups: pageContains ? [{ filters: [{ dimension: "page", operator: "contains", expression: pageContains }] }] : [], rowLimit: 25 }),
});
if (!response.ok) throw new Error("Search Console request failed with status " + response.status);
return response.json();
}
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
const data = await querySearchConsole({ startDate: "2026-05-01", endDate: "2026-05-31", pageContains: "/blog/claude-code-analytics-implementation" });
console.log(JSON.stringify(data.rows ?? [], null, 2));
}
Para señales de borde, usa Workers Analytics Engine y el ejemplo de writeDataPoint.
// cloudflare-worker.js
function json(data, status = 200) {
return new Response(JSON.stringify(data), { status, headers: { "content-type": "application/json" } });
}
export default {
async fetch(request, env) {
if (request.method !== "POST") return json({ ok: false, error: "method_not_allowed" }, 405);
const event = await request.json().catch(() => null);
if (!event?.event_name || !event?.slug) return json({ ok: false, error: "event_name_and_slug_required" }, 400);
const country = request.cf?.country || request.headers.get("cf-ipcountry") || "XX";
env.ANALYTICS?.writeDataPoint({ blobs: [event.event_name, event.slug, event.cta_id || "", country], doubles: [Number(event.value || 1)], indexes: [String(event.slug).slice(0, 96)] });
return json({ ok: true });
},
};
No guardes email, nombre, texto libre ni IP cruda en Cloudflare. Guarda slug, evento, país, estado y tiempo.
Casos de uso y errores
Caso 1: SEO. Si GSC muestra muchas impresiones y poco CTR, revisa título y description. Si PV es alto pero article_read_complete bajo, cambia intro y estructura. Caso 2: productos. Con purchase_link_click puedes saber qué artículos llevan a producto y qué precio interesa. Caso 3: formación. cta_click mide interés; generate_lead mide formulario completado. Caso 4: campañas. Guarda UTM al aterrizar para no perder la fuente cuando la consulta llega después.
Errores concretos: nombres de evento inconsistentes, envío antes de consentimiento, conversiones duplicadas por cliente y servidor, tratar GSC como log completo, y cargar tantos scripts que empeoran Core Web Vitals. Divide dashboards en crecimiento editorial, funnel de ingresos y calidad técnica. Revisa GA4 DebugView, Realtime, Plausible Goals, PostHog Events y Cloudflare dentro de 24 horas.
Para ordenar una instalación real, usa products si necesitas plantillas o training si el equipo necesita revisar medición, implementación y dashboards juntos.
PDF gratis: cheatsheet de Claude Code
Introduce tu email y descarga una hoja con comandos, hábitos de revisión y flujos seguros.
Cuidamos tus datos y no enviamos spam.
Sobre el autor
Masa
Ingeniero enfocado en workflows prácticos con Claude Code.
Artículos relacionados
Escalera de permisos de Claude Code para ampliar acceso sin perder control
Pasa de read-only a ediciones limitadas, comandos de prueba y checks de deploy con menos riesgo.
Claude Code Small PR Proof Pack: cambios pequeños que sí se pueden revisar
Un paquete de prueba para PRs de Claude Code: diff, checks, URL pública, CTA y rollback.
Gate de revisión antes del commit con Claude Code
Cómo revisar con Claude Code antes del commit: diff, build, URL pública, Gumroad, consultoría, tests y archivos ajenos.