Saltar al contenido

lección 1

StreamVibe: desde cero

Conoces la startup, el equipo, los 15K usuarios sin métricas y la presentación a inversores del viernes. Este es tu momento.

⏱ 25 min

## El final del camino -- y el principio de todo

Han pasado más de cinco años desde aquel primer día en FreshMart donde te temblaban las manos al abrir un CSV de María. Cinco años en los que has limpiado datos sucios en retail, calculado cohortes de retención en SaaS, reconciliado fuentes de atribución en marketing, diseñado feature stores para detección de fraude en banca, implementado pipelines de ETAs en tiempo real en logística, navegado regulación GDPR en salud, y presentado arquitecturas de ingesta de 10 millones de eventos por hora al VP de Engineering de una fábrica de automoción.

En FreshMart eras el junior que preguntaba todo. En TaskFlow empezaste a proponer soluciones. En AdPulse ya presentabas resultados a clientes. En NeoBank tomabas decisiones de arquitectura bajo presión regulatoria. En FlashRoute eras dueño de un dominio completo de datos en tiempo real. En MediTrack diseñabas soluciones para datos sensibles que afectaban a la salud de personas. En SmartFactory liderabas el diseño técnico ante el VP de Engineering de una planta con 2000 sensores.

Ahora Marta Vidal, CEO de StreamVibe, te dice las palabras que todo senior quiere escuchar: "No hay nada. Tú decides. Construye."

## Cómo llegaste aquí

Fernando Aguirre, el VP de Engineering de SmartFactory, te escribió un viernes por la tarde: "Tengo una amiga que está montando una plataforma de streaming. Necesita a alguien que le construya todo el stack de datos desde cero. Le di tu nombre. Se llama Marta Vidal -- trabajó en Netflix España como Product Manager antes de lanzar su propia cosa. Es buena gente y tiene prisa." La llamada con Marta duró 40 minutos. No hubo entrevista técnica. No hubo coding challenge. No hubo panel de 6 personas.

Marta te preguntó: "Si tuvieras que construir la infraestructura de datos de una plataforma de streaming desde cero, con presupuesto limitado y un deadline de una semana para mostrar métricas a inversores, ¿por dónde empezarías?" Tu respuesta fue clara, estructurada, con opciones y trade-offs. Le hablaste de event tracking, de métricas de producto que importan a VCs, de DuckDB como motor analítico para startups, de cómo montar un lake con S3 por céntimos. Ella se convenció en 5 minutos. El resto de la llamada fue Marta contándote su visión del producto y por qué cree que el streaming de nicho tiene futuro.

## StreamVibe -- La empresa

StreamVibe es una plataforma de streaming de vídeo fundada hace 18 meses por Marta Vidal y Daniel Ruiz. Contenido de nicho: documentales independientes, cortometrajes premiados, cine europeo de autor y series originales cortas. No compiten con Netflix en catálogo -- compiten en curación y comunidad. Su propuesta de valor es "el contenido que Netflix no te muestra, seleccionado por personas que de verdad lo entienden". Piensan en sí mismos como "la Criterion Channel en español".

  • Usuarios registrados: 20.000. Activos mensuales: ~15.000 -- pero ojo, ese número sale de contar quién ABRIÓ la app (el campo last_login_at), no quién vio algo. El MAU de producto, el que mide el consumo, hay que calcularlo desde las reproducciones y va a salir más bajo. Parte de tu trabajo esta semana es decidir cuál de los dos es "el" número y escribirlo
  • Contenido: 3.000 horas (1.200 títulos entre documentales, cortos, películas y series)
  • Planes: Free (con anuncios, catálogo limitado a 200 títulos) + Premium (7.99 EUR/mes, todo)
  • Usuarios premium: 3.800 (~25% de MAU, ~9% de registrados)
  • Ingresos mensuales: ~30K EUR (suscripciones) + ~5K EUR (ads programáticos)
  • Burn rate: ~80K EUR/mes (30 personas, oficina, infra, licencias contenido)
  • Runway: 8 meses sin nueva inversión
  • Equipo: 30 personas (12 tech, 8 contenido, 5 marketing, 5 operaciones)
  • Fundación: septiembre 2022. Pre-seed de 500K EUR de business angels.
  • Objetivo: Serie A de 3-5M EUR para escalar contenido y tecnología.
  • Infraestructura de datos: NINGUNA. Cero pipelines. Cero métricas calculadas. Cero lake.
  • Deadline inmediato: presentación a Meridian Capital el viernes a las 11:00.

## El equipo que te rodea

Las tres personas que te van a pedir cosas esta semana, y lo que quiere cada una

## Los personajes -- sus voces y sus necesidades

Marta Vidal (35) -- CEO y cofundadora. Ex-Product Manager en Netflix España durante 4 años, donde aprendió a pensar en métricas de engagement y retención. Dejó Netflix para crear "la plataforma que Netflix nunca será: curada, personal, para gente que quiere contenido con alma". Es inteligente, tiene urgencia constante y sabe exactamente qué métricas quiere -- pero no tiene idea de cómo calcularlas ni qué infraestructura hace falta. Su frase del primer día:

Marta: "El viernes a las 11 presento a Meridian Capital. Necesito UN dashboard con 3-4 métricas que cuenten la historia de que StreamVibe funciona. Que los usuarios vuelven, que se quedan viendo contenido, que crecemos. Si no lo tenemos, perdemos la ronda. Y si perdemos la ronda, en 8 meses cerramos."

Daniel Ruiz (33) -- CTO y cofundador. Full-stack developer que construyó toda la aplicación él solo en los primeros 6 meses. Node.js, PostgreSQL, React, CDN de vídeo con CloudFront. Ha contratado a 3 devs que le ayudan, pero el código base sigue siendo "su bebé". La base de datos PostgreSQL no tiene índices analíticos, no tiene particiones temporales, y está dimensionada para servir la app -- no para que alguien haga queries pesadas de analytics. Su actitud:

Daniel: "Ni se te ocurra hacer queries pesadas contra producción. Me caes bien pero si me tiras la app, me da igual cuánta experiencia tengas. La base tiene 20K usuarios y casi 4 millones de filas en la tabla de reproducciones -- y eso son sólo las últimas 3 semanas, porque lo anterior lo va borrando un cron que puse para que la base no se me fuera de las manos. Cualquier query que tarde más de 2 segundos me pone nervioso. Dime qué necesitas y te doy acceso a una réplica de lectura, pero NO toques prod directamente."

Sofía García (29) -- Head of Content. Viene del mundo audiovisual: trabajó en Filmin y en una productora independiente. Es la persona que selecciona qué contenido licenciar, negocia contratos con distribuidoras y decide la programación del catálogo. Tiene 200K EUR de presupuesto para licencias del Q2 y necesita decidir en qué invertirlos. Hasta ahora decide por intuición y por lo que lee en foros de cine. Su petición:

Sofía: "Tengo 200K EUR para licencias del Q2. Necesito saber: ¿comedia o drama? ¿series cortas de 6 episodios o películas de 90 minutos? ¿documentales de naturaleza o de true crime? Dame datos, no intuiciones. Si puedes decirme qué géneros retienen más y qué duración funciona mejor, puedo tomar decisiones de 50K EUR con confianza en lugar de con el estómago."

## La situación actual -- el vacío de datos

Esto es lo que existe HOY en StreamVibe en términos de datos e infraestructura analítica:

  • PostgreSQL 15 en AWS RDS (db.t3.medium): tablas users, subscriptions, content y playbacks
  • La tabla playbacks tiene ~3,8M filas (sólo las últimas 3 semanas) con user_id, content_id, started_at, ended_at, watch_seconds
  • Tabla content con ~1200 títulos: título, tipo, género, duración, año
  • Tabla users con ~20K filas: registro, plan, país, dispositivo preferido
  • Los pagos NO están en la base: viven en la pasarela (Stripe). Lo que hay es una exportación periódica en CSV. Descubrir que una fuente clave está fuera de la base operacional es de lo primero que encuentra un ingeniero de datos al llegar
  • El coste de licencia está en dos sitios que no coinciden: una columna license_cost_eur en content, que Daniel rellena a veces, y un Excel que lleva Sofía a mano. El Excel tiene packs (una fila puede cubrir varios títulos), los nombres no siempre casan con el catálogo y hay costes en blanco. Decidir cuál manda, cruzarlos y ver cuánto catálogo queda sin coste es parte de tu trabajo
  • Un CSV de gasto de marketing semanal por canal, que te hará falta si alguien pregunta por el coste de adquisición
  • Logs del reproductor en CloudWatch (formato libre, no estructurado)
  • Google Analytics básico en la web (pageviews, no métricas de producto)
  • Metabase instalado apuntando DIRECTAMENTE a PostgreSQL de producción
  • Un Google Sheet de Sofía con "sensaciones" sobre qué contenido funciona
  • CERO event tracking estructurado del comportamiento en la app
  • CERO data lake o data warehouse separado de producción
  • CERO pipelines ETL automatizados
  • CERO métricas de producto calculadas (no saben el DAU real, ni retention, ni churn)
  • CERO documentación de modelo de datos

La buena noticia: hay datos. La tabla playbacks acumula unos 3,8 millones de reproducciones. La mala: sólo se conservan las de las últimas 3 semanas. El proceso de limpieza que Daniel montó para que la base no creciera sin control borra lo más antiguo, así que el histórico anterior ya no está. No es ideal -- además le faltan campos como dispositivo por sesión, no tiene session_id, y los timestamps tienen zona horaria inconsistente -- pero es ALGO. Y algo es infinitamente mejor que nada cuando tienes 5 días.

Tres semanas cambian lo que puedes prometer, y conviene decirlo antes de que alguien lo pida. Con 21 días se calcula perfectamente el DAU, el engagement por título y la retención a 1, 3 y 7 días desde la primera reproducción de cada usuario. Lo que NO se puede calcular es retención a 30 días ni cohortes mensuales: no hay histórico donde mirar. Esa frase se la tienes que decir a Marta hoy, no el viernes por la mañana.

Sobre lo de "zona horaria inconsistente", que no es lo que parece: las fechas son TIMESTAMPTZ, y un TIMESTAMPTZ de PostgreSQL no guarda ninguna zona horaria -- guarda un instante absoluto y convierte al escribir y al leer usando la zona de la sesión. El problema real es más sutil y más dañino: si la aplicación insertó una fecha sin indicar el desfase (un '2024-10-08 20:14:03' a secas), PostgreSQL le aplicó la zona que tuviera esa conexión. Si los servidores, los procesos de fondo y el portátil desde el que alguien hizo una carga no tenían la misma configuración, hay filas desplazadas unas respecto a otras -- y no se nota, porque la columna promete que todo está bien. Se detecta mirando si el reparto de reproducciones por hora del día tiene sentido para una audiencia española (si el pico sale a las 3 de la madrugada, algo está corrido). Y antes de calcular nada por día, decide en qué zona horaria empieza un día para ti y escríbelo: día UTC, día de Madrid o día local de cada usuario dan tres cifras de DAU distintas. Ojo con la tercera: la tabla users tiene país, pero un país no es una zona horaria.

En TaskFlow aprendiste que "los datos perfectos no existen". En FreshMart aprendiste que "datos sucios con contexto de negocio son más valiosos que datos limpios sin contexto". Aquí aplicas ambas lecciones: la tabla playbacks no es perfecta ni tiene todo el histórico, pero tiene 3 semanas de comportamiento real de usuarios. Con eso puedes calcular tendencias, cohortes cortas y engagement. Lo que le falta (dispositivo, session_id, calidad de streaming) se puede añadir DESPUÉS del viernes -- pero sólo empieza a acumularse desde el día en que lo instrumentes, así que decidirlo hoy vale más que decidirlo el jueves.

## El modelo de datos actual de producción

1-- Esquema simplificado de la BD operacional de StreamVibe
2-- (creado por Daniel en los primeros meses, sin pensar en analytics)
3
4CREATE TABLE users (
5 user_id SERIAL PRIMARY KEY,
6 email VARCHAR(255) UNIQUE NOT NULL,
7 username VARCHAR(50),
8 plan VARCHAR(10) DEFAULT 'free', -- 'free' o 'premium'
9 country VARCHAR(5) DEFAULT 'ES',
10 registered_at TIMESTAMPTZ DEFAULT NOW(),
11 last_login_at TIMESTAMPTZ,
12 device_preference VARCHAR(20) -- 'mobile', 'desktop', 'smart_tv', 'tablet'
13);
14
15CREATE TABLE content (
16 content_id SERIAL PRIMARY KEY,
17 title VARCHAR(500) NOT NULL,
18 content_type VARCHAR(20), -- 'movie', 'documentary', 'short', 'series_episode'
19 genre VARCHAR(50), -- 'drama', 'comedy', 'documentary', 'thriller', 'scifi'
20 duration_seconds INTEGER NOT NULL,
21 release_year INTEGER,
22 director VARCHAR(200),
23 rating_imdb NUMERIC(3,1),
24 license_cost_eur NUMERIC(10,2),
25 added_at TIMESTAMPTZ DEFAULT NOW(),
26 is_premium BOOLEAN DEFAULT FALSE
27);
28
29CREATE TABLE playbacks (
30 playback_id SERIAL PRIMARY KEY,
31 user_id INTEGER REFERENCES users(user_id),
32 content_id INTEGER REFERENCES content(content_id),
33 started_at TIMESTAMPTZ NOT NULL,
34 ended_at TIMESTAMPTZ,
35 watch_seconds INTEGER DEFAULT 0,
36 completed BOOLEAN DEFAULT FALSE,
37 created_at TIMESTAMPTZ DEFAULT NOW()
38 -- SIN session_id, SIN device, SIN quality, SIN posición de abandono
39);
40
41CREATE TABLE subscriptions (
42 sub_id SERIAL PRIMARY KEY,
43 user_id INTEGER REFERENCES users(user_id),
44 plan VARCHAR(10) NOT NULL,
45 started_at TIMESTAMPTZ NOT NULL,
46 ended_at TIMESTAMPTZ, -- NULL si sigue activa
47 monthly_price_eur NUMERIC(5,2) DEFAULT 7.99
48);
49
50-- NO hay índices analíticos. Daniel los quitó porque "ralentizaban los INSERTs".
51-- NO hay particiones. La tabla playbacks es un bloque monolítico de 3M filas.

El esquema de producción: funcional para la app, inútil para analytics sin trabajó previo.

Nota cómo Daniel diseñó las tablas pensando en la aplicación, no en analytics. No hay índices en started_at (porque la app busca por user_id). No hay tabla de sesiones (la app no las necesita). No hay flag de dispositivo por reproducción (la app lo infiere del user-agent al momento). Todo esto es normal y esperado -- un CTO que construye solo no piensa en analytics porque tiene prioridades más urgentes (que la app funcione). Tu trabajó no es juzgarlo; es construir encima de lo que hay.

## Lo que te llevas de tu carrera -- 7 empresas de experiencia

  • FreshMart: Los datos siempre empiezan sucios. El negocio necesita respuestas rápidas, no excusas técnicas.
  • TaskFlow: Eventos de producto, cohortes, métricas SaaS (DAU, MAU, retention, churn). La base de todo.
  • AdPulse: Reconciliar múltiples fuentes. Presentar a stakeholders no técnicos con confianza y claridad.
  • NeoBank: Arquitectura robusta, documentar decisiones (ADRs), gestionar incidentes en producción con calma.
  • FlashRoute: Real-time, streaming, presión de escala. Ownership completo de un dominio de datos.
  • MediTrack: Gobernanza, privacidad, audit trails. Comunicar restricciones técnicas a boards ejecutivos.
  • SmartFactory: Liderar diseño técnico, evaluar trade-offs, pensar a 6-12 meses vista, presentar al VP.

Todo eso converge aquí. No necesitas que nadie te diga qué hacer. Sabes identificar qué métricas importan (TaskFlow). Sabes diseñar arquitectura con presupuesto limitado (NeoBank). Sabes implementar pipelines end-to-end (FlashRoute). Sabes comunicar con negocio (AdPulse). Sabes documentar decisiones (SmartFactory). Sabes priorizar con un deadline real e inamovible (todos).

## Tu plan para la semana

  1. 01.Lunes (Día 1): Definir estrategia de datos completa. Qué medir, cómo, con qué. Diseñar la arquitectura dentro del techo de 15 EUR/mes que pone Daniel. Presentar propuesta.
  2. 02.Martes (Día 2): Implementar pipeline ETL. Extraer de PostgreSQL (réplica), transformar a Parquet, cargar en S3. Configurar DuckDB como motor analítico.
  3. 03.Miércoles mañana (Día 3): Calcular métricas de producto: DAU, WAU, MAU, retention por cohorte, engagement score, completion rate.
  4. 04.Miércoles tarde (Día 3): Modelo analítico de contenido para Sofía. Qué géneros retienen, qué duraciones funcionan, ranking de ROI por título.
  5. 05.Jueves (Día 4): Dashboard para inversores en Metabase. Sistema de recomendaciones básico (collaborative filtering).
  6. 06.Viernes (Día 5): A/B testing framework. Preparar la presentación a Meridian Capital con Marta.

El viernes es INAMOVIBLE. Meridian Capital no reprograma. Si no hay dashboard con métricas reales a las 11:00 del viernes, StreamVibe pierde la ronda de inversión. Y si pierde la ronda, cierra en 8 meses. La presión es real.

Bienvenido a StreamVibe. No hay legacy. No hay deuda técnica de datos (porque no hay datos). No hay decisiones previas que respetar. El lienzo está completamente en blanco. Es el proyecto que todo senior data engineer sueña con tener: construir desde cero, con autonomía total y un deadline que te obliga a ser pragmático. Vamos.

## Lo primero que se hace al llegar: mirar qué hay

Antes de diseñar nada, se mira. No es desconfianza: es que nadie -- ni Daniel, ni Marta -- sabe qué hay de verdad en esas tablas, porque nadie ha mirado nunca con ojos de analítica. Una auditoría inicial contesta a cuatro preguntas, en este orden. ¿Cuánto hay? Filas por tabla, rango de fechas, cuánto entra cada día; esto decide el tamaño de todo lo que vas a construir después. ¿Desde cuándo? La fecha más antigua manda sobre lo que puedes prometer: si sólo hay tres semanas, no hay cohortes mensuales, y eso hay que decirlo antes de que alguien lo pida. ¿Qué está roto? Nulos donde no debería haberlos, duplicados, filas que apuntan a cosas que ya no existen, números imposibles. ¿Y qué falta? Lo que no está es tan importante como lo que está.

En esa última pregunta hay una distinción que vale dinero. Aquí falta el identificador de sesión, falta el dispositivo por reproducción y falta la posición en la que alguien abandonó un vídeo -- y las tres no son iguales. La sesión se puede RECONSTRUIR agrupando las reproducciones de un usuario separadas por menos de treinta minutos y dándoles un identificador inventado, que es lo que se hace siempre. El dispositivo quizá se pueda sacar de los registros del reproductor. La posición de abandono no hay manera: nadie la guardó nunca. Distinguir lo reconstruible de lo perdido es lo primero, porque lo perdido sólo se puede empezar a guardar desde hoy -- y por eso decidirlo el lunes vale más que decidirlo el jueves.

Una regla que te ahorra disgustos: la auditoría se escribe y se guarda. Dentro de dos meses, cuando alguien discuta una cifra, el documento que dice qué había el primer día es lo que zanja la discusión. Y otra sobre las comprobaciones de calidad: el esquema te dice lo que DEBERÍA cumplirse, y conviene comprobar que se cumple de verdad -- sobre todo cuando los datos llegan por un fichero o por una carga, porque ahí las restricciones de la tabla no se aplican. Pero no te quedes sólo en contar nulos: es la comprobación más fácil y casi nunca la que encuentra el problema.

## Qué es un "usuario activo", y por qué hay que escribirlo

"Activo" no es una propiedad del usuario: es una definición que alguien elige. Tres definiciones razonables de la misma palabra, sobre los mismos datos. ENTRÓ: tiene un acceso en los últimos 30 días; es la más fácil de calcular y la más generosa, porque incluye a quien abrió la app, miró la portada y se fue. CONSUMIÓ: tiene al menos una reproducción en los últimos 30 días; es la que mide el producto. CONSUMIÓ DE VERDAD: tiene al menos una reproducción de más de cinco minutos; es la que distingue ver de curiosear. Las tres son defendibles y dan números distintos, normalmente en ese orden de mayor a menor.

Y cuando NO salen en ese orden, has encontrado algo: si hay más gente que reproduce que gente que "entró", es que el último acceso sólo se apunta cuando alguien se identifica a mano, y la mitad de tus usuarios tienen la sesión guardada y no vuelven a pasar por ahí. Eso, por sí solo, ya es un hallazgo de la auditoría. Lo que no es defendible es no decir cuál estás usando, porque la siguiente persona que recalcule la cifra usará otra y los dos números aparecerán en la misma presentación. La regla práctica: elige la definición que se parezca a lo que tu negocio cobra. StreamVibe cobra por ver contenido, así que la buena es la segunda o la tercera, no la primera. Escríbela en una línea, ponla al lado de la cifra en el dashboard, y no la cambies a mitad de trimestre -- si la cambias, recalcula también el pasado, o tendrás una subida que no existió.

Estos son los datos crudos que existen en StreamVibe cuando llegas: catálogo, usuarios, suscripciones, pagos, eventos y el Excel de costes de licencias (con los problemas típicos de un dato hecho a mano). Descárgalos desde el apartado de datasets del caso. Ojo con dos cosas: el fichero de reproducciones es un EXTRACTO, no la tabla entera -- trae todas las reproducciones de 1.500 usuarios, no una muestra de filas al azar, y eso es lo que permite calcular retención sobre él. Y los pagos no salen de la base de datos, salen de una exportación de la pasarela. Tú diseñas el modelo desde cero.

volver a la visión del caso y descargar los datasets

Regístrate para guardar tu progreso.