diff --git a/Backend/GestionIntegral.Api/Models/Dtos/Suscripciones/FacturaConsolidadaDto.cs b/Backend/GestionIntegral.Api/Models/Dtos/Suscripciones/FacturaConsolidadaDto.cs index a016451..e02bd06 100644 --- a/Backend/GestionIntegral.Api/Models/Dtos/Suscripciones/FacturaConsolidadaDto.cs +++ b/Backend/GestionIntegral.Api/Models/Dtos/Suscripciones/FacturaConsolidadaDto.cs @@ -8,6 +8,7 @@ namespace GestionIntegral.Api.Dtos.Suscripciones public string EstadoPago { get; set; } = string.Empty; public string EstadoFacturacion { get; set; } = string.Empty; public string? NumeroFactura { get; set; } + public decimal TotalPagado { get; set; } public List Detalles { get; set; } = new List(); } } \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Services/Suscripciones/FacturacionService.cs b/Backend/GestionIntegral.Api/Services/Suscripciones/FacturacionService.cs index 1386387..41cbdd9 100644 --- a/Backend/GestionIntegral.Api/Services/Suscripciones/FacturacionService.cs +++ b/Backend/GestionIntegral.Api/Services/Suscripciones/FacturacionService.cs @@ -282,7 +282,7 @@ namespace GestionIntegral.Api.Services.Suscripciones { var periodo = $"{anio}-{mes:D2}"; var facturasData = await _facturaRepository.GetByPeriodoEnrichedAsync(periodo, nombreSuscriptor, estadoPago, estadoFacturacion); - var detallesData = await _facturaDetalleRepository.GetDetallesPorPeriodoAsync(periodo); // Necesitaremos este nuevo método en el repo + var detallesData = await _facturaDetalleRepository.GetDetallesPorPeriodoAsync(periodo); var empresas = await _empresaRepository.GetAllAsync(null, null); var resumenes = facturasData @@ -301,6 +301,7 @@ namespace GestionIntegral.Api.Services.Suscripciones EstadoPago = itemFactura.Factura.EstadoPago, EstadoFacturacion = itemFactura.Factura.EstadoFacturacion, NumeroFactura = itemFactura.Factura.NumeroFactura, + TotalPagado = itemFactura.TotalPagado, Detalles = detallesData .Where(d => d.IdFactura == itemFactura.Factura.IdFactura) .Select(d => new FacturaDetalleDto { Descripcion = d.Descripcion, ImporteNeto = d.ImporteNeto }) @@ -314,7 +315,7 @@ namespace GestionIntegral.Api.Services.Suscripciones NombreSuscriptor = primerItem.NombreSuscriptor, Facturas = facturasConsolidadas, ImporteTotal = facturasConsolidadas.Sum(f => f.ImporteFinal), - SaldoPendienteTotal = facturasConsolidadas.Sum(f => f.EstadoPago == "Pagada" ? 0 : f.ImporteFinal) + SaldoPendienteTotal = facturasConsolidadas.Sum(f => f.ImporteFinal - f.TotalPagado) }; }); diff --git a/Backend/GestionIntegral.Api/Services/Suscripciones/PagoService.cs b/Backend/GestionIntegral.Api/Services/Suscripciones/PagoService.cs index 5f4b85e..895a4ac 100644 --- a/Backend/GestionIntegral.Api/Services/Suscripciones/PagoService.cs +++ b/Backend/GestionIntegral.Api/Services/Suscripciones/PagoService.cs @@ -70,14 +70,11 @@ namespace GestionIntegral.Api.Services.Suscripciones { var factura = await _facturaRepository.GetByIdAsync(createDto.IdFactura); if (factura == null) return (null, "La factura especificada no existe."); - - // Usar EstadoPago para la validación if (factura.EstadoPago == "Anulada") return (null, "No se puede registrar un pago sobre una factura anulada."); var formaPago = await _formaPagoRepository.GetByIdAsync(createDto.IdFormaPago); if (formaPago == null || !formaPago.Activo) return (null, "La forma de pago no es válida."); - // Obtenemos la suma de pagos ANTERIORES var totalPagadoAnteriormente = await _pagoRepository.GetTotalPagadoAprobadoAsync(createDto.IdFactura, transaction); var nuevoPago = new Pago @@ -96,37 +93,31 @@ namespace GestionIntegral.Api.Services.Suscripciones var pagoCreado = await _pagoRepository.CreateAsync(nuevoPago, transaction); if (pagoCreado == null) throw new DataException("No se pudo registrar el pago."); - // Calculamos el nuevo total EN MEMORIA var nuevoTotalPagado = totalPagadoAnteriormente + pagoCreado.Monto; - // Comparamos y actualizamos el estado si es necesario - // CORRECCIÓN: Usar EstadoPago y el método correcto del repositorio - if (factura.EstadoPago != "Pagada" && nuevoTotalPagado >= factura.ImporteFinal) + // Nueva lógica para manejar todos los estados de pago + string nuevoEstadoPago = factura.EstadoPago; + if (nuevoTotalPagado >= factura.ImporteFinal) { - bool actualizado = await _facturaRepository.UpdateEstadoPagoAsync(factura.IdFactura, "Pagada", transaction); - if (!actualizado) throw new DataException("No se pudo actualizar el estado de la factura a 'Pagada'."); + nuevoEstadoPago = "Pagada"; + } + else if (nuevoTotalPagado > 0) + { + nuevoEstadoPago = "Pagada Parcialmente"; + } + // Si nuevoTotalPagado es 0, el estado no cambia. + + // Solo actualizamos si el estado calculado es diferente al actual. + if (nuevoEstadoPago != factura.EstadoPago) + { + bool actualizado = await _facturaRepository.UpdateEstadoPagoAsync(factura.IdFactura, nuevoEstadoPago, transaction); + if (!actualizado) throw new DataException($"No se pudo actualizar el estado de la factura a '{nuevoEstadoPago}'."); } transaction.Commit(); _logger.LogInformation("Pago manual ID {IdPago} registrado para Factura ID {IdFactura} por Usuario ID {IdUsuario}", pagoCreado.IdPago, pagoCreado.IdFactura, idUsuario); - // Construimos el DTO de respuesta SIN volver a consultar la base de datos - var usuario = await _usuarioRepository.GetByIdAsync(idUsuario); - var dto = new PagoDto - { - IdPago = pagoCreado.IdPago, - IdFactura = pagoCreado.IdFactura, - FechaPago = pagoCreado.FechaPago.ToString("yyyy-MM-dd"), - IdFormaPago = pagoCreado.IdFormaPago, - NombreFormaPago = formaPago.Nombre, - Monto = pagoCreado.Monto, - Estado = pagoCreado.Estado, - Referencia = pagoCreado.Referencia, - Observaciones = pagoCreado.Observaciones, - IdUsuarioRegistro = pagoCreado.IdUsuarioRegistro, - NombreUsuarioRegistro = usuario != null ? $"{usuario.Nombre} {usuario.Apellido}" : "N/A" - }; - + var dto = await MapToDto(pagoCreado); // MapToDto ahora es más simple return (dto, null); } catch (Exception ex) diff --git a/Frontend/src/models/dtos/Suscripciones/ResumenCuentaSuscriptorDto.ts b/Frontend/src/models/dtos/Suscripciones/ResumenCuentaSuscriptorDto.ts index 26663bd..045205a 100644 --- a/Frontend/src/models/dtos/Suscripciones/ResumenCuentaSuscriptorDto.ts +++ b/Frontend/src/models/dtos/Suscripciones/ResumenCuentaSuscriptorDto.ts @@ -12,6 +12,7 @@ export interface FacturaConsolidadaDto { estadoPago: string; estadoFacturacion: string; numeroFactura?: string | null; + totalPagado: number; detalles: FacturaDetalleDto[]; // Añadimos el id del suscriptor para que sea fácil pasarlo a los handlers idSuscriptor: number; diff --git a/Frontend/src/pages/Suscripciones/ConsultaFacturasPage.tsx b/Frontend/src/pages/Suscripciones/ConsultaFacturasPage.tsx index 4045118..69e0425 100644 --- a/Frontend/src/pages/Suscripciones/ConsultaFacturasPage.tsx +++ b/Frontend/src/pages/Suscripciones/ConsultaFacturasPage.tsx @@ -24,7 +24,7 @@ const meses = [ { value: 10, label: 'Octubre' }, { value: 11, label: 'Noviembre' }, { value: 12, label: 'Diciembre' } ]; -const estadosPago = ['Pendiente', 'Pagada', 'Rechazada', 'Anulada']; +const estadosPago = ['Pendiente', 'Pagada', 'Pagada Parcialmente', 'Rechazada', 'Anulada']; const estadosFacturacion = ['Pendiente de Facturar', 'Facturado']; const SuscriptorRow: React.FC<{ @@ -33,50 +33,83 @@ const SuscriptorRow: React.FC<{ handleOpenHistorial: (factura: FacturaConsolidadaDto) => void; }> = ({ resumen, handleMenuOpen, handleOpenHistorial }) => { const [open, setOpen] = useState(false); + + // Función para formatear moneda + const formatCurrency = (value: number) => `$${value.toFixed(2)}`; + return ( *': { borderBottom: 'unset' } }} hover> setOpen(!open)}>{open ? : } {resumen.nombreSuscriptor} - 0 ? 'error.main' : 'success.main' }}>${resumen.saldoPendienteTotal.toFixed(2)} - de ${resumen.importeTotal.toFixed(2)} + 0 ? 'error.main' : 'success.main' }}> + {formatCurrency(resumen.saldoPendienteTotal)} + + de {formatCurrency(resumen.importeTotal)} - + - + - Facturas del Período para {resumen.nombreSuscriptor} + + Facturas del Período para {resumen.nombreSuscriptor} + - EmpresaImporte - Estado PagoEstado Facturación - Nro. FacturaAcciones + Empresa + Importe Total + Pagado + Saldo + Estado Pago + Estado Facturación + Nro. Factura + Acciones - {resumen.facturas.map((factura) => ( - - {factura.nombreEmpresa} - ${factura.importeFinal.toFixed(2)} - - - {factura.numeroFactura || '-'} - - handleMenuOpen(e, factura)} disabled={factura.estadoPago === 'Anulada'}> - - - - handleOpenHistorial(factura)}> - + {resumen.facturas.map((factura) => { + const saldo = factura.importeFinal - factura.totalPagado; + return ( + + {factura.nombreEmpresa} + {formatCurrency(factura.importeFinal)} + + {formatCurrency(factura.totalPagado)} + + 0 ? 'error.main' : 'inherit' }}> + {formatCurrency(saldo)} + + + + + + {factura.numeroFactura || '-'} + + handleMenuOpen(e, factura)} disabled={factura.estadoPago === 'Anulada'}> + - - - - ))} + + handleOpenHistorial(factura)}> + + + + + + ); + })}
@@ -162,7 +195,7 @@ const ConsultaFacturasPage: React.FC = () => { setLoadingLogs(false); } }; - + const handleSubmitPagoModal = async (data: CreatePagoDto) => { setApiError(null); try { @@ -231,10 +264,10 @@ const ConsultaFacturasPage: React.FC = () => { {loading ? () : resumenes.length === 0 ? (No hay facturas para el período seleccionado.) : (resumenes.map(resumen => ( - )))} @@ -257,12 +290,12 @@ const ConsultaFacturasPage: React.FC = () => { idFactura: selectedFactura.idFactura, nombreSuscriptor: resumenes.find(r => r.idSuscriptor === resumenes.find(res => res.facturas.some(f => f.idFactura === selectedFactura.idFactura))?.idSuscriptor)?.nombreSuscriptor || '', importeFinal: selectedFactura.importeFinal, - saldoPendiente: selectedFactura.estadoPago === 'Pagada' ? 0 : selectedFactura.importeFinal, + saldoPendiente: selectedFactura.importeFinal - selectedFactura.totalPagado, + totalPagado: selectedFactura.totalPagado, idSuscriptor: resumenes.find(res => res.facturas.some(f => f.idFactura === selectedFactura.idFactura))?.idSuscriptor || 0, periodo: '', fechaEmision: '', fechaVencimiento: '', - totalPagado: selectedFactura.importeFinal - (selectedFactura.estadoPago === 'Pagada' ? 0 : selectedFactura.importeFinal), estadoPago: selectedFactura.estadoPago, estadoFacturacion: selectedFactura.estadoFacturacion, numeroFactura: selectedFactura.numeroFactura, @@ -272,7 +305,7 @@ const ConsultaFacturasPage: React.FC = () => { errorMessage={apiError} clearErrorMessage={() => setApiError(null)} /> - + setHistorialModalOpen(false)}