- Create financial types (Account, Journal, JournalEntry, Invoice, Payment) - Create financial API client with full CRUD operations - Create comprehensive hooks (useAccounts, useJournals, useJournalEntries, etc.) - Create AccountsPage with account type filtering and stats - Create JournalEntriesPage with journal filtering and post/cancel actions - Create InvoicesPage with customer/supplier filtering and validation MGN-010 Financial frontend implementation Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
750 lines
20 KiB
TypeScript
750 lines
20 KiB
TypeScript
import { useState, useEffect, useCallback } from 'react';
|
|
import { financialApi } from '../api/financial.api';
|
|
import type {
|
|
Account,
|
|
AccountFilters,
|
|
CreateAccountDto,
|
|
UpdateAccountDto,
|
|
AccountType,
|
|
Journal,
|
|
JournalFilters,
|
|
CreateJournalDto,
|
|
UpdateJournalDto,
|
|
JournalEntry,
|
|
JournalEntryFilters,
|
|
CreateJournalEntryDto,
|
|
UpdateJournalEntryDto,
|
|
FinancialInvoice,
|
|
InvoiceFilters,
|
|
CreateInvoiceDto,
|
|
UpdateInvoiceDto,
|
|
Payment,
|
|
PaymentFilters,
|
|
CreatePaymentDto,
|
|
UpdatePaymentDto,
|
|
} from '../types';
|
|
|
|
// ==================== Account Types Hook ====================
|
|
|
|
export function useAccountTypes() {
|
|
const [accountTypes, setAccountTypes] = useState<AccountType[]>([]);
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
const fetchAccountTypes = useCallback(async () => {
|
|
setIsLoading(true);
|
|
setError(null);
|
|
try {
|
|
const data = await financialApi.getAccountTypes();
|
|
setAccountTypes(data);
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : 'Error al cargar tipos de cuenta');
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
fetchAccountTypes();
|
|
}, [fetchAccountTypes]);
|
|
|
|
return {
|
|
accountTypes,
|
|
isLoading,
|
|
error,
|
|
refresh: fetchAccountTypes,
|
|
};
|
|
}
|
|
|
|
// ==================== Accounts Hook ====================
|
|
|
|
export interface UseAccountsOptions extends AccountFilters {
|
|
autoFetch?: boolean;
|
|
}
|
|
|
|
export function useAccounts(options: UseAccountsOptions = {}) {
|
|
const { autoFetch = true, ...filters } = options;
|
|
const [accounts, setAccounts] = useState<Account[]>([]);
|
|
const [total, setTotal] = useState(0);
|
|
const [page, setPage] = useState(filters.page || 1);
|
|
const [totalPages, setTotalPages] = useState(1);
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
const fetchAccounts = useCallback(async () => {
|
|
setIsLoading(true);
|
|
setError(null);
|
|
try {
|
|
const response = await financialApi.getAccounts({ ...filters, page });
|
|
setAccounts(response.data);
|
|
setTotal(response.meta.total);
|
|
setTotalPages(response.meta.totalPages);
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : 'Error al cargar cuentas');
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
}, [filters.companyId, filters.accountTypeId, filters.parentId, filters.isReconcilable, filters.isDeprecated, filters.search, filters.limit, filters.sortBy, filters.sortOrder, page]);
|
|
|
|
useEffect(() => {
|
|
if (autoFetch) {
|
|
fetchAccounts();
|
|
}
|
|
}, [fetchAccounts, autoFetch]);
|
|
|
|
const createAccount = async (data: CreateAccountDto) => {
|
|
setIsLoading(true);
|
|
try {
|
|
const newAccount = await financialApi.createAccount(data);
|
|
await fetchAccounts();
|
|
return newAccount;
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : 'Error al crear cuenta');
|
|
throw err;
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
const updateAccount = async (id: string, data: UpdateAccountDto) => {
|
|
setIsLoading(true);
|
|
try {
|
|
const updated = await financialApi.updateAccount(id, data);
|
|
await fetchAccounts();
|
|
return updated;
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : 'Error al actualizar cuenta');
|
|
throw err;
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
const deleteAccount = async (id: string) => {
|
|
setIsLoading(true);
|
|
try {
|
|
await financialApi.deleteAccount(id);
|
|
await fetchAccounts();
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : 'Error al eliminar cuenta');
|
|
throw err;
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
return {
|
|
accounts,
|
|
total,
|
|
page,
|
|
totalPages,
|
|
isLoading,
|
|
error,
|
|
setPage,
|
|
refresh: fetchAccounts,
|
|
createAccount,
|
|
updateAccount,
|
|
deleteAccount,
|
|
};
|
|
}
|
|
|
|
// ==================== Single Account Hook ====================
|
|
|
|
export function useAccount(accountId: string | null) {
|
|
const [account, setAccount] = useState<Account | null>(null);
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
const fetchAccount = useCallback(async () => {
|
|
if (!accountId) {
|
|
setAccount(null);
|
|
return;
|
|
}
|
|
|
|
setIsLoading(true);
|
|
setError(null);
|
|
try {
|
|
const data = await financialApi.getAccountById(accountId);
|
|
setAccount(data);
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : 'Error al cargar cuenta');
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
}, [accountId]);
|
|
|
|
useEffect(() => {
|
|
fetchAccount();
|
|
}, [fetchAccount]);
|
|
|
|
return {
|
|
account,
|
|
isLoading,
|
|
error,
|
|
refresh: fetchAccount,
|
|
};
|
|
}
|
|
|
|
// ==================== Journals Hook ====================
|
|
|
|
export interface UseJournalsOptions extends JournalFilters {
|
|
autoFetch?: boolean;
|
|
}
|
|
|
|
export function useJournals(options: UseJournalsOptions = {}) {
|
|
const { autoFetch = true, ...filters } = options;
|
|
const [journals, setJournals] = useState<Journal[]>([]);
|
|
const [total, setTotal] = useState(0);
|
|
const [page, setPage] = useState(filters.page || 1);
|
|
const [totalPages, setTotalPages] = useState(1);
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
const fetchJournals = useCallback(async () => {
|
|
setIsLoading(true);
|
|
setError(null);
|
|
try {
|
|
const response = await financialApi.getJournals({ ...filters, page });
|
|
setJournals(response.data);
|
|
setTotal(response.meta.total);
|
|
setTotalPages(response.meta.totalPages);
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : 'Error al cargar diarios');
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
}, [filters.companyId, filters.journalType, filters.active, filters.search, filters.limit, page]);
|
|
|
|
useEffect(() => {
|
|
if (autoFetch) {
|
|
fetchJournals();
|
|
}
|
|
}, [fetchJournals, autoFetch]);
|
|
|
|
const createJournal = async (data: CreateJournalDto) => {
|
|
setIsLoading(true);
|
|
try {
|
|
const newJournal = await financialApi.createJournal(data);
|
|
await fetchJournals();
|
|
return newJournal;
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : 'Error al crear diario');
|
|
throw err;
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
const updateJournal = async (id: string, data: UpdateJournalDto) => {
|
|
setIsLoading(true);
|
|
try {
|
|
const updated = await financialApi.updateJournal(id, data);
|
|
await fetchJournals();
|
|
return updated;
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : 'Error al actualizar diario');
|
|
throw err;
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
const deleteJournal = async (id: string) => {
|
|
setIsLoading(true);
|
|
try {
|
|
await financialApi.deleteJournal(id);
|
|
await fetchJournals();
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : 'Error al eliminar diario');
|
|
throw err;
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
return {
|
|
journals,
|
|
total,
|
|
page,
|
|
totalPages,
|
|
isLoading,
|
|
error,
|
|
setPage,
|
|
refresh: fetchJournals,
|
|
createJournal,
|
|
updateJournal,
|
|
deleteJournal,
|
|
};
|
|
}
|
|
|
|
// ==================== Journal Entries Hook ====================
|
|
|
|
export interface UseJournalEntriesOptions extends JournalEntryFilters {
|
|
autoFetch?: boolean;
|
|
}
|
|
|
|
export function useJournalEntries(options: UseJournalEntriesOptions = {}) {
|
|
const { autoFetch = true, ...filters } = options;
|
|
const [entries, setEntries] = useState<JournalEntry[]>([]);
|
|
const [total, setTotal] = useState(0);
|
|
const [page, setPage] = useState(filters.page || 1);
|
|
const [totalPages, setTotalPages] = useState(1);
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
const fetchEntries = useCallback(async () => {
|
|
setIsLoading(true);
|
|
setError(null);
|
|
try {
|
|
const response = await financialApi.getEntries({ ...filters, page });
|
|
setEntries(response.data);
|
|
setTotal(response.meta.total);
|
|
setTotalPages(response.meta.totalPages);
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : 'Error al cargar asientos');
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
}, [filters.companyId, filters.journalId, filters.status, filters.dateFrom, filters.dateTo, filters.search, filters.limit, filters.sortBy, filters.sortOrder, page]);
|
|
|
|
useEffect(() => {
|
|
if (autoFetch) {
|
|
fetchEntries();
|
|
}
|
|
}, [fetchEntries, autoFetch]);
|
|
|
|
const createEntry = async (data: CreateJournalEntryDto) => {
|
|
setIsLoading(true);
|
|
try {
|
|
const newEntry = await financialApi.createEntry(data);
|
|
await fetchEntries();
|
|
return newEntry;
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : 'Error al crear asiento');
|
|
throw err;
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
const updateEntry = async (id: string, data: UpdateJournalEntryDto) => {
|
|
setIsLoading(true);
|
|
try {
|
|
const updated = await financialApi.updateEntry(id, data);
|
|
await fetchEntries();
|
|
return updated;
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : 'Error al actualizar asiento');
|
|
throw err;
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
const deleteEntry = async (id: string) => {
|
|
setIsLoading(true);
|
|
try {
|
|
await financialApi.deleteEntry(id);
|
|
await fetchEntries();
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : 'Error al eliminar asiento');
|
|
throw err;
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
const postEntry = async (id: string) => {
|
|
setIsLoading(true);
|
|
try {
|
|
await financialApi.postEntry(id);
|
|
await fetchEntries();
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : 'Error al publicar asiento');
|
|
throw err;
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
const cancelEntry = async (id: string) => {
|
|
setIsLoading(true);
|
|
try {
|
|
await financialApi.cancelEntry(id);
|
|
await fetchEntries();
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : 'Error al cancelar asiento');
|
|
throw err;
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
return {
|
|
entries,
|
|
total,
|
|
page,
|
|
totalPages,
|
|
isLoading,
|
|
error,
|
|
setPage,
|
|
refresh: fetchEntries,
|
|
createEntry,
|
|
updateEntry,
|
|
deleteEntry,
|
|
postEntry,
|
|
cancelEntry,
|
|
};
|
|
}
|
|
|
|
// ==================== Single Journal Entry Hook ====================
|
|
|
|
export function useJournalEntry(entryId: string | null) {
|
|
const [entry, setEntry] = useState<JournalEntry | null>(null);
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
const fetchEntry = useCallback(async () => {
|
|
if (!entryId) {
|
|
setEntry(null);
|
|
return;
|
|
}
|
|
|
|
setIsLoading(true);
|
|
setError(null);
|
|
try {
|
|
const data = await financialApi.getEntryById(entryId);
|
|
setEntry(data);
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : 'Error al cargar asiento');
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
}, [entryId]);
|
|
|
|
useEffect(() => {
|
|
fetchEntry();
|
|
}, [fetchEntry]);
|
|
|
|
return {
|
|
entry,
|
|
isLoading,
|
|
error,
|
|
refresh: fetchEntry,
|
|
};
|
|
}
|
|
|
|
// ==================== Invoices Hook ====================
|
|
|
|
export interface UseInvoicesOptions extends InvoiceFilters {
|
|
autoFetch?: boolean;
|
|
}
|
|
|
|
export function useInvoices(options: UseInvoicesOptions = {}) {
|
|
const { autoFetch = true, ...filters } = options;
|
|
const [invoices, setInvoices] = useState<FinancialInvoice[]>([]);
|
|
const [total, setTotal] = useState(0);
|
|
const [page, setPage] = useState(filters.page || 1);
|
|
const [totalPages, setTotalPages] = useState(1);
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
const fetchInvoices = useCallback(async () => {
|
|
setIsLoading(true);
|
|
setError(null);
|
|
try {
|
|
const response = await financialApi.getInvoices({ ...filters, page });
|
|
setInvoices(response.data);
|
|
setTotal(response.meta.total);
|
|
setTotalPages(response.meta.totalPages);
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : 'Error al cargar facturas');
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
}, [filters.companyId, filters.partnerId, filters.invoiceType, filters.status, filters.dateFrom, filters.dateTo, filters.search, filters.limit, filters.sortBy, filters.sortOrder, page]);
|
|
|
|
useEffect(() => {
|
|
if (autoFetch) {
|
|
fetchInvoices();
|
|
}
|
|
}, [fetchInvoices, autoFetch]);
|
|
|
|
const createInvoice = async (data: CreateInvoiceDto) => {
|
|
setIsLoading(true);
|
|
try {
|
|
const newInvoice = await financialApi.createInvoice(data);
|
|
await fetchInvoices();
|
|
return newInvoice;
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : 'Error al crear factura');
|
|
throw err;
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
const updateInvoice = async (id: string, data: UpdateInvoiceDto) => {
|
|
setIsLoading(true);
|
|
try {
|
|
const updated = await financialApi.updateInvoice(id, data);
|
|
await fetchInvoices();
|
|
return updated;
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : 'Error al actualizar factura');
|
|
throw err;
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
const deleteInvoice = async (id: string) => {
|
|
setIsLoading(true);
|
|
try {
|
|
await financialApi.deleteInvoice(id);
|
|
await fetchInvoices();
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : 'Error al eliminar factura');
|
|
throw err;
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
const validateInvoice = async (id: string) => {
|
|
setIsLoading(true);
|
|
try {
|
|
await financialApi.validateInvoice(id);
|
|
await fetchInvoices();
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : 'Error al validar factura');
|
|
throw err;
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
const cancelInvoice = async (id: string) => {
|
|
setIsLoading(true);
|
|
try {
|
|
await financialApi.cancelInvoice(id);
|
|
await fetchInvoices();
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : 'Error al cancelar factura');
|
|
throw err;
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
return {
|
|
invoices,
|
|
total,
|
|
page,
|
|
totalPages,
|
|
isLoading,
|
|
error,
|
|
setPage,
|
|
refresh: fetchInvoices,
|
|
createInvoice,
|
|
updateInvoice,
|
|
deleteInvoice,
|
|
validateInvoice,
|
|
cancelInvoice,
|
|
};
|
|
}
|
|
|
|
// ==================== Single Invoice Hook ====================
|
|
|
|
export function useInvoice(invoiceId: string | null) {
|
|
const [invoice, setInvoice] = useState<FinancialInvoice | null>(null);
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
const fetchInvoice = useCallback(async () => {
|
|
if (!invoiceId) {
|
|
setInvoice(null);
|
|
return;
|
|
}
|
|
|
|
setIsLoading(true);
|
|
setError(null);
|
|
try {
|
|
const data = await financialApi.getInvoiceById(invoiceId);
|
|
setInvoice(data);
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : 'Error al cargar factura');
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
}, [invoiceId]);
|
|
|
|
useEffect(() => {
|
|
fetchInvoice();
|
|
}, [fetchInvoice]);
|
|
|
|
return {
|
|
invoice,
|
|
isLoading,
|
|
error,
|
|
refresh: fetchInvoice,
|
|
};
|
|
}
|
|
|
|
// ==================== Payments Hook ====================
|
|
|
|
export interface UsePaymentsOptions extends PaymentFilters {
|
|
autoFetch?: boolean;
|
|
}
|
|
|
|
export function usePayments(options: UsePaymentsOptions = {}) {
|
|
const { autoFetch = true, ...filters } = options;
|
|
const [payments, setPayments] = useState<Payment[]>([]);
|
|
const [total, setTotal] = useState(0);
|
|
const [page, setPage] = useState(filters.page || 1);
|
|
const [totalPages, setTotalPages] = useState(1);
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
const fetchPayments = useCallback(async () => {
|
|
setIsLoading(true);
|
|
setError(null);
|
|
try {
|
|
const response = await financialApi.getPayments({ ...filters, page });
|
|
setPayments(response.data);
|
|
setTotal(response.meta.total);
|
|
setTotalPages(response.meta.totalPages);
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : 'Error al cargar pagos');
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
}, [filters.companyId, filters.partnerId, filters.paymentType, filters.paymentMethod, filters.status, filters.dateFrom, filters.dateTo, filters.search, filters.limit, filters.sortBy, filters.sortOrder, page]);
|
|
|
|
useEffect(() => {
|
|
if (autoFetch) {
|
|
fetchPayments();
|
|
}
|
|
}, [fetchPayments, autoFetch]);
|
|
|
|
const createPayment = async (data: CreatePaymentDto) => {
|
|
setIsLoading(true);
|
|
try {
|
|
const newPayment = await financialApi.createPayment(data);
|
|
await fetchPayments();
|
|
return newPayment;
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : 'Error al crear pago');
|
|
throw err;
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
const updatePayment = async (id: string, data: UpdatePaymentDto) => {
|
|
setIsLoading(true);
|
|
try {
|
|
const updated = await financialApi.updatePayment(id, data);
|
|
await fetchPayments();
|
|
return updated;
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : 'Error al actualizar pago');
|
|
throw err;
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
const deletePayment = async (id: string) => {
|
|
setIsLoading(true);
|
|
try {
|
|
await financialApi.deletePayment(id);
|
|
await fetchPayments();
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : 'Error al eliminar pago');
|
|
throw err;
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
const postPayment = async (id: string) => {
|
|
setIsLoading(true);
|
|
try {
|
|
await financialApi.postPayment(id);
|
|
await fetchPayments();
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : 'Error al publicar pago');
|
|
throw err;
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
const cancelPayment = async (id: string) => {
|
|
setIsLoading(true);
|
|
try {
|
|
await financialApi.cancelPayment(id);
|
|
await fetchPayments();
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : 'Error al cancelar pago');
|
|
throw err;
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
return {
|
|
payments,
|
|
total,
|
|
page,
|
|
totalPages,
|
|
isLoading,
|
|
error,
|
|
setPage,
|
|
refresh: fetchPayments,
|
|
createPayment,
|
|
updatePayment,
|
|
deletePayment,
|
|
postPayment,
|
|
cancelPayment,
|
|
};
|
|
}
|
|
|
|
// ==================== Single Payment Hook ====================
|
|
|
|
export function usePayment(paymentId: string | null) {
|
|
const [payment, setPayment] = useState<Payment | null>(null);
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
const fetchPayment = useCallback(async () => {
|
|
if (!paymentId) {
|
|
setPayment(null);
|
|
return;
|
|
}
|
|
|
|
setIsLoading(true);
|
|
setError(null);
|
|
try {
|
|
const data = await financialApi.getPaymentById(paymentId);
|
|
setPayment(data);
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : 'Error al cargar pago');
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
}, [paymentId]);
|
|
|
|
useEffect(() => {
|
|
fetchPayment();
|
|
}, [fetchPayment]);
|
|
|
|
return {
|
|
payment,
|
|
isLoading,
|
|
error,
|
|
refresh: fetchPayment,
|
|
};
|
|
}
|