diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..fcc972b --- /dev/null +++ b/.env.example @@ -0,0 +1,42 @@ +# ============================================================================= +# Student Enrollment System - Environment Variables +# ============================================================================= +# Copiar este archivo a .env y ajustar los valores +# NUNCA commitear el archivo .env con credenciales reales +# ============================================================================= + +# ----------------------------------------------------------------------------- +# Database (SQL Server) +# ----------------------------------------------------------------------------- +DB_HOST=localhost +DB_PORT=1433 +DB_NAME=StudentEnrollment +DB_USER=sa +DB_PASSWORD=Your_Str0ng_P@ssword! + +# ----------------------------------------------------------------------------- +# API (.NET) +# ----------------------------------------------------------------------------- +ASPNETCORE_ENVIRONMENT=Development +API_PORT=5000 +CORS_ORIGINS=http://localhost:4200 + +# ----------------------------------------------------------------------------- +# GraphQL +# ----------------------------------------------------------------------------- +GRAPHQL_MAX_DEPTH=5 +GRAPHQL_MAX_COMPLEXITY=100 +GRAPHQL_INTROSPECTION=true + +# ----------------------------------------------------------------------------- +# Logging (Serilog) +# ----------------------------------------------------------------------------- +LOG_LEVEL=Information +LOG_PATH=logs/app-.log + +# ----------------------------------------------------------------------------- +# Frontend (Angular) +# ----------------------------------------------------------------------------- +FRONTEND_PORT=4200 +GRAPHQL_URL=http://localhost:5000/graphql +HEALTH_CHECK_URL=http://localhost:5000/health diff --git a/docs/CODE_REVIEW_CHECKLIST.md b/docs/CODE_REVIEW_CHECKLIST.md new file mode 100644 index 0000000..7e9b3b9 --- /dev/null +++ b/docs/CODE_REVIEW_CHECKLIST.md @@ -0,0 +1,127 @@ +# Code Review Checklist - Sistema Registro Estudiantes + +## Estado: Validado + +### Arquitectura (Clean Architecture) + +| Criterio | Estado | Evidencia | +|----------|--------|-----------| +| Separación de capas | ✅ | Domain, Application, Adapters, Host | +| Regla de dependencia | ✅ | Domain no depende de nada externo | +| Ports & Adapters | ✅ | IStudentRepository, IEnrollmentRepository | +| CQRS implementado | ✅ | Commands y Queries separados | + +### Principios SOLID + +| Principio | Estado | Evidencia | +|-----------|--------|-----------| +| **S**ingle Responsibility | ✅ | Cada clase tiene una responsabilidad | +| **O**pen/Closed | ✅ | Extensible via interfaces | +| **L**iskov Substitution | ✅ | Repositorios intercambiables | +| **I**nterface Segregation | ✅ | Interfaces específicas por entidad | +| **D**ependency Inversion | ✅ | Inyección de dependencias | + +### Clean Code + +| Criterio | Estado | Notas | +|----------|--------|-------| +| Naming conventions | ✅ | PascalCase clases, camelCase variables | +| Métodos pequeños | ✅ | < 20 líneas promedio | +| Sin código duplicado | ✅ | DRY aplicado | +| Comentarios mínimos | ✅ | Código autodocumentado | +| Archivos < 100 líneas | ✅ | Refactorizado donde necesario | + +### Seguridad + +| Criterio | Estado | Ubicación | +|----------|--------|-----------| +| Input validation | ✅ | FluentValidation + Regex | +| SQL Injection prevention | ✅ | EF Core parametrizado | +| XSS prevention | ✅ | Sanitización en validators | +| Security headers | ✅ | Program.cs middleware | +| Rate limiting | ✅ | 100 req/min | +| Query complexity limits | ✅ | Depth 5, complexity 100 | + +### Testing + +| Tipo | Cantidad | Cobertura | +|------|----------|-----------| +| Domain Tests | 30 | Entidades, ValueObjects, Services | +| Application Tests | 66 | Commands, Queries, Validators | +| Integration Tests | 5 | GraphQL flujo completo | +| Angular Unit Tests | 24 | Services, Pipes | +| E2E Tests (Playwright) | 20 | Flujos principales | +| **Total** | **145** | | + +### Convenciones de Código + +#### Backend (.NET) + +- [x] Async/await en operaciones I/O +- [x] Records para DTOs inmutables +- [x] Nullable habilitado +- [x] Global usings configurados +- [x] FluentValidation para validaciones + +#### Frontend (Angular) + +- [x] Standalone components +- [x] Signals para estado reactivo +- [x] Lazy loading por feature +- [x] OnPush change detection +- [x] Apollo Client para GraphQL + +### GraphQL + +| Criterio | Estado | +|----------|--------| +| Types bien definidos | ✅ | +| DataLoaders para N+1 | ✅ | +| Error handling | ✅ | +| Payloads con errors | ✅ | +| Depth limiting | ✅ | + +### Performance + +| Optimización | Implementada | +|--------------|--------------| +| Response compression | ✅ Brotli + Gzip | +| Output caching | ✅ 5 min para subjects/professors | +| Apollo cache | ✅ cache-and-network | +| Lazy loading | ✅ Por feature module | +| Bundle optimization | ✅ < 800KB initial | + +### Documentación + +| Documento | Estado | +|-----------|--------| +| README.md | ✅ | +| CLAUDE.md | ✅ | +| OWASP_CHECKLIST.md | ✅ | +| GraphQL Schema | ✅ (Banana Cake Pop) | + +--- + +## Checklist de Revisión Manual + +### Antes de Merge + +- [ ] Todos los tests pasan +- [ ] Build sin errores ni warnings +- [ ] Código formateado +- [ ] Sin TODOs pendientes críticos +- [ ] Variables de entorno documentadas + +### Seguridad + +- [ ] Sin secrets hardcodeados +- [ ] Sin console.log en producción +- [ ] Validaciones en frontend Y backend +- [ ] Error messages no exponen detalles internos + +### UX + +- [ ] Loading states implementados +- [ ] Error messages claros +- [ ] Responsive design funcional +- [ ] Accesibilidad básica (a11y) diff --git a/docs/DEPLOYMENT.md b/docs/DEPLOYMENT.md new file mode 100644 index 0000000..9ae9213 --- /dev/null +++ b/docs/DEPLOYMENT.md @@ -0,0 +1,419 @@ +# Manual de Despliegue + +## Requisitos del Sistema + +| Componente | Versión Mínima | +|------------|----------------| +| .NET SDK | 10.0 | +| Node.js | 22.x | +| SQL Server | 2022 | +| Docker | 24.x | +| Docker Compose | 2.x | + +## Variables de Entorno + +### Backend (.NET) + +| Variable | Descripción | Ejemplo | +|----------|-------------|---------| +| `ConnectionStrings__DefaultConnection` | Connection string SQL Server | `Server=db;Database=StudentEnrollment;...` | +| `ASPNETCORE_ENVIRONMENT` | Ambiente | `Production` | +| `ASPNETCORE_URLS` | URLs de escucha | `http://+:5000` | + +### Frontend (Angular) + +| Variable | Descripción | Ejemplo | +|----------|-------------|---------| +| `API_URL` | URL del backend GraphQL | `https://api.example.com/graphql` | + +## Despliegue con Docker + +### 1. Estructura de Archivos + +``` +deploy/ +└── docker/ + ├── Dockerfile.api + ├── Dockerfile.frontend + ├── docker-compose.yml + └── nginx.conf +``` + +### 2. Dockerfile Backend + +```dockerfile +# deploy/docker/Dockerfile.api +FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build +WORKDIR /src + +COPY src/backend/ . +RUN dotnet restore Host/Host.csproj +RUN dotnet publish Host/Host.csproj -c Release -o /app + +FROM mcr.microsoft.com/dotnet/aspnet:10.0 +WORKDIR /app +COPY --from=build /app . + +# Non-root user +RUN adduser --disabled-password --gecos '' appuser +USER appuser + +EXPOSE 5000 +HEALTHCHECK --interval=30s --timeout=3s \ + CMD curl -f http://localhost:5000/health || exit 1 + +ENTRYPOINT ["dotnet", "Host.dll"] +``` + +### 3. Dockerfile Frontend + +```dockerfile +# deploy/docker/Dockerfile.frontend +FROM node:22-alpine AS build +WORKDIR /app + +COPY src/frontend/package*.json ./ +RUN npm ci + +COPY src/frontend/ . +RUN npm run build -- --configuration production + +FROM nginx:alpine +COPY --from=build /app/dist/student-enrollment/browser /usr/share/nginx/html +COPY deploy/docker/nginx.conf /etc/nginx/conf.d/default.conf + +EXPOSE 80 +HEALTHCHECK --interval=30s --timeout=3s \ + CMD curl -f http://localhost/ || exit 1 +``` + +### 4. Nginx Configuration + +```nginx +# deploy/docker/nginx.conf +server { + listen 80; + server_name localhost; + root /usr/share/nginx/html; + index index.html; + + # Gzip + gzip on; + gzip_types text/plain text/css application/json application/javascript; + + # SPA routing + location / { + try_files $uri $uri/ /index.html; + } + + # Proxy GraphQL + location /graphql { + proxy_pass http://api:5000/graphql; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + } + + # Security headers + add_header X-Frame-Options "DENY" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-XSS-Protection "1; mode=block" always; +} +``` + +### 5. Docker Compose + +```yaml +# deploy/docker/docker-compose.yml +services: + db: + image: mcr.microsoft.com/mssql/server:2022-latest + environment: + - ACCEPT_EULA=Y + - SA_PASSWORD=${DB_PASSWORD} + ports: + - "1433:1433" + volumes: + - sqlserver-data:/var/opt/mssql + healthcheck: + test: /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P $${SA_PASSWORD} -Q "SELECT 1" -C + interval: 10s + timeout: 3s + retries: 10 + + api: + build: + context: ../.. + dockerfile: deploy/docker/Dockerfile.api + environment: + - ConnectionStrings__DefaultConnection=Server=db;Database=StudentEnrollment;User Id=sa;Password=${DB_PASSWORD};TrustServerCertificate=True + - ASPNETCORE_ENVIRONMENT=Production + ports: + - "5000:5000" + depends_on: + db: + condition: service_healthy + + frontend: + build: + context: ../.. + dockerfile: deploy/docker/Dockerfile.frontend + ports: + - "80:80" + depends_on: + - api + +volumes: + sqlserver-data: +``` + +### 6. Ejecutar + +```bash +cd deploy/docker + +# Crear archivo .env +echo "DB_PASSWORD=Your_Str0ng_P@ssword!" > .env + +# Build e iniciar +docker-compose up -d --build + +# Ver logs +docker-compose logs -f + +# Detener +docker-compose down +``` + +## Despliegue Manual + +### Backend + +```bash +cd src/backend/Host + +# Build producción +dotnet publish -c Release -o ./publish + +# Configurar connection string +export ConnectionStrings__DefaultConnection="Server=..." + +# Ejecutar migraciones +dotnet ef database update --project ../Adapters/Driven/Persistence + +# Iniciar +cd publish +dotnet Host.dll +``` + +### Frontend + +```bash +cd src/frontend + +# Build producción +ng build --configuration production + +# Los archivos quedan en dist/student-enrollment/browser/ +# Servir con cualquier servidor web (nginx, apache, etc.) +``` + +## Checklist Pre-Producción + +### Seguridad + +- [ ] Connection strings en variables de entorno (no en código) +- [ ] HTTPS habilitado +- [ ] CORS configurado solo para dominios permitidos +- [ ] Rate limiting activo +- [ ] Security headers configurados +- [ ] Logs sin datos sensibles + +### Performance + +- [ ] Response compression habilitado +- [ ] Output caching configurado +- [ ] Bundle Angular optimizado (< 200KB transferidos) +- [ ] Índices de BD aplicados + +### Monitoreo + +- [ ] Health checks funcionando (`/health`) +- [ ] Logs centralizados +- [ ] Métricas de aplicación + +### Base de Datos + +- [ ] Backup configurado +- [ ] Migraciones aplicadas +- [ ] Datos seed cargados (5 profesores, 10 materias) + +## URLs de Verificación + +| Servicio | URL | Esperado | +|----------|-----|----------| +| API Health | `http://api:5000/health` | 200 OK | +| GraphQL Playground | `http://api:5000/graphql` | Banana Cake Pop | +| Frontend | `http://frontend:80` | App Angular | + +## Rollback + +```bash +# Docker +docker-compose down +docker-compose up -d --no-build # Usa imágenes anteriores + +# Manual +# Restaurar versión anterior de DLLs/archivos +# Rollback de migraciones si es necesario: +dotnet ef database update +``` + +--- + +## Despliegue en Kubernetes (k3s) + +### Estructura de Manifiestos + +``` +deploy/k3s/ +├── namespace.yaml # Namespace dedicado +├── 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 +├── kustomization.yaml # Kustomize config +└── deploy.sh # Script de despliegue +``` + +### Requisitos k3s + +- k3s instalado y funcionando +- kubectl configurado +- Acceso al cluster +- (Opcional) cert-manager para TLS + +### Despliegue Rápido + +```bash +cd deploy/k3s + +# Opción 1: Con script +./deploy.sh all # Build + Deploy + +# Opción 2: Con kustomize +kubectl apply -k . + +# Verificar estado +kubectl get all -n student-enrollment +``` + +### Comandos del Script + +```bash +./deploy.sh build # Construir imágenes Docker +./deploy.sh deploy # Desplegar a k3s +./deploy.sh status # Ver estado del cluster +./deploy.sh logs api # Ver logs del API +./deploy.sh forward # Port-forward para desarrollo +./deploy.sh delete # Eliminar deployment +``` + +### Configurar Secrets + +```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' \ + --dry-run=client -o yaml > secrets.yaml +``` + +### Acceso Local (Desarrollo) + +```bash +# Agregar entrada en /etc/hosts +echo "127.0.0.1 students.local" | sudo tee -a /etc/hosts + +# Port forward +./deploy.sh forward + +# Acceder en: +# - Frontend: http://localhost:8080 +# - API: http://localhost:5000/graphql +``` + +### Habilitar TLS (Producción) + +```bash +# 1. Instalar cert-manager +kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.14.0/cert-manager.yaml + +# 2. Editar ingress-tls.yaml con tu dominio y email + +# 3. Aplicar ingress con TLS +kubectl apply -f ingress-tls.yaml -n student-enrollment +``` + +### Scaling Manual + +```bash +# Escalar API +kubectl scale deployment student-api -n student-enrollment --replicas=3 + +# Escalar Frontend +kubectl scale deployment student-frontend -n student-enrollment --replicas=2 +``` + +### Monitoreo + +```bash +# Estado de pods +kubectl get pods -n student-enrollment -w + +# Logs en tiempo real +kubectl logs -n student-enrollment -l app=student-api -f + +# Eventos +kubectl get events -n student-enrollment --sort-by='.lastTimestamp' + +# Recursos +kubectl top pods -n student-enrollment +``` + +### Rollback en k3s + +```bash +# Ver historial de deployments +kubectl rollout history deployment/student-api -n student-enrollment + +# Rollback a versión anterior +kubectl rollout undo deployment/student-api -n student-enrollment + +# Rollback a revisión específica +kubectl rollout undo deployment/student-api -n student-enrollment --to-revision=2 +``` + +### Troubleshooting + +```bash +# Pod no inicia +kubectl describe pod -n student-enrollment + +# Conectar a pod +kubectl exec -it -n student-enrollment -- /bin/sh + +# Verificar conectividad BD +kubectl exec -it -n student-enrollment -- \ + curl -v telnet://sqlserver:1433 + +# Verificar ingress +kubectl describe ingress student-ingress -n student-enrollment +``` diff --git a/docs/ENTREGABLES.md b/docs/ENTREGABLES.md new file mode 100644 index 0000000..f37a60e --- /dev/null +++ b/docs/ENTREGABLES.md @@ -0,0 +1,270 @@ +# Entregables - Prueba Técnica Senior .NET/Angular + +**Proyecto:** Sistema de Registro de Estudiantes +**Empresa:** Inter Rapidísimo +**Cargo:** Desarrollador Master .NET/Angular + +--- + +## Resumen Ejecutivo + +Sistema web completo para gestión de inscripciones de estudiantes con las siguientes características: + +- **Backend:** .NET 10, GraphQL (HotChocolate), Clean Architecture +- **Frontend:** Angular 21, Standalone Components, Signals +- **Base de Datos:** SQL Server con EF Core +- **Seguridad:** OWASP compliant, rate limiting, query complexity +- **Testing:** 145 tests automatizados +- **Despliegue:** Docker + Kubernetes (k3s) + +--- + +## Estructura del Proyecto + +``` +/ +├── src/ +│ ├── backend/ # .NET 10 API GraphQL +│ │ ├── Domain/ # Entidades, Value Objects, Ports +│ │ ├── Application/ # Commands, Queries, DTOs +│ │ ├── Adapters/ # GraphQL API, Persistence +│ │ └── Host/ # Entry point, DI +│ └── frontend/ # Angular 21 SPA +├── tests/ # Tests automatizados +├── docs/ # Documentación +└── deploy/ # Docker + k3s +``` + +--- + +## Funcionalidades Implementadas + +### Requisitos Funcionales + +| # | Requisito | Estado | +|---|-----------|--------| +| 1 | CRUD completo de estudiantes | ✅ | +| 2 | Inscripción en materias (max 3) | ✅ | +| 3 | Visualización de compañeros de clase | ✅ | +| 4 | Restricción de profesor único | ✅ | +| 5 | 10 materias, 5 profesores | ✅ | +| 6 | 3 créditos por materia | ✅ | +| 7 | Validación de inscripciones | ✅ | +| 8 | UI responsiva | ✅ | +| 9 | Manejo de errores | ✅ | + +### Reglas de Negocio + +- ✅ Máximo 3 materias por estudiante (9 créditos) +- ✅ No repetir profesor en inscripciones +- ✅ Validación en Domain Layer (pura, testeable) +- ✅ Mensajes de error descriptivos + +--- + +## Arquitectura + +### Clean Architecture + +``` +Host → Adapters → Application → Domain +``` + +- **Domain:** Entidades puras, sin dependencias +- **Application:** Casos de uso con CQRS +- **Adapters:** GraphQL API + EF Core +- **Host:** Composición y DI + +### Patrones Implementados + +| Patrón | Uso | +|--------|-----| +| CQRS | Separación Commands/Queries | +| Repository | Abstracción de persistencia | +| Ports & Adapters | Inversión de dependencias | +| DataLoader | Evitar N+1 en GraphQL | +| Specification | Consultas reutilizables | + +--- + +## Testing + +### Resumen de Tests + +| Tipo | Cantidad | Cobertura | +|------|----------|-----------| +| Domain Tests | 30 | Entidades, Value Objects, Services | +| Application Tests | 66 | Commands, Queries, Validators | +| Integration Tests | 5 | GraphQL API completa | +| Angular Unit Tests | 24 | Services, Pipes | +| E2E Tests (Playwright) | 20 | Flujos de usuario | +| **Total** | **145** | | + +### Ejecutar Tests + +```bash +# Backend +dotnet test + +# Frontend +cd src/frontend +ng test --watch=false + +# E2E +npx playwright test +``` + +--- + +## Seguridad (OWASP) + +| Control | Implementación | +|---------|----------------| +| Input Validation | FluentValidation + Regex | +| SQL Injection | EF Core parametrizado | +| XSS Prevention | Sanitización en validators | +| Security Headers | CSP, HSTS, X-Frame-Options | +| Rate Limiting | 100 req/min (fixed window) | +| Query Complexity | Depth 5, Cost 100 | +| Logging | Serilog (sin datos sensibles) | + +--- + +## Documentación + +| Documento | Ubicación | +|-----------|-----------| +| README principal | `/README.md` | +| Arquitectura Backend | `/docs/entregables/02-diseno/arquitectura/` | +| Modelo de Dominio | `/docs/entregables/02-diseno/modelo-dominio/` | +| Diseño BD | `/docs/entregables/02-diseno/base-datos/` | +| Esquema GraphQL | `/docs/entregables/02-diseno/esquema-graphql/` | +| ADRs | `/docs/architecture/decisions/` | +| OWASP Checklist | `/docs/OWASP_CHECKLIST.md` | +| Manual Despliegue | `/docs/DEPLOYMENT.md` | + +--- + +## Despliegue + +### Docker + +```bash +cd deploy/docker +docker-compose up -d --build +``` + +### Kubernetes (k3s) + +```bash +cd deploy/k3s +./deploy.sh all +``` + +### URLs + +| Servicio | URL | +|----------|-----| +| Frontend | http://localhost:80 | +| API GraphQL | http://localhost:5000/graphql | +| Playground | http://localhost:5000/graphql | + +--- + +## Competencias Demostradas + +### Arquitectura y Diseño + +- ✅ Clean Architecture con separación de capas +- ✅ Principios SOLID aplicados +- ✅ Domain-Driven Design (DDD lite) +- ✅ CQRS para separación de concerns + +### Backend (.NET) + +- ✅ .NET 10 con C# 13 +- ✅ GraphQL con HotChocolate +- ✅ EF Core con Fluent API +- ✅ FluentValidation +- ✅ DataLoaders para N+1 + +### Frontend (Angular) + +- ✅ Angular 21 con Standalone Components +- ✅ Signals para estado reactivo +- ✅ Apollo Client para GraphQL +- ✅ Angular Material UI +- ✅ Lazy Loading + +### DevOps + +- ✅ Docker multi-stage builds +- ✅ Kubernetes manifests +- ✅ Health checks +- ✅ Horizontal Pod Autoscaler + +### Testing + +- ✅ Unit tests con alta cobertura +- ✅ Integration tests +- ✅ E2E tests con Playwright +- ✅ Mocking de dependencias + +### Seguridad + +- ✅ OWASP Top 10 compliance +- ✅ Input validation +- ✅ Rate limiting +- ✅ Security headers + +--- + +## Cómo Ejecutar + +### Desarrollo Local + +```bash +# Backend +cd src/backend/Host +dotnet run + +# Frontend +cd src/frontend +npm install +ng serve +``` + +### Con Docker + +```bash +cd deploy/docker +docker-compose up -d +``` + +### Tests + +```bash +# Todos los tests backend +dotnet test + +# Tests frontend +cd src/frontend && ng test + +# E2E +cd src/frontend && npx playwright test +``` + +--- + +## Decisiones Técnicas + +Ver ADRs completos en `/docs/architecture/decisions/`: + +1. **ADR-001:** Clean Architecture - Testabilidad y mantenibilidad +2. **ADR-002:** GraphQL vs REST - Flexibilidad en queries +3. **ADR-003:** Signals vs RxJS - Simplicidad en estado local +4. **ADR-004:** Validación 3 capas - Defensa en profundidad + +--- + +*Desarrollado como prueba técnica para Inter Rapidísimo* diff --git a/docs/OWASP_CHECKLIST.md b/docs/OWASP_CHECKLIST.md new file mode 100644 index 0000000..accc5ef --- /dev/null +++ b/docs/OWASP_CHECKLIST.md @@ -0,0 +1,78 @@ +# OWASP Top 10 Security Checklist + +## Estado: Validado + +| # | Vulnerabilidad | Mitigación Implementada | Ubicación | +|---|---------------|-------------------------|-----------| +| A01 | **Broken Access Control** | No aplica (sin autenticación requerida) | N/A | +| A02 | **Cryptographic Failures** | HTTPS forzado en producción (HSTS) | `Program.cs:127` | +| A03 | **Injection** | FluentValidation + Regex sanitization, EF Core parameterized queries | `CreateStudentValidator.cs`, `EnrollStudentValidator.cs` | +| A04 | **Insecure Design** | Clean Architecture, input validation en todas las capas | Arquitectura por capas | +| A05 | **Security Misconfiguration** | Security headers (CSP, X-Frame-Options, etc.), Exception details disabled in prod | `Program.cs:106-130`, `GraphQLExtensions.cs:53` | +| A06 | **Vulnerable Components** | Dependencias actualizadas (.NET 10, Angular 21) | `*.csproj`, `package.json` | +| A07 | **Auth Failures** | No aplica (sin autenticación en este MVP) | N/A | +| A08 | **Data Integrity Failures** | Input validation, FluentValidation, GraphQL type safety | Validators | +| A09 | **Security Logging Failures** | Serilog structured logging, sensitive data filtering | `appsettings.json:38-45` | +| A10 | **Server-Side Request Forgery** | No endpoints que acepten URLs externas | N/A | + +## Medidas de Seguridad Implementadas + +### Backend (.NET) + +1. **Input Validation** + - FluentValidation con regex patterns + - Sanitización de HTML/scripts + - Longitud máxima de campos + - Validación de formato email + +2. **Security Headers** + - `Content-Security-Policy` + - `X-Content-Type-Options: nosniff` + - `X-Frame-Options: DENY` + - `Referrer-Policy: strict-origin-when-cross-origin` + - `Permissions-Policy` + - `Strict-Transport-Security` (producción) + +3. **Rate Limiting** + - 100 requests/minuto para queries GraphQL + - 30 mutations/minuto + - Queue limit para prevenir acumulación + +4. **GraphQL Security** + - Query depth limit: 5 niveles + - Query complexity limit: 100 + - Execution timeout: 30 segundos + - Pagination max: 50 items + +5. **Logging Seguro** + - Filtrado de datos sensibles (passwords, tokens) + - Structured logging con Serilog + - Rotación de logs (7 días) + +### Frontend (Angular) + +1. **XSS Prevention** + - Angular sanitization por defecto + - Content Security Policy + +2. **CSRF Protection** + - No cookies de sesión (stateless GraphQL) + +3. **Secure Communication** + - Solo HTTPS en producción + - GraphQL sobre HTTPS + +## Pruebas de Seguridad Recomendadas + +```bash +# Test security headers +curl -I http://localhost:5000/graphql + +# Test rate limiting (debe retornar 429 después de 100 requests) +for i in {1..150}; do curl -s -o /dev/null -w "%{http_code}\n" http://localhost:5000/graphql; done + +# Test query depth (debe fallar con depth > 5) +curl -X POST http://localhost:5000/graphql \ + -H "Content-Type: application/json" \ + -d '{"query":"{ students { enrollments { subject { professor { subjects { name } } } } } }"}' +``` diff --git a/docs/PLAN_ACTIVIDADES.md b/docs/PLAN_ACTIVIDADES.md new file mode 100644 index 0000000..d6ae00d --- /dev/null +++ b/docs/PLAN_ACTIVIDADES.md @@ -0,0 +1,245 @@ +# Plan de Actividades - Prueba Técnica Senior .NET/Angular + +## Información del Proyecto +- **Cargo:** Desarrollador Master .NET/Angular +- **Empresa:** Inter Rapidísimo +- **Proyecto:** Sistema de Registro de Estudiantes +- **Stack:** .NET 10 + GraphQL (HotChocolate) + Angular 21 + SQL Server + +--- + +## Procesos de Fábrica de Software + +| Código | Proceso | Descripción | +|--------|---------|-------------| +| **AN** | Análisis | Levantamiento de requisitos, historias de usuario | +| **DI** | Diseño | Arquitectura, modelos, prototipos UI/UX | +| **DE** | Desarrollo | Codificación, implementación | +| **QA** | Quality Assurance | Testing, revisión de código | +| **DV** | DevOps | CI/CD, containerización, despliegue | +| **DO** | Documentación | Técnica, usuario, API | +| **SE** | Seguridad | Validaciones, autenticación, OWASP | + +--- + +## Tabla de Actividades + +### Fase 1: Análisis y Planificación (AN) + +| # | Actividad | Detalle | Rol | Proceso | +|---|-----------|---------|-----|---------| +| 1.1 | Análisis de requisitos funcionales | Identificar y documentar los 9 requisitos del enunciado, criterios de aceptación por cada uno | Analista | AN | +| 1.2 | Identificación de reglas de negocio | Documentar restricciones: máx 3 materias, 3 créditos/materia, 5 profesores con 2 materias c/u, restricción de profesor único | Analista | AN | +| 1.3 | Definición de historias de usuario | Crear historias con formato "Como [rol] quiero [acción] para [beneficio]" con criterios de aceptación | Product Owner | AN | +| 1.4 | Análisis de requisitos no funcionales | Definir: rendimiento (<200ms respuesta), seguridad (OWASP Top 10), usabilidad (responsive), mantenibilidad | Arquitecto | AN | +| 1.5 | Identificación de riesgos técnicos | Mapear riesgos: complejidad de validaciones, integración frontend-backend, manejo de concurrencia | Líder Técnico | AN | + +### Fase 2: Diseño de Arquitectura (DI) + +| # | Actividad | Detalle | Rol | Proceso | +|---|-----------|---------|-----|---------| +| 2.1 | Diseño de arquitectura backend | Definir Clean Architecture: Domain, Application, Infrastructure, GraphQL. Diagrama de capas y dependencias | Arquitecto Backend | DI | +| 2.2 | Diseño del modelo de dominio | Crear diagrama de entidades: Student, Subject, Professor, Enrollment. Definir agregados y value objects | Arquitecto Backend | DI | +| 2.3 | Diseño de base de datos | Modelo E-R normalizado (3FN), índices, constraints, scripts DDL con integridad referencial | DBA/Arquitecto | DI | +| 2.4 | Diseño de esquema GraphQL | Definir Types, Queries, Mutations, Inputs, Payloads. Diseñar resolvers y DataLoaders para N+1 | Arquitecto Backend | DI | +| 2.5 | Diseño de arquitectura frontend | Definir estructura Angular: standalone components, signals, lazy loading, Apollo Client para GraphQL | Arquitecto Frontend | DI | +| 2.6 | Diseño de componentes UI | Wireframes de pantallas: listado estudiantes, formulario inscripción, selección materias, vista compañeros | UI/UX Designer | DI | +| 2.7 | Definición de contratos GraphQL | Schema GraphQL completo, DTOs de request/response, interfaces de servicios, contratos entre capas | Arquitecto | DI | +| 2.8 | Diseño de estrategia de manejo de errores | Definir excepciones de dominio, error handling en GraphQL (Union types para errores), respuestas estandarizadas | Arquitecto | DI | + +### Fase 3: Configuración del Entorno (DV) + +| # | Actividad | Detalle | Rol | Proceso | +|---|-----------|---------|-----|---------| +| 3.1 | Inicialización del repositorio | Crear estructura de carpetas, .gitignore, README, CLAUDE.md con convenciones del proyecto | DevOps | DV | +| 3.2 | Configuración solución .NET | Crear solución con 4 proyectos (Domain, Application, Infrastructure, GraphQL), referencias entre proyectos | Backend Dev | DV | +| 3.3 | Configuración proyecto Angular | ng new con standalone, configurar ESLint, Prettier, paths aliases, Apollo Angular para GraphQL | Frontend Dev | DV | +| 3.4 | Configuración de base de datos | Docker compose para SQL Server, scripts de inicialización, connection strings por ambiente | DevOps/DBA | DV | +| 3.5 | Configuración de variables de entorno | User secrets para desarrollo, appsettings por ambiente, environment.ts en Angular | DevOps | DV | +| 3.6 | Configuración de herramientas de calidad | EditorConfig, .NET analyzers, ESLint rules, Husky para pre-commit hooks | DevOps | DV | + +### Fase 4: Desarrollo Backend (DE) + +| # | Actividad | Detalle | Rol | Proceso | +|---|-----------|---------|-----|---------| +| 4.1 | Implementar capa Domain | Entidades (Student, Subject, Professor, Enrollment), Value Objects, interfaces de repositorios, excepciones de dominio | Backend Dev | DE | +| 4.2 | Implementar reglas de negocio en Domain | Validaciones en entidades: EnrollmentPolicy (max 3 materias), ProfessorConstraint (no repetir profesor) | Backend Dev | DE | +| 4.3 | Implementar capa Application - DTOs | Records inmutables para requests/responses, mappers con Mapster o extension methods | Backend Dev | DE | +| 4.4 | Implementar capa Application - Servicios | StudentService, EnrollmentService con casos de uso: Create, Update, Delete, GetAll, GetById, GetClassmates | Backend Dev | DE | +| 4.5 | Implementar validadores FluentValidation | Validators para CreateStudentInput, EnrollStudentInput con reglas de negocio | Backend Dev | DE | +| 4.6 | Implementar capa Infrastructure - DbContext | Configurar EF Core 10, Fluent API para mapeos, configuraciones de entidades, seeding de datos iniciales | Backend Dev | DE | +| 4.7 | Implementar capa Infrastructure - Repositorios | Repositorios genérico y específicos, Unit of Work, queries optimizadas con Include/ThenInclude | Backend Dev | DE | +| 4.8 | Implementar GraphQL Types | StudentType, SubjectType, ProfessorType, EnrollmentType con HotChocolate | Backend Dev | DE | +| 4.9 | Implementar GraphQL Queries | Resolvers para: students, student(id), subjects, availableSubjects(studentId), classmates(studentId), professors | Backend Dev | DE | +| 4.10 | Implementar GraphQL Mutations | createStudent, updateStudent, deleteStudent, enrollStudent, unenrollStudent con Payloads de respuesta | Backend Dev | DE | +| 4.11 | Implementar DataLoaders | DataLoaders para evitar N+1: StudentByIdDataLoader, SubjectByIdDataLoader, ProfessorByIdDataLoader | Backend Dev | DE | +| 4.12 | Implementar middleware de errores | ExceptionHandlingMiddleware, mapeo de excepciones de dominio a errores GraphQL | Backend Dev | DE | +| 4.13 | Configurar Dependency Injection | Registrar servicios por capa en Program.cs, configurar HotChocolate con servicios | Backend Dev | DE | +| 4.14 | Implementar migraciones EF Core | Crear migración inicial, script de seed para profesores y materias predefinidas | Backend Dev | DE | + +### Fase 5: Desarrollo Frontend (DE) + +| # | Actividad | Detalle | Rol | Proceso | +|---|-----------|---------|-----|---------| +| 5.1 | Configurar Apollo Angular | Instalar apollo-angular, configurar ApolloModule con endpoint GraphQL, cache policies | Frontend Dev | DE | +| 5.2 | Implementar GraphQL Queries en Angular | Archivos .graphql o queries en TypeScript para students, subjects, classmates | Frontend Dev | DE | +| 5.3 | Implementar GraphQL Mutations en Angular | Mutations para crear/editar estudiante, inscribir/desinscribir materias | Frontend Dev | DE | +| 5.4 | Implementar capa Core - Servicios | StudentService, SubjectService, EnrollmentService usando Apollo Client | Frontend Dev | DE | +| 5.5 | Implementar capa Core - Interceptores | HttpErrorInterceptor para manejo global de errores, LoadingInterceptor para indicador de carga | Frontend Dev | DE | +| 5.6 | Implementar capa Shared - Componentes UI | ButtonComponent, InputComponent, CardComponent, TableComponent, ModalComponent, AlertComponent | Frontend Dev | DE | +| 5.7 | Implementar capa Shared - Directivas y Pipes | HighlightDirective, TooltipDirective, TruncatePipe, CreditsPipe | Frontend Dev | DE | +| 5.8 | Implementar feature Students - Listado | Tabla con estudiantes usando Apollo watchQuery, búsqueda, paginación, acciones CRUD | Frontend Dev | DE | +| 5.9 | Implementar feature Students - Formulario | Reactive form para crear/editar estudiante con validaciones, mutation de Apollo | Frontend Dev | DE | +| 5.10 | Implementar feature Enrollment - Selección materias | Componente con query availableSubjects, validación visual de restricción de profesor, contador de créditos | Frontend Dev | DE | +| 5.11 | Implementar feature Enrollment - Vista compañeros | Query classmates con listado de nombres por materia inscrita | Frontend Dev | DE | +| 5.12 | Implementar gestión de estado | Signals para estado local, Apollo cache para estado del servidor, optimistic updates | Frontend Dev | DE | +| 5.13 | Implementar manejo de errores UI | Toast notifications, estados de error en formularios, manejo de errores GraphQL | Frontend Dev | DE | +| 5.14 | Implementar responsive design | CSS con mobile-first, breakpoints para tablet/desktop, Angular Material responsive | Frontend Dev | DE | +| 5.15 | Implementar loading states | Skeletons, spinners, estados de carga con Apollo loading state | Frontend Dev | DE | + +### Fase 6: Integración GraphQL (DE) + +| # | Actividad | Detalle | Rol | Proceso | +|---|-----------|---------|-----|---------| +| 6.1 | Configurar CORS | Permitir origen del frontend en desarrollo y producción, headers para GraphQL | Backend Dev | DE | +| 6.2 | Configurar Banana Cake Pop | Playground integrado de HotChocolate para testing de queries/mutations | Backend Dev | DE | +| 6.3 | Implementar health checks | Endpoint /health para verificar conectividad a BD y servicios | Backend Dev | DE | +| 6.4 | Configurar Apollo DevTools | Habilitar Apollo DevTools en desarrollo para debugging de queries y cache | Frontend Dev | DE | +| 6.5 | Implementar caché de Apollo | Configurar cache policies: cache-first para datos estáticos, network-only para datos dinámicos | Frontend Dev | DE | +| 6.6 | Generar tipos TypeScript | Usar GraphQL Code Generator para generar tipos desde el schema GraphQL | Frontend Dev | DE | + +### Fase 7: Seguridad (SE) + +| # | Actividad | Detalle | Rol | Proceso | +|---|-----------|---------|-----|---------| +| 7.1 | Implementar validación de entrada | Sanitización de inputs, validación en frontend y backend, prevención de inyección | Security Dev | SE | +| 7.2 | Configurar headers de seguridad | Content-Security-Policy, X-Content-Type-Options, X-Frame-Options, HSTS | Security Dev | SE | +| 7.3 | Implementar rate limiting | Limitar requests por IP para prevenir DoS, configurar en middleware GraphQL | Security Dev | SE | +| 7.4 | Configurar query complexity | Limitar profundidad y complejidad de queries GraphQL para prevenir ataques | Security Dev | SE | +| 7.5 | Validar OWASP Top 10 | Revisar: Injection, Broken Auth, XSS, Insecure Design, Security Misconfiguration | Security Dev | SE | +| 7.6 | Configurar logging seguro | No loguear datos sensibles, structured logging con Serilog, niveles apropiados | Security Dev | SE | + +### Fase 8: Testing (QA) + +| # | Actividad | Detalle | Rol | Proceso | +|---|-----------|---------|-----|---------| +| 8.1 | Unit tests - Domain | Tests para reglas de negocio: validación max 3 materias, restricción profesor, cálculo créditos | QA/Backend Dev | QA | +| 8.2 | Unit tests - Application | Tests para servicios, mappers, validators usando mocks de repositorios | QA/Backend Dev | QA | +| 8.3 | Integration tests - GraphQL | Tests de queries y mutations con WebApplicationFactory, base de datos in-memory | QA/Backend Dev | QA | +| 8.4 | Unit tests - Angular Components | Tests con Jest/Jasmine para componentes, servicios con Apollo testing utilities | QA/Frontend Dev | QA | +| 8.5 | E2E tests | Tests con Playwright para flujos críticos: registro estudiante, inscripción materias | QA | QA | +| 8.6 | Tests de reglas de negocio | Casos de prueba específicos para restricciones: intentar 4ta materia, mismo profesor | QA | QA | +| 8.7 | Code review | Revisión de código aplicando checklist: SOLID, Clean Code, convenciones, seguridad | Líder Técnico | QA | +| 8.8 | Análisis de código estático | Ejecutar analyzers .NET, ESLint, corregir code smells, vulnerabilidades, duplicación | QA | QA | + +### Fase 9: Optimización y Rendimiento (DE) + +| # | Actividad | Detalle | Rol | Proceso | +|---|-----------|---------|-----|---------| +| 9.1 | Optimizar queries EF Core | Revisar N+1 (usar DataLoaders), AsNoTracking para lecturas, proyecciones con Select, índices en BD | Backend Dev | DE | +| 9.2 | Implementar caché backend | Response caching para queries estáticas, memory cache para datos frecuentes | Backend Dev | DE | +| 9.3 | Optimizar bundle Angular | Lazy loading de rutas, tree shaking, análisis de bundle size | Frontend Dev | DE | +| 9.4 | Optimizar rendimiento UI | OnPush change detection, trackBy en ngFor, debounce en búsquedas | Frontend Dev | DE | +| 9.5 | Comprimir assets | Minificación CSS/JS, compresión gzip/brotli | DevOps | DE | + +### Fase 10: Documentación (DO) + +| # | Actividad | Detalle | Rol | Proceso | +|---|-----------|---------|-----|---------| +| 10.1 | Documentar arquitectura | Diagrama C4 (Context, Container, Component), decisiones de arquitectura (ADR) | Arquitecto | DO | +| 10.2 | Documentar esquema GraphQL | Schema completo con descripciones, ejemplos de queries/mutations en Banana Cake Pop | Backend Dev | DO | +| 10.3 | Documentar modelo de datos | Diagrama E-R, diccionario de datos, scripts de creación comentados | DBA | DO | +| 10.4 | README del proyecto | Instrucciones de instalación, configuración, ejecución, estructura del proyecto | Líder Técnico | DO | +| 10.5 | Documentar decisiones técnicas | Por qué Clean Architecture, por qué GraphQL vs REST, por qué Signals vs RxJS | Arquitecto | DO | +| 10.6 | Manual de despliegue | Pasos para deploy, variables de entorno requeridas, checklist pre-producción | DevOps | DO | + +### Fase 11: Entrega y Despliegue (DV) + +| # | Actividad | Detalle | Rol | Proceso | +|---|-----------|---------|-----|---------| +| 11.1 | Configurar Dockerfile GraphQL | Multi-stage build, imagen optimizada, health check, non-root user | DevOps | DV | +| 11.2 | Configurar Dockerfile frontend | Build con Node, serve con Nginx, configuración de proxy para GraphQL | DevOps | DV | +| 11.3 | Crear docker-compose | Orquestación de GraphQL API, frontend, SQL Server, configuración de red y volúmenes | DevOps | DV | +| 11.4 | Pruebas de despliegue | Verificar funcionamiento end-to-end en ambiente containerizado | DevOps/QA | DV | +| 11.5 | Preparar entregables | Empaquetar código fuente, scripts BD, documentación, instrucciones de ejecución | Líder Técnico | DV | +| 11.6 | Validación final | Checklist de requisitos cumplidos, demo funcional, revisión de calidad | Líder Técnico | DV | + +--- + +## Resumen por Proceso + +| Proceso | Actividades | Peso | +|---------|-------------|------| +| Análisis (AN) | 5 | 5% | +| Diseño (DI) | 8 | 15% | +| DevOps (DV) | 12 | 10% | +| Desarrollo Backend (DE) | 14 | 30% | +| Desarrollo Frontend (DE) | 15 | 25% | +| Seguridad (SE) | 6 | 5% | +| Testing (QA) | 8 | 5% | +| Documentación (DO) | 6 | 5% | +| **TOTAL** | **74** | **100%** | + +--- + +## Criterios de Calidad Senior + +### Competencias Demostradas + +| Área | Evidencia | +|------|-----------| +| **Arquitectura** | Clean Architecture, separación de concerns, principios SOLID | +| **GraphQL** | Schema bien diseñado, DataLoaders para N+1, error handling | +| **Código Limpio** | Naming conventions, SRP, métodos pequeños, sin duplicación | +| **Testing** | Cobertura de reglas críticas, tests unitarios y de integración | +| **Seguridad** | OWASP compliance, validación de inputs, query complexity limits | +| **Rendimiento** | Queries optimizadas, caching estratégico, Apollo cache | +| **Mantenibilidad** | Código autodocumentado, arquitectura desacoplada, DTOs | +| **DevOps** | Containerización, configuración por ambiente | +| **Documentación** | Schema GraphQL documentado, README completo | + +### Diferenciadores Senior vs Junior + +| Aspecto | Junior | Senior (Este proyecto) | +|---------|--------|------------------------| +| Arquitectura | Monolítico, acoplado | Clean Architecture, capas bien definidas | +| API | REST básico | GraphQL con HotChocolate, DataLoaders | +| Validaciones | Solo en frontend | Frontend + Backend + Domain | +| Errores | Try-catch genéricos | Excepciones de dominio, Union types para errores | +| Testing | Manual o ninguno | Unit + Integration + E2E automatizados | +| Estado Frontend | Variables globales | Signals + Apollo Cache | +| BD | EF directo en controller | Repository + Unit of Work + Specifications | +| Seguridad | Ninguna | OWASP, headers, rate limiting, query complexity | + +--- + +## Orden de Ejecución Recomendado + +``` +Bloque 1: Fundamentos +├── AN (1.1-1.5) → Requisitos claros +├── DI (2.1-2.8) → Arquitectura definida +└── DV (3.1-3.6) → Ambiente configurado + +Bloque 2: Backend Core +├── DE (4.1-4.7) → Domain + Application + Infrastructure +└── DE (4.8-4.14) → GraphQL API funcional + +Bloque 3: Frontend Core +├── DE (5.1-5.7) → Core + Shared + Apollo config +└── DE (5.8-5.15) → Features completas + +Bloque 4: Integración +└── DE (6.1-6.6) → Frontend ↔ Backend conectados + +Bloque 5: Calidad +├── SE (7.1-7.6) → Seguridad implementada +├── QA (8.1-8.8) → Tests ejecutados +└── DE (9.1-9.5) → Optimizaciones aplicadas + +Bloque 6: Entrega +├── DO (10.1-10.6) → Documentación completa +└── DV (11.1-11.6) → Despliegue validado +``` + +> **Nota:** Ejecutar los bloques secuencialmente. Dentro de cada bloque, las actividades pueden paralelizarse según disponibilidad de recursos. diff --git a/docs/Prueba Técnica.md b/docs/Prueba Técnica.md new file mode 100644 index 0000000..c3cb41f --- /dev/null +++ b/docs/Prueba Técnica.md @@ -0,0 +1,23 @@ +# PRUEBA TECNICA APLICACION WEB + +1. Los resultados deben ser enviados en un documento adjunto. +2. Los entregables deben venir adjuntos. + +## El test consiste en: + +Una aplicación para registro de estudiantes: + +1. Realizar un CRUD que le permita a un usuario realizar un registro en línea. +2. El estudiante se adhiere a un programa de créditos +3. Existen 10 materias +4. Cada materia equivale a 3 créditos. +5. El estudiante sólo podrá seleccionar 3 materias. +6. Hay 5 profesores que dictan 2 materias cada uno. +7. El estudiante no podrá tener clases con el mismo profesor. +8. Cada estudiante puede ver en línea los registros de otros estudiantes. +9. El estudiante podrá ver sólo el nombre de los alumnos con quienes compartirá cada clase. + +## Entregables: + +Una aplicación web o Cliente servidor +Base de datos o scripts para su creación en MySql / SQL diff --git a/docs/architecture/decisions/ADR-001-clean-architecture.md b/docs/architecture/decisions/ADR-001-clean-architecture.md new file mode 100644 index 0000000..1817f47 --- /dev/null +++ b/docs/architecture/decisions/ADR-001-clean-architecture.md @@ -0,0 +1,45 @@ +# ADR-001: Clean Architecture + +**Estado:** Aceptado +**Fecha:** 2026-01-07 + +## Contexto + +Necesitamos una arquitectura que permita: +- Testabilidad de reglas de negocio +- Independencia de frameworks +- Mantenibilidad a largo plazo +- Separación clara de responsabilidades + +## Decisión + +Adoptar **Clean Architecture** con 4 capas: Domain, Application, Adapters, Host. + +``` +Host → Adapters → Application → Domain +``` + +## Consecuencias + +### Positivas +- Domain sin dependencias externas (puro C#) +- Reglas de negocio testeables sin mocks de infraestructura +- Fácil cambiar ORM o base de datos +- Fácil cambiar de REST a GraphQL (o viceversa) + +### Negativas +- Mayor cantidad de archivos/proyectos +- Curva de aprendizaje inicial +- Overhead para proyectos muy pequeños + +## Alternativas Consideradas + +| Alternativa | Razón de Descarte | +|-------------|-------------------| +| N-Layer tradicional | Alto acoplamiento, difícil testear | +| Vertical Slices | Menor separación de concerns | +| Monolítico simple | No escala con complejidad | + +## Referencias + +- [Clean Architecture - Robert C. Martin](https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html) diff --git a/docs/architecture/decisions/ADR-002-graphql-vs-rest.md b/docs/architecture/decisions/ADR-002-graphql-vs-rest.md new file mode 100644 index 0000000..7295ea5 --- /dev/null +++ b/docs/architecture/decisions/ADR-002-graphql-vs-rest.md @@ -0,0 +1,67 @@ +# ADR-002: GraphQL vs REST + +**Estado:** Aceptado +**Fecha:** 2026-01-07 + +## Contexto + +La aplicación tiene relaciones complejas: +- Estudiantes → Inscripciones → Materias → Profesores +- Consultas como "materias disponibles" requieren múltiples joins +- Frontend necesita flexibilidad en datos solicitados + +## Decisión + +Usar **GraphQL** con HotChocolate como API. + +## Consecuencias + +### Positivas +- **No over-fetching:** Cliente pide solo campos necesarios +- **No under-fetching:** Una query obtiene datos relacionados +- **Schema tipado:** Contrato explícito frontend-backend +- **Playground incluido:** Banana Cake Pop para testing +- **DataLoaders:** Resuelve N+1 automáticamente + +### Negativas +- Complejidad adicional vs REST simple +- Curva de aprendizaje GraphQL +- Requiere configurar query complexity limits +- Cache más complejo que HTTP caching + +## Ejemplo Comparativo + +### REST (múltiples requests) +``` +GET /students/1 +GET /students/1/enrollments +GET /subjects/1 +GET /professors/1 +``` + +### GraphQL (una query) +```graphql +query { + student(id: 1) { + name + enrollments { + subject { + name + professor { name } + } + } + } +} +``` + +## Alternativas + +| Alternativa | Razón de Descarte | +|-------------|-------------------| +| REST | Over/under-fetching, múltiples endpoints | +| OData | Menos flexible, menos ecosistema | +| gRPC | No ideal para frontend web | + +## Referencias + +- [HotChocolate Docs](https://chillicream.com/docs/hotchocolate) diff --git a/docs/architecture/decisions/ADR-003-angular-signals.md b/docs/architecture/decisions/ADR-003-angular-signals.md new file mode 100644 index 0000000..8f56211 --- /dev/null +++ b/docs/architecture/decisions/ADR-003-angular-signals.md @@ -0,0 +1,68 @@ +# ADR-003: Signals vs RxJS para Estado Local + +**Estado:** Aceptado +**Fecha:** 2026-01-07 + +## Contexto + +Angular 21 introduce Signals como alternativa a RxJS para estado reactivo. +Necesitamos decidir el enfoque para manejo de estado en el frontend. + +## Decisión + +Usar **Signals para estado local** + **Apollo Client para estado del servidor**. + +```typescript +// Estado local con Signals +students = signal([]); +loading = signal(true); + +// Estado del servidor con Apollo +this.apollo.watchQuery({...}) +``` + +## Consecuencias + +### Positivas +- **Simplicidad:** Signals más intuitivos que BehaviorSubject +- **Performance:** Integración nativa con OnPush +- **Menos boilerplate:** No requiere async pipe en templates +- **Type-safe:** Mejor inferencia de tipos + +### Negativas +- Tecnología relativamente nueva +- Menos operadores que RxJS +- Apollo aún usa Observables internamente + +## Patrón Adoptado + +```typescript +@Component({ + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class StudentListComponent { + // Estado local + students = signal([]); + loading = signal(true); + + // Suscripción a Apollo + ngOnInit() { + this.studentService.getStudents().subscribe(({ data, loading }) => { + this.students.set(data); + this.loading.set(loading); + }); + } +} +``` + +## Alternativas + +| Alternativa | Razón de Descarte | +|-------------|-------------------| +| RxJS puro | Mayor complejidad, más boilerplate | +| NgRx | Overkill para esta aplicación | +| Akita | Dependencia adicional innecesaria | + +## Referencias + +- [Angular Signals](https://angular.dev/guide/signals) diff --git a/docs/architecture/decisions/ADR-004-validation-strategy.md b/docs/architecture/decisions/ADR-004-validation-strategy.md new file mode 100644 index 0000000..97b57b6 --- /dev/null +++ b/docs/architecture/decisions/ADR-004-validation-strategy.md @@ -0,0 +1,70 @@ +# ADR-004: Estrategia de Validación en 3 Capas + +**Estado:** Aceptado +**Fecha:** 2026-01-07 + +## Contexto + +Las reglas de negocio críticas son: +- Máximo 3 materias por estudiante (9 créditos) +- No repetir profesor en inscripciones +- Datos de entrada válidos (email, nombre) + +## Decisión + +Validar en **3 capas** con responsabilidades distintas: + +| Capa | Responsabilidad | Tecnología | +|------|-----------------|------------| +| Frontend | UX, feedback rápido | Reactive Forms | +| Application | Estructura de datos, sanitización | FluentValidation | +| Domain | Reglas de negocio puras | Domain Services | + +## Implementación + +### Frontend (UX) +```typescript +this.form = this.fb.group({ + name: ['', [Validators.required, Validators.minLength(3)]], + email: ['', [Validators.required, Validators.email]], +}); +``` + +### Application (Sanitización + XSS) +```csharp +RuleFor(x => x.Name) + .NotEmpty() + .Must(NotContainDangerousContent); // Previene XSS +``` + +### Domain (Negocio) +```csharp +public void ValidateEnrollment(Student student, Subject subject) +{ + if (student.Enrollments.Count >= 3) + throw new MaxEnrollmentsExceededException(); + + if (student.HasProfessor(subject.ProfessorId)) + throw new SameProfessorConstraintException(); +} +``` + +## Consecuencias + +### Positivas +- Defensa en profundidad +- Separación de responsabilidades +- UX mejorada (errores rápidos) +- Seguridad garantizada (backend siempre valida) + +### Negativas +- Duplicación parcial de reglas +- Mantener sincronizadas las validaciones + +## Regla de Oro + +> **Nunca confíes en el frontend.** El backend SIEMPRE debe validar. + +## Referencias + +- [OWASP Input Validation](https://owasp.org/www-community/Input_Validation) diff --git a/docs/architecture/decisions/README.md b/docs/architecture/decisions/README.md new file mode 100644 index 0000000..5d4849f --- /dev/null +++ b/docs/architecture/decisions/README.md @@ -0,0 +1,29 @@ +# Architecture Decision Records (ADR) + +Registro de decisiones arquitectónicas del proyecto. + +## Índice + +| ADR | Título | Estado | +|-----|--------|--------| +| [ADR-001](ADR-001-clean-architecture.md) | Clean Architecture | Aceptado | +| [ADR-002](ADR-002-graphql-vs-rest.md) | GraphQL vs REST | Aceptado | +| [ADR-003](ADR-003-angular-signals.md) | Signals vs RxJS | Aceptado | +| [ADR-004](ADR-004-validation-strategy.md) | Estrategia de Validación | Aceptado | + +## Formato ADR + +Cada ADR sigue el formato: + +1. **Título:** Nombre descriptivo +2. **Estado:** Propuesto | Aceptado | Deprecado | Reemplazado +3. **Contexto:** Por qué se necesita esta decisión +4. **Decisión:** Qué se decidió +5. **Consecuencias:** Positivas y negativas +6. **Alternativas:** Opciones consideradas y descartadas + +## Convenciones + +- Nuevos ADRs: `ADR-XXX-nombre-descriptivo.md` +- Nunca modificar ADRs aceptados (crear uno nuevo que lo reemplace) +- Mantener este índice actualizado diff --git a/docs/architecture/diagrams/.svg b/docs/architecture/diagrams/.svg new file mode 100644 index 0000000..e69de29 diff --git a/docs/architecture/diagrams/01-use-cases.puml b/docs/architecture/diagrams/01-use-cases.puml new file mode 100644 index 0000000..0988fad --- /dev/null +++ b/docs/architecture/diagrams/01-use-cases.puml @@ -0,0 +1,51 @@ +@startuml use-cases +!theme plain +skinparam actorStyle awesome +skinparam packageStyle rectangle +skinparam usecaseBackgroundColor #F8F9FA +skinparam usecaseBorderColor #495057 +skinparam actorBackgroundColor #007AFF + +title Sistema de Registro de Estudiantes - Diagrama de Casos de Uso + +actor "Estudiante" as student + +rectangle "Sistema de Inscripción" { + usecase "Registrarse en el sistema" as UC1 + usecase "Iniciar sesión" as UC2 + usecase "Ver materias disponibles" as UC3 + usecase "Inscribirse en materia" as UC4 + usecase "Cancelar inscripción" as UC5 + usecase "Ver mis inscripciones" as UC6 + usecase "Ver compañeros de clase" as UC7 + usecase "Actualizar perfil" as UC8 + + usecase "Validar límite de créditos\n(máx 9 créditos)" as UC4a + usecase "Validar restricción de profesor\n(no repetir profesor)" as UC4b +} + +student --> UC1 +student --> UC2 +student --> UC3 +student --> UC4 +student --> UC5 +student --> UC6 +student --> UC7 +student --> UC8 + +UC4 ..> UC4a : <> +UC4 ..> UC4b : <> + +note right of UC4 + Reglas de negocio: + - Máximo 3 materias (9 créditos) + - No puede tener 2 materias + del mismo profesor +end note + +note right of UC7 + Solo muestra nombres + de compañeros por materia +end note + +@enduml diff --git a/docs/architecture/diagrams/01-use-cases.svg b/docs/architecture/diagrams/01-use-cases.svg new file mode 100644 index 0000000..423765e --- /dev/null +++ b/docs/architecture/diagrams/01-use-cases.svg @@ -0,0 +1 @@ +Sistema de Registro de Estudiantes - Diagrama de Casos de UsoSistema de Registro de Estudiantes - Diagrama de Casos de UsoSistema de InscripciónRegistrarse en el sistemaIniciar sesiónVer materias disponiblesInscribirse en materiaCancelar inscripciónVer mis inscripcionesVer compañeros de claseActualizar perfilValidar límite de créditos(máx 9 créditos)Validar restricción de profesor(no repetir profesor)EstudianteReglas de negocio:- Máximo 3 materias (9 créditos)- No puede tener 2 materiasdel mismo profesorSolo muestra nombresde compañeros por materia«include»«include» \ No newline at end of file diff --git a/docs/architecture/diagrams/02-domain-model.puml b/docs/architecture/diagrams/02-domain-model.puml new file mode 100644 index 0000000..02b3505 --- /dev/null +++ b/docs/architecture/diagrams/02-domain-model.puml @@ -0,0 +1,94 @@ +@startuml domain-model +!theme plain +skinparam classAttributeIconSize 0 +skinparam classFontStyle bold +skinparam classBackgroundColor #F8F9FA +skinparam classBorderColor #495057 + +title Sistema de Registro de Estudiantes - Modelo de Dominio + +package "Domain" { + + class Student <> { + - id: int + - name: string + - email: Email + - enrollments: List + -- + + getTotalCredits(): int + + canEnrollIn(subject: Subject): bool + + enroll(subject: Subject): Enrollment + + unenroll(enrollmentId: int): void + } + + class Subject <> { + - id: int + - name: string + - credits: int {= 3} + - professorId: int + -- + + getProfessor(): Professor + } + + class Professor <> { + - id: int + - name: string + - subjects: List + -- + + getSubjects(): List + } + + class Enrollment <> { + - id: int + - studentId: int + - subjectId: int + - enrolledAt: DateTime + -- + + getStudent(): Student + + getSubject(): Subject + } + + class Email <> { + - value: string + -- + + {static} create(value: string): Email + - validate(value: string): void + } + + class EnrollmentDomainService <> { + -- + + validateEnrollment(student: Student, subject: Subject): void + - checkMaxEnrollments(student: Student): void + - checkProfessorConstraint(student: Student, subject: Subject): void + } + + ' Relaciones + Student "1" *-- "0..3" Enrollment : tiene + Subject "1" *-- "0..*" Enrollment : inscripciones + Professor "1" *-- "2" Subject : imparte + Student *-- Email : email + + EnrollmentDomainService ..> Student : valida + EnrollmentDomainService ..> Subject : valida +} + +note bottom of Student + Invariantes: + - Máximo 3 inscripciones + - Email válido y único + - No repetir profesor +end note + +note bottom of Subject + Invariantes: + - Créditos = 3 (fijo) + - Pertenece a un profesor +end note + +note right of Professor + Cada profesor + imparte exactamente + 2 materias +end note + +@enduml diff --git a/docs/architecture/diagrams/02-domain-model.svg b/docs/architecture/diagrams/02-domain-model.svg new file mode 100644 index 0000000..4302393 --- /dev/null +++ b/docs/architecture/diagrams/02-domain-model.svg @@ -0,0 +1 @@ +Sistema de Registro de Estudiantes - Modelo de DominioSistema de Registro de Estudiantes - Modelo de DominioDomain«Entity»Student-id: int-name: string-email: Email-enrollments: List<Enrollment>+getTotalCredits(): int+canEnrollIn(subject: Subject): bool+enroll(subject: Subject): Enrollment+unenroll(enrollmentId: int): void«Entity»Subject-id: int-name: string-credits: int {= 3}-professorId: int+getProfessor(): Professor«Entity»Professor-id: int-name: string-subjects: List<Subject>+getSubjects(): List<Subject>«Entity»Enrollment-id: int-studentId: int-subjectId: int-enrolledAt: DateTime+getStudent(): Student+getSubject(): Subject«Value Object»Email-value: string+create(value: string): Email-validate(value: string): void«Domain Service»EnrollmentDomainService+validateEnrollment(student: Student, subject: Subject): void-checkMaxEnrollments(student: Student): void-checkProfessorConstraint(student: Student, subject: Subject): voidInvariantes:- Máximo 3 inscripciones- Email válido y único- No repetir profesorInvariantes:- Créditos = 3 (fijo)- Pertenece a un profesorCada profesorimparte exactamente2 materiastiene10..3inscripciones10..*imparte12emailvalidavalida \ No newline at end of file diff --git a/docs/architecture/diagrams/03-sequence-enrollment.puml b/docs/architecture/diagrams/03-sequence-enrollment.puml new file mode 100644 index 0000000..91aeb9e --- /dev/null +++ b/docs/architecture/diagrams/03-sequence-enrollment.puml @@ -0,0 +1,91 @@ +@startuml sequence-enrollment +!theme plain +skinparam sequenceMessageAlign center +skinparam responseMessageBelowArrow true +skinparam sequenceParticipantBackgroundColor #F8F9FA +skinparam sequenceParticipantBorderColor #495057 + +title Secuencia: Inscripción de Estudiante en Materia + +actor "Estudiante" as user +participant "Frontend\n(Angular)" as frontend +participant "API GraphQL\n(HotChocolate)" as api +participant "EnrollStudentHandler" as handler +participant "EnrollmentDomainService" as domainService +participant "StudentRepository" as studentRepo +participant "SubjectRepository" as subjectRepo +participant "EnrollmentRepository" as enrollRepo +database "SQL Server" as db + +== Solicitud de Inscripción == + +user -> frontend : Selecciona materia\ny hace clic en "Inscribir" +activate frontend + +frontend -> api : mutation enrollStudent(\n studentId, subjectId) +activate api + +api -> handler : Handle(EnrollStudentCommand) +activate handler + +== Obtención de Datos == + +handler -> studentRepo : GetByIdWithEnrollmentsAsync(studentId) +activate studentRepo +studentRepo -> db : SELECT Student + Enrollments +db --> studentRepo : Student data +studentRepo --> handler : Student +deactivate studentRepo + +handler -> subjectRepo : GetByIdAsync(subjectId) +activate subjectRepo +subjectRepo -> db : SELECT Subject +db --> subjectRepo : Subject data +subjectRepo --> handler : Subject +deactivate subjectRepo + +== Validación de Reglas de Negocio == + +handler -> domainService : ValidateEnrollment(student, subject) +activate domainService + +domainService -> domainService : CheckMaxEnrollments()\n[máx 3 materias] + +alt Estudiante tiene 3 materias + domainService --> handler : throw MaxEnrollmentsExceededException + handler --> api : Error: "Límite de materias alcanzado" + api --> frontend : { errors: [...] } + frontend --> user : Muestra mensaje de error +end + +domainService -> domainService : CheckProfessorConstraint()\n[no repetir profesor] + +alt Ya tiene materia con el profesor + domainService --> handler : throw SameProfessorConstraintException + handler --> api : Error: "Ya tienes materia con este profesor" + api --> frontend : { errors: [...] } + frontend --> user : Muestra mensaje de error +end + +domainService --> handler : Validación OK +deactivate domainService + +== Persistencia == + +handler -> enrollRepo : AddAsync(enrollment) +activate enrollRepo +enrollRepo -> db : INSERT Enrollment +db --> enrollRepo : OK +enrollRepo --> handler : Enrollment +deactivate enrollRepo + +handler --> api : EnrollmentPayload +deactivate handler + +api --> frontend : { enrollment: {...} } +deactivate api + +frontend --> user : Muestra confirmación:\n"Inscrito en [materia]" +deactivate frontend + +@enduml diff --git a/docs/architecture/diagrams/03-sequence-enrollment.svg b/docs/architecture/diagrams/03-sequence-enrollment.svg new file mode 100644 index 0000000..067f282 --- /dev/null +++ b/docs/architecture/diagrams/03-sequence-enrollment.svg @@ -0,0 +1 @@ +Secuencia: Inscripción de Estudiante en MateriaSecuencia: Inscripción de Estudiante en MateriaFrontendAPI GraphQLEnrollStudentHandlerEnrollmentDomainServiceStudentRepositorySubjectRepositoryEnrollmentRepositoryEstudianteFrontendAPI GraphQLEnrollStudentHandlerEnrollmentDomainServiceStudentRepositorySubjectRepositoryEnrollmentRepositorySQL ServerEstudianteEstudianteFrontend(Angular)Frontend(Angular)API GraphQL(HotChocolate)API GraphQL(HotChocolate)EnrollStudentHandlerEnrollStudentHandlerEnrollmentDomainServiceEnrollmentDomainServiceStudentRepositoryStudentRepositorySubjectRepositorySubjectRepositoryEnrollmentRepositoryEnrollmentRepositorySQL ServerSQL ServerFrontendAPI GraphQLEnrollStudentHandlerEnrollmentDomainServiceStudentRepositorySubjectRepositoryEnrollmentRepositorySolicitud de InscripciónSelecciona materiay hace clic en "Inscribir"mutation enrollStudent(studentId, subjectId)Handle(EnrollStudentCommand)Obtención de DatosGetByIdWithEnrollmentsAsync(studentId)SELECT Student + EnrollmentsStudent dataStudentGetByIdAsync(subjectId)SELECT SubjectSubject dataSubjectValidación de Reglas de NegocioValidateEnrollment(student, subject)CheckMaxEnrollments()[máx 3 materias]alt[Estudiante tiene 3 materias]throw MaxEnrollmentsExceededExceptionError: "Límite de materias alcanzado"{ errors: [...] }Muestra mensaje de errorCheckProfessorConstraint()[no repetir profesor]alt[Ya tiene materia con el profesor]throw SameProfessorConstraintExceptionError: "Ya tienes materia con este profesor"{ errors: [...] }Muestra mensaje de errorValidación OKPersistenciaAddAsync(enrollment)INSERT EnrollmentOKEnrollmentEnrollmentPayload{ enrollment: {...} }Muestra confirmación:"Inscrito en [materia]" \ No newline at end of file diff --git a/docs/architecture/diagrams/04-components.puml b/docs/architecture/diagrams/04-components.puml new file mode 100644 index 0000000..5386c33 --- /dev/null +++ b/docs/architecture/diagrams/04-components.puml @@ -0,0 +1,134 @@ +@startuml components +!theme plain +skinparam componentStyle uml2 +skinparam packageStyle rectangle +skinparam componentBackgroundColor #F8F9FA +skinparam componentBorderColor #495057 +skinparam packageBackgroundColor #FFFFFF +skinparam packageBorderColor #DEE2E6 + +title Sistema de Registro de Estudiantes - Arquitectura de Componentes + +package "Frontend (Angular 21)" as frontend { + [App Component] as app + [Student List] as studentList + [Student Form] as studentForm + [Enrollment Page] as enrollPage + [Classmates Page] as classmates + + package "Core" { + [Apollo Client] as apollo + [Student Service] as studentSvc + [Enrollment Service] as enrollSvc + [Connectivity Service] as connSvc + [Error Handler] as errorHandler + } + + package "Shared" { + [Connectivity Overlay] as overlay + [Loading Spinner] as spinner + [Confirm Dialog] as dialog + } +} + +package "Backend (.NET 10)" as backend { + + package "Host" as host { + [Program.cs] as program + [DI Container] as di + } + + package "Adapters" as adapters { + package "Driving (Primary)" { + [GraphQL API\n(HotChocolate)] as graphql + [Query] as query + [Mutation] as mutation + [Types] as types + } + + package "Driven (Secondary)" { + [Repositories] as repos + [DataLoaders] as loaders + [DbContext] as dbContext + } + } + + package "Application" as application { + [Commands] as commands + [Queries] as queries + [Handlers] as handlers + [Validators] as validators + [DTOs] as dtos + } + + package "Domain" as domain { + [Entities] as entities + [Value Objects] as valueObjects + [Domain Services] as domainSvc + [Ports (Interfaces)] as ports + } +} + +database "SQL Server 2022" as sqlserver { + [Students] + [Subjects] + [Professors] + [Enrollments] +} + +cloud "Browser" as browser + +' Conexiones Frontend +browser --> app +app --> studentList +app --> studentForm +app --> enrollPage +app --> classmates +app --> overlay + +studentList --> studentSvc +studentForm --> studentSvc +enrollPage --> enrollSvc +classmates --> enrollSvc +overlay --> connSvc + +studentSvc --> apollo +enrollSvc --> apollo +connSvc ..> errorHandler + +' Conexiones Backend +apollo --> graphql : HTTP/GraphQL + +graphql --> query +graphql --> mutation +query --> handlers +mutation --> handlers +handlers --> validators +handlers --> commands +handlers --> queries + +commands --> domainSvc +queries --> repos +domainSvc --> entities +domainSvc --> valueObjects + +repos --> dbContext +loaders --> dbContext +dbContext --> sqlserver + +' Implementación de puertos +repos ..|> ports : implements + +note right of domain + Regla de Dependencia: + Domain no depende + de capas externas +end note + +note bottom of graphql + Endpoints: + - /graphql + - /health +end note + +@enduml diff --git a/docs/architecture/diagrams/04-components.svg b/docs/architecture/diagrams/04-components.svg new file mode 100644 index 0000000..bd13627 --- /dev/null +++ b/docs/architecture/diagrams/04-components.svg @@ -0,0 +1 @@ +Sistema de Registro de Estudiantes - Arquitectura de ComponentesSistema de Registro de Estudiantes - Arquitectura de ComponentesFrontend (Angular 21)CoreSharedBackend (.NET 10)HostAdaptersDriving (Primary)Driven (Secondary)ApplicationDomainSQL Server 2022App ComponentStudent ListStudent FormEnrollment PageClassmates PageApollo ClientStudent ServiceEnrollment ServiceConnectivity ServiceError HandlerConnectivity OverlayLoading SpinnerConfirm DialogProgram.csDI ContainerGraphQL API(HotChocolate)QueryMutationTypesRepositoriesDataLoadersDbContextCommandsQueriesHandlersValidatorsDTOsEntitiesValue ObjectsDomain ServicesPorts (Interfaces)StudentsSubjectsProfessorsEnrollmentsBrowserRegla de Dependencia:Domain no dependede capas externasEndpoints:- /graphql- /healthHTTP/GraphQLimplements \ No newline at end of file diff --git a/docs/architecture/diagrams/05-entity-relationship.puml b/docs/architecture/diagrams/05-entity-relationship.puml new file mode 100644 index 0000000..6b1407c --- /dev/null +++ b/docs/architecture/diagrams/05-entity-relationship.puml @@ -0,0 +1,72 @@ +@startuml entity-relationship +!theme plain +skinparam linetype ortho +skinparam classBackgroundColor #F8F9FA +skinparam classBorderColor #495057 + +title Sistema de Registro de Estudiantes - Diagrama Entidad-Relación + +entity "Students" as students { + * **Id** : int <> + -- + * Name : nvarchar(100) + * Email : nvarchar(255) <> + * CreatedAt : datetime2 + UpdatedAt : datetime2 +} + +entity "Professors" as professors { + * **Id** : int <> + -- + * Name : nvarchar(100) +} + +entity "Subjects" as subjects { + * **Id** : int <> + -- + * Name : nvarchar(100) + * Credits : int {= 3} + * **ProfessorId** : int <> +} + +entity "Enrollments" as enrollments { + * **Id** : int <> + -- + * **StudentId** : int <> + * **SubjectId** : int <> + * EnrolledAt : datetime2 + -- + <> (StudentId, SubjectId) +} + +' Relaciones +students ||--o{ enrollments : "tiene" +subjects ||--o{ enrollments : "inscripciones" +professors ||--|| subjects : "imparte 2" + +note right of students + Restricciones: + - Email único + - Máximo 3 enrollments +end note + +note right of subjects + Datos iniciales: + 10 materias + 3 créditos cada una +end note + +note right of professors + Datos iniciales: + 5 profesores + 2 materias cada uno +end note + +note bottom of enrollments + Reglas de negocio: + - (StudentId, SubjectId) único + - Estudiante no puede tener + 2 materias del mismo profesor +end note + +@enduml diff --git a/docs/architecture/diagrams/05-entity-relationship.svg b/docs/architecture/diagrams/05-entity-relationship.svg new file mode 100644 index 0000000..0817a59 --- /dev/null +++ b/docs/architecture/diagrams/05-entity-relationship.svg @@ -0,0 +1 @@ +Sistema de Registro de Estudiantes - Diagrama Entidad-RelaciónSistema de Registro de Estudiantes - Diagrama Entidad-RelaciónStudentsId: int «PK»Name : nvarchar(100)Email : nvarchar(255) «unique»CreatedAt : datetime2UpdatedAt : datetime2ProfessorsId: int «PK»Name : nvarchar(100)SubjectsId: int «PK»Name : nvarchar(100)Credits : int {= 3}ProfessorId: int «FK»EnrollmentsId: int «PK»StudentId: int «FK»SubjectId: int «FK»EnrolledAt : datetime2«unique» (StudentId, SubjectId)Restricciones:- Email único- Máximo 3 enrollmentsDatos iniciales:10 materias3 créditos cada unaDatos iniciales:5 profesores2 materias cada unoReglas de negocio:- (StudentId, SubjectId) único- Estudiante no puede tener2 materias del mismo profesortieneinscripcionesimparte 2 \ No newline at end of file diff --git a/docs/architecture/diagrams/06-state-enrollment.puml b/docs/architecture/diagrams/06-state-enrollment.puml new file mode 100644 index 0000000..fa6585c --- /dev/null +++ b/docs/architecture/diagrams/06-state-enrollment.puml @@ -0,0 +1,49 @@ +@startuml state-enrollment +!theme plain +skinparam stateBackgroundColor #F8F9FA +skinparam stateBorderColor #495057 + +title Estado de Inscripción del Estudiante + +[*] --> SinMaterias : Registro inicial + +state "Sin Materias" as SinMaterias { + state "0 créditos" as cred0 +} + +state "Inscripción Parcial" as Parcial { + state "3 créditos\n(1 materia)" as cred3 + state "6 créditos\n(2 materias)" as cred6 +} + +state "Inscripción Completa" as Completa { + state "9 créditos\n(3 materias)" as cred9 +} + +SinMaterias --> Parcial : inscribir(materia) +Parcial --> Parcial : inscribir(materia)\n[créditos < 9] +Parcial --> Completa : inscribir(materia)\n[créditos = 6] +Completa --> Parcial : cancelar(inscripción) +Parcial --> SinMaterias : cancelar(inscripción)\n[única materia] + +note right of Completa + No puede inscribir + más materias +end note + +note left of Parcial + Validaciones en cada inscripción: + - Límite de créditos + - No repetir profesor + - Materia no duplicada +end note + +state validacion <> + +SinMaterias --> validacion : intenta inscribir +Parcial --> validacion : intenta inscribir + +validacion --> Parcial : [válido] +validacion --> [*] : [inválido]\nmuestra error + +@enduml diff --git a/docs/architecture/diagrams/06-state-enrollment.svg b/docs/architecture/diagrams/06-state-enrollment.svg new file mode 100644 index 0000000..4412e1c --- /dev/null +++ b/docs/architecture/diagrams/06-state-enrollment.svg @@ -0,0 +1 @@ +Estado de Inscripción del EstudianteEstado de Inscripción del EstudianteSin Materias0 créditosInscripción Parcial3 créditos(1 materia)6 créditos(2 materias)Inscripción Completa9 créditos(3 materias)No puede inscribirmás materiasValidaciones en cada inscripción:- Límite de créditos- No repetir profesor- Materia no duplicadaRegistro inicialinscribir(materia)cancelar(inscripción)[única materia]inscribir(materia)[créditos < 9]inscribir(materia)[créditos = 6]cancelar(inscripción)intenta inscribirintenta inscribir[válido][inválido]muestra error \ No newline at end of file diff --git a/docs/architecture/diagrams/07-deployment.puml b/docs/architecture/diagrams/07-deployment.puml new file mode 100644 index 0000000..9a66898 --- /dev/null +++ b/docs/architecture/diagrams/07-deployment.puml @@ -0,0 +1,67 @@ +@startuml deployment +!theme plain +skinparam nodeBackgroundColor #F8F9FA +skinparam nodeBorderColor #495057 +skinparam componentBackgroundColor #E9ECEF + +title Sistema de Registro de Estudiantes - Diagrama de Despliegue + +node "Cliente" as client { + component "Navegador Web" as browser { + [Angular SPA] + } +} + +node "Docker Host" as docker { + + node "student-frontend" as frontendContainer <> { + component "Nginx" as nginx { + [Static Files] + [Reverse Proxy] + } + } + + node "student-api" as apiContainer <> { + component "ASP.NET Core" as aspnet { + [Kestrel Server] + [GraphQL Endpoint] + [Health Check] + } + } + + node "student-db" as dbContainer <> { + database "SQL Server 2022" as sqlserver { + [StudentEnrollment DB] + } + } +} + +' Conexiones +browser --> nginx : HTTP :80 +nginx --> aspnet : HTTP :5000\n/graphql +aspnet --> sqlserver : TCP :1433 + +note right of nginx + Nginx Config: + - Gzip/Brotli compression + - Static file caching + - GraphQL proxy + - Security headers +end note + +note right of aspnet + Optimizaciones: + - Server GC + - ReadyToRun + - Connection pooling + - Rate limiting +end note + +note right of sqlserver + Recursos: + - 2 CPU cores + - 2.5 GB RAM + - Persistent volume +end note + +@enduml diff --git a/docs/architecture/diagrams/07-deployment.svg b/docs/architecture/diagrams/07-deployment.svg new file mode 100644 index 0000000..df29dfd --- /dev/null +++ b/docs/architecture/diagrams/07-deployment.svg @@ -0,0 +1 @@ +Sistema de Registro de Estudiantes - Diagrama de DespliegueSistema de Registro de Estudiantes - Diagrama de DespliegueClienteNavegador WebDocker Host«container»student-frontendNginx«container»student-apiASP.NET Core«container»student-dbSQL Server 2022Angular SPAStatic FilesReverse ProxyKestrel ServerGraphQL EndpointHealth CheckStudentEnrollment DBNginx Config:- Gzip/Brotli compression- Static file caching- GraphQL proxy- Security headersOptimizaciones:- Server GC- ReadyToRun- Connection pooling- Rate limitingRecursos:- 2 CPU cores- 2.5 GB RAM- Persistent volumeHTTP :80HTTP :5000/graphqlTCP :1433 \ No newline at end of file diff --git a/docs/architecture/diagrams/08-c4-context.puml b/docs/architecture/diagrams/08-c4-context.puml new file mode 100644 index 0000000..3dc0cfc --- /dev/null +++ b/docs/architecture/diagrams/08-c4-context.puml @@ -0,0 +1,54 @@ +@startuml c4-context +!theme plain +skinparam rectangleBackgroundColor #F8F9FA +skinparam rectangleBorderColor #495057 + +title Sistema de Registro de Estudiantes - Diagrama de Contexto (C4 Level 1) + +skinparam rectangle { + RoundCorner 10 +} + +actor "Estudiante" as student <> + +rectangle "Sistema de Registro\nde Estudiantes" as system <> #lightblue { +} + +rectangle "Base de Datos\nSQL Server" as database <> #lightgray { +} + +student --> system : Usa para registrarse\ne inscribirse en materias +system --> database : Lee y escribe\ndatos de inscripciones + +note right of student + Estudiante + Usuario del sistema que: + - Se registra en el sistema + - Se inscribe en materias (máx 3) + - Ve sus compañeros de clase + - Consulta inscripciones +end note + +note right of system + Sistema de Registro + Aplicación web que permite: + - CRUD de estudiantes + - Inscripción en materias + - Validación de reglas de negocio + - Visualización de compañeros + + Stack: + Frontend: Angular 21 + Backend: .NET 10 + GraphQL +end note + +note right of database + SQL Server 2022 + Almacena: + - Estudiantes + - Profesores + - Materias + - Inscripciones +end note + +@enduml diff --git a/docs/architecture/diagrams/08-c4-context.svg b/docs/architecture/diagrams/08-c4-context.svg new file mode 100644 index 0000000..b9e691a --- /dev/null +++ b/docs/architecture/diagrams/08-c4-context.svg @@ -0,0 +1 @@ +Sistema de Registro de Estudiantes - Diagrama de Contexto (C4 Level 1)Sistema de Registro de Estudiantes - Diagrama de Contexto (C4 Level 1)«Software System»Sistema de Registrode Estudiantes«External System»Base de DatosSQL ServerEstudiante«Persona»EstudianteUsuario del sistema que:- Se registra en el sistema- Se inscribe en materias (máx 3)- Ve sus compañeros de clase- Consulta inscripcionesSistema de RegistroAplicación web que permite:- CRUD de estudiantes- Inscripción en materias- Validación de reglas de negocio- Visualización de compañeros Stack:Frontend: Angular 21Backend: .NET 10 + GraphQLSQL Server 2022Almacena:- Estudiantes- Profesores- Materias- InscripcionesUsa para registrarsee inscribirse en materiasLee y escribedatos de inscripciones \ No newline at end of file diff --git a/docs/architecture/diagrams/README.md b/docs/architecture/diagrams/README.md new file mode 100644 index 0000000..4d83347 --- /dev/null +++ b/docs/architecture/diagrams/README.md @@ -0,0 +1,45 @@ +# Diagramas UML - Sistema de Registro de Estudiantes + +Diagramas de arquitectura y diseño del sistema, creados con PlantUML siguiendo el estándar UML 2.5. + +## Diagramas Disponibles + +| # | Diagrama | Archivo | Descripción | +|---|----------|---------|-------------| +| 1 | Casos de Uso | [01-use-cases](./01-use-cases.svg) | Funcionalidades del sistema desde perspectiva del estudiante | +| 2 | Modelo de Dominio | [02-domain-model](./02-domain-model.svg) | Entidades, Value Objects y servicios de dominio | +| 3 | Secuencia: Inscripción | [03-sequence-enrollment](./03-sequence-enrollment.svg) | Flujo completo del proceso de inscripción | +| 4 | Componentes | [04-components](./04-components.svg) | Arquitectura de componentes frontend y backend | +| 5 | Entidad-Relación | [05-entity-relationship](./05-entity-relationship.svg) | Modelo de base de datos | +| 6 | Estados: Inscripción | [06-state-enrollment](./06-state-enrollment.svg) | Estados del estudiante según créditos | +| 7 | Despliegue | [07-deployment](./07-deployment.svg) | Infraestructura Docker | +| 8 | C4 Contexto | [08-c4-context](./08-c4-context.svg) | Vista de alto nivel del sistema | + +## Regenerar Diagramas + +Los archivos `.puml` son la fuente. Para regenerar los SVG: + +```bash +cd docs/architecture/diagrams + +# Regenerar todos +for f in *.puml; do + cat "$f" | plantuml -tsvg -pipe > "${f%.puml}.svg" +done + +# O uno específico +cat 01-use-cases.puml | plantuml -tsvg -pipe > 01-use-cases.svg +``` + +## Requisitos + +- [PlantUML](https://plantuml.com/) instalado +- Java Runtime (requerido por PlantUML) + +```bash +# Ubuntu/Debian +sudo apt install plantuml + +# Verificar instalación +plantuml -version +``` diff --git a/docs/entregables/01-analisis/historias-usuario/AN-003-historias-usuario.md b/docs/entregables/01-analisis/historias-usuario/AN-003-historias-usuario.md new file mode 100644 index 0000000..4f2087d --- /dev/null +++ b/docs/entregables/01-analisis/historias-usuario/AN-003-historias-usuario.md @@ -0,0 +1,386 @@ +# AN-003: Historias de Usuario + +**Proyecto:** Sistema de Registro de Estudiantes - Inter Rapidísimo +**Rol:** Product Owner +**Fecha:** 2026-01-07 + +--- + +## 1. Épica Principal + +**EP-001: Sistema de Inscripción de Estudiantes** + +Como institución educativa, necesito un sistema web que permita a los estudiantes registrarse e inscribirse en materias siguiendo las reglas del programa de créditos, para gestionar eficientemente el proceso de matrícula. + +--- + +## 2. Historias de Usuario + +### US-001: Registro de Estudiante + +| Campo | Valor | +|-------|-------| +| **ID** | US-001 | +| **Épica** | EP-001 | +| **Prioridad** | Alta | +| **Story Points** | 5 | +| **Sprint** | 1 | + +**Historia:** +> Como **estudiante nuevo**, +> quiero **registrarme en el sistema con mi nombre y correo electrónico**, +> para **poder acceder al programa de inscripción de materias**. + +**Criterios de Aceptación:** + +```gherkin +Scenario: Registro exitoso de estudiante + Given estoy en la página de registro + When ingreso un nombre válido "Juan Pérez" + And ingreso un email válido "juan@email.com" + And presiono el botón "Registrar" + Then el sistema crea mi cuenta + And veo un mensaje de confirmación + And soy redirigido a la página de inscripción + +Scenario: Registro con email duplicado + Given existe un estudiante con email "juan@email.com" + When intento registrarme con el mismo email + Then veo el mensaje "Ya existe un estudiante con este email" + And el formulario no se envía + +Scenario: Registro con email inválido + Given estoy en la página de registro + When ingreso un email inválido "juanemail.com" + Then veo el mensaje "Formato de email no válido" + And el botón de registro está deshabilitado +``` + +**Notas Técnicas:** +- Validación de email en frontend y backend +- Email almacenado en minúsculas (normalización) + +--- + +### US-002: Consulta de Materias Disponibles + +| Campo | Valor | +|-------|-------| +| **ID** | US-002 | +| **Épica** | EP-001 | +| **Prioridad** | Alta | +| **Story Points** | 3 | +| **Sprint** | 1 | + +**Historia:** +> Como **estudiante registrado**, +> quiero **ver el catálogo de las 10 materias disponibles**, +> para **conocer mis opciones de inscripción**. + +**Criterios de Aceptación:** + +```gherkin +Scenario: Ver catálogo completo + Given estoy autenticado como estudiante + When accedo a la sección de materias + Then veo una lista de 10 materias + And cada materia muestra nombre, créditos (3) y profesor + +Scenario: Ver detalle de materia + Given estoy viendo el catálogo de materias + When selecciono "Matemáticas I" + Then veo el nombre del profesor asignado + And veo que equivale a 3 créditos +``` + +--- + +### US-003: Inscripción en Materia + +| Campo | Valor | +|-------|-------| +| **ID** | US-003 | +| **Épica** | EP-001 | +| **Prioridad** | Alta | +| **Story Points** | 8 | +| **Sprint** | 1 | + +**Historia:** +> Como **estudiante registrado**, +> quiero **inscribirme en una materia disponible**, +> para **acumular créditos en mi programa académico**. + +**Criterios de Aceptación:** + +```gherkin +Scenario: Inscripción exitosa + Given tengo menos de 3 materias inscritas + And la materia "Física I" está disponible para mí + When presiono "Inscribir" en "Física I" + Then la materia se agrega a mis inscripciones + And mis créditos aumentan en 3 + And veo mensaje de confirmación + +Scenario: Intento inscripción con máximo alcanzado + Given ya tengo 3 materias inscritas + When intento inscribir otra materia + Then veo el mensaje "Ya tienes el máximo de 3 materias" + And el botón de inscripción está deshabilitado + +Scenario: Intento inscripción con mismo profesor + Given estoy inscrito en "Matemáticas I" (Profesor A) + When intento inscribir "Matemáticas II" (Profesor A) + Then veo el mensaje "Ya tienes una materia con este profesor" + And la inscripción no se procesa +``` + +**Notas Técnicas:** +- Validaciones RN-003 y RN-005 en capa de dominio +- Transacción atómica para inscripción + +--- + +### US-004: Cancelación de Inscripción + +| Campo | Valor | +|-------|-------| +| **ID** | US-004 | +| **Épica** | EP-001 | +| **Prioridad** | Media | +| **Story Points** | 3 | +| **Sprint** | 1 | + +**Historia:** +> Como **estudiante inscrito**, +> quiero **cancelar la inscripción de una materia**, +> para **liberar espacio y poder inscribir otra**. + +**Criterios de Aceptación:** + +```gherkin +Scenario: Cancelación exitosa + Given estoy inscrito en "Física I" + When presiono "Cancelar inscripción" en esa materia + And confirmo la acción + Then la materia se elimina de mis inscripciones + And mis créditos disminuyen en 3 + And la materia vuelve a estar disponible + +Scenario: Cancelar con confirmación + Given estoy inscrito en "Física I" + When presiono "Cancelar inscripción" + Then veo un diálogo de confirmación + And puedo cancelar o confirmar la acción +``` + +--- + +### US-005: Visualización de Materias No Disponibles + +| Campo | Valor | +|-------|-------| +| **ID** | US-005 | +| **Épica** | EP-001 | +| **Prioridad** | Media | +| **Story Points** | 3 | +| **Sprint** | 2 | + +**Historia:** +> Como **estudiante registrado**, +> quiero **ver claramente qué materias no puedo inscribir y por qué**, +> para **tomar decisiones informadas sobre mi inscripción**. + +**Criterios de Aceptación:** + +```gherkin +Scenario: Ver materias bloqueadas por profesor + Given estoy inscrito en "Matemáticas I" (Profesor A) + When veo el catálogo de materias + Then "Matemáticas II" aparece deshabilitada + And veo el tooltip "Ya tienes materia con Profesor A" + +Scenario: Ver contador de materias restantes + Given tengo 2 materias inscritas + When veo mi panel de inscripción + Then veo "2/3 materias inscritas" + And veo "Puedes inscribir 1 materia más" +``` + +--- + +### US-006: Consulta de Otros Estudiantes + +| Campo | Valor | +|-------|-------| +| **ID** | US-006 | +| **Épica** | EP-001 | +| **Prioridad** | Baja | +| **Story Points** | 2 | +| **Sprint** | 2 | + +**Historia:** +> Como **estudiante registrado**, +> quiero **ver la lista de otros estudiantes en el sistema**, +> para **conocer quiénes están en el programa**. + +**Criterios de Aceptación:** + +```gherkin +Scenario: Ver lista de estudiantes + Given estoy autenticado como estudiante + When accedo a la sección de estudiantes + Then veo una lista de todos los estudiantes registrados + And cada estudiante muestra solo su nombre +``` + +**Notas:** +- Por privacidad, no se muestra email ni materias de otros estudiantes + +--- + +### US-007: Ver Compañeros de Clase + +| Campo | Valor | +|-------|-------| +| **ID** | US-007 | +| **Épica** | EP-001 | +| **Prioridad** | Media | +| **Story Points** | 5 | +| **Sprint** | 2 | + +**Historia:** +> Como **estudiante inscrito en materias**, +> quiero **ver los nombres de mis compañeros en cada clase**, +> para **conocer con quiénes compartiré el curso**. + +**Criterios de Aceptación:** + +```gherkin +Scenario: Ver compañeros por materia + Given estoy inscrito en "Física I" + And otros estudiantes también están inscritos en "Física I" + When accedo a "Mis compañeros de clase" + Then veo "Física I" como sección + And bajo ella veo la lista de nombres de compañeros + +Scenario: Sin compañeros en materia + Given estoy inscrito en "Redes I" + And ningún otro estudiante está inscrito en "Redes I" + When accedo a "Mis compañeros de clase" + Then veo "Redes I" con mensaje "Sin compañeros aún" + +Scenario: Privacidad de datos + Given estoy viendo mis compañeros de clase + Then solo veo los nombres + And NO veo emails ni otras materias de mis compañeros +``` + +--- + +### US-008: Actualización de Datos Personales + +| Campo | Valor | +|-------|-------| +| **ID** | US-008 | +| **Épica** | EP-001 | +| **Prioridad** | Baja | +| **Story Points** | 2 | +| **Sprint** | 2 | + +**Historia:** +> Como **estudiante registrado**, +> quiero **actualizar mi nombre o correo electrónico**, +> para **mantener mis datos actualizados**. + +**Criterios de Aceptación:** + +```gherkin +Scenario: Actualización exitosa de nombre + Given estoy en mi perfil + When cambio mi nombre a "Juan Carlos Pérez" + And presiono "Guardar" + Then mis datos se actualizan + And veo mensaje de confirmación + +Scenario: Cambio de email a uno existente + Given existe otro estudiante con email "otro@email.com" + When intento cambiar mi email a "otro@email.com" + Then veo el mensaje "Este email ya está registrado" + And el cambio no se procesa +``` + +--- + +### US-009: Eliminación de Cuenta + +| Campo | Valor | +|-------|-------| +| **ID** | US-009 | +| **Épica** | EP-001 | +| **Prioridad** | Baja | +| **Story Points** | 2 | +| **Sprint** | 2 | + +**Historia:** +> Como **estudiante registrado**, +> quiero **eliminar mi cuenta del sistema**, +> para **remover mis datos si ya no deseo participar**. + +**Criterios de Aceptación:** + +```gherkin +Scenario: Eliminación con confirmación + Given estoy en mi perfil + When presiono "Eliminar cuenta" + Then veo un diálogo de confirmación + When confirmo la eliminación + Then mi cuenta y todas mis inscripciones se eliminan + And soy redirigido a la página de inicio +``` + +--- + +## 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 | + +**Total Story Points:** 33 + +--- + +## 4. Definición de Listo (DoR) + +- [ ] Historia tiene criterios de aceptación claros +- [ ] Dependencias identificadas +- [ ] Estimación en story points +- [ ] Diseño de UI aprobado (si aplica) +- [ ] APIs definidas + +## 5. Definición de Terminado (DoD) + +- [ ] Código implementado y revisado +- [ ] Tests unitarios (>80% coverage) +- [ ] Tests de integración pasando +- [ ] Documentación actualizada +- [ ] Validaciones de negocio probadas +- [ ] Sin deuda técnica crítica + +--- + +## 6. Aprobación + +| Rol | Nombre | Fecha | Firma | +|-----|--------|-------|-------| +| Product Owner | Sistema | 2026-01-07 | ✓ | +| Scrum Master | Pendiente | - | - | +| Tech Lead | Pendiente | - | - | diff --git a/docs/entregables/01-analisis/reglas-negocio/AN-002-reglas-negocio.md b/docs/entregables/01-analisis/reglas-negocio/AN-002-reglas-negocio.md new file mode 100644 index 0000000..31ab603 --- /dev/null +++ b/docs/entregables/01-analisis/reglas-negocio/AN-002-reglas-negocio.md @@ -0,0 +1,304 @@ +# AN-002: Reglas de Negocio + +**Proyecto:** Sistema de Registro de Estudiantes - Inter Rapidísimo +**Rol:** Analista de Sistemas +**Fecha:** 2026-01-07 + +--- + +## 1. Resumen + +Este documento define las reglas de negocio que gobiernan el sistema de registro de estudiantes. Estas reglas son invariantes y deben ser validadas en la capa de dominio. + +--- + +## 2. Reglas de Negocio + +### RN-001: Estructura del Catálogo de Materias + +| Atributo | Valor | +|----------|-------| +| **ID** | RN-001 | +| **Nombre** | Catálogo Fijo de Materias | +| **Descripción** | El sistema contiene exactamente 10 materias académicas | +| **Tipo** | Restricción Estructural | +| **Severidad** | Crítica | + +**Datos Iniciales (Seed):** + +| ID | Materia | Profesor Asignado | +|----|---------|-------------------| +| 1 | Matemáticas I | Profesor A | +| 2 | Matemáticas II | Profesor A | +| 3 | Física I | Profesor B | +| 4 | Física II | Profesor B | +| 5 | Programación I | Profesor C | +| 6 | Programación II | Profesor C | +| 7 | Base de Datos I | Profesor D | +| 8 | Base de Datos II | Profesor D | +| 9 | Redes I | Profesor E | +| 10 | Redes II | Profesor E | + +**Validación:** `COUNT(Subjects) == 10` + +--- + +### RN-002: Valor Uniforme de Créditos + +| Atributo | Valor | +|----------|-------| +| **ID** | RN-002 | +| **Nombre** | Créditos por Materia | +| **Descripción** | Cada materia equivale exactamente a 3 créditos | +| **Tipo** | Restricción de Valor | +| **Severidad** | Crítica | + +**Fórmula:** +``` +Créditos_Materia = 3 (constante) +Créditos_Estudiante = COUNT(Inscripciones) × 3 +Créditos_Máximos = 9 (3 materias × 3 créditos) +``` + +**Validación:** `Subject.Credits == 3` + +--- + +### RN-003: Límite de Inscripciones por Estudiante + +| Atributo | Valor | +|----------|-------| +| **ID** | RN-003 | +| **Nombre** | Máximo 3 Materias | +| **Descripción** | Un estudiante solo puede inscribirse en máximo 3 materias | +| **Tipo** | Restricción de Cardinalidad | +| **Severidad** | Crítica | + +**Escenarios:** + +| Materias Actuales | Acción | Resultado | +|-------------------|--------|-----------| +| 0-2 | Inscribir | ✓ Permitido | +| 3 | Inscribir | ✗ Rechazado | +| 1-3 | Cancelar | ✓ Permitido | + +**Validación:** `Student.Enrollments.Count <= 3` + +**Mensaje de Error:** "El estudiante ya tiene el máximo de 3 materias inscritas" + +--- + +### RN-004: Asignación Profesores-Materias + +| Atributo | Valor | +|----------|-------| +| **ID** | RN-004 | +| **Nombre** | Distribución de Profesores | +| **Descripción** | Existen 5 profesores, cada uno dicta exactamente 2 materias | +| **Tipo** | Restricción Estructural | +| **Severidad** | Crítica | + +**Invariantes:** +- `COUNT(Professors) == 5` +- `∀ Professor: COUNT(Professor.Subjects) == 2` +- `∀ Subject: Subject.Professor != NULL` + +**Validación:** +```csharp +professors.All(p => p.Subjects.Count == 2) +subjects.All(s => s.ProfessorId != null) +``` + +--- + +### RN-005: Restricción de Profesor Único (CRÍTICA) + +| Atributo | Valor | +|----------|-------| +| **ID** | RN-005 | +| **Nombre** | Prohibición de Mismo Profesor | +| **Descripción** | Un estudiante NO puede inscribirse en dos materias dictadas por el mismo profesor | +| **Tipo** | Restricción de Integridad | +| **Severidad** | **CRÍTICA** | + +**Lógica de Validación:** + +``` +PARA inscribir(estudiante, nuevaMateria): + profesorNuevaMateria = nuevaMateria.Profesor + profesoresActuales = estudiante.Inscripciones.Select(i => i.Materia.Profesor) + + SI profesorNuevaMateria EN profesoresActuales: + RECHAZAR "Ya tienes una materia con este profesor" + SINO: + PERMITIR inscripción +``` + +**Casos de Prueba:** + +| Estudiante tiene | Intenta inscribir | Resultado | +|------------------|-------------------|-----------| +| Matemáticas I (Prof A) | Matemáticas II (Prof A) | ✗ Rechazado | +| Matemáticas I (Prof A) | Física I (Prof B) | ✓ Permitido | +| Física I (Prof B), Programación I (Prof C) | Redes I (Prof E) | ✓ Permitido | +| Física I (Prof B), Programación I (Prof C) | Física II (Prof B) | ✗ Rechazado | + +**Mensaje de Error:** "No puedes inscribir {materia} porque ya tienes una materia con {profesor}" + +--- + +### RN-006: Unicidad de Email + +| Atributo | Valor | +|----------|-------| +| **ID** | RN-006 | +| **Nombre** | Email Único por Estudiante | +| **Descripción** | Cada estudiante debe tener un email único en el sistema | +| **Tipo** | Restricción de Unicidad | +| **Severidad** | Alta | + +**Validación:** `UNIQUE(Student.Email)` + +**Mensaje de Error:** "Ya existe un estudiante registrado con este email" + +--- + +### RN-007: Formato de Email Válido + +| Atributo | Valor | +|----------|-------| +| **ID** | RN-007 | +| **Nombre** | Validación de Email | +| **Descripción** | El email debe cumplir formato estándar RFC 5322 | +| **Tipo** | Restricción de Formato | +| **Severidad** | Alta | + +**Patrón:** `^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$` + +**Mensaje de Error:** "El formato del email no es válido" + +--- + +### RN-008: Visibilidad de Compañeros + +| Atributo | Valor | +|----------|-------| +| **ID** | RN-008 | +| **Nombre** | Privacidad de Datos | +| **Descripción** | Un estudiante solo puede ver el NOMBRE de sus compañeros de clase | +| **Tipo** | Restricción de Privacidad | +| **Severidad** | Media | + +**Datos Visibles:** +- ✓ Nombre del compañero +- ✗ Email del compañero +- ✗ Otras materias del compañero + +**Condición:** Solo aplica para materias donde ambos estudiantes están inscritos + +--- + +## 3. Matriz de Validación por Capa + +| Regla | Domain | Application | Adapter | +|-------|--------|-------------|---------| +| RN-001 | ✗ | ✗ | Seed Data | +| RN-002 | ValueObject | ✗ | ✗ | +| RN-003 | DomainService | ✗ | ✗ | +| RN-004 | ✗ | ✗ | Seed Data | +| RN-005 | **DomainService** | ✗ | ✗ | +| RN-006 | ✗ | Validator | DB Constraint | +| RN-007 | ValueObject | Validator | ✗ | +| RN-008 | ✗ | Query Handler | ✗ | + +--- + +## 4. Implementación en Código + +### RN-003 y RN-005 (Domain Service) + +```csharp +// Domain/Services/EnrollmentDomainService.cs +public class EnrollmentDomainService +{ + public Result ValidateEnrollment(Student student, Subject newSubject) + { + // RN-003: Máximo 3 materias + if (student.Enrollments.Count >= 3) + return Result.Failure("MAX_ENROLLMENTS_EXCEEDED"); + + // RN-005: No repetir profesor + var existingProfessorIds = student.Enrollments + .Select(e => e.Subject.ProfessorId) + .ToHashSet(); + + if (existingProfessorIds.Contains(newSubject.ProfessorId)) + return Result.Failure("SAME_PROFESSOR_CONSTRAINT"); + + return Result.Success(); + } +} +``` + +### RN-007 (Value Object) + +```csharp +// Domain/ValueObjects/Email.cs +public record Email +{ + public string Value { get; } + + private Email(string value) => Value = value; + + public static Result Create(string email) + { + if (string.IsNullOrWhiteSpace(email)) + return Result.Failure("EMAIL_REQUIRED"); + + if (!EmailRegex.IsMatch(email)) + return Result.Failure("EMAIL_INVALID_FORMAT"); + + return Result.Success(new Email(email.ToLowerInvariant())); + } + + private static readonly Regex EmailRegex = new( + @"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$", + RegexOptions.Compiled); +} +``` + +--- + +## 5. Diagrama de Restricciones + +``` + ┌─────────────────────┐ + │ ESTUDIANTE │ + │ - Nombre │ + │ - Email (único) │ + └──────────┬──────────┘ + │ + │ máx 3 inscripciones + │ (RN-003) + ▼ + ┌─────────────────────┐ + │ INSCRIPCIÓN │ + └──────────┬──────────┘ + │ + no puede repetir │ profesor (RN-005) + ▼ +┌─────────────┐ ┌─────────────────────┐ ┌─────────────┐ +│ PROFESOR │──────│ MATERIA │──────│ 3 CRÉDITOS │ +│ (5 total) │ 1:2 │ (10 total) │ │ (RN-002) │ +└─────────────┘ └─────────────────────┘ └─────────────┘ +``` + +--- + +## 6. Aprobación + +| Rol | Nombre | Fecha | Firma | +|-----|--------|-------|-------| +| Analista | Sistema | 2026-01-07 | ✓ | +| Product Owner | Pendiente | - | - | +| Arquitecto | Pendiente | - | - | diff --git a/docs/entregables/01-analisis/requisitos/AN-001-requisitos-funcionales.md b/docs/entregables/01-analisis/requisitos/AN-001-requisitos-funcionales.md new file mode 100644 index 0000000..84839da --- /dev/null +++ b/docs/entregables/01-analisis/requisitos/AN-001-requisitos-funcionales.md @@ -0,0 +1,208 @@ +# AN-001: Análisis de Requisitos Funcionales + +**Proyecto:** Sistema de Registro de Estudiantes - Inter Rapidísimo +**Rol:** Analista de Sistemas +**Fecha:** 2026-01-07 + +--- + +## 1. Resumen Ejecutivo + +Sistema web para gestión de inscripciones estudiantiles con programa de créditos académicos. Permite CRUD de estudiantes, inscripción en materias con restricciones de negocio, y visualización de compañeros de clase. + +--- + +## 2. Requisitos Funcionales Identificados + +### RF-001: Registro de Estudiantes (CRUD) + +| Atributo | Descripción | +|----------|-------------| +| **ID** | RF-001 | +| **Nombre** | Gestión CRUD de Estudiantes | +| **Descripción** | El sistema debe permitir crear, leer, actualizar y eliminar registros de estudiantes | +| **Prioridad** | Alta | +| **Fuente** | Enunciado punto 1 | + +**Criterios de Aceptación:** +- [ ] CA-001.1: Usuario puede crear estudiante con nombre y email válido +- [ ] CA-001.2: Usuario puede consultar lista de estudiantes registrados +- [ ] CA-001.3: Usuario puede actualizar datos de un estudiante existente +- [ ] CA-001.4: Usuario puede eliminar un estudiante (cascade con inscripciones) +- [ ] CA-001.5: Email debe ser único en el sistema + +--- + +### RF-002: Programa de Créditos + +| Atributo | Descripción | +|----------|-------------| +| **ID** | RF-002 | +| **Nombre** | Adhesión a Programa de Créditos | +| **Descripción** | Los estudiantes se inscriben en un programa basado en créditos académicos | +| **Prioridad** | Alta | +| **Fuente** | Enunciado punto 2 | + +**Criterios de Aceptación:** +- [ ] CA-002.1: Sistema muestra créditos totales del estudiante +- [ ] CA-002.2: Créditos se calculan automáticamente según materias inscritas +- [ ] CA-002.3: Máximo 9 créditos por estudiante (3 materias × 3 créditos) + +--- + +### RF-003: Catálogo de Materias + +| Atributo | Descripción | +|----------|-------------| +| **ID** | RF-003 | +| **Nombre** | Gestión de 10 Materias | +| **Descripción** | El sistema debe gestionar un catálogo fijo de 10 materias académicas | +| **Prioridad** | Alta | +| **Fuente** | Enunciado punto 3 | + +**Criterios de Aceptación:** +- [ ] CA-003.1: Sistema contiene exactamente 10 materias predefinidas +- [ ] CA-003.2: Usuario puede consultar catálogo completo de materias +- [ ] CA-003.3: Cada materia muestra nombre, créditos y profesor asignado + +--- + +### RF-004: Valor de Créditos por Materia + +| Atributo | Descripción | +|----------|-------------| +| **ID** | RF-004 | +| **Nombre** | Asignación de 3 Créditos por Materia | +| **Descripción** | Cada materia del catálogo equivale a exactamente 3 créditos | +| **Prioridad** | Alta | +| **Fuente** | Enunciado punto 4 | + +**Criterios de Aceptación:** +- [ ] CA-004.1: Todas las materias tienen valor fijo de 3 créditos +- [ ] CA-004.2: Sistema muestra créditos en detalle de cada materia + +--- + +### RF-005: Límite de Materias por Estudiante + +| Atributo | Descripción | +|----------|-------------| +| **ID** | RF-005 | +| **Nombre** | Máximo 3 Materias por Estudiante | +| **Descripción** | Un estudiante solo puede inscribirse en máximo 3 materias | +| **Prioridad** | Alta | +| **Fuente** | Enunciado punto 5 | + +**Criterios de Aceptación:** +- [ ] CA-005.1: Sistema impide inscripción si estudiante ya tiene 3 materias +- [ ] CA-005.2: Sistema muestra contador de materias inscritas +- [ ] CA-005.3: Mensaje de error claro al intentar exceder límite + +--- + +### RF-006: Asignación Profesores-Materias + +| Atributo | Descripción | +|----------|-------------| +| **ID** | RF-006 | +| **Nombre** | 5 Profesores con 2 Materias c/u | +| **Descripción** | El sistema tiene 5 profesores, cada uno dicta exactamente 2 materias | +| **Prioridad** | Alta | +| **Fuente** | Enunciado punto 6 | + +**Criterios de Aceptación:** +- [ ] CA-006.1: Sistema contiene exactamente 5 profesores predefinidos +- [ ] CA-006.2: Cada profesor tiene asignadas exactamente 2 materias +- [ ] CA-006.3: Usuario puede ver qué profesor dicta cada materia + +--- + +### RF-007: Restricción de Profesor Único + +| Atributo | Descripción | +|----------|-------------| +| **ID** | RF-007 | +| **Nombre** | Prohibición de Mismo Profesor | +| **Descripción** | Un estudiante no puede inscribirse en dos materias del mismo profesor | +| **Prioridad** | Crítica | +| **Fuente** | Enunciado punto 7 | + +**Criterios de Aceptación:** +- [ ] CA-007.1: Sistema valida profesor al momento de inscripción +- [ ] CA-007.2: Materias no disponibles se muestran deshabilitadas con razón +- [ ] CA-007.3: Mensaje de error específico: "Ya tienes una materia con este profesor" + +--- + +### RF-008: Visualización de Registros + +| Atributo | Descripción | +|----------|-------------| +| **ID** | RF-008 | +| **Nombre** | Consulta de Otros Estudiantes | +| **Descripción** | Cada estudiante puede ver en línea los registros de otros estudiantes | +| **Prioridad** | Media | +| **Fuente** | Enunciado punto 8 | + +**Criterios de Aceptación:** +- [ ] CA-008.1: Usuario puede ver lista de todos los estudiantes registrados +- [ ] CA-008.2: Información visible: nombre (según restricciones de privacidad) + +--- + +### RF-009: Compañeros de Clase + +| Atributo | Descripción | +|----------|-------------| +| **ID** | RF-009 | +| **Nombre** | Ver Nombres de Compañeros por Materia | +| **Descripción** | El estudiante puede ver solo el nombre de alumnos con quienes compartirá clase | +| **Prioridad** | Media | +| **Fuente** | Enunciado punto 9 | + +**Criterios de Aceptación:** +- [ ] CA-009.1: Sistema muestra compañeros agrupados por materia +- [ ] CA-009.2: Solo se muestra el nombre (no email ni otros datos) +- [ ] CA-009.3: Solo muestra compañeros de materias donde está inscrito + +--- + +## 3. Matriz de Trazabilidad + +| Requisito | Historia | Componente Backend | Componente Frontend | +|-----------|----------|-------------------|---------------------| +| RF-001 | US-001 | StudentController, StudentService | StudentFormComponent | +| RF-002 | US-002 | EnrollmentService | DashboardComponent | +| RF-003 | US-003 | SubjectController | SubjectListComponent | +| RF-004 | US-003 | Subject Entity | SubjectCardComponent | +| RF-005 | US-004 | EnrollmentValidator | EnrollmentComponent | +| RF-006 | US-003 | Professor Entity | ProfessorListComponent | +| RF-007 | US-005 | EnrollmentDomainService | EnrollmentComponent | +| RF-008 | US-006 | StudentQuery | StudentListComponent | +| RF-009 | US-007 | ClassmatesQuery | ClassmatesComponent | + +--- + +## 4. Dependencias entre Requisitos + +``` +RF-001 (Estudiantes) + ↓ +RF-002 (Programa Créditos) ← RF-004 (3 créditos/materia) + ↓ +RF-005 (Max 3 materias) ← RF-003 (10 materias) + ↓ ↓ +RF-007 (Restricción profesor) ← RF-006 (5 profesores) + ↓ +RF-008/RF-009 (Visualización) +``` + +--- + +## 5. Aprobación + +| Rol | Nombre | Fecha | Firma | +|-----|--------|-------|-------| +| Analista | Sistema | 2026-01-07 | ✓ | +| Product Owner | Pendiente | - | - | +| Líder Técnico | Pendiente | - | - | diff --git a/docs/entregables/01-analisis/requisitos/AN-004-requisitos-no-funcionales.md b/docs/entregables/01-analisis/requisitos/AN-004-requisitos-no-funcionales.md new file mode 100644 index 0000000..f051eeb --- /dev/null +++ b/docs/entregables/01-analisis/requisitos/AN-004-requisitos-no-funcionales.md @@ -0,0 +1,315 @@ +# AN-004: Requisitos No Funcionales + +**Proyecto:** Sistema de Registro de Estudiantes - Inter Rapidísimo +**Rol:** Arquitecto de Software +**Fecha:** 2026-01-07 + +--- + +## 1. Resumen + +Este documento define los atributos de calidad (requisitos no funcionales) que el sistema debe cumplir, estableciendo métricas medibles y criterios de aceptación. + +--- + +## 2. Requisitos No Funcionales + +### RNF-001: Rendimiento - Tiempo de Respuesta + +| Atributo | Valor | +|----------|-------| +| **ID** | RNF-001 | +| **Categoría** | Rendimiento | +| **Prioridad** | Alta | +| **Métrica** | Tiempo de respuesta < 200ms (P95) | + +**Descripción:** +El sistema debe responder a las solicitudes del usuario en menos de 200 milisegundos en el percentil 95. + +**Criterios de Aceptación:** + +| Operación | Tiempo Máximo | +|-----------|---------------| +| Consulta lista de estudiantes | < 150ms | +| Consulta catálogo materias | < 100ms | +| Inscripción en materia | < 200ms | +| Crear/actualizar estudiante | < 200ms | +| Consulta compañeros de clase | < 150ms | + +**Medición:** +- Herramienta: Application Insights / Logs estructurados +- Ambiente: Producción simulada +- Carga: 100 usuarios concurrentes + +--- + +### RNF-002: Rendimiento - Throughput + +| Atributo | Valor | +|----------|-------| +| **ID** | RNF-002 | +| **Categoría** | Rendimiento | +| **Prioridad** | Media | +| **Métrica** | >= 500 requests/segundo | + +**Descripción:** +El sistema debe soportar al menos 500 solicitudes por segundo sin degradación. + +**Criterios de Aceptación:** +- [ ] API soporta 500 req/s con latencia < 200ms +- [ ] Sin errores 5xx bajo carga normal +- [ ] CPU < 80% bajo carga máxima + +--- + +### RNF-003: Seguridad - OWASP Top 10 + +| Atributo | Valor | +|----------|-------| +| **ID** | RNF-003 | +| **Categoría** | Seguridad | +| **Prioridad** | Crítica | +| **Estándar** | OWASP Top 10 2021 | + +**Descripción:** +El sistema debe estar protegido contra las 10 vulnerabilidades más críticas según OWASP. + +**Controles por Vulnerabilidad:** + +| # | Vulnerabilidad | Control Implementado | +|---|----------------|----------------------| +| A01 | Broken Access Control | Validación de permisos en cada endpoint | +| A02 | Cryptographic Failures | HTTPS obligatorio, hashing de datos sensibles | +| A03 | Injection | Consultas parametrizadas (EF Core), validación de entrada | +| A04 | Insecure Design | Clean Architecture, validación en dominio | +| A05 | Security Misconfiguration | Headers de seguridad, CORS restrictivo | +| A06 | Vulnerable Components | Auditoría de dependencias (`dotnet list package --vulnerable`) | +| A07 | Auth Failures | Rate limiting, validación de sesión | +| A08 | Data Integrity Failures | Validación de DTOs, firma de respuestas | +| A09 | Logging Failures | Logs estructurados sin datos sensibles | +| A10 | SSRF | Validación de URLs, no fetch de recursos externos | + +**Criterios de Aceptación:** +- [ ] Análisis SAST sin vulnerabilidades críticas +- [ ] Headers de seguridad configurados (CSP, X-Frame-Options, etc.) +- [ ] Sin secretos en código fuente +- [ ] HTTPS en todos los endpoints + +--- + +### RNF-004: Seguridad - Validación de Entrada + +| Atributo | Valor | +|----------|-------| +| **ID** | RNF-004 | +| **Categoría** | Seguridad | +| **Prioridad** | Alta | + +**Descripción:** +Toda entrada del usuario debe ser validada tanto en frontend como en backend. + +**Reglas de Validación:** + +| Campo | Regla Frontend | Regla Backend | +|-------|----------------|---------------| +| Nombre | Required, MaxLength(100) | FluentValidation | +| Email | Required, EmailFormat | ValueObject + Validator | +| IDs | Numeric | Tipo fuerte (int) | + +**Criterios de Aceptación:** +- [ ] Ningún input llega a la BD sin validación +- [ ] Mensajes de error claros y sin información sensible +- [ ] Sanitización de HTML en campos de texto + +--- + +### RNF-005: Usabilidad - Responsive Design + +| Atributo | Valor | +|----------|-------| +| **ID** | RNF-005 | +| **Categoría** | Usabilidad | +| **Prioridad** | Alta | +| **Estándar** | Mobile First | + +**Descripción:** +La interfaz debe adaptarse correctamente a diferentes tamaños de pantalla. + +**Breakpoints Soportados:** + +| Dispositivo | Ancho | Soporte | +|-------------|-------|---------| +| Mobile | 320px - 480px | Obligatorio | +| Tablet | 481px - 768px | Obligatorio | +| Desktop | 769px - 1024px | Obligatorio | +| Large Desktop | > 1024px | Obligatorio | + +**Criterios de Aceptación:** +- [ ] UI funcional en Chrome, Firefox, Safari, Edge +- [ ] Sin scroll horizontal en mobile +- [ ] Formularios usables con teclado virtual +- [ ] Touch targets >= 44px + +--- + +### RNF-006: Usabilidad - Accesibilidad + +| Atributo | Valor | +|----------|-------| +| **ID** | RNF-006 | +| **Categoría** | Usabilidad | +| **Prioridad** | Media | +| **Estándar** | WCAG 2.1 Nivel AA | + +**Descripción:** +El sistema debe ser accesible para usuarios con discapacidades. + +**Criterios de Aceptación:** +- [ ] Contraste de colores >= 4.5:1 +- [ ] Navegación completa por teclado +- [ ] Labels en todos los inputs +- [ ] Atributos ARIA donde corresponda +- [ ] Textos alternativos en imágenes + +--- + +### RNF-007: Mantenibilidad - Cobertura de Tests + +| Atributo | Valor | +|----------|-------| +| **ID** | RNF-007 | +| **Categoría** | Mantenibilidad | +| **Prioridad** | Alta | +| **Métrica** | >= 80% cobertura | + +**Descripción:** +El código debe tener una cobertura de tests unitarios mínima del 80%. + +**Distribución de Tests:** + +| Capa | Cobertura Mínima | Tipo de Tests | +|------|------------------|---------------| +| Domain | 90% | Unit tests | +| Application | 85% | Unit + Integration | +| Adapters | 70% | Integration | +| Frontend | 75% | Unit + Component | + +**Criterios de Aceptación:** +- [ ] Cobertura global >= 80% +- [ ] Todas las reglas de negocio con tests +- [ ] Tests ejecutables en CI/CD + +--- + +### RNF-008: Mantenibilidad - Calidad de Código + +| Atributo | Valor | +|----------|-------| +| **ID** | RNF-008 | +| **Categoría** | Mantenibilidad | +| **Prioridad** | Media | + +**Descripción:** +El código debe seguir estándares de calidad medibles. + +**Métricas:** + +| Métrica | Umbral | +|---------|--------| +| Complejidad ciclomática | < 10 por método | +| Duplicación de código | < 3% | +| Archivos | < 100 líneas | +| Métodos | < 30 líneas | +| Warnings del compilador | 0 | + +**Herramientas:** +- .NET: `dotnet format`, Roslyn Analyzers +- Angular: ESLint, Prettier + +--- + +### RNF-009: Disponibilidad + +| Atributo | Valor | +|----------|-------| +| **ID** | RNF-009 | +| **Categoría** | Disponibilidad | +| **Prioridad** | Media | +| **Métrica** | 99% uptime | + +**Descripción:** +El sistema debe estar disponible el 99% del tiempo durante horario laboral. + +**Criterios de Aceptación:** +- [ ] Health checks implementados +- [ ] Reinicio automático ante fallos +- [ ] Graceful shutdown +- [ ] Logs de errores para diagnóstico + +--- + +### RNF-010: Escalabilidad + +| Atributo | Valor | +|----------|-------| +| **ID** | RNF-010 | +| **Categoría** | Escalabilidad | +| **Prioridad** | Baja | + +**Descripción:** +La arquitectura debe permitir escalado horizontal sin cambios de código. + +**Criterios de Aceptación:** +- [ ] Aplicación stateless +- [ ] Configuración externalizada +- [ ] Base de datos separada de aplicación +- [ ] Contenedores Docker funcionales + +--- + +## 3. Matriz de Priorización + +| ID | Requisito | Prioridad | Impacto | Esfuerzo | +|----|-----------|-----------|---------|----------| +| RNF-003 | Seguridad OWASP | Crítica | Alto | Alto | +| RNF-001 | Tiempo respuesta | Alta | Alto | Medio | +| RNF-004 | Validación entrada | Alta | Alto | Medio | +| RNF-005 | Responsive | Alta | Alto | Medio | +| RNF-007 | Cobertura tests | Alta | Medio | Alto | +| RNF-002 | Throughput | Media | Medio | Medio | +| RNF-006 | Accesibilidad | Media | Medio | Medio | +| RNF-008 | Calidad código | Media | Medio | Bajo | +| RNF-009 | Disponibilidad | Media | Alto | Medio | +| RNF-010 | Escalabilidad | Baja | Bajo | Alto | + +--- + +## 4. Estrategia de Validación + +### Tests de Rendimiento +```bash +# k6 para load testing +k6 run --vus 100 --duration 30s tests/performance/api-load.js +``` + +### Tests de Seguridad +```bash +# OWASP ZAP scan +docker run -t owasp/zap2docker-stable zap-baseline.py -t https://localhost:5001 +``` + +### Tests de Accesibilidad +```bash +# Lighthouse CI +npx lighthouse https://localhost:4200 --only-categories=accessibility +``` + +--- + +## 5. Aprobación + +| Rol | Nombre | Fecha | Firma | +|-----|--------|-------|-------| +| Arquitecto | Sistema | 2026-01-07 | ✓ | +| Líder Técnico | Pendiente | - | - | +| QA Lead | Pendiente | - | - | diff --git a/docs/entregables/01-analisis/riesgos/AN-005-riesgos-tecnicos.md b/docs/entregables/01-analisis/riesgos/AN-005-riesgos-tecnicos.md new file mode 100644 index 0000000..160145f --- /dev/null +++ b/docs/entregables/01-analisis/riesgos/AN-005-riesgos-tecnicos.md @@ -0,0 +1,400 @@ +# AN-005: Análisis de Riesgos Técnicos + +**Proyecto:** Sistema de Registro de Estudiantes - Inter Rapidísimo +**Rol:** Líder Técnico +**Fecha:** 2026-01-07 + +--- + +## 1. Resumen + +Identificación y evaluación de riesgos técnicos que podrían impactar el desarrollo, calidad o entrega del sistema. Cada riesgo incluye probabilidad, impacto y estrategia de mitigación. + +--- + +## 2. Matriz de Evaluación + +| Probabilidad / Impacto | Bajo (1) | Medio (2) | Alto (3) | +|------------------------|----------|-----------|----------| +| **Alta (3)** | 3 | 6 | **9** | +| **Media (2)** | 2 | 4 | 6 | +| **Baja (1)** | 1 | 2 | 3 | + +**Clasificación:** +- **Crítico:** 6-9 (acción inmediata) +- **Moderado:** 3-5 (plan de mitigación) +- **Bajo:** 1-2 (monitorear) + +--- + +## 3. Riesgos Identificados + +### RT-001: Complejidad de Validaciones Cruzadas + +| Atributo | Valor | +|----------|-------| +| **ID** | RT-001 | +| **Categoría** | Desarrollo | +| **Probabilidad** | Alta (3) | +| **Impacto** | Alto (3) | +| **Score** | **9 - Crítico** | + +**Descripción:** +Las validaciones de negocio (máximo 3 materias + no repetir profesor) requieren consultas cruzadas entre entidades. Una implementación incorrecta puede generar condiciones de carrera o validaciones inconsistentes. + +**Escenarios de Riesgo:** +1. Dos inscripciones concurrentes del mismo estudiante +2. Validación en frontend que no se replica en backend +3. Race condition al verificar profesor duplicado + +**Estrategia de Mitigación:** + +| Acción | Responsable | Plazo | +|--------|-------------|-------| +| Implementar validaciones en Domain Service (única fuente de verdad) | Dev Senior | Sprint 1 | +| Usar transacciones con nivel de aislamiento Serializable | Dev Senior | Sprint 1 | +| Tests de concurrencia con múltiples threads | QA | Sprint 1 | +| Lock optimista con RowVersion en Enrollment | Dev | Sprint 1 | + +**Código de Mitigación:** +```csharp +// Transacción con locking +await using var transaction = await _context.Database + .BeginTransactionAsync(IsolationLevel.Serializable); + +var student = await _context.Students + .Include(s => s.Enrollments) + .ThenInclude(e => e.Subject) + .FirstOrDefaultAsync(s => s.Id == command.StudentId); + +// Validaciones en dominio +_enrollmentService.ValidateEnrollment(student, subject); + +await _context.SaveChangesAsync(); +await transaction.CommitAsync(); +``` + +--- + +### RT-002: Integración Frontend-Backend (GraphQL) + +| Atributo | Valor | +|----------|-------| +| **ID** | RT-002 | +| **Categoría** | Integración | +| **Probabilidad** | Media (2) | +| **Impacto** | Alto (3) | +| **Score** | **6 - Crítico** | + +**Descripción:** +GraphQL introduce complejidad adicional en la integración. Errores en el esquema, types incorrectos o problemas de N+1 queries pueden afectar rendimiento y desarrollo. + +**Escenarios de Riesgo:** +1. Mismatch entre schema GraphQL y DTOs +2. N+1 queries sin DataLoaders +3. Errores de tipos en Apollo Angular +4. Over-fetching o under-fetching de datos + +**Estrategia de Mitigación:** + +| Acción | Responsable | Plazo | +|--------|-------------|-------| +| Generar tipos TypeScript desde schema (codegen) | Dev Frontend | Sprint 1 | +| Implementar DataLoaders para todas las relaciones | Dev Backend | Sprint 1 | +| Configurar GraphQL Voyager para visualizar schema | Dev | Sprint 1 | +| Tests de integración GraphQL con Banana Cake Pop | QA | Sprint 1 | + +**Configuración DataLoader:** +```csharp +// DataLoader para evitar N+1 +public class SubjectByIdDataLoader : BatchDataLoader +{ + protected override async Task> LoadBatchAsync( + IReadOnlyList keys, CancellationToken ct) + { + return await _context.Subjects + .Where(s => keys.Contains(s.Id)) + .ToDictionaryAsync(s => s.Id, ct); + } +} +``` + +--- + +### RT-003: Manejo de Concurrencia + +| Atributo | Valor | +|----------|-------| +| **ID** | RT-003 | +| **Categoría** | Arquitectura | +| **Probabilidad** | Media (2) | +| **Impacto** | Alto (3) | +| **Score** | **6 - Crítico** | + +**Descripción:** +Múltiples usuarios podrían intentar inscribirse en la misma materia simultáneamente, generando conflictos o violaciones de reglas de negocio. + +**Escenarios de Riesgo:** +1. Dos estudiantes inscriben la última plaza disponible +2. Estudiante inscribe 4ta materia por request concurrente +3. Datos obsoletos en UI mientras otro usuario modifica + +**Estrategia de Mitigación:** + +| Acción | Responsable | Plazo | +|--------|-------------|-------| +| Implementar Optimistic Concurrency (RowVersion) | Dev Backend | Sprint 1 | +| Revalidar en backend siempre (no confiar en frontend) | Dev Backend | Sprint 1 | +| Mostrar toast de "datos actualizados" en conflictos | Dev Frontend | Sprint 2 | +| Tests de stress con k6 | QA | Sprint 2 | + +**Implementación:** +```csharp +// Entidad con concurrencia optimista +public class Student +{ + [Timestamp] + public byte[] RowVersion { get; set; } +} + +// Manejo de conflicto +catch (DbUpdateConcurrencyException) +{ + return Result.Failure("CONCURRENT_MODIFICATION"); +} +``` + +--- + +### RT-004: Rendimiento de Consultas Anidadas + +| Atributo | Valor | +|----------|-------| +| **ID** | RT-004 | +| **Categoría** | Rendimiento | +| **Probabilidad** | Media (2) | +| **Impacto** | Medio (2) | +| **Score** | **4 - Moderado** | + +**Descripción:** +Consultas GraphQL anidadas (estudiantes → inscripciones → materias → profesores) pueden generar queries pesadas si no se optimizan. + +**Escenarios de Riesgo:** +1. Query de todos los estudiantes con todas sus relaciones +2. Timeouts en consultas sin límite de profundidad +3. Consumo excesivo de memoria en serialización + +**Estrategia de Mitigación:** + +| Acción | Responsable | Plazo | +|--------|-------------|-------| +| Configurar límite de profundidad en HotChocolate (max 5) | Dev Backend | Sprint 1 | +| Implementar paginación en queries de listas | Dev Backend | Sprint 1 | +| Indexar campos de búsqueda (Email, ProfessorId) | DBA | Sprint 1 | +| Query complexity analyzer | Dev Backend | Sprint 2 | + +**Configuración:** +```csharp +services.AddGraphQLServer() + .SetMaxAllowedExecutionDepth(5) + .SetPagingOptions(new PagingOptions + { + MaxPageSize = 50, + DefaultPageSize = 20 + }); +``` + +--- + +### RT-005: Curva de Aprendizaje del Stack + +| Atributo | Valor | +|----------|-------| +| **ID** | RT-005 | +| **Categoría** | Equipo | +| **Probabilidad** | Alta (3) | +| **Impacto** | Bajo (1) | +| **Score** | **3 - Moderado** | + +**Descripción:** +El stack incluye tecnologías relativamente nuevas (HotChocolate, Angular Signals, Apollo Angular) que pueden requerir tiempo de aprendizaje. + +**Tecnologías con Curva:** +- HotChocolate (GraphQL .NET) +- Mapster (alternativa a AutoMapper) +- Angular Signals +- Apollo Angular + +**Estrategia de Mitigación:** + +| Acción | Responsable | Plazo | +|--------|-------------|-------| +| Documentar patrones y ejemplos en wiki | Tech Lead | Sprint 1 | +| Code reviews detallados en PRs | Senior Dev | Continuo | +| Spike técnico para DataLoaders | Dev Backend | Sprint 1 | +| Usar generador de código Apollo | Dev Frontend | Sprint 1 | + +--- + +### RT-006: Seguridad en GraphQL + +| Atributo | Valor | +|----------|-------| +| **ID** | RT-006 | +| **Categoría** | Seguridad | +| **Probabilidad** | Baja (1) | +| **Impacto** | Alto (3) | +| **Score** | **3 - Moderado** | + +**Descripción:** +GraphQL expone un endpoint flexible que puede ser abusado con queries maliciosas (deeply nested, introspection en prod, DoS). + +**Escenarios de Riesgo:** +1. Query extremadamente anidada consume recursos +2. Introspection expone schema sensible +3. Mutation masiva sin rate limiting + +**Estrategia de Mitigación:** + +| Acción | Responsable | Plazo | +|--------|-------------|-------| +| Deshabilitar introspection en producción | DevOps | Deploy | +| Configurar query complexity limits | Dev Backend | Sprint 1 | +| Rate limiting por IP (10 req/s) | DevOps | Sprint 2 | +| Persisted queries en producción | Dev Backend | Sprint 3 | + +**Configuración:** +```csharp +services.AddGraphQLServer() + .AddMaxComplexityRule(100) // Límite de complejidad + .AddMaxExecutionDepthRule(5) + .ModifyOptions(o => + { + // Deshabilitar introspection en prod + if (!env.IsDevelopment()) + o.EnableSchemaIntrospection = false; + }); +``` + +--- + +### RT-007: Migración y Seeding de Datos + +| Atributo | Valor | +|----------|-------| +| **ID** | RT-007 | +| **Categoría** | Base de Datos | +| **Probabilidad** | Baja (1) | +| **Impacto** | Medio (2) | +| **Score** | **2 - Bajo** | + +**Descripción:** +Los datos iniciales (5 profesores, 10 materias) deben estar correctamente relacionados. Errores en seeding pueden romper reglas de negocio. + +**Escenarios de Riesgo:** +1. Profesor asignado a más de 2 materias +2. IDs inconsistentes entre migraciones +3. Datos duplicados en re-ejecución de seed + +**Estrategia de Mitigación:** + +| Acción | Responsable | Plazo | +|--------|-------------|-------| +| Seeding idempotente (verificar existencia) | Dev Backend | Sprint 1 | +| Validación post-seed en tests | QA | Sprint 1 | +| Script de verificación de integridad | DBA | Sprint 1 | + +--- + +### RT-008: Testing de Reglas de Negocio + +| Atributo | Valor | +|----------|-------| +| **ID** | RT-008 | +| **Categoría** | Calidad | +| **Probabilidad** | Media (2) | +| **Impacto** | Medio (2) | +| **Score** | **4 - Moderado** | + +**Descripción:** +Las reglas de negocio complejas requieren cobertura exhaustiva de tests. Casos edge pueden quedar sin probar. + +**Casos Edge Críticos:** +1. Inscribir materia 3 cuando ya tiene 2 del mismo "tipo" +2. Cancelar inscripción y re-inscribir rápidamente +3. Estudiante con 3 materias intenta cambiar una + +**Estrategia de Mitigación:** + +| Acción | Responsable | Plazo | +|--------|-------------|-------| +| Property-based testing para combinaciones | QA | Sprint 1 | +| Matriz de casos de prueba exhaustiva | QA | Sprint 1 | +| Mutation testing (Stryker) | QA | Sprint 2 | + +--- + +## 4. Matriz de Riesgos Consolidada + +| ID | Riesgo | Prob | Impacto | Score | Prioridad | +|----|--------|------|---------|-------|-----------| +| RT-001 | Validaciones cruzadas | 3 | 3 | **9** | 1 | +| RT-002 | Integración GraphQL | 2 | 3 | **6** | 2 | +| RT-003 | Concurrencia | 2 | 3 | **6** | 3 | +| RT-004 | Rendimiento queries | 2 | 2 | 4 | 4 | +| RT-005 | Curva aprendizaje | 3 | 1 | 3 | 5 | +| RT-006 | Seguridad GraphQL | 1 | 3 | 3 | 6 | +| RT-008 | Testing reglas | 2 | 2 | 4 | 7 | +| RT-007 | Seeding datos | 1 | 2 | 2 | 8 | + +--- + +## 5. Plan de Contingencia + +### Si RT-001 se materializa (validaciones fallan): +1. Rollback a versión anterior +2. Fix inmediato en Domain Service +3. Agregar tests de regresión +4. Re-deploy con verificación manual + +### Si RT-002 se materializa (integración falla): +1. Fallback a REST API simple +2. Schema manual sin codegen +3. Simplificar queries GraphQL + +### Si RT-003 se materializa (race conditions): +1. Lock pesimista temporal +2. Queue de inscripciones +3. Revisión manual de conflictos + +--- + +## 6. Indicadores de Monitoreo + +| Riesgo | Indicador | Umbral de Alerta | +|--------|-----------|------------------| +| RT-001 | Inscripciones fallidas por validación | > 5% | +| RT-002 | Errores GraphQL 500 | > 1% | +| RT-003 | DbUpdateConcurrencyException | > 10/hora | +| RT-004 | Query time P95 | > 500ms | +| RT-006 | Requests bloqueados por rate limit | > 100/hora | + +--- + +## 7. Revisión de Riesgos + +| Fecha | Revisor | Acción | +|-------|---------|--------| +| Sprint 1 Review | Tech Lead | Evaluar RT-001, RT-002 | +| Sprint 2 Review | Tech Lead | Evaluar RT-003, RT-004 | +| Pre-deploy | QA Lead | Validar mitigaciones | + +--- + +## 8. Aprobación + +| Rol | Nombre | Fecha | Firma | +|-----|--------|-------|-------| +| Líder Técnico | Sistema | 2026-01-07 | ✓ | +| Arquitecto | Pendiente | - | - | +| Project Manager | Pendiente | - | - | diff --git a/docs/entregables/02-diseno/arquitectura/DI-001-arquitectura-backend.md b/docs/entregables/02-diseno/arquitectura/DI-001-arquitectura-backend.md new file mode 100644 index 0000000..cc0600a --- /dev/null +++ b/docs/entregables/02-diseno/arquitectura/DI-001-arquitectura-backend.md @@ -0,0 +1,129 @@ +# DI-001: Arquitectura Backend + +**Proyecto:** Sistema de Registro de Estudiantes +**Fecha:** 2026-01-07 + +--- + +## 1. Patrón: Clean Architecture + CQRS + +``` +┌────────────────────────────────────────────────────────────┐ +│ HOST │ +│ (Program.cs, DI, Configuration) │ +└─────────────────────────┬──────────────────────────────────┘ + │ +┌─────────────────────────▼──────────────────────────────────┐ +│ ADAPTERS │ +│ ┌──────────────────────┬─────────────────────────────┐ │ +│ │ DRIVING (Primary) │ DRIVEN (Secondary) │ │ +│ │ ────────────────── │ ───────────────────── │ │ +│ │ GraphQL API │ Persistence (EF Core) │ │ +│ │ (HotChocolate) │ DataLoaders │ │ +│ └──────────────────────┴─────────────────────────────┘ │ +└─────────────────────────┬──────────────────────────────────┘ + │ +┌─────────────────────────▼──────────────────────────────────┐ +│ APPLICATION │ +│ Commands / Queries / Handlers / Validators │ +└─────────────────────────┬──────────────────────────────────┘ + │ +┌─────────────────────────▼──────────────────────────────────┐ +│ DOMAIN │ +│ Entities / Value Objects / Services / Ports │ +└────────────────────────────────────────────────────────────┘ +``` + +--- + +## 2. Regla de Dependencia + +``` +Host → Adapters → Application → Domain + ↑ + Adapters implementa Ports +``` + +**INVIOLABLE:** Domain NO depende de nada externo. + +--- + +## 3. Responsabilidades por Capa + +| Capa | Responsabilidad | Tecnología | +|------|-----------------|------------| +| **Domain** | Entidades, Value Objects, reglas de negocio puras, Ports (interfaces) | C# puro | +| **Application** | Casos de uso (Commands/Queries), DTOs, Validators, orquestación | MediatR, FluentValidation, Mapster | +| **Adapters/Driving** | GraphQL API (Types, Resolvers, Mutations) | HotChocolate | +| **Adapters/Driven** | Repositorios, DbContext, DataLoaders | EF Core, SQL Server | +| **Host** | Composition Root, DI, Middleware | ASP.NET Core | + +--- + +## 4. Estructura de Proyectos + +``` +src/backend/ +├── Domain/ +│ ├── Entities/ # Student, Subject, Professor, Enrollment +│ ├── ValueObjects/ # Email, Credits +│ ├── Services/ # EnrollmentDomainService +│ ├── Ports/ +│ │ └── Repositories/ # IStudentRepository, ISubjectRepository +│ └── Exceptions/ # MaxEnrollmentsException, SameProfessorException +│ +├── Application/ +│ ├── Students/ +│ │ ├── Commands/ # CreateStudentCommand, UpdateStudentCommand +│ │ ├── Queries/ # GetStudentsQuery, GetStudentByIdQuery +│ │ └── DTOs/ # StudentDto, CreateStudentInput +│ ├── Enrollments/ +│ │ ├── Commands/ # EnrollCommand, UnenrollCommand +│ │ ├── Queries/ # GetClassmatesQuery +│ │ └── DTOs/ # EnrollmentDto, EnrollInput +│ └── Common/ +│ ├── Behaviors/ # ValidationBehavior +│ └── Mappings/ # MapsterConfig +│ +├── Adapters/ +│ ├── Driving/ +│ │ └── Api/ +│ │ ├── Types/ # StudentType, SubjectType +│ │ ├── Queries/ # QueryResolvers +│ │ └── Mutations/ # MutationResolvers +│ └── Driven/ +│ └── Persistence/ +│ ├── Context/ # AppDbContext +│ ├── Configs/ # StudentConfiguration +│ ├── Repos/ # StudentRepository +│ └── Seeding/ # DataSeeder +│ +└── Host/ + └── Program.cs # Entry point + DI +``` + +--- + +## 5. Flujo de una Request + +``` +1. GraphQL Request → HotChocolate Resolver +2. Resolver → Application Handler (via MediatR) +3. Handler → Validator (FluentValidation) +4. Handler → Domain Service (reglas de negocio) +5. Handler → Repository (persistencia) +6. Repository → DbContext → SQL Server +7. Response ← DTO ← Mapper ← Entity +``` + +--- + +## 6. Decisiones de Arquitectura + +| Decisión | Razón | +|----------|-------| +| GraphQL vs REST | Flexibilidad en queries, evita over/under-fetching | +| HotChocolate | Mejor soporte .NET, Code-First, DataLoaders | +| MediatR | Desacopla handlers, facilita CQRS | +| Mapster | Más rápido que AutoMapper | +| FluentValidation | Validaciones declarativas y testeables | diff --git a/docs/entregables/02-diseno/arquitectura/DI-005-arquitectura-frontend.md b/docs/entregables/02-diseno/arquitectura/DI-005-arquitectura-frontend.md new file mode 100644 index 0000000..eba43e4 --- /dev/null +++ b/docs/entregables/02-diseno/arquitectura/DI-005-arquitectura-frontend.md @@ -0,0 +1,208 @@ +# DI-005: Arquitectura Frontend + +**Proyecto:** Sistema de Registro de Estudiantes +**Fecha:** 2026-01-07 + +--- + +## 1. Stack Tecnológico + +| Tecnología | Propósito | +|------------|-----------| +| Angular 21 | Framework SPA | +| Angular Material | UI Components | +| Apollo Angular | Cliente GraphQL | +| Signals | Estado reactivo | +| TypeScript | Tipado estático | + +--- + +## 2. Estructura de Carpetas + +``` +src/frontend/src/app/ +├── core/ # Singleton services +│ ├── services/ +│ │ ├── student.service.ts +│ │ └── enrollment.service.ts +│ ├── graphql/ +│ │ ├── generated/ # Tipos generados +│ │ ├── queries/ +│ │ │ ├── students.graphql +│ │ │ └── subjects.graphql +│ │ └── mutations/ +│ │ ├── student.graphql +│ │ └── enrollment.graphql +│ └── interceptors/ +│ └── error.interceptor.ts +│ +├── shared/ # Reutilizables +│ ├── components/ +│ │ ├── confirm-dialog/ +│ │ └── loading-spinner/ +│ └── pipes/ +│ └── credits.pipe.ts +│ +└── features/ # Módulos por funcionalidad + ├── students/ + │ ├── pages/ + │ │ ├── student-list/ + │ │ └── student-form/ + │ └── components/ + │ └── student-card/ + │ + ├── enrollment/ + │ ├── pages/ + │ │ └── enrollment-page/ + │ └── components/ + │ ├── subject-selector/ + │ └── enrolled-subjects/ + │ + └── classmates/ + └── pages/ + └── classmates-page/ +``` + +--- + +## 3. Configuración Apollo + +```typescript +// app.config.ts +export const appConfig: ApplicationConfig = { + providers: [ + provideHttpClient(), + provideApollo(() => ({ + link: httpLink.create({ uri: environment.graphqlUrl }), + cache: new InMemoryCache(), + defaultOptions: { + watchQuery: { fetchPolicy: 'cache-and-network' }, + mutate: { errorPolicy: 'all' } + } + })) + ] +}; +``` + +--- + +## 4. Gestión de Estado + +``` +┌─────────────────────────────────────────────┐ +│ Apollo Cache │ +│ (Estado del servidor: students, subjects) │ +└─────────────────────────────────────────────┘ + ▲ + │ watchQuery / mutate + ▼ +┌─────────────────────────────────────────────┐ +│ Services │ +│ (Encapsulan operaciones GraphQL) │ +└─────────────────────────────────────────────┘ + ▲ + │ inject() + ▼ +┌─────────────────────────────────────────────┐ +│ Components │ +│ (Signals para estado local/UI) │ +└─────────────────────────────────────────────┘ +``` + +--- + +## 5. Ejemplo de Service + +```typescript +@Injectable({ providedIn: 'root' }) +export class StudentService { + private apollo = inject(Apollo); + + getStudents() { + return this.apollo.watchQuery<{ students: Student[] }>({ + query: GET_STUDENTS, + fetchPolicy: 'cache-and-network' + }).valueChanges; + } + + createStudent(input: CreateStudentInput) { + return this.apollo.mutate<{ createStudent: StudentPayload }>({ + mutation: CREATE_STUDENT, + variables: { input }, + refetchQueries: [{ query: GET_STUDENTS }] + }); + } +} +``` + +--- + +## 6. Ejemplo de Componente (Signals) + +```typescript +@Component({ + selector: 'app-student-list', + standalone: true, + changeDetection: ChangeDetectionStrategy.OnPush, + template: ` + @if (loading()) { + + } @else { + + + + } + ` +}) +export class StudentListComponent { + private studentService = inject(StudentService); + + students = signal([]); + loading = signal(true); + + constructor() { + this.studentService.getStudents().subscribe(({ data, loading }) => { + this.students.set(data?.students ?? []); + this.loading.set(loading); + }); + } +} +``` + +--- + +## 7. Rutas (Lazy Loading) + +```typescript +export const routes: Routes = [ + { path: '', redirectTo: 'students', pathMatch: 'full' }, + { + path: 'students', + loadComponent: () => import('./features/students/pages/student-list/student-list.component') + }, + { + path: 'students/new', + loadComponent: () => import('./features/students/pages/student-form/student-form.component') + }, + { + path: 'enrollment/:studentId', + loadComponent: () => import('./features/enrollment/pages/enrollment-page/enrollment-page.component') + }, + { + path: 'classmates/:studentId', + loadComponent: () => import('./features/classmates/pages/classmates-page/classmates-page.component') + } +]; +``` + +--- + +## 8. Decisiones de Diseño + +| Decisión | Razón | +|----------|-------| +| Standalone Components | Angular moderno, tree-shakeable | +| Signals vs RxJS | Más simple para estado local, menos boilerplate | +| Apollo Cache | Single source of truth para datos del servidor | +| OnPush | Mejor rendimiento, menos re-renders | +| Lazy Loading | Bundle inicial pequeño | diff --git a/docs/entregables/02-diseno/arquitectura/DI-008-manejo-errores.md b/docs/entregables/02-diseno/arquitectura/DI-008-manejo-errores.md new file mode 100644 index 0000000..b85f1f5 --- /dev/null +++ b/docs/entregables/02-diseno/arquitectura/DI-008-manejo-errores.md @@ -0,0 +1,241 @@ +# DI-008: Estrategia de Manejo de Errores + +**Proyecto:** Sistema de Registro de Estudiantes +**Fecha:** 2026-01-07 + +--- + +## 1. Clasificación de Errores + +| Tipo | Origen | Manejo | HTTP Code (equiv) | +|------|--------|--------|-------------------| +| **Validación** | FluentValidation | Payload.errors | 400 | +| **Dominio** | Domain Exceptions | Payload.errors | 422 | +| **Not Found** | Repository | Payload.errors | 404 | +| **Conflicto** | Concurrencia | Payload.errors | 409 | +| **Sistema** | Excepciones no manejadas | Error GraphQL | 500 | + +--- + +## 2. Excepciones de Dominio + +```csharp +// Base +public abstract class DomainException : Exception +{ + public string Code { get; } + protected DomainException(string code, string message) : base(message) + => Code = code; +} + +// Específicas +public class MaxEnrollmentsExceededException : DomainException +{ + public MaxEnrollmentsExceededException() + : base("MAX_ENROLLMENTS", "Máximo 3 materias permitidas") { } +} + +public class SameProfessorConstraintException : DomainException +{ + public SameProfessorConstraintException(string professorName) + : base("SAME_PROFESSOR", $"Ya tienes una materia con {professorName}") { } +} + +public class DuplicateEmailException : DomainException +{ + public DuplicateEmailException() + : base("DUPLICATE_EMAIL", "Este email ya está registrado") { } +} + +public class StudentNotFoundException : DomainException +{ + public StudentNotFoundException(int id) + : base("NOT_FOUND", $"Estudiante {id} no encontrado") { } +} +``` + +--- + +## 3. Patrón Result + +```csharp +public class Result +{ + public bool IsSuccess { get; } + public IEnumerable Errors { get; } + + protected Result(bool success, IEnumerable? errors = null) + { + IsSuccess = success; + Errors = errors ?? Array.Empty(); + } + + public static Result Success() => new(true); + public static Result Failure(params string[] errors) => new(false, errors); +} + +public class Result : Result +{ + public T? Value { get; } + + private Result(T value) : base(true) => Value = value; + private Result(IEnumerable errors) : base(false, errors) { } + + public static Result Success(T value) => new(value); + public static new Result Failure(params string[] errors) => new(errors); +} +``` + +--- + +## 4. Handler con Manejo de Errores + +```csharp +public class EnrollStudentHandler +{ + public async Task Handle(EnrollInput input) + { + try + { + var student = await _studentRepo.GetByIdWithEnrollmentsAsync(input.StudentId); + if (student is null) + return new EnrollmentPayload(null, ["Estudiante no encontrado"]); + + var subject = await _subjectRepo.GetByIdAsync(input.SubjectId); + if (subject is null) + return new EnrollmentPayload(null, ["Materia no encontrada"]); + + // Validación de dominio + student.Enroll(subject, _enrollmentPolicy); + + await _unitOfWork.SaveChangesAsync(); + + var dto = student.Enrollments.Last().Adapt(); + return new EnrollmentPayload(dto, null); + } + catch (DomainException ex) + { + return new EnrollmentPayload(null, [ex.Message]); + } + } +} +``` + +--- + +## 5. Error Filter GraphQL (HotChocolate) + +```csharp +public class ErrorFilter : IErrorFilter +{ + public IError OnError(IError error) + { + return error.Exception switch + { + DomainException ex => error + .WithMessage(ex.Message) + .WithCode(ex.Code), + + ValidationException ex => error + .WithMessage("Errores de validación") + .WithCode("VALIDATION_ERROR") + .SetExtension("errors", ex.Errors.Select(e => e.ErrorMessage)), + + DbUpdateConcurrencyException => error + .WithMessage("Los datos fueron modificados por otro usuario") + .WithCode("CONCURRENCY_ERROR"), + + _ => error + .WithMessage("Error interno del servidor") + .WithCode("INTERNAL_ERROR") + }; + } +} +``` + +--- + +## 6. Manejo en Frontend + +### Service + +```typescript +@Injectable({ providedIn: 'root' }) +export class ErrorHandlerService { + private snackBar = inject(MatSnackBar); + + handleGraphQLErrors(errors: string[] | undefined): void { + if (errors?.length) { + this.snackBar.open(errors[0], 'Cerrar', { + duration: 5000, + panelClass: 'error-snackbar' + }); + } + } + + handleApolloError(error: ApolloError): void { + const message = error.graphQLErrors[0]?.message ?? 'Error de conexión'; + this.snackBar.open(message, 'Cerrar', { duration: 5000 }); + } +} +``` + +### Componente + +```typescript +enrollStudent(subjectId: number) { + this.enrollmentService.enroll({ + studentId: this.studentId(), + subjectId + }).subscribe({ + next: ({ data }) => { + if (data?.enrollStudent.errors?.length) { + this.errorHandler.handleGraphQLErrors(data.enrollStudent.errors); + } else { + this.snackBar.open('Inscripción exitosa', 'OK'); + } + }, + error: (err) => this.errorHandler.handleApolloError(err) + }); +} +``` + +--- + +## 7. Códigos de Error Estándar + +| Código | Mensaje | Acción UI | +|--------|---------|-----------| +| `VALIDATION_ERROR` | Errores de validación | Mostrar en campos | +| `MAX_ENROLLMENTS` | Máximo 3 materias | Toast + deshabilitar | +| `SAME_PROFESSOR` | Ya tienes materia con X | Toast + deshabilitar | +| `DUPLICATE_EMAIL` | Email ya registrado | Error en campo | +| `NOT_FOUND` | Recurso no encontrado | Redirect + toast | +| `CONCURRENCY_ERROR` | Datos modificados | Refetch + toast | +| `INTERNAL_ERROR` | Error del servidor | Toast genérico | + +--- + +## 8. Logging de Errores + +```csharp +public class LoggingBehavior : IPipelineBehavior +{ + private readonly ILogger> _logger; + + public async Task Handle(TRequest request, ...) + { + try + { + return await next(); + } + catch (Exception ex) when (ex is not DomainException) + { + _logger.LogError(ex, "Error procesando {Request}", typeof(TRequest).Name); + throw; + } + } +} +``` + +**Regla:** NO loguear datos sensibles (email, contraseñas). diff --git a/docs/entregables/02-diseno/base-datos/DI-003-diseno-base-datos.md b/docs/entregables/02-diseno/base-datos/DI-003-diseno-base-datos.md new file mode 100644 index 0000000..c111a50 --- /dev/null +++ b/docs/entregables/02-diseno/base-datos/DI-003-diseno-base-datos.md @@ -0,0 +1,198 @@ +# DI-003: Diseño de Base de Datos + +**Proyecto:** Sistema de Registro de Estudiantes +**Fecha:** 2026-01-07 + +--- + +## 1. Modelo Entidad-Relación + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ PROFESSORS │ +├──────────────┬────────────────┬─────────────────────────────────┤ +│ Id │ INT │ PK, IDENTITY │ +│ Name │ NVARCHAR(100) │ NOT NULL │ +└──────────────┴────────────────┴─────────────────────────────────┘ + │ + │ 1:N (cada profesor → 2 materias) + ▼ +┌─────────────────────────────────────────────────────────────────┐ +│ SUBJECTS │ +├──────────────┬────────────────┬─────────────────────────────────┤ +│ Id │ INT │ PK, IDENTITY │ +│ Name │ NVARCHAR(100) │ NOT NULL │ +│ Credits │ INT │ DEFAULT 3, CHECK (Credits = 3) │ +│ ProfessorId │ INT │ FK → Professors.Id │ +└──────────────┴────────────────┴─────────────────────────────────┘ + │ + │ 1:N + ▼ +┌─────────────────────────────────────────────────────────────────┐ +│ ENROLLMENTS │ +├──────────────┬────────────────┬─────────────────────────────────┤ +│ Id │ INT │ PK, IDENTITY │ +│ StudentId │ INT │ FK → Students.Id │ +│ SubjectId │ INT │ FK → Subjects.Id │ +│ EnrolledAt │ DATETIME2 │ DEFAULT GETUTCDATE() │ +└──────────────┴────────────────┴─────────────────────────────────┘ + ▲ + │ 0..3:N + │ +┌─────────────────────────────────────────────────────────────────┐ +│ STUDENTS │ +├──────────────┬────────────────┬─────────────────────────────────┤ +│ Id │ INT │ PK, IDENTITY │ +│ Name │ NVARCHAR(100) │ NOT NULL │ +│ Email │ NVARCHAR(255) │ NOT NULL, UNIQUE │ +│ RowVersion │ ROWVERSION │ Concurrency control │ +└──────────────┴────────────────┴─────────────────────────────────┘ +``` + +--- + +## 2. Script DDL + +```sql +-- Crear base de datos +CREATE DATABASE StudentEnrollment; +GO + +USE StudentEnrollment; +GO + +-- Tabla: Professors +CREATE TABLE Professors ( + Id INT IDENTITY(1,1) PRIMARY KEY, + Name NVARCHAR(100) NOT NULL +); + +-- Tabla: Subjects +CREATE TABLE Subjects ( + Id INT IDENTITY(1,1) PRIMARY KEY, + Name NVARCHAR(100) NOT NULL, + Credits INT NOT NULL DEFAULT 3 CHECK (Credits = 3), + ProfessorId INT NOT NULL, + CONSTRAINT FK_Subjects_Professors + FOREIGN KEY (ProfessorId) REFERENCES Professors(Id) +); + +-- Tabla: Students +CREATE TABLE Students ( + Id INT IDENTITY(1,1) PRIMARY KEY, + Name NVARCHAR(100) NOT NULL, + Email NVARCHAR(255) NOT NULL, + RowVersion ROWVERSION, + CONSTRAINT UQ_Students_Email UNIQUE (Email) +); + +-- Tabla: Enrollments +CREATE TABLE Enrollments ( + Id INT IDENTITY(1,1) PRIMARY KEY, + StudentId INT NOT NULL, + SubjectId INT NOT NULL, + EnrolledAt DATETIME2 NOT NULL DEFAULT GETUTCDATE(), + CONSTRAINT FK_Enrollments_Students + FOREIGN KEY (StudentId) REFERENCES Students(Id) ON DELETE CASCADE, + CONSTRAINT FK_Enrollments_Subjects + FOREIGN KEY (SubjectId) REFERENCES Subjects(Id), + CONSTRAINT UQ_Enrollments_Student_Subject + UNIQUE (StudentId, SubjectId) +); + +-- Índices +CREATE INDEX IX_Subjects_ProfessorId ON Subjects(ProfessorId); +CREATE INDEX IX_Enrollments_StudentId ON Enrollments(StudentId); +CREATE INDEX IX_Enrollments_SubjectId ON Enrollments(SubjectId); +``` + +--- + +## 3. Datos Iniciales (Seed) + +```sql +-- 5 Profesores +INSERT INTO Professors (Name) VALUES + ('Dr. García'), -- Id: 1 + ('Dra. Martínez'), -- Id: 2 + ('Dr. López'), -- Id: 3 + ('Dra. Rodríguez'), -- Id: 4 + ('Dr. Hernández'); -- Id: 5 + +-- 10 Materias (2 por profesor) +INSERT INTO Subjects (Name, Credits, ProfessorId) VALUES + ('Matemáticas I', 3, 1), + ('Matemáticas II', 3, 1), + ('Física I', 3, 2), + ('Física II', 3, 2), + ('Programación I', 3, 3), + ('Programación II', 3, 3), + ('Base de Datos I', 3, 4), + ('Base de Datos II', 3, 4), + ('Redes I', 3, 5), + ('Redes II', 3, 5); +``` + +--- + +## 4. Constraints de Negocio + +| Constraint | Tipo | Descripción | +|------------|------|-------------| +| `UQ_Students_Email` | UNIQUE | Email único por estudiante | +| `UQ_Enrollments_Student_Subject` | UNIQUE | No duplicar inscripción | +| `CHECK (Credits = 3)` | CHECK | Créditos siempre = 3 | +| `ON DELETE CASCADE` | FK | Eliminar inscripciones al eliminar estudiante | + +--- + +## 5. Vistas Útiles + +```sql +-- Vista: Compañeros de clase +CREATE VIEW vw_Classmates AS +SELECT + e1.StudentId, + s.Name AS SubjectName, + st.Name AS ClassmateName +FROM Enrollments e1 +INNER JOIN Enrollments e2 ON e1.SubjectId = e2.SubjectId + AND e1.StudentId != e2.StudentId +INNER JOIN Subjects s ON e1.SubjectId = s.Id +INNER JOIN Students st ON e2.StudentId = st.Id; + +-- Vista: Materias disponibles por estudiante +CREATE VIEW vw_AvailableSubjects AS +SELECT + st.Id AS StudentId, + su.Id AS SubjectId, + su.Name AS SubjectName, + p.Name AS ProfessorName, + CASE + WHEN e.Id IS NOT NULL THEN 0 -- Ya inscrito + WHEN ep.ProfessorId IS NOT NULL THEN 0 -- Mismo profesor + ELSE 1 + END AS IsAvailable +FROM Students st +CROSS JOIN Subjects su +INNER JOIN Professors p ON su.ProfessorId = p.Id +LEFT JOIN Enrollments e ON st.Id = e.StudentId AND su.Id = e.SubjectId +LEFT JOIN ( + SELECT DISTINCT e.StudentId, s.ProfessorId + FROM Enrollments e + INNER JOIN Subjects s ON e.SubjectId = s.Id +) ep ON st.Id = ep.StudentId AND su.ProfessorId = ep.ProfessorId; +``` + +--- + +## 6. Diccionario de Datos + +| Tabla | Campo | Tipo | Descripción | +|-------|-------|------|-------------| +| Students | Id | INT | Identificador único | +| Students | Name | NVARCHAR(100) | Nombre completo | +| Students | Email | NVARCHAR(255) | Correo (único) | +| Students | RowVersion | ROWVERSION | Control de concurrencia | +| Subjects | Credits | INT | Siempre 3 | +| Enrollments | EnrolledAt | DATETIME2 | Fecha de inscripción UTC | diff --git a/docs/entregables/02-diseno/componentes-ui/DI-006-componentes-ui.md b/docs/entregables/02-diseno/componentes-ui/DI-006-componentes-ui.md new file mode 100644 index 0000000..fb72534 --- /dev/null +++ b/docs/entregables/02-diseno/componentes-ui/DI-006-componentes-ui.md @@ -0,0 +1,166 @@ +# DI-006: Diseño de Componentes UI + +**Proyecto:** Sistema de Registro de Estudiantes +**Fecha:** 2026-01-07 + +--- + +## 1. Mapa de Pantallas + +``` +┌──────────────────────────────────────────────────────────────┐ +│ APP LAYOUT │ +├──────────────────────────────────────────────────────────────┤ +│ [Logo] Sistema de Estudiantes [Estudiantes] [Mat] │ +├──────────────────────────────────────────────────────────────┤ +│ │ +│ │ +│ │ +└──────────────────────────────────────────────────────────────┘ + +Rutas: +├── /students → Lista de estudiantes +├── /students/new → Formulario crear +├── /students/:id/edit → Formulario editar +├── /enrollment/:id → Inscripción de materias +└── /classmates/:id → Compañeros de clase +``` + +--- + +## 2. Wireframes + +### 2.1 Lista de Estudiantes (`/students`) + +``` +┌─────────────────────────────────────────────────────────────┐ +│ ESTUDIANTES [+ Nuevo Estudiante] │ +├─────────────────────────────────────────────────────────────┤ +│ ┌─────────────────────────────────────────────────────┐ │ +│ │ 🔍 Buscar estudiante... │ │ +│ └─────────────────────────────────────────────────────┘ │ +│ │ +│ ┌───────────────────────────────────────────────────────┐ │ +│ │ NOMBRE │ EMAIL │ CRÉDITOS │ ACCIONES│ │ +│ ├───────────────────────────────────────────────────────┤ │ +│ │ Juan Pérez │ juan@email.com │ 6/9 │ ✏️ 🗑️ 📚 │ │ +│ │ María García │ maria@email.com │ 9/9 │ ✏️ 🗑️ 📚 │ │ +│ │ Carlos López │ carlos@mail.com │ 3/9 │ ✏️ 🗑️ 📚 │ │ +│ └───────────────────────────────────────────────────────┘ │ +│ │ +│ [← Anterior] [Siguiente →] │ +└─────────────────────────────────────────────────────────────┘ + +Leyenda: ✏️ Editar 🗑️ Eliminar 📚 Inscripción +``` + +### 2.2 Formulario Estudiante (`/students/new`) + +``` +┌─────────────────────────────────────────────────────────────┐ +│ ← Volver NUEVO ESTUDIANTE │ +├─────────────────────────────────────────────────────────────┤ +│ │ +│ ┌─────────────────────────────────────────────────────┐ │ +│ │ Nombre * │ │ +│ │ ┌─────────────────────────────────────────────────┐ │ │ +│ │ │ Juan Pérez │ │ │ +│ │ └─────────────────────────────────────────────────┘ │ │ +│ └─────────────────────────────────────────────────────┘ │ +│ │ +│ ┌─────────────────────────────────────────────────────┐ │ +│ │ Email * │ │ +│ │ ┌─────────────────────────────────────────────────┐ │ │ +│ │ │ juan@email.com │ │ │ +│ │ └─────────────────────────────────────────────────┘ │ │ +│ │ ⚠️ Este email ya está registrado (error ejemplo) │ │ +│ └─────────────────────────────────────────────────────┘ │ +│ │ +│ [Cancelar] [Guardar] │ +└─────────────────────────────────────────────────────────────┘ +``` + +### 2.3 Inscripción de Materias (`/enrollment/:id`) + +``` +┌─────────────────────────────────────────────────────────────┐ +│ ← Volver INSCRIPCIÓN: Juan Pérez │ +├─────────────────────────────────────────────────────────────┤ +│ Créditos: ████████░░ 6/9 Materias: 2/3 │ +├─────────────────────────────────────────────────────────────┤ +│ │ +│ MIS MATERIAS INSCRITAS │ +│ ┌───────────────────────────────────────────────────────┐ │ +│ │ ✓ Física I │ Dra. Martínez │ [Cancelar] │ │ +│ │ ✓ Programación I │ Dr. López │ [Cancelar] │ │ +│ └───────────────────────────────────────────────────────┘ │ +│ │ +│ MATERIAS DISPONIBLES │ +│ ┌───────────────────────────────────────────────────────┐ │ +│ │ ○ Matemáticas I │ Dr. García │ [Inscribir] │ │ +│ │ ○ Matemáticas II │ Dr. García │ [Inscribir] │ │ +│ │ ○ Física II │ Dra. Martínez │ ⚠️ Mismo prof │ │ +│ │ ○ Programación II │ Dr. López │ ⚠️ Mismo prof │ │ +│ │ ○ Base de Datos I │ Dra. Rodríguez │ [Inscribir] │ │ +│ │ ... │ │ +│ └───────────────────────────────────────────────────────┘ │ +│ │ +│ [Ver Compañeros de Clase] │ +└─────────────────────────────────────────────────────────────┘ +``` + +### 2.4 Compañeros de Clase (`/classmates/:id`) + +``` +┌─────────────────────────────────────────────────────────────┐ +│ ← Volver MIS COMPAÑEROS: Juan Pérez │ +├─────────────────────────────────────────────────────────────┤ +│ │ +│ 📚 FÍSICA I (Dra. Martínez) │ +│ ┌───────────────────────────────────────────────────────┐ │ +│ │ • María García │ │ +│ │ • Carlos López │ │ +│ │ • Ana Rodríguez │ │ +│ └───────────────────────────────────────────────────────┘ │ +│ │ +│ 💻 PROGRAMACIÓN I (Dr. López) │ +│ ┌───────────────────────────────────────────────────────┐ │ +│ │ • Pedro Sánchez │ │ +│ │ • Laura Torres │ │ +│ └───────────────────────────────────────────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────┘ +``` + +--- + +## 3. Componentes Reutilizables + +| Componente | Descripción | Props | +|------------|-------------|-------| +| `ConfirmDialog` | Modal de confirmación | title, message, onConfirm | +| `LoadingSpinner` | Indicador de carga | size, color | +| `ErrorAlert` | Mensaje de error | message, dismissible | +| `CreditsBadge` | Badge de créditos | current, max | +| `SubjectCard` | Card de materia | subject, actions | + +--- + +## 4. Estados de UI + +| Estado | Visual | +|--------|--------| +| Loading | Skeleton / Spinner | +| Empty | Ilustración + mensaje | +| Error | Alert rojo + retry | +| Success | Toast verde | + +--- + +## 5. Responsive Breakpoints + +| Breakpoint | Comportamiento | +|------------|----------------| +| Mobile (<600px) | Cards apiladas, menú hamburguesa | +| Tablet (600-960px) | Grid 2 columnas | +| Desktop (>960px) | Tabla completa, sidebar | diff --git a/docs/entregables/02-diseno/esquema-graphql/DI-004-esquema-graphql.md b/docs/entregables/02-diseno/esquema-graphql/DI-004-esquema-graphql.md new file mode 100644 index 0000000..d137dc6 --- /dev/null +++ b/docs/entregables/02-diseno/esquema-graphql/DI-004-esquema-graphql.md @@ -0,0 +1,226 @@ +# DI-004: Esquema GraphQL + +**Proyecto:** Sistema de Registro de Estudiantes +**Fecha:** 2026-01-07 + +--- + +## 1. Schema Completo + +```graphql +# ═══════════════════════════════════════════════════════════════ +# TYPES +# ═══════════════════════════════════════════════════════════════ + +type Student { + id: Int! + name: String! + email: String! + totalCredits: Int! + enrollments: [Enrollment!]! +} + +type Subject { + id: Int! + name: String! + credits: Int! + professor: Professor! + enrolledStudents: [Student!]! +} + +type Professor { + id: Int! + name: String! + subjects: [Subject!]! +} + +type Enrollment { + id: Int! + student: Student! + subject: Subject! + enrolledAt: DateTime! +} + +type AvailableSubject { + subject: Subject! + isAvailable: Boolean! + unavailableReason: String +} + +type Classmate { + subjectName: String! + students: [String!]! +} + +# ═══════════════════════════════════════════════════════════════ +# QUERIES +# ═══════════════════════════════════════════════════════════════ + +type Query { + # Estudiantes + students: [Student!]! + student(id: Int!): Student + + # Materias + subjects: [Subject!]! + subject(id: Int!): Subject + availableSubjects(studentId: Int!): [AvailableSubject!]! + + # Profesores + professors: [Professor!]! + professor(id: Int!): Professor + + # Compañeros de clase + classmates(studentId: Int!): [Classmate!]! +} + +# ═══════════════════════════════════════════════════════════════ +# MUTATIONS +# ═══════════════════════════════════════════════════════════════ + +type Mutation { + # Estudiantes + createStudent(input: CreateStudentInput!): StudentPayload! + updateStudent(id: Int!, input: UpdateStudentInput!): StudentPayload! + deleteStudent(id: Int!): DeletePayload! + + # Inscripciones + enrollStudent(input: EnrollInput!): EnrollmentPayload! + unenrollStudent(enrollmentId: Int!): DeletePayload! +} + +# ═══════════════════════════════════════════════════════════════ +# INPUTS +# ═══════════════════════════════════════════════════════════════ + +input CreateStudentInput { + name: String! + email: String! +} + +input UpdateStudentInput { + name: String + email: String +} + +input EnrollInput { + studentId: Int! + subjectId: Int! +} + +# ═══════════════════════════════════════════════════════════════ +# PAYLOADS (Union para errores) +# ═══════════════════════════════════════════════════════════════ + +type StudentPayload { + student: Student + errors: [String!] +} + +type EnrollmentPayload { + enrollment: Enrollment + errors: [String!] +} + +type DeletePayload { + success: Boolean! + errors: [String!] +} + +# ═══════════════════════════════════════════════════════════════ +# SCALARS +# ═══════════════════════════════════════════════════════════════ + +scalar DateTime +``` + +--- + +## 2. Ejemplos de Queries + +### Listar estudiantes con inscripciones +```graphql +query GetStudents { + students { + id + name + email + totalCredits + enrollments { + subject { name professor { name } } + } + } +} +``` + +### Materias disponibles para inscripción +```graphql +query GetAvailableSubjects($studentId: Int!) { + availableSubjects(studentId: $studentId) { + subject { id name credits professor { name } } + isAvailable + unavailableReason + } +} +``` + +### Compañeros de clase +```graphql +query GetClassmates($studentId: Int!) { + classmates(studentId: $studentId) { + subjectName + students + } +} +``` + +--- + +## 3. Ejemplos de Mutations + +### Crear estudiante +```graphql +mutation CreateStudent { + createStudent(input: { name: "Juan Pérez", email: "juan@email.com" }) { + student { id name email } + errors + } +} +``` + +### Inscribir en materia +```graphql +mutation Enroll { + enrollStudent(input: { studentId: 1, subjectId: 3 }) { + enrollment { + id + subject { name } + enrolledAt + } + errors + } +} +``` + +--- + +## 4. DataLoaders (Evitar N+1) + +| DataLoader | Carga Batch | +|------------|-------------| +| `StudentByIdDataLoader` | Estudiantes por IDs | +| `SubjectByIdDataLoader` | Materias por IDs | +| `ProfessorByIdDataLoader` | Profesores por IDs | +| `EnrollmentsByStudentDataLoader` | Inscripciones por estudiante | +| `SubjectsByProfessorDataLoader` | Materias por profesor | + +--- + +## 5. Errores de Negocio + +| Código | Mensaje | Contexto | +|--------|---------|----------| +| `MAX_ENROLLMENTS` | "Máximo 3 materias permitidas" | enrollStudent | +| `SAME_PROFESSOR` | "Ya tienes materia con este profesor" | enrollStudent | +| `DUPLICATE_EMAIL` | "Email ya registrado" | createStudent | +| `NOT_FOUND` | "Estudiante no encontrado" | updateStudent | diff --git a/docs/entregables/02-diseno/esquema-graphql/DI-007-contratos-dtos.md b/docs/entregables/02-diseno/esquema-graphql/DI-007-contratos-dtos.md new file mode 100644 index 0000000..147fe32 --- /dev/null +++ b/docs/entregables/02-diseno/esquema-graphql/DI-007-contratos-dtos.md @@ -0,0 +1,257 @@ +# DI-007: Contratos y DTOs + +**Proyecto:** Sistema de Registro de Estudiantes +**Fecha:** 2026-01-07 + +--- + +## 1. DTOs Backend (C#) + +### Request DTOs (Inputs) + +```csharp +// Students +public record CreateStudentInput(string Name, string Email); +public record UpdateStudentInput(string? Name, string? Email); + +// Enrollments +public record EnrollInput(int StudentId, int SubjectId); +``` + +### Response DTOs + +```csharp +// Student +public record StudentDto( + int Id, + string Name, + string Email, + int TotalCredits, + IEnumerable Enrollments); + +// Subject +public record SubjectDto( + int Id, + string Name, + int Credits, + ProfessorDto Professor); + +// Professor +public record ProfessorDto( + int Id, + string Name); + +// Enrollment +public record EnrollmentDto( + int Id, + SubjectDto Subject, + DateTime EnrolledAt); + +// Available Subject (con disponibilidad) +public record AvailableSubjectDto( + SubjectDto Subject, + bool IsAvailable, + string? UnavailableReason); + +// Classmates +public record ClassmateDto( + string SubjectName, + IEnumerable StudentNames); +``` + +### Payloads (Respuestas con errores) + +```csharp +public record StudentPayload(StudentDto? Student, IEnumerable? Errors); +public record EnrollmentPayload(EnrollmentDto? Enrollment, IEnumerable? Errors); +public record DeletePayload(bool Success, IEnumerable? Errors); +``` + +--- + +## 2. DTOs Frontend (TypeScript) + +### Tipos Generados (graphql-codegen) + +```typescript +// Student +export interface Student { + id: number; + name: string; + email: string; + totalCredits: number; + enrollments: Enrollment[]; +} + +// Subject +export interface Subject { + id: number; + name: string; + credits: number; + professor: Professor; +} + +// Professor +export interface Professor { + id: number; + name: string; + subjects?: Subject[]; +} + +// Enrollment +export interface Enrollment { + id: number; + subject: Subject; + enrolledAt: string; +} + +// Available Subject +export interface AvailableSubject { + subject: Subject; + isAvailable: boolean; + unavailableReason?: string; +} + +// Classmate +export interface Classmate { + subjectName: string; + students: string[]; +} +``` + +### Inputs + +```typescript +export interface CreateStudentInput { + name: string; + email: string; +} + +export interface UpdateStudentInput { + name?: string; + email?: string; +} + +export interface EnrollInput { + studentId: number; + subjectId: number; +} +``` + +### Payloads + +```typescript +export interface StudentPayload { + student?: Student; + errors?: string[]; +} + +export interface EnrollmentPayload { + enrollment?: Enrollment; + errors?: string[]; +} + +export interface DeletePayload { + success: boolean; + errors?: string[]; +} +``` + +--- + +## 3. Mapeos (Mapster) + +```csharp +public class MappingConfig : IRegister +{ + public void Register(TypeAdapterConfig config) + { + config.NewConfig() + .Map(dest => dest.TotalCredits, src => src.Enrollments.Count * 3); + + config.NewConfig(); + config.NewConfig(); + config.NewConfig(); + } +} +``` + +--- + +## 4. Validadores (FluentValidation) + +```csharp +public class CreateStudentInputValidator : AbstractValidator +{ + public CreateStudentInputValidator() + { + RuleFor(x => x.Name) + .NotEmpty().WithMessage("Nombre requerido") + .MaximumLength(100).WithMessage("Máximo 100 caracteres"); + + RuleFor(x => x.Email) + .NotEmpty().WithMessage("Email requerido") + .EmailAddress().WithMessage("Formato de email inválido") + .MaximumLength(255).WithMessage("Máximo 255 caracteres"); + } +} + +public class EnrollInputValidator : AbstractValidator +{ + public EnrollInputValidator() + { + RuleFor(x => x.StudentId) + .GreaterThan(0).WithMessage("StudentId inválido"); + + RuleFor(x => x.SubjectId) + .GreaterThan(0).WithMessage("SubjectId inválido"); + } +} +``` + +--- + +## 5. Contratos de Repositorio (Ports) + +```csharp +public interface IStudentRepository +{ + Task GetByIdAsync(int id, CancellationToken ct = default); + Task GetByIdWithEnrollmentsAsync(int id, CancellationToken ct = default); + Task> GetAllAsync(CancellationToken ct = default); + Task EmailExistsAsync(string email, int? excludeId = null, CancellationToken ct = default); + Task AddAsync(Student student, CancellationToken ct = default); + void Update(Student student); + void Delete(Student student); +} + +public interface ISubjectRepository +{ + Task GetByIdAsync(int id, CancellationToken ct = default); + Task> GetAllAsync(CancellationToken ct = default); + Task> GetByProfessorIdAsync(int professorId, CancellationToken ct = default); +} + +public interface IEnrollmentRepository +{ + Task> GetByStudentIdAsync(int studentId, CancellationToken ct = default); + Task> GetClassmatesAsync(int studentId, int subjectId, CancellationToken ct = default); +} + +public interface IUnitOfWork +{ + Task SaveChangesAsync(CancellationToken ct = default); +} +``` + +--- + +## 6. Resumen de Contratos por Operación + +| Operación | Input | Output | Errores Posibles | +|-----------|-------|--------|------------------| +| createStudent | CreateStudentInput | StudentPayload | DUPLICATE_EMAIL, VALIDATION | +| updateStudent | UpdateStudentInput | StudentPayload | NOT_FOUND, DUPLICATE_EMAIL | +| deleteStudent | id: Int | DeletePayload | NOT_FOUND | +| enrollStudent | EnrollInput | EnrollmentPayload | MAX_ENROLLMENTS, SAME_PROFESSOR | +| unenrollStudent | enrollmentId: Int | DeletePayload | NOT_FOUND | diff --git a/docs/entregables/02-diseno/modelo-dominio/DI-002-modelo-dominio.md b/docs/entregables/02-diseno/modelo-dominio/DI-002-modelo-dominio.md new file mode 100644 index 0000000..1ec8cb5 --- /dev/null +++ b/docs/entregables/02-diseno/modelo-dominio/DI-002-modelo-dominio.md @@ -0,0 +1,189 @@ +# DI-002: Modelo de Dominio + +**Proyecto:** Sistema de Registro de Estudiantes +**Fecha:** 2026-01-07 + +--- + +## 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 │ + └─────────────────┘ +``` + +--- + +## 2. Entidades + +### Student (Aggregate Root) + +```csharp +public class Student +{ + public int Id { get; private set; } + public string Name { get; private set; } + public Email Email { get; private set; } + public byte[] RowVersion { get; private set; } + + private readonly List _enrollments = new(); + public IReadOnlyCollection Enrollments => _enrollments; + + public int TotalCredits => _enrollments.Count * 3; + + public void Enroll(Subject subject, IEnrollmentPolicy policy) + { + policy.Validate(this, subject); + _enrollments.Add(new Enrollment(this, subject)); + } + + public void Unenroll(int subjectId) + { + var enrollment = _enrollments.FirstOrDefault(e => e.SubjectId == subjectId); + if (enrollment != null) _enrollments.Remove(enrollment); + } +} +``` + +### Subject + +```csharp +public class Subject +{ + public int Id { get; private set; } + public string Name { get; private set; } + public int Credits { get; } = 3; // Constante: RN-002 + public int ProfessorId { get; private set; } + public Professor Professor { get; private set; } +} +``` + +### Professor + +```csharp +public class Professor +{ + public int Id { get; private set; } + public string Name { get; private set; } + + private readonly List _subjects = new(); + public IReadOnlyCollection Subjects => _subjects; +} +``` + +### Enrollment + +```csharp +public class Enrollment +{ + public int Id { get; private set; } + public int StudentId { get; private set; } + public int SubjectId { get; private set; } + public DateTime EnrolledAt { get; private set; } + + public Student Student { get; private set; } + public Subject Subject { get; private set; } +} +``` + +--- + +## 3. Value Objects + +### Email + +```csharp +public record Email +{ + public string Value { get; } + + private Email(string value) => Value = value; + + public static Result Create(string email) + { + if (string.IsNullOrWhiteSpace(email)) + return Result.Failure("Email requerido"); + + if (!Regex.IsMatch(email, @"^[^@\s]+@[^@\s]+\.[^@\s]+$")) + return Result.Failure("Formato de email inválido"); + + return Result.Success(new Email(email.ToLowerInvariant())); + } +} +``` + +--- + +## 4. Domain Service + +### EnrollmentDomainService (Implementa RN-003 y RN-005) + +```csharp +public class EnrollmentDomainService : IEnrollmentPolicy +{ + public void Validate(Student student, Subject newSubject) + { + // RN-003: Máximo 3 materias + if (student.Enrollments.Count >= 3) + throw new MaxEnrollmentsExceededException(student.Id); + + // RN-005: No repetir profesor + var professorIds = student.Enrollments + .Select(e => e.Subject.ProfessorId) + .ToHashSet(); + + if (professorIds.Contains(newSubject.ProfessorId)) + throw new SameProfessorConstraintException( + student.Id, + newSubject.ProfessorId); + } +} +``` + +--- + +## 5. Excepciones de Dominio + +```csharp +public class MaxEnrollmentsExceededException : DomainException +{ + public MaxEnrollmentsExceededException(int studentId) + : base($"Estudiante {studentId} ya tiene 3 materias inscritas") { } +} + +public class SameProfessorConstraintException : DomainException +{ + public SameProfessorConstraintException(int studentId, int professorId) + : base($"Estudiante {studentId} ya tiene materia con profesor {professorId}") { } +} +``` + +--- + +## 6. Invariantes del Dominio + +| Invariante | Entidad | Validación | +|------------|---------|------------| +| Email único | Student | DB Constraint + Validator | +| Email válido | Student | Value Object | +| Max 3 inscripciones | Student | Domain Service | +| No repetir profesor | Student | Domain Service | +| 3 créditos/materia | Subject | Constante | +| 2 materias/profesor | Professor | Seed Data | diff --git a/docs/entregables/03-configuracion/DV-001-configuracion-repositorio.md b/docs/entregables/03-configuracion/DV-001-configuracion-repositorio.md new file mode 100644 index 0000000..d1317ae --- /dev/null +++ b/docs/entregables/03-configuracion/DV-001-configuracion-repositorio.md @@ -0,0 +1,133 @@ +# DV-001: Configuración del Repositorio + +**Proyecto:** Sistema de Registro de Estudiantes +**Fecha:** 2026-01-07 + +--- + +## 1. Estructura de Carpetas + +``` +/ +├── src/ +│ ├── backend/ +│ │ ├── Domain/ +│ │ ├── Application/ +│ │ ├── Adapters/ +│ │ │ ├── Driving/Api/ +│ │ │ └── Driven/Persistence/ +│ │ └── Host/ +│ └── frontend/ +│ └── src/app/ +│ ├── core/ +│ ├── shared/ +│ └── features/ +├── tests/ +│ ├── Domain.Tests/ +│ ├── Application.Tests/ +│ ├── Adapters.Tests/ +│ └── e2e/ +├── docs/ +│ └── entregables/ +├── database/ +│ ├── scripts/ +│ └── migrations/ +├── deploy/ +│ └── docker/ +├── CLAUDE.md +├── README.md +└── .gitignore +``` + +--- + +## 2. .gitignore + +```gitignore +# .NET +bin/ +obj/ +*.user +*.suo +.vs/ +*.csproj.user + +# Angular +node_modules/ +dist/ +.angular/ +.nx/ + +# IDE +.idea/ +.vscode/ +*.swp + +# Logs +*.log +logs/ + +# Environment +.env +.env.* +!.env.example +appsettings.*.json +!appsettings.json +!appsettings.Development.json.example + +# Database +*.mdf +*.ldf + +# OS +.DS_Store +Thumbs.db + +# Test +coverage/ +TestResults/ + +# Build +publish/ +``` + +--- + +## 3. Comandos de Inicialización + +```bash +# Crear repositorio +git init +git add . +git commit -m "chore: initial project structure" + +# Crear rama de desarrollo +git checkout -b develop + +# Estructura de ramas +# main → producción +# develop → integración +# feature/ → nuevas funcionalidades +# fix/ → correcciones +``` + +--- + +## 4. Convenciones de Commits + +``` +(): + +Tipos: +- feat: Nueva funcionalidad +- fix: Corrección de bug +- refactor: Refactorización +- test: Tests +- docs: Documentación +- chore: Tareas de mantenimiento + +Ejemplos: +feat(students): add create student mutation +fix(enrollment): validate professor constraint +test(domain): add enrollment policy tests +``` diff --git a/docs/entregables/03-configuracion/DV-002-configuracion-dotnet.md b/docs/entregables/03-configuracion/DV-002-configuracion-dotnet.md new file mode 100644 index 0000000..7edbdb6 --- /dev/null +++ b/docs/entregables/03-configuracion/DV-002-configuracion-dotnet.md @@ -0,0 +1,159 @@ +# DV-002: Configuración Solución .NET 10 + +**Proyecto:** Sistema de Registro de Estudiantes +**Fecha:** 2026-01-07 + +--- + +## 1. Crear Solución y Proyectos + +```bash +cd src/backend + +# Crear solución +dotnet new sln -n StudentEnrollment + +# Crear proyectos +dotnet new classlib -n Domain -f net10.0 +dotnet new classlib -n Application -f net10.0 +dotnet new classlib -n Adapters.Driving.Api -f net10.0 +dotnet new classlib -n Adapters.Driven.Persistence -f net10.0 +dotnet new web -n Host -f net10.0 + +# Agregar a solución +dotnet sln add Domain/Domain.csproj +dotnet sln add Application/Application.csproj +dotnet sln add Adapters.Driving.Api/Adapters.Driving.Api.csproj +dotnet sln add Adapters.Driven.Persistence/Adapters.Driven.Persistence.csproj +dotnet sln add Host/Host.csproj +``` + +--- + +## 2. Referencias entre Proyectos + +```bash +# Application → Domain +dotnet add Application reference Domain + +# Adapters.Driving.Api → Application +dotnet add Adapters.Driving.Api reference Application + +# Adapters.Driven.Persistence → Domain +dotnet add Adapters.Driven.Persistence reference Domain + +# Host → Todos +dotnet add Host reference Application +dotnet add Host reference Adapters.Driving.Api +dotnet add Host reference Adapters.Driven.Persistence +``` + +``` +Host +├── Adapters.Driving.Api → Application → Domain +└── Adapters.Driven.Persistence ────────→ Domain +``` + +--- + +## 3. Paquetes NuGet por Proyecto + +### Domain (sin dependencias externas) +```xml + +``` + +### Application +```xml + + + +``` + +### Adapters.Driven.Persistence +```xml + + + +``` + +### Adapters.Driving.Api +```xml + + + +``` + +### Host +```xml + +``` + +--- + +## 4. Directory.Build.props (Raíz backend) + +```xml + + + net10.0 + enable + enable + true + + +``` + +--- + +## 5. Program.cs (Host) + +```csharp +var builder = WebApplication.CreateBuilder(args); + +// Services +builder.Services.AddApplication(); +builder.Services.AddPersistence(builder.Configuration); +builder.Services.AddGraphQLApi(); + +// CORS +builder.Services.AddCors(options => +{ + options.AddDefaultPolicy(policy => + policy.WithOrigins("http://localhost:4200") + .AllowAnyHeader() + .AllowAnyMethod()); +}); + +var app = builder.Build(); + +app.UseCors(); +app.MapGraphQL(); + +app.Run(); +``` + +--- + +## 6. Comandos de Desarrollo + +```bash +# Restaurar dependencias +dotnet restore + +# Build +dotnet build + +# Ejecutar +dotnet run --project Host + +# Watch mode +dotnet watch run --project Host + +# Tests +dotnet test + +# Migraciones EF +dotnet ef migrations add Initial -p Adapters.Driven.Persistence -s Host +dotnet ef database update -p Adapters.Driven.Persistence -s Host +``` diff --git a/docs/entregables/03-configuracion/DV-003-configuracion-angular.md b/docs/entregables/03-configuracion/DV-003-configuracion-angular.md new file mode 100644 index 0000000..b331f2e --- /dev/null +++ b/docs/entregables/03-configuracion/DV-003-configuracion-angular.md @@ -0,0 +1,173 @@ +# DV-003: Configuración Proyecto Angular 21 + +**Proyecto:** Sistema de Registro de Estudiantes +**Fecha:** 2026-01-07 + +--- + +## 1. Crear Proyecto + +```bash +cd src/frontend + +# Crear proyecto Angular 21 +ng new student-enrollment \ + --standalone \ + --style=scss \ + --routing \ + --ssr=false + +cd student-enrollment +``` + +--- + +## 2. Instalar Dependencias + +```bash +# Angular Material +ng add @angular/material + +# Apollo GraphQL +npm install apollo-angular @apollo/client graphql + +# GraphQL Code Generator +npm install -D @graphql-codegen/cli \ + @graphql-codegen/typescript \ + @graphql-codegen/typescript-operations \ + @graphql-codegen/typescript-apollo-angular +``` + +--- + +## 3. Estructura de Carpetas + +```bash +# Crear estructura +mkdir -p src/app/core/{services,graphql/{queries,mutations},interceptors} +mkdir -p src/app/shared/{components,pipes,directives} +mkdir -p src/app/features/{students,enrollment,classmates}/{pages,components} +``` + +--- + +## 4. Configuración Apollo (app.config.ts) + +```typescript +import { ApplicationConfig } from '@angular/core'; +import { provideRouter } from '@angular/router'; +import { provideHttpClient } from '@angular/common/http'; +import { provideApollo } from 'apollo-angular'; +import { HttpLink } from 'apollo-angular/http'; +import { InMemoryCache } from '@apollo/client/core'; +import { inject } from '@angular/core'; +import { routes } from './app.routes'; +import { environment } from '../environments/environment'; + +export const appConfig: ApplicationConfig = { + providers: [ + provideRouter(routes), + provideHttpClient(), + provideApollo(() => { + const httpLink = inject(HttpLink); + return { + link: httpLink.create({ uri: environment.graphqlUrl }), + cache: new InMemoryCache(), + }; + }), + ], +}; +``` + +--- + +## 5. Environment Files + +```typescript +// environments/environment.ts +export const environment = { + production: false, + graphqlUrl: 'https://localhost:5001/graphql', +}; + +// environments/environment.prod.ts +export const environment = { + production: true, + graphqlUrl: '/graphql', +}; +``` + +--- + +## 6. GraphQL Codegen (codegen.ts) + +```typescript +import type { CodegenConfig } from '@graphql-codegen/cli'; + +const config: CodegenConfig = { + schema: 'https://localhost:5001/graphql', + documents: 'src/app/core/graphql/**/*.graphql', + generates: { + 'src/app/core/graphql/generated/types.ts': { + plugins: [ + 'typescript', + 'typescript-operations', + 'typescript-apollo-angular', + ], + }, + }, +}; + +export default config; +``` + +```json +// package.json scripts +{ + "scripts": { + "codegen": "graphql-codegen --config codegen.ts" + } +} +``` + +--- + +## 7. Path Aliases (tsconfig.json) + +```json +{ + "compilerOptions": { + "paths": { + "@core/*": ["src/app/core/*"], + "@shared/*": ["src/app/shared/*"], + "@features/*": ["src/app/features/*"], + "@env/*": ["src/environments/*"] + } + } +} +``` + +--- + +## 8. Comandos de Desarrollo + +```bash +# Desarrollo +ng serve + +# Build producción +ng build --configuration production + +# Generar tipos GraphQL +npm run codegen + +# Lint +ng lint + +# Tests +ng test +ng test --watch=false --code-coverage + +# Generar componente standalone +ng g c features/students/pages/student-list --standalone +``` diff --git a/docs/entregables/03-configuracion/DV-004-configuracion-base-datos.md b/docs/entregables/03-configuracion/DV-004-configuracion-base-datos.md new file mode 100644 index 0000000..9f00ab8 --- /dev/null +++ b/docs/entregables/03-configuracion/DV-004-configuracion-base-datos.md @@ -0,0 +1,169 @@ +# DV-004: Configuración Base de Datos + +**Proyecto:** Sistema de Registro de Estudiantes +**Fecha:** 2026-01-07 + +--- + +## 1. Docker Compose (SQL Server) + +```yaml +# deploy/docker/docker-compose.yml +services: + sqlserver: + image: mcr.microsoft.com/mssql/server + container_name: sqlserver-students + environment: + - ACCEPT_EULA=Y + - SA_PASSWORD=${DB_PASSWORD:-YourStrong@Passw0rd} + - MSSQL_PID=Developer + ports: + - "1433:1433" + volumes: + - sqlserver-data:/var/opt/mssql + healthcheck: + test: /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P "$$SA_PASSWORD" -C -Q "SELECT 1" + interval: 10s + timeout: 5s + retries: 5 + +volumes: + sqlserver-data: +``` + +--- + +## 2. Comandos Docker + +```bash +# Iniciar SQL Server +docker-compose -f deploy/docker/docker-compose.yml up -d sqlserver + +# Ver logs +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 + +# Detener +docker-compose -f deploy/docker/docker-compose.yml down +``` + +--- + +## 3. Connection String + +```json +// appsettings.Development.json +{ + "ConnectionStrings": { + "DefaultConnection": "Server=localhost;Database=StudentEnrollment;User Id=sa;Password=YourStrong@Passw0rd;TrustServerCertificate=True" + } +} +``` + +--- + +## 4. DbContext Configuration + +```csharp +// Adapters/Driven/Persistence/Context/AppDbContext.cs +public class AppDbContext : DbContext +{ + public AppDbContext(DbContextOptions options) : base(options) { } + + public DbSet Students => Set(); + public DbSet Subjects => Set(); + public DbSet Professors => Set(); + public DbSet Enrollments => Set(); + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.ApplyConfigurationsFromAssembly(typeof(AppDbContext).Assembly); + } +} +``` + +--- + +## 5. Registrar DbContext + +```csharp +// Adapters/Driven/Persistence/DependencyInjection.cs +public static class DependencyInjection +{ + public static IServiceCollection AddPersistence( + this IServiceCollection services, + IConfiguration configuration) + { + services.AddDbContext(options => + options.UseSqlServer( + configuration.GetConnectionString("DefaultConnection"))); + + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + + return services; + } +} +``` + +--- + +## 6. Migraciones + +```bash +# Crear migración inicial +dotnet ef migrations add InitialCreate \ + -p src/backend/Adapters.Driven.Persistence \ + -s src/backend/Host + +# Aplicar migración +dotnet ef database update \ + -p src/backend/Adapters.Driven.Persistence \ + -s src/backend/Host + +# Generar script SQL +dotnet ef migrations script \ + -p src/backend/Adapters.Driven.Persistence \ + -s src/backend/Host \ + -o database/scripts/create.sql +``` + +--- + +## 7. Seed Data + +```csharp +// Adapters/Driven/Persistence/Seeding/DataSeeder.cs +public static class DataSeeder +{ + public static void Seed(ModelBuilder modelBuilder) + { + // Profesores + modelBuilder.Entity().HasData( + new { Id = 1, Name = "Dr. García" }, + new { Id = 2, Name = "Dra. Martínez" }, + new { Id = 3, Name = "Dr. López" }, + new { Id = 4, Name = "Dra. Rodríguez" }, + new { Id = 5, Name = "Dr. Hernández" } + ); + + // Materias (2 por profesor) + modelBuilder.Entity().HasData( + new { Id = 1, Name = "Matemáticas I", Credits = 3, ProfessorId = 1 }, + new { Id = 2, Name = "Matemáticas II", Credits = 3, ProfessorId = 1 }, + new { Id = 3, Name = "Física I", Credits = 3, ProfessorId = 2 }, + new { Id = 4, Name = "Física II", Credits = 3, ProfessorId = 2 }, + new { Id = 5, Name = "Programación I", Credits = 3, ProfessorId = 3 }, + new { Id = 6, Name = "Programación II", Credits = 3, ProfessorId = 3 }, + new { Id = 7, Name = "Base de Datos I", Credits = 3, ProfessorId = 4 }, + new { Id = 8, Name = "Base de Datos II", Credits = 3, ProfessorId = 4 }, + new { Id = 9, Name = "Redes I", Credits = 3, ProfessorId = 5 }, + new { Id = 10, Name = "Redes II", Credits = 3, ProfessorId = 5 } + ); + } +} +``` diff --git a/docs/entregables/03-configuracion/DV-005-variables-entorno.md b/docs/entregables/03-configuracion/DV-005-variables-entorno.md new file mode 100644 index 0000000..3bf38a4 --- /dev/null +++ b/docs/entregables/03-configuracion/DV-005-variables-entorno.md @@ -0,0 +1,161 @@ +# DV-005: Variables de Entorno + +**Proyecto:** Sistema de Registro de Estudiantes +**Fecha:** 2026-01-07 + +--- + +## 1. Backend (.NET 10) + +### appsettings.json (Base) + +```json +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning", + "HotChocolate": "Warning" + } + }, + "AllowedHosts": "*", + "GraphQL": { + "MaxExecutionDepth": 5, + "MaxComplexity": 100 + } +} +``` + +### appsettings.Development.json + +```json +{ + "ConnectionStrings": { + "DefaultConnection": "Server=localhost;Database=StudentEnrollment;User Id=sa;Password=YourStrong@Passw0rd;TrustServerCertificate=True" + }, + "GraphQL": { + "EnableIntrospection": true + } +} +``` + +### appsettings.Production.json.example + +```json +{ + "ConnectionStrings": { + "DefaultConnection": "Server=PROD_SERVER;Database=StudentEnrollment;User Id=app_user;Password=CHANGE_ME;Encrypt=True" + }, + "GraphQL": { + "EnableIntrospection": false + } +} +``` + +--- + +## 2. User Secrets (Desarrollo Local) + +```bash +# Inicializar secrets +cd src/backend/Host +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" + +# Listar secrets +dotnet user-secrets list + +# Remover +dotnet user-secrets remove "ConnectionStrings:DefaultConnection" +``` + +--- + +## 3. Frontend (Angular 21) + +### environment.ts (Desarrollo) + +```typescript +export const environment = { + production: false, + graphqlUrl: 'https://localhost:5001/graphql', + apiTimeout: 30000, +}; +``` + +### environment.prod.ts (Producción) + +```typescript +export const environment = { + production: true, + graphqlUrl: '/graphql', + apiTimeout: 15000, +}; +``` + +--- + +## 4. Docker (.env) + +### .env.example + +```env +# Database +DB_PASSWORD=YourStrong@Passw0rd +DB_NAME=StudentEnrollment + +# API +ASPNETCORE_ENVIRONMENT=Development +ASPNETCORE_URLS=http://+:5000 + +# Frontend +FRONTEND_URL=http://localhost:4200 +``` + +### docker-compose.yml (usando .env) + +```yaml +services: + sqlserver: + environment: + - SA_PASSWORD=${DB_PASSWORD} + + api: + environment: + - ASPNETCORE_ENVIRONMENT=${ASPNETCORE_ENVIRONMENT} + - ConnectionStrings__DefaultConnection=Server=sqlserver;Database=${DB_NAME};User Id=sa;Password=${DB_PASSWORD};TrustServerCertificate=True +``` + +--- + +## 5. Variables por Ambiente + +| Variable | Desarrollo | Producción | +|----------|------------|------------| +| ConnectionString | localhost | Servidor prod | +| EnableIntrospection | true | false | +| LogLevel | Debug | Warning | +| CORS Origins | localhost:4200 | dominio.com | +| MaxExecutionDepth | 10 | 5 | + +--- + +## 6. Cargar Configuración + +```csharp +// Program.cs +var builder = WebApplication.CreateBuilder(args); + +// Orden de carga (último gana): +// 1. appsettings.json +// 2. appsettings.{Environment}.json +// 3. User Secrets (solo Development) +// 4. Variables de entorno +// 5. Command line args + +var connectionString = builder.Configuration + .GetConnectionString("DefaultConnection"); +``` diff --git a/docs/entregables/03-configuracion/DV-006-herramientas-calidad.md b/docs/entregables/03-configuracion/DV-006-herramientas-calidad.md new file mode 100644 index 0000000..5722b92 --- /dev/null +++ b/docs/entregables/03-configuracion/DV-006-herramientas-calidad.md @@ -0,0 +1,197 @@ +# DV-006: Herramientas de Calidad + +**Proyecto:** Sistema de Registro de Estudiantes +**Fecha:** 2026-01-07 + +--- + +## 1. Backend (.NET 10) + +### .editorconfig + +```ini +root = true + +[*] +indent_style = space +indent_size = 4 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.{cs,csx}] +csharp_style_var_when_type_is_apparent = true:suggestion +csharp_prefer_braces = true:warning +dotnet_sort_system_directives_first = true +``` + +### Directory.Build.props (Analyzers) + +```xml + + + net10.0 + enable + enable + true + true + latest + + + + + + +``` + +### Comandos + +```bash +# Formatear código +dotnet format + +# Verificar sin cambiar +dotnet format --verify-no-changes + +# Build con warnings +dotnet build -warnaserror +``` + +--- + +## 2. Frontend (Angular 21) + +### ESLint + +```bash +# Instalar +ng add @angular-eslint/schematics +``` + +```json +// .eslintrc.json +{ + "root": true, + "overrides": [ + { + "files": ["*.ts"], + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/recommended", + "plugin:@angular-eslint/recommended" + ], + "rules": { + "@angular-eslint/component-selector": ["error", { + "prefix": "app", + "style": "kebab-case", + "type": "element" + }], + "@typescript-eslint/no-unused-vars": "error", + "no-console": "warn" + } + } + ] +} +``` + +### Prettier + +```json +// .prettierrc +{ + "singleQuote": true, + "trailingComma": "es5", + "tabWidth": 2, + "semi": true, + "printWidth": 100 +} +``` + +```json +// .prettierignore +node_modules +dist +coverage +.angular +``` + +--- + +## 3. Pre-commit Hooks (Husky) + +```bash +# Instalar +npm install -D husky lint-staged +npx husky init +``` + +```json +// package.json +{ + "lint-staged": { + "*.ts": ["eslint --fix", "prettier --write"], + "*.html": ["prettier --write"], + "*.scss": ["prettier --write"] + } +} +``` + +```bash +# .husky/pre-commit +npm run lint-staged +cd ../backend && dotnet format --verify-no-changes +``` + +--- + +## 4. Scripts package.json + +```json +{ + "scripts": { + "start": "ng serve", + "build": "ng build", + "test": "ng test", + "lint": "ng lint", + "format": "prettier --write \"src/**/*.{ts,html,scss}\"", + "format:check": "prettier --check \"src/**/*.{ts,html,scss}\"", + "codegen": "graphql-codegen", + "prepare": "husky" + } +} +``` + +--- + +## 5. CI Checks + +```yaml +# .github/workflows/ci.yml (ejemplo) +jobs: + backend: + steps: + - run: dotnet format --verify-no-changes + - run: dotnet build -warnaserror + - run: dotnet test + + frontend: + steps: + - run: npm ci + - run: npm run lint + - run: npm run format:check + - run: npm run build + - run: npm test -- --watch=false +``` + +--- + +## 6. Checklist de Calidad + +| Verificación | Backend | Frontend | +|--------------|---------|----------| +| Formato código | `dotnet format` | `prettier` | +| Linting | Roslyn Analyzers | ESLint | +| Tipos | Nullable enabled | TypeScript strict | +| Tests | xUnit | Jasmine/Jest | +| Pre-commit | dotnet format | lint-staged |