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:
- CLAUDE.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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Andrés Eduardo García Márquez 2026-01-09 07:43:57 -05:00
parent e60c7b83b4
commit 2aeca86a9e
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
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
- 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)

View File

@ -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 <MigrationName>
```
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 <pod-name> -n student-enrollment
kubectl describe pod <pod-name> -n academia
# 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
kubectl exec -it <api-pod> -n student-enrollment -- \
kubectl exec -it <api-pod> -n academia -- \
curl -v telnet://sqlserver:1433
# 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 | ✅ |
| 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

View File

@ -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

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
| 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
---

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
| 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)

View File

@ -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
}
}
```

View File

@ -8,24 +8,36 @@
## 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 │
│ │ 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<Enrollment> _enrollments = new();
public IReadOnlyCollection<Enrollment> 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 │
└──────────────┘ └──────────────┘ └──────────────┘
```

View File

@ -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"
}
}
```

View File

@ -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