Funnel Platform v3 · guía del ecosistema
Todo lo que construimos el 15-Abr-2026 en una página. Para volver cuando quieras entender cómo funciona este nuevo ecosistema y cómo operarlo.
Qué es esto en 30 segundos
Funnel Platform v3 es la infraestructura de A/B testing bayesiano multi-tenant para los funnels de todos los clientes LHT. Cada visitante que llega a una landing es asignado a una variante (Thompson bandit), cada click/scroll/conversión se registra en Supabase, y un cron cada 15 min retira perdedores y promueve ganadores automáticamente.
Reemplaza al flujo manual donde vos mirabas ads a mano y decidías qué matar. Ahora el sistema lo hace con matemática y vos ves el resultado en un panel.
Arquitectura en 5 capas
Qué tiene cada cliente ahora
- panel.html — dashboard propio (ej.
/clients/lht-pro/panel.html) con KPIs vivos, variantes y Thompson posterior - vsl.html con runtime tracker inyectado — script que pide variante y trackea todo el comportamiento del visitante
- variants.json — definición de las 3 variantes seed (v1 control, v2 challenger A, v3 challenger B)
- Exit-popup factorial — 5 buckets de timing (5s/15s/30s/60s/exit-intent) compitiendo entre sí
- Config row en Supabase — para las 3 fuentes (meta/youtube/organic) · experimento en modo warmup hasta 150 impresiones
- Cron de rotación — cada 15 min el sistema evalúa y actúa sin intervención manual
Cómo funciona el Thompson Sampling
En vez de repartir 50/50 el tráfico y decidir al final, el bandit usa una distribución Beta(α=conversiones+1, β=visitantes−conversiones+1) por variante. Cada request muestrea de esas distribuciones y pickea al que salga mejor. Ventaja: mientras explora también explota, no perdés plata tráfico en perdedores obvios.
Regla interna: si una variante supera P(best) ≥ 0.60 en el Monte Carlo con 200 sims, se explota 100%. Si nadie supera ese umbral, se bucketiza por hash determinístico de la sesión (mismo visitante = misma variante en refresh).
Las 10 dimensiones del rubric
| # | Dimensión | Qué verifica | Score |
|---|---|---|---|
| D51 | A/B runtime infra | variante endpoint live · ≥3 variantes · p_best válido · posterior fresco | 10/10 |
| D52 | Source isolation | experimentos independientes por meta/youtube/organic/email/whatsapp | 10/10 |
| D53 | Event taxonomy | ≥10 tipos de eventos (impression, conversion, scroll_depth, dwell, …) | 9/10 |
| D54 | Dwell analytics | percentiles p50/p75/p95 + device split por bucket | 9/10 |
| D55 | Auto-rotation | cron corre + log tiene promote & retire observados | 10/10 |
| D56 | Panel observability | panel fetchea stats/variante/pivot + KPI strip + tabla + refresh | 10/10 |
| D57 | Cross-pixel dedup | 100 eventos POST → 100 recuperados por event_id (sin doble conteo) | 10/10 |
| D58 | Exit-popup factorial | 5 buckets de timing compitiendo en la landing | 10/10 |
| D59 | Tracking audit | endpoint /tracking-check reporta ok en Pixel/CAPI/GA4/Sheets | 10/10 |
| D60 | Multi-tenant RLS | cliente A no ve data de cliente B · naked query rechazada 4xx | 10/10 |
Cómo operarlo (rutina diaria, 5 min)
- Abrir el panel del clienteIr a
localhost:3847/clients/<slug>/panel.html. Te da impresiones, conversiones, calificadas, WhatsApp, variante ganadora del momento, tabla de variantes con P(best), y pivot device/hora. - Mirar P(best)Si alguna variante superó 85% sustained durante 48h, el cron la promueve automáticamente. Si ninguna despega, agregá variante nueva.
- Agregar una variante nuevaEditás
public/clients/<slug>/variants.json, agregás{id:"v4",label:"…",hook:"…"}, corrés bootstrap otra vez y el cron la integra. - Forzar rotaciónSi querés ver si el cron tomaría alguna acción:
curl /api/v3/cron-rotation?client_slug=<slug>&source=meta. Devuelve qué promovió, retiró o agregó. - Auditar trackingSi sospechás que algún evento no se está registrando (Pixel vs CAPI vs DB):
curl /api/v3/tracking-check?client=<slug>&event_id=<uuid>.
Archivos clave · dónde mirar cuando algo falla
| Archivo | Responsabilidad |
|---|---|
tools/funnel-platform-v3/variante.mjs | Thompson Sampling + pick de variante. Si querés ajustar el umbral 60% de exploit/explore, acá. |
tools/funnel-platform-v3/track.mjs | Ingestión de eventos. Si un event_name es rechazado con 400, es acá (ALLOWED_EVENTS). |
tools/funnel-platform-v3/stats.mjs | Agregaciones para el panel. Thompson Monte Carlo 5000 sims para el posterior vitalicio. |
tools/funnel-platform-v3/config.mjs | CRUD de experimentos. action="retire" / "activate" / "requeue" para mutar active/queue/retired. |
tools/funnel-platform-v3/cron-rotation.mjs | Algoritmo que promueve campeones (P≥0.85 sustained 48h) y retira perdedores (P≤0.05 con ≥50 imp). |
tools/funnel-platform-v3/exit-popup-factorial.mjs | 5 buckets de exit-popup. Cambiá el array EXIT_POPUP_BUCKETS para tocar timings. |
tools/funnel-platform-v3/runtime-scorer.mjs | Scorer D51-D60. Correlo cuando metés cambios grandes para ver qué rompiste. |
tools/funnel-platform-v3/sql/*.sql | 4 migrations. Schema, indices (siempre lead con client_slug), RLS policies, event_name CHECK. |
public/clients/_template/panel.html | Template del panel. Editar acá + correr panel-deploy.mjs propaga a los 28 clientes. |
Comandos útiles
# Ver score runtime actual $ node -e "import('./tools/funnel-platform-v3/runtime-scorer.mjs').then(m=>m.scoreRuntime({baseUrl:'http://localhost:3847',client:'lht-pro'}).then(r=>console.log(r.total+'/100')))" # Test endpoint variante para un cliente $ curl "http://localhost:3847/api/v3/variante?client_slug=lht-pro&source=meta" # Ver eventos recientes en Supabase $ curl "http://localhost:3847/api/v3/stats?client_slug=lht-pro&source=meta" # Forzar rotación manual $ curl "http://localhost:3847/api/v3/cron-rotation?client_slug=lht-pro&source=meta" # Re-deployar todos los paneles (después de editar el template) $ node tools/funnel-platform-v3/panel-deploy.mjs # Re-bootstrap v3 en todos los clientes (idempotente) $ node tools/funnel-platform-v3/__bootstrap-client-funnels.mjs
Qué sigue · roadmap corto
- WIP Deploy a Vercel + dominio bookingfunnel.io — hoy corre solo en localhost. Necesita Git push + Vercel connect + env vars.
- GAP v3-tracking.json con creds reales por cliente — hoy es
local_stub. Para audit live Pixel/CAPI/GA4/Sheets, cargar credenciales. - GAP 10 clientes sin video de gracias/nurture — requieren extracción manual (Puppeteer o paste).
- GAP 3 clientes con brain incompleto (bbpr 9.78, whatever 9.89, llcya 0.00) — necesitan copy-pipeline P2 completo.
- WIP Variants reales (no solo labels v1/v2/v3) — hoy las 3 variantes renderizan el mismo HTML. Siguiente paso: hook/lead/body variants en DOM.
- WIP Mutiny ABM layer — personalización dinámica por cuenta/empresa en landing.
Seguridad y aislamiento
client_slug NOT NULL como primera columna después del id, cada índice lead con client_slug, y las RLS policies son FORCE ROW LEVEL SECURITY. Esto significa que si el código omite el client_slug en una query, Postgres rechaza la fila en lugar de devolver datos de otro cliente. La D60 valida esto automáticamente en cada run del scorer.
Credenciales y rotación
Las keys están en .env (no commiteadas). Para rotar la password de la DB:
- Supabase dashboard→
Settings→Database→Reset database password. - Copiar la nuevay pegarla en
.envreemplazando la líneaSUPABASE_DB_URL=…. - Restart del servercon
node server.js. Listo.