feat(frontend): Agregar hooks React Query para módulo HSE

Implementación completa de 16 hooks personalizados de React Query para
gestión del módulo HSE (Health, Safety & Environment):

**Incidentes (7 hooks):**
- useIncidentes, useIncidente, useIncidenteStats
- useCreateIncidente, useUpdateIncidente
- useInvestigateIncidente, useCloseIncidente

**Capacitaciones (6 hooks):**
- useCapacitaciones, useCapacitacion
- useCreateCapacitacion, useUpdateCapacitacion
- useToggleCapacitacion, useDeleteCapacitacion

**Inspecciones (7 hooks):**
- useTiposInspeccion (con staleTime 30min)
- useInspecciones, useInspeccion, useInspeccionesStats
- useCreateInspeccion, useUpdateEstadoInspeccion
- useAddHallazgo

**Características:**
- Query Keys jerárquicos type-safe
- Manejo centralizado de errores con toast
- Invalidación inteligente de queries relacionadas
- Patrón consistente con useConstruccion.ts
- TypeScript completamente tipado
- 291 líneas de código

**Validaciones:**
✓ npm run type-check (sin errores)
✓ npm run lint (sin warnings)

Archivos:
- web/src/hooks/useHSE.ts (nuevo, 291 líneas)
- web/src/hooks/index.ts (export agregado)

Relacionado: MAA-017 (Módulo HSE Backend)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Adrian Flores Cortes 2026-02-02 21:25:18 -06:00
parent cbb4f265c5
commit dac9ae6f19
2 changed files with 292 additions and 0 deletions

View File

@ -2,3 +2,4 @@ export * from './useConstruccion';
export * from './usePresupuestos';
export * from './useReports';
export * from './useBidding';
export * from './useHSE';

291
web/src/hooks/useHSE.ts Normal file
View File

@ -0,0 +1,291 @@
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { AxiosError } from 'axios';
import toast from 'react-hot-toast';
import { ApiError } from '../services/api';
import {
incidentesApi,
IncidenteFilters,
CreateIncidenteDto,
UpdateIncidenteDto,
} from '../services/hse/incidentes.api';
import {
capacitacionesApi,
CapacitacionFilters,
CreateCapacitacionDto,
UpdateCapacitacionDto,
} from '../services/hse/capacitaciones.api';
import {
inspeccionesApi,
InspeccionFilters,
CreateInspeccionDto,
CreateHallazgoDto,
} from '../services/hse/inspecciones.api';
// Query Keys
export const hseKeys = {
incidentes: {
all: ['hse', 'incidentes'] as const,
list: (filters?: IncidenteFilters) => [...hseKeys.incidentes.all, 'list', filters] as const,
detail: (id: string) => [...hseKeys.incidentes.all, 'detail', id] as const,
stats: () => [...hseKeys.incidentes.all, 'stats'] as const,
},
capacitaciones: {
all: ['hse', 'capacitaciones'] as const,
list: (filters?: CapacitacionFilters) =>
[...hseKeys.capacitaciones.all, 'list', filters] as const,
detail: (id: string) => [...hseKeys.capacitaciones.all, 'detail', id] as const,
},
inspecciones: {
all: ['hse', 'inspecciones'] as const,
list: (filters?: InspeccionFilters) =>
[...hseKeys.inspecciones.all, 'list', filters] as const,
detail: (id: string) => [...hseKeys.inspecciones.all, 'detail', id] as const,
stats: () => [...hseKeys.inspecciones.all, 'stats'] as const,
tipos: () => [...hseKeys.inspecciones.all, 'tipos'] as const,
},
};
// Error handler
const handleError = (error: AxiosError<ApiError>) => {
const message = error.response?.data?.message || 'Ha ocurrido un error';
toast.error(message);
};
// ==================== INCIDENTES ====================
export function useIncidentes(filters?: IncidenteFilters) {
return useQuery({
queryKey: hseKeys.incidentes.list(filters),
queryFn: () => incidentesApi.list(filters),
});
}
export function useIncidente(id: string) {
return useQuery({
queryKey: hseKeys.incidentes.detail(id),
queryFn: () => incidentesApi.get(id),
enabled: !!id,
});
}
export function useIncidenteStats() {
return useQuery({
queryKey: hseKeys.incidentes.stats(),
queryFn: () => incidentesApi.getStats(),
});
}
export function useCreateIncidente() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (data: CreateIncidenteDto) => incidentesApi.create(data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: hseKeys.incidentes.all });
toast.success('Incidente registrado exitosamente');
},
onError: handleError,
});
}
export function useUpdateIncidente() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ id, data }: { id: string; data: UpdateIncidenteDto }) =>
incidentesApi.update(id, data),
onSuccess: (_, { id }) => {
queryClient.invalidateQueries({ queryKey: hseKeys.incidentes.all });
queryClient.invalidateQueries({ queryKey: hseKeys.incidentes.detail(id) });
toast.success('Incidente actualizado');
},
onError: handleError,
});
}
export function useInvestigateIncidente() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({
id,
investigadorId,
fechaInvestigacion,
}: {
id: string;
investigadorId: string;
fechaInvestigacion?: string;
}) => incidentesApi.investigate(id, { investigadorId, fechaInvestigacion }),
onSuccess: (_, { id }) => {
queryClient.invalidateQueries({ queryKey: hseKeys.incidentes.all });
queryClient.invalidateQueries({ queryKey: hseKeys.incidentes.detail(id) });
toast.success('Investigación iniciada');
},
onError: handleError,
});
}
export function useCloseIncidente() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({
id,
fechaCierre,
observaciones,
}: {
id: string;
fechaCierre?: string;
observaciones?: string;
}) => incidentesApi.close(id, { fechaCierre, observaciones }),
onSuccess: (_, { id }) => {
queryClient.invalidateQueries({ queryKey: hseKeys.incidentes.all });
queryClient.invalidateQueries({ queryKey: hseKeys.incidentes.detail(id) });
toast.success('Incidente cerrado');
},
onError: handleError,
});
}
// ==================== CAPACITACIONES ====================
export function useCapacitaciones(filters?: CapacitacionFilters) {
return useQuery({
queryKey: hseKeys.capacitaciones.list(filters),
queryFn: () => capacitacionesApi.list(filters),
});
}
export function useCapacitacion(id: string) {
return useQuery({
queryKey: hseKeys.capacitaciones.detail(id),
queryFn: () => capacitacionesApi.get(id),
enabled: !!id,
});
}
export function useCreateCapacitacion() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (data: CreateCapacitacionDto) => capacitacionesApi.create(data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: hseKeys.capacitaciones.all });
toast.success('Capacitacion creada exitosamente');
},
onError: handleError,
});
}
export function useUpdateCapacitacion() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ id, data }: { id: string; data: UpdateCapacitacionDto }) =>
capacitacionesApi.update(id, data),
onSuccess: (_, { id }) => {
queryClient.invalidateQueries({ queryKey: hseKeys.capacitaciones.all });
queryClient.invalidateQueries({ queryKey: hseKeys.capacitaciones.detail(id) });
toast.success('Capacitacion actualizada');
},
onError: handleError,
});
}
export function useToggleCapacitacion() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (id: string) => capacitacionesApi.toggleActive(id),
onSuccess: (_, id) => {
queryClient.invalidateQueries({ queryKey: hseKeys.capacitaciones.all });
queryClient.invalidateQueries({ queryKey: hseKeys.capacitaciones.detail(id) });
toast.success('Estado actualizado');
},
onError: handleError,
});
}
export function useDeleteCapacitacion() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (id: string) => capacitacionesApi.delete(id),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: hseKeys.capacitaciones.all });
toast.success('Capacitacion eliminada');
},
onError: handleError,
});
}
// ==================== INSPECCIONES ====================
export function useTiposInspeccion() {
return useQuery({
queryKey: hseKeys.inspecciones.tipos(),
queryFn: () => inspeccionesApi.listTipos(),
staleTime: 1000 * 60 * 30, // 30 minutos - datos relativamente estáticos
});
}
export function useInspecciones(filters?: InspeccionFilters) {
return useQuery({
queryKey: hseKeys.inspecciones.list(filters),
queryFn: () => inspeccionesApi.list(filters),
});
}
export function useInspeccion(id: string) {
return useQuery({
queryKey: hseKeys.inspecciones.detail(id),
queryFn: () => inspeccionesApi.get(id),
enabled: !!id,
});
}
export function useInspeccionesStats() {
return useQuery({
queryKey: hseKeys.inspecciones.stats(),
queryFn: () => inspeccionesApi.getStats(),
});
}
export function useCreateInspeccion() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (data: CreateInspeccionDto) => inspeccionesApi.create(data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: hseKeys.inspecciones.all });
toast.success('Inspección creada exitosamente');
},
onError: handleError,
});
}
export function useUpdateEstadoInspeccion() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({
id,
data,
}: {
id: string;
data: { estado: string; observaciones?: string };
}) => inspeccionesApi.updateEstado(id, data),
onSuccess: (_, { id }) => {
queryClient.invalidateQueries({ queryKey: hseKeys.inspecciones.all });
queryClient.invalidateQueries({ queryKey: hseKeys.inspecciones.detail(id) });
queryClient.invalidateQueries({ queryKey: hseKeys.inspecciones.stats() });
toast.success('Estado de inspección actualizado');
},
onError: handleError,
});
}
export function useAddHallazgo() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ id, data }: { id: string; data: CreateHallazgoDto }) =>
inspeccionesApi.addHallazgo(id, data),
onSuccess: (_, { id }) => {
queryClient.invalidateQueries({ queryKey: hseKeys.inspecciones.all });
queryClient.invalidateQueries({ queryKey: hseKeys.inspecciones.detail(id) });
queryClient.invalidateQueries({ queryKey: hseKeys.inspecciones.stats() });
toast.success('Hallazgo agregado');
},
onError: handleError,
});
}