🦆

NutriTrack — Pipeline MotherDuck + DuckDB

Analítica serverless híbrida: S3 + local → cloud SQL · Generado por CULTIVA IA · Datos-Análisis
MotherDuck DuckDB S3 Parquet Python · TypeScript
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
J3
J4
J5
J6
J7
J8
J9
J10
J11
J12
J13
J14
J15
J16

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.