docs: add credentials section and update namespace to academia
Deploy to k3s / deploy (push) Failing after 27s
Details
Deploy to k3s / deploy (push) Failing after 27s
Details
- Document ~/.secrets/credentials.env usage - Add auto-login examples for Gitea, API, and K3s - Update namespace from student-enrollment to academia - Remove CLAUDE.md from .gitignore to track in repo Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
aeff93f7c6
commit
8a69ac615f
|
|
@ -135,7 +135,6 @@ desktop.ini
|
||||||
.cache/
|
.cache/
|
||||||
tmp/
|
tmp/
|
||||||
temp/
|
temp/
|
||||||
CLAUDE.md
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════
|
||||||
# Data / Runtime
|
# Data / Runtime
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,911 @@
|
||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
Este archivo proporciona orientación a Claude Code (claude.ai/code) para trabajar con el código de este repositorio.
|
||||||
|
|
||||||
|
## Contexto
|
||||||
|
|
||||||
|
**Prueba técnica** para posición de **Desarrollador Master .NET/Angular** en **Inter Rapidísimo**.
|
||||||
|
- Cargo: Desarrollador Senior .NET/Angular
|
||||||
|
- Empresa: Inter Rapidísimo (logística y mensajería)
|
||||||
|
- Ubicación: Bogotá, Av. El Dorado (presencial)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Credenciales y Autenticación
|
||||||
|
|
||||||
|
**Archivo de credenciales:** `~/.secrets/credentials.env`
|
||||||
|
|
||||||
|
### Uso Obligatorio
|
||||||
|
|
||||||
|
Antes de cualquier operación que requiera autenticación (Gitea, K3s, SSH, etc.):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
source ~/.secrets/credentials.env
|
||||||
|
```
|
||||||
|
|
||||||
|
### Variables Disponibles
|
||||||
|
|
||||||
|
| Variable | Uso |
|
||||||
|
|----------|-----|
|
||||||
|
| `GITEA_URL` | URL del servidor Gitea |
|
||||||
|
| `GITEA_ADMIN_USER` | Usuario Gitea |
|
||||||
|
| `GITEA_ADMIN_PASS` | Contraseña Gitea |
|
||||||
|
| `K8S_MASTER_HOST` | Host del master K3s |
|
||||||
|
| `K8S_SUDO_PASS` | Password sudo en K3s |
|
||||||
|
|
||||||
|
### Login Automático desde Terminal
|
||||||
|
|
||||||
|
**Git (Gitea):**
|
||||||
|
```bash
|
||||||
|
source ~/.secrets/credentials.env
|
||||||
|
git remote set-url gitea "https://${GITEA_ADMIN_USER}:${GITEA_ADMIN_PASS}@devops.ingeniumcodex.com/andresgarcia0313/academia.git"
|
||||||
|
```
|
||||||
|
|
||||||
|
**API Gitea:**
|
||||||
|
```bash
|
||||||
|
source ~/.secrets/credentials.env
|
||||||
|
curl -u "$GITEA_ADMIN_USER:$GITEA_ADMIN_PASS" "$GITEA_URL/api/v1/user/repos"
|
||||||
|
```
|
||||||
|
|
||||||
|
**K3s (SSH):**
|
||||||
|
```bash
|
||||||
|
source ~/.secrets/credentials.env
|
||||||
|
ssh $K8S_MASTER_HOST "echo $K8S_SUDO_PASS | sudo -S kubectl get pods -A"
|
||||||
|
```
|
||||||
|
|
||||||
|
> **IMPORTANTE:** Siempre intentar autenticación automática usando estas variables. NO preguntar al usuario por credenciales si ya están en el archivo.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Resumen del Proyecto
|
||||||
|
|
||||||
|
**Aplicación web de registro de estudiantes** - Sistema CRUD con reglas de negocio específicas.
|
||||||
|
|
||||||
|
## Stack Tecnológico
|
||||||
|
|
||||||
|
| Capa | Tecnología |
|
||||||
|
|------|------------|
|
||||||
|
| **Backend** | .NET 10 (C#) |
|
||||||
|
| **API** | GraphQL (HotChocolate) |
|
||||||
|
| **Frontend** | Angular 21 (TypeScript) |
|
||||||
|
| **Base de datos** | SQL Server / SQLite (dev) |
|
||||||
|
| **ORM** | Entity Framework Core |
|
||||||
|
| **Autenticación** | JWT (HMAC-SHA256) + PBKDF2 |
|
||||||
|
| **Validación** | FluentValidation |
|
||||||
|
| **Mapping** | Mapster |
|
||||||
|
| **UI** | Angular Material |
|
||||||
|
| **GraphQL Client** | Apollo Angular |
|
||||||
|
| **CI/CD** | Gitea Actions + K3s |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Requisitos de Negocio
|
||||||
|
|
||||||
|
### Funcionalidad Principal
|
||||||
|
- CRUD completo para inscripción de estudiantes
|
||||||
|
- Estudiantes se inscriben en programa basado en créditos
|
||||||
|
- Visualización de inscripciones de otros estudiantes (solo nombres en clases compartidas)
|
||||||
|
|
||||||
|
### Reglas del Dominio (CRÍTICAS)
|
||||||
|
- **10 asignaturas** en total, cada una vale **3 créditos**
|
||||||
|
- **5 profesores**, cada uno imparte **exactamente 2 asignaturas**
|
||||||
|
- Estudiantes pueden seleccionar **máximo 3 asignaturas** (= 9 créditos)
|
||||||
|
- **Restricción clave:** Un estudiante NO puede tener clases con el mismo profesor
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Arquitectura
|
||||||
|
|
||||||
|
### Clean Architecture + CQRS (4 Capas)
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────────┐
|
||||||
|
│ HOST (Composition Root) │
|
||||||
|
│ Program.cs / DI / Configuration │
|
||||||
|
└──────────────────────────────┬──────────────────────────────────┘
|
||||||
|
│
|
||||||
|
┌──────────────────────────────▼──────────────────────────────────┐
|
||||||
|
│ ADAPTERS │
|
||||||
|
│ ┌─────────────────────────────────────────────────────────┐ │
|
||||||
|
│ │ DRIVING (Primary) │ DRIVEN (Secondary) │ │
|
||||||
|
│ │ ───────────────── │ ────────────────── │ │
|
||||||
|
│ │ GraphQL API ◄──Angular SPA │ Persistence (EF Core) │ │
|
||||||
|
│ │ (HotChocolate) │ DataLoaders │ │
|
||||||
|
│ │ Middleware │ External Services │ │
|
||||||
|
│ └─────────────────────────────────────────────────────────┘ │
|
||||||
|
└──────────────────────────────┬──────────────────────────────────┘
|
||||||
|
│
|
||||||
|
┌──────────────────────────────▼──────────────────────────────────┐
|
||||||
|
│ APPLICATION │
|
||||||
|
│ ┌─────────────────────────────────────────────────────────┐ │
|
||||||
|
│ │ Commands / Queries / Handlers / DTOs / Validators │ │
|
||||||
|
│ └─────────────────────────────────────────────────────────┘ │
|
||||||
|
└──────────────────────────────┬──────────────────────────────────┘
|
||||||
|
│
|
||||||
|
┌──────────────────────────────▼──────────────────────────────────┐
|
||||||
|
│ DOMAIN │
|
||||||
|
│ ┌───────────┐ ┌───────────┐ ┌───────────┐ ┌───────────────┐ │
|
||||||
|
│ │ Entities │ │ Value │ │ Domain │ │ Ports │ │
|
||||||
|
│ │ │ │ Objects │ │ Services │ │ (Contracts) │ │
|
||||||
|
│ └───────────┘ └───────────┘ └───────────┘ └───────────────┘ │
|
||||||
|
└─────────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### Regla de Dependencia (INVIOLABLE)
|
||||||
|
|
||||||
|
```
|
||||||
|
Host → Adapters → Application → Domain
|
||||||
|
│ ▲
|
||||||
|
└── implementa Ports ────┘
|
||||||
|
|
||||||
|
• Domain define Ports (interfaces)
|
||||||
|
• Adapters/Driven implementa Ports
|
||||||
|
• Domain NO depende de NADA externo
|
||||||
|
```
|
||||||
|
|
||||||
|
### Nomenclatura Ports & Adapters
|
||||||
|
|
||||||
|
| Término | Significado | Ubicación |
|
||||||
|
|---------|-------------|-----------|
|
||||||
|
| **Ports** | Contratos/Interfaces | `Domain/Ports/` |
|
||||||
|
| **Driving Adapters** | Entrada (quien llama) | `Adapters/Driving/` |
|
||||||
|
| **Driven Adapters** | Salida (a quien se llama) | `Adapters/Driven/` |
|
||||||
|
|
||||||
|
### Diagramas UML
|
||||||
|
|
||||||
|
Ubicación: `docs/architecture/diagrams/`
|
||||||
|
|
||||||
|
| Diagrama | Archivo | Propósito |
|
||||||
|
|----------|---------|-----------|
|
||||||
|
| Casos de Uso | `01-use-cases.puml` | Funcionalidades del estudiante |
|
||||||
|
| Modelo de Dominio | `02-domain-model.puml` | Entidades y Value Objects |
|
||||||
|
| Secuencia | `03-sequence-enrollment.puml` | Flujo de inscripción |
|
||||||
|
| Componentes | `04-components.puml` | Arquitectura del sistema |
|
||||||
|
| Entidad-Relación | `05-entity-relationship.puml` | Modelo de base de datos |
|
||||||
|
| Estados | `06-state-enrollment.puml` | Estados de inscripción |
|
||||||
|
| Despliegue | `07-deployment.puml` | Infraestructura Docker |
|
||||||
|
| C4 Contexto | `08-c4-context.puml` | Vista de alto nivel |
|
||||||
|
|
||||||
|
Regenerar SVG: `cat archivo.puml | plantuml -tsvg -pipe > archivo.svg`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Estructura del Proyecto
|
||||||
|
|
||||||
|
```
|
||||||
|
/
|
||||||
|
├── src/
|
||||||
|
│ │
|
||||||
|
│ │ ═══════════════ BACKEND (.NET 10) ═══════════════
|
||||||
|
│ │
|
||||||
|
│ ├── backend/
|
||||||
|
│ │ │
|
||||||
|
│ │ ├── Domain/ # ENTITIES (Núcleo puro)
|
||||||
|
│ │ │ ├── Entities/ # Student, Subject, Professor, Enrollment, User
|
||||||
|
│ │ │ ├── ValueObjects/ # Email
|
||||||
|
│ │ │ ├── ReadModels/ # ClassmateInfo
|
||||||
|
│ │ │ ├── Exceptions/ # DomainException
|
||||||
|
│ │ │ ├── Ports/
|
||||||
|
│ │ │ │ └── Repositories/ # IStudentRepository, IUserRepository, etc.
|
||||||
|
│ │ │ └── Services/ # EnrollmentDomainService
|
||||||
|
│ │ │
|
||||||
|
│ │ ├── Application/ # USE CASES (CQRS)
|
||||||
|
│ │ │ ├── Common/
|
||||||
|
│ │ │ │ ├── Behaviors/ # ValidationBehavior
|
||||||
|
│ │ │ │ └── ValidationPatterns.cs # Regex patterns centralizados
|
||||||
|
│ │ │ ├── Auth/ # Autenticación
|
||||||
|
│ │ │ │ ├── Commands/ # LoginCommand, RegisterCommand, ResetPasswordCommand
|
||||||
|
│ │ │ │ ├── DTOs/ # AuthDtos (LoginRequest, RegisterRequest, etc.)
|
||||||
|
│ │ │ │ ├── IJwtService.cs # Interfaz JWT
|
||||||
|
│ │ │ │ ├── IPasswordService.cs # Interfaz hashing
|
||||||
|
│ │ │ │ └── JwtOptions.cs # Configuración JWT
|
||||||
|
│ │ │ ├── Students/
|
||||||
|
│ │ │ │ ├── Commands/ # CreateStudent, UpdateStudent, DeleteStudent
|
||||||
|
│ │ │ │ ├── Queries/ # GetStudents, GetStudentById, GetStudentsPaged
|
||||||
|
│ │ │ │ └── DTOs/ # StudentDto
|
||||||
|
│ │ │ ├── Subjects/
|
||||||
|
│ │ │ │ ├── Queries/ # GetSubjects, GetAvailableSubjects
|
||||||
|
│ │ │ │ └── DTOs/ # SubjectDto
|
||||||
|
│ │ │ ├── Professors/
|
||||||
|
│ │ │ │ ├── Queries/ # GetProfessors
|
||||||
|
│ │ │ │ └── DTOs/ # ProfessorDto
|
||||||
|
│ │ │ ├── Enrollments/
|
||||||
|
│ │ │ │ ├── Commands/ # EnrollStudent, UnenrollStudent
|
||||||
|
│ │ │ │ ├── Queries/ # GetClassmates
|
||||||
|
│ │ │ │ └── DTOs/ # EnrollmentDtos
|
||||||
|
│ │ │ └── DependencyInjection.cs
|
||||||
|
│ │ │
|
||||||
|
│ │ ├── Adapters/ # INTERFACE ADAPTERS
|
||||||
|
│ │ │ │
|
||||||
|
│ │ │ ├── Driving/ # Entrada (Primary)
|
||||||
|
│ │ │ │ └── Api/ # GraphQL (HotChocolate)
|
||||||
|
│ │ │ │ ├── Types/
|
||||||
|
│ │ │ │ │ ├── Auth/ # AuthMutations, AuthQueries
|
||||||
|
│ │ │ │ │ ├── Students/ # StudentType
|
||||||
|
│ │ │ │ │ ├── Subjects/ # SubjectType
|
||||||
|
│ │ │ │ │ ├── Professors/ # ProfessorType
|
||||||
|
│ │ │ │ │ ├── Enrollments/ # EnrollmentType
|
||||||
|
│ │ │ │ │ ├── Query.cs
|
||||||
|
│ │ │ │ │ └── Mutation.cs
|
||||||
|
│ │ │ │ ├── Middleware/ # GraphQLErrorFilter
|
||||||
|
│ │ │ │ └── Extensions/ # GraphQLExtensions
|
||||||
|
│ │ │ │
|
||||||
|
│ │ │ └── Driven/ # Salida (Secondary)
|
||||||
|
│ │ │ └── Persistence/ # EF Core
|
||||||
|
│ │ │ ├── Context/ # AppDbContext
|
||||||
|
│ │ │ ├── Configurations/# Fluent API configs (incl. UserConfiguration)
|
||||||
|
│ │ │ ├── Repositories/ # Student, Subject, Professor, Enrollment, User, UnitOfWork
|
||||||
|
│ │ │ ├── Services/ # JwtService, PasswordService
|
||||||
|
│ │ │ ├── DataLoaders/ # StudentById, SubjectById, ProfessorById
|
||||||
|
│ │ │ ├── Migrations/ # InitialCreate, SeedData, AddUsersTable
|
||||||
|
│ │ │ ├── Seeding/ # DataSeeder
|
||||||
|
│ │ │ ├── CompiledQueries.cs
|
||||||
|
│ │ │ └── DependencyInjection.cs
|
||||||
|
│ │ │
|
||||||
|
│ │ └── Host/ # COMPOSITION ROOT
|
||||||
|
│ │ └── Program.cs
|
||||||
|
│ │
|
||||||
|
│ │ ═══════════════ FRONTEND (Angular 21) ═══════════════
|
||||||
|
│ │
|
||||||
|
│ └── frontend/
|
||||||
|
│ ├── src/
|
||||||
|
│ │ ├── app/
|
||||||
|
│ │ │ ├── core/ # Singleton services
|
||||||
|
│ │ │ │ ├── services/ # AuthService, StudentService, EnrollmentService, NotificationService
|
||||||
|
│ │ │ │ ├── guards/ # auth.guard.ts (authGuard, adminGuard, guestGuard)
|
||||||
|
│ │ │ │ ├── interceptors/ # ErrorInterceptor, AuthInterceptor
|
||||||
|
│ │ │ │ ├── models/ # student.model.ts
|
||||||
|
│ │ │ │ └── graphql/
|
||||||
|
│ │ │ │ ├── queries/ # students.queries.ts
|
||||||
|
│ │ │ │ ├── mutations/ # students.mutations.ts, auth.mutations.ts
|
||||||
|
│ │ │ │ └── generated/ # types.ts (Apollo codegen)
|
||||||
|
│ │ │ ├── shared/ # Componentes reutilizables
|
||||||
|
│ │ │ │ ├── components/ui/ # ConfirmDialog, EmptyState, LoadingSpinner
|
||||||
|
│ │ │ │ └── pipes/ # credits.pipe, initials.pipe
|
||||||
|
│ │ │ └── features/ # Módulos por funcionalidad
|
||||||
|
│ │ │ ├── auth/ # Autenticación
|
||||||
|
│ │ │ │ └── pages/ # Login, Register, ResetPassword
|
||||||
|
│ │ │ ├── dashboard/ # Dashboard estudiante
|
||||||
|
│ │ │ │ └── pages/ # StudentDashboard
|
||||||
|
│ │ │ ├── students/
|
||||||
|
│ │ │ │ └── pages/ # StudentList, StudentForm
|
||||||
|
│ │ │ ├── enrollment/
|
||||||
|
│ │ │ │ └── pages/ # EnrollmentPage
|
||||||
|
│ │ │ └── classmates/
|
||||||
|
│ │ │ └── pages/ # ClassmatesPage
|
||||||
|
│ │ ├── environments/ # environment.ts, environment.prod.ts
|
||||||
|
│ │ └── main.ts
|
||||||
|
│ └── e2e/ # Playwright E2E tests
|
||||||
|
│ ├── mocks/ # graphql.mock.ts
|
||||||
|
│ ├── student-crud.spec.ts
|
||||||
|
│ ├── enrollment.spec.ts
|
||||||
|
│ └── classmates.spec.ts
|
||||||
|
│
|
||||||
|
├── tests/ # Backend tests
|
||||||
|
│ ├── Common/ # Builders compartidos
|
||||||
|
│ │ └── Builders/ # StudentBuilder, SubjectBuilder, etc.
|
||||||
|
│ ├── Domain.Tests/
|
||||||
|
│ │ ├── Entities/ # StudentTests
|
||||||
|
│ │ ├── ValueObjects/ # EmailTests
|
||||||
|
│ │ └── Services/ # EnrollmentDomainServiceTests
|
||||||
|
│ ├── Application.Tests/
|
||||||
|
│ │ ├── Students/ # StudentCommandsTests, StudentQueriesTests
|
||||||
|
│ │ ├── Enrollments/ # EnrollStudent, Unenroll, GetClassmates Tests
|
||||||
|
│ │ ├── Subjects/ # SubjectQueriesTests
|
||||||
|
│ │ ├── Professors/ # ProfessorQueriesTests
|
||||||
|
│ │ └── Validators/ # ValidatorTests
|
||||||
|
│ └── Integration.Tests/ # EnrollmentFlowTests
|
||||||
|
│
|
||||||
|
├── scripts/
|
||||||
|
│ └── dev-start.sh # Script desarrollo local (SQLite)
|
||||||
|
│
|
||||||
|
├── docs/
|
||||||
|
│ ├── architecture/
|
||||||
|
│ │ └── decisions/ # ADR-001 a ADR-004
|
||||||
|
│ ├── entregables/
|
||||||
|
│ │ ├── 01-analisis/ # Requisitos, reglas, historias, riesgos
|
||||||
|
│ │ ├── 02-diseno/ # Arquitectura, modelo dominio, DB, GraphQL, UI
|
||||||
|
│ │ └── 03-configuracion/ # Repositorio, .NET, Angular, DB, env, calidad
|
||||||
|
│ ├── qa/ # Reportes de QA
|
||||||
|
│ │ ├── QA-REPORT-*-MANUAL-TESTS.md
|
||||||
|
│ │ └── QA-REPORT-*-REGRESSION-TESTS.md
|
||||||
|
│ ├── CODE_REVIEW_CHECKLIST.md
|
||||||
|
│ ├── DEFECTOS_QA.md # Defectos encontrados
|
||||||
|
│ ├── DEPLOYMENT.md
|
||||||
|
│ ├── ENTREGABLES.md
|
||||||
|
│ ├── OWASP_CHECKLIST.md
|
||||||
|
│ ├── PLAN_ACTIVIDADES.md
|
||||||
|
│ ├── RECOMMENDATIONS.md # Recomendaciones de mejora
|
||||||
|
│ └── Prueba Técnica.md
|
||||||
|
│
|
||||||
|
├── database/
|
||||||
|
│ ├── stored-procedures/
|
||||||
|
│ └── views/
|
||||||
|
│
|
||||||
|
├── deploy/
|
||||||
|
│ ├── docker/
|
||||||
|
│ │ ├── Dockerfile.api # Multi-stage Alpine + .NET 10
|
||||||
|
│ │ ├── Dockerfile.frontend # Multi-stage Node + Nginx
|
||||||
|
│ │ ├── docker-compose.yml
|
||||||
|
│ │ ├── nginx.conf # Proxy + SPA routing
|
||||||
|
│ │ └── start.sh # Script de despliegue Docker
|
||||||
|
│ ├── k3s/ # Kubernetes manifests
|
||||||
|
│ │ ├── api.yaml # Deployment + Service API
|
||||||
|
│ │ ├── frontend.yaml # Deployment + Service Frontend
|
||||||
|
│ │ ├── sqlserver.yaml # StatefulSet SQL Server
|
||||||
|
│ │ ├── ingress.yaml # Traefik + HTTPS
|
||||||
|
│ │ ├── hpa.yaml # Horizontal Pod Autoscaler
|
||||||
|
│ │ ├── kustomization.yaml # Kustomize overlay
|
||||||
|
│ │ └── deploy.sh # Script orquestación K3s
|
||||||
|
│ └── scripts/
|
||||||
|
│ └── deploy-k3s.sh # Deploy remoto
|
||||||
|
│
|
||||||
|
└── .gitea/
|
||||||
|
└── workflows/
|
||||||
|
└── deploy.yaml # CI/CD Pipeline
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Comandos de Desarrollo
|
||||||
|
|
||||||
|
### Backend (.NET)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Restaurar dependencias
|
||||||
|
dotnet restore
|
||||||
|
|
||||||
|
# Build completo
|
||||||
|
dotnet build
|
||||||
|
|
||||||
|
# Ejecutar GraphQL API (puerto 5000/5001)
|
||||||
|
dotnet run --project src/backend/Host
|
||||||
|
|
||||||
|
# Tests
|
||||||
|
dotnet test # Todos
|
||||||
|
dotnet test --filter "Category=Unit" # Solo unit
|
||||||
|
dotnet test --filter "Category=Integration" # Solo integration
|
||||||
|
|
||||||
|
# Migraciones EF Core
|
||||||
|
dotnet ef migrations add InitialCreate \
|
||||||
|
-p src/backend/Adapters/Driven/Persistence \
|
||||||
|
-s src/backend/Host
|
||||||
|
dotnet ef database update \
|
||||||
|
-p src/backend/Adapters/Driven/Persistence \
|
||||||
|
-s src/backend/Host
|
||||||
|
|
||||||
|
# Watch mode (hot reload)
|
||||||
|
dotnet watch run --project src/backend/Host
|
||||||
|
|
||||||
|
# GraphQL Playground disponible en: https://localhost:5001/graphql
|
||||||
|
```
|
||||||
|
|
||||||
|
### Frontend (Angular)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd src/frontend
|
||||||
|
|
||||||
|
# Instalar dependencias
|
||||||
|
npm install
|
||||||
|
|
||||||
|
# Servidor desarrollo (puerto 4200)
|
||||||
|
ng serve
|
||||||
|
|
||||||
|
# Build producción
|
||||||
|
ng build --configuration production
|
||||||
|
|
||||||
|
# Tests unitarios
|
||||||
|
ng test
|
||||||
|
ng test --watch=false --code-coverage # Con coverage
|
||||||
|
|
||||||
|
# E2E tests
|
||||||
|
npx playwright test
|
||||||
|
|
||||||
|
# Lint
|
||||||
|
ng lint
|
||||||
|
|
||||||
|
# Generar componente
|
||||||
|
ng g c features/students/components/my-component --standalone
|
||||||
|
```
|
||||||
|
|
||||||
|
### Desarrollo Local (Sin Docker)
|
||||||
|
|
||||||
|
Script que levanta backend + frontend con **SQLite** (sin necesidad de SQL Server):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Iniciar todo
|
||||||
|
./scripts/dev-start.sh start
|
||||||
|
|
||||||
|
# Ver estado
|
||||||
|
./scripts/dev-start.sh status
|
||||||
|
|
||||||
|
# Detener
|
||||||
|
./scripts/dev-start.sh stop
|
||||||
|
|
||||||
|
# Reiniciar
|
||||||
|
./scripts/dev-start.sh restart
|
||||||
|
```
|
||||||
|
|
||||||
|
**Características:**
|
||||||
|
- Backend usa SQLite en `./data/dev.db`
|
||||||
|
- No requiere Docker ni SQL Server
|
||||||
|
- Frontend en puerto 4200
|
||||||
|
- Backend en puerto 5000
|
||||||
|
- Hot reload habilitado
|
||||||
|
- PIDs guardados para cleanup
|
||||||
|
|
||||||
|
### Docker
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Levantar todo el stack
|
||||||
|
docker-compose up -d
|
||||||
|
|
||||||
|
# Solo base de datos
|
||||||
|
docker-compose up -d sqlserver
|
||||||
|
|
||||||
|
# Rebuild
|
||||||
|
docker-compose up -d --build
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
docker-compose logs -f api
|
||||||
|
```
|
||||||
|
|
||||||
|
### Docker Compose Optimizado (Producción)
|
||||||
|
|
||||||
|
Ubicación: `deploy/docker/`
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd deploy/docker
|
||||||
|
./start.sh # Despliegue con un solo comando
|
||||||
|
```
|
||||||
|
|
||||||
|
**Recursos asignados (optimizado para 12 cores / 15GB RAM):**
|
||||||
|
|
||||||
|
| Servicio | CPU | RAM | Características |
|
||||||
|
|----------|-----|-----|-----------------|
|
||||||
|
| SQL Server | 2 cores | 2.5 GB | Volumen persistente |
|
||||||
|
| API .NET | 4 cores | 1.5 GB | Server GC, ReadyToRun, Health check |
|
||||||
|
| Frontend | 2 cores | 256 MB | Nginx + Brotli, cache agresivo |
|
||||||
|
|
||||||
|
**URLs después del despliegue:**
|
||||||
|
- Frontend: http://localhost
|
||||||
|
- API GraphQL: http://localhost:5000/graphql
|
||||||
|
- Health Check: http://localhost:5000/health
|
||||||
|
|
||||||
|
### Despliegue K3s (Producción)
|
||||||
|
|
||||||
|
**Dominio:** `https://academia.ingeniumcodex.com`
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd deploy/k3s
|
||||||
|
./deploy.sh all # Build + transfer + deploy completo
|
||||||
|
./deploy.sh status # Ver estado
|
||||||
|
./deploy.sh clean # Eliminar namespace
|
||||||
|
```
|
||||||
|
|
||||||
|
**URLs de producción:**
|
||||||
|
- Frontend: https://academia.ingeniumcodex.com
|
||||||
|
- API GraphQL: https://academia.ingeniumcodex.com/graphql
|
||||||
|
- Health Check: https://academia.ingeniumcodex.com/health
|
||||||
|
|
||||||
|
**Autenticación:**
|
||||||
|
- Login: https://academia.ingeniumcodex.com/login
|
||||||
|
- Registro estudiantes: https://academia.ingeniumcodex.com/register
|
||||||
|
- Reset password: https://academia.ingeniumcodex.com/reset-password
|
||||||
|
- Dashboard estudiante: https://academia.ingeniumcodex.com/dashboard
|
||||||
|
|
||||||
|
**Namespace:** `academia`
|
||||||
|
|
||||||
|
### CI/CD Pipeline (Gitea Actions)
|
||||||
|
|
||||||
|
**Ubicación:** `.gitea/workflows/deploy.yaml`
|
||||||
|
|
||||||
|
**Trigger:** Push a `main`
|
||||||
|
|
||||||
|
**Flujo automático:**
|
||||||
|
1. Checkout código
|
||||||
|
2. Sync a K3s master vía rsync
|
||||||
|
3. Build imágenes en paralelo (API + Frontend)
|
||||||
|
4. Import a K3s containerd
|
||||||
|
5. Apply Kustomize manifests
|
||||||
|
6. Rolling restart deployments
|
||||||
|
7. Health checks
|
||||||
|
8. Rollback automático si falla
|
||||||
|
|
||||||
|
**Secretos requeridos en Gitea:**
|
||||||
|
- `K3S_SSH_KEY`: Clave SSH privada
|
||||||
|
- `K3S_SUDO_PASS`: Password sudo en K3s
|
||||||
|
|
||||||
|
**Tiempo de despliegue:** ~3-5 minutos
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## GraphQL API
|
||||||
|
|
||||||
|
**Endpoint:** `https://localhost:5001/graphql`
|
||||||
|
**Playground:** `https://localhost:5001/graphql` (Banana Cake Pop)
|
||||||
|
|
||||||
|
### Queries
|
||||||
|
|
||||||
|
```graphql
|
||||||
|
# Obtener usuario autenticado (requiere JWT)
|
||||||
|
query Me {
|
||||||
|
me {
|
||||||
|
id
|
||||||
|
username
|
||||||
|
role
|
||||||
|
studentId
|
||||||
|
studentName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Obtener todos los estudiantes
|
||||||
|
query GetStudents {
|
||||||
|
students {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
email
|
||||||
|
totalCredits
|
||||||
|
enrollments {
|
||||||
|
subject {
|
||||||
|
name
|
||||||
|
credits
|
||||||
|
professor { name }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Obtener estudiante por ID
|
||||||
|
query GetStudentById($id: Int!) {
|
||||||
|
student(id: $id) {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
email
|
||||||
|
enrollments { subject { name } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Obtener todas las materias
|
||||||
|
query GetSubjects {
|
||||||
|
subjects {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
credits
|
||||||
|
professor { id name }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Obtener materias disponibles para un estudiante (filtra restricciones)
|
||||||
|
query GetAvailableSubjects($studentId: Int!) {
|
||||||
|
availableSubjects(studentId: $studentId) {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
credits
|
||||||
|
professor { name }
|
||||||
|
isAvailable # false si ya tiene materia del mismo profesor
|
||||||
|
unavailableReason # "Ya tienes una materia con este profesor"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Ver compañeros de clase por materia
|
||||||
|
query GetClassmates($studentId: Int!) {
|
||||||
|
classmates(studentId: $studentId) {
|
||||||
|
subjectName
|
||||||
|
students { name }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Obtener todos los profesores
|
||||||
|
query GetProfessors {
|
||||||
|
professors {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
subjects { id name }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Mutations
|
||||||
|
|
||||||
|
```graphql
|
||||||
|
# ========== AUTENTICACIÓN ==========
|
||||||
|
|
||||||
|
# Login
|
||||||
|
mutation Login($input: LoginRequestInput!) {
|
||||||
|
login(input: $input) {
|
||||||
|
success
|
||||||
|
token
|
||||||
|
error
|
||||||
|
user {
|
||||||
|
id
|
||||||
|
username
|
||||||
|
role
|
||||||
|
studentId
|
||||||
|
studentName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Registro (crea User + Student opcionalmente)
|
||||||
|
mutation Register($input: RegisterRequestInput!) {
|
||||||
|
register(input: $input) {
|
||||||
|
success
|
||||||
|
token
|
||||||
|
error
|
||||||
|
recoveryCode # Solo se muestra UNA vez
|
||||||
|
user { id username role studentId studentName }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Recuperar contraseña
|
||||||
|
mutation ResetPassword($input: ResetPasswordRequestInput!) {
|
||||||
|
resetPassword(input: $input) {
|
||||||
|
success
|
||||||
|
error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# ========== ESTUDIANTES ==========
|
||||||
|
|
||||||
|
# Crear estudiante
|
||||||
|
mutation CreateStudent($input: CreateStudentInput!) {
|
||||||
|
createStudent(input: $input) {
|
||||||
|
student { id name email }
|
||||||
|
errors
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Actualizar estudiante
|
||||||
|
mutation UpdateStudent($id: Int!, $input: UpdateStudentInput!) {
|
||||||
|
updateStudent(id: $id, input: $input) {
|
||||||
|
student { id name email }
|
||||||
|
errors
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Eliminar estudiante
|
||||||
|
mutation DeleteStudent($id: Int!) {
|
||||||
|
deleteStudent(id: $id) {
|
||||||
|
success
|
||||||
|
errors
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Inscribir estudiante en materia
|
||||||
|
mutation EnrollStudent($input: EnrollStudentInput!) {
|
||||||
|
enrollStudent(input: $input) {
|
||||||
|
enrollment {
|
||||||
|
id
|
||||||
|
student { name }
|
||||||
|
subject { name }
|
||||||
|
}
|
||||||
|
errors # ["Máximo 3 materias", "Ya tienes materia con este profesor"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Cancelar inscripción
|
||||||
|
mutation UnenrollStudent($enrollmentId: Int!) {
|
||||||
|
unenrollStudent(enrollmentId: $enrollmentId) {
|
||||||
|
success
|
||||||
|
errors
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Subscriptions (Opcional - Tiempo Real)
|
||||||
|
|
||||||
|
```graphql
|
||||||
|
# Notificación cuando un estudiante se inscribe
|
||||||
|
subscription OnStudentEnrolled {
|
||||||
|
studentEnrolled {
|
||||||
|
student { name }
|
||||||
|
subject { name }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Autenticación
|
||||||
|
|
||||||
|
### Sistema JWT
|
||||||
|
|
||||||
|
**Algoritmo:** HMAC-SHA256
|
||||||
|
**Expiración:** Configurable (default 60 min)
|
||||||
|
**Hashing passwords:** PBKDF2-SHA256 con 100,000 iteraciones
|
||||||
|
|
||||||
|
### Flujos de Autenticación
|
||||||
|
|
||||||
|
| Flujo | Endpoint | Descripción |
|
||||||
|
|-------|----------|-------------|
|
||||||
|
| **Login** | `mutation login` | Valida credenciales, retorna JWT + UserInfo |
|
||||||
|
| **Registro** | `mutation register` | Crea User + Student (opcional), genera Recovery Code |
|
||||||
|
| **Reset Password** | `mutation resetPassword` | Valida recovery code, actualiza password |
|
||||||
|
|
||||||
|
### Entidad User
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public class User {
|
||||||
|
public int Id;
|
||||||
|
public string Username; // Almacenado en minúsculas
|
||||||
|
public string PasswordHash; // PBKDF2-SHA256
|
||||||
|
public string RecoveryCodeHash; // Para reset password
|
||||||
|
public string Role; // "Admin" | "Student"
|
||||||
|
public int? StudentId; // FK a Student (nullable)
|
||||||
|
public DateTime CreatedAt;
|
||||||
|
public DateTime? LastLoginAt;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Roles y Guards
|
||||||
|
|
||||||
|
| Rol | Acceso |
|
||||||
|
|-----|--------|
|
||||||
|
| **Admin** | CRUD completo de estudiantes, ver todo |
|
||||||
|
| **Student** | Dashboard propio, inscripciones propias, ver compañeros |
|
||||||
|
|
||||||
|
**Guards Angular:**
|
||||||
|
- `authGuard`: Requiere autenticación → redirige a `/login`
|
||||||
|
- `adminGuard`: Requiere role Admin → redirige a `/dashboard`
|
||||||
|
- `guestGuard`: Solo no autenticados → redirige a `/dashboard`
|
||||||
|
|
||||||
|
### Variables de Entorno JWT
|
||||||
|
|
||||||
|
```bash
|
||||||
|
JWT_SECRET_KEY # Secret para firmar tokens (REQUERIDO, mín. 32 chars)
|
||||||
|
JWT_ISSUER # Default: "StudentEnrollmentApi"
|
||||||
|
JWT_AUDIENCE # Default: "StudentEnrollmentApp"
|
||||||
|
JWT_EXPIRATION_MINUTES # Default: 60
|
||||||
|
```
|
||||||
|
|
||||||
|
### Seguridad Implementada
|
||||||
|
|
||||||
|
- **PBKDF2-SHA256** con 100,000 iteraciones (salt 16 bytes)
|
||||||
|
- **Fixed-time comparison** para recovery codes (timing attacks)
|
||||||
|
- **Rate limiting**: 30 mutations/min, 100 queries/min
|
||||||
|
- **JWT validation**: Issuer, Audience, Lifetime, SigningKey
|
||||||
|
- **Recovery code**: 12 caracteres, mostrado solo una vez
|
||||||
|
- **Serilog**: Filtra tokens y passwords de logs
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Validaciones de Negocio
|
||||||
|
|
||||||
|
### Ubicación en el Código
|
||||||
|
|
||||||
|
| Validación | Capa | Ruta |
|
||||||
|
|------------|------|------|
|
||||||
|
| Email válido | Domain | `Domain/ValueObjects/Email.cs` |
|
||||||
|
| Max 3 materias | Domain | `Domain/Services/EnrollmentDomainService.cs` |
|
||||||
|
| No repetir profesor | Domain | `Domain/Services/EnrollmentDomainService.cs` |
|
||||||
|
| Datos request válidos | Application | `Application/{Feature}/Commands/*Validator.cs` |
|
||||||
|
| Estudiante existe | Application | `Application/{Feature}/Commands/*Handler.cs` |
|
||||||
|
| Repos implementados | Adapters | `Adapters/Driven/Persistence/Repositories/` |
|
||||||
|
|
||||||
|
### Reglas Críticas
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// Domain/Services/EnrollmentDomainService.cs
|
||||||
|
public void ValidateEnrollment(Student student, Subject newSubject)
|
||||||
|
{
|
||||||
|
// Regla 1: Max 3 materias
|
||||||
|
if (student.Enrollments.Count >= 3)
|
||||||
|
throw new MaxEnrollmentsExceededException(student.Id);
|
||||||
|
|
||||||
|
// Regla 2: No repetir profesor
|
||||||
|
var professorIds = student.Enrollments.Select(e => e.Subject.ProfessorId);
|
||||||
|
if (professorIds.Contains(newSubject.ProfessorId))
|
||||||
|
throw new SameProfessorConstraintException(student.Id, newSubject.ProfessorId);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Manejo de Errores
|
||||||
|
|
||||||
|
### Códigos de Error del Backend
|
||||||
|
|
||||||
|
| Código | Mensaje Usuario | Causa |
|
||||||
|
|--------|-----------------|-------|
|
||||||
|
| `MAX_ENROLLMENTS` | Has alcanzado el límite máximo de 3 materias | Estudiante intenta inscribir 4ta materia |
|
||||||
|
| `SAME_PROFESSOR` | Ya tienes una materia con este profesor | Violación restricción profesor único |
|
||||||
|
| `STUDENT_NOT_FOUND` | El estudiante no existe en el sistema | ID inválido o eliminado |
|
||||||
|
| `SUBJECT_NOT_FOUND` | La materia seleccionada no existe | ID materia inválido |
|
||||||
|
| `DUPLICATE_ENROLLMENT` | Ya estás inscrito en esta materia | Inscripción duplicada |
|
||||||
|
| `VALIDATION_ERROR` | Los datos ingresados no son válidos | FluentValidation falló |
|
||||||
|
| `NETWORK_ERROR` | No se pudo conectar con el servidor | API no disponible |
|
||||||
|
| `INVALID_CREDENTIALS` | Usuario o contraseña incorrectos | Login fallido |
|
||||||
|
| `USERNAME_EXISTS` | El nombre de usuario ya está en uso | Registro duplicado |
|
||||||
|
| `INVALID_RECOVERY_CODE` | Código de recuperación inválido | Reset password fallido |
|
||||||
|
| `UNAUTHORIZED` | No tienes permiso para esta acción | JWT inválido o expirado |
|
||||||
|
|
||||||
|
### Frontend: Servicio de Errores
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// core/services/error-handler.service.ts
|
||||||
|
// Uso en componentes:
|
||||||
|
this.errorHandler.handle(error, 'Contexto.accion');
|
||||||
|
|
||||||
|
// Características:
|
||||||
|
// - Mensajes amigables para usuarios
|
||||||
|
// - Logging detallado en consola (solo dev)
|
||||||
|
// - Traducción automática de códigos GraphQL
|
||||||
|
```
|
||||||
|
|
||||||
|
### Backend: Filtro de Errores
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// Adapters/Driving/Api/Middleware/GraphQLErrorFilter.cs
|
||||||
|
// Transforma excepciones de dominio a errores GraphQL
|
||||||
|
// con código y mensaje estructurado
|
||||||
|
```
|
||||||
|
|
||||||
|
### Monitoreo de Conectividad
|
||||||
|
|
||||||
|
El sistema verifica la conexión con el servidor cada **5 segundos**:
|
||||||
|
|
||||||
|
| Componente | Archivo | Función |
|
||||||
|
|------------|---------|---------|
|
||||||
|
| Backend | `Host/Program.cs` | Endpoint `/health` con estado DB |
|
||||||
|
| Servicio | `core/services/connectivity.service.ts` | Polling y estado reactivo |
|
||||||
|
| Overlay | `shared/components/ui/connectivity-overlay/` | UI bloqueante |
|
||||||
|
|
||||||
|
**Comportamiento:**
|
||||||
|
- Verifica `/health` cada 5 segundos
|
||||||
|
- Requiere 2 fallos consecutivos para mostrar overlay (evita falsos positivos)
|
||||||
|
- Overlay bloquea toda interacción hasta restaurar conexión
|
||||||
|
- Muestra instrucciones para solucionar el problema
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Convenciones
|
||||||
|
|
||||||
|
### .NET
|
||||||
|
- **Async/Await:** Todos los métodos I/O
|
||||||
|
- **Records:** Para DTOs inmutables
|
||||||
|
- **Nullable:** Habilitado globalmente
|
||||||
|
- **Naming:** PascalCase clases/métodos, camelCase variables
|
||||||
|
|
||||||
|
### Angular
|
||||||
|
- **Standalone:** Todos los componentes
|
||||||
|
- **Signals:** Estado reactivo
|
||||||
|
- **Lazy Loading:** Por feature
|
||||||
|
- **OnPush:** Change detection strategy
|
||||||
|
|
||||||
|
### Commits
|
||||||
|
```
|
||||||
|
feat(students): add create student form
|
||||||
|
fix(enrollment): validate professor constraint
|
||||||
|
refactor(api): extract validation middleware
|
||||||
|
test(domain): add enrollment policy tests
|
||||||
|
docs(readme): update setup instructions
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Restricciones Críticas
|
||||||
|
|
||||||
|
### NUNCA
|
||||||
|
- Modificar sin leer archivo completo primero
|
||||||
|
- Archivos > 100 líneas (refactorizar)
|
||||||
|
- Hardcodear connection strings
|
||||||
|
- Lógica de negocio en Controllers
|
||||||
|
- Exponer entidades de dominio en API
|
||||||
|
|
||||||
|
### SIEMPRE
|
||||||
|
- DTOs para entrada/salida
|
||||||
|
- Tests para reglas de negocio
|
||||||
|
- Validar antes de persistir
|
||||||
|
- Async en operaciones I/O
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Ciclo OODA
|
||||||
|
|
||||||
|
1. **OBSERVAR:** ¿Qué se solicita? ¿Qué existe?
|
||||||
|
2. **ORIENTAR:** ¿Riesgos? ¿Restricciones?
|
||||||
|
3. **DECIDIR:** Si hay duda, investigar primero
|
||||||
|
4. **ACTUAR:** Ejecutar con precisión, validar
|
||||||
|
|
||||||
|
> **Regla de oro:** "Mide dos veces, corta una"
|
||||||
Loading…
Reference in New Issue