feat(auditoria): mostrar el nombre del usuario en lugar del id (tabla y filtro)

This commit is contained in:
2026-09-17 08:54:56 +00:00
parent 9643f866cc
commit 84582ce6a2
6 changed files with 66 additions and 19 deletions

View File

@@ -23,8 +23,8 @@ const respuestaOk = (data: unknown) =>
new Response(JSON.stringify(data), { status: 200, headers: { 'Content-Type': 'application/json' } })
const auditoriaFake = [
{ id: 'a1', usuarioId: 'admin', accion: 'LEAD_CREADO', entidad: 'lead', entidadId: 'l1', detalle: 'Lead nuevo', createdAt: '2026-09-01T10:00:00Z' },
{ id: 'a2', usuarioId: 'vendedor1', accion: 'ETAPA_CAMBIADA', entidad: 'oportunidad', entidadId: 'o1', detalle: 'Cambiado a Cotizador', createdAt: '2026-09-02T14:00:00Z' },
{ id: 'a1', usuarioId: 'admin', usuarioNombre: 'Administrador FRASA', accion: 'LEAD_CREADO', entidad: 'lead', entidadId: 'l1', detalle: 'Lead nuevo', createdAt: '2026-09-01T10:00:00Z' },
{ id: 'a2', usuarioId: 'vendedor1', usuarioNombre: 'Martin Zavala', accion: 'ETAPA_CAMBIADA', entidad: 'oportunidad', entidadId: 'o1', detalle: 'Cambiado a Cotizador', createdAt: '2026-09-02T14:00:00Z' },
]
let fetchMock: ReturnType<typeof vi.fn<FetchFn>>
@@ -49,9 +49,9 @@ describe('AuditoriaLog', () => {
await waitFor(() => expect(fetchMock).toHaveBeenCalledOnce())
expect(screen.getByText('LEAD_CREADO')).toBeTruthy()
expect(screen.getByText('ETAPA_CAMBIADA')).toBeTruthy()
// User IDs may appear in multiple cells; verify they exist
expect(screen.getAllByText('admin').length).toBeGreaterThanOrEqual(1)
expect(screen.getAllByText('vendedor1').length).toBeGreaterThanOrEqual(1)
// La tabla muestra el NOMBRE del usuario (el id solo como respaldo/tooltip)
expect(screen.getAllByText('Administrador FRASA').length).toBeGreaterThanOrEqual(1)
expect(screen.getAllByText('Martin Zavala').length).toBeGreaterThanOrEqual(1)
})
it('muestra "Sin registros" cuando no hay datos', async () => {

View File

@@ -15,7 +15,7 @@ export function AuditoriaLog() {
const [usuario, setUsuario] = useState('')
const [fechaDesde, setFechaDesde] = useState('')
const [fechaHasta, setFechaHasta] = useState('')
const [usuariosLista, setUsuariosLista] = useState<string[]>([])
const [usuariosLista, setUsuariosLista] = useState<[string, string][]>([])
const cargar = useCallback(async () => {
setCargando(true)
@@ -29,9 +29,12 @@ export function AuditoriaLog() {
})
setItems(data)
setPagina(1)
// Extraer usuarios únicos para el select
const usrs = [...new Set(data.map((i) => i.usuarioId).filter(Boolean))] as string[]
setUsuariosLista(usrs)
// Usuarios únicos para el select: se muestra el nombre y se filtra por id
const porId = new Map<string, string>()
for (const i of data) {
if (i.usuarioId) porId.set(i.usuarioId, i.usuarioNombre ?? i.usuarioId)
}
setUsuariosLista([...porId.entries()].sort((a, b) => a[1].localeCompare(b[1])))
} catch {
setError('No se pudieron cargar los registros de auditoría.')
} finally {
@@ -74,9 +77,9 @@ export function AuditoriaLog() {
className="ml-2 rounded-lg border border-[#D1D5DB] bg-white px-2 py-1.5 text-xs dark:border-[#334155] dark:bg-[#0F172A] dark:text-[#E2E8F0]"
>
<option value="">Todos</option>
{usuariosLista.map((u) => (
<option key={u} value={u}>
{u}
{usuariosLista.map(([id, nombre]) => (
<option key={id} value={id}>
{nombre}
</option>
))}
</select>
@@ -136,7 +139,9 @@ export function AuditoriaLog() {
<td className="whitespace-nowrap px-3 py-2 text-[#374151] dark:text-[#CBD5E1]">
{new Date(item.createdAt).toLocaleString('es-MX')}
</td>
<td className="px-3 py-2 text-[#374151] dark:text-[#CBD5E1]">{item.usuarioId}</td>
<td className="whitespace-nowrap px-3 py-2 text-[#374151] dark:text-[#CBD5E1]" title={item.usuarioId}>
{item.usuarioNombre ?? item.usuarioId}
</td>
<td className="px-3 py-2 text-[#374151] dark:text-[#CBD5E1]">{item.accion}</td>
<td className="px-3 py-2 text-[#374151] dark:text-[#CBD5E1]">{item.entidad}</td>
<td className="max-w-[200px] truncate px-3 py-2 text-[#64748B] dark:text-[#94A3B8]">{item.detalle ?? '—'}</td>

View File

@@ -170,6 +170,8 @@ export async function reindexarConocimiento(): Promise<{
export interface AuditoriaItem {
id: string
usuarioId: string
/** Nombre del usuario que hizo la acción (el API lo resuelve) */
usuarioNombre?: string
accion: string
entidad: string
entidadId: string