# DEV-GUIDE.md Guía de desarrollo y convenciones del proyecto. ## 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) ```bash 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:** ```bash 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:** ```bash source ~/.secrets/credentials.env curl -u "$GITEA_ADMIN_USER:$GITEA_ADMIN_PASS" "$GITEA_URL/api/v1/user/repos" ``` **K3s (SSH):** ```bash 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) ```bash # 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) ```bash 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): ```bash # 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 ```bash # 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/` ```bash 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` ```bash 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:** 1. Checkout código 2. Sync a K3s master vía rsync 3. Build imágenes en paralelo (API + Frontend) 4. Import a K3s containerd 5. Apply Kustomize manifests 6. Rolling restart deployments 7. Health checks 8. Rollback automático si falla **Secretos requeridos en Gitea:** - `K3S_SSH_KEY`: Clave SSH privada - `K3S_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 ```graphql # 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 ```graphql # ========== 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) ```graphql # 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 ```csharp 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 `/login` - `adminGuard`: Requiere role Admin → redirige a `/dashboard` - `guestGuard`: Solo no autenticados → redirige a `/dashboard` ### Variables de Entorno JWT ```bash 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 ```csharp // 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 ```typescript // 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 ```csharp // 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 `/health` cada 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:** ```bash # 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 1. **Identificar** qué documentos afecta el cambio 2. **Actualizar** archivos MD con la nueva información 3. **Modificar** diagramas PUML si aplica 4. **Validar** que los PUML compilan sin errores 5. **Regenerar** PNG y SVG de diagramas modificados 6. **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`: 1. ✅ Actualizar `DI-002-modelo-dominio.md` (agregar campo) 2. ✅ Actualizar `02-domain-model.puml` (diagrama de clases) 3. ✅ Actualizar `DI-004-esquema-graphql.md` (si se expone en API) 4. ✅ Actualizar `AN-001-requisitos-funcionales.md` (si es nuevo requisito) 5. ✅ Actualizar `ENTREGABLES.md` (resumen de funcionalidades) --- ## Ciclo OODA 1. **OBSERVAR:** ¿Qué se solicita? ¿Qué existe? 2. **ORIENTAR:** ¿Riesgos? ¿Restricciones? 3. **DECIDIR:** Si hay duda, investigar primero 4. **ACTUAR:** Ejecutar con precisión, validar > **Regla de oro:** "Mide dos veces, corta una"