Analytics

packages/analytics/README.md

@repo/analytics

Paquete de mediciones, analytics y tracking de conversiones para el monorepo Nidus. Centraliza la integración de Google Analytics 4 (GA4), Google Tag Manager (GTM) y Meta (Facebook) Pixel en un único paquete reutilizable.

Cada app configura sus propios IDs a través de variables de entorno y monta los componentes una vez en su layout raíz.


Índice

  1. Qué resuelve
  2. Arquitectura del paquete
  3. Instalación
  4. Variables de entorno por app
  5. Uso en el layout raíz
  6. Tracking de eventos
  7. API completa
  8. Tests
  9. Agregar variables a App Hosting
  10. Extensión: agregar una nueva plataforma

1. Qué resuelve

ProblemaSolución
Código de tracking duplicado en cada appUn único paquete compartido
Scripts lentos que bloquean el FCPnext/script con strategy="afterInteractive"
Tipos incorrectos para window.fbq / window.dataLayerTipos declarados en globals.d.ts
Re-envío de PageView en navegación SPAAnalyticsPageViewTracker escucha cambios de ruta
IDs de analytics hardcodeadosVariables de entorno por app, pasadas como props
XSS al inyectar IDs en scripts inlineSanitización y validación de formato antes de la inyección

2. Arquitectura del paquete

text
packages/analytics/
├── src/
│   ├── index.ts                          # Tipos + funciones de eventos (sin componentes)
│   ├── types.ts                          # Todas las interfaces y tipos
│   ├── globals.d.ts                      # Augmentación de Window (fbq, dataLayer)
│   ├── gtm/
│   │   ├── GTMScript.tsx                 # RSC — inyecta <script> GTM en <head>
│   │   ├── GTMNoScript.tsx               # RSC — <noscript> fallback en <body>
│   │   ├── events.ts                     # pushGTMEvent, trackGTMPageView
│   │   └── index.ts                      # Re-exports de @repo/analytics/gtm
│   ├── facebook/
│   │   ├── FacebookPixelScript.tsx       # RSC — inyecta pixel init en <body>
│   │   ├── events.ts                     # trackFbEvent, trackFbCustomEvent, trackFbPageView
│   │   └── index.ts                      # Re-exports de @repo/analytics/facebook
│   ├── ga4/
│   │   ├── events.ts                     # trackGA4Event, trackGA4PageView
│   │   └── index.ts                      # Re-exports de @repo/analytics/ga4
│   ├── hooks/
│   │   └── useAnalyticsTrackEvents.ts    # Hook: scroll depth, clicks, section views (GA4)
│   └── tracker/
│       ├── AnalyticsPageViewTracker.tsx  # Client Component — re-tracking en cambio de ruta
│       └── index.ts                      # Re-exports de @repo/analytics/tracker
├── tests/
│   ├── gtm.events.test.ts
│   └── facebook.events.test.ts
└── vitest.config.ts

Entry points del paquete

ImportContenido
@repo/analyticsTipos + funciones de eventos (GTM + FB + GA4)
@repo/analytics/ga4trackGA4Event, trackGA4PageView
@repo/analytics/gtmGTMScript, GTMNoScript, funciones GTM
@repo/analytics/facebookFacebookPixelScript, funciones FB Pixel
@repo/analytics/trackerAnalyticsPageViewTracker (Client Component)

Por qué separar los entry points? Los Server Components (GTMScript, GTMNoScript, FacebookPixelScript) no pueden mezclarse con el 'use client' del tracker en algunos bundlers. La separación permite importarlos de forma independiente sin errores.


3. Instalación

En el package.json de cada app que quiera usar analytics:

json
{
  "dependencies": {
    "@repo/analytics": "workspace:*"
  }
}
bash
pnpm install

4. Variables de entorno por app

Cada app define sus propios IDs. Los componentes reciben el ID como prop desde process.env.NEXT_PUBLIC_*nunca se hardcodean IDs dentro del paquete.

VariableDescripciónEjemplo
NEXT_PUBLIC_GTM_IDGoogle Tag Manager container IDGTM-XXXXXXXX
NEXT_PUBLIC_FB_PIXEL_IDMeta Pixel numeric ID1234567890123456

.env.local (desarrollo)

bash
# apps/landing/.env.local
NEXT_PUBLIC_GTM_ID=GTM-XXXXXXXX
NEXT_PUBLIC_FB_PIXEL_ID=1234567890123456

5. Uso en el layout raíz

Montar los scripts una sola vez en app/layout.tsx de cada app. Los componentes de script son Server Components — no añaden JS al bundle del cliente.

tsx
// apps/landing/app/layout.tsx
import { GTMScript, GTMNoScript } from '@repo/analytics/gtm';
import { FacebookPixelScript } from '@repo/analytics/facebook';
import { AnalyticsPageViewTracker } from '@repo/analytics/tracker';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="es">
      <head>
        {/* 1. GTM script — se carga después del FCP */}
        <GTMScript gtmId={process.env.NEXT_PUBLIC_GTM_ID} />
      </head>
      <body>
        {/* 2. GTM noscript fallback — justo después de <body> */}
        <GTMNoScript gtmId={process.env.NEXT_PUBLIC_GTM_ID} />

        {/* 3. Meta Pixel init */}
        <FacebookPixelScript pixelId={process.env.NEXT_PUBLIC_FB_PIXEL_ID} />

        {/* 4. Re-dispara PageView en cada cambio de ruta (SPA navigation) */}
        <AnalyticsPageViewTracker />

        {children}
      </body>
    </html>
  );
}

Si una variable de entorno no está definida, el componente simplemente no renderiza nada — no hay errores en runtime.

Deshabilitar plataformas selectivamente

tsx
{
  /* Solo GTM, sin Facebook Pixel */
}
<AnalyticsPageViewTracker enableFacebook={false} />;

{
  /* Solo Facebook Pixel */
}
<AnalyticsPageViewTracker enableGTM={false} />;

6. Tracking de eventos

Importar las funciones desde @repo/analytics en cualquier Client Component.

tsx
'use client';
import { pushGTMEvent, trackFbEvent, trackFbCustomEvent, trackGA4Event } from '@repo/analytics';

// ── GA4 ──────────────────────────────────────────────────────────────────────

// Evento personalizado
trackGA4Event('landing_cta_click', {
  cta_source: 'hero',
  scroll_target: 'waitlist-form',
  page_path: window.location.pathname,
});

// Vista de página (manual)
import { trackGA4PageView } from '@repo/analytics/ga4';
trackGA4PageView('/onboarding/paso-2', 'Onboarding — Paso 2');

// ── Hook de tracking automático (scroll depth, clicks, section views) ─────────
import { useAnalyticsTrackEvents } from '@repo/analytics';

// Montar en cualquier Client Component o layout
useAnalyticsTrackEvents({
  enableScrollDepth: true, // Emite scroll_depth a GA4 en 25/50/75/90%
  enableClickTracking: true, // Registra clicks en elementos con data-track
  enableSectionViews: true, // Registra visibilidad de secciones con IntersectionObserver
  scrollThresholds: [25, 50, 75, 90],
});

// ── GTM ──────────────────────────────────────────────────────────────────────

// Evento personalizado (cualquier clave/valor)
pushGTMEvent({ event: 'cta_click', button_text: 'Empezar gratis', section: 'hero' });

// Vista de página virtual (útil para modales o steps de un wizard)
import { trackGTMPageView } from '@repo/analytics';
trackGTMPageView('/onboarding/step-2', 'Onboarding — Paso 2');

// ── Facebook Pixel ────────────────────────────────────────────────────────────

// Evento estándar
trackFbEvent('Lead', { content_name: 'formulario_contacto' });
trackFbEvent('Purchase', { value: 4999, currency: 'ARS', num_items: 1 });
trackFbEvent('CompleteRegistration', { status: 'confirmed' });
trackFbEvent('ViewContent', { content_type: 'product', content_ids: ['plan-pro'] });

// Evento personalizado
trackFbCustomEvent('presupuesto_solicitado', { tipo_obra: 'reforma_baño', zona: 'CABA' });
trackFbCustomEvent('video_reproducido', { titulo: 'Demo Nidus', duracion_seg: 42 });

Eventos estándar de Meta Pixel disponibles

PageView · ViewContent · Search · AddToCart · AddToWishlist · InitiateCheckout · AddPaymentInfo · Purchase · Lead · CompleteRegistration · Contact · CustomizeProduct · Donate · FindLocation · Schedule · StartTrial · SubmitApplication · Subscribe


7. API completa

@repo/analytics/ga4

ExportTipoDescripción
trackGA4Event(name, params?)FunciónEmite un evento a GA4 vía gtag('event', ...)
trackGA4PageView(path, title?)FunciónEmite el evento estándar page_view a GA4

@repo/analytics — Hook

ExportTipoDescripción
useAnalyticsTrackEvents(opts)HookScroll depth, click tracking y section views automáticos enviados a GA4

Opciones de useAnalyticsTrackEvents:

PropTipoDefaultDescripción
enableScrollDepthbooleantrueActiva tracking por profundidad de scroll
enableClickTrackingbooleantrueActiva tracking de clicks
enableSectionViewsbooleantrueActiva tracking de visibilidad de sección
scrollThresholdsArray<number>[25, 50, 75, 90]Porcentajes donde se emite el evento

@repo/analytics/gtm

ExportTipoDescripción
GTMScriptServer ComponentInyecta el snippet GTM en <head>
GTMNoScriptServer Component<noscript> fallback en <body>
pushGTMEvent(data)FunciónEmpuja un evento al window.dataLayer
trackGTMPageView(path, title?)FunciónEmpuja un evento page_view a GTM

@repo/analytics/facebook

ExportTipoDescripción
FacebookPixelScriptServer ComponentInyecta el snippet del pixel y dispara el PageView inicial
trackFbEvent(event, params?)FunciónDispara un evento estándar de Meta Pixel
trackFbCustomEvent(event, params?)FunciónDispara un evento personalizado de Meta Pixel
trackFbPageView()FunciónDispara fbq('track', 'PageView') manualmente

@repo/analytics/tracker

ExportTipoDescripción
AnalyticsPageViewTrackerClient ComponentDispara PageView en GTM y/o FB Pixel en cada cambio de ruta

Props de AnalyticsPageViewTracker:

PropTipoDefaultDescripción
enableGTMbooleantrueActiva tracking de page_view en GTM
enableFacebookbooleantrueActiva tracking de PageView en Meta Pixel

8. Tests

El paquete usa Vitest con entorno jsdom para simular el DOM del navegador.

bash
# Ejecutar tests en watch mode
pnpm test --filter @repo/analytics

# Ejecutar tests una sola vez (CI)
pnpm test:run --filter @repo/analytics

Qué cubren los tests

ArchivoQué se prueba
tests/gtm.events.test.tspushGTMEvent, trackGTMPageView, inicialización de dataLayer, SSR
tests/facebook.events.test.tstrackFbEvent, trackFbCustomEvent, trackFbPageView, ausencia de fbq, SSR

Agregar un test nuevo

ts
// tests/mi-nuevo.test.ts
import { describe, it, expect } from 'vitest';
import { pushGTMEvent } from '../src/gtm/events';

describe('Mi test', () => {
  it('...', () => {
    pushGTMEvent({ event: 'mi_evento' });
    expect(window.dataLayer[0]?.event).toBe('mi_evento');
  });
});

9. Agregar variables a App Hosting

Firebase App Hosting lee la configuración desde el archivo apphosting.yaml (producción) o apphosting.test.yaml (ambiente de prueba) de cada app.

Para la app landing

yaml
# apps/landing/apphosting.yaml  (o apphosting.test.yaml)
env:
  # ── Analytics ──────────────────────────────────────────────────────────────

  # Google Tag Manager — container ID (formato GTM-XXXXXXXX)
  - variable: NEXT_PUBLIC_GTM_ID
    value: 'GTM-XXXXXXXX'
    availability:
      - BUILD
      - RUNTIME

  # Meta (Facebook) Pixel — ID numérico de 15-16 dígitos
  - variable: NEXT_PUBLIC_FB_PIXEL_ID
    value: '1234567890123456'
    availability:
      - BUILD
      - RUNTIME

availability: [BUILD, RUNTIME] es necesario porque Next.js embebe las variables NEXT_PUBLIC_* en el bundle durante el build. Sin BUILD, el valor sería undefined en el cliente.

Para otras apps (01-base-app, etc.)

Repetir el mismo bloque en el apphosting.yaml correspondiente con los IDs propios de cada app. Cada app tiene su propio GTM container y Pixel ID — no compartir IDs entre apps.

Variables secretas (si el ID es sensible)

Si el pixel ID no debe ser visible en el repositorio, usar el Secret Manager de Firebase:

yaml
- variable: NEXT_PUBLIC_FB_PIXEL_ID
  secret: fb-pixel-id-landing
  availability:
    - BUILD
    - RUNTIME

10. Extensión: agregar una nueva plataforma

Ejemplo: agregar TikTok Pixel.

  1. Crear src/tiktok/TikTokPixelScript.tsx (Server Component con validación de ID).
  2. Crear src/tiktok/events.ts con las funciones de tracking (ttq.track, etc.).
  3. Crear src/tiktok/index.ts con los re-exports.
  4. Agregar el nuevo entry point en package.json:
    json
    "./tiktok": {
      "types": "./src/tiktok/index.ts",
      "import": "./src/tiktok/index.ts",
      "default": "./src/tiktok/index.ts"
    }
    
  5. Montar el componente en el layout y agregar NEXT_PUBLIC_TIKTOK_PIXEL_ID en apphosting.yaml.
  6. Agregar tests en tests/tiktok.events.test.ts.

Contribución y validación

bash
pnpm lint --filter @repo/analytics
pnpm check-types --filter @repo/analytics
pnpm test:run --filter @repo/analytics

pnpm lint --filter @repo/01-base-package pnpm build --filter @repo/01-base-package pnpm check-types --filter @repo/01-base-package

## 6. Buenas practicas - API chica y coherente. - Tipos explicitos para todos los contratos. - README actualizado por cada cambio de API.