diff --git a/CLAUDE.md b/CLAUDE.md index 4cc944d..f9fbcab 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -907,6 +907,96 @@ docs(readme): update setup instructions --- +## 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? diff --git a/README.md b/README.md index 4a7d060..9ed7195 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,8 @@ Sistema web para gestionar inscripciones de estudiantes en materias con restricc - Inscripción/cancelación de materias con validación de reglas - Visualización de compañeros de clase por materia - Interfaz responsive con Angular Material +- **Sistema de autenticación con flujo de activación** +- **Control de acceso por roles (Admin/Student)** ### Calidad y Robustez - **Manejo de errores**: Mensajes amigables para usuarios + logging detallado para desarrolladores @@ -58,7 +60,7 @@ cd Interrapidisimo ### Paso 2: Iniciar SQL Server con Docker ```bash -docker run -e "ACCEPT_EULA=Y" -e "SA_PASSWORD=Your_password123" \ +docker run -e "ACCEPT_EULA=Y" -e "SA_PASSWORD=Asde71.4Asde71.4" \ -p 1433:1433 --name sqlserver -d mcr.microsoft.com/mssql/server:2022-latest ``` @@ -236,7 +238,7 @@ docker restart sqlserver # Conectar con sqlcmd docker exec -it sqlserver /opt/mssql-tools18/bin/sqlcmd \ - -S localhost -U sa -P "Your_password123" -C + -S localhost -U sa -P "Asde71.4Asde71.4" -C ``` ## API GraphQL @@ -327,7 +329,7 @@ docker start sqlserver # Verificar conexión docker exec -it sqlserver /opt/mssql-tools18/bin/sqlcmd \ - -S localhost -U sa -P "Your_password123" -C -Q "SELECT 1" + -S localhost -U sa -P "Asde71.4Asde71.4" -C -Q "SELECT 1" ``` ### Puerto 5000 en uso @@ -367,7 +369,7 @@ Verificar que el password en `appsettings.json` coincida con el del contenedor D ```json { "ConnectionStrings": { - "DefaultConnection": "Server=localhost;Database=StudentEnrollment;User Id=sa;Password=Your_password123;TrustServerCertificate=True" + "DefaultConnection": "Server=localhost;Database=StudentEnrollment;User Id=sa;Password=Asde71.4Asde71.4;TrustServerCertificate=True" } } ``` @@ -484,8 +486,41 @@ Ver [DEPLOYMENT.md](docs/DEPLOYMENT.md) para instrucciones detalladas. - [Despliegue](docs/architecture/diagrams/07-deployment.svg) - [C4 Contexto](docs/architecture/diagrams/08-c4-context.svg) -## Seguridad +## Autenticación y Roles +### Flujo de Activación de Estudiantes + +``` +1. Admin crea estudiante → Sistema genera código de activación (12 chars) +2. Admin comparte código/URL con estudiante +3. Estudiante accede a /activate?code=XXXX +4. Estudiante crea credenciales (usuario + contraseña) +5. Sistema genera código de recuperación (mostrado una sola vez) +6. Estudiante inicia sesión → Dashboard personal +``` + +### Roles del Sistema + +| Rol | Permisos | +|-----|----------| +| **Admin** | CRUD estudiantes, ver todo, generar códigos de activación | +| **Student** | Dashboard personal, inscribir materias, ver compañeros | + +### URLs de Autenticación + +| Ruta | Descripción | +|------|-------------| +| `/login` | Inicio de sesión | +| `/activate?code=XXX` | Activación de cuenta | +| `/dashboard` | Dashboard de estudiante | +| `/admin` | Panel de administración | + +### Seguridad + +- **JWT** con HMAC-SHA256, expiración configurable +- **PBKDF2-SHA256** para hashing de contraseñas (100,000 iteraciones) +- **Código de activación:** 12 caracteres, expira en 48 horas +- **Código de recuperación:** 12 caracteres, se muestra solo una vez - Input validation con FluentValidation - Sanitización contra XSS - Security headers (CSP, HSTS, X-Frame-Options) diff --git a/docs/DEPLOYMENT.md b/docs/DEPLOYMENT.md index ac6b2c1..5c8394d 100644 --- a/docs/DEPLOYMENT.md +++ b/docs/DEPLOYMENT.md @@ -185,7 +185,7 @@ volumes: cd deploy/docker # Crear archivo .env -echo "DB_PASSWORD=Your_Str0ng_P@ssword!" > .env +echo "DB_PASSWORD=Asde71.4Asde71.4" > .env # Build e iniciar docker-compose up -d --build @@ -254,7 +254,7 @@ Script que levanta backend + frontend con **SQLite** (sin necesidad de SQL Serve ```yaml # Variables de entorno en el workflow K3S_HOST: 100.67.198.92 # IP del master (hp62a) -NAMESPACE: student-enrollment +NAMESPACE: academia DOMAIN: academia.ingeniumcodex.com ``` @@ -366,20 +366,25 @@ dotnet ef database update ``` deploy/k3s/ -├── namespace.yaml # Namespace dedicado +├── namespace.yaml # Namespace: academia ├── secrets.yaml # Credenciales BD ├── configmap.yaml # Configuración ├── sqlserver.yaml # Base de datos ├── api.yaml # Backend GraphQL ├── frontend.yaml # Frontend Angular -├── ingress.yaml # Traefik ingress -├── ingress-tls.yaml # TLS con cert-manager -├── hpa.yaml # Autoscaling -├── networkpolicy.yaml # Seguridad de red +├── ingress.yaml # Traefik IngressRoute + TLS +├── networkpolicy.yaml # Seguridad de red (incluido en kustomize) +├── hpa.yaml # Autoscaling (opcional, no incluido) ├── kustomization.yaml # Kustomize config └── deploy.sh # Script de despliegue ``` +**Nota:** `networkpolicy.yaml` está incluido en `kustomization.yaml` y aplica las siguientes reglas: +- Default deny: Bloquea todo tráfico entrante por defecto +- Frontend: Solo acepta tráfico desde Ingress +- API: Solo acepta tráfico desde Frontend e Ingress +- SQL Server: Solo acepta conexiones desde API + ### Requisitos k3s - k3s instalado y funcionando @@ -399,7 +404,7 @@ cd deploy/k3s kubectl apply -k . # Verificar estado -kubectl get all -n student-enrollment +kubectl get all -n academia ``` ### Comandos del Script @@ -418,9 +423,9 @@ kubectl get all -n student-enrollment ```bash # Editar secrets antes de desplegar kubectl create secret generic student-secrets \ - --namespace=student-enrollment \ - --from-literal=db-password='TuPasswordSeguro123!' \ - --from-literal=db-connection-string='Server=sqlserver;Database=StudentEnrollment;User Id=sa;Password=TuPasswordSeguro123!;TrustServerCertificate=True' \ + --namespace=academia \ + --from-literal=db-password='Asde71.4Asde71.4' \ + --from-literal=db-connection-string='Server=sqlserver;Database=StudentEnrollment;User Id=sa;Password=Asde71.4Asde71.4;TrustServerCertificate=True' \ --dry-run=client -o yaml > secrets.yaml ``` @@ -447,61 +452,61 @@ kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/ # 2. Editar ingress-tls.yaml con tu dominio y email # 3. Aplicar ingress con TLS -kubectl apply -f ingress-tls.yaml -n student-enrollment +kubectl apply -f ingress-tls.yaml -n academia ``` ### Scaling Manual ```bash # Escalar API -kubectl scale deployment student-api -n student-enrollment --replicas=3 +kubectl scale deployment student-api -n academia --replicas=3 # Escalar Frontend -kubectl scale deployment student-frontend -n student-enrollment --replicas=2 +kubectl scale deployment student-frontend -n academia --replicas=2 ``` ### Monitoreo ```bash # Estado de pods -kubectl get pods -n student-enrollment -w +kubectl get pods -n academia -w # Logs en tiempo real -kubectl logs -n student-enrollment -l app=student-api -f +kubectl logs -n academia -l app=student-api -f # Eventos -kubectl get events -n student-enrollment --sort-by='.lastTimestamp' +kubectl get events -n academia --sort-by='.lastTimestamp' # Recursos -kubectl top pods -n student-enrollment +kubectl top pods -n academia ``` ### Rollback en k3s ```bash # Ver historial de deployments -kubectl rollout history deployment/student-api -n student-enrollment +kubectl rollout history deployment/student-api -n academia # Rollback a versión anterior -kubectl rollout undo deployment/student-api -n student-enrollment +kubectl rollout undo deployment/student-api -n academia # Rollback a revisión específica -kubectl rollout undo deployment/student-api -n student-enrollment --to-revision=2 +kubectl rollout undo deployment/student-api -n academia --to-revision=2 ``` ### Troubleshooting ```bash # Pod no inicia -kubectl describe pod -n student-enrollment +kubectl describe pod -n academia # Conectar a pod -kubectl exec -it -n student-enrollment -- /bin/sh +kubectl exec -it -n academia -- /bin/sh # Verificar conectividad BD -kubectl exec -it -n student-enrollment -- \ +kubectl exec -it -n academia -- \ curl -v telnet://sqlserver:1433 # Verificar ingress -kubectl describe ingress student-ingress -n student-enrollment +kubectl describe ingress student-ingress -n academia ``` diff --git a/docs/ENTREGABLES.md b/docs/ENTREGABLES.md index f37a60e..0da2904 100644 --- a/docs/ENTREGABLES.md +++ b/docs/ENTREGABLES.md @@ -52,6 +52,9 @@ Sistema web completo para gestión de inscripciones de estudiantes con las sigui | 7 | Validación de inscripciones | ✅ | | 8 | UI responsiva | ✅ | | 9 | Manejo de errores | ✅ | +| 10 | Autenticación JWT | ✅ | +| 11 | Flujo de activación de estudiantes | ✅ | +| 12 | Control de acceso por roles (Admin/Student) | ✅ | ### Reglas de Negocio @@ -60,6 +63,14 @@ Sistema web completo para gestión de inscripciones de estudiantes con las sigui - ✅ Validación en Domain Layer (pura, testeable) - ✅ Mensajes de error descriptivos +### Sistema de Autenticación + +- ✅ **JWT** con HMAC-SHA256 +- ✅ **Flujo de Activación:** Admin crea estudiante → Código de activación → Estudiante activa cuenta +- ✅ **Roles:** Admin (gestión completa) / Student (dashboard personal) +- ✅ **Recuperación:** Código de recuperación generado en activación +- ✅ **Seguridad:** PBKDF2-SHA256 para hashing de contraseñas + --- ## Arquitectura @@ -85,6 +96,33 @@ Host → Adapters → Application → Domain | DataLoader | Evitar N+1 en GraphQL | | Specification | Consultas reutilizables | +### Diagramas de Arquitectura + +Todos los diagramas están disponibles en `/docs/architecture/diagrams/` en formatos PNG y SVG. + +| # | Diagrama | Archivo | Descripción | +|---|----------|---------|-------------| +| 1 | **Casos de Uso** | `01-use-cases` | Actores (Estudiante, Admin), funcionalidades del sistema, reglas de negocio | +| 2 | **Modelo de Dominio** | `02-domain-model` | Entidades (User, Student, Professor, Subject, Enrollment), Value Objects, Domain Services | +| 3 | **Secuencia Inscripción** | `03-sequence-enrollment` | Flujo completo de inscripción con JWT, validaciones y persistencia | +| 4 | **Componentes** | `04-components` | Arquitectura Clean Architecture: Frontend Angular 21, Backend .NET 10, GraphQL | +| 5 | **Entidad-Relación** | `05-entity-relationship` | Modelo de base de datos: tablas, relaciones, restricciones | +| 6 | **Estados** | `06-state-enrollment` | Estados de cuenta (activación) e inscripciones (0-9 créditos) | +| 7 | **Despliegue** | `07-deployment` | Infraestructura K3s: Nginx, ASP.NET Core, SQL Server, Traefik Ingress | +| 8 | **C4 Contexto** | `08-c4-context` | Vista de alto nivel: actores, sistema, sistemas externos | + +#### Requisitos de la Prueba Técnica Cubiertos + +| Requisito | Diagrama(s) | +|-----------|-------------| +| CRUD de estudiantes | 01, 04 | +| Programa de créditos | 02, 06 | +| 10 materias, 3 créditos c/u | 02, 05 | +| Máximo 3 materias (9 créditos) | 01, 02, 03, 06 | +| 5 profesores, 2 materias c/u | 02, 05 | +| No repetir profesor | 01, 02, 03, 05 | +| Ver compañeros de clase | 01, 04 | + --- ## Testing @@ -94,24 +132,56 @@ Host → Adapters → Application → Domain | Tipo | Cantidad | Cobertura | |------|----------|-----------| | Domain Tests | 30 | Entidades, Value Objects, Services | -| Application Tests | 66 | Commands, Queries, Validators | +| Application Tests | 98 | Commands, Queries, Validators, **Auth** | | Integration Tests | 5 | GraphQL API completa | | Angular Unit Tests | 24 | Services, Pipes | -| E2E Tests (Playwright) | 20 | Flujos de usuario | -| **Total** | **145** | | +| E2E Tests (Playwright) | 97 | Flujos de usuario completos | +| **Total** | **254** | | + +### Tests E2E por Categoría + +| Categoría | Tests | Descripción | +|-----------|-------|-------------| +| Autenticación | 15 | Login, registro, reset password, logout | +| Control de Acceso | 16 | Roles Admin/Student, guards, protección rutas | +| Reglas de Negocio | 16 | Max 3 materias, mismo profesor, inscribir/cancelar | +| Flujo Activación | 18 | Creación estudiante, código, activación cuenta | +| CRUD Estudiantes | 6 | Crear, listar, validaciones | +| Inscripciones | 7 | Navegar, inscribir, cancelar | +| Compañeros | 7 | Listar, navegar | +| Otros | 12 | Estados UI, edge cases | + +### Tests Backend de Auth (Nuevos) + +| Handler | Tests | Casos Cubiertos | +|---------|-------|-----------------| +| LoginCommand | 6 | Credenciales válidas/inválidas, normalización, lastLogin | +| RegisterCommand | 8 | Registro exitoso, usuario duplicado, validaciones, recovery code | +| ResetPasswordCommand | 8 | Reset válido/inválido, validaciones, hashing | +| ActivateAccountCommand | 10 | Activación, expiración, username duplicado, JWT | ### Ejecutar Tests ```bash -# Backend -dotnet test +# Backend - Todos +dotnet test tests/Application.Tests +dotnet test tests/Domain.Tests +dotnet test tests/Integration.Tests -# Frontend -cd src/frontend -ng test --watch=false +# Backend - Solo Auth +dotnet test tests/Application.Tests --filter "FullyQualifiedName~Auth" -# E2E -npx playwright test +# Frontend Unit Tests +cd src/frontend && ng test --watch=false + +# E2E - Todos +cd src/frontend && npx playwright test + +# E2E - Por categoría +npx playwright test auth.spec.ts +npx playwright test role-access.spec.ts +npx playwright test enrollment-restrictions.spec.ts +npx playwright test activation.spec.ts ``` --- @@ -140,9 +210,24 @@ npx playwright test | Diseño BD | `/docs/entregables/02-diseno/base-datos/` | | Esquema GraphQL | `/docs/entregables/02-diseno/esquema-graphql/` | | ADRs | `/docs/architecture/decisions/` | +| **Diagramas UML** | `/docs/architecture/diagrams/` | | OWASP Checklist | `/docs/OWASP_CHECKLIST.md` | | Manual Despliegue | `/docs/DEPLOYMENT.md` | +### Diagramas Incluidos + +``` +docs/architecture/diagrams/ +├── 01-use-cases.png # Casos de uso +├── 02-domain-model.png # Modelo de dominio +├── 03-sequence-enrollment.png # Secuencia inscripción +├── 04-components.png # Arquitectura componentes +├── 05-entity-relationship.png # Diagrama E-R +├── 06-state-enrollment.png # Estados inscripción +├── 07-deployment.png # Despliegue K3s +└── 08-c4-context.png # Contexto C4 +``` + --- ## Despliegue diff --git a/docs/RECOMMENDATIONS.md b/docs/RECOMMENDATIONS.md index 5aad187..3fad8dd 100644 --- a/docs/RECOMMENDATIONS.md +++ b/docs/RECOMMENDATIONS.md @@ -152,9 +152,10 @@ El sistema está **listo para demostración** y cumple con todos los requisitos - Health check post-deploy #### 2. Kubernetes (k3s) Deployment -- **Namespace:** `student-enrollment` -- **Servicios:** student-api, student-frontend, mssql -- **Ingress:** `students.ingeniumcodex.com` (Traefik) +- **Namespace:** `academia` +- **Servicios:** student-api, student-frontend, sqlserver +- **Ingress:** `academia.ingeniumcodex.com` (Traefik) +- **Seguridad:** NetworkPolicy (default-deny + allow rules) - **TLS:** Configurado con cert-manager #### 3. Optimizaciones de Deployment @@ -174,10 +175,10 @@ El sistema está **listo para demostración** y cumple con todos los requisitos ### URLs de Producción | Servicio | URL | |----------|-----| -| Frontend | https://students.ingeniumcodex.com | -| GraphQL API | https://students.ingeniumcodex.com/graphql | -| Health Check | https://students.ingeniumcodex.com/health | +| Frontend | https://academia.ingeniumcodex.com | +| GraphQL API | https://academia.ingeniumcodex.com/graphql | +| Health Check | https://academia.ingeniumcodex.com/health | ### Repositorio Git -- **URL:** https://devops.ingeniumcodex.com/andresgarcia0313/student-enrollment.git +- **URL:** https://devops.ingeniumcodex.com/andresgarcia0313/academia.git - **CI/CD:** Auto-deploy en push a main diff --git a/docs/entregables/01-analisis/historias-usuario/AN-003-historias-usuario.md b/docs/entregables/01-analisis/historias-usuario/AN-003-historias-usuario.md index 4f2087d..d855e39 100644 --- a/docs/entregables/01-analisis/historias-usuario/AN-003-historias-usuario.md +++ b/docs/entregables/01-analisis/historias-usuario/AN-003-historias-usuario.md @@ -340,21 +340,144 @@ Scenario: Eliminación con confirmación --- +### US-010: Activación de Cuenta de Estudiante + +| Campo | Valor | +|-------|-------| +| **ID** | US-010 | +| **Épica** | EP-001 | +| **Prioridad** | Alta | +| **Story Points** | 8 | +| **Sprint** | 1 | + +**Historia:** +> Como **estudiante nuevo**, +> quiero **activar mi cuenta usando el código proporcionado por el administrador**, +> para **crear mis credenciales y acceder al sistema**. + +**Criterios de Aceptación:** + +```gherkin +Scenario: Validación de código de activación + Given tengo un código de activación válido + When accedo a la URL de activación con el código + Then el sistema valida el código + And veo un mensaje de bienvenida con mi nombre + +Scenario: Creación de credenciales + Given mi código de activación fue validado + When ingreso un nombre de usuario único + And ingreso una contraseña válida (mín. 6 caracteres) + And confirmo la contraseña + And presiono "Activar Cuenta" + Then mi cuenta se activa exitosamente + And veo mi código de recuperación (una sola vez) + And soy redirigido al login + +Scenario: Código de activación expirado + Given mi código de activación expiró (>48 horas) + When accedo a la URL de activación + Then veo el mensaje "Código de activación expirado" + And veo instrucciones para contactar al administrador +``` + +--- + +### US-011: Creación de Estudiante por Administrador + +| Campo | Valor | +|-------|-------| +| **ID** | US-011 | +| **Épica** | EP-001 | +| **Prioridad** | Alta | +| **Story Points** | 5 | +| **Sprint** | 1 | + +**Historia:** +> Como **administrador del sistema**, +> quiero **crear estudiantes y obtener su código de activación**, +> para **permitirles activar sus cuentas de forma segura**. + +**Criterios de Aceptación:** + +```gherkin +Scenario: Crear estudiante con código de activación + Given estoy autenticado como administrador + When navego al panel de gestión de estudiantes + And presiono "Nuevo Estudiante" + And ingreso nombre y email válidos + And presiono "Crear" + Then el estudiante se crea sin credenciales + And veo un modal con el código de activación + And veo la URL de activación completa + And veo la fecha de expiración del código + +Scenario: Copiar código de activación + Given se muestra el modal de activación + When presiono el botón de copiar código + Then el código se copia al portapapeles + And veo confirmación visual +``` + +--- + +### US-012: Control de Acceso por Roles + +| Campo | Valor | +|-------|-------| +| **ID** | US-012 | +| **Épica** | EP-001 | +| **Prioridad** | Alta | +| **Story Points** | 5 | +| **Sprint** | 1 | + +**Historia:** +> Como **sistema**, +> quiero **restringir el acceso según el rol del usuario**, +> para **garantizar que cada usuario solo acceda a funcionalidades autorizadas**. + +**Criterios de Aceptación:** + +```gherkin +Scenario: Estudiante accede a su dashboard + Given estoy autenticado como estudiante + When accedo a /dashboard + Then veo mi información personal + And veo opciones: "Mi Portal", "Mis Materias", "Compañeros" + And NO veo: "Panel Admin", "Gestión Estudiantes" + +Scenario: Estudiante intenta acceder a rutas de admin + Given estoy autenticado como estudiante + When intento navegar a /admin o /students + Then soy redirigido automáticamente a /dashboard + +Scenario: Administrador accede al panel completo + Given estoy autenticado como administrador + When accedo a /admin + Then veo el panel de administración completo + And puedo gestionar todos los estudiantes +``` + +--- + ## 3. Backlog Priorizado | Prioridad | Historia | Story Points | Sprint | |-----------|----------|--------------|--------| | 1 | US-001: Registro de Estudiante | 5 | 1 | -| 2 | US-002: Consulta de Materias | 3 | 1 | -| 3 | US-003: Inscripción en Materia | 8 | 1 | -| 4 | US-004: Cancelación de Inscripción | 3 | 1 | -| 5 | US-005: Materias No Disponibles | 3 | 2 | -| 6 | US-007: Ver Compañeros | 5 | 2 | -| 7 | US-006: Consulta de Estudiantes | 2 | 2 | -| 8 | US-008: Actualizar Datos | 2 | 2 | -| 9 | US-009: Eliminar Cuenta | 2 | 2 | +| 2 | US-011: Creación por Admin | 5 | 1 | +| 3 | US-010: Activación de Cuenta | 8 | 1 | +| 4 | US-012: Control de Acceso | 5 | 1 | +| 5 | US-002: Consulta de Materias | 3 | 1 | +| 6 | US-003: Inscripción en Materia | 8 | 1 | +| 7 | US-004: Cancelación de Inscripción | 3 | 1 | +| 8 | US-005: Materias No Disponibles | 3 | 2 | +| 9 | US-007: Ver Compañeros | 5 | 2 | +| 10 | US-006: Consulta de Estudiantes | 2 | 2 | +| 11 | US-008: Actualizar Datos | 2 | 2 | +| 12 | US-009: Eliminar Cuenta | 2 | 2 | -**Total Story Points:** 33 +**Total Story Points:** 51 --- diff --git a/docs/entregables/01-analisis/requisitos/AN-001-requisitos-funcionales.md b/docs/entregables/01-analisis/requisitos/AN-001-requisitos-funcionales.md index 84839da..9e46cbe 100644 --- a/docs/entregables/01-analisis/requisitos/AN-001-requisitos-funcionales.md +++ b/docs/entregables/01-analisis/requisitos/AN-001-requisitos-funcionales.md @@ -167,6 +167,64 @@ Sistema web para gestión de inscripciones estudiantiles con programa de crédit --- +### RF-010: Autenticación y Roles + +| Atributo | Descripción | +|----------|-------------| +| **ID** | RF-010 | +| **Nombre** | Sistema de Autenticación con Roles | +| **Descripción** | El sistema debe soportar autenticación JWT con roles Admin y Student | +| **Prioridad** | Alta | +| **Fuente** | Requisito de seguridad implícito | + +**Criterios de Aceptación:** +- [ ] CA-010.1: Usuario puede iniciar sesión con credenciales válidas +- [ ] CA-010.2: Sistema genera token JWT con claims de rol +- [ ] CA-010.3: Rutas protegidas requieren autenticación +- [ ] CA-010.4: Admin puede acceder a gestión de estudiantes +- [ ] CA-010.5: Student solo puede acceder a su dashboard personal + +--- + +### RF-011: Flujo de Activación de Estudiantes + +| Atributo | Descripción | +|----------|-------------| +| **ID** | RF-011 | +| **Nombre** | Activación de Cuenta de Estudiante | +| **Descripción** | Admin crea estudiantes con código de activación. Estudiantes activan su cuenta para crear credenciales | +| **Prioridad** | Alta | +| **Fuente** | Requisito de seguridad y UX | + +**Criterios de Aceptación:** +- [ ] CA-011.1: Admin puede crear estudiante y obtener código de activación +- [ ] CA-011.2: Código de activación es de 12 caracteres alfanuméricos +- [ ] CA-011.3: Código de activación expira en 48 horas +- [ ] CA-011.4: Estudiante puede validar código y crear credenciales +- [ ] CA-011.5: Sistema genera código de recuperación al activar +- [ ] CA-011.6: Código de recuperación se muestra solo una vez +- [ ] CA-011.7: Admin puede regenerar código si expira + +--- + +### RF-012: Control de Acceso por Rol + +| Atributo | Descripción | +|----------|-------------| +| **ID** | RF-012 | +| **Nombre** | Restricción de Acceso por Rol | +| **Descripción** | El sistema debe restringir funcionalidades según el rol del usuario | +| **Prioridad** | Alta | +| **Fuente** | Requisito de seguridad | + +**Criterios de Aceptación:** +- [ ] CA-012.1: Admin puede ver y gestionar todos los estudiantes +- [ ] CA-012.2: Student no puede acceder a rutas de administración +- [ ] CA-012.3: Student solo ve su información y materias +- [ ] CA-012.4: Intentos de acceso no autorizado redirigen al dashboard + +--- + ## 3. Matriz de Trazabilidad | Requisito | Historia | Componente Backend | Componente Frontend | @@ -180,14 +238,21 @@ Sistema web para gestión de inscripciones estudiantiles con programa de crédit | RF-007 | US-005 | EnrollmentDomainService | EnrollmentComponent | | RF-008 | US-006 | StudentQuery | StudentListComponent | | RF-009 | US-007 | ClassmatesQuery | ClassmatesComponent | +| RF-010 | US-010 | LoginCommand, JwtService | LoginComponent | +| RF-011 | US-011 | ActivateAccountCommand | ActivateComponent | +| RF-012 | US-012 | AuthorizationMiddleware | auth.guard.ts | --- ## 4. Dependencias entre Requisitos ``` -RF-001 (Estudiantes) +RF-010 (Autenticación) ↓ +RF-011 (Activación) ──► RF-012 (Control Acceso) + ↓ ↓ +RF-001 (Estudiantes) │ + ↓ ▼ RF-002 (Programa Créditos) ← RF-004 (3 créditos/materia) ↓ RF-005 (Max 3 materias) ← RF-003 (10 materias) diff --git a/docs/entregables/02-diseno/esquema-graphql/DI-004-esquema-graphql.md b/docs/entregables/02-diseno/esquema-graphql/DI-004-esquema-graphql.md index d137dc6..ca03187 100644 --- a/docs/entregables/02-diseno/esquema-graphql/DI-004-esquema-graphql.md +++ b/docs/entregables/02-diseno/esquema-graphql/DI-004-esquema-graphql.md @@ -17,9 +17,19 @@ type Student { name: String! email: String! totalCredits: Int! + isActivated: Boolean! + activationExpiresAt: DateTime enrollments: [Enrollment!]! } +type User { + id: Int! + username: String! + role: String! # "Admin" | "Student" + studentId: Int + studentName: String +} + type Subject { id: Int! name: String! @@ -57,6 +67,10 @@ type Classmate { # ═══════════════════════════════════════════════════════════════ type Query { + # Autenticación + me: User # Usuario autenticado actual + validateActivationCode(code: String!): ActivationValidation! + # Estudiantes students: [Student!]! student(id: Int!): Student @@ -74,15 +88,27 @@ type Query { classmates(studentId: Int!): [Classmate!]! } +type ActivationValidation { + isValid: Boolean! + studentName: String + error: String +} + # ═══════════════════════════════════════════════════════════════ # MUTATIONS # ═══════════════════════════════════════════════════════════════ type Mutation { - # Estudiantes - createStudent(input: CreateStudentInput!): StudentPayload! + # Autenticación + login(input: LoginInput!): AuthPayload! + activateAccount(input: ActivateAccountInput!): AuthPayload! + resetPassword(input: ResetPasswordInput!): ResetPayload! + + # Estudiantes (Admin crea con código de activación) + createStudent(input: CreateStudentInput!): CreateStudentPayload! updateStudent(id: Int!, input: UpdateStudentInput!): StudentPayload! deleteStudent(id: Int!): DeletePayload! + regenerateActivationCode(studentId: Int!): ActivationCodePayload! # Inscripciones enrollStudent(input: EnrollInput!): EnrollmentPayload! @@ -93,6 +119,25 @@ type Mutation { # INPUTS # ═══════════════════════════════════════════════════════════════ +# Autenticación +input LoginInput { + username: String! + password: String! +} + +input ActivateAccountInput { + activationCode: String! + username: String! + password: String! +} + +input ResetPasswordInput { + username: String! + recoveryCode: String! + newPassword: String! +} + +# Estudiantes input CreateStudentInput { name: String! email: String! @@ -112,6 +157,35 @@ input EnrollInput { # PAYLOADS (Union para errores) # ═══════════════════════════════════════════════════════════════ +# Autenticación +type AuthPayload { + success: Boolean! + token: String + recoveryCode: String # Solo en activación (se muestra una vez) + user: User + error: String +} + +type ResetPayload { + success: Boolean! + error: String +} + +# Estudiantes +type CreateStudentPayload { + student: Student + activationCode: String # Código para activar cuenta + activationUrl: String # URL completa de activación + expiresAt: DateTime # Cuándo expira el código + errors: [String!] +} + +type ActivationCodePayload { + activationCode: String! + activationUrl: String! + expiresAt: DateTime! +} + type StudentPayload { student: Student errors: [String!] @@ -224,3 +298,54 @@ mutation Enroll { | `SAME_PROFESSOR` | "Ya tienes materia con este profesor" | enrollStudent | | `DUPLICATE_EMAIL` | "Email ya registrado" | createStudent | | `NOT_FOUND` | "Estudiante no encontrado" | updateStudent | +| `INVALID_CREDENTIALS` | "Usuario o contraseña incorrectos" | login | +| `USERNAME_EXISTS` | "El nombre de usuario ya está en uso" | activateAccount | +| `INVALID_ACTIVATION_CODE` | "Código de activación inválido o expirado" | activateAccount | +| `INVALID_RECOVERY_CODE` | "Código de recuperación inválido" | resetPassword | +| `UNAUTHORIZED` | "No tienes permiso para esta acción" | Operaciones protegidas | + +--- + +## 6. Ejemplos de Autenticación + +### Login +```graphql +mutation Login { + login(input: { username: "admin", password: "Admin123!" }) { + success + token + user { id username role studentId studentName } + error + } +} +``` + +### Activar Cuenta +```graphql +mutation ActivateAccount { + activateAccount(input: { + activationCode: "MSAGDM5DNLAF" + username: "juan.perez" + password: "MiPassword123" + }) { + success + token + recoveryCode # Solo se muestra UNA vez + user { id username role studentId studentName } + error + } +} +``` + +### Crear Estudiante (Admin) +```graphql +mutation CreateStudent { + createStudent(input: { name: "Juan Pérez", email: "juan@email.com" }) { + student { id name email isActivated } + activationCode # "MSAGDM5DNLAF" + activationUrl # "https://app.com/activate?code=MSAGDM5DNLAF" + expiresAt # "2026-01-11T06:00:00Z" + errors + } +} +``` diff --git a/docs/entregables/02-diseno/modelo-dominio/DI-002-modelo-dominio.md b/docs/entregables/02-diseno/modelo-dominio/DI-002-modelo-dominio.md index 1ec8cb5..185ac6c 100644 --- a/docs/entregables/02-diseno/modelo-dominio/DI-002-modelo-dominio.md +++ b/docs/entregables/02-diseno/modelo-dominio/DI-002-modelo-dominio.md @@ -8,25 +8,37 @@ ## 1. Diagrama de Entidades ``` -┌─────────────────┐ ┌─────────────────┐ -│ PROFESSOR │ │ STUDENT │ -├─────────────────┤ ├─────────────────┤ -│ Id: int (PK) │ │ Id: int (PK) │ -│ Name: string │ │ Name: string │ -└────────┬────────┘ │ Email: Email │ - │ │ RowVersion │ - │ 1:2 └────────┬────────┘ - ▼ │ -┌─────────────────┐ │ 0..3 -│ SUBJECT │ ▼ -├─────────────────┤ ┌─────────────────┐ -│ Id: int (PK) │◄──────│ ENROLLMENT │ -│ Name: string │ 1:N ├─────────────────┤ -│ Credits: 3 │ │ Id: int (PK) │ -│ ProfessorId: FK │ │ StudentId: FK │ -└─────────────────┘ │ SubjectId: FK │ - │ EnrolledAt │ - └─────────────────┘ +┌─────────────────┐ ┌─────────────────────────┐ +│ PROFESSOR │ │ STUDENT │ +├─────────────────┤ ├─────────────────────────┤ +│ Id: int (PK) │ │ Id: int (PK) │ +│ Name: string │ │ Name: string │ +└────────┬────────┘ │ Email: Email │ + │ │ ActivationCodeHash? │ ← Nuevo + │ 1:2 │ ActivationExpiresAt? │ ← Nuevo + ▼ │ IsActivated (computed) │ ← Nuevo +┌─────────────────┐ │ RowVersion │ +│ SUBJECT │ └────────┬────────────────┘ +├─────────────────┤ │ +│ Id: int (PK) │ │ 0..3 +│ Name: string │ ▼ +│ Credits: 3 │ ┌─────────────────┐ +│ ProfessorId: FK │◄──────│ ENROLLMENT │ +└─────────────────┘ 1:N ├─────────────────┤ + │ Id: int (PK) │ +┌─────────────────┐ │ StudentId: FK │ +│ USER │ │ SubjectId: FK │ +├─────────────────┤ │ EnrolledAt │ +│ Id: int (PK) │ └─────────────────┘ +│ Username │ +│ PasswordHash │ +│ RecoveryCodeHash│ +│ Role (Admin/ │ +│ Student) │ +│ StudentId?: FK │───────► 0..1 Student +│ CreatedAt │ +│ LastLoginAt? │ +└─────────────────┘ ``` --- @@ -38,10 +50,16 @@ ```csharp public class Student { + public const int MaxEnrollments = 3; + public int Id { get; private set; } public string Name { get; private set; } public Email Email { get; private set; } - public byte[] RowVersion { get; private set; } + + // Campos de Activación (nuevo flujo) + public string? ActivationCodeHash { get; private set; } + public DateTime? ActivationExpiresAt { get; private set; } + public bool IsActivated => ActivationCodeHash == null; private readonly List _enrollments = new(); public IReadOnlyCollection Enrollments => _enrollments; @@ -59,6 +77,40 @@ public class Student var enrollment = _enrollments.FirstOrDefault(e => e.SubjectId == subjectId); if (enrollment != null) _enrollments.Remove(enrollment); } + + // Métodos de activación + public void SetActivationCode(string codeHash, TimeSpan expiresIn) + { + ActivationCodeHash = codeHash; + ActivationExpiresAt = DateTime.UtcNow.Add(expiresIn); + } + + public void ClearActivationCode() + { + ActivationCodeHash = null; + ActivationExpiresAt = null; + } + + public bool IsActivationExpired() => + ActivationExpiresAt.HasValue && DateTime.UtcNow > ActivationExpiresAt.Value; +} +``` + +### User (Autenticación) + +```csharp +public class User +{ + public int Id { get; private set; } + public string Username { get; private set; } // Almacenado en minúsculas + public string PasswordHash { get; private set; } // PBKDF2-SHA256 + public string RecoveryCodeHash { get; private set; } + public string Role { get; private set; } // "Admin" | "Student" + public int? StudentId { get; private set; } // FK opcional a Student + public DateTime CreatedAt { get; private set; } + public DateTime? LastLoginAt { get; private set; } + + public Student? Student { get; private set; } // Navegación } ``` @@ -187,3 +239,33 @@ public class SameProfessorConstraintException : DomainException | No repetir profesor | Student | Domain Service | | 3 créditos/materia | Subject | Constante | | 2 materias/profesor | Professor | Seed Data | +| Username único | User | DB Constraint | +| Código activación expira | Student | ActivationExpiresAt | +| Student ↔ User (1:0..1) | User | StudentId nullable | + +--- + +## 7. Flujo de Activación + +``` +┌──────────────┐ ┌──────────────┐ ┌──────────────┐ +│ ADMIN │ │ STUDENT │ │ USER │ +│ (creates) │───►│ (pending) │ │ (not yet) │ +└──────────────┘ └──────┬───────┘ └──────────────┘ + │ + SetActivationCode() + │ + ▼ + ┌──────────────┐ + │ STUDENT │ + │ (has code) │ + └──────┬───────┘ + │ + Activate(username, password) + │ + ▼ +┌──────────────┐ ┌──────────────┐ ┌──────────────┐ +│ ADMIN │ │ STUDENT │◄───│ USER │ +│ (creates) │ │ (activated) │ │ StudentId=X │ +└──────────────┘ └──────────────┘ └──────────────┘ +``` diff --git a/docs/entregables/03-configuracion/DV-004-configuracion-base-datos.md b/docs/entregables/03-configuracion/DV-004-configuracion-base-datos.md index 9f00ab8..c0127a9 100644 --- a/docs/entregables/03-configuracion/DV-004-configuracion-base-datos.md +++ b/docs/entregables/03-configuracion/DV-004-configuracion-base-datos.md @@ -15,7 +15,7 @@ services: container_name: sqlserver-students environment: - ACCEPT_EULA=Y - - SA_PASSWORD=${DB_PASSWORD:-YourStrong@Passw0rd} + - SA_PASSWORD=${DB_PASSWORD:-Asde71.4Asde71.4} - MSSQL_PID=Developer ports: - "1433:1433" @@ -44,7 +44,7 @@ docker logs sqlserver-students # Conectar a SQL Server docker exec -it sqlserver-students /opt/mssql-tools18/bin/sqlcmd \ - -S localhost -U sa -P 'YourStrong@Passw0rd' -C + -S localhost -U sa -P 'Asde71.4Asde71.4' -C # Detener docker-compose -f deploy/docker/docker-compose.yml down @@ -58,7 +58,7 @@ docker-compose -f deploy/docker/docker-compose.yml down // appsettings.Development.json { "ConnectionStrings": { - "DefaultConnection": "Server=localhost;Database=StudentEnrollment;User Id=sa;Password=YourStrong@Passw0rd;TrustServerCertificate=True" + "DefaultConnection": "Server=localhost;Database=StudentEnrollment;User Id=sa;Password=Asde71.4Asde71.4;TrustServerCertificate=True" } } ``` diff --git a/docs/entregables/03-configuracion/DV-005-variables-entorno.md b/docs/entregables/03-configuracion/DV-005-variables-entorno.md index 3bf38a4..d00de88 100644 --- a/docs/entregables/03-configuracion/DV-005-variables-entorno.md +++ b/docs/entregables/03-configuracion/DV-005-variables-entorno.md @@ -31,7 +31,7 @@ ```json { "ConnectionStrings": { - "DefaultConnection": "Server=localhost;Database=StudentEnrollment;User Id=sa;Password=YourStrong@Passw0rd;TrustServerCertificate=True" + "DefaultConnection": "Server=localhost;Database=StudentEnrollment;User Id=sa;Password=Asde71.4Asde71.4;TrustServerCertificate=True" }, "GraphQL": { "EnableIntrospection": true @@ -63,7 +63,7 @@ dotnet user-secrets init # Guardar connection string dotnet user-secrets set "ConnectionStrings:DefaultConnection" \ - "Server=localhost;Database=StudentEnrollment;User Id=sa;Password=YourStrong@Passw0rd;TrustServerCertificate=True" + "Server=localhost;Database=StudentEnrollment;User Id=sa;Password=Asde71.4Asde71.4;TrustServerCertificate=True" # Listar secrets dotnet user-secrets list @@ -104,7 +104,7 @@ export const environment = { ```env # Database -DB_PASSWORD=YourStrong@Passw0rd +DB_PASSWORD=Asde71.4Asde71.4 DB_NAME=StudentEnrollment # API