docs: update project documentation for activation feature

Updated documentation across all deliverables:

Requirements (AN-001):
- Add activation flow requirements
- Document admin management capabilities

User Stories (AN-003):
- Add activation-related user stories
- Admin stories for student management

Domain Model (DI-002):
- Document Student activation fields
- Update entity relationships

GraphQL Schema (DI-004):
- Add activation mutations and queries
- Document new endpoints

Configuration:
- DV-004: Database schema changes
- DV-005: New environment variables

Project docs:
- DEV-GUIDE.md: Update project structure and commands
- README.md: Add activation feature documentation
- ENTREGABLES.md: Update deliverables summary
- DEPLOYMENT.md: Update deployment instructions
- RECOMMENDATIONS.md: Add security recommendations
This commit is contained in:
Andrés Eduardo García Márquez 2026-01-09 07:43:57 -05:00
parent cf5ba2010d
commit df06225b98
11 changed files with 696 additions and 85 deletions

View File

@ -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 ## Ciclo OODA
1. **OBSERVAR:** ¿Qué se solicita? ¿Qué existe? 1. **OBSERVAR:** ¿Qué se solicita? ¿Qué existe?

View File

@ -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 - Inscripción/cancelación de materias con validación de reglas
- Visualización de compañeros de clase por materia - Visualización de compañeros de clase por materia
- Interfaz responsive con Angular Material - Interfaz responsive con Angular Material
- **Sistema de autenticación con flujo de activación**
- **Control de acceso por roles (Admin/Student)**
### Calidad y Robustez ### Calidad y Robustez
- **Manejo de errores**: Mensajes amigables para usuarios + logging detallado para desarrolladores - **Manejo de errores**: Mensajes amigables para usuarios + logging detallado para desarrolladores
@ -58,7 +60,7 @@ cd Interrapidisimo
### Paso 2: Iniciar SQL Server con Docker ### Paso 2: Iniciar SQL Server con Docker
```bash ```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 -p 1433:1433 --name sqlserver -d mcr.microsoft.com/mssql/server:2022-latest
``` ```
@ -236,7 +238,7 @@ docker restart sqlserver
# Conectar con sqlcmd # Conectar con sqlcmd
docker exec -it sqlserver /opt/mssql-tools18/bin/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 ## API GraphQL
@ -327,7 +329,7 @@ docker start sqlserver
# Verificar conexión # Verificar conexión
docker exec -it sqlserver /opt/mssql-tools18/bin/sqlcmd \ 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 ### Puerto 5000 en uso
@ -367,7 +369,7 @@ Verificar que el password en `appsettings.json` coincida con el del contenedor D
```json ```json
{ {
"ConnectionStrings": { "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) - [Despliegue](docs/architecture/diagrams/07-deployment.svg)
- [C4 Contexto](docs/architecture/diagrams/08-c4-context.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 - Input validation con FluentValidation
- Sanitización contra XSS - Sanitización contra XSS
- Security headers (CSP, HSTS, X-Frame-Options) - Security headers (CSP, HSTS, X-Frame-Options)

View File

@ -185,7 +185,7 @@ volumes:
cd deploy/docker cd deploy/docker
# Crear archivo .env # Crear archivo .env
echo "DB_PASSWORD=Your_Str0ng_P@ssword!" > .env echo "DB_PASSWORD=Asde71.4Asde71.4" > .env
# Build e iniciar # Build e iniciar
docker-compose up -d --build docker-compose up -d --build
@ -254,7 +254,7 @@ Script que levanta backend + frontend con **SQLite** (sin necesidad de SQL Serve
```yaml ```yaml
# Variables de entorno en el workflow # Variables de entorno en el workflow
K3S_HOST: 100.67.198.92 # IP del master (hp62a) K3S_HOST: 100.67.198.92 # IP del master (hp62a)
NAMESPACE: student-enrollment NAMESPACE: academia
DOMAIN: academia.ingeniumcodex.com DOMAIN: academia.ingeniumcodex.com
``` ```
@ -366,20 +366,25 @@ dotnet ef database update <MigrationName>
``` ```
deploy/k3s/ deploy/k3s/
├── namespace.yaml # Namespace dedicado ├── namespace.yaml # Namespace: academia
├── secrets.yaml # Credenciales BD ├── secrets.yaml # Credenciales BD
├── configmap.yaml # Configuración ├── configmap.yaml # Configuración
├── sqlserver.yaml # Base de datos ├── sqlserver.yaml # Base de datos
├── api.yaml # Backend GraphQL ├── api.yaml # Backend GraphQL
├── frontend.yaml # Frontend Angular ├── frontend.yaml # Frontend Angular
├── ingress.yaml # Traefik ingress ├── ingress.yaml # Traefik IngressRoute + TLS
├── ingress-tls.yaml # TLS con cert-manager ├── networkpolicy.yaml # Seguridad de red (incluido en kustomize)
├── hpa.yaml # Autoscaling ├── hpa.yaml # Autoscaling (opcional, no incluido)
├── networkpolicy.yaml # Seguridad de red
├── kustomization.yaml # Kustomize config ├── kustomization.yaml # Kustomize config
└── deploy.sh # Script de despliegue └── 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 ### Requisitos k3s
- k3s instalado y funcionando - k3s instalado y funcionando
@ -399,7 +404,7 @@ cd deploy/k3s
kubectl apply -k . kubectl apply -k .
# Verificar estado # Verificar estado
kubectl get all -n student-enrollment kubectl get all -n academia
``` ```
### Comandos del Script ### Comandos del Script
@ -418,9 +423,9 @@ kubectl get all -n student-enrollment
```bash ```bash
# Editar secrets antes de desplegar # Editar secrets antes de desplegar
kubectl create secret generic student-secrets \ kubectl create secret generic student-secrets \
--namespace=student-enrollment \ --namespace=academia \
--from-literal=db-password='TuPasswordSeguro123!' \ --from-literal=db-password='Asde71.4Asde71.4' \
--from-literal=db-connection-string='Server=sqlserver;Database=StudentEnrollment;User Id=sa;Password=TuPasswordSeguro123!;TrustServerCertificate=True' \ --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 --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 # 2. Editar ingress-tls.yaml con tu dominio y email
# 3. Aplicar ingress con TLS # 3. Aplicar ingress con TLS
kubectl apply -f ingress-tls.yaml -n student-enrollment kubectl apply -f ingress-tls.yaml -n academia
``` ```
### Scaling Manual ### Scaling Manual
```bash ```bash
# Escalar API # Escalar API
kubectl scale deployment student-api -n student-enrollment --replicas=3 kubectl scale deployment student-api -n academia --replicas=3
# Escalar Frontend # Escalar Frontend
kubectl scale deployment student-frontend -n student-enrollment --replicas=2 kubectl scale deployment student-frontend -n academia --replicas=2
``` ```
### Monitoreo ### Monitoreo
```bash ```bash
# Estado de pods # Estado de pods
kubectl get pods -n student-enrollment -w kubectl get pods -n academia -w
# Logs en tiempo real # Logs en tiempo real
kubectl logs -n student-enrollment -l app=student-api -f kubectl logs -n academia -l app=student-api -f
# Eventos # Eventos
kubectl get events -n student-enrollment --sort-by='.lastTimestamp' kubectl get events -n academia --sort-by='.lastTimestamp'
# Recursos # Recursos
kubectl top pods -n student-enrollment kubectl top pods -n academia
``` ```
### Rollback en k3s ### Rollback en k3s
```bash ```bash
# Ver historial de deployments # 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 # 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 # 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 ### Troubleshooting
```bash ```bash
# Pod no inicia # Pod no inicia
kubectl describe pod <pod-name> -n student-enrollment kubectl describe pod <pod-name> -n academia
# Conectar a pod # Conectar a pod
kubectl exec -it <pod-name> -n student-enrollment -- /bin/sh kubectl exec -it <pod-name> -n academia -- /bin/sh
# Verificar conectividad BD # Verificar conectividad BD
kubectl exec -it <api-pod> -n student-enrollment -- \ kubectl exec -it <api-pod> -n academia -- \
curl -v telnet://sqlserver:1433 curl -v telnet://sqlserver:1433
# Verificar ingress # Verificar ingress
kubectl describe ingress student-ingress -n student-enrollment kubectl describe ingress student-ingress -n academia
``` ```

View File

@ -52,6 +52,9 @@ Sistema web completo para gestión de inscripciones de estudiantes con las sigui
| 7 | Validación de inscripciones | ✅ | | 7 | Validación de inscripciones | ✅ |
| 8 | UI responsiva | ✅ | | 8 | UI responsiva | ✅ |
| 9 | Manejo de errores | ✅ | | 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 ### 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) - ✅ Validación en Domain Layer (pura, testeable)
- ✅ Mensajes de error descriptivos - ✅ 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 ## Arquitectura
@ -85,6 +96,33 @@ Host → Adapters → Application → Domain
| DataLoader | Evitar N+1 en GraphQL | | DataLoader | Evitar N+1 en GraphQL |
| Specification | Consultas reutilizables | | 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 ## Testing
@ -94,24 +132,56 @@ Host → Adapters → Application → Domain
| Tipo | Cantidad | Cobertura | | Tipo | Cantidad | Cobertura |
|------|----------|-----------| |------|----------|-----------|
| Domain Tests | 30 | Entidades, Value Objects, Services | | 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 | | Integration Tests | 5 | GraphQL API completa |
| Angular Unit Tests | 24 | Services, Pipes | | Angular Unit Tests | 24 | Services, Pipes |
| E2E Tests (Playwright) | 20 | Flujos de usuario | | E2E Tests (Playwright) | 97 | Flujos de usuario completos |
| **Total** | **145** | | | **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 ### Ejecutar Tests
```bash ```bash
# Backend # Backend - Todos
dotnet test dotnet test tests/Application.Tests
dotnet test tests/Domain.Tests
dotnet test tests/Integration.Tests
# Frontend # Backend - Solo Auth
cd src/frontend dotnet test tests/Application.Tests --filter "FullyQualifiedName~Auth"
ng test --watch=false
# E2E # Frontend Unit Tests
npx playwright test 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/` | | Diseño BD | `/docs/entregables/02-diseno/base-datos/` |
| Esquema GraphQL | `/docs/entregables/02-diseno/esquema-graphql/` | | Esquema GraphQL | `/docs/entregables/02-diseno/esquema-graphql/` |
| ADRs | `/docs/architecture/decisions/` | | ADRs | `/docs/architecture/decisions/` |
| **Diagramas UML** | `/docs/architecture/diagrams/` |
| OWASP Checklist | `/docs/OWASP_CHECKLIST.md` | | OWASP Checklist | `/docs/OWASP_CHECKLIST.md` |
| Manual Despliegue | `/docs/DEPLOYMENT.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 ## Despliegue

View File

@ -152,9 +152,10 @@ El sistema está **listo para demostración** y cumple con todos los requisitos
- Health check post-deploy - Health check post-deploy
#### 2. Kubernetes (k3s) Deployment #### 2. Kubernetes (k3s) Deployment
- **Namespace:** `student-enrollment` - **Namespace:** `academia`
- **Servicios:** student-api, student-frontend, mssql - **Servicios:** student-api, student-frontend, sqlserver
- **Ingress:** `students.ingeniumcodex.com` (Traefik) - **Ingress:** `academia.ingeniumcodex.com` (Traefik)
- **Seguridad:** NetworkPolicy (default-deny + allow rules)
- **TLS:** Configurado con cert-manager - **TLS:** Configurado con cert-manager
#### 3. Optimizaciones de Deployment #### 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 ### URLs de Producción
| Servicio | URL | | Servicio | URL |
|----------|-----| |----------|-----|
| Frontend | https://students.ingeniumcodex.com | | Frontend | https://academia.ingeniumcodex.com |
| GraphQL API | https://students.ingeniumcodex.com/graphql | | GraphQL API | https://academia.ingeniumcodex.com/graphql |
| Health Check | https://students.ingeniumcodex.com/health | | Health Check | https://academia.ingeniumcodex.com/health |
### Repositorio Git ### 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 - **CI/CD:** Auto-deploy en push a main

View File

@ -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 ## 3. Backlog Priorizado
| Prioridad | Historia | Story Points | Sprint | | Prioridad | Historia | Story Points | Sprint |
|-----------|----------|--------------|--------| |-----------|----------|--------------|--------|
| 1 | US-001: Registro de Estudiante | 5 | 1 | | 1 | US-001: Registro de Estudiante | 5 | 1 |
| 2 | US-002: Consulta de Materias | 3 | 1 | | 2 | US-011: Creación por Admin | 5 | 1 |
| 3 | US-003: Inscripción en Materia | 8 | 1 | | 3 | US-010: Activación de Cuenta | 8 | 1 |
| 4 | US-004: Cancelación de Inscripción | 3 | 1 | | 4 | US-012: Control de Acceso | 5 | 1 |
| 5 | US-005: Materias No Disponibles | 3 | 2 | | 5 | US-002: Consulta de Materias | 3 | 1 |
| 6 | US-007: Ver Compañeros | 5 | 2 | | 6 | US-003: Inscripción en Materia | 8 | 1 |
| 7 | US-006: Consulta de Estudiantes | 2 | 2 | | 7 | US-004: Cancelación de Inscripción | 3 | 1 |
| 8 | US-008: Actualizar Datos | 2 | 2 | | 8 | US-005: Materias No Disponibles | 3 | 2 |
| 9 | US-009: Eliminar Cuenta | 2 | 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
--- ---

View File

@ -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 ## 3. Matriz de Trazabilidad
| Requisito | Historia | Componente Backend | Componente Frontend | | 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-007 | US-005 | EnrollmentDomainService | EnrollmentComponent |
| RF-008 | US-006 | StudentQuery | StudentListComponent | | RF-008 | US-006 | StudentQuery | StudentListComponent |
| RF-009 | US-007 | ClassmatesQuery | ClassmatesComponent | | 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 ## 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-002 (Programa Créditos) ← RF-004 (3 créditos/materia)
RF-005 (Max 3 materias) ← RF-003 (10 materias) RF-005 (Max 3 materias) ← RF-003 (10 materias)

View File

@ -17,9 +17,19 @@ type Student {
name: String! name: String!
email: String! email: String!
totalCredits: Int! totalCredits: Int!
isActivated: Boolean!
activationExpiresAt: DateTime
enrollments: [Enrollment!]! enrollments: [Enrollment!]!
} }
type User {
id: Int!
username: String!
role: String! # "Admin" | "Student"
studentId: Int
studentName: String
}
type Subject { type Subject {
id: Int! id: Int!
name: String! name: String!
@ -57,6 +67,10 @@ type Classmate {
# ═══════════════════════════════════════════════════════════════ # ═══════════════════════════════════════════════════════════════
type Query { type Query {
# Autenticación
me: User # Usuario autenticado actual
validateActivationCode(code: String!): ActivationValidation!
# Estudiantes # Estudiantes
students: [Student!]! students: [Student!]!
student(id: Int!): Student student(id: Int!): Student
@ -74,15 +88,27 @@ type Query {
classmates(studentId: Int!): [Classmate!]! classmates(studentId: Int!): [Classmate!]!
} }
type ActivationValidation {
isValid: Boolean!
studentName: String
error: String
}
# ═══════════════════════════════════════════════════════════════ # ═══════════════════════════════════════════════════════════════
# MUTATIONS # MUTATIONS
# ═══════════════════════════════════════════════════════════════ # ═══════════════════════════════════════════════════════════════
type Mutation { type Mutation {
# Estudiantes # Autenticación
createStudent(input: CreateStudentInput!): StudentPayload! 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! updateStudent(id: Int!, input: UpdateStudentInput!): StudentPayload!
deleteStudent(id: Int!): DeletePayload! deleteStudent(id: Int!): DeletePayload!
regenerateActivationCode(studentId: Int!): ActivationCodePayload!
# Inscripciones # Inscripciones
enrollStudent(input: EnrollInput!): EnrollmentPayload! enrollStudent(input: EnrollInput!): EnrollmentPayload!
@ -93,6 +119,25 @@ type Mutation {
# INPUTS # 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 { input CreateStudentInput {
name: String! name: String!
email: String! email: String!
@ -112,6 +157,35 @@ input EnrollInput {
# PAYLOADS (Union para errores) # 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 { type StudentPayload {
student: Student student: Student
errors: [String!] errors: [String!]
@ -224,3 +298,54 @@ mutation Enroll {
| `SAME_PROFESSOR` | "Ya tienes materia con este profesor" | enrollStudent | | `SAME_PROFESSOR` | "Ya tienes materia con este profesor" | enrollStudent |
| `DUPLICATE_EMAIL` | "Email ya registrado" | createStudent | | `DUPLICATE_EMAIL` | "Email ya registrado" | createStudent |
| `NOT_FOUND` | "Estudiante no encontrado" | updateStudent | | `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
}
}
```

View File

@ -8,25 +8,37 @@
## 1. Diagrama de Entidades ## 1. Diagrama de Entidades
``` ```
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────────────┐
│ PROFESSOR │ │ STUDENT │ │ PROFESSOR │ │ STUDENT │
├─────────────────┤ ├─────────────────┤ ├─────────────────┤ ├─────────────────────────┤
│ Id: int (PK) │ │ Id: int (PK) │ │ Id: int (PK) │ │ Id: int (PK) │
│ Name: string │ │ Name: string │ │ Name: string │ │ Name: string │
└────────┬────────┘ │ Email: Email │ └────────┬────────┘ │ Email: Email │
│ │ RowVersion │ │ │ ActivationCodeHash? │ ← Nuevo
│ 1:2 └────────┬────────┘ │ 1:2 │ ActivationExpiresAt? │ ← Nuevo
▼ │ ▼ │ IsActivated (computed) │ ← Nuevo
┌─────────────────┐ │ 0..3 ┌─────────────────┐ │ RowVersion │
│ SUBJECT │ ▼ │ SUBJECT │ └────────┬────────────────┘
├─────────────────┤ ┌─────────────────┐ ├─────────────────┤ │
│ Id: int (PK) │◄──────│ ENROLLMENT │ │ Id: int (PK) │ │ 0..3
│ Name: string │ 1:N ├─────────────────┤ │ Name: string │ ▼
│ Credits: 3 │ │ Id: int (PK) │ │ Credits: 3 │ ┌─────────────────┐
│ ProfessorId: FK │ │ StudentId: FK │ │ ProfessorId: FK │◄──────│ ENROLLMENT │
└─────────────────┘ │ SubjectId: FK │ └─────────────────┘ 1:N ├─────────────────┤
│ EnrolledAt │ │ 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 ```csharp
public class Student public class Student
{ {
public const int MaxEnrollments = 3;
public int Id { get; private set; } public int Id { get; private set; }
public string Name { get; private set; } public string Name { get; private set; }
public Email Email { 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<Enrollment> _enrollments = new(); private readonly List<Enrollment> _enrollments = new();
public IReadOnlyCollection<Enrollment> Enrollments => _enrollments; public IReadOnlyCollection<Enrollment> Enrollments => _enrollments;
@ -59,6 +77,40 @@ public class Student
var enrollment = _enrollments.FirstOrDefault(e => e.SubjectId == subjectId); var enrollment = _enrollments.FirstOrDefault(e => e.SubjectId == subjectId);
if (enrollment != null) _enrollments.Remove(enrollment); 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 | | No repetir profesor | Student | Domain Service |
| 3 créditos/materia | Subject | Constante | | 3 créditos/materia | Subject | Constante |
| 2 materias/profesor | Professor | Seed Data | | 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 │
└──────────────┘ └──────────────┘ └──────────────┘
```

View File

@ -15,7 +15,7 @@ services:
container_name: sqlserver-students container_name: sqlserver-students
environment: environment:
- ACCEPT_EULA=Y - ACCEPT_EULA=Y
- SA_PASSWORD=${DB_PASSWORD:-YourStrong@Passw0rd} - SA_PASSWORD=${DB_PASSWORD:-Asde71.4Asde71.4}
- MSSQL_PID=Developer - MSSQL_PID=Developer
ports: ports:
- "1433:1433" - "1433:1433"
@ -44,7 +44,7 @@ docker logs sqlserver-students
# Conectar a SQL Server # Conectar a SQL Server
docker exec -it sqlserver-students /opt/mssql-tools18/bin/sqlcmd \ 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 # Detener
docker-compose -f deploy/docker/docker-compose.yml down 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 // appsettings.Development.json
{ {
"ConnectionStrings": { "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"
} }
} }
``` ```

View File

@ -31,7 +31,7 @@
```json ```json
{ {
"ConnectionStrings": { "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": { "GraphQL": {
"EnableIntrospection": true "EnableIntrospection": true
@ -63,7 +63,7 @@ dotnet user-secrets init
# Guardar connection string # Guardar connection string
dotnet user-secrets set "ConnectionStrings:DefaultConnection" \ 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 # Listar secrets
dotnet user-secrets list dotnet user-secrets list
@ -104,7 +104,7 @@ export const environment = {
```env ```env
# Database # Database
DB_PASSWORD=YourStrong@Passw0rd DB_PASSWORD=Asde71.4Asde71.4
DB_NAME=StudentEnrollment DB_NAME=StudentEnrollment
# API # API