Fix: Captura y Muestra del Error Por Recibo Duplicado
All checks were successful
Optimized Build and Deploy / remote-build-and-deploy (push) Successful in 2m2s

This commit is contained in:
2025-11-05 13:52:14 -03:00
parent e8215f8586
commit 8c7278ceae
5 changed files with 157 additions and 135 deletions

View File

@@ -16,7 +16,7 @@ namespace GestionIntegral.Api.Data.Repositories.Contables
Task<PagoDistribuidor?> CreateAsync(PagoDistribuidor nuevoPago, int idUsuario, IDbTransaction transaction);
Task<bool> UpdateAsync(PagoDistribuidor pagoAActualizar, int idUsuario, IDbTransaction transaction);
Task<bool> DeleteAsync(int idPago, int idUsuario, IDbTransaction transaction);
Task<bool> ExistsByReciboAndTipoMovimientoAsync(int recibo, string tipoMovimiento, int? excludeIdPago = null);
Task<PagoDistribuidor?> GetByReciboAndTipoMovimientoAsync(int recibo, string tipoMovimiento, int? excludeIdPago = null);
Task<IEnumerable<(PagoDistribuidorHistorico Historial, string NombreUsuarioModifico)>> GetHistorialAsync(
DateTime? fechaDesde, DateTime? fechaHasta,
int? idUsuarioModifico, string? tipoModificacion,

View File

@@ -70,9 +70,10 @@ namespace GestionIntegral.Api.Data.Repositories.Contables
}
}
public async Task<bool> ExistsByReciboAndTipoMovimientoAsync(int recibo, string tipoMovimiento, int? excludeIdPago = null)
public async Task<PagoDistribuidor?> GetByReciboAndTipoMovimientoAsync(int recibo, string tipoMovimiento, int? excludeIdPago = null)
{
var sqlBuilder = new StringBuilder("SELECT COUNT(1) FROM dbo.cue_PagosDistribuidor WHERE Recibo = @ReciboParam AND TipoMovimiento = @TipoMovParam");
var sqlBuilder = new StringBuilder(SelectQueryBase()); // Reutiliza la consulta base
sqlBuilder.Append(" WHERE Recibo = @ReciboParam AND TipoMovimiento = @TipoMovParam");
var parameters = new DynamicParameters();
parameters.Add("ReciboParam", recibo);
parameters.Add("TipoMovParam", tipoMovimiento);
@@ -85,12 +86,12 @@ namespace GestionIntegral.Api.Data.Repositories.Contables
try
{
using var connection = _cf.CreateConnection();
return await connection.ExecuteScalarAsync<bool>(sqlBuilder.ToString(), parameters);
return await connection.QuerySingleOrDefaultAsync<PagoDistribuidor>(sqlBuilder.ToString(), parameters);
}
catch (Exception ex)
{
_log.LogError(ex, "Error en ExistsByReciboAndTipoMovimientoAsync. Recibo: {Recibo}, Tipo: {Tipo}", recibo, tipoMovimiento);
return true; // Asumir que existe en caso de error para prevenir duplicados
_log.LogError(ex, "Error en GetByReciboAndTipoMovimientoAsync. Recibo: {Recibo}, Tipo: {Tipo}", recibo, tipoMovimiento);
throw; // Relanzar para que el servicio lo maneje
}
}

View File

@@ -93,8 +93,18 @@ namespace GestionIntegral.Api.Services.Contables
return (null, "Tipo de pago no válido.");
if (await _empresaRepo.GetByIdAsync(createDto.IdEmpresa) == null)
return (null, "Empresa no válida.");
if (await _pagoRepo.ExistsByReciboAndTipoMovimientoAsync(createDto.Recibo, createDto.TipoMovimiento))
return (null, $"Ya existe un pago '{createDto.TipoMovimiento}' con el número de recibo '{createDto.Recibo}'.");
var pagoExistente = await _pagoRepo.GetByReciboAndTipoMovimientoAsync(createDto.Recibo, createDto.TipoMovimiento);
if (pagoExistente != null)
{
// Si encontramos un duplicado, obtenemos los detalles para el mensaje de error
var distribuidor = await _distribuidorRepo.GetByIdSimpleAsync(pagoExistente.IdDistribuidor);
var empresa = await _empresaRepo.GetByIdAsync(pagoExistente.IdEmpresa);
string mensajeError = $"El recibo N° {createDto.Recibo} ya fue registrado como '{pagoExistente.TipoMovimiento}' el {pagoExistente.Fecha:dd/MM/yyyy} " +
$"para el distribuidor '{distribuidor?.Nombre ?? "Desconocido"}' en la empresa '{empresa?.Nombre ?? "Desconocida"}'.";
return (null, mensajeError);
}
var nuevoPago = new PagoDistribuidor
{
@@ -270,30 +280,30 @@ namespace GestionIntegral.Api.Services.Contables
}
}
}
public async Task<IEnumerable<PagoDistribuidorHistorialDto>> ObtenerHistorialAsync(
DateTime? fechaDesde, DateTime? fechaHasta,
int? idUsuarioModifico, string? tipoModificacion,
int? idPagoAfectado)
{
var historialData = await _pagoRepo.GetHistorialAsync(fechaDesde, fechaHasta, idUsuarioModifico, tipoModificacion, idPagoAfectado);
return historialData.Select(h => new PagoDistribuidorHistorialDto
{
Id_Pago = h.Historial.Id_Pago,
Id_Distribuidor = h.Historial.Id_Distribuidor,
Fecha = h.Historial.Fecha,
TipoMovimiento = h.Historial.TipoMovimiento,
Recibo = h.Historial.Recibo,
Monto = h.Historial.Monto,
Id_TipoPago = h.Historial.Id_TipoPago,
Detalle = h.Historial.Detalle,
Id_Empresa = h.Historial.Id_Empresa,
Id_Usuario = h.Historial.Id_Usuario,
NombreUsuarioModifico = h.NombreUsuarioModifico,
FechaMod = h.Historial.FechaMod,
TipoMod = h.Historial.TipoMod
}).ToList();
}
var historialData = await _pagoRepo.GetHistorialAsync(fechaDesde, fechaHasta, idUsuarioModifico, tipoModificacion, idPagoAfectado);
return historialData.Select(h => new PagoDistribuidorHistorialDto
{
Id_Pago = h.Historial.Id_Pago,
Id_Distribuidor = h.Historial.Id_Distribuidor,
Fecha = h.Historial.Fecha,
TipoMovimiento = h.Historial.TipoMovimiento,
Recibo = h.Historial.Recibo,
Monto = h.Historial.Monto,
Id_TipoPago = h.Historial.Id_TipoPago,
Detalle = h.Historial.Detalle,
Id_Empresa = h.Historial.Id_Empresa,
Id_Usuario = h.Historial.Id_Usuario,
NombreUsuarioModifico = h.NombreUsuarioModifico,
FechaMod = h.Historial.FechaMod,
TipoMod = h.Historial.TipoMod
}).ToList();
}
}
}

View File

@@ -64,6 +64,7 @@ const PagoDistribuidorFormModal: React.FC<PagoDistribuidorFormModalProps> = ({
const isEditing = Boolean(initialData);
useEffect(() => {
// Esta función se encarga de cargar los datos de los dropdowns.
const fetchDropdownData = async () => {
setLoadingDropdowns(true);
try {
@@ -126,14 +127,16 @@ const PagoDistribuidorFormModal: React.FC<PagoDistribuidorFormModalProps> = ({
setLoading(true);
try {
const montoNum = parseFloat(monto);
if (isEditing && initialData) {
const dataToSubmit: UpdatePagoDistribuidorDto = {
monto: montoNum,
idTipoPago: Number(idTipoPago),
detalle: detalle || undefined,
};
// << INICIO DE LA CORRECCIÓN >>
await onSubmit(dataToSubmit, initialData.idPago);
// << FIN DE LA CORRECCIÓN >>
} else {
const dataToSubmit: CreatePagoDistribuidorDto = {
idDistribuidor: Number(idDistribuidor),
@@ -147,7 +150,9 @@ const PagoDistribuidorFormModal: React.FC<PagoDistribuidorFormModalProps> = ({
};
await onSubmit(dataToSubmit);
}
onClose();
} catch (error: any) {
console.error("Error en submit de PagoDistribuidorFormModal:", error);
} finally {

View File

@@ -1,8 +1,8 @@
import React, { useState, useEffect, useCallback } from 'react';
import {
Box, Typography, TextField, Button, Paper, IconButton, Menu, MenuItem, Chip,
Table, TableBody, TableCell, TableContainer, TableHead, TableRow, TablePagination,
CircularProgress, Alert, FormControl, InputLabel, Select, Tooltip
Box, Typography, TextField, Button, Paper, IconButton, Menu, MenuItem, Chip,
Table, TableBody, TableCell, TableContainer, TableHead, TableRow, TablePagination,
CircularProgress, Alert, FormControl, InputLabel, Select, Tooltip
} from '@mui/material';
import AddIcon from '@mui/icons-material/Add';
import MoreVertIcon from '@mui/icons-material/MoreVert';
@@ -28,11 +28,11 @@ const GestionarPagosDistribuidorPage: React.FC = () => {
const [pagos, setPagos] = useState<PagoDistribuidorDto[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [apiErrorMessage, setApiErrorMessage] = useState<string | null>(null);
const [pageApiErrorMessage, setPageApiErrorMessage] = useState<string | null>(null);
const [modalApiErrorMessage, setModalApiErrorMessage] = useState<string | null>(null);
// Filtros
const [filtroFechaDesde, setFiltroFechaDesde] = useState<string>(new Date().toISOString().split('T')[0]); //useState('');
const [filtroFechaHasta, setFiltroFechaHasta] = useState<string>(new Date().toISOString().split('T')[0]);//useState('');
const [filtroFechaDesde, setFiltroFechaDesde] = useState<string>(new Date().toISOString().split('T')[0]);
const [filtroFechaHasta, setFiltroFechaHasta] = useState<string>(new Date().toISOString().split('T')[0]);
const [filtroIdDistribuidor, setFiltroIdDistribuidor] = useState<number | string>('');
const [filtroIdEmpresa, setFiltroIdEmpresa] = useState<number | string>('');
const [filtroTipoMov, setFiltroTipoMov] = useState<'Recibido' | 'Realizado' | ''>('');
@@ -50,7 +50,6 @@ const GestionarPagosDistribuidorPage: React.FC = () => {
const [selectedRow, setSelectedRow] = useState<PagoDistribuidorDto | null>(null);
const { tienePermiso, isSuperAdmin } = usePermissions();
// Permisos CP001 (Ver), CP002 (Crear), CP003 (Modificar), CP004 (Eliminar)
const puedeVer = isSuperAdmin || tienePermiso("CP001");
const puedeCrear = isSuperAdmin || tienePermiso("CP002");
const puedeModificar = isSuperAdmin || tienePermiso("CP003");
@@ -59,21 +58,27 @@ const GestionarPagosDistribuidorPage: React.FC = () => {
const fetchFiltersDropdownData = useCallback(async () => {
setLoadingFiltersDropdown(true);
try {
const [distData, empData] = await Promise.all([
distribuidorService.getAllDistribuidores(),
empresaService.getAllEmpresas()
]);
setDistribuidores(distData);
setEmpresas(empData);
} catch (err) { console.error(err); setError("Error al cargar opciones de filtro.");
const [distData, empData] = await Promise.all([
distribuidorService.getAllDistribuidores(),
empresaService.getAllEmpresas()
]);
setDistribuidores(distData);
setEmpresas(empData);
} catch (err) {
console.error(err); setError("Error al cargar opciones de filtro.");
} finally { setLoadingFiltersDropdown(false); }
}, []);
useEffect(() => { fetchFiltersDropdownData(); }, [fetchFiltersDropdownData]);
const clearModalApiErrorMessage = useCallback(() => {
setModalApiErrorMessage(null);
}, []);
const cargarPagos = useCallback(async () => {
if (!puedeVer) { setError("No tiene permiso."); setLoading(false); return; }
setLoading(true); setError(null); setApiErrorMessage(null);
setLoading(true); setError(null); setPageApiErrorMessage(null);
try {
const params = {
fechaDesde: filtroFechaDesde || null, fechaHasta: filtroFechaHasta || null,
@@ -83,19 +88,20 @@ const GestionarPagosDistribuidorPage: React.FC = () => {
};
const data = await pagoDistribuidorService.getAllPagosDistribuidor(params);
setPagos(data);
} catch (err) { console.error(err); setError('Error al cargar los pagos.');
} catch (err) {
console.error(err); setError('Error al cargar los pagos.');
} finally { setLoading(false); }
}, [puedeVer, filtroFechaDesde, filtroFechaHasta, filtroIdDistribuidor, filtroIdEmpresa, filtroTipoMov]);
useEffect(() => { cargarPagos(); }, [cargarPagos]);
const handleOpenModal = (item?: PagoDistribuidorDto) => {
setEditingPago(item || null); setApiErrorMessage(null); setModalOpen(true);
setEditingPago(item || null); setModalApiErrorMessage(null); setModalOpen(true);
};
const handleCloseModal = () => { setModalOpen(false); setEditingPago(null); };
const handleSubmitModal = async (data: CreatePagoDistribuidorDto | UpdatePagoDistribuidorDto, idPago?: number) => {
setApiErrorMessage(null);
setModalApiErrorMessage(null);
try {
if (idPago && editingPago) {
await pagoDistribuidorService.updatePagoDistribuidor(idPago, data as UpdatePagoDistribuidorDto);
@@ -105,15 +111,19 @@ const GestionarPagosDistribuidorPage: React.FC = () => {
cargarPagos();
} catch (err: any) {
const message = axios.isAxiosError(err) && err.response?.data?.message ? err.response.data.message : 'Error al guardar el pago.';
setApiErrorMessage(message); throw err;
setModalApiErrorMessage(message);
throw err;
}
};
const handleDelete = async (idPago: number) => {
if (window.confirm(`¿Seguro de eliminar este pago (ID: ${idPago})? Esta acción revertirá el impacto en el saldo.`)) {
setApiErrorMessage(null);
try { await pagoDistribuidorService.deletePagoDistribuidor(idPago); cargarPagos(); }
catch (err: any) { const msg = axios.isAxiosError(err) && err.response?.data?.message ? err.response.data.message : 'Error al eliminar.'; setApiErrorMessage(msg); }
setPageApiErrorMessage(null);
try { await pagoDistribuidorService.deletePagoDistribuidor(idPago); cargarPagos(); }
catch (err: any) {
const msg = axios.isAxiosError(err) && err.response?.data?.message ? err.response.data.message : 'Error al eliminar.';
setPageApiErrorMessage(msg);
}
}
handleMenuClose();
};
@@ -130,18 +140,11 @@ const GestionarPagosDistribuidorPage: React.FC = () => {
const displayData = pagos.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage);
const formatDate = (dateString?: string | null): string => {
if (!dateString) return '-';
// La fecha llega como "YYYY-MM-DD" o "YYYY-MM-DDTHH:mm:ss"
// Tomamos solo la parte de la fecha para evitar problemas de zona horaria.
const datePart = dateString.split('T')[0];
const parts = datePart.split('-');
// Aseguramos que el formato sea el esperado antes de reordenar
if (parts.length === 3) {
// parts[0] = YYYY, parts[1] = MM, parts[2] = DD
return `${parts[2]}/${parts[1]}/${parts[0]}`; // Formato DD/MM/YYYY
return `${parts[2]}/${parts[1]}/${parts[0]}`;
}
// Si el formato no es el esperado, devolvemos la parte de la fecha tal como vino.
return datePart;
};
@@ -151,93 +154,96 @@ const GestionarPagosDistribuidorPage: React.FC = () => {
<Box sx={{ p: 1 }}>
<Typography variant="h5" gutterBottom>Pagos de Distribuidores</Typography>
<Paper sx={{ p: 2, mb: 2 }}>
<Typography variant="h6" gutterBottom>Filtros <FilterListIcon fontSize="small"/></Typography>
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 2, alignItems: 'center', mb: 2}}>
<TextField label="Fecha Desde" type="date" size="small" value={filtroFechaDesde} onChange={(e) => setFiltroFechaDesde(e.target.value)} InputLabelProps={{ shrink: true }} sx={{minWidth: 170}}/>
<TextField label="Fecha Hasta" type="date" size="small" value={filtroFechaHasta} onChange={(e) => setFiltroFechaHasta(e.target.value)} InputLabelProps={{ shrink: true }} sx={{minWidth: 170}}/>
<FormControl size="small" sx={{minWidth: 200, flexGrow: 1}} disabled={loadingFiltersDropdown}>
<InputLabel>Distribuidor</InputLabel>
<Select value={filtroIdDistribuidor} label="Distribuidor" onChange={(e) => setFiltroIdDistribuidor(e.target.value as number | string)}>
<MenuItem value=""><em>Todos</em></MenuItem>
{distribuidores.map(d => <MenuItem key={d.idDistribuidor} value={d.idDistribuidor}>{d.nombre}</MenuItem>)}
</Select>
</FormControl>
<FormControl size="small" sx={{minWidth: 200, flexGrow: 1}} disabled={loadingFiltersDropdown}>
<InputLabel>Empresa (Saldo)</InputLabel>
<Select value={filtroIdEmpresa} label="Empresa (Saldo)" onChange={(e) => setFiltroIdEmpresa(e.target.value as number | string)}>
<MenuItem value=""><em>Todas</em></MenuItem>
{empresas.map(e => <MenuItem key={e.idEmpresa} value={e.idEmpresa}>{e.nombre}</MenuItem>)}
</Select>
</FormControl>
<FormControl size="small" sx={{minWidth: 150, flexGrow: 1}}>
<InputLabel>Tipo Mov.</InputLabel>
<Select value={filtroTipoMov} label="Tipo Mov." onChange={(e) => setFiltroTipoMov(e.target.value as 'Recibido' | 'Realizado' | '')}>
<MenuItem value=""><em>Todos</em></MenuItem>
<MenuItem value="Recibido">Recibido</MenuItem>
<MenuItem value="Realizado">Realizado</MenuItem>
</Select>
</FormControl>
</Box>
{puedeCrear && (<Button variant="contained" startIcon={<AddIcon />} onClick={() => handleOpenModal()}>Registrar Pago</Button>)}
<Typography variant="h6" gutterBottom>Filtros <FilterListIcon fontSize="small" /></Typography>
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 2, alignItems: 'center', mb: 2 }}>
<TextField label="Fecha Desde" type="date" size="small" value={filtroFechaDesde} onChange={(e) => setFiltroFechaDesde(e.target.value)} InputLabelProps={{ shrink: true }} sx={{ minWidth: 170 }} />
<TextField label="Fecha Hasta" type="date" size="small" value={filtroFechaHasta} onChange={(e) => setFiltroFechaHasta(e.target.value)} InputLabelProps={{ shrink: true }} sx={{ minWidth: 170 }} />
<FormControl size="small" sx={{ minWidth: 200, flexGrow: 1 }} disabled={loadingFiltersDropdown}>
<InputLabel>Distribuidor</InputLabel>
<Select value={filtroIdDistribuidor} label="Distribuidor" onChange={(e) => setFiltroIdDistribuidor(e.target.value as number | string)}>
<MenuItem value=""><em>Todos</em></MenuItem>
{distribuidores.map(d => <MenuItem key={d.idDistribuidor} value={d.idDistribuidor}>{d.nombre}</MenuItem>)}
</Select>
</FormControl>
<FormControl size="small" sx={{ minWidth: 200, flexGrow: 1 }} disabled={loadingFiltersDropdown}>
<InputLabel>Empresa (Saldo)</InputLabel>
<Select value={filtroIdEmpresa} label="Empresa (Saldo)" onChange={(e) => setFiltroIdEmpresa(e.target.value as number | string)}>
<MenuItem value=""><em>Todas</em></MenuItem>
{empresas.map(e => <MenuItem key={e.idEmpresa} value={e.idEmpresa}>{e.nombre}</MenuItem>)}
</Select>
</FormControl>
<FormControl size="small" sx={{ minWidth: 150, flexGrow: 1 }}>
<InputLabel>Tipo Mov.</InputLabel>
<Select value={filtroTipoMov} label="Tipo Mov." onChange={(e) => setFiltroTipoMov(e.target.value as 'Recibido' | 'Realizado' | '')}>
<MenuItem value=""><em>Todos</em></MenuItem>
<MenuItem value="Recibido">Recibido</MenuItem>
<MenuItem value="Realizado">Realizado</MenuItem>
</Select>
</FormControl>
</Box>
{puedeCrear && (<Button variant="contained" startIcon={<AddIcon />} onClick={() => handleOpenModal()}>Registrar Pago</Button>)}
</Paper>
{loading && <Box sx={{ display: 'flex', justifyContent: 'center', my: 2 }}><CircularProgress /></Box>}
{error && !loading && <Alert severity="error" sx={{my: 2}}>{error}</Alert>}
{apiErrorMessage && <Alert severity="error" sx={{my: 2}}>{apiErrorMessage}</Alert>}
{error && !loading && <Alert severity="error" sx={{ my: 2 }}>{error}</Alert>}
{pageApiErrorMessage && <Alert severity="error" sx={{ my: 2 }}>{pageApiErrorMessage}</Alert>}
{!loading && !error && puedeVer && (
<TableContainer component={Paper}>
<Table size="small">
<TableHead><TableRow>
<TableCell>Fecha</TableCell><TableCell>Distribuidor</TableCell><TableCell>Empresa (Saldo)</TableCell>
<TableCell>Tipo Mov.</TableCell><TableCell>Recibo N°</TableCell>
<TableCell align="right">Monto</TableCell><TableCell>Tipo Pago</TableCell>
<TableCell>Detalle</TableCell>
{(puedeModificar || puedeEliminar) && <TableCell align="right">Acciones</TableCell>}
</TableRow></TableHead>
<TableBody>
{displayData.length === 0 ? (
<TableRow><TableCell colSpan={puedeModificar || puedeEliminar ? 9 : 8} align="center">No se encontraron pagos.</TableCell></TableRow>
) : (
displayData.map((p) => (
<TableRow key={p.idPago} hover>
<TableCell>{formatDate(p.fecha)}</TableCell><TableCell>{p.nombreDistribuidor}</TableCell>
<TableCell>{p.nombreEmpresa}</TableCell>
<TableCell>
<Chip label={p.tipoMovimiento} color={p.tipoMovimiento === 'Recibido' ? 'success' : 'warning'} size="small"/>
</TableCell>
<TableCell>{p.recibo}</TableCell>
<TableCell align="right">${p.monto.toFixed(2)}</TableCell>
<TableCell>{p.nombreTipoPago}</TableCell>
<TableCell><Tooltip title={p.detalle || ''}><Box sx={{maxWidth: 150, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap'}}>{p.detalle || '-'}</Box></Tooltip></TableCell>
{(puedeModificar || puedeEliminar) && (
<TableCell align="right">
<IconButton onClick={(e) => handleMenuOpen(e, p)} disabled={!puedeModificar && !puedeEliminar}><MoreVertIcon /></IconButton>
</TableCell>
)}
</TableRow>
)))}
</TableBody>
</Table>
<TablePagination
rowsPerPageOptions={[25, 50, 100]} component="div" count={pagos.length}
rowsPerPage={rowsPerPage} page={page} onPageChange={handleChangePage}
onRowsPerPageChange={handleChangeRowsPerPage} labelRowsPerPage="Filas por página:"
/>
</TableContainer>
)}
<TableContainer component={Paper}>
<Table size="small">
<TableHead><TableRow>
<TableCell>Fecha</TableCell><TableCell>Distribuidor</TableCell><TableCell>Empresa (Saldo)</TableCell>
<TableCell>Tipo Mov.</TableCell><TableCell>Recibo N°</TableCell>
<TableCell align="right">Monto</TableCell><TableCell>Tipo Pago</TableCell>
<TableCell>Detalle</TableCell>
{(puedeModificar || puedeEliminar) && <TableCell align="right">Acciones</TableCell>}
</TableRow></TableHead>
<TableBody>
{displayData.length === 0 ? (
<TableRow><TableCell colSpan={puedeModificar || puedeEliminar ? 9 : 8} align="center">No se encontraron pagos.</TableCell></TableRow>
) : (
displayData.map((p) => (
<TableRow key={p.idPago} hover>
<TableCell>{formatDate(p.fecha)}</TableCell><TableCell>{p.nombreDistribuidor}</TableCell>
<TableCell>{p.nombreEmpresa}</TableCell>
<TableCell>
<Chip label={p.tipoMovimiento} color={p.tipoMovimiento === 'Recibido' ? 'success' : 'warning'} size="small" />
</TableCell>
<TableCell>{p.recibo}</TableCell>
<TableCell align="right">${p.monto.toFixed(2)}</TableCell>
<TableCell>{p.nombreTipoPago}</TableCell>
<TableCell><Tooltip title={p.detalle || ''}><Box sx={{ maxWidth: 150, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{p.detalle || '-'}</Box></Tooltip></TableCell>
{(puedeModificar || puedeEliminar) && (
<TableCell align="right">
<IconButton onClick={(e) => handleMenuOpen(e, p)} disabled={!puedeModificar && !puedeEliminar}><MoreVertIcon /></IconButton>
</TableCell>
)}
</TableRow>
)))}
</TableBody>
</Table>
<TablePagination
rowsPerPageOptions={[25, 50, 100]} component="div" count={pagos.length}
rowsPerPage={rowsPerPage} page={page} onPageChange={handleChangePage}
onRowsPerPageChange={handleChangeRowsPerPage} labelRowsPerPage="Filas por página:"
/>
</TableContainer>
)}
<Menu anchorEl={anchorEl} open={Boolean(anchorEl)} onClose={handleMenuClose}>
{puedeModificar && selectedRow && (
<MenuItem onClick={() => { handleOpenModal(selectedRow); handleMenuClose(); }}><EditIcon fontSize="small" sx={{mr:1}}/> Modificar</MenuItem>)}
<MenuItem onClick={() => { handleOpenModal(selectedRow); handleMenuClose(); }}><EditIcon fontSize="small" sx={{ mr: 1 }} /> Modificar</MenuItem>)}
{puedeEliminar && selectedRow && (
<MenuItem onClick={() => handleDelete(selectedRow.idPago)}><DeleteIcon fontSize="small" sx={{mr:1}}/> Eliminar</MenuItem>)}
<MenuItem onClick={() => handleDelete(selectedRow.idPago)}><DeleteIcon fontSize="small" sx={{ mr: 1 }} /> Eliminar</MenuItem>)}
</Menu>
<PagoDistribuidorFormModal
open={modalOpen} onClose={handleCloseModal} onSubmit={handleSubmitModal}
initialData={editingPago} errorMessage={apiErrorMessage}
clearErrorMessage={() => setApiErrorMessage(null)}
open={modalOpen}
onClose={handleCloseModal}
onSubmit={handleSubmitModal}
initialData={editingPago}
errorMessage={modalApiErrorMessage}
clearErrorMessage={clearModalApiErrorMessage}
/>
</Box>
);