35 KiB
CLAUDE.md
Este archivo proporciona orientación a Claude Code (claude.ai/code) para trabajar con el código de este repositorio.
Contexto
Prueba técnica para posición de Desarrollador Master .NET/Angular en Inter Rapidísimo.
- Cargo: Desarrollador Senior .NET/Angular
- Empresa: Inter Rapidísimo (logística y mensajería)
- Ubicación: Bogotá, Av. El Dorado (presencial)
Credenciales y Autenticación
CRÍTICO: Antes de cualquier operación que requiera autenticación, revisar
~/.secrets/credentials.env
Archivo de credenciales: ~/.secrets/credentials.env
Uso Obligatorio (SIEMPRE)
source ~/.secrets/credentials.env
Variables Disponibles
| Variable | Uso |
|---|---|
GIT_USER |
Usuario Git para Gitea (andresgarcia0313) |
GIT_PASS |
Contraseña Git para Gitea |
GIT_REMOTE_URL |
URL base con credenciales embebidas |
GITEA_URL |
URL del servidor Gitea |
GITEA_ADMIN_USER |
Usuario API Gitea |
GITEA_ADMIN_PASS |
Contraseña API Gitea |
K8S_MASTER_HOST |
Host del master K3s |
K8S_SUDO_PASS |
Password sudo en K3s |
Login Automático (OBLIGATORIO)
Git Push/Pull a Gitea:
source ~/.secrets/credentials.env
# Configurar remote con credenciales
git remote set-url gitea "${GIT_REMOTE_URL}/andresgarcia0313/academia.git"
# O directamente
git push gitea main
API Gitea:
source ~/.secrets/credentials.env
curl -u "$GITEA_ADMIN_USER:$GITEA_ADMIN_PASS" "$GITEA_URL/api/v1/user/repos"
K3s (SSH):
source ~/.secrets/credentials.env
ssh $K8S_MASTER_HOST "echo $K8S_SUDO_PASS | sudo -S kubectl get pods -A"
REGLA: SIEMPRE autenticarse automáticamente usando
~/.secrets/credentials.env. NUNCA preguntar al usuario por credenciales. Si falla la autenticación, revisar primero el archivo de credenciales.
Resumen del Proyecto
Aplicación web de registro de estudiantes - Sistema CRUD con reglas de negocio específicas.
Stack Tecnológico
| Capa | Tecnología |
|---|---|
| Backend | .NET 10 (C#) |
| API | GraphQL (HotChocolate) |
| Frontend | Angular 21 (TypeScript) |
| Base de datos | SQL Server / SQLite (dev) |
| ORM | Entity Framework Core |
| Autenticación | JWT (HMAC-SHA256) + PBKDF2 |
| Validación | FluentValidation |
| Mapping | Mapster |
| UI | Angular Material |
| GraphQL Client | Apollo Angular |
| CI/CD | Gitea Actions + K3s |
Requisitos de Negocio
Funcionalidad Principal
- CRUD completo para inscripción de estudiantes
- Estudiantes se inscriben en programa basado en créditos
- Visualización de inscripciones de otros estudiantes (solo nombres en clases compartidas)
Reglas del Dominio (CRÍTICAS)
- 10 asignaturas en total, cada una vale 3 créditos
- 5 profesores, cada uno imparte exactamente 2 asignaturas
- Estudiantes pueden seleccionar máximo 3 asignaturas (= 9 créditos)
- Restricción clave: Un estudiante NO puede tener clases con el mismo profesor
Arquitectura
Clean Architecture + CQRS (4 Capas)
┌─────────────────────────────────────────────────────────────────┐
│ HOST (Composition Root) │
│ Program.cs / DI / Configuration │
└──────────────────────────────┬──────────────────────────────────┘
│
┌──────────────────────────────▼──────────────────────────────────┐
│ ADAPTERS │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ DRIVING (Primary) │ DRIVEN (Secondary) │ │
│ │ ───────────────── │ ────────────────── │ │
│ │ GraphQL API ◄──Angular SPA │ Persistence (EF Core) │ │
│ │ (HotChocolate) │ DataLoaders │ │
│ │ Middleware │ External Services │ │
│ └─────────────────────────────────────────────────────────┘ │
└──────────────────────────────┬──────────────────────────────────┘
│
┌──────────────────────────────▼──────────────────────────────────┐
│ APPLICATION │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Commands / Queries / Handlers / DTOs / Validators │ │
│ └─────────────────────────────────────────────────────────┘ │
└──────────────────────────────┬──────────────────────────────────┘
│
┌──────────────────────────────▼──────────────────────────────────┐
│ DOMAIN │
│ ┌───────────┐ ┌───────────┐ ┌───────────┐ ┌───────────────┐ │
│ │ Entities │ │ Value │ │ Domain │ │ Ports │ │
│ │ │ │ Objects │ │ Services │ │ (Contracts) │ │
│ └───────────┘ └───────────┘ └───────────┘ └───────────────┘ │
└─────────────────────────────────────────────────────────────────┘
Regla de Dependencia (INVIOLABLE)
Host → Adapters → Application → Domain
│ ▲
└── implementa Ports ────┘
• Domain define Ports (interfaces)
• Adapters/Driven implementa Ports
• Domain NO depende de NADA externo
Nomenclatura Ports & Adapters
| Término | Significado | Ubicación |
|---|---|---|
| Ports | Contratos/Interfaces | Domain/Ports/ |
| Driving Adapters | Entrada (quien llama) | Adapters/Driving/ |
| Driven Adapters | Salida (a quien se llama) | Adapters/Driven/ |
Diagramas UML
Ubicación: docs/architecture/diagrams/
| Diagrama | Archivo | Propósito |
|---|---|---|
| Casos de Uso | 01-use-cases.puml |
Funcionalidades del estudiante |
| Modelo de Dominio | 02-domain-model.puml |
Entidades y Value Objects |
| Secuencia | 03-sequence-enrollment.puml |
Flujo de inscripción |
| Componentes | 04-components.puml |
Arquitectura del sistema |
| Entidad-Relación | 05-entity-relationship.puml |
Modelo de base de datos |
| Estados | 06-state-enrollment.puml |
Estados de inscripción |
| Despliegue | 07-deployment.puml |
Infraestructura Docker |
| C4 Contexto | 08-c4-context.puml |
Vista de alto nivel |
Regenerar SVG: cat archivo.puml | plantuml -tsvg -pipe > archivo.svg
Estructura del Proyecto
/
├── src/
│ │
│ │ ═══════════════ BACKEND (.NET 10) ═══════════════
│ │
│ ├── backend/
│ │ │
│ │ ├── Domain/ # ENTITIES (Núcleo puro)
│ │ │ ├── Entities/ # Student, Subject, Professor, Enrollment, User
│ │ │ ├── ValueObjects/ # Email
│ │ │ ├── ReadModels/ # ClassmateInfo
│ │ │ ├── Exceptions/ # DomainException
│ │ │ ├── Ports/
│ │ │ │ └── Repositories/ # IStudentRepository, IUserRepository, etc.
│ │ │ └── Services/ # EnrollmentDomainService
│ │ │
│ │ ├── Application/ # USE CASES (CQRS)
│ │ │ ├── Common/
│ │ │ │ ├── Behaviors/ # ValidationBehavior
│ │ │ │ └── ValidationPatterns.cs # Regex patterns centralizados
│ │ │ ├── Auth/ # Autenticación
│ │ │ │ ├── Commands/ # LoginCommand, RegisterCommand, ResetPasswordCommand
│ │ │ │ ├── DTOs/ # AuthDtos (LoginRequest, RegisterRequest, etc.)
│ │ │ │ ├── IJwtService.cs # Interfaz JWT
│ │ │ │ ├── IPasswordService.cs # Interfaz hashing
│ │ │ │ └── JwtOptions.cs # Configuración JWT
│ │ │ ├── Students/
│ │ │ │ ├── Commands/ # CreateStudent, UpdateStudent, DeleteStudent
│ │ │ │ ├── Queries/ # GetStudents, GetStudentById, GetStudentsPaged
│ │ │ │ └── DTOs/ # StudentDto
│ │ │ ├── Subjects/
│ │ │ │ ├── Queries/ # GetSubjects, GetAvailableSubjects
│ │ │ │ └── DTOs/ # SubjectDto
│ │ │ ├── Professors/
│ │ │ │ ├── Queries/ # GetProfessors
│ │ │ │ └── DTOs/ # ProfessorDto
│ │ │ ├── Enrollments/
│ │ │ │ ├── Commands/ # EnrollStudent, UnenrollStudent
│ │ │ │ ├── Queries/ # GetClassmates
│ │ │ │ └── DTOs/ # EnrollmentDtos
│ │ │ └── DependencyInjection.cs
│ │ │
│ │ ├── Adapters/ # INTERFACE ADAPTERS
│ │ │ │
│ │ │ ├── Driving/ # Entrada (Primary)
│ │ │ │ └── Api/ # GraphQL (HotChocolate)
│ │ │ │ ├── Types/
│ │ │ │ │ ├── Auth/ # AuthMutations, AuthQueries
│ │ │ │ │ ├── Students/ # StudentType
│ │ │ │ │ ├── Subjects/ # SubjectType
│ │ │ │ │ ├── Professors/ # ProfessorType
│ │ │ │ │ ├── Enrollments/ # EnrollmentType
│ │ │ │ │ ├── Query.cs
│ │ │ │ │ └── Mutation.cs
│ │ │ │ ├── Middleware/ # GraphQLErrorFilter
│ │ │ │ └── Extensions/ # GraphQLExtensions
│ │ │ │
│ │ │ └── Driven/ # Salida (Secondary)
│ │ │ └── Persistence/ # EF Core
│ │ │ ├── Context/ # AppDbContext
│ │ │ ├── Configurations/# Fluent API configs (incl. UserConfiguration)
│ │ │ ├── Repositories/ # Student, Subject, Professor, Enrollment, User, UnitOfWork
│ │ │ ├── Services/ # JwtService, PasswordService
│ │ │ ├── DataLoaders/ # StudentById, SubjectById, ProfessorById
│ │ │ ├── Migrations/ # InitialCreate, SeedData, AddUsersTable
│ │ │ ├── Seeding/ # DataSeeder
│ │ │ ├── CompiledQueries.cs
│ │ │ └── DependencyInjection.cs
│ │ │
│ │ └── Host/ # COMPOSITION ROOT
│ │ └── Program.cs
│ │
│ │ ═══════════════ FRONTEND (Angular 21) ═══════════════
│ │
│ └── frontend/
│ ├── src/
│ │ ├── app/
│ │ │ ├── core/ # Singleton services
│ │ │ │ ├── services/ # AuthService, StudentService, EnrollmentService, NotificationService
│ │ │ │ ├── guards/ # auth.guard.ts (authGuard, adminGuard, guestGuard)
│ │ │ │ ├── interceptors/ # ErrorInterceptor, AuthInterceptor
│ │ │ │ ├── models/ # student.model.ts
│ │ │ │ └── graphql/
│ │ │ │ ├── queries/ # students.queries.ts
│ │ │ │ ├── mutations/ # students.mutations.ts, auth.mutations.ts
│ │ │ │ └── generated/ # types.ts (Apollo codegen)
│ │ │ ├── shared/ # Componentes reutilizables
│ │ │ │ ├── components/ui/ # ConfirmDialog, EmptyState, LoadingSpinner
│ │ │ │ └── pipes/ # credits.pipe, initials.pipe
│ │ │ └── features/ # Módulos por funcionalidad
│ │ │ ├── auth/ # Autenticación
│ │ │ │ └── pages/ # Login, Register, ResetPassword
│ │ │ ├── dashboard/ # Dashboard estudiante
│ │ │ │ └── pages/ # StudentDashboard
│ │ │ ├── students/
│ │ │ │ └── pages/ # StudentList, StudentForm
│ │ │ ├── enrollment/
│ │ │ │ └── pages/ # EnrollmentPage
│ │ │ └── classmates/
│ │ │ └── pages/ # ClassmatesPage
│ │ ├── environments/ # environment.ts, environment.prod.ts
│ │ └── main.ts
│ └── e2e/ # Playwright E2E tests
│ ├── mocks/ # graphql.mock.ts
│ ├── student-crud.spec.ts
│ ├── enrollment.spec.ts
│ └── classmates.spec.ts
│
├── tests/ # Backend tests
│ ├── Common/ # Builders compartidos
│ │ └── Builders/ # StudentBuilder, SubjectBuilder, etc.
│ ├── Domain.Tests/
│ │ ├── Entities/ # StudentTests
│ │ ├── ValueObjects/ # EmailTests
│ │ └── Services/ # EnrollmentDomainServiceTests
│ ├── Application.Tests/
│ │ ├── Students/ # StudentCommandsTests, StudentQueriesTests
│ │ ├── Enrollments/ # EnrollStudent, Unenroll, GetClassmates Tests
│ │ ├── Subjects/ # SubjectQueriesTests
│ │ ├── Professors/ # ProfessorQueriesTests
│ │ └── Validators/ # ValidatorTests
│ └── Integration.Tests/ # EnrollmentFlowTests
│
├── scripts/
│ └── dev-start.sh # Script desarrollo local (SQLite)
│
├── docs/
│ ├── architecture/
│ │ └── decisions/ # ADR-001 a ADR-004
│ ├── entregables/
│ │ ├── 01-analisis/ # Requisitos, reglas, historias, riesgos
│ │ ├── 02-diseno/ # Arquitectura, modelo dominio, DB, GraphQL, UI
│ │ └── 03-configuracion/ # Repositorio, .NET, Angular, DB, env, calidad
│ ├── qa/ # Reportes de QA
│ │ ├── QA-REPORT-*-MANUAL-TESTS.md
│ │ └── QA-REPORT-*-REGRESSION-TESTS.md
│ ├── CODE_REVIEW_CHECKLIST.md
│ ├── DEFECTOS_QA.md # Defectos encontrados
│ ├── DEPLOYMENT.md
│ ├── ENTREGABLES.md
│ ├── OWASP_CHECKLIST.md
│ ├── PLAN_ACTIVIDADES.md
│ ├── RECOMMENDATIONS.md # Recomendaciones de mejora
│ └── Prueba Técnica.md
│
├── database/
│ ├── stored-procedures/
│ └── views/
│
├── deploy/
│ ├── docker/
│ │ ├── Dockerfile.api # Multi-stage Alpine + .NET 10
│ │ ├── Dockerfile.frontend # Multi-stage Node + Nginx
│ │ ├── docker-compose.yml
│ │ ├── nginx.conf # Proxy + SPA routing
│ │ └── start.sh # Script de despliegue Docker
│ ├── k3s/ # Kubernetes manifests
│ │ ├── api.yaml # Deployment + Service API
│ │ ├── frontend.yaml # Deployment + Service Frontend
│ │ ├── sqlserver.yaml # StatefulSet SQL Server
│ │ ├── ingress.yaml # Traefik + HTTPS
│ │ ├── hpa.yaml # Horizontal Pod Autoscaler
│ │ ├── kustomization.yaml # Kustomize overlay
│ │ └── deploy.sh # Script orquestación K3s
│ └── scripts/
│ └── deploy-k3s.sh # Deploy remoto
│
└── .gitea/
└── workflows/
└── deploy.yaml # CI/CD Pipeline
Comandos de Desarrollo
Backend (.NET)
# Restaurar dependencias
dotnet restore
# Build completo
dotnet build
# Ejecutar GraphQL API (puerto 5000/5001)
dotnet run --project src/backend/Host
# Tests
dotnet test # Todos
dotnet test --filter "Category=Unit" # Solo unit
dotnet test --filter "Category=Integration" # Solo integration
# Migraciones EF Core
dotnet ef migrations add InitialCreate \
-p src/backend/Adapters/Driven/Persistence \
-s src/backend/Host
dotnet ef database update \
-p src/backend/Adapters/Driven/Persistence \
-s src/backend/Host
# Watch mode (hot reload)
dotnet watch run --project src/backend/Host
# GraphQL Playground disponible en: https://localhost:5001/graphql
Frontend (Angular)
cd src/frontend
# Instalar dependencias
npm install
# Servidor desarrollo (puerto 4200)
ng serve
# Build producción
ng build --configuration production
# Tests unitarios
ng test
ng test --watch=false --code-coverage # Con coverage
# E2E tests
npx playwright test
# Lint
ng lint
# Generar componente
ng g c features/students/components/my-component --standalone
Desarrollo Local (Sin Docker)
Script que levanta backend + frontend con SQLite (sin necesidad de SQL Server):
# Iniciar todo
./scripts/dev-start.sh start
# Ver estado
./scripts/dev-start.sh status
# Detener
./scripts/dev-start.sh stop
# Reiniciar
./scripts/dev-start.sh restart
Características:
- Backend usa SQLite en
./data/dev.db - No requiere Docker ni SQL Server
- Frontend en puerto 4200
- Backend en puerto 5000
- Hot reload habilitado
- PIDs guardados para cleanup
Docker
# Levantar todo el stack
docker-compose up -d
# Solo base de datos
docker-compose up -d sqlserver
# Rebuild
docker-compose up -d --build
# Logs
docker-compose logs -f api
Docker Compose Optimizado (Producción)
Ubicación: deploy/docker/
cd deploy/docker
./start.sh # Despliegue con un solo comando
Recursos asignados (optimizado para 12 cores / 15GB RAM):
| Servicio | CPU | RAM | Características |
|---|---|---|---|
| SQL Server | 2 cores | 2.5 GB | Volumen persistente |
| API .NET | 4 cores | 1.5 GB | Server GC, ReadyToRun, Health check |
| Frontend | 2 cores | 256 MB | Nginx + Brotli, cache agresivo |
URLs después del despliegue:
- Frontend: http://localhost
- API GraphQL: http://localhost:5000/graphql
- Health Check: http://localhost:5000/health
Despliegue K3s (Producción)
Dominio: https://academia.ingeniumcodex.com
cd deploy/k3s
./deploy.sh all # Build + transfer + deploy completo
./deploy.sh status # Ver estado
./deploy.sh clean # Eliminar namespace
URLs de producción:
- Frontend: https://academia.ingeniumcodex.com
- API GraphQL: https://academia.ingeniumcodex.com/graphql
- Health Check: https://academia.ingeniumcodex.com/health
Autenticación:
- Login: https://academia.ingeniumcodex.com/login
- Registro estudiantes: https://academia.ingeniumcodex.com/register
- Reset password: https://academia.ingeniumcodex.com/reset-password
- Dashboard estudiante: https://academia.ingeniumcodex.com/dashboard
Namespace: academia
CI/CD Pipeline (Gitea Actions)
Ubicación: .gitea/workflows/deploy.yaml
Trigger: Push a main
Flujo automático:
- Checkout código
- Sync a K3s master vía rsync
- Build imágenes en paralelo (API + Frontend)
- Import a K3s containerd
- Apply Kustomize manifests
- Rolling restart deployments
- Health checks
- Rollback automático si falla
Secretos requeridos en Gitea:
K3S_SSH_KEY: Clave SSH privadaK3S_SUDO_PASS: Password sudo en K3s
Tiempo de despliegue: ~3-5 minutos
GraphQL API
Endpoint: https://localhost:5001/graphql
Playground: https://localhost:5001/graphql (Banana Cake Pop)
Queries
# Obtener usuario autenticado (requiere JWT)
query Me {
me {
id
username
role
studentId
studentName
}
}
# Obtener todos los estudiantes
query GetStudents {
students {
id
name
email
totalCredits
enrollments {
subject {
name
credits
professor { name }
}
}
}
}
# Obtener estudiante por ID
query GetStudentById($id: Int!) {
student(id: $id) {
id
name
email
enrollments { subject { name } }
}
}
# Obtener todas las materias
query GetSubjects {
subjects {
id
name
credits
professor { id name }
}
}
# Obtener materias disponibles para un estudiante (filtra restricciones)
query GetAvailableSubjects($studentId: Int!) {
availableSubjects(studentId: $studentId) {
id
name
credits
professor { name }
isAvailable # false si ya tiene materia del mismo profesor
unavailableReason # "Ya tienes una materia con este profesor"
}
}
# Ver compañeros de clase por materia
query GetClassmates($studentId: Int!) {
classmates(studentId: $studentId) {
subjectName
students { name }
}
}
# Obtener todos los profesores
query GetProfessors {
professors {
id
name
subjects { id name }
}
}
Mutations
# ========== AUTENTICACIÓN ==========
# Login
mutation Login($input: LoginRequestInput!) {
login(input: $input) {
success
token
error
user {
id
username
role
studentId
studentName
}
}
}
# Registro (crea User + Student opcionalmente)
mutation Register($input: RegisterRequestInput!) {
register(input: $input) {
success
token
error
recoveryCode # Solo se muestra UNA vez
user { id username role studentId studentName }
}
}
# Recuperar contraseña
mutation ResetPassword($input: ResetPasswordRequestInput!) {
resetPassword(input: $input) {
success
error
}
}
# ========== ESTUDIANTES ==========
# Crear estudiante
mutation CreateStudent($input: CreateStudentInput!) {
createStudent(input: $input) {
student { id name email }
errors
}
}
# Actualizar estudiante
mutation UpdateStudent($id: Int!, $input: UpdateStudentInput!) {
updateStudent(id: $id, input: $input) {
student { id name email }
errors
}
}
# Eliminar estudiante
mutation DeleteStudent($id: Int!) {
deleteStudent(id: $id) {
success
errors
}
}
# Inscribir estudiante en materia
mutation EnrollStudent($input: EnrollStudentInput!) {
enrollStudent(input: $input) {
enrollment {
id
student { name }
subject { name }
}
errors # ["Máximo 3 materias", "Ya tienes materia con este profesor"]
}
}
# Cancelar inscripción
mutation UnenrollStudent($enrollmentId: Int!) {
unenrollStudent(enrollmentId: $enrollmentId) {
success
errors
}
}
Subscriptions (Opcional - Tiempo Real)
# Notificación cuando un estudiante se inscribe
subscription OnStudentEnrolled {
studentEnrolled {
student { name }
subject { name }
}
}
Autenticación
Sistema JWT
Algoritmo: HMAC-SHA256 Expiración: Configurable (default 60 min) Hashing passwords: PBKDF2-SHA256 con 100,000 iteraciones
Flujos de Autenticación
| Flujo | Endpoint | Descripción |
|---|---|---|
| Login | mutation login |
Valida credenciales, retorna JWT + UserInfo |
| Registro | mutation register |
Crea User + Student (opcional), genera Recovery Code |
| Reset Password | mutation resetPassword |
Valida recovery code, actualiza password |
Entidad User
public class User {
public int Id;
public string Username; // Almacenado en minúsculas
public string PasswordHash; // PBKDF2-SHA256
public string RecoveryCodeHash; // Para reset password
public string Role; // "Admin" | "Student"
public int? StudentId; // FK a Student (nullable)
public DateTime CreatedAt;
public DateTime? LastLoginAt;
}
Roles y Guards
| Rol | Acceso |
|---|---|
| Admin | CRUD completo de estudiantes, ver todo |
| Student | Dashboard propio, inscripciones propias, ver compañeros |
Guards Angular:
authGuard: Requiere autenticación → redirige a/loginadminGuard: Requiere role Admin → redirige a/dashboardguestGuard: Solo no autenticados → redirige a/dashboard
Variables de Entorno JWT
JWT_SECRET_KEY # Secret para firmar tokens (REQUERIDO, mín. 32 chars)
JWT_ISSUER # Default: "StudentEnrollmentApi"
JWT_AUDIENCE # Default: "StudentEnrollmentApp"
JWT_EXPIRATION_MINUTES # Default: 60
Seguridad Implementada
- PBKDF2-SHA256 con 100,000 iteraciones (salt 16 bytes)
- Fixed-time comparison para recovery codes (timing attacks)
- Rate limiting: 30 mutations/min, 100 queries/min
- JWT validation: Issuer, Audience, Lifetime, SigningKey
- Recovery code: 12 caracteres, mostrado solo una vez
- Serilog: Filtra tokens y passwords de logs
Validaciones de Negocio
Ubicación en el Código
| Validación | Capa | Ruta |
|---|---|---|
| Email válido | Domain | Domain/ValueObjects/Email.cs |
| Max 3 materias | Domain | Domain/Services/EnrollmentDomainService.cs |
| No repetir profesor | Domain | Domain/Services/EnrollmentDomainService.cs |
| Datos request válidos | Application | Application/{Feature}/Commands/*Validator.cs |
| Estudiante existe | Application | Application/{Feature}/Commands/*Handler.cs |
| Repos implementados | Adapters | Adapters/Driven/Persistence/Repositories/ |
Reglas Críticas
// Domain/Services/EnrollmentDomainService.cs
public void ValidateEnrollment(Student student, Subject newSubject)
{
// Regla 1: Max 3 materias
if (student.Enrollments.Count >= 3)
throw new MaxEnrollmentsExceededException(student.Id);
// Regla 2: No repetir profesor
var professorIds = student.Enrollments.Select(e => e.Subject.ProfessorId);
if (professorIds.Contains(newSubject.ProfessorId))
throw new SameProfessorConstraintException(student.Id, newSubject.ProfessorId);
}
Manejo de Errores
Códigos de Error del Backend
| Código | Mensaje Usuario | Causa |
|---|---|---|
MAX_ENROLLMENTS |
Has alcanzado el límite máximo de 3 materias | Estudiante intenta inscribir 4ta materia |
SAME_PROFESSOR |
Ya tienes una materia con este profesor | Violación restricción profesor único |
STUDENT_NOT_FOUND |
El estudiante no existe en el sistema | ID inválido o eliminado |
SUBJECT_NOT_FOUND |
La materia seleccionada no existe | ID materia inválido |
DUPLICATE_ENROLLMENT |
Ya estás inscrito en esta materia | Inscripción duplicada |
VALIDATION_ERROR |
Los datos ingresados no son válidos | FluentValidation falló |
NETWORK_ERROR |
No se pudo conectar con el servidor | API no disponible |
INVALID_CREDENTIALS |
Usuario o contraseña incorrectos | Login fallido |
USERNAME_EXISTS |
El nombre de usuario ya está en uso | Registro duplicado |
INVALID_RECOVERY_CODE |
Código de recuperación inválido | Reset password fallido |
UNAUTHORIZED |
No tienes permiso para esta acción | JWT inválido o expirado |
Frontend: Servicio de Errores
// core/services/error-handler.service.ts
// Uso en componentes:
this.errorHandler.handle(error, 'Contexto.accion');
// Características:
// - Mensajes amigables para usuarios
// - Logging detallado en consola (solo dev)
// - Traducción automática de códigos GraphQL
Backend: Filtro de Errores
// Adapters/Driving/Api/Middleware/GraphQLErrorFilter.cs
// Transforma excepciones de dominio a errores GraphQL
// con código y mensaje estructurado
Monitoreo de Conectividad
El sistema verifica la conexión con el servidor cada 5 segundos:
| Componente | Archivo | Función |
|---|---|---|
| Backend | Host/Program.cs |
Endpoint /health con estado DB |
| Servicio | core/services/connectivity.service.ts |
Polling y estado reactivo |
| Overlay | shared/components/ui/connectivity-overlay/ |
UI bloqueante |
Comportamiento:
- Verifica
/healthcada 5 segundos - Requiere 2 fallos consecutivos para mostrar overlay (evita falsos positivos)
- Overlay bloquea toda interacción hasta restaurar conexión
- Muestra instrucciones para solucionar el problema
Convenciones
.NET
- Async/Await: Todos los métodos I/O
- Records: Para DTOs inmutables
- Nullable: Habilitado globalmente
- Naming: PascalCase clases/métodos, camelCase variables
Angular
- Standalone: Todos los componentes
- Signals: Estado reactivo
- Lazy Loading: Por feature
- OnPush: Change detection strategy
Commits
feat(students): add create student form
fix(enrollment): validate professor constraint
refactor(api): extract validation middleware
test(domain): add enrollment policy tests
docs(readme): update setup instructions
Restricciones Críticas
NUNCA
- Modificar sin leer archivo completo primero
- Archivos > 100 líneas (refactorizar)
- Hardcodear connection strings
- Lógica de negocio en Controllers
- Exponer entidades de dominio en API
SIEMPRE
- DTOs para entrada/salida
- Tests para reglas de negocio
- Validar antes de persistir
- Async en operaciones I/O
Actualización de Documentación (OBLIGATORIO)
REGLA: Después de implementar cambios significativos, SIEMPRE actualizar la documentación correspondiente.
Cuándo Actualizar
| Tipo de Cambio | Documentos a Actualizar |
|---|---|
| Nueva entidad/campo en dominio | DI-002-modelo-dominio.md, 02-domain-model.puml |
| Nuevo endpoint GraphQL | DI-004-esquema-graphql.md |
| Nueva funcionalidad | AN-001-requisitos-funcionales.md, AN-003-historias-usuario.md |
| Cambios en arquitectura | DI-001-arquitectura-backend.md, 04-components.puml |
| Nuevo flujo de usuario | 03-sequence-*.puml, historia de usuario correspondiente |
| Cambios en BD | DI-003-diseno-base-datos.md, 05-entity-relationship.puml |
| Nuevas rutas/páginas | DI-005-arquitectura-frontend.md, DI-006-componentes-ui.md |
| Cambios en despliegue | DEPLOYMENT.md, 07-deployment.puml |
| Cualquier cambio significativo | ENTREGABLES.md, README.md |
Archivos de Documentación
docs/
├── entregables/
│ ├── 01-analisis/ # Requisitos, historias, reglas
│ ├── 02-diseno/ # Arquitectura, modelo, esquemas
│ └── 03-configuracion/ # Setup, variables, calidad
├── architecture/
│ ├── decisions/ # ADRs (decisiones arquitectónicas)
│ └── diagrams/ # Diagramas PUML + SVG
├── qa/ # Reportes de QA
├── ENTREGABLES.md # Resumen ejecutivo
├── DEPLOYMENT.md # Guía de despliegue
└── README.md (raíz) # Documentación principal
Diagramas PlantUML
Ubicación: docs/architecture/diagrams/
| Diagrama | Archivo | Actualizar cuando... |
|---|---|---|
| Casos de Uso | 01-use-cases.puml |
Nuevas funcionalidades de usuario |
| Modelo Dominio | 02-domain-model.puml |
Cambios en entidades/relaciones |
| Secuencia | 03-sequence-*.puml |
Nuevos flujos o cambios en existentes |
| Componentes | 04-components.puml |
Cambios en arquitectura |
| E-R | 05-entity-relationship.puml |
Cambios en base de datos |
| Estados | 06-state-*.puml |
Nuevos estados o transiciones |
| Despliegue | 07-deployment.puml |
Cambios en infraestructura |
| C4 | 08-c4-context.puml |
Cambios en contexto del sistema |
Validar y regenerar PUML después de modificar:
# Validar que compila (sin errores)
cat docs/architecture/diagrams/archivo.puml | plantuml -tpng -pipe > /dev/null && echo "OK" || echo "ERROR"
# Regenerar PNG y SVG
cd docs/architecture/diagrams
cat archivo.puml | plantuml -tpng -pipe > archivo.png
cat archivo.puml | plantuml -tsvg -pipe > archivo.svg
# Validar TODOS los diagramas
for f in docs/architecture/diagrams/*.puml; do
echo -n "Validando $f... "
cat "$f" | plantuml -tpng -pipe > /dev/null 2>&1 && echo "OK" || echo "ERROR"
done
Proceso de Actualización
- Identificar qué documentos afecta el cambio
- Actualizar archivos MD con la nueva información
- Modificar diagramas PUML si aplica
- Validar que los PUML compilan sin errores
- Regenerar PNG y SVG de diagramas modificados
- Verificar consistencia entre documentos
IMPORTANTE: SIEMPRE validar que los archivos PUML compilan antes de hacer commit. Un diagrama con errores de sintaxis rompe la documentación.
Ejemplo
Si se agrega un nuevo campo ActivationCode a la entidad Student:
- ✅ Actualizar
DI-002-modelo-dominio.md(agregar campo) - ✅ Actualizar
02-domain-model.puml(diagrama de clases) - ✅ Actualizar
DI-004-esquema-graphql.md(si se expone en API) - ✅ Actualizar
AN-001-requisitos-funcionales.md(si es nuevo requisito) - ✅ Actualizar
ENTREGABLES.md(resumen de funcionalidades)
Ciclo OODA
- OBSERVAR: ¿Qué se solicita? ¿Qué existe?
- ORIENTAR: ¿Riesgos? ¿Restricciones?
- DECIDIR: Si hay duda, investigar primero
- ACTUAR: Ejecutar con precisión, validar
Regla de oro: "Mide dos veces, corta una"