docs: add project documentation and env template

Environment:
- .env.example template with all configuration variables
- Database, API, GraphQL, and frontend settings

Documentation:
- Architecture Decision Records (ADR-001 to ADR-004)
- Deployment guide with Docker and K8s instructions
- OWASP security checklist
- Code review checklist
- Activity plan and deliverables

Architecture diagrams (PlantUML):
- Use cases, domain model, sequence diagrams
- Component, ER, state, and deployment diagrams
- C4 context diagram

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Andrés Eduardo García Márquez 2026-01-07 23:00:56 -05:00
parent 9f11aab2a8
commit 3e62a94df7
49 changed files with 6367 additions and 0 deletions

42
.env.example Normal file
View File

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

View File

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

419
docs/DEPLOYMENT.md Normal file
View File

@ -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 <MigrationName>
```
---
## 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 <pod-name> -n student-enrollment
# Conectar a pod
kubectl exec -it <pod-name> -n student-enrollment -- /bin/sh
# Verificar conectividad BD
kubectl exec -it <api-pod> -n student-enrollment -- \
curl -v telnet://sqlserver:1433
# Verificar ingress
kubectl describe ingress student-ingress -n student-enrollment
```

270
docs/ENTREGABLES.md Normal file
View File

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

78
docs/OWASP_CHECKLIST.md Normal file
View File

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

245
docs/PLAN_ACTIVIDADES.md Normal file
View File

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

23
docs/Prueba Técnica.md Normal file
View File

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

View File

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

View File

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

View File

@ -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<Student[]>([]);
loading = signal(true);
// Estado del servidor con Apollo
this.apollo.watchQuery<GetStudentsQuery>({...})
```
## 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<Student[]>([]);
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)

View File

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

View File

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

View File

View File

@ -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 : <<include>>
UC4 ..> UC4b : <<include>>
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

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -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 <<Entity>> {
- id: int
- name: string
- email: Email
- enrollments: List<Enrollment>
--
+ getTotalCredits(): int
+ canEnrollIn(subject: Subject): bool
+ enroll(subject: Subject): Enrollment
+ unenroll(enrollmentId: int): void
}
class Subject <<Entity>> {
- id: int
- name: string
- credits: int {= 3}
- professorId: int
--
+ getProfessor(): Professor
}
class Professor <<Entity>> {
- id: int
- name: string
- subjects: List<Subject>
--
+ getSubjects(): List<Subject>
}
class Enrollment <<Entity>> {
- id: int
- studentId: int
- subjectId: int
- enrolledAt: DateTime
--
+ getStudent(): Student
+ getSubject(): Subject
}
class Email <<Value Object>> {
- value: string
--
+ {static} create(value: string): Email
- validate(value: string): void
}
class EnrollmentDomainService <<Domain Service>> {
--
+ 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
<b>Invariantes:</b>
- Máximo 3 inscripciones
- Email válido y único
- No repetir profesor
end note
note bottom of Subject
<b>Invariantes:</b>
- Créditos = 3 (fijo)
- Pertenece a un profesor
end note
note right of Professor
Cada profesor
imparte exactamente
2 materias
end note
@enduml

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 24 KiB

View File

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

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 34 KiB

View File

@ -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
<b>Regla de Dependencia:</b>
Domain no depende
de capas externas
end note
note bottom of graphql
Endpoints:
- /graphql
- /health
end note
@enduml

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 51 KiB

View File

@ -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 <<PK>>
--
* Name : nvarchar(100)
* Email : nvarchar(255) <<unique>>
* CreatedAt : datetime2
UpdatedAt : datetime2
}
entity "Professors" as professors {
* **Id** : int <<PK>>
--
* Name : nvarchar(100)
}
entity "Subjects" as subjects {
* **Id** : int <<PK>>
--
* Name : nvarchar(100)
* Credits : int {= 3}
* **ProfessorId** : int <<FK>>
}
entity "Enrollments" as enrollments {
* **Id** : int <<PK>>
--
* **StudentId** : int <<FK>>
* **SubjectId** : int <<FK>>
* EnrolledAt : datetime2
--
<<unique>> (StudentId, SubjectId)
}
' Relaciones
students ||--o{ enrollments : "tiene"
subjects ||--o{ enrollments : "inscripciones"
professors ||--|| subjects : "imparte 2"
note right of students
<b>Restricciones:</b>
- Email único
- Máximo 3 enrollments
end note
note right of subjects
<b>Datos iniciales:</b>
10 materias
3 créditos cada una
end note
note right of professors
<b>Datos iniciales:</b>
5 profesores
2 materias cada uno
end note
note bottom of enrollments
<b>Reglas de negocio:</b>
- (StudentId, SubjectId) único
- Estudiante no puede tener
2 materias del mismo profesor
end note
@enduml

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -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
<b>Validaciones en cada inscripción:</b>
- Límite de créditos
- No repetir profesor
- Materia no duplicada
end note
state validacion <<choice>>
SinMaterias --> validacion : intenta inscribir
Parcial --> validacion : intenta inscribir
validacion --> Parcial : [válido]
validacion --> [*] : [inválido]\nmuestra error
@enduml

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -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 <<container>> {
component "Nginx" as nginx {
[Static Files]
[Reverse Proxy]
}
}
node "student-api" as apiContainer <<container>> {
component "ASP.NET Core" as aspnet {
[Kestrel Server]
[GraphQL Endpoint]
[Health Check]
}
}
node "student-db" as dbContainer <<container>> {
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
<b>Nginx Config:</b>
- Gzip/Brotli compression
- Static file caching
- GraphQL proxy
- Security headers
end note
note right of aspnet
<b>Optimizaciones:</b>
- Server GC
- ReadyToRun
- Connection pooling
- Rate limiting
end note
note right of sqlserver
<b>Recursos:</b>
- 2 CPU cores
- 2.5 GB RAM
- Persistent volume
end note
@enduml

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 22 KiB

View File

@ -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 <<Persona>>
rectangle "Sistema de Registro\nde Estudiantes" as system <<Software System>> #lightblue {
}
rectangle "Base de Datos\nSQL Server" as database <<External System>> #lightgray {
}
student --> system : Usa para registrarse\ne inscribirse en materias
system --> database : Lee y escribe\ndatos de inscripciones
note right of student
<b>Estudiante</b>
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
<b>Sistema de Registro</b>
Aplicación web que permite:
- CRUD de estudiantes
- Inscripción en materias
- Validación de reglas de negocio
- Visualización de compañeros
<b>Stack:</b>
Frontend: Angular 21
Backend: .NET 10 + GraphQL
end note
note right of database
<b>SQL Server 2022</b>
Almacena:
- Estudiantes
- Profesores
- Materias
- Inscripciones
end note
@enduml

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 10 KiB

View File

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

View File

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

View File

@ -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<Email> Create(string email)
{
if (string.IsNullOrWhiteSpace(email))
return Result.Failure<Email>("EMAIL_REQUIRED");
if (!EmailRegex.IsMatch(email))
return Result.Failure<Email>("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 | - | - |

View File

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

View File

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

View File

@ -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<int, Subject>
{
protected override async Task<IReadOnlyDictionary<int, Subject>> LoadBatchAsync(
IReadOnlyList<int> 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 | - | - |

View File

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

View File

@ -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()) {
<mat-spinner />
} @else {
<mat-table [dataSource]="students()">
<!-- columns -->
</mat-table>
}
`
})
export class StudentListComponent {
private studentService = inject(StudentService);
students = signal<Student[]>([]);
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 |

View File

@ -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<string> Errors { get; }
protected Result(bool success, IEnumerable<string>? errors = null)
{
IsSuccess = success;
Errors = errors ?? Array.Empty<string>();
}
public static Result Success() => new(true);
public static Result Failure(params string[] errors) => new(false, errors);
}
public class Result<T> : Result
{
public T? Value { get; }
private Result(T value) : base(true) => Value = value;
private Result(IEnumerable<string> errors) : base(false, errors) { }
public static Result<T> Success(T value) => new(value);
public static new Result<T> Failure(params string[] errors) => new(errors);
}
```
---
## 4. Handler con Manejo de Errores
```csharp
public class EnrollStudentHandler
{
public async Task<EnrollmentPayload> 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<EnrollmentDto>();
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<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
{
private readonly ILogger<LoggingBehavior<TRequest, TResponse>> _logger;
public async Task<TResponse> 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).

View File

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

View File

@ -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] │
├──────────────────────────────────────────────────────────────┤
│ │
<router-outlet>
│ │
└──────────────────────────────────────────────────────────────┘
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 |

View File

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

View File

@ -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<EnrollmentDto> 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<string> StudentNames);
```
### Payloads (Respuestas con errores)
```csharp
public record StudentPayload(StudentDto? Student, IEnumerable<string>? Errors);
public record EnrollmentPayload(EnrollmentDto? Enrollment, IEnumerable<string>? Errors);
public record DeletePayload(bool Success, IEnumerable<string>? 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<Student, StudentDto>()
.Map(dest => dest.TotalCredits, src => src.Enrollments.Count * 3);
config.NewConfig<Subject, SubjectDto>();
config.NewConfig<Professor, ProfessorDto>();
config.NewConfig<Enrollment, EnrollmentDto>();
}
}
```
---
## 4. Validadores (FluentValidation)
```csharp
public class CreateStudentInputValidator : AbstractValidator<CreateStudentInput>
{
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<EnrollInput>
{
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<Student?> GetByIdAsync(int id, CancellationToken ct = default);
Task<Student?> GetByIdWithEnrollmentsAsync(int id, CancellationToken ct = default);
Task<IEnumerable<Student>> GetAllAsync(CancellationToken ct = default);
Task<bool> 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<Subject?> GetByIdAsync(int id, CancellationToken ct = default);
Task<IEnumerable<Subject>> GetAllAsync(CancellationToken ct = default);
Task<IEnumerable<Subject>> GetByProfessorIdAsync(int professorId, CancellationToken ct = default);
}
public interface IEnrollmentRepository
{
Task<IEnumerable<Enrollment>> GetByStudentIdAsync(int studentId, CancellationToken ct = default);
Task<IEnumerable<Student>> GetClassmatesAsync(int studentId, int subjectId, CancellationToken ct = default);
}
public interface IUnitOfWork
{
Task<int> 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 |

View File

@ -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<Enrollment> _enrollments = new();
public IReadOnlyCollection<Enrollment> 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<Subject> _subjects = new();
public IReadOnlyCollection<Subject> 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<Email> Create(string email)
{
if (string.IsNullOrWhiteSpace(email))
return Result.Failure<Email>("Email requerido");
if (!Regex.IsMatch(email, @"^[^@\s]+@[^@\s]+\.[^@\s]+$"))
return Result.Failure<Email>("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 |

View File

@ -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
```
<tipo>(<alcance>): <descripción>
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
```

View File

@ -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
<!-- Solo C# puro -->
```
### Application
```xml
<PackageReference Include="FluentValidation" />
<PackageReference Include="Mapster" />
<PackageReference Include="MediatR" />
```
### Adapters.Driven.Persistence
```xml
<PackageReference Include="Microsoft.EntityFrameworkCore" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" />
```
### Adapters.Driving.Api
```xml
<PackageReference Include="HotChocolate.AspNetCore" />
<PackageReference Include="HotChocolate.Data" />
<PackageReference Include="HotChocolate.Data.EntityFramework" />
```
### Host
```xml
<PackageReference Include="Serilog.AspNetCore" />
```
---
## 4. Directory.Build.props (Raíz backend)
```xml
<Project>
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
</Project>
```
---
## 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
```

View File

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

View File

@ -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<AppDbContext> options) : base(options) { }
public DbSet<Student> Students => Set<Student>();
public DbSet<Subject> Subjects => Set<Subject>();
public DbSet<Professor> Professors => Set<Professor>();
public DbSet<Enrollment> Enrollments => Set<Enrollment>();
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<AppDbContext>(options =>
options.UseSqlServer(
configuration.GetConnectionString("DefaultConnection")));
services.AddScoped<IStudentRepository, StudentRepository>();
services.AddScoped<ISubjectRepository, SubjectRepository>();
services.AddScoped<IUnitOfWork, UnitOfWork>();
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<Professor>().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<Subject>().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 }
);
}
}
```

View File

@ -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");
```

View File

@ -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
<Project>
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
<AnalysisLevel>latest</AnalysisLevel>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" PrivateAssets="all" />
</ItemGroup>
</Project>
```
### 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 |