Decisiones Arquitectónicas (ADRs)
Los ADRs documentan el contexto, la decisión y las consecuencias de cada elección arquitectónica significativa. Se numeran secuencialmente y son inmutables una vez adoptados.
ADR-001 — Monorepo con Turborepo + pnpm workspaces
Section titled “ADR-001 — Monorepo con Turborepo + pnpm workspaces”Estado: ✅ Implementado
Fecha: Abril 2026
Contexto:
El proyecto tiene un frontend React, un backend NestJS y tests E2E Playwright. Necesitamos compartir tipos TypeScript y schemas Zod entre los tres sin duplicar código.
Decisión:
Un único repositorio con Turborepo v2 + pnpm v9 workspaces, con la estructura apps/ y packages/.
Consecuencias:
- ✅ Tipos y schemas compartidos sin publicar paquetes npm
- ✅ Build cache incremental con Turborepo
- ✅ Un solo PR cubre cambios en api + web + shared-types
- ⚠️ Repositorio más grande — hay que mantener los pipelines de CI ajustados para no correr builds innecesarios
ADR-010 — AI Backend como microservicio propio
Section titled “ADR-010 — AI Backend como microservicio propio”Estado: ✅ Implementado
Fecha: Abril 2026
Contexto:
El sistema necesita capacidades de IA: tutor conversacional, motor adaptativo, generación de contenido, evaluación de respuestas abiertas y búsqueda semántica. La solución original era delegar todo a NappAI Core vía webhooks externos, pero esto genera dependencia de latencia y acoplamiento.
Decisión:
Construir apps/ai-backend como microservicio NestJS propio que:
- Gestiona sus propias credenciales de LLM cifradas (AES-256-GCM)
- Enruta cada caso de uso al proveedor LLM óptimo (Anthropic / OpenAI / Gemini)
- Mantiene una base de datos vectorial Qdrant para búsqueda semántica
- Expone métricas de uso y coste en tiempo real
- Implementa circuit breaker con fallback automático entre proveedores
Puerto interno: http://localhost:3001
Autenticación inter-servicio: AIBACKEND_API_KEY (InternalAuthGuard)
Consecuencias:
- ✅ Latencia controlada (red interna, no internet)
- ✅ Costes visibles y gestionables desde el panel admin
- ✅ Sin dependencia de disponibilidad de NappAI Core para funcionalidades IA
- ⚠️ Requiere gestionar credenciales de 3 proveedores LLM
- ⚠️ Operación de Qdrant (vector DB) añade complejidad de infraestructura
- ⚠️ Seguridad adicional:
encryptedValuede credenciales NUNCA se devuelve en ningún GET
Ver sección completa de AI Backend para detalles de implementación.
ADR-011 — Multi-portal frontend (Creator Studio)
Section titled “ADR-011 — Multi-portal frontend (Creator Studio)”Estado: ✅ Implementado
Fecha: Abril 2026
Contexto:
El frontend originalmente tenía dos portales: el de estudiante (/dashboard) y el de administrador (/admin/*). Los instructores aprobados (CONTENT_ADMIN) necesitaban un espacio propio para gestionar su contenido, distinto del panel de super-administración.
Decisión:
Dividir el frontend en cuatro portales independientes, cada uno con su propio layout, guard y sidebar:
| Portal | Ruta base | Guard | Layout |
|---|---|---|---|
| Estudiante | /dashboard, /learn/* | ProtectedRoute | AppLayout |
| Creator Studio | /creator/* | CreatorGuard | CreatorLayout |
| Administración | /admin/* | AdminGuard | AdminLayout |
| Corporativo | /corporate/* | CorporateGuard | CorporateLayout |
Consecuencias:
- ✅ Separación clara de responsabilidades en UX y routing
- ✅ Guards por rol evitan acceso accidental entre portales
- ✅ Los editores de contenido viven en
/creator/editor/*(no en/admin/) - ⚠️ Cuatro layouts que mantener — seguir el patrón
{Name}Layoutcon sidebar configurable - ⚠️ El
ProfileCardyUserMenuson compartidos entreCreatorLayoutyAppLayout
ADR-012 — Guide Quality Module con versionado por snapshot
Section titled “ADR-012 — Guide Quality Module con versionado por snapshot”Estado: ✅ Implementado
Fecha: Abril 2026
Contexto:
Los creadores necesitan producir guías de referencia (análogas a documentación técnica) con secciones, bloques de contenido y control de calidad. El contenido aprobado debe ser inmutable para que los estudiantes siempre vean versiones estables.
Decisión:
Implementar el GuideModule con tres modelos en cascada:
Guide → GuideSection[] → GuideBlock[] ↓ GuideVersion (snapshot inmutable)GuideBlockes el átomo editable (markdown, code, quiz-embed, video)GuideVersioncongela unasnapshotJsoncompleta del guide en el momento de aprobación- Los estudiantes siempre leen
GuideVersion.snapshotJson, nunca los bloques vivos
Alternativas descartadas:
- Versionado por diff: más eficiente en espacio, pero complejo de reconstruir y de leer por agentes IA
- Un solo modelo plano: sin estructura de secciones, inviable para guías de más de 3000 palabras
Consecuencias:
- ✅ Los contenidos aprobados son inmutables — certificados válidos indefinidamente
- ✅ Los borradores de creador no afectan a los estudiantes
- ✅ El snapshot completo es fácil de indexar en Qdrant para búsqueda semántica
- ⚠️ Duplicación de datos (bloques vivos + snapshot) — asumible dado el tamaño de las guías
ADR-013 — Audit Logs con Decorador + Interceptor Global
Section titled “ADR-013 — Audit Logs con Decorador + Interceptor Global”Estado: ✅ Implementado
Fecha: 2026-04-17
Contexto:
El proyecto necesitaba trazabilidad de acciones administrativas y de negocio (crear paths, aprobar instructores, modificar usuarios, etc.) sin contaminar la lógica de los servicios con código de auditoría. El riesgo de omitir el logging por olvido era alto si se dependía de llamadas manuales en cada service.
Decisión:
Decorador @AuditLog() declarado en los controllers + interceptor NestJS global (AuditLogInterceptor) que lo procesa de forma transparente tras cada request exitosa. El módulo AuditLogsModule es @Global() — no requiere importación explícita en cada módulo de feature.
@Patch(':id')@AuditLog({ action: 'PATH_UPDATED', category: 'CONTENT_MANAGEMENT', severity: 'INFO', entityType: 'PATH', entityIdParam: 'id', captureBody: true,})async update(@Param('id') id: string, @Body() dto: UpdatePathDto) { ... }Alternativas descartadas:
- Llamadas manuales en cada service: propenso a omisiones, acopla lógica de negocio con auditoría
- Middleware HTTP: no tiene acceso al resultado de la operación ni a los metadatos del decorador
Consecuencias:
- ✅ Zero-touch para la lógica de negocio — el interceptor escribe el log sin modificar nada
- ✅
AuditLogsModuleglobal — un solo import enAppModule - ✅ Panel SUPER_ADMIN en
/admin/audit-logscon filtros y export CSV - ⚠️ Incompatible con
@Res()sinpassthrough: true— si un controller usa@Res(), debe cambiarse a@Res({ passthrough: true })+return valor - ⚠️ Nueva
AuditAction→ requiere añadirla al enum enschema.prisma+pnpm prisma db push