import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { AxiosError } from 'axios'; import toast from 'react-hot-toast'; import { ApiError } from '../services/api'; import { conceptosApi, ConceptoFilters, CreateConceptoDto, UpdateConceptoDto, presupuestosApi, PresupuestoFilters, CreatePresupuestoDto, UpdatePresupuestoDto, CreatePresupuestoPartidaDto, UpdatePresupuestoPartidaDto, RejectPresupuestoDto, estimacionesApi, EstimacionFilters, CreateEstimacionDto, UpdateEstimacionDto, CreateEstimacionPartidaDto, UpdateEstimacionPartidaDto, CreateGeneradorDto, UpdateGeneradorDto, } from '../services/presupuestos'; // ==================== QUERY KEYS ==================== export const presupuestosKeys = { conceptos: { all: ['presupuestos', 'conceptos'] as const, list: (filters?: ConceptoFilters) => [...presupuestosKeys.conceptos.all, 'list', filters] as const, detail: (id: string) => [...presupuestosKeys.conceptos.all, 'detail', id] as const, tree: (rootId?: string) => [...presupuestosKeys.conceptos.all, 'tree', rootId] as const, }, presupuestos: { all: ['presupuestos', 'presupuestos'] as const, list: (filters?: PresupuestoFilters) => [...presupuestosKeys.presupuestos.all, 'list', filters] as const, detail: (id: string) => [...presupuestosKeys.presupuestos.all, 'detail', id] as const, }, estimaciones: { all: ['presupuestos', 'estimaciones'] as const, list: (filters?: EstimacionFilters) => [...presupuestosKeys.estimaciones.all, 'list', filters] as const, detail: (id: string) => [...presupuestosKeys.estimaciones.all, 'detail', id] as const, }, }; // ==================== ERROR HANDLER ==================== const handleError = (error: AxiosError) => { const message = error.response?.data?.message || 'Ha ocurrido un error'; toast.error(message); }; // ==================== CONCEPTOS ==================== export function useConceptos(filters?: ConceptoFilters) { return useQuery({ queryKey: presupuestosKeys.conceptos.list(filters), queryFn: () => conceptosApi.list(filters), }); } export function useConcepto(id: string) { return useQuery({ queryKey: presupuestosKeys.conceptos.detail(id), queryFn: () => conceptosApi.get(id), enabled: !!id, }); } export function useConceptosTree(rootId?: string) { return useQuery({ queryKey: presupuestosKeys.conceptos.tree(rootId), queryFn: () => conceptosApi.getTree(rootId), }); } export function useCreateConcepto() { const queryClient = useQueryClient(); return useMutation({ mutationFn: (data: CreateConceptoDto) => conceptosApi.create(data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: presupuestosKeys.conceptos.all }); toast.success('Concepto creado exitosamente'); }, onError: handleError, }); } export function useUpdateConcepto() { const queryClient = useQueryClient(); return useMutation({ mutationFn: ({ id, data }: { id: string; data: UpdateConceptoDto }) => conceptosApi.update(id, data), onSuccess: (_, { id }) => { queryClient.invalidateQueries({ queryKey: presupuestosKeys.conceptos.all }); queryClient.invalidateQueries({ queryKey: presupuestosKeys.conceptos.detail(id) }); toast.success('Concepto actualizado'); }, onError: handleError, }); } export function useDeleteConcepto() { const queryClient = useQueryClient(); return useMutation({ mutationFn: (id: string) => conceptosApi.delete(id), onSuccess: () => { queryClient.invalidateQueries({ queryKey: presupuestosKeys.conceptos.all }); toast.success('Concepto eliminado'); }, onError: handleError, }); } // ==================== PRESUPUESTOS ==================== export function usePresupuestos(filters?: PresupuestoFilters) { return useQuery({ queryKey: presupuestosKeys.presupuestos.list(filters), queryFn: () => presupuestosApi.list(filters), }); } export function usePresupuesto(id: string) { return useQuery({ queryKey: presupuestosKeys.presupuestos.detail(id), queryFn: () => presupuestosApi.get(id), enabled: !!id, }); } export function useCreatePresupuesto() { const queryClient = useQueryClient(); return useMutation({ mutationFn: (data: CreatePresupuestoDto) => presupuestosApi.create(data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: presupuestosKeys.presupuestos.all }); toast.success('Presupuesto creado exitosamente'); }, onError: handleError, }); } export function useUpdatePresupuesto() { const queryClient = useQueryClient(); return useMutation({ mutationFn: ({ id, data }: { id: string; data: UpdatePresupuestoDto }) => presupuestosApi.update(id, data), onSuccess: (_, { id }) => { queryClient.invalidateQueries({ queryKey: presupuestosKeys.presupuestos.all }); queryClient.invalidateQueries({ queryKey: presupuestosKeys.presupuestos.detail(id) }); toast.success('Presupuesto actualizado'); }, onError: handleError, }); } export function useDeletePresupuesto() { const queryClient = useQueryClient(); return useMutation({ mutationFn: (id: string) => presupuestosApi.delete(id), onSuccess: () => { queryClient.invalidateQueries({ queryKey: presupuestosKeys.presupuestos.all }); toast.success('Presupuesto eliminado'); }, onError: handleError, }); } export function useApprovePresupuesto() { const queryClient = useQueryClient(); return useMutation({ mutationFn: (id: string) => presupuestosApi.approve(id), onSuccess: (_, id) => { queryClient.invalidateQueries({ queryKey: presupuestosKeys.presupuestos.all }); queryClient.invalidateQueries({ queryKey: presupuestosKeys.presupuestos.detail(id) }); toast.success('Presupuesto aprobado'); }, onError: handleError, }); } export function useDuplicatePresupuesto() { const queryClient = useQueryClient(); return useMutation({ mutationFn: (id: string) => presupuestosApi.duplicate(id), onSuccess: () => { queryClient.invalidateQueries({ queryKey: presupuestosKeys.presupuestos.all }); toast.success('Presupuesto duplicado exitosamente'); }, onError: handleError, }); } export function usePresupuestoWithPartidas(id: string) { return useQuery({ queryKey: [...presupuestosKeys.presupuestos.detail(id), 'partidas'], queryFn: () => presupuestosApi.getWithPartidas(id), enabled: !!id, }); } export function usePresupuestoVersions(id: string) { return useQuery({ queryKey: [...presupuestosKeys.presupuestos.detail(id), 'versions'], queryFn: () => presupuestosApi.getVersions(id), enabled: !!id, }); } export function useCreatePresupuestoVersion() { const queryClient = useQueryClient(); return useMutation({ mutationFn: ({ id, notas }: { id: string; notas?: string }) => presupuestosApi.createVersion(id, notas), onSuccess: (_, { id }) => { queryClient.invalidateQueries({ queryKey: presupuestosKeys.presupuestos.all }); queryClient.invalidateQueries({ queryKey: presupuestosKeys.presupuestos.detail(id) }); toast.success('Nueva version creada exitosamente'); }, onError: handleError, }); } export function useRejectPresupuesto() { const queryClient = useQueryClient(); return useMutation({ mutationFn: ({ id, data }: { id: string; data: RejectPresupuestoDto }) => presupuestosApi.reject(id, data), onSuccess: (_, { id }) => { queryClient.invalidateQueries({ queryKey: presupuestosKeys.presupuestos.all }); queryClient.invalidateQueries({ queryKey: presupuestosKeys.presupuestos.detail(id) }); toast.success('Presupuesto rechazado'); }, onError: handleError, }); } export function useAddPresupuestoPartida() { const queryClient = useQueryClient(); return useMutation({ mutationFn: ({ presupuestoId, data }: { presupuestoId: string; data: CreatePresupuestoPartidaDto }) => presupuestosApi.addPartida(presupuestoId, data), onSuccess: (_, { presupuestoId }) => { queryClient.invalidateQueries({ queryKey: presupuestosKeys.presupuestos.detail(presupuestoId) }); toast.success('Partida agregada al presupuesto'); }, onError: handleError, }); } export function useUpdatePresupuestoPartida() { const queryClient = useQueryClient(); return useMutation({ mutationFn: ({ presupuestoId, partidaId, data, }: { presupuestoId: string; partidaId: string; data: UpdatePresupuestoPartidaDto; }) => presupuestosApi.updatePartida(presupuestoId, partidaId, data), onSuccess: (_, { presupuestoId }) => { queryClient.invalidateQueries({ queryKey: presupuestosKeys.presupuestos.detail(presupuestoId) }); toast.success('Partida actualizada'); }, onError: handleError, }); } export function useDeletePresupuestoPartida() { const queryClient = useQueryClient(); return useMutation({ mutationFn: ({ presupuestoId, partidaId }: { presupuestoId: string; partidaId: string }) => presupuestosApi.deletePartida(presupuestoId, partidaId), onSuccess: (_, { presupuestoId }) => { queryClient.invalidateQueries({ queryKey: presupuestosKeys.presupuestos.detail(presupuestoId) }); toast.success('Partida eliminada del presupuesto'); }, onError: handleError, }); } export function useExportPresupuestoPdf() { return useMutation({ mutationFn: (id: string) => presupuestosApi.exportPdf(id), onSuccess: (blob) => { const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `presupuesto-${Date.now()}.pdf`; document.body.appendChild(a); a.click(); window.URL.revokeObjectURL(url); document.body.removeChild(a); toast.success('PDF exportado exitosamente'); }, onError: handleError, }); } export function useExportPresupuestoExcel() { return useMutation({ mutationFn: (id: string) => presupuestosApi.exportExcel(id), onSuccess: (blob) => { const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `presupuesto-${Date.now()}.xlsx`; document.body.appendChild(a); a.click(); window.URL.revokeObjectURL(url); document.body.removeChild(a); toast.success('Excel exportado exitosamente'); }, onError: handleError, }); } // ==================== ESTIMACIONES ==================== export function useEstimaciones(filters?: EstimacionFilters) { return useQuery({ queryKey: presupuestosKeys.estimaciones.list(filters), queryFn: () => estimacionesApi.list(filters), }); } export function useEstimacion(id: string) { return useQuery({ queryKey: presupuestosKeys.estimaciones.detail(id), queryFn: () => estimacionesApi.get(id), enabled: !!id, }); } export function useCreateEstimacion() { const queryClient = useQueryClient(); return useMutation({ mutationFn: (data: CreateEstimacionDto) => estimacionesApi.create(data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: presupuestosKeys.estimaciones.all }); toast.success('Estimacion creada exitosamente'); }, onError: handleError, }); } export function useUpdateEstimacion() { const queryClient = useQueryClient(); return useMutation({ mutationFn: ({ id, data }: { id: string; data: UpdateEstimacionDto }) => estimacionesApi.update(id, data), onSuccess: (_, { id }) => { queryClient.invalidateQueries({ queryKey: presupuestosKeys.estimaciones.all }); queryClient.invalidateQueries({ queryKey: presupuestosKeys.estimaciones.detail(id) }); toast.success('Estimacion actualizada'); }, onError: handleError, }); } export function useDeleteEstimacion() { const queryClient = useQueryClient(); return useMutation({ mutationFn: (id: string) => estimacionesApi.delete(id), onSuccess: () => { queryClient.invalidateQueries({ queryKey: presupuestosKeys.estimaciones.all }); toast.success('Estimacion eliminada'); }, onError: handleError, }); } // ==================== ESTIMACIONES WORKFLOW ==================== export function useSubmitEstimacion() { const queryClient = useQueryClient(); return useMutation({ mutationFn: (id: string) => estimacionesApi.submit(id), onSuccess: (_, id) => { queryClient.invalidateQueries({ queryKey: presupuestosKeys.estimaciones.all }); queryClient.invalidateQueries({ queryKey: presupuestosKeys.estimaciones.detail(id) }); toast.success('Estimacion enviada a revision'); }, onError: handleError, }); } export function useApproveEstimacion() { const queryClient = useQueryClient(); return useMutation({ mutationFn: ({ id, montoAprobado }: { id: string; montoAprobado?: number }) => estimacionesApi.approve(id, montoAprobado), onSuccess: (_, { id }) => { queryClient.invalidateQueries({ queryKey: presupuestosKeys.estimaciones.all }); queryClient.invalidateQueries({ queryKey: presupuestosKeys.estimaciones.detail(id) }); toast.success('Estimacion aprobada'); }, onError: handleError, }); } export function useRejectEstimacion() { const queryClient = useQueryClient(); return useMutation({ mutationFn: ({ id, motivo }: { id: string; motivo: string }) => estimacionesApi.reject(id, motivo), onSuccess: (_, { id }) => { queryClient.invalidateQueries({ queryKey: presupuestosKeys.estimaciones.all }); queryClient.invalidateQueries({ queryKey: presupuestosKeys.estimaciones.detail(id) }); toast.success('Estimacion rechazada'); }, onError: handleError, }); } export function useMarkEstimacionInvoiced() { const queryClient = useQueryClient(); return useMutation({ mutationFn: ({ id, montoFacturado }: { id: string; montoFacturado: number }) => estimacionesApi.markAsInvoiced(id, montoFacturado), onSuccess: (_, { id }) => { queryClient.invalidateQueries({ queryKey: presupuestosKeys.estimaciones.all }); queryClient.invalidateQueries({ queryKey: presupuestosKeys.estimaciones.detail(id) }); toast.success('Estimacion marcada como facturada'); }, onError: handleError, }); } export function useMarkEstimacionPaid() { const queryClient = useQueryClient(); return useMutation({ mutationFn: ({ id, montoCobrado }: { id: string; montoCobrado: number }) => estimacionesApi.markAsPaid(id, montoCobrado), onSuccess: (_, { id }) => { queryClient.invalidateQueries({ queryKey: presupuestosKeys.estimaciones.all }); queryClient.invalidateQueries({ queryKey: presupuestosKeys.estimaciones.detail(id) }); toast.success('Estimacion marcada como cobrada'); }, onError: handleError, }); } // ==================== ESTIMACIONES PARTIDAS ==================== export function useAddEstimacionPartida() { const queryClient = useQueryClient(); return useMutation({ mutationFn: ({ estimacionId, data }: { estimacionId: string; data: CreateEstimacionPartidaDto }) => estimacionesApi.addPartida(estimacionId, data), onSuccess: (_, { estimacionId }) => { queryClient.invalidateQueries({ queryKey: presupuestosKeys.estimaciones.detail(estimacionId) }); toast.success('Partida agregada a la estimacion'); }, onError: handleError, }); } export function useUpdateEstimacionPartida() { const queryClient = useQueryClient(); return useMutation({ mutationFn: ({ estimacionId, partidaId, data, }: { estimacionId: string; partidaId: string; data: UpdateEstimacionPartidaDto; }) => estimacionesApi.updatePartida(estimacionId, partidaId, data), onSuccess: (_, { estimacionId }) => { queryClient.invalidateQueries({ queryKey: presupuestosKeys.estimaciones.detail(estimacionId) }); toast.success('Partida actualizada'); }, onError: handleError, }); } export function useRemoveEstimacionPartida() { const queryClient = useQueryClient(); return useMutation({ mutationFn: ({ estimacionId, partidaId }: { estimacionId: string; partidaId: string }) => estimacionesApi.removePartida(estimacionId, partidaId), onSuccess: (_, { estimacionId }) => { queryClient.invalidateQueries({ queryKey: presupuestosKeys.estimaciones.detail(estimacionId) }); toast.success('Partida eliminada de la estimacion'); }, onError: handleError, }); } // ==================== ESTIMACIONES GENERADORES ==================== export function useAddGenerador() { const queryClient = useQueryClient(); return useMutation({ mutationFn: ({ estimacionId, partidaId, data, }: { estimacionId: string; partidaId: string; data: CreateGeneradorDto; }) => estimacionesApi.addGenerador(estimacionId, partidaId, data), onSuccess: (_, { estimacionId }) => { queryClient.invalidateQueries({ queryKey: presupuestosKeys.estimaciones.detail(estimacionId) }); toast.success('Generador agregado'); }, onError: handleError, }); } export function useUpdateGenerador() { const queryClient = useQueryClient(); return useMutation({ mutationFn: ({ estimacionId, partidaId, generadorId, data, }: { estimacionId: string; partidaId: string; generadorId: string; data: UpdateGeneradorDto; }) => estimacionesApi.updateGenerador(estimacionId, partidaId, generadorId, data), onSuccess: (_, { estimacionId }) => { queryClient.invalidateQueries({ queryKey: presupuestosKeys.estimaciones.detail(estimacionId) }); toast.success('Generador actualizado'); }, onError: handleError, }); } export function useRemoveGenerador() { const queryClient = useQueryClient(); return useMutation({ mutationFn: ({ estimacionId, partidaId, generadorId, }: { estimacionId: string; partidaId: string; generadorId: string; }) => estimacionesApi.removeGenerador(estimacionId, partidaId, generadorId), onSuccess: (_, { estimacionId }) => { queryClient.invalidateQueries({ queryKey: presupuestosKeys.estimaciones.detail(estimacionId) }); toast.success('Generador eliminado'); }, onError: handleError, }); }