UDT-008: Gestión completa de usuarios #11

Merged
dmolinari merged 16 commits from feature/UDT-008 into main 2026-04-16 00:01:36 +00:00
2 changed files with 83 additions and 0 deletions
Showing only changes of commit 851fed8692 - Show all commits

View File

@@ -4,11 +4,14 @@ import { Badge } from '@/components/ui/badge'
import { useUser } from '../hooks/useUser' import { useUser } from '../hooks/useUser'
import { useDeactivateUser } from '../hooks/useDeactivateUser' import { useDeactivateUser } from '../hooks/useDeactivateUser'
import { useReactivateUser } from '../hooks/useReactivateUser' import { useReactivateUser } from '../hooks/useReactivateUser'
import { ResetPasswordModal } from '../components/ResetPasswordModal'
import { useAuthStore } from '@/stores/authStore'
export function UserDetailPage() { export function UserDetailPage() {
const { id } = useParams<{ id: string }>() const { id } = useParams<{ id: string }>()
const userId = Number(id) const userId = Number(id)
const navigate = useNavigate() const navigate = useNavigate()
const loggedUserId = useAuthStore((s) => s.user?.id)
const { data: user, isLoading } = useUser(userId) const { data: user, isLoading } = useUser(userId)
const { mutate: deactivate, isPending: deactivating } = useDeactivateUser() const { mutate: deactivate, isPending: deactivating } = useDeactivateUser()
@@ -90,6 +93,8 @@ export function UserDetailPage() {
{reactivating ? 'Reactivando...' : 'Reactivar'} {reactivating ? 'Reactivando...' : 'Reactivar'}
</Button> </Button>
)} )}
{loggedUserId !== userId && <ResetPasswordModal userId={userId} />}
</div> </div>
</div> </div>
) )

View File

@@ -0,0 +1,78 @@
import { describe, it, expect, beforeAll, afterAll, afterEach } from 'vitest'
import { render, screen, waitFor } from '@testing-library/react'
import { http, HttpResponse } from 'msw'
import { setupServer } from 'msw/node'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { MemoryRouter, Routes, Route } from 'react-router-dom'
import { UserDetailPage } from '../../../features/users/pages/UserDetailPage'
import { useAuthStore } from '../../../stores/authStore'
const API_URL = 'http://localhost:5000'
const adminUser = {
id: 1, username: 'admin', nombre: 'Admin', rol: 'admin',
permisos: ['administracion:usuarios:gestionar'],
mustChangePassword: false,
}
const target = {
id: 5,
username: 'cajero1',
nombre: 'Juan',
apellido: 'Perez',
email: 'juan@test.com',
rol: 'cajero',
activo: true,
permisosJson: '[]',
fechaModificacion: null,
ultimoLogin: null,
}
const server = setupServer()
beforeAll(() => server.listen({ onUnhandledRequest: 'error' }))
afterEach(() => {
server.resetHandlers()
useAuthStore.getState().clearAuth()
})
afterAll(() => server.close())
function renderDetail(userId: number) {
useAuthStore.setState({ user: adminUser })
const qc = new QueryClient({ defaultOptions: { queries: { retry: false }, mutations: { retry: false } } })
return render(
<QueryClientProvider client={qc}>
<MemoryRouter initialEntries={[`/usuarios/${userId}`]}>
<Routes>
<Route path="/usuarios/:id" element={<UserDetailPage />} />
</Routes>
</MemoryRouter>
</QueryClientProvider>,
)
}
describe('UserDetailPage — reset password wiring', () => {
it('shows "Resetear contraseña" button when viewing another user', async () => {
server.use(
http.get(`${API_URL}/api/v1/users/5`, () => HttpResponse.json(target)),
)
renderDetail(5)
await waitFor(() => expect(screen.getByText('Juan Perez')).toBeInTheDocument())
expect(screen.getByRole('button', { name: /resetear contraseña/i })).toBeInTheDocument()
})
it('hides "Resetear contraseña" button when viewing own profile (prevent cannot-self-reset)', async () => {
server.use(
http.get(`${API_URL}/api/v1/users/1`, () =>
HttpResponse.json({ ...target, id: 1, username: 'admin', nombre: 'Admin', apellido: 'Root', rol: 'admin' }),
),
)
renderDetail(1)
await waitFor(() => expect(screen.getByText('Admin Root')).toBeInTheDocument())
expect(screen.queryByRole('button', { name: /resetear contraseña/i })).not.toBeInTheDocument()
})
})