Skip to content

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: encryptedValue de 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:

PortalRuta baseGuardLayout
Estudiante/dashboard, /learn/*ProtectedRouteAppLayout
Creator Studio/creator/*CreatorGuardCreatorLayout
Administración/admin/*AdminGuardAdminLayout
Corporativo/corporate/*CorporateGuardCorporateLayout

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}Layout con sidebar configurable
  • ⚠️ El ProfileCard y UserMenu son compartidos entre CreatorLayout y AppLayout

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)
  • GuideBlock es el átomo editable (markdown, code, quiz-embed, video)
  • GuideVersion congela una snapshotJson completa 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
  • AuditLogsModule global — un solo import en AppModule
  • ✅ Panel SUPER_ADMIN en /admin/audit-logs con filtros y export CSV
  • ⚠️ Incompatible con @Res() sin passthrough: true — si un controller usa @Res(), debe cambiarse a @Res({ passthrough: true }) + return valor
  • ⚠️ Nueva AuditAction → requiere añadirla al enum en schema.prisma + pnpm prisma db push