diff --git a/docs/architecture/diagrams/01-use-cases.png b/docs/architecture/diagrams/01-use-cases.png new file mode 100644 index 0000000..be2ab55 Binary files /dev/null and b/docs/architecture/diagrams/01-use-cases.png differ diff --git a/docs/architecture/diagrams/01-use-cases.puml b/docs/architecture/diagrams/01-use-cases.puml index 0988fad..d0673f5 100644 --- a/docs/architecture/diagrams/01-use-cases.puml +++ b/docs/architecture/diagrams/01-use-cases.puml @@ -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 : <> -UC4 ..> UC4b : <> +' Relaciones Admin +admin --> UC_LOGIN +admin --> UC_RECOVER +admin --> UC_CRUD +admin --> UC_LIST -note right of UC4 +' Extensiones CRUD +UC_CRUD ..> UC_CREATE : <> +UC_CRUD ..> UC_EDIT : <> +UC_CRUD ..> UC_DELETE : <> + +' Validaciones inscripción +UC_ENROLL ..> UC_VAL_CREDITS : <> +UC_ENROLL ..> UC_VAL_PROF : <> + +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 diff --git a/docs/architecture/diagrams/01-use-cases.svg b/docs/architecture/diagrams/01-use-cases.svg index 423765e..4f737a2 100644 --- a/docs/architecture/diagrams/01-use-cases.svg +++ b/docs/architecture/diagrams/01-use-cases.svg @@ -1 +1 @@ -Sistema de Registro de Estudiantes - Diagrama de Casos de UsoSistema de Registro de Estudiantes - Diagrama de Casos de UsoSistema de InscripciónRegistrarse en el sistemaIniciar sesiónVer materias disponiblesInscribirse en materiaCancelar inscripciónVer mis inscripcionesVer compañeros de claseActualizar perfilValidar límite de créditos(máx 9 créditos)Validar restricción de profesor(no repetir profesor)EstudianteReglas de negocio:- Máximo 3 materias (9 créditos)- No puede tener 2 materiasdel mismo profesorSolo muestra nombresde compañeros por materia«include»«include» \ No newline at end of file +Sistema de Registro de Estudiantes - Diagrama de Casos de UsoSistema de Registro de Estudiantes - Diagrama de Casos de UsoSistema de Inscripción AcadémicaIniciar sesiónRecuperar contraseñaRegistrarseActivar cuentaVer dashboard personalVer materias disponiblesInscribirse en materiaCancelar inscripciónVer mis inscripcionesVer compañeros de claseGestionar estudiantes(CRUD)Ver todos los estudiantesCrear estudianteEditar estudianteEliminar estudianteValidar límite de créditos(máx 9 créditos)Validar restricción de profesor(no repetir profesor)EstudianteAdministradorReglas de negocio:- Máximo 3 materias (9 créditos)- No puede tener 2 materiasdel mismo profesor- Requiere cuenta activadaSolo muestra nombresde compañeros por materiaEl estudiante recibecódigo de activaciónal registrarseAcceso completo alCRUD de estudiantes«include»«include»«include»«include»«include» \ No newline at end of file diff --git a/docs/architecture/diagrams/02-domain-model.png b/docs/architecture/diagrams/02-domain-model.png new file mode 100644 index 0000000..57f05af Binary files /dev/null and b/docs/architecture/diagrams/02-domain-model.png differ diff --git a/docs/architecture/diagrams/02-domain-model.puml b/docs/architecture/diagrams/02-domain-model.puml index 02b3505..c23687b 100644 --- a/docs/architecture/diagrams/02-domain-model.puml +++ b/docs/architecture/diagrams/02-domain-model.puml @@ -9,16 +9,40 @@ title Sistema de Registro de Estudiantes - Modelo de Dominio package "Domain" { + class User <> { + - 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 <> { - id: int - name: string - email: Email + - activationCodeHash: string? + - activationExpiresAt: DateTime? - enrollments: List -- + 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 <> { @@ -62,7 +86,14 @@ package "Domain" { - checkProfessorConstraint(student: Student, subject: Subject): void } + enum UserRoles <> { + 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 + Autenticación: + - PBKDF2-SHA256 (100k iter) + - JWT para sesiones + - Recovery code para reset +end note + note bottom of Student Invariantes: - Máximo 3 inscripciones - Email válido y único - No repetir profesor + - Requiere activación end note note bottom of Subject diff --git a/docs/architecture/diagrams/02-domain-model.svg b/docs/architecture/diagrams/02-domain-model.svg index 4302393..ceb4dc5 100644 --- a/docs/architecture/diagrams/02-domain-model.svg +++ b/docs/architecture/diagrams/02-domain-model.svg @@ -1 +1 @@ -Sistema de Registro de Estudiantes - Modelo de DominioSistema de Registro de Estudiantes - Modelo de DominioDomain«Entity»Student-id: int-name: string-email: Email-enrollments: List<Enrollment>+getTotalCredits(): int+canEnrollIn(subject: Subject): bool+enroll(subject: Subject): Enrollment+unenroll(enrollmentId: int): void«Entity»Subject-id: int-name: string-credits: int {= 3}-professorId: int+getProfessor(): Professor«Entity»Professor-id: int-name: string-subjects: List<Subject>+getSubjects(): List<Subject>«Entity»Enrollment-id: int-studentId: int-subjectId: int-enrolledAt: DateTime+getStudent(): Student+getSubject(): Subject«Value Object»Email-value: string+create(value: string): Email-validate(value: string): void«Domain Service»EnrollmentDomainService+validateEnrollment(student: Student, subject: Subject): void-checkMaxEnrollments(student: Student): void-checkProfessorConstraint(student: Student, subject: Subject): voidInvariantes:- Máximo 3 inscripciones- Email válido y único- No repetir profesorInvariantes:- Créditos = 3 (fijo)- Pertenece a un profesorCada profesorimparte exactamente2 materiastiene10..3inscripciones10..*imparte12emailvalidavalida \ No newline at end of file +Sistema de Registro de Estudiantes - Modelo de DominioSistema de Registro de Estudiantes - Modelo de DominioDomain«Entity»User-id: int-username: string-passwordHash: string-recoveryCodeHash: string-role: string-studentId: int?-createdAt: DateTime-lastLoginAt: DateTime?+Create(username, passwordHash, ...): User+UpdatePassword(newHash): void+UpdateLastLogin(): void+IsAdmin: bool+IsStudent: bool«Entity»Student-id: int-name: string-email: Email-activationCodeHash: string?-activationExpiresAt: DateTime?-enrollments: List<Enrollment>+getTotalCredits(): int+canEnroll(): bool+hasProfessor(professorId): bool+addEnrollment(enrollment): void+removeEnrollment(enrollment): void+setActivationCode(hash, expiresIn): void+clearActivationCode(): void+isActivated: bool+isActivationExpired(): bool«Entity»Subject-id: int-name: string-credits: int {= 3}-professorId: int+getProfessor(): Professor«Entity»Professor-id: int-name: string-subjects: List<Subject>+getSubjects(): List<Subject>«Entity»Enrollment-id: int-studentId: int-subjectId: int-enrolledAt: DateTime+getStudent(): Student+getSubject(): Subject«Value Object»Email-value: string+create(value: string): Email-validate(value: string): void«Domain Service»EnrollmentDomainService+validateEnrollment(student: Student, subject: Subject): void-checkMaxEnrollments(student: Student): void-checkProfessorConstraint(student: Student, subject: Subject): void«Enumeration»UserRolesAdminStudentAutenticación:- PBKDF2-SHA256 (100k iter)- JWT para sesiones- Recovery code para resetInvariantes:- Máximo 3 inscripciones- Email válido y único- No repetir profesor- Requiere activaciónInvariantes:- Créditos = 3 (fijo)- Pertenece a un profesorCada profesorimparte exactamente2 materiasvinculado a0..10..1tienetiene10..3inscripciones10..*imparte12emailvalidavalida \ No newline at end of file diff --git a/docs/architecture/diagrams/03-sequence-enrollment.png b/docs/architecture/diagrams/03-sequence-enrollment.png new file mode 100644 index 0000000..f238827 Binary files /dev/null and b/docs/architecture/diagrams/03-sequence-enrollment.png differ diff --git a/docs/architecture/diagrams/03-sequence-enrollment.puml b/docs/architecture/diagrams/03-sequence-enrollment.puml index 91aeb9e..942e7c2 100644 --- a/docs/architecture/diagrams/03-sequence-enrollment.puml +++ b/docs/architecture/diagrams/03-sequence-enrollment.puml @@ -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 ] 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 diff --git a/docs/architecture/diagrams/03-sequence-enrollment.svg b/docs/architecture/diagrams/03-sequence-enrollment.svg index 067f282..7526ba8 100644 --- a/docs/architecture/diagrams/03-sequence-enrollment.svg +++ b/docs/architecture/diagrams/03-sequence-enrollment.svg @@ -1 +1 @@ -Secuencia: Inscripción de Estudiante en MateriaSecuencia: Inscripción de Estudiante en MateriaFrontendAPI GraphQLEnrollStudentHandlerEnrollmentDomainServiceStudentRepositorySubjectRepositoryEnrollmentRepositoryEstudianteFrontendAPI GraphQLEnrollStudentHandlerEnrollmentDomainServiceStudentRepositorySubjectRepositoryEnrollmentRepositorySQL ServerEstudianteEstudianteFrontend(Angular)Frontend(Angular)API GraphQL(HotChocolate)API GraphQL(HotChocolate)EnrollStudentHandlerEnrollStudentHandlerEnrollmentDomainServiceEnrollmentDomainServiceStudentRepositoryStudentRepositorySubjectRepositorySubjectRepositoryEnrollmentRepositoryEnrollmentRepositorySQL ServerSQL ServerFrontendAPI GraphQLEnrollStudentHandlerEnrollmentDomainServiceStudentRepositorySubjectRepositoryEnrollmentRepositorySolicitud de InscripciónSelecciona materiay hace clic en "Inscribir"mutation enrollStudent(studentId, subjectId)Handle(EnrollStudentCommand)Obtención de DatosGetByIdWithEnrollmentsAsync(studentId)SELECT Student + EnrollmentsStudent dataStudentGetByIdAsync(subjectId)SELECT SubjectSubject dataSubjectValidación de Reglas de NegocioValidateEnrollment(student, subject)CheckMaxEnrollments()[máx 3 materias]alt[Estudiante tiene 3 materias]throw MaxEnrollmentsExceededExceptionError: "Límite de materias alcanzado"{ errors: [...] }Muestra mensaje de errorCheckProfessorConstraint()[no repetir profesor]alt[Ya tiene materia con el profesor]throw SameProfessorConstraintExceptionError: "Ya tienes materia con este profesor"{ errors: [...] }Muestra mensaje de errorValidación OKPersistenciaAddAsync(enrollment)INSERT EnrollmentOKEnrollmentEnrollmentPayload{ enrollment: {...} }Muestra confirmación:"Inscrito en [materia]" \ No newline at end of file +Secuencia: Inscripción de Estudiante en Materia (con JWT)Secuencia: Inscripción de Estudiante en Materia (con JWT)FrontendAPI GraphQLJWT MiddlewareEnrollStudentHandlerEnrollmentDomainServiceStudentRepositorySubjectRepositoryEnrollmentRepositoryEstudianteFrontendAPI GraphQLJWT MiddlewareEnrollStudentHandlerEnrollmentDomainServiceStudentRepositorySubjectRepositoryEnrollmentRepositorySQL ServerEstudianteEstudianteFrontend(Angular)Frontend(Angular)API GraphQL(HotChocolate)API GraphQL(HotChocolate)JWT MiddlewareJWT MiddlewareEnrollStudentHandlerEnrollStudentHandlerEnrollmentDomainServiceEnrollmentDomainServiceStudentRepositoryStudentRepositorySubjectRepositorySubjectRepositoryEnrollmentRepositoryEnrollmentRepositorySQL ServerSQL ServerFrontendAPI GraphQLJWT MiddlewareEnrollStudentHandlerEnrollmentDomainServiceStudentRepositorySubjectRepositoryEnrollmentRepositoryAutenticación (previo)El estudiante ya inició sesióny tiene un JWT válido almacenadoSolicitud de InscripciónSelecciona materiay hace clic en "Inscribir"mutation enrollStudent(studentId, subjectId)[Authorization: Bearer <JWT>]Validate JWTVerify signature& expirationClaimsPrincipalHandle(EnrollStudentCommand)Obtención de DatosGetByIdWithEnrollmentsAsync(studentId)SELECT Student + EnrollmentsStudent dataStudentalt[Cuenta no activada]Error: "Cuenta no activada"{ errors: [...] }Muestra mensaje:"Activa tu cuenta primero"GetByIdAsync(subjectId)SELECT SubjectSubject dataSubjectValidación de Reglas de NegocioValidateEnrollment(student, subject)CheckMaxEnrollments()[máx 3 materias]alt[Estudiante tiene 3 materias]throw MaxEnrollmentsExceededExceptionError: "Límite de materias alcanzado"{ errors: [...] }Muestra mensaje de errorCheckProfessorConstraint()[no repetir profesor]alt[Ya tiene materia con el profesor]throw SameProfessorConstraintExceptionError: "Ya tienes materia con este profesor"{ errors: [...] }Muestra mensaje de errorValidación OKPersistenciaAddAsync(enrollment)INSERT EnrollmentOKEnrollmentEnrollmentPayload{ enrollment: {...} }Muestra confirmación:"Inscrito en [materia]" \ No newline at end of file diff --git a/docs/architecture/diagrams/04-components.png b/docs/architecture/diagrams/04-components.png new file mode 100644 index 0000000..edba79f Binary files /dev/null and b/docs/architecture/diagrams/04-components.png differ diff --git a/docs/architecture/diagrams/04-components.puml b/docs/architecture/diagrams/04-components.puml index 5386c33..893751a 100644 --- a/docs/architecture/diagrams/04-components.puml +++ b/docs/architecture/diagrams/04-components.puml @@ -11,19 +11,43 @@ 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 "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 Regla de Dependencia: @@ -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 diff --git a/docs/architecture/diagrams/04-components.svg b/docs/architecture/diagrams/04-components.svg index bd13627..9bc4cd8 100644 --- a/docs/architecture/diagrams/04-components.svg +++ b/docs/architecture/diagrams/04-components.svg @@ -1 +1 @@ -Sistema de Registro de Estudiantes - Arquitectura de ComponentesSistema de Registro de Estudiantes - Arquitectura de ComponentesFrontend (Angular 21)CoreSharedBackend (.NET 10)HostAdaptersDriving (Primary)Driven (Secondary)ApplicationDomainSQL Server 2022App ComponentStudent ListStudent FormEnrollment PageClassmates PageApollo ClientStudent ServiceEnrollment ServiceConnectivity ServiceError HandlerConnectivity OverlayLoading SpinnerConfirm DialogProgram.csDI ContainerGraphQL API(HotChocolate)QueryMutationTypesRepositoriesDataLoadersDbContextCommandsQueriesHandlersValidatorsDTOsEntitiesValue ObjectsDomain ServicesPorts (Interfaces)StudentsSubjectsProfessorsEnrollmentsBrowserRegla de Dependencia:Domain no dependede capas externasEndpoints:- /graphql- /healthHTTP/GraphQLimplements \ No newline at end of file +Sistema de Registro de Estudiantes - Arquitectura de ComponentesSistema de Registro de Estudiantes - Arquitectura de ComponentesFrontend (Angular 21)FeaturesAuthDashboardStudentsEnrollmentCoreGuardsSharedBackend (.NET 10)HostAdaptersDriving (Primary)Driven (Secondary)ApplicationEnrollmentsDomainSQL Server 2022App ComponentLogin PageRegister PageReset PasswordActivate AccountLogin CommandRegister CommandReset PasswordStudent DashboardAdmin DashboardStudent ListStudent FormStudent CommandsStudent QueriesEnrollment PageClassmates PageApollo ClientAuth ServiceStudent ServiceEnrollment ServiceConnectivity ServiceError HandlerAuth GuardAdmin GuardGuest GuardConnectivity OverlayLoading SpinnerConfirm DialogProgram.csDI ContainerGraphQL API(HotChocolate)QueryMutationAuth TypesStudent TypesRepositoriesDataLoadersDbContextJWT ServicePassword ServiceValidatorsDTOsEnroll CommandsClassmates QueryUser EntityStudent EntitySubject EntityEnrollment EntityValue ObjectsDomain ServicesPorts (Interfaces)Users TableStudents TableSubjects TableProfessors TableEnrollments TableBrowserRegla de Dependencia:Domain no dependede capas externasEndpoints:- /graphql- /healthAuth: JWT BearerHMAC-SHA256Configurable expirationHTTP/GraphQL+ JWT Headerimplementsimplements \ No newline at end of file diff --git a/docs/architecture/diagrams/05-entity-relationship.png b/docs/architecture/diagrams/05-entity-relationship.png new file mode 100644 index 0000000..fc0d1ea Binary files /dev/null and b/docs/architecture/diagrams/05-entity-relationship.png differ diff --git a/docs/architecture/diagrams/05-entity-relationship.puml b/docs/architecture/diagrams/05-entity-relationship.puml index 6b1407c..48caa0b 100644 --- a/docs/architecture/diagrams/05-entity-relationship.puml +++ b/docs/architecture/diagrams/05-entity-relationship.puml @@ -6,11 +6,25 @@ skinparam classBorderColor #495057 title Sistema de Registro de Estudiantes - Diagrama Entidad-Relación +entity "Users" as users { + * **Id** : int <> + -- + * Username : nvarchar(50) <> + * PasswordHash : nvarchar(255) + * RecoveryCodeHash : nvarchar(255) + * Role : nvarchar(20) + StudentId : int <> + * CreatedAt : datetime2 + LastLoginAt : datetime2 +} + entity "Students" as students { * **Id** : int <> -- * Name : nvarchar(100) * Email : nvarchar(255) <> + 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 + Autenticación: + - Password: PBKDF2-SHA256 + - Roles: Admin, Student + - Recovery code para reset +end note + note right of students Restricciones: - Email único - Máximo 3 enrollments + - Activación requerida end note note right of subjects diff --git a/docs/architecture/diagrams/05-entity-relationship.svg b/docs/architecture/diagrams/05-entity-relationship.svg index 0817a59..bb66fa3 100644 --- a/docs/architecture/diagrams/05-entity-relationship.svg +++ b/docs/architecture/diagrams/05-entity-relationship.svg @@ -1 +1 @@ -Sistema de Registro de Estudiantes - Diagrama Entidad-RelaciónSistema de Registro de Estudiantes - Diagrama Entidad-RelaciónStudentsId: int «PK»Name : nvarchar(100)Email : nvarchar(255) «unique»CreatedAt : datetime2UpdatedAt : datetime2ProfessorsId: int «PK»Name : nvarchar(100)SubjectsId: int «PK»Name : nvarchar(100)Credits : int {= 3}ProfessorId: int «FK»EnrollmentsId: int «PK»StudentId: int «FK»SubjectId: int «FK»EnrolledAt : datetime2«unique» (StudentId, SubjectId)Restricciones:- Email único- Máximo 3 enrollmentsDatos iniciales:10 materias3 créditos cada unaDatos iniciales:5 profesores2 materias cada unoReglas de negocio:- (StudentId, SubjectId) único- Estudiante no puede tener2 materias del mismo profesortieneinscripcionesimparte 2 \ No newline at end of file +Sistema de Registro de Estudiantes - Diagrama Entidad-RelaciónSistema de Registro de Estudiantes - Diagrama Entidad-RelaciónUsersId: int «PK»Username : nvarchar(50) «unique»PasswordHash : nvarchar(255)RecoveryCodeHash : nvarchar(255)Role : nvarchar(20)StudentId : int «FK, nullable»CreatedAt : datetime2LastLoginAt : datetime2StudentsId: int «PK»Name : nvarchar(100)Email : nvarchar(255) «unique»ActivationCodeHash : nvarchar(255)ActivationExpiresAt : datetime2CreatedAt : datetime2UpdatedAt : datetime2ProfessorsId: int «PK»Name : nvarchar(100)SubjectsId: int «PK»Name : nvarchar(100)Credits : int {= 3}ProfessorId: int «FK»EnrollmentsId: int «PK»StudentId: int «FK»SubjectId: int «FK»EnrolledAt : datetime2«unique» (StudentId, SubjectId)Autenticación:- Password: PBKDF2-SHA256- Roles: Admin, Student- Recovery code para resetRestricciones:- Email único- Máximo 3 enrollments- Activación requeridaDatos iniciales:10 materias3 créditos cada unaDatos iniciales:5 profesores2 materias cada unoReglas de negocio:- (StudentId, SubjectId) único- Estudiante no puede tener2 materias del mismo profesorvinculado atieneinscripcionesimparte 2 \ No newline at end of file diff --git a/docs/architecture/diagrams/06-state-enrollment.png b/docs/architecture/diagrams/06-state-enrollment.png new file mode 100644 index 0000000..59a6ea4 Binary files /dev/null and b/docs/architecture/diagrams/06-state-enrollment.png differ diff --git a/docs/architecture/diagrams/06-state-enrollment.puml b/docs/architecture/diagrams/06-state-enrollment.puml index fa6585c..0e098b7 100644 --- a/docs/architecture/diagrams/06-state-enrollment.puml +++ b/docs/architecture/diagrams/06-state-enrollment.puml @@ -3,28 +3,54 @@ 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 "Sin Materias" as SinMaterias { - state "0 créditos" as cred0 +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 "Inscripción Parcial" as Parcial { - state "3 créditos\n(1 materia)" as cred3 - state "6 créditos\n(2 materias)" as cred6 +state "Inscripciones" as inscripciones { + 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] } -state "Inscripción Completa" as Completa { - state "9 créditos\n(3 materias)" as cred9 -} +Activo --> SinMaterias : cuenta activa -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] +state validacion <> + +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 Validaciones en cada inscripción: + - Cuenta activa - Límite de créditos - No repetir profesor - Materia no duplicada end note -state validacion <> - -SinMaterias --> validacion : intenta inscribir -Parcial --> validacion : intenta inscribir - -validacion --> Parcial : [válido] -validacion --> [*] : [inválido]\nmuestra error +note bottom of validacion + Si la cuenta no está + activa, redirige a + página de activación +end note @enduml diff --git a/docs/architecture/diagrams/06-state-enrollment.svg b/docs/architecture/diagrams/06-state-enrollment.svg index 4412e1c..39efc08 100644 --- a/docs/architecture/diagrams/06-state-enrollment.svg +++ b/docs/architecture/diagrams/06-state-enrollment.svg @@ -1 +1 @@ -Estado de Inscripción del EstudianteEstado de Inscripción del EstudianteSin Materias0 créditosInscripción Parcial3 créditos(1 materia)6 créditos(2 materias)Inscripción Completa9 créditos(3 materias)No puede inscribirmás materiasValidaciones en cada inscripción:- Límite de créditos- No repetir profesor- Materia no duplicadaRegistro inicialinscribir(materia)cancelar(inscripción)[única materia]inscribir(materia)[créditos < 9]inscribir(materia)[créditos = 6]cancelar(inscripción)intenta inscribirintenta inscribir[válido][inválido]muestra error \ No newline at end of file +Estados del Estudiante y sus InscripcionesEstados del Estudiante y sus InscripcionesCuentaInscripcionesRegistrado(Pendiente Activación)ActivoSin Materias0 créditosInscripción Parcial3 créditos(1 materia)6 créditos(2 materias)Inscripción Completa9 créditos(3 materias)El estudiante recibecódigo de activaciónpor email (24h validez)No puede inscribirmás materiasValidaciones en cada inscripción:- Cuenta activa- Límite de créditos- No repetir profesor- Materia no duplicadaSi la cuenta no estáactiva, redirige apágina de activaciónRegistro inicialactivar(código)código expirado[regenerar código]inscribir(materia)cancelar(inscripción)[única materia]inscribir(materia)[créditos < 9]inscribir(materia)[créditos = 6]cancelar(inscripción)cuenta activaintenta inscribirintenta inscribir[válido y cuenta activa][inválido]muestra error \ No newline at end of file diff --git a/docs/architecture/diagrams/07-deployment.png b/docs/architecture/diagrams/07-deployment.png new file mode 100644 index 0000000..d5b9f27 Binary files /dev/null and b/docs/architecture/diagrams/07-deployment.png differ diff --git a/docs/architecture/diagrams/07-deployment.puml b/docs/architecture/diagrams/07-deployment.puml index 9a66898..f440621 100644 --- a/docs/architecture/diagrams/07-deployment.puml +++ b/docs/architecture/diagrams/07-deployment.puml @@ -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 <> { + node "frontend-deployment" as frontendPod <> { component "Nginx" as nginx { [Static Files] [Reverse Proxy] } } - node "student-api" as apiContainer <> { + node "api-deployment" as apiPod <> { component "ASP.NET Core" as aspnet { [Kestrel Server] [GraphQL Endpoint] + [JWT Auth] [Health Check] } } - node "student-db" as dbContainer <> { + node "sqlserver-statefulset" as dbPod <> { database "SQL Server 2022" as sqlserver { [StudentEnrollment DB] } } + + node "Traefik Ingress" as ingress <> { + [TLS Termination] + [Routing Rules] + } + + component "NetworkPolicy" as netpol <> { + [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 + Dominio: + academia.ingeniumcodex.com + TLS: Let's Encrypt +end note + note right of nginx Nginx Config: - 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 + CI/CD: Gitea Actions + Namespace: academia + Seguridad: NetworkPolicy +end note + +note right of netpol + Flujo permitido: + Ingress → Frontend → API → SQL + (Todo otro tráfico bloqueado) +end note + @enduml diff --git a/docs/architecture/diagrams/07-deployment.svg b/docs/architecture/diagrams/07-deployment.svg index df29dfd..2ec4546 100644 --- a/docs/architecture/diagrams/07-deployment.svg +++ b/docs/architecture/diagrams/07-deployment.svg @@ -1 +1 @@ -Sistema de Registro de Estudiantes - Diagrama de DespliegueSistema de Registro de Estudiantes - Diagrama de DespliegueClienteNavegador WebDocker Host«container»student-frontendNginx«container»student-apiASP.NET Core«container»student-dbSQL Server 2022Angular SPAStatic FilesReverse ProxyKestrel ServerGraphQL EndpointHealth CheckStudentEnrollment DBNginx Config:- Gzip/Brotli compression- Static file caching- GraphQL proxy- Security headersOptimizaciones:- Server GC- ReadyToRun- Connection pooling- Rate limitingRecursos:- 2 CPU cores- 2.5 GB RAM- Persistent volumeHTTP :80HTTP :5000/graphqlTCP :1433 \ No newline at end of file +Sistema de Registro de Estudiantes - Diagrama de DespliegueSistema de Registro de Estudiantes - Diagrama de DespliegueClienteNavegador WebK3s Cluster (Namespace: academia)«Pod»frontend-deploymentNginx«Pod»api-deploymentASP.NET Core«StatefulSet»sqlserver-statefulsetSQL Server 2022«Ingress»Traefik IngressAngular SPAStatic FilesReverse ProxyKestrel ServerGraphQL EndpointJWT AuthHealth CheckStudentEnrollment DBTLS TerminationRouting RulesInternetDominio:academia.ingeniumcodex.comTLS:Let's EncryptNginx Config:- Gzip/Brotli compression- Static file caching- GraphQL proxy- Security headersOptimizaciones:- Server GC- ReadyToRun- Connection pooling- Rate limiting- JWT validationRecursos:- 2 CPU cores- 2.5 GB RAM- Persistent volumeCI/CD:Gitea ActionsAuto-scaling:HPANamespace:academiaHTTPSHTTPS :443HTTP :80HTTP :5000/graphqlTCP :1433 \ No newline at end of file diff --git a/docs/architecture/diagrams/08-c4-context.png b/docs/architecture/diagrams/08-c4-context.png new file mode 100644 index 0000000..3dee358 Binary files /dev/null and b/docs/architecture/diagrams/08-c4-context.png differ diff --git a/docs/architecture/diagrams/08-c4-context.puml b/docs/architecture/diagrams/08-c4-context.puml index 3dc0cfc..7d1e543 100644 --- a/docs/architecture/diagrams/08-c4-context.puml +++ b/docs/architecture/diagrams/08-c4-context.puml @@ -10,28 +10,44 @@ skinparam rectangle { } actor "Estudiante" as student <> +actor "Administrador" as admin <> -rectangle "Sistema de Registro\nde Estudiantes" as system <> #lightblue { +rectangle "Sistema de Inscripción\nAcadémica" as system <> #lightblue { } rectangle "Base de Datos\nSQL Server" as database <> #lightgray { } -student --> system : Usa para registrarse\ne inscribirse en materias -system --> database : Lee y escribe\ndatos de inscripciones +rectangle "Servidor SMTP\n(Email)" as smtp <> #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 Estudiante 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 + Administrador + Usuario privilegiado que: + - Gestiona todos los estudiantes + - Crea, edita, elimina registros + - Visualiza todo el sistema end note note right of system - Sistema de Registro + Sistema de Inscripción Académica 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 Stack: Frontend: Angular 21 Backend: .NET 10 + GraphQL + Auth: JWT + Roles (Admin/Student) end note note right of database SQL Server 2022 Almacena: + - Usuarios (auth) - Estudiantes - Profesores - Materias - Inscripciones end note +note right of smtp + Servicio de Email + Para: + - Códigos de activación + - Notificaciones +end note + @enduml diff --git a/docs/architecture/diagrams/08-c4-context.svg b/docs/architecture/diagrams/08-c4-context.svg index b9e691a..2a6bdc8 100644 --- a/docs/architecture/diagrams/08-c4-context.svg +++ b/docs/architecture/diagrams/08-c4-context.svg @@ -1 +1 @@ -Sistema de Registro de Estudiantes - Diagrama de Contexto (C4 Level 1)Sistema de Registro de Estudiantes - Diagrama de Contexto (C4 Level 1)«Software System»Sistema de Registrode Estudiantes«External System»Base de DatosSQL ServerEstudiante«Persona»EstudianteUsuario del sistema que:- Se registra en el sistema- Se inscribe en materias (máx 3)- Ve sus compañeros de clase- Consulta inscripcionesSistema de RegistroAplicación web que permite:- CRUD de estudiantes- Inscripción en materias- Validación de reglas de negocio- Visualización de compañeros Stack:Frontend: Angular 21Backend: .NET 10 + GraphQLSQL Server 2022Almacena:- Estudiantes- Profesores- Materias- InscripcionesUsa para registrarsee inscribirse en materiasLee y escribedatos de inscripciones \ No newline at end of file +Sistema de Registro de Estudiantes - Diagrama de Contexto (C4 Level 1)Sistema de Registro de Estudiantes - Diagrama de Contexto (C4 Level 1)«Software System»Sistema de InscripciónAcadémica«External System»Base de DatosSQL Server«External System»Servidor SMTP(Email)Estudiante«Persona»Administrador«Persona»EstudianteUsuario del sistema que:- Se registra y activa cuenta- Se inscribe en materias (máx 3)- Ve sus compañeros de clase- Consulta sus inscripciones- Accede a su dashboard personalAdministradorUsuario privilegiado que:- Gestiona todos los estudiantes- Crea, edita, elimina registros- Visualiza todo el sistemaSistema de Inscripción AcadémicaAplicación web que permite:- Autenticación (JWT + PBKDF2)- CRUD de estudiantes- Inscripción en materias- Validación de reglas de negocio- Visualización de compañeros Stack:Frontend: Angular 21Backend: .NET 10 + GraphQLAuth: JWT + Roles (Admin/Student)SQL Server 2022Almacena:- Usuarios (auth)- Estudiantes- Profesores- Materias- InscripcionesServicio de EmailPara:- Códigos de activación- NotificacionesSe registra, activa cuenta,se inscribe en materias,ve compañeros de claseGestiona estudiantes(CRUD completo)Lee y escribedatos de usuarios,estudiantes e inscripcionesEnvía códigosde activación \ No newline at end of file