← Inicio / Proyectos / LMS

Un LMS construido como si fuera para una empresa.

NestJS + Angular + Postgres + MinIO, con CI/CD a dos ambientes en mi propio servidor. El producto es la excusa; el proceso es el punto.

119
tests en 3 capas
19
pull requests mergeados
2
ambientes en paralelo
6
agentes de IA con reglas
Capítulo 01 · Por qué lo construí

Un reto: nada de atajos

El LMS no nació de un cliente ni de una necesidad de negocio. Lo diseñé como un reto personal: construir una aplicación desde cero como se hace en un equipo profesional, aunque el equipo fuera casi solo yo.

Elegí un LMS porque conozco el dominio desde la docencia: cursos, tareas, entregas y notas. Así podía enfocarme encómo construirlo y no tanto en qué construir.

La regla fue simple: nada de atajos. Si en una empresa un cambio pasa por un Pull Request, aquí también, aunque yo mismo lo revise. Si allá hay tests que bloquean el merge, aquí también.

Las reglas que me impuse

Capítulo 02 · Arquitectura

Cómo se conectan las piezas

Angular servido por Nginx, una API NestJS que concentra la autorización, Postgres nativo y MinIO para archivos.

Navegador
usuario
Nginx
sirve la SPA de Angular · proxy de /api
NestJS API
JWT · roles + propiedad en el service
PostgreSQL 16
nativo · Prisma ORM
MinIO (S3)
adjuntos · URLs prefirmadas
Capítulo 03 · CI/CD

La cadena de ensamblaje

Cada PR entra como un paquete y avanza por estaciones. Si una falla, el PR no se puede mergear. Prueba los botones.

PR

Haz click en una estación para ver qué valida.

CI · 3 jobs en paralelo: Backend (NestJS) · Frontend (Angular) · E2E (Playwright)
merge → develop
Stack dev · :8080 · lms_dev
merge → main
Stack prod · :80 · lms_prod
  1. CD:
  2. detectar ambiente
  3. sincronizar
  4. migrar BD
  5. MinIO
  6. build y levantar
Capítulo 04 · Testing

La pirámide viva

Muchos tests rápidos abajo, pocos y caros arriba. Elige un módulo para ver su pirámide real.

5E2E
25Integración (Postgres real)
89Unitarios (backend + frontend)
Módulo
Todo el proyecto
119 tests en total

Fuente: conteo manual de it()/test() en el repo LMS · 2026-10-07

Capítulo 05 · GitFlow

El metro de ramas

Los últimos PRs reales del proyecto. Cada rama sale de develop y vuelve por Pull Request; main recibe releases.

Historial de PRs del LMS dibujado como líneas de metromaindevelopramas#12 · feat/courses-frontend · feat(courses): CRUD de cursos en frontend con inscripción#12#13 · docs/acceso-db-test · docs(db): guía de acceso a la base de test#13#14 · chore/dev-backend-loopback · chore(docker): publicar backend de dev solo en loopback#14#15 · feat/frontend-navigation · feat(frontend): agregar navegación compartida#15#16 · release 1.0release 1.0 · #16#17 · chore/docker-test-db · chore(docker): base de test descartable para integración y E2E#17#35 · docs/convencion-prs-por-capa · docs: convención de PRs por capa para historias#35#51 · feat/frontend-home · feat(frontend): mejorar inicio con accesos por rol#51
  1. #12feat/courses-frontendfeat(courses): CRUD de cursos en frontend con inscripción
  2. #13docs/acceso-db-testdocs(db): guía de acceso a la base de test
  3. #14chore/dev-backend-loopbackchore(docker): publicar backend de dev solo en loopback
  4. #15feat/frontend-navigationfeat(frontend): agregar navegación compartida
  5. #17chore/docker-test-dbchore(docker): base de test descartable para integración y E2E
  6. #35docs/convencion-prs-por-capadocs: convención de PRs por capa para historias
  7. #51feat/frontend-homefeat(frontend): mejorar inicio con accesos por rol
feat(db)feat(backend)feat(frontend) · Closes #issue
Capítulo 06 · Infraestructura

El homelab por dentro

Un solo servidor, dos ambientes aislados por base de datos y por bucket. Los ambientes viven en gitflow.cl (prod) y dev.gitflow.cl (dev) detrás de Cloudflare Tunnel + Access: solo el equipo de desarrollo puede entrar.

Techo · Cloudflare Tunnel + Access · activo
Docker · prod
frontend · Nginx :80backend · NestJS
Docker · dev
frontend · Nginx :8080backend · NestJS
Compartido
MinIO · bucket dev / bucket prod · red lms-shared
Postgres 16
lms_devlms_prod
Cimientos · Ubuntu Server + runner self-hosted de GitHub Actions
Capítulo 07 · Claude Code

El equipo de agentes

Uso Claude Code con agentes especializados por capa. Cada uno tiene límites claros, y ninguno hace commit ni push por su cuenta.

database
✓ schema y migraciones
✗ services, frontend
backend
✓ apps/backend
✗ frontend, docker, CI
frontend
✓ apps/frontend
✗ backend, docker
Yo decidoIA propone → yo reviso → commit → PR → CI
devops
✓ docker/ y workflows
✗ no ejecuta comandos
reviewer
✓ revisa todo el repo
✗ solo lectura
server-auditor
✓ diagnostica el servidor
✗ solo lectura
Capítulo 08 · Problemas reales

Lo que se rompió y cómo lo arreglé

Docker no llegaba a Postgres

no pg_hba.conf entry for host "172.x.x.x"

Causa: un contenedor que le habla a la IP LAN de su propio host no hace hairpin NAT: Postgres lo ve llegar con la IP interna del bridge de Docker, no con la de la LAN.

Fix: una regla en pg_hba.conf para el rango de las redes bridge de Docker.

[OTRO PROBLEMA REAL]
Capítulo 09 · Equipo

Quién participó

JC
Javier Choque
autor · arquitectura, backend, frontend e infraestructura
YB
Yomara Bernal
colaboración · navegación compartida del frontend y tests E2E
Capítulo 10 · Roadmap

Hacia dónde va

Base ya construida: autenticación, cursos y adjuntos. Lo que sigue está planificado como épicas e historias en GitHub; abre una épica para ver sus historias.

E1Organizaciones y tenencia2 historiasplanificada
  1. E1-H1Crear organización con admin inicial#29
  2. E1-H2Aislamiento de datos por organización#30
E2Autenticación y usuarios2 historiasplanificada
  1. E2-H1Iniciar sesión y elegir organización#31
  2. E2-H2Ver y editar mi perfil#32
E3Roles y permisos2 historiasplanificada
  1. E3-H1Asignar rol dentro de la organización#33
  2. E3-H2Permisos efectivos por rol#34
E4Cursos3 historiasplanificada
  1. E4-H1Crear curso con fechas y visibilidad#36
  2. E4-H2Organizar el curso en secciones#37
  3. E4-H3Categorizar cursos (propuesta)#38
E5Participantes y matriculación3 historiasplanificada
  1. E5-H1Inscripción propia del estudiante#39
  2. E5-H2Docente matricula estudiantes en su curso#40
  3. E5-H3Matrícula masiva por CSV#41
E6Actividades3 historiasplanificada
  1. E6-H1Publicar tarea con fecha límite#42
  2. E6-H2Subir entrega antes del plazo#43
  3. E6-H3Archivo descargable del curso#44
E7Calificaciones3 historiasplanificada
  1. E7-H1Calificar una entrega con nota y comentario#46
  2. E7-H2Ver mis notas por curso#47
  3. E7-H3Definir escala de calificaciones por curso (propuesta)#48
E8Navegación del usuario1 historiaplanificada
  1. E8-H1Ver mis cursos con progreso en la página principal#49
E9Progreso y finalización1 historiaplanificada
  1. E9-H1Ver mi progreso en un curso (propuesta)#50
E10Subdominios por organización0 historiasplanificada

Historias por definir.

E11Calendario, noticias, avisos y notificaciones0 historiasplanificada

Historias por definir.

INFRAPublicar MinIO en files.gitflow.cl para URLs prefirmadas fuera de la LAN#45