Cargando roles...
+
+ if (isError) {
+ const msg = isAxiosError(error) ? (error.message ?? 'Error al cargar roles') : 'Error al cargar roles'
+ return (
+ No hay roles registrados.
+ }
+
+ function handleDeactivate(codigo: string) {
+ deactivateMut.mutate(codigo)
+ }
+
+ const deactivateErr = deactivateMut.error
+ const deactivateErrMsg =
+ deactivateErr && isAxiosError(deactivateErr)
+ ? (deactivateErr.response?.data as { message?: string } | undefined)?.message ??
+ 'No se pudo desactivar el rol'
+ : deactivateErr
+ ? 'No se pudo desactivar el rol'
+ : null
+
+ return (
+
+ {deactivateErrMsg && (
+
+
+ {deactivateErrMsg}
+
+ )}
+
+
+
+ | Código |
+ Nombre |
+ Descripción |
+ Estado |
+ Acciones |
+
+
+
+ {roles.map((r) => (
+
+ | {r.codigo} |
+ {r.nombre} |
+ {r.descripcion ?? '—'} |
+
+ {r.activo ? (
+ Activo
+ ) : (
+ Inactivo
+ )}
+ |
+
+
+
+
+ {r.activo && (
+
+ )}
+ |
+
+ ))}
+
+
+
+ )
+}
diff --git a/src/web/src/features/roles/hooks/useCreateRole.ts b/src/web/src/features/roles/hooks/useCreateRole.ts
new file mode 100644
index 0000000..99713d1
--- /dev/null
+++ b/src/web/src/features/roles/hooks/useCreateRole.ts
@@ -0,0 +1,14 @@
+import { useMutation, useQueryClient } from '@tanstack/react-query'
+import { createRole } from '../api/createRole'
+import type { CreateRolRequest } from '../api/types'
+import { rolesQueryKey } from './useRoles'
+
+export function useCreateRole() {
+ const qc = useQueryClient()
+ return useMutation({
+ mutationFn: (payload: CreateRolRequest) => createRole(payload),
+ onSuccess: () => {
+ void qc.invalidateQueries({ queryKey: rolesQueryKey })
+ },
+ })
+}
diff --git a/src/web/src/features/roles/hooks/useDeactivateRole.ts b/src/web/src/features/roles/hooks/useDeactivateRole.ts
new file mode 100644
index 0000000..755fb7e
--- /dev/null
+++ b/src/web/src/features/roles/hooks/useDeactivateRole.ts
@@ -0,0 +1,13 @@
+import { useMutation, useQueryClient } from '@tanstack/react-query'
+import { deactivateRole } from '../api/deactivateRole'
+import { rolesQueryKey } from './useRoles'
+
+export function useDeactivateRole() {
+ const qc = useQueryClient()
+ return useMutation({
+ mutationFn: (codigo: string) => deactivateRole(codigo),
+ onSuccess: () => {
+ void qc.invalidateQueries({ queryKey: rolesQueryKey })
+ },
+ })
+}
diff --git a/src/web/src/features/roles/hooks/useRol.ts b/src/web/src/features/roles/hooks/useRol.ts
new file mode 100644
index 0000000..87bb3f2
--- /dev/null
+++ b/src/web/src/features/roles/hooks/useRol.ts
@@ -0,0 +1,10 @@
+import { useQuery } from '@tanstack/react-query'
+import { getRol } from '../api/getRol'
+
+export function useRol(codigo: string | undefined) {
+ return useQuery({
+ queryKey: ['roles', codigo],
+ queryFn: () => getRol(codigo!),
+ enabled: Boolean(codigo),
+ })
+}
diff --git a/src/web/src/features/roles/hooks/useRoles.ts b/src/web/src/features/roles/hooks/useRoles.ts
new file mode 100644
index 0000000..68c3caf
--- /dev/null
+++ b/src/web/src/features/roles/hooks/useRoles.ts
@@ -0,0 +1,12 @@
+import { useQuery } from '@tanstack/react-query'
+import { listRoles } from '../api/listRoles'
+
+export const rolesQueryKey = ['roles'] as const
+
+export function useRoles() {
+ return useQuery({
+ queryKey: rolesQueryKey,
+ queryFn: listRoles,
+ staleTime: 30_000,
+ })
+}
diff --git a/src/web/src/features/roles/hooks/useUpdateRole.ts b/src/web/src/features/roles/hooks/useUpdateRole.ts
new file mode 100644
index 0000000..b9fc025
--- /dev/null
+++ b/src/web/src/features/roles/hooks/useUpdateRole.ts
@@ -0,0 +1,20 @@
+import { useMutation, useQueryClient } from '@tanstack/react-query'
+import { updateRole } from '../api/updateRole'
+import type { UpdateRolRequest } from '../api/types'
+import { rolesQueryKey } from './useRoles'
+
+interface UpdateRoleVars {
+ codigo: string
+ payload: UpdateRolRequest
+}
+
+export function useUpdateRole() {
+ const qc = useQueryClient()
+ return useMutation({
+ mutationFn: ({ codigo, payload }: UpdateRoleVars) => updateRole(codigo, payload),
+ onSuccess: (_data, vars) => {
+ void qc.invalidateQueries({ queryKey: rolesQueryKey })
+ void qc.invalidateQueries({ queryKey: ['roles', vars.codigo] })
+ },
+ })
+}
diff --git a/src/web/src/features/roles/pages/EditRolPage.tsx b/src/web/src/features/roles/pages/EditRolPage.tsx
new file mode 100644
index 0000000..b5e7973
--- /dev/null
+++ b/src/web/src/features/roles/pages/EditRolPage.tsx
@@ -0,0 +1,46 @@
+import { useNavigate, useParams } from 'react-router-dom'
+import { useAuthStore } from '@/stores/authStore'
+import {
+ Card,
+ CardContent,
+ CardDescription,
+ CardHeader,
+ CardTitle,
+} from '@/components/ui/card'
+import { Alert, AlertDescription } from '@/components/ui/alert'
+import { AlertCircle } from 'lucide-react'
+import { useRol } from '../hooks/useRol'
+import { EditRolForm } from '../components/RolForm'
+
+export function EditRolPage() {
+ const navigate = useNavigate()
+ const { codigo } = useParams<{ codigo: string }>()
+ const user = useAuthStore((s) => s.user)
+ const { data: rol, isLoading, isError } = useRol(codigo)
+
+ if (!user || user.rol !== 'admin') {
+ void navigate('/', { replace: true })
+ return null
+ }
+
+ return (
+