🎯 Qué construyes: una plataforma donde instructores publican cursos con vídeo y los alumnos los compran y consumen. Frontend real, pagos reales (en modo prueba), despliegue real.
Prerrequisitos: capítulos 12-22, apéndices L y P. Duración: 4-6 semanas.
🧠 Por qué este proyecto. Es el primero donde el despliegue forma parte del problema, no es un apéndice. Vídeo pesado, pagos que no pueden fallar, contenido que hay que proteger y un frontend que debe ser rápido. Tocas todas las decisiones que hacen difícil un producto real.
Alcance
Dentro: catálogo público, registro, compra, reproducción de vídeo protegida, progreso del alumno, panel del instructor, despliegue con CI/CD.
Fuera (no-objetivos explícitos — capítulo 25): app móvil, chat en vivo, certificados verificables, subtítulos automáticos, sistema de afiliados. Escribe esta lista en tu README: la disciplina de decir que no es parte del ejercicio.
Requisitos funcionales
Público
- Catálogo con búsqueda, filtros por categoría, nivel y precio, y paginación.
- Ficha de curso: temario, instructor, valoraciones, primera lección gratuita de muestra.
- SEO: renderizado en servidor o estático, metadatos Open Graph, sitemap.
Alumno
- Registro/login, incluyendo OAuth con Google (cap. 20).
- Compra con Stripe en modo prueba (apéndice P).
- "Mis cursos", reproductor con seguimiento de progreso (retoma donde lo dejó).
- Marcar lecciones completadas; barra de progreso del curso.
- Valorar y reseñar cursos que haya comprado (solo esos).
Instructor
- Panel: crear curso, secciones y lecciones.
- Subida de vídeo (caso 5 del cap. 26): URL prefirmada, procesado asíncrono, estados
subiendo→procesando→listo. - Publicar/despublicar. Ver ventas e ingresos.
Administración
- Aprobar cursos antes de su publicación.
- Ver métricas globales.
Requisitos no funcionales
| # | Requisito |
|---|---|
| NF1 | El vídeo NO es accesible sin haber comprado — y no basta con esconder la URL |
| NF2 | El pago es idempotente: doble clic o webhook duplicado no cobra dos veces |
| NF3 | La compra se confirma por webhook de Stripe, nunca por la redirección del navegador |
| NF4 | El catálogo carga en < 1,5 s con conexión 4G simulada (Lighthouse ≥ 90) |
| NF5 | Imágenes optimizadas (WebP/AVIF), lazy loading, tamaños responsive |
| NF6 | Accesibilidad: navegable por teclado, contraste AA, reproductor con controles accesibles |
| NF7 | CI: en cada PR corren lint, tests y build; sin verde no se mergea |
| NF8 | CD: merge a main despliega automáticamente |
| NF9 | Secretos en el gestor de secretos del proveedor, nunca en el repositorio |
| NF10 | Healthcheck, logs estructurados y alerta si la tasa de 5xx supera el 1% |
| NF11 | Backups automáticos diarios de la base de datos, con restauración probada |
⚠️ NF1 es el requisito técnicamente interesante del proyecto. Poner el vídeo en una carpeta con un nombre difícil de adivinar no es protección: en cuanto un alumno comparte la URL, se acabó. La solución real son URLs firmadas con caducidad corta (minutos) generadas por tu backend tras comprobar la compra, servidas por el CDN. Si además usas HLS, cada fragmento se firma por separado y la protección es aún más granular.
⚠️ NF3 es el que más gente hace mal. Confirmar la compra cuando el navegador vuelve a
/graciases un agujero: el usuario puede cerrar el navegador antes (paga y no recibe el curso) o navegar directamente a esa URL (recibe el curso sin pagar). La única fuente de verdad es el webhook firmado del proveedor de pagos, verificando la firma. La redirección solo sirve para enseñar un "gracias" bonito.
Arquitectura de referencia
graph TD
U[Usuario] --> CDN[CDN · estáticos y vídeo]
U --> FE[Frontend · Next.js SSR]
FE --> API[API backend]
API --> PG[(PostgreSQL)]
API --> R[(Redis · sesiones y caché)]
API --> Q[(Cola)]
Q --> W[Workers: transcodificar,<br/>emails, miniaturas]
API --> S3[(Object storage)]
W --> S3
S3 --> CDN
ST[Stripe] -->|webhook firmado| API
API --> OBS[Logs · métricas · alertas]Fases
Fase 1 · Esqueleto desplegado (semana 1) Monorepo, Docker Compose local, "hola mundo" de front y back, desplegado en producción el primer día con CI/CD. Parece exagerado; es lo que evita que el despliegue sea un drama en la semana 6.
Fase 2 · Catálogo y cuentas (semana 2) Modelo de datos, cursos, secciones, lecciones. Registro, login, OAuth. Catálogo público con SEO.
Fase 3 · Vídeo (semana 3) Subida con URL prefirmada, cola de procesado, transcodificación, reproductor. La parte más arriesgada: hazla pronto.
Fase 4 · Pagos (semana 4) Stripe Checkout, webhooks con verificación de firma, idempotencia, acceso condicionado a la compra, reembolsos.
Fase 5 · Experiencia (semana 5) Progreso, reseñas, panel del instructor, optimización de rendimiento y accesibilidad.
Fase 6 · Producción (semana 6) Observabilidad, alertas, backups con restauración probada, documentación, ADRs.
Criterios de aceptación
□ La URL pública funciona y un desconocido puede registrarse y comprar (modo prueba de Stripe)
□ Copiar la URL del vídeo y abrirla en incógnito → acceso denegado
□ Completar un pago y cerrar el navegador ANTES de la redirección → el curso aparece igual
□ Reenviar el mismo webhook de Stripe dos veces → un solo acceso, un solo cobro
□ Un alumno no puede reseñar un curso que no ha comprado
□ Lighthouse ≥ 90 en rendimiento y accesibilidad en la página de catálogo
□ Toda la app es navegable solo con teclado
□ Un PR con un test roto NO se puede mergear
□ Merge a main despliega solo, y se puede revertir
□ docker compose up levanta el entorno completo en local
□ Restauraste un backup en una base limpia y la app funcionó
□ No hay ni un secreto en git log -pTrampas conocidas
| Trampa | Por qué duele | Qué hacer |
|---|---|---|
| Subir el vídeo a través de tu API | Bloquea workers durante minutos | URL prefirmada (cap. 26.5) |
| Guardar el progreso en cada segundo de reproducción | Miles de escrituras por alumno | Guardar cada 15-30 s y al pausar/salir |
| Confiar en la redirección de Stripe | Compras perdidas y accesos regalados | Solo el webhook firmado |
| Servir el vídeo desde tu servidor | Ancho de banda carísimo y lento | CDN, siempre |
| Dejar el despliegue para el final | Descubres los problemas sin tiempo | Despliega en la fase 1 |
Contraseñas de Stripe en el .env del repo | Filtración de claves reales | Gestor de secretos + .env.example |
Extensiones
- HLS con calidades adaptativas — transcodificar a varios bitrates y servir un
.m3u8. - Recomendaciones — "porque compraste X", empezando por lo simple (misma categoría) antes de pensar en nada sofisticado.
- Cupones y descuentos — con todas sus trampas: caducidad, uso único, límite global.
- Modo offline (PWA, apéndice N) — descargar lecciones para ver sin conexión.
- Multi-idioma (i18n) en la interfaz y en las fichas de curso.
- Panel de analítica para el instructor — tasa de finalización por lección, para detectar dónde abandonan los alumnos.
Siguiente: 04-diseno-de-sistema.md