From 8a69ac615fce1abfc65a3a73d22f12de4835bbdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Eduardo=20Garc=C3=ADa=20M=C3=A1rquez?= Date: Thu, 8 Jan 2026 16:48:56 -0500 Subject: [PATCH] docs: add credentials section and update namespace to academia - Document ~/.secrets/credentials.env usage - Add auto-login examples for Gitea, API, and K3s - Update namespace from student-enrollment to academia - Remove CLAUDE.md from .gitignore to track in repo Co-Authored-By: Claude Opus 4.5 --- .gitignore | 1 - CLAUDE.md | 911 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 911 insertions(+), 1 deletion(-) create mode 100644 CLAUDE.md diff --git a/.gitignore b/.gitignore index e1eb868..c8aff19 100644 --- a/.gitignore +++ b/.gitignore @@ -135,7 +135,6 @@ desktop.ini .cache/ tmp/ temp/ -CLAUDE.md # ═══════════════════════════════════════════════════════════════ # Data / Runtime diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..fa1926b --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,911 @@ +# 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 + +**Archivo de credenciales:** `~/.secrets/credentials.env` + +### Uso Obligatorio + +Antes de cualquier operación que requiera autenticación (Gitea, K3s, SSH, etc.): + +```bash +source ~/.secrets/credentials.env +``` + +### Variables Disponibles + +| Variable | Uso | +|----------|-----| +| `GITEA_URL` | URL del servidor Gitea | +| `GITEA_ADMIN_USER` | Usuario Gitea | +| `GITEA_ADMIN_PASS` | Contraseña Gitea | +| `K8S_MASTER_HOST` | Host del master K3s | +| `K8S_SUDO_PASS` | Password sudo en K3s | + +### Login Automático desde Terminal + +**Git (Gitea):** +```bash +source ~/.secrets/credentials.env +git remote set-url gitea "https://${GITEA_ADMIN_USER}:${GITEA_ADMIN_PASS}@devops.ingeniumcodex.com/andresgarcia0313/academia.git" +``` + +**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" +``` + +> **IMPORTANTE:** Siempre intentar autenticación automática usando estas variables. NO preguntar al usuario por credenciales si ya están en el archivo. + +--- + +## 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 + +--- + +## 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"