docs(diagrams): update architecture diagrams for activation feature

Updated PlantUML diagrams and regenerated SVG/PNG exports:

01-use-cases: Add activation and admin use cases
02-domain-model: Add activation fields to Student entity
03-sequence-enrollment: Include activation check in flow
04-components: Add activation and admin components
05-entity-relationship: Add activation columns to Student table
06-state-enrollment: Add inactive/pending states
07-deployment: Update for current infrastructure
08-c4-context: Add admin actor and activation system

All diagrams validated and exported in both SVG and PNG formats.
This commit is contained in:
Andrés Eduardo García Márquez 2026-01-09 07:43:35 -05:00
parent 8e5a401601
commit cf5ba2010d
24 changed files with 372 additions and 95 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

View File

@ -9,43 +9,84 @@ skinparam actorBackgroundColor #007AFF
title Sistema de Registro de Estudiantes - Diagrama de Casos de Uso
actor "Estudiante" as student
actor "Administrador" as admin
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
rectangle "Sistema de Inscripción Académica" {
' Autenticación (ambos actores)
usecase "Iniciar sesión" as UC_LOGIN
usecase "Recuperar contraseña" as UC_RECOVER
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
' Solo estudiantes
usecase "Registrarse" as UC_REGISTER
usecase "Activar cuenta" as UC_ACTIVATE
usecase "Ver dashboard personal" as UC_DASHBOARD
usecase "Ver materias disponibles" as UC_SUBJECTS
usecase "Inscribirse en materia" as UC_ENROLL
usecase "Cancelar inscripción" as UC_UNENROLL
usecase "Ver mis inscripciones" as UC_MY_ENROLL
usecase "Ver compañeros de clase" as UC_CLASSMATES
' Solo administrador
usecase "Gestionar estudiantes\n(CRUD)" as UC_CRUD
usecase "Ver todos los estudiantes" as UC_LIST
usecase "Crear estudiante" as UC_CREATE
usecase "Editar estudiante" as UC_EDIT
usecase "Eliminar estudiante" as UC_DELETE
' Validaciones (includes)
usecase "Validar límite de créditos\n(máx 9 créditos)" as UC_VAL_CREDITS
usecase "Validar restricción de profesor\n(no repetir profesor)" as UC_VAL_PROF
}
student --> UC1
student --> UC2
student --> UC3
student --> UC4
student --> UC5
student --> UC6
student --> UC7
student --> UC8
' Relaciones Estudiante
student --> UC_REGISTER
student --> UC_LOGIN
student --> UC_ACTIVATE
student --> UC_RECOVER
student --> UC_DASHBOARD
student --> UC_SUBJECTS
student --> UC_ENROLL
student --> UC_UNENROLL
student --> UC_MY_ENROLL
student --> UC_CLASSMATES
UC4 ..> UC4a : <<include>>
UC4 ..> UC4b : <<include>>
' Relaciones Admin
admin --> UC_LOGIN
admin --> UC_RECOVER
admin --> UC_CRUD
admin --> UC_LIST
note right of UC4
' Extensiones CRUD
UC_CRUD ..> UC_CREATE : <<include>>
UC_CRUD ..> UC_EDIT : <<include>>
UC_CRUD ..> UC_DELETE : <<include>>
' Validaciones inscripción
UC_ENROLL ..> UC_VAL_CREDITS : <<include>>
UC_ENROLL ..> UC_VAL_PROF : <<include>>
note right of UC_ENROLL
Reglas de negocio:
- Máximo 3 materias (9 créditos)
- No puede tener 2 materias
del mismo profesor
- Requiere cuenta activada
end note
note right of UC7
note right of UC_CLASSMATES
Solo muestra nombres
de compañeros por materia
end note
note right of UC_ACTIVATE
El estudiante recibe
código de activación
al registrarse
end note
note bottom of admin
Acceso completo al
CRUD de estudiantes
end note
@enduml

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

View File

@ -9,16 +9,40 @@ title Sistema de Registro de Estudiantes - Modelo de Dominio
package "Domain" {
class User <<Entity>> {
- id: int
- username: string
- passwordHash: string
- recoveryCodeHash: string
- role: string
- studentId: int?
- createdAt: DateTime
- lastLoginAt: DateTime?
--
+ {static} Create(username, passwordHash, ...): User
+ UpdatePassword(newHash): void
+ UpdateLastLogin(): void
+ IsAdmin: bool
+ IsStudent: bool
}
class Student <<Entity>> {
- id: int
- name: string
- email: Email
- activationCodeHash: string?
- activationExpiresAt: DateTime?
- enrollments: List<Enrollment>
--
+ getTotalCredits(): int
+ canEnrollIn(subject: Subject): bool
+ enroll(subject: Subject): Enrollment
+ unenroll(enrollmentId: int): void
+ canEnroll(): bool
+ hasProfessor(professorId): bool
+ addEnrollment(enrollment): void
+ removeEnrollment(enrollment): void
+ setActivationCode(hash, expiresIn): void
+ clearActivationCode(): void
+ isActivated: bool
+ isActivationExpired(): bool
}
class Subject <<Entity>> {
@ -62,7 +86,14 @@ package "Domain" {
- checkProfessorConstraint(student: Student, subject: Subject): void
}
enum UserRoles <<Enumeration>> {
Admin
Student
}
' Relaciones
User "0..1" -- "0..1" Student : vinculado a
User --> UserRoles : tiene
Student "1" *-- "0..3" Enrollment : tiene
Subject "1" *-- "0..*" Enrollment : inscripciones
Professor "1" *-- "2" Subject : imparte
@ -72,11 +103,19 @@ package "Domain" {
EnrollmentDomainService ..> Subject : valida
}
note bottom of User
<b>Autenticación:</b>
- PBKDF2-SHA256 (100k iter)
- JWT para sesiones
- Recovery code para reset
end note
note bottom of Student
<b>Invariantes:</b>
- Máximo 3 inscripciones
- Email válido y único
- No repetir profesor
- Requiere activación
end note
note bottom of Subject

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

View File

@ -5,11 +5,12 @@ skinparam responseMessageBelowArrow true
skinparam sequenceParticipantBackgroundColor #F8F9FA
skinparam sequenceParticipantBorderColor #495057
title Secuencia: Inscripción de Estudiante en Materia
title Secuencia: Inscripción de Estudiante en Materia (con JWT)
actor "Estudiante" as user
participant "Frontend\n(Angular)" as frontend
participant "API GraphQL\n(HotChocolate)" as api
participant "JWT Middleware" as jwt
participant "EnrollStudentHandler" as handler
participant "EnrollmentDomainService" as domainService
participant "StudentRepository" as studentRepo
@ -17,14 +18,27 @@ participant "SubjectRepository" as subjectRepo
participant "EnrollmentRepository" as enrollRepo
database "SQL Server" as db
== Autenticación (previo) ==
note over user, frontend
El estudiante ya inició sesión
y tiene un JWT válido almacenado
end note
== Solicitud de Inscripción ==
user -> frontend : Selecciona materia\ny hace clic en "Inscribir"
activate frontend
frontend -> api : mutation enrollStudent(\n studentId, subjectId)
frontend -> api : mutation enrollStudent(\n studentId, subjectId)\n[Authorization: Bearer <JWT>]
activate api
api -> jwt : Validate JWT
activate jwt
jwt -> jwt : Verify signature\n& expiration
jwt --> api : ClaimsPrincipal
deactivate jwt
api -> handler : Handle(EnrollStudentCommand)
activate handler
@ -37,6 +51,12 @@ db --> studentRepo : Student data
studentRepo --> handler : Student
deactivate studentRepo
alt Cuenta no activada
handler --> api : Error: "Cuenta no activada"
api --> frontend : { errors: [...] }
frontend --> user : Muestra mensaje:\n"Activa tu cuenta primero"
end
handler -> subjectRepo : GetByIdAsync(subjectId)
activate subjectRepo
subjectRepo -> db : SELECT Subject

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

View File

@ -11,19 +11,43 @@ title Sistema de Registro de Estudiantes - Arquitectura de Componentes
package "Frontend (Angular 21)" as frontend {
[App Component] as app
package "Features" {
package "Auth" {
[Login Page] as loginPage
[Register Page] as registerPage
[Reset Password] as resetPage
[Activate Account] as activatePage
}
package "Dashboard" {
[Student Dashboard] as studentDash
[Admin Dashboard] as adminDash
}
package "Students" {
[Student List] as studentList
[Student Form] as studentForm
}
package "Enrollment" {
[Enrollment Page] as enrollPage
[Classmates Page] as classmates
}
}
package "Core" {
[Apollo Client] as apollo
[Auth Service] as authSvc
[Student Service] as studentSvc
[Enrollment Service] as enrollSvc
[Connectivity Service] as connSvc
[Error Handler] as errorHandler
}
package "Guards" {
[Auth Guard] as authGuard
[Admin Guard] as adminGuard
[Guest Guard] as guestGuard
}
package "Shared" {
[Connectivity Overlay] as overlay
[Loading Spinner] as spinner
@ -43,26 +67,42 @@ package "Backend (.NET 10)" as backend {
[GraphQL API\n(HotChocolate)] as graphql
[Query] as query
[Mutation] as mutation
[Types] as types
[Auth Types] as authTypes
[Student Types] as studentTypes
}
package "Driven (Secondary)" {
[Repositories] as repos
[DataLoaders] as loaders
[DbContext] as dbContext
[JWT Service] as jwtSvc
[Password Service] as passSvc
}
}
package "Application" as application {
[Commands] as commands
[Queries] as queries
[Handlers] as handlers
package "Auth" {
[Login Command] as loginCmd
[Register Command] as registerCmd
[Reset Password] as resetCmd
}
package "Students" {
[Student Commands] as studentCmds
[Student Queries] as studentQs
}
package "Enrollments" {
[Enroll Commands] as enrollCmds
[Classmates Query] as classmatesQ
}
[Validators] as validators
[DTOs] as dtos
}
package "Domain" as domain {
[Entities] as entities
[User Entity] as userEntity
[Student Entity] as studentEntity
[Subject Entity] as subjectEntity
[Enrollment Entity] as enrollEntity
[Value Objects] as valueObjects
[Domain Services] as domainSvc
[Ports (Interfaces)] as ports
@ -70,46 +110,67 @@ package "Backend (.NET 10)" as backend {
}
database "SQL Server 2022" as sqlserver {
[Students]
[Subjects]
[Professors]
[Enrollments]
[Users Table] as tblUsers
[Students Table] as tblStudents
[Subjects Table] as tblSubjects
[Professors Table] as tblProf
[Enrollments Table] as tblEnroll
}
cloud "Browser" as browser
' Conexiones Frontend
browser --> app
app --> loginPage
app --> registerPage
app --> studentDash
app --> adminDash
app --> studentList
app --> studentForm
app --> enrollPage
app --> classmates
app --> overlay
loginPage --> authSvc
registerPage --> authSvc
resetPage --> authSvc
activatePage --> authSvc
studentDash --> studentSvc
adminDash --> studentSvc
studentList --> studentSvc
studentForm --> studentSvc
enrollPage --> enrollSvc
classmates --> enrollSvc
overlay --> connSvc
authSvc --> apollo
studentSvc --> apollo
enrollSvc --> apollo
connSvc ..> errorHandler
' Guards
authGuard ..> authSvc
adminGuard ..> authSvc
guestGuard ..> authSvc
' Conexiones Backend
apollo --> graphql : HTTP/GraphQL
apollo --> graphql : HTTP/GraphQL\n+ JWT Header
graphql --> query
graphql --> mutation
query --> handlers
mutation --> handlers
handlers --> validators
handlers --> commands
handlers --> queries
graphql --> authTypes
commands --> domainSvc
queries --> repos
domainSvc --> entities
mutation --> loginCmd
mutation --> registerCmd
mutation --> resetCmd
mutation --> studentCmds
mutation --> enrollCmds
query --> studentQs
query --> classmatesQ
loginCmd --> jwtSvc
loginCmd --> passSvc
registerCmd --> passSvc
studentCmds --> domainSvc
enrollCmds --> domainSvc
domainSvc --> studentEntity
domainSvc --> valueObjects
repos --> dbContext
@ -118,6 +179,7 @@ dbContext --> sqlserver
' Implementación de puertos
repos ..|> ports : implements
jwtSvc ..|> ports : implements
note right of domain
<b>Regla de Dependencia:</b>
@ -129,6 +191,12 @@ note bottom of graphql
Endpoints:
- /graphql
- /health
Auth: JWT Bearer
end note
note right of jwtSvc
HMAC-SHA256
Configurable expiration
end note
@enduml

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

View File

@ -6,11 +6,25 @@ skinparam classBorderColor #495057
title Sistema de Registro de Estudiantes - Diagrama Entidad-Relación
entity "Users" as users {
* **Id** : int <<PK>>
--
* Username : nvarchar(50) <<unique>>
* PasswordHash : nvarchar(255)
* RecoveryCodeHash : nvarchar(255)
* Role : nvarchar(20)
StudentId : int <<FK, nullable>>
* CreatedAt : datetime2
LastLoginAt : datetime2
}
entity "Students" as students {
* **Id** : int <<PK>>
--
* Name : nvarchar(100)
* Email : nvarchar(255) <<unique>>
ActivationCodeHash : nvarchar(255)
ActivationExpiresAt : datetime2
* CreatedAt : datetime2
UpdatedAt : datetime2
}
@ -40,14 +54,23 @@ entity "Enrollments" as enrollments {
}
' Relaciones
users ||--o| students : "vinculado a"
students ||--o{ enrollments : "tiene"
subjects ||--o{ enrollments : "inscripciones"
professors ||--|| subjects : "imparte 2"
note right of users
<b>Autenticación:</b>
- Password: PBKDF2-SHA256
- Roles: Admin, Student
- Recovery code para reset
end note
note right of students
<b>Restricciones:</b>
- Email único
- Máximo 3 enrollments
- Activación requerida
end note
note right of subjects

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

View File

@ -3,10 +3,19 @@
skinparam stateBackgroundColor #F8F9FA
skinparam stateBorderColor #495057
title Estado de Inscripción del Estudiante
title Estados del Estudiante y sus Inscripciones
[*] --> SinMaterias : Registro inicial
[*] --> Registrado : Registro inicial
state "Cuenta" as cuenta {
state "Registrado\n(Pendiente Activación)" as Registrado
state "Activo" as Activo
Registrado --> Activo : activar(código)
Registrado --> Registrado : código expirado\n[regenerar código]
}
state "Inscripciones" as inscripciones {
state "Sin Materias" as SinMaterias {
state "0 créditos" as cred0
}
@ -25,6 +34,23 @@ 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]
}
Activo --> SinMaterias : cuenta activa
state validacion <<choice>>
SinMaterias --> validacion : intenta inscribir
Parcial --> validacion : intenta inscribir
validacion --> Parcial : [válido y cuenta activa]
validacion --> [*] : [inválido]\nmuestra error
note right of Registrado
El estudiante recibe
código de activación
por email (24h validez)
end note
note right of Completa
No puede inscribir
@ -33,17 +59,16 @@ end note
note left of Parcial
<b>Validaciones en cada inscripción:</b>
- Cuenta activa
- 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
note bottom of validacion
Si la cuenta no está
activa, redirige a
página de activación
end note
@enduml

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

View File

@ -12,35 +12,58 @@ node "Cliente" as client {
}
}
node "Docker Host" as docker {
node "K3s Cluster (Namespace: academia)" as k3s {
node "student-frontend" as frontendContainer <<container>> {
node "frontend-deployment" as frontendPod <<Pod>> {
component "Nginx" as nginx {
[Static Files]
[Reverse Proxy]
}
}
node "student-api" as apiContainer <<container>> {
node "api-deployment" as apiPod <<Pod>> {
component "ASP.NET Core" as aspnet {
[Kestrel Server]
[GraphQL Endpoint]
[JWT Auth]
[Health Check]
}
}
node "student-db" as dbContainer <<container>> {
node "sqlserver-statefulset" as dbPod <<StatefulSet>> {
database "SQL Server 2022" as sqlserver {
[StudentEnrollment DB]
}
}
node "Traefik Ingress" as ingress <<Ingress>> {
[TLS Termination]
[Routing Rules]
}
component "NetworkPolicy" as netpol <<Security>> {
[default-deny-ingress]
[allow-frontend-ingress]
[allow-api-ingress]
[allow-sqlserver-from-api]
}
}
cloud "Internet" as internet
' Conexiones
browser --> nginx : HTTP :80
browser --> internet : HTTPS
internet --> ingress : HTTPS :443
ingress --> nginx : HTTP :80
nginx --> aspnet : HTTP :5000\n/graphql
aspnet --> sqlserver : TCP :1433
note right of ingress
<b>Dominio:</b>
academia.ingeniumcodex.com
<b>TLS:</b> Let's Encrypt
end note
note right of nginx
<b>Nginx Config:</b>
- Gzip/Brotli compression
@ -55,6 +78,7 @@ note right of aspnet
- ReadyToRun
- Connection pooling
- Rate limiting
- JWT validation
end note
note right of sqlserver
@ -64,4 +88,16 @@ note right of sqlserver
- Persistent volume
end note
note bottom of k3s
<b>CI/CD:</b> Gitea Actions
<b>Namespace:</b> academia
<b>Seguridad:</b> NetworkPolicy
end note
note right of netpol
<b>Flujo permitido:</b>
Ingress → Frontend → API → SQL
(Todo otro tráfico bloqueado)
end note
@enduml

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

View File

@ -10,28 +10,44 @@ skinparam rectangle {
}
actor "Estudiante" as student <<Persona>>
actor "Administrador" as admin <<Persona>>
rectangle "Sistema de Registro\nde Estudiantes" as system <<Software System>> #lightblue {
rectangle "Sistema de Inscripción\nAcadémica" 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
rectangle "Servidor SMTP\n(Email)" as smtp <<External System>> #lightgray {
}
student --> system : Se registra, activa cuenta,\nse inscribe en materias,\nve compañeros de clase
admin --> system : Gestiona estudiantes\n(CRUD completo)
system --> database : Lee y escribe\ndatos de usuarios,\nestudiantes e inscripciones
system --> smtp : Envía códigos\nde activación
note right of student
<b>Estudiante</b>
Usuario del sistema que:
- Se registra en el sistema
- Se registra y activa cuenta
- Se inscribe en materias (máx 3)
- Ve sus compañeros de clase
- Consulta inscripciones
- Consulta sus inscripciones
- Accede a su dashboard personal
end note
note left of admin
<b>Administrador</b>
Usuario privilegiado que:
- Gestiona todos los estudiantes
- Crea, edita, elimina registros
- Visualiza todo el sistema
end note
note right of system
<b>Sistema de Registro</b>
<b>Sistema de Inscripción Académica</b>
Aplicación web que permite:
- Autenticación (JWT + PBKDF2)
- CRUD de estudiantes
- Inscripción en materias
- Validación de reglas de negocio
@ -40,15 +56,24 @@ note right of system
<b>Stack:</b>
Frontend: Angular 21
Backend: .NET 10 + GraphQL
Auth: JWT + Roles (Admin/Student)
end note
note right of database
<b>SQL Server 2022</b>
Almacena:
- Usuarios (auth)
- Estudiantes
- Profesores
- Materias
- Inscripciones
end note
note right of smtp
<b>Servicio de Email</b>
Para:
- Códigos de activación
- Notificaciones
end note
@enduml

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 17 KiB