# Deep dive #3 — Analytics: Hotjar + dashboard product

**Status**: ✅ Sprint técnico DONE (2026-05-04). 🟡 Pendiente activación manual del PAT/Site ID + configuración del dashboard.
**Frente master**: #3 NOW (½ día estimado, ~30 min reales el sprint técnico)
**Doc operativo**: este archivo
**Doc relacionado**: [`../google-ads-setup.md`](../google-ads-setup.md) — GA4 + Google Ads ya en producción.

---

## Por qué es NOW

Estamos por escalar #9 Marketing (3 ads). Sin medición de comportamiento real (qué hace el visitante después de hacer click en una ad), tirás dinero al vacío:

- **GA4** te dice **qué pages se visitan** y conversiones macro.
- **Hotjar** te dice **qué hacen** dentro de cada page (heatmaps, session recordings, scroll depth, dónde se traban).
- Sin Hotjar, las decisiones de optimización post-campaña son adivinanzas.

Costo: **free tier alcanza** para empezar (35 sesiones/día por sitio, 3 heatmaps activos). Migrar a paid solo si volume justifica.

---

## Estado al iniciar (2026-05-04)

### Existe (validado)

- **GA4** activo en producción: `G-X565WS9CVY` (config en `config/site.php` slot `google_analytics`)
- **Google Ads** activo: `AW-18117373089` (slot `google_ads`)
- Inyección en `resources/views/layout/front/master.blade.php` líneas 208-234 (gtag.js compartido GA4 + Ads)
- `config/site.php` con slot `google_analytics` y `google_ads`
- `database/seeders/AnalyticsSeeder.php` carga GA4 desde JSON a tabla `settings` via `SiteConfigService`
- `database/seeders/project-data/analytics.json` con GA4 + GTM + FB Pixel (estos 2 últimos disabled)
- Cookie banner `resources/views/components/cookie-banner.blade.php` controla consent

### Falta (gap del frente #3)

- ❌ **Hotjar** instalado y operativo
- ❌ **Eventos GA4 custom** (CTA clicks, form submits, scroll depth) — hoy solo hay pageview default
- ❌ **Dashboard product analytics** — sin reporte estructurado de qué medir

---

## Decisiones tomadas (2026-05-04)

### Sobre Hotjar

| Pregunta | Respuesta | Razón |
|----------|-----------|-------|
| ¿Free tier o paid? | **Free tier** para empezar | 35 sesiones/día por sitio = suficiente para iterar. Upgrade solo si volume justifica. |
| ¿Cuenta única o por tenant? | **Cuenta única + Site ID por tenant** | Free tier permite múltiples sitios. Cada cliente que escale ads puede tener su propio Site ID. |
| ¿Cuándo activar? | **Por defecto OFF, opt-in vía config** | Hotjar carga JS extra → impacto en performance. Solo activar para sitios donde necesitamos data. |
| ¿Cookie consent? | **Hotjar carga independiente del cookie banner por ahora** | Hotjar tiene su propio consent management nativo. Si el banner del sitio rechaza, Hotjar respeta IP-based suppress. |
| ¿Heatmaps por demo? | **Sí — un heatmap por landing principal de cada demo activo** | El free tier permite 3 activos a la vez. Empezar con los 3 de demos con ads activos. |

### Sobre dashboard product analytics

| Pregunta | Respuesta | Razón |
|----------|-----------|-------|
| ¿Build custom o usar GA4 nativo? | **GA4 nativo + Looker Studio** para visualización | Build custom = dev time. GA4 + Looker = $0, listo en 1 hora. |
| ¿Eventos custom críticos a trackear ahora? | **CTA clicks + form submits + scroll depth** | Lo mínimo para medir conversión de ads. Más eventos cuando tengamos data. |
| ¿Quién mira el dashboard? | **Founder semanal** (15 min) | Sin volumen aún. Cuando llegue Leandro, él lo monitorea diariamente. |

---

## Plan de ejecución técnico

### Sprint técnico — DONE 2026-05-04 (~30 min)

| # | Acción | Archivo | Estado |
|---|--------|---------|--------|
| 1 | Slot `hotjar` en `config/site.php` con doc inline | `config/site.php` línea ~290 | ✅ |
| 2 | Slot `hotjar` en JSON seed | `database/seeders/project-data/analytics.json` | ✅ |
| 3 | `AnalyticsSeeder.php` carga Hotjar también | `database/seeders/AnalyticsSeeder.php` | ✅ |
| 4 | Snippet Hotjar en `master.blade.php` (con misma lógica condicional que GA4: enabled + site_id no vacío + (no local OR track_in_local)) | `resources/views/layout/front/master.blade.php` línea ~236 | ✅ |
| 5 | Validar `php artisan view:cache` | — | ✅ |
| 6 | Crear este deep dive | `docs/bewpro2.0/roadmap/01-analytics-hotjar.md` | ✅ |

**Por qué `enabled: false` por defecto**: Hotjar carga JS adicional que impacta TTI/performance. Es decisión consciente activar por sitio. El dueño del sitio (founder o tenant) lo enciende cuando quiere medir.

### Sprint manual — paso del founder (~15 min)

| # | Acción | Detalle |
|---|--------|---------|
| 7 | ✅ **Cuenta Hotjar creada** | 2026-05-05 con cd.bewpro@gmail.com |
| 8 | ✅ **Site creado en Hotjar** | URL: `https://bewpro.com` |
| 9 | ✅ **Script ID copiado** | `92480ff2f0490` (hash hex del nuevo formato Contentsquare) |
| 10 | ⚠️ **Activación SOLO en bp-bewpro** | Ver §"Activación segura" debajo. **NO** modificar `analytics.json` con valores reales — propagaría Hotjar a todos los tenants nuevos. |
| 11 | **Validar** | Visitar `bewpro.com` con DevTools abierto → Network tab → buscar request a `t.contentsquare.net/uxa/92480ff2f0490.js`. Si aparece, OK. Después de 24hs Hotjar tiene primera data. |

### ⚠️ Activación segura — bewpro vs tenants

**Bug evitado**: el archivo `database/seeders/project-data/analytics.json` se commitea al repo y `AnalyticsSeeder` lo corre **en CADA tenant** durante provisioning. Si el JSON tuviera `hotjar.enabled: true` + `hotjar.script_id: "92480ff2f0490"`, **todos los tenants nuevos** propagarían sus datos a la cuenta Hotjar de bewpro. Privacy violation + scope bug.

**Diseño correcto**:
- `analytics.json` queda con defaults seguros (`enabled: false`, `script_id: ""`).
- Bewpro activa Hotjar **solo en su DB** (`bp-bewpro`) vía comando dedicado.
- Otros tenants quedan con Hotjar deshabilitado por default. Si en el futuro un cliente quiere Hotjar para su sitio, lo activa con SU script_id (con el mismo comando + `--force`, o panel admin).

**Comando para activar en bewpro** (correr en VPS1, donde corre bewpro.com):

```bash
ssh root@72.61.45.136
cd /home/bewpro22/public_html/bewpro
sudo -u bewpro22 /opt/cpanel/ea-php82/root/usr/bin/php artisan bewpro:enable-hotjar 92480ff2f0490
```

El comando valida que la DB actual sea `bp-bewpro` antes de setear (defensa contra ejecutar accidentalmente en otra DB). Si querés activar para un cliente específico, usar `--force` con el script_id de ESE cliente.

### Sprint dashboard — distribuído (1-2 hr cuando haya data)

| # | Acción | Owner |
|---|--------|-------|
| 12 | Configurar 3 heatmaps en Hotjar (uno por landing de demo con ads activo) | Founder |
| 13 | Activar Session Recordings con sample rate 100% para sitios con volume bajo | Founder |
| 14 | Setup eventos GA4 custom: `cta_click`, `form_submit`, `scroll_depth_75` | Dev (script global en master.blade.php) — pendiente sprint 2 |
| 15 | Configurar Looker Studio dashboard conectado a GA4 con: visitas / sources / conversiones / scroll depth / demos top | Founder + Dev |
| 16 | Agendar review semanal del dashboard (15 min) | Founder |

---

## Implementación: cómo funciona el snippet de Hotjar

> **Nota 2026-05-05**: Hotjar fue adquirida por Contentsquare. El snippet "clásico" (`static.hotjar.com/c/hotjar-{id}.js`) sigue funcionando, pero los nuevos sitios reciben un snippet simplificado que apunta a `t.contentsquare.net/uxa/{script_id}.js`. **Usamos el formato nuevo**.

```blade
@php
    $hjEnabled = config('site.hotjar.enabled', false);
    $hjScriptId = config('site.hotjar.script_id', '');
    $hjTrackInLocal = config('site.hotjar.track_in_local', false);

    $loadHotjar = $hjEnabled
        && !empty($hjScriptId)
        && preg_match('/^[a-f0-9]+$/', $hjScriptId)  // hash hex defensivo
        && (!$isLocal || $hjTrackInLocal);
@endphp
@if ($loadHotjar)
    <script src="https://t.contentsquare.net/uxa/{{ $hjScriptId }}.js" async></script>
@endif
```

**Defensa contra inyección**: como el `script_id` se renderiza directo en el `src` del `<script>`, validamos con regex que sea solo hex (`/^[a-f0-9]+$/`) antes de cargar. Si alguien setea un valor malicioso por error, simplemente no carga.

**Lógica de carga** (idéntica patrón a GA4):
- `enabled: true` + `site_id` no vacío → considera cargar.
- En entorno local solo carga si `track_in_local: true` (default false → no contamina dev).
- En cualquier otro entorno (production, staging) carga si los anteriores se cumplen.

**Override por tenant**: si en algún momento queremos Site IDs diferentes por proyecto, solo hay que cargar el override desde DB via `SiteConfigService::set('hotjar', [...], 1)` — el config helper ya prioriza DB sobre archivo.

---

## Eventos GA4 custom — DONE 2026-05-04 (Sprint 2 adelantado)

Implementados como script inline al final de `master.blade.php` (después de `<x-recaptcha-script />`). Solo se ejecuta si GA4 está cargado (`window.gtag` definido).

| Evento | Trigger | Contexto enviado |
|--------|---------|------------------|
| `cta_click` | Click en selectores comunes de CTAs (`[data-cta]`, `.btn-primary`, `.btn-modern`, `a.btn-secondary`, `.cta`, `.hero-cta`) | `demo`, `page`, `cta_label` (texto o data-cta), `cta_href` |
| `form_submit_attempt` | Submit de cualquier `<form>` | `demo`, `page`, `form_id` |
| `scroll_depth_25` / `_50` / `_75` / `_100` | User llega al 25/50/75/100% del scroll de la page (1 vez por session) | `demo`, `page` |
| `outbound_click` | Click en link a dominio externo (no bewpro.com) | `demo`, `page`, `outbound_url` |

**Decisiones de diseño**:

- **Sin modificar cada blade**: usamos selectores comunes + delegated event listeners. Si querés trackear un CTA específico que no matchea los defaults, agregale `data-cta="nombre-descriptivo"`.
- **`form_submit_success` deferred**: requiere instrumentar server-side (responder con header o disparar evento desde controller). No crítico para hoy — el `attempt` ya da insight de intención.
- **Scroll depth con throttling 200ms**: evita spam al GA4 cuando el user hace scroll rápido. `IntersectionObserver` no se usó porque nuestros sentinels variarían por demo; el cálculo basado en `window.scrollY / docH` es más confiable.
- **`cta_href` y `outbound_url` truncados a 200 chars**: GA4 limit de event params.
- **Auto-detect del demo activo**: leemos `html.demo-X` className. Si no encuentra, marca `unknown` (no crashea).
- **Performance**: todo `passive: true` en listeners, IIFE, sin librerías externas. ~1KB inline.

### Cómo verlo en GA4

```
GA4 → Reports → Engagement → Events
  → ahí aparecerán los nuevos eventos en 24-48hs
  → click en un evento → ver "Event parameters" para filtrar por demo, page, cta_label

Ejemplos de queries útiles:
  - ¿Cuál es el CTA con más clicks en bewpro.com?
    Events → cta_click → group by cta_label

  - ¿Qué % llega al 75% del scroll en la home?
    Events → scroll_depth_75 → filter page = "/"

  - ¿Forms abandonados? (attempt sin success — cuando implementemos success)
    cta_click vs form_submit_attempt ratio
```

### Marcar como conversiones en GA4 (manual del founder)

Para que GA4 considere ciertos eventos como conversiones (y los reporte en Acquisition):

```
GA4 → Configure → Events → tocá el toggle "Mark as conversion" en:
  - form_submit_attempt   (intent → puede convertir)
  - cta_click             (con filtro por cta_label="Comprar" o similar)
```

Esto aparece después de que el evento se haya disparado al menos 1 vez (puede tardar hasta 24hs en ser visible).

---

## Auditoría GA4 — qué reportes mirar

**Acceso**: https://analytics.google.com/ con `cd.bewpro@gmail.com` → propiedad de bewpro.com (Tracking ID `G-X565WS9CVY`).

### Tráfico — quién llega

| Pregunta | Reporte | Path |
|----------|---------|------|
| ¿Cuántos visitantes únicos? | User acquisition | Reports → Acquisition → User acquisition |
| ¿De dónde vienen? | Traffic acquisition | Reports → Acquisition → Traffic acquisition |
| ¿Mobile o desktop? | Tech overview | Reports → Tech → Tech overview |
| ¿De qué países? | Demographic details | Reports → Demographics → Demographic details |
| ¿Qué keywords usaron en Google? | Acquisition + Google Ads link | Acquisition → Google Ads (después de linkear cuentas) |

### Comportamiento — qué hacen adentro

| Pregunta | Reporte | Path |
|----------|---------|------|
| ¿Páginas más visitadas? | Pages and screens | Reports → Engagement → Pages and screens |
| ¿Cuánto tiempo se quedan? | "Average engagement time" en Pages | misma vista |
| ¿Cuántas páginas ven por sesión? | Engagement overview | Reports → Engagement → Overview |
| ¿% que se va sin interactuar? | "Bounce rate" (configurar comparable) | Engagement → Pages → Customize report |
| ¿Eventos automáticos GA4? (scroll 90%, outbound, file_download, video_*) | Events overview | Reports → Engagement → Events |
| **¿Qué CTAs clickean?** (post-deploy de eventos custom) | Events → `cta_click` → Event parameters | Reports → Engagement → Events |
| **¿Hasta dónde scrollean?** | Events → `scroll_depth_*` | misma vista |

### Conversiones

| Pregunta | Reporte | Path |
|----------|---------|------|
| ¿Cuántos compraron? | Conversions | Reports → Engagement → Conversions |
| ¿Source con mejor conversión? | Conversions agrupadas por source | Acquisition → Traffic acquisition con métric "Conversions" |
| ¿Funnels (visit → cta_click → form_submit → conversion)? | Funnel exploration | Explore → Funnel exploration (manual setup, ~10 min) |

### Qué hacer en los primeros 7 días post-deploy

| Día | Acción |
|-----|--------|
| Día 0 (hoy) | Deploy de los eventos custom + verificar en DebugView (https://analytics.google.com/ → Configure → DebugView) que se disparan al navegar bewpro.com con `?gtm_debug=x` |
| Día 1-2 | Revisar Events para confirmar que `cta_click`, `form_submit_attempt`, `scroll_depth_*`, `outbound_click` aparecen con volumen >0 |
| Día 3 | Mark `form_submit_attempt` como conversion. Crear audiencia "Engaged users" (engagement > 30s + scroll 75) |
| Día 7 | Primera review: ¿qué CTAs convierten? ¿En qué % se cae el scroll? ¿Qué pages tienen bounce alto? |

---

## Comandos útiles

```bash
# Verificar que Hotjar está cargando (después de setear Site ID y deploy)
curl -s https://bewpro.com | grep -c "static.hotjar.com"
# → 1 si está OK, 0 si no carga

# Ver si los settings de la DB están cargados
php artisan tinker
>>> config('site.hotjar')

# Re-correr seeder después de cambiar el JSON
php artisan db:seed --class=AnalyticsSeeder

# Limpiar caches de config tras cambio en site.php
php artisan config:clear
```

---

## KPIs / éxito

| Métrica | Baseline | Goal 30 días |
|---------|----------|--------------|
| GA4 activo en bewpro.com | ✅ Sí | ✅ Sí (sin cambio) |
| Hotjar instalado en bewpro.com | ❌ No | ✅ Sí |
| Heatmaps activos | 0 | 3 (uno por demo con ads activos) |
| Session recordings disponibles | 0 | >50 sesiones/semana |
| Eventos GA4 custom configurados | 0 | ✅ 4 implementados (cta_click, form_submit_attempt, scroll_depth_*, outbound_click). Pendiente: form_submit_success (server-side) |
| Dashboard Looker Studio | ❌ No existe | ✅ Activo, review semanal |
| Decisiones de producto basadas en data (no en intuición) | 0 | ≥1 cada 2 semanas |

---

## Trade-offs aceptados

- **Hotjar carga JS extra** → impacto en TTI/performance ~50-100ms en mobile. Justificado por valor del feedback. Mitigación: `enabled: false` por default; cada sitio decide cuándo activar.
- **Free tier limita a 35 sesiones/día/sitio**. Si un sitio supera, los recordings extras se pierden. OK para validation phase. Upgrade a Paid solo cuando una landing convierta sostenido.
- **GA4 + Hotjar duplican algunas métricas** (visitas, sources). No es problema — Hotjar va por behavior, GA4 por funnel. Cada uno cubre su lado.
- **Sin eventos GA4 custom hoy** → conversiones macro funcionan, pero no podemos medir intermedios. Aceptable para esta iteración; sprint 2 lo cubre cuando haya volume.

---

## Decisiones de seguridad/privacidad

- Hotjar se carga **después** de GA4 — si el cookie banner reject está activo y bloquea analytics, ambos se omiten consistentemente.
- Hotjar **no captura inputs sensibles por default** (passwords, credit cards). Está documentado en su privacy policy. Validar al primer recording que no veamos datos sensibles del cliente.
- En `Política de Privacidad` (`/legal/privacy`) — agregar Hotjar a la lista §4 "Con quién compartimos datos" después de activarlo.

---

*Iniciado: 2026-05-04*. Sprint técnico ✅ DONE mismo día. Pendiente: paso manual del founder (crear cuenta Hotjar + setear Site ID) y sprint 2 de eventos custom + dashboard.
