Commit Graph

232 Commits

Author SHA1 Message Date
6822d56e11 fix(web): montar Toaster + feedback toast en PermisosEditor [UDT-009]
Sonner estaba como dependencia pero el componente Toaster nunca se monto
en el arbol de la app. ChangeMyPasswordPage ya usaba toast() pero no
mostraba nada visualmente. Agregado <Toaster richColors closeButton /> en
App.tsx (top-right) y toasts de exito/error en PermisosEditor.handleSave
para confirmar al usuario que el cambio se persistio.
2026-04-16 10:11:04 -03:00
a30b10ebff fix(web): UsuarioPermisos shape nested para matchear backend [UDT-009]
El backend devuelve { rolPermisos, overrides: {grant, deny}, effective }
(nested) segun spec, pero el frontend lo lee como {grant, deny} planos.
Causaba TypeError: permisoData.grant is not iterable al abrir tab Permisos.
Tests del frontend actualizados con el shape correcto.
2026-04-16 10:06:43 -03:00
b7882613a4 feat(web): UserEditPage con tabs Perfil+Permisos [UDT-009] 2026-04-15 21:50:55 -03:00
9dbf3e895d feat(web): tabs.tsx + PermisosEditor tri-state [UDT-009] 2026-04-15 21:49:48 -03:00
c1426b2257 feat(web): api + hooks permisos overrides [UDT-009] 2026-04-15 21:48:06 -03:00
7d4dc4d2bb feat(api): PUT /api/v1/users/{id}/permisos/overrides + excepciones domain + ExceptionFilter [UDT-009] 2026-04-15 21:43:38 -03:00
47323302cc feat(api): GET /api/v1/users/{id}/permisos con CQRS handler [UDT-009] 2026-04-15 21:43:08 -03:00
5fd88b5a9d feat(infra): IUsuarioRepository.UpdatePermisosJsonAsync + impl Dapper [UDT-009] 2026-04-15 21:33:39 -03:00
bf64ffb35e feat(api): PermissionAuthorizationHandler resuelve overrides desde DB por request [UDT-009] 2026-04-15 21:32:35 -03:00
fb07a1139a feat(application): LoginCommandHandler usa PermisoResolver para permisos efectivos [UDT-009] 2026-04-15 21:29:33 -03:00
86310de286 feat(security): remover claim permisos del JWT post-UDT-009 [UDT-009] 2026-04-15 21:28:26 -03:00
54955231bf feat(infra): V009 migration + Usuario.WithPermisosJson + SqlTestFixture V009 schema [UDT-009] 2026-04-15 21:27:29 -03:00
da1eb83ac1 feat(application): PermisosOverride record + PermisoResolver static helper [UDT-009] 2026-04-15 21:25:09 -03:00
06908263f6 fix(web): cablear ResetPasswordModal en UserEditPage [UDT-008]
El row click de UsersListPage navega directo a /usuarios/:id/editar,
por lo que el modal montado solo en UserDetailPage no era alcanzable
desde el flujo real. Ahora tambien esta en el header del EditPage,
al lado del boton Volver, oculto cuando el target es el user logueado.
2026-04-15 21:00:08 -03:00
9e93c70d8b refactor(web): mover Cambiar contraseña de sidebar a menu perfil [UDT-008]
La seccion Mi cuenta en el sidebar quedaba desprolija con un unico item.
Se movio Cambiar contraseña al dropdown del avatar en AppHeader donde
pertenece semanticamente.
2026-04-15 20:55:26 -03:00
851fed8692 fix(web): cablear ResetPasswordModal en UserDetailPage [UDT-008]
El componente ResetPasswordModal estaba implementado pero nunca montado en una pagina.
Ahora se renderiza en UserDetailPage, oculto cuando el target es el usuario logueado
(evita hit de cannot-self-reset en backend).
2026-04-15 20:54:25 -03:00
2e2d4543ad feat(web): router wiring completo + nav link usuarios + MustChangePasswordGate integration [UDT-008]
- Agrega ProtectedPage helper que combina ProtectedRoute + MustChangePasswordGate + ProtectedLayout
- Rutas nuevas: /usuarios, /usuarios/:id, /usuarios/:id/editar con permisos RBAC
- /perfil/contrasena sin MustChangePasswordGate (evita redirect loop)
- Sidebar: sección "Mi cuenta" con cambio de contraseña; link Usuarios en sección admin
2026-04-15 18:12:54 -03:00
25ed0f6452 feat(web): ChangeMyPasswordPage + ResetPasswordModal — hooks, pages, modal [UDT-008] 2026-04-15 18:09:59 -03:00
64e0a8b5fb feat(web): UserDetailPage + UserEditPage — get/update/deactivate/reactivate hooks y pages [UDT-008] 2026-04-15 18:06:54 -03:00
9512f4125d feat(web): UsersListPage — api client, hook, filters, table, pagination [UDT-008] 2026-04-15 18:05:07 -03:00
d998d215e0 feat(web): authStore username+mustChangePassword + MustChangePasswordGate [UDT-008] 2026-04-15 18:02:20 -03:00
14c385fdb1 feat(api): UpdateUsuario — handler, validator, anti-lockout guard, revoke tokens [UDT-008] 2026-04-15 17:49:19 -03:00
2925336783 feat(api): List + GetById usuarios — handlers, repo, endpoints [UDT-008] 2026-04-15 17:46:23 -03:00
9dcd63543e feat(auth): extend LoginResponse with username + mustChangePassword + ultimoLogin [UDT-008] 2026-04-15 17:39:48 -03:00
d1f7b3805b feat(domain): V008 migration + Usuario with-methods + DomainException hierarchy [UDT-008] 2026-04-15 17:36:46 -03:00
96e7290fb7 refactor(web): eliminar guards inline rol admin en páginas de roles/permisos [UDT-006] 2026-04-15 16:49:21 -03:00
f6cdd7650b feat(web): ProtectedRoute extraído + router migrado + CreateUserPage cleanup [UDT-006] 2026-04-15 16:41:39 -03:00
8935115da9 feat(web): usePermission + CanPerform [UDT-006] 2026-04-15 16:40:23 -03:00
2efd5e2fdb feat(web): authStore + useLogin persisten permisos [UDT-006] 2026-04-15 16:39:18 -03:00
0218d8d371 feat(api): migrar controllers admin a RequirePermission [UDT-006] 2026-04-15 16:34:32 -03:00
4866c4f21f feat(api): ForbiddenProblemDetailsHandler 403 shape [UDT-006] 2026-04-15 16:27:36 -03:00
58d0df601f feat(api): RequirePermissionAttribute + PermissionAuthorizationHandler [UDT-006] 2026-04-15 16:26:30 -03:00
cdb8dcd03c feat(api): login response permisos desde RolPermiso [UDT-006] 2026-04-15 16:24:21 -03:00
1a864e9f8b fix(app): validar formato codigo rol en GetRolPermisos [UDT-005]
Agrega GetRolPermisosQueryValidator con regex ^[a-z][a-z0-9_]*$ para
rechazar codigos invalidos con 400 en GET /api/v1/roles/{codigo}/permisos.
2026-04-15 15:56:49 -03:00
885a8cef17 feat(web): BATCH 6 - feature permisos con grid por modulo [UDT-005]
- api/types.ts: PermisoDto, AssignPermisosRequest
- api/listPermisos, getRolPermisos, assignPermisos
- hooks: usePermisos, useRolPermisos, useAssignPermisos (TanStack Query)
- components/RolPermisosEditor: checkbox-grid agrupado por modulo (codigo.split(':')[0])
- pages/RolPermisosPage: selector rol activo + guard admin + RolPermisosEditor
- router.tsx: ruta /admin/permisos
- AppSidebar.tsx: link Permisos (KeyRound icon) en seccion admin
- tests: 5 smoke tests RolPermisosEditor (render, prefill, toggle, save, 400)
2026-04-15 15:46:49 -03:00
4913a35d06 feat(api): BATCH 5 - PermisosController + tests HTTP [UDT-005] 2026-04-15 15:42:03 -03:00
be2257a9bf feat(infra): BATCH 4 - Permiso/RolPermiso repos Dapper + tests integracion [UDT-005] 2026-04-15 15:39:25 -03:00
704794a2e2 feat(app): BATCH 3 - handlers permisos con TDD [UDT-005] 2026-04-15 15:31:26 -03:00
7ddb71c24c feat(domain): BATCH 2 - Permiso entity + catalogo const [UDT-005] 2026-04-15 15:31:20 -03:00
fae06fb8b8 feat(web): UDT-004 gestion de roles + UserForm dinamico
- features/roles: API clients (list/get/create/update/deactivate), TanStack Query hooks, RolForm (create + edit variants), RolesList con acciones y guard 409, paginas RolesPage/NewRolPage/EditRolPage
- router.tsx: rutas /admin/roles, /admin/roles/nuevo, /admin/roles/:codigo/editar
- AppSidebar: nav Roles (admin-only)
- features/users: useRolesForSelect wrapper (filtra activo=true), UserForm fetchea roles async con loading/error states; elimina ROL_OPTIONS hardcoded
- tests: 47 vitest verdes (10 authStore + 5 auth api + 7 axios + 3 useCreateUser + 3 RolesList + 5 LoginPage + 7 UserForm + 7 RolForm). Typecheck limpio
2026-04-15 12:58:08 -03:00
6f999b8fcd feat(api): UDT-004 controller de roles + refactor validator UDT-003 a lookup dinamico
- RolesController /api/v1/roles CRUD admin-only: GET list, GET {codigo}, POST, PUT, DELETE (soft-delete con guard 409)
- ExceptionFilter: mapea RolNotFound (404), RolAlreadyExists (409), RolInUse (409)
- DI: registra 5 handlers de Roles (Application) y IRolRepository/RolRepository (Infrastructure)
- CreateUsuarioCommandValidator: reemplaza whitelist hardcoded por IRolRepository.ExistsActiveByCodigoAsync via MustAsync; constructor recibe (AuthOptions, IRolRepository)
- Tests: 202 verdes (173 application + 29 api). Nuevas: RolesEndpointTests (13 integration), CreateUsuarioCommandValidatorTests reescrito con NSubstitute mock, CreateUsuario_WithInactiveRol_Returns400 en Api.Tests
- Fix: ApiIntegration pasa de IClassFixture (N factories) a ICollectionFixture (1 factory shared) — evitaba ObjectDisposedException sobre RSABCrypt al compartir coleccion con multiples test classes
- tests/tests.runsettings: MaxCpuCount=1 para evitar race entre assemblies sobre SIGCM2_Test
2026-04-15 12:50:24 -03:00
34b714750a feat(api): UDT-004 dominio + repositorio + application roles (tdd)
- Migraciones V003 (tabla Rol + 8 seeds canonicos) y V004 (drop CK + FK Usuario.Rol)
- Dominio: Rol entity + 3 excepciones (RolNotFound/AlreadyExists/InUse)
- Infraestructura: RolRepository (Dapper) con List/Get/ExistsActive/Add/Update/HasActiveUsuarios
- Application: CRUD queries y commands (List, Get, Create, Update, Deactivate) + validators (codigo regex ^[a-z][a-z0-9_]*$)
- Validator UDT-003: whitelist alineada a codigos canonicos (full IRolRepository lookup diferido a Phase 5.1)
- Tests: 169 application + 15 api (todos verdes). Respawn configurado para re-seedear Rol canonical post-reset.
- Estricto TDD: RED/GREEN/TRIANGULATE en todos los handlers nuevos.
2026-04-15 12:31:29 -03:00
bce591e63c fix(auth): preserve JWT claim names in bearer middleware
JwtBearerOptions.MapInboundClaims defaulted to true, which mapped the
'sub' claim to ClaimTypes.NameIdentifier in HttpContext.User. Logout
endpoint read User.FindFirst("sub") and got null, returning 401 for
any authenticated caller.

Fix: set MapInboundClaims=false and pin NameClaimType="name" so the
JWT claims land in the principal with their original names, aligning
with how JwtService.GetPrincipalFromExpiredToken (used by refresh)
already consumes them.

Unblocks Login_Refresh_Logout_FullFlow integration test (15/15 green).
2026-04-15 11:03:15 -03:00
dd99e5cc69 feat(web): UDT-003 formulario de alta de usuarios (admin)
Agrega CreateUserPage con UserForm (react-hook-form + Zod), hook useCreateUser
(TanStack Query mutation), ruta /users/new protegida y entrada en AppSidebar.
Incluye tests Vitest: UserForm (9 casos) y useCreateUser (3 casos).
2026-04-15 10:57:11 -03:00
3d598faffc feat(api): UDT-003 registro de usuarios — backend completo (Phases 1-6)
- Domain: Usuario.ForCreation factory, UsernameAlreadyExistsException, IUsuarioRepository extendido
- Application: CreateUsuarioCommand/Validator/Handler, UsuarioCreatedDto, AuthOptions password policy
- Infrastructure: UsuarioRepository.ExistsByUsernameAsync + AddAsync (INSERT OUTPUT INSERTED.Id), RoleClaimType="rol" en TokenValidationParameters
- Api: UsuariosController POST api/v1/users [Authorize(Roles="admin")], ExceptionFilter mapea UsernameAlreadyExistsException + SqlException 2627 → 409
- Tests (unit): 43 tests — 33 validator + 10 handler (107 total, green)
- Tests (integration): 7 tests CreateUsuarioEndpoint — 401/403/400/201/409/race/e2e (green)
- Fix: TestWebAppFactory.ConfigureTestServices reemplaza SqlConnectionFactory singleton con CS de test correcto
2026-04-15 10:47:48 -03:00
96dbeecc0f fix(web): use endsWith for /auth path exclusion in refresh interceptor
Avoids substring-match false positives on future endpoints whose URL could
contain /auth/refresh or /auth/login as infix (W-01 from verify report).
2026-04-14 13:59:37 -03:00
7fadb88da0 docs(web): smoke test checklist UDT-002 — login, refresh, logout, reuse detection 2026-04-14 13:52:59 -03:00
dd4f4dbd5e test(web): LoginPage — verify setAuth receives expiresIn and calculates expiresAt 2026-04-14 13:51:41 -03:00
bdaaaffaf6 feat(web): axiosClient — request/response interceptors with singleton refresh queue 2026-04-14 13:50:49 -03:00
d40b7247fc feat(web): authApi — add refresh() and logout() with types and tests 2026-04-14 13:49:39 -03:00