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:
parent
cf5ba2010d
commit
df06225b98
90
CLAUDE.md
90
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?
|
||||
|
|
|
|||
45
README.md
45
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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
```
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
|
|||
|
|
@ -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 │
|
||||
└──────────────┘ └──────────────┘ └──────────────┘
|
||||
```
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue