KPIs actuales · NutriTrack · Junio 2026
MAU
4.217
▲ 8.3% vs. mayo
Revenue MRR
€42.8K
▲ 12.1% vs. mayo
Churn Rate
3.1%
▼ 0.4pp (mejora)
Eventos/día
183K
▲ 6.7% vs. mayo
Signups (mes)
389
▲ 22% vs. mayo
Arquitectura del pipeline híbrido
PostgreSQL
OLTP operativo
→
S3 Parquet
3 TB eventos
→
DuckDB local
dev · test queries
→
MotherDuck
analytics DB cloud
→
Mat. Tables
daily_kpis · cohorts
→
FastAPI / Node
/api/analytics
→
Dashboard
equipo · clientes
Implementación — snippets clave
Python
connect.py
# Autenticación MotherDuck — NutriTrack import duckdb import os # Token en variable de entorno (jamás en código) conn = duckdb.connect( "md:nutritrack_analytics", config={ "motherduck_token": os.environ["MOTHERDUCK_TOKEN"] } ) # Crear base de datos cloud (persiste entre sesiones) conn.sql("CREATE DATABASE IF NOT EXISTS nutritrack") conn.sql("USE nutritrack") print("✅ Conectado a MotherDuck — nutritrack")
Python
load_s3.py
# Carga incremental desde S3 → MotherDuck conn.sql(""" CREATE TABLE IF NOT EXISTS events ( event_id VARCHAR, user_id VARCHAR, event_type VARCHAR, amount DOUBLE, plan VARCHAR, created_at TIMESTAMP ) """) # Append incremental — evita recargar 3 TB conn.sql(""" INSERT INTO events SELECT * FROM read_parquet( 's3://nutritrack-data/events/2026-06-*.parquet', hive_partitioning = true ) WHERE event_id NOT IN (SELECT event_id FROM events) AND created_at::DATE = current_date - 1 """) print(f"Filas cargadas: {conn.sql('SELECT COUNT(*) FROM events').fetchone()[0]:,}")
Python
daily_transform.py
# Tabla materializada de KPIs diarios conn.sql(""" CREATE OR REPLACE TABLE daily_kpis AS SELECT DATE(created_at) AS date, COUNT(DISTINCT user_id) AS dau, COUNT(DISTINCT CASE WHEN event_type = 'signup' THEN user_id END) AS signups, COUNT(DISTINCT CASE WHEN event_type = 'churn' THEN user_id END) AS churns, COUNT(DISTINCT CASE WHEN event_type = 'premium_upgrade' THEN user_id END) AS upgrades, SUM(CASE WHEN event_type = 'premium_upgrade' THEN amount ELSE 0 END) AS revenue, COUNT(*) AS total_events FROM events WHERE created_at >= current_date - INTERVAL 90 DAYS GROUP BY 1 ORDER BY 1 DESC """) print("✅ daily_kpis actualizada")
TypeScript
analytics.ts
// API de analytics — expone MotherDuck a frontend import duckdb from "duckdb-async" const db = await duckdb.Database.create( "md:nutritrack", { motherduck_token: process.env.MOTHERDUCK_TOKEN! } ) async function getKPIs(days: number = 30) { return db.all(` SELECT date, dau, signups, revenue, churns FROM daily_kpis WHERE date >= current_date - INTERVAL '${days} days' ORDER BY date `) } // Endpoint Express app.get("/api/kpis", async (req, res) => { const data = await getKPIs(parseInt(req.query.days) || 30) res.json({ ok: true, data }) })
Eventos diarios — últimas 2 semanas
Fuente: materialized table daily_kpis · MotherDuck
Revenue por plan
Distribución MRR junio 2026
Clinic €99
€26.1K
Pro €29
€14.2K
Basic €9
€2.5K
Query en MotherDuck:
SELECT plan, SUM(amount) AS mrr FROM events WHERE event_type = 'premium_upgrade' AND DATE_TRUNC('month', created_at) = '2026-06-01' GROUP BY 1
Cohort retention — semanas 0–5
Retención de usuarios por cohorte de registro
Query ejecutada en MotherDuck · tabla: cohort_retention · cohortes ene–may 2026
| Cohorte | Usuarios | Sem 0 (100%) | Sem 1 | Sem 2 | Sem 3 | Sem 4 | Sem 5 |
|---|---|---|---|---|---|---|---|
| Ene 2026 | 612 | 100% | 68% | 54% | 41% | 38% | 34% |
| Feb 2026 | 548 | 100% | 71% | 57% | 46% | 41% | 37% |
| Mar 2026 | 623 | 100% | 74% | 61% | 52% | 48% | 44% |
| Abr 2026 | 701 | 100% | 69% | 56% | 47% | 42% | — |
| May 2026 | 734 | 100% | 77% | 62% | — | — | — |
Insight: La cohorte de marzo 2026 muestra la mejor retención en semana 2 (61%) — coincide con el lanzamiento del feature de seguimiento de macros. Recomendación: destacar ese feature en onboarding.
Buenas practicas implementadas
Ejecucion hibrida
Queries de desarrollo en DuckDB local. Deploy exacto en MotherDuck sin cambiar SQL.
Parquet en S3
Datos raw en S3 particionados por fecha. MotherDuck los consulta directamente sin mover 3 TB.
Tablas materializadas
daily_kpis y cohort_retention pre-calculadas. Dashboard carga en ms, no recalcula.
Token en env var
MOTHERDUCK_TOKEN nunca en código fuente. Acceso por variable de entorno en prod y CI/CD.
Carga incremental
INSERT WHERE NOT IN evita recargar el historial completo cada noche. Solo nuevos eventos.
Compartir DB, no CSV
GRANT SELECT a colegas para acceso live. Sin enviar archivos, sin datos desactualizados.
Coste consciente
Queries costosas pre-agregadas. El dashboard ejecuta SELECT sobre materialized tables, no sobre raw events.
Cron diario
daily_transform.py ejecuta a las 02:00h UTC via cron. Datos frescos cada mañana sin intervención manual.