diff --git a/Backend/GestionIntegral.Api/Controllers/Suscripciones/FacturacionController.cs b/Backend/GestionIntegral.Api/Controllers/Suscripciones/FacturacionController.cs index a8315c2..3b4c2de 100644 --- a/Backend/GestionIntegral.Api/Controllers/Suscripciones/FacturacionController.cs +++ b/Backend/GestionIntegral.Api/Controllers/Suscripciones/FacturacionController.cs @@ -72,15 +72,16 @@ namespace GestionIntegral.Api.Controllers.Suscripciones [HttpGet("{anio:int}/{mes:int}")] public async Task GetFacturas( - int anio, int mes, - [FromQuery] string? nombreSuscriptor, - [FromQuery] string? estadoPago, - [FromQuery] string? estadoFacturacion) + int anio, int mes, + [FromQuery] string? nombreSuscriptor, + [FromQuery] string? estadoPago, + [FromQuery] string? estadoFacturacion, + [FromQuery] string? tipoFactura) { if (!TienePermiso(PermisoGestionarFacturacion)) return Forbid(); if (anio < 2020 || mes < 1 || mes > 12) return BadRequest(new { message = "El período no es válido." }); - var resumenes = await _facturacionService.ObtenerResumenesDeCuentaPorPeriodo(anio, mes, nombreSuscriptor, estadoPago, estadoFacturacion); + var resumenes = await _facturacionService.ObtenerResumenesDeCuentaPorPeriodo(anio, mes, nombreSuscriptor, estadoPago, estadoFacturacion, tipoFactura); return Ok(resumenes); } diff --git a/Backend/GestionIntegral.Api/Data/Repositories/Suscripciones/FacturaRepository.cs b/Backend/GestionIntegral.Api/Data/Repositories/Suscripciones/FacturaRepository.cs index 6e6a174..f62c7f6 100644 --- a/Backend/GestionIntegral.Api/Data/Repositories/Suscripciones/FacturaRepository.cs +++ b/Backend/GestionIntegral.Api/Data/Repositories/Suscripciones/FacturaRepository.cs @@ -104,7 +104,8 @@ namespace GestionIntegral.Api.Data.Repositories.Suscripciones return rowsAffected == idsFacturas.Count(); } - public async Task> GetByPeriodoEnrichedAsync(string periodo, string? nombreSuscriptor, string? estadoPago, string? estadoFacturacion) + public async Task> GetByPeriodoEnrichedAsync( + string periodo, string? nombreSuscriptor, string? estadoPago, string? estadoFacturacion, string? tipoFactura) { var sqlBuilder = new StringBuilder(@" WITH FacturaConEmpresa AS ( @@ -149,6 +150,12 @@ namespace GestionIntegral.Api.Data.Repositories.Suscripciones parameters.Add("EstadoFacturacion", estadoFacturacion); } + if (!string.IsNullOrWhiteSpace(tipoFactura)) + { + sqlBuilder.Append(" AND f.TipoFactura = @TipoFactura"); + parameters.Add("TipoFactura", tipoFactura); + } + sqlBuilder.Append(" ORDER BY s.NombreCompleto, f.IdFactura;"); try diff --git a/Backend/GestionIntegral.Api/Data/Repositories/Suscripciones/IFacturaRepository.cs b/Backend/GestionIntegral.Api/Data/Repositories/Suscripciones/IFacturaRepository.cs index 53328c3..1572d4a 100644 --- a/Backend/GestionIntegral.Api/Data/Repositories/Suscripciones/IFacturaRepository.cs +++ b/Backend/GestionIntegral.Api/Data/Repositories/Suscripciones/IFacturaRepository.cs @@ -15,7 +15,8 @@ namespace GestionIntegral.Api.Data.Repositories.Suscripciones Task UpdateEstadoPagoAsync(int idFactura, string nuevoEstadoPago, IDbTransaction transaction); Task UpdateNumeroFacturaAsync(int idFactura, string numeroFactura, IDbTransaction transaction); Task UpdateLoteDebitoAsync(IEnumerable idsFacturas, int idLoteDebito, IDbTransaction transaction); - Task> GetByPeriodoEnrichedAsync(string periodo, string? nombreSuscriptor, string? estadoPago, string? estadoFacturacion); + Task> GetByPeriodoEnrichedAsync( + string periodo, string? nombreSuscriptor, string? estadoPago, string? estadoFacturacion, string? tipoFactura); Task UpdateEstadoYMotivoAsync(int idFactura, string nuevoEstadoPago, string? motivoRechazo, IDbTransaction transaction); Task GetUltimoPeriodoFacturadoAsync(); Task> GetFacturasPagadasPendientesDeFacturar(string periodo); diff --git a/Backend/GestionIntegral.Api/Models/Dtos/Suscripciones/FacturaConsolidadaDto.cs b/Backend/GestionIntegral.Api/Models/Dtos/Suscripciones/FacturaConsolidadaDto.cs index e02bd06..0494c17 100644 --- a/Backend/GestionIntegral.Api/Models/Dtos/Suscripciones/FacturaConsolidadaDto.cs +++ b/Backend/GestionIntegral.Api/Models/Dtos/Suscripciones/FacturaConsolidadaDto.cs @@ -9,6 +9,8 @@ namespace GestionIntegral.Api.Dtos.Suscripciones public string EstadoFacturacion { get; set; } = string.Empty; public string? NumeroFactura { get; set; } public decimal TotalPagado { get; set; } + public string TipoFactura { get; set; } = string.Empty; + public int IdSuscriptor { get; set; } public List Detalles { get; set; } = new List(); } } \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Models/Suscripciones/Factura.cs b/Backend/GestionIntegral.Api/Models/Suscripciones/Factura.cs index 73940fe..f70229a 100644 --- a/Backend/GestionIntegral.Api/Models/Suscripciones/Factura.cs +++ b/Backend/GestionIntegral.Api/Models/Suscripciones/Factura.cs @@ -15,5 +15,6 @@ namespace GestionIntegral.Api.Models.Suscripciones public string? NumeroFactura { get; set; } public int? IdLoteDebito { get; set; } public string? MotivoRechazo { get; set; } + public string TipoFactura { get; set; } = string.Empty; } } \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Services/Suscripciones/DebitoAutomaticoService.cs b/Backend/GestionIntegral.Api/Services/Suscripciones/DebitoAutomaticoService.cs index dfe8abe..0dbecd0 100644 --- a/Backend/GestionIntegral.Api/Services/Suscripciones/DebitoAutomaticoService.cs +++ b/Backend/GestionIntegral.Api/Services/Suscripciones/DebitoAutomaticoService.cs @@ -42,7 +42,7 @@ namespace GestionIntegral.Api.Services.Suscripciones { // Este número debe ser gestionado para no repetirse. Por ahora, lo mantenemos como 1. const int identificacionArchivo = 1; - + var periodo = $"{anio}-{mes:D2}"; var fechaGeneracion = DateTime.Now; @@ -102,7 +102,11 @@ namespace GestionIntegral.Api.Services.Suscripciones var facturas = await _facturaRepository.GetByPeriodoAsync(periodo); var resultado = new List<(Factura, Suscriptor)>(); - foreach (var f in facturas.Where(fa => fa.EstadoPago == "Pendiente" || fa.EstadoPago == "Pagada Parcialmente" || fa.EstadoPago == "Rechazada")) + // Filtramos por estado Y POR TIPO DE FACTURA + foreach (var f in facturas.Where(fa => + (fa.EstadoPago == "Pendiente" || fa.EstadoPago == "Pagada Parcialmente" || fa.EstadoPago == "Rechazada") && + fa.TipoFactura == "Mensual" + )) { var suscriptor = await _suscriptorRepository.GetByIdAsync(f.IdSuscriptor); if (suscriptor == null || string.IsNullOrWhiteSpace(suscriptor.CBU) || suscriptor.CBU.Length != 22) @@ -141,7 +145,12 @@ namespace GestionIntegral.Api.Services.Suscripciones private string FormatNumericString(string? value, int length) => (value ?? "").PadLeft(length, '0'); private string MapTipoDocumento(string tipo) => tipo.ToUpper() switch { - "DNI" => "0096", "CUIT" => "0080", "CUIL" => "0086", "LE" => "0089", "LC" => "0090", _ => "0000" + "DNI" => "0096", + "CUIT" => "0080", + "CUIL" => "0086", + "LE" => "0089", + "LC" => "0090", + _ => "0000" }; private string CrearRegistroHeader(DateTime fechaGeneracion, decimal importeTotal, int cantidadRegistros, int identificacionArchivo) @@ -212,7 +221,7 @@ namespace GestionIntegral.Api.Services.Suscripciones public async Task ProcesarArchivoRespuesta(IFormFile archivo, int idUsuario) { // Se mantiene la lógica original para procesar el archivo de respuesta del banco. - + var respuesta = new ProcesamientoLoteResponseDto(); if (archivo == null || archivo.Length == 0) { diff --git a/Backend/GestionIntegral.Api/Services/Suscripciones/FacturacionService.cs b/Backend/GestionIntegral.Api/Services/Suscripciones/FacturacionService.cs index 41cbdd9..baaf2e5 100644 --- a/Backend/GestionIntegral.Api/Services/Suscripciones/FacturacionService.cs +++ b/Backend/GestionIntegral.Api/Services/Suscripciones/FacturacionService.cs @@ -278,10 +278,11 @@ namespace GestionIntegral.Api.Services.Suscripciones }); } - public async Task> ObtenerResumenesDeCuentaPorPeriodo(int anio, int mes, string? nombreSuscriptor, string? estadoPago, string? estadoFacturacion) + public async Task> ObtenerResumenesDeCuentaPorPeriodo( + int anio, int mes, string? nombreSuscriptor, string? estadoPago, string? estadoFacturacion, string? tipoFactura) { var periodo = $"{anio}-{mes:D2}"; - var facturasData = await _facturaRepository.GetByPeriodoEnrichedAsync(periodo, nombreSuscriptor, estadoPago, estadoFacturacion); + var facturasData = await _facturaRepository.GetByPeriodoEnrichedAsync(periodo, nombreSuscriptor, estadoPago, estadoFacturacion, tipoFactura); var detallesData = await _facturaDetalleRepository.GetDetallesPorPeriodoAsync(periodo); var empresas = await _empresaRepository.GetAllAsync(null, null); @@ -302,10 +303,17 @@ namespace GestionIntegral.Api.Services.Suscripciones EstadoFacturacion = itemFactura.Factura.EstadoFacturacion, NumeroFactura = itemFactura.Factura.NumeroFactura, TotalPagado = itemFactura.TotalPagado, + + // Faltaba esta línea para pasar el tipo de factura al frontend. + TipoFactura = itemFactura.Factura.TipoFactura, + Detalles = detallesData .Where(d => d.IdFactura == itemFactura.Factura.IdFactura) .Select(d => new FacturaDetalleDto { Descripcion = d.Descripcion, ImporteNeto = d.ImporteNeto }) - .ToList() + .ToList(), + + // Pasamos el id del suscriptor para facilitar las cosas en el frontend + IdSuscriptor = itemFactura.Factura.IdSuscriptor }; }).ToList(); @@ -579,7 +587,7 @@ namespace GestionIntegral.Api.Services.Suscripciones } } - private async Task CalcularImporteParaSuscripcion(Suscripcion suscripcion, int anio, int mes, IDbTransaction transaction) + public async Task CalcularImporteParaSuscripcion(Suscripcion suscripcion, int anio, int mes, IDbTransaction transaction) { decimal importeTotal = 0; var diasDeEntrega = suscripcion.DiasEntrega.Split(',').ToHashSet(); diff --git a/Backend/GestionIntegral.Api/Services/Suscripciones/IFacturacionService.cs b/Backend/GestionIntegral.Api/Services/Suscripciones/IFacturacionService.cs index fc57241..e8e2ee9 100644 --- a/Backend/GestionIntegral.Api/Services/Suscripciones/IFacturacionService.cs +++ b/Backend/GestionIntegral.Api/Services/Suscripciones/IFacturacionService.cs @@ -1,5 +1,7 @@ +using System.Data; using GestionIntegral.Api.Dtos.Comunicaciones; using GestionIntegral.Api.Dtos.Suscripciones; +using GestionIntegral.Api.Models.Suscripciones; namespace GestionIntegral.Api.Services.Suscripciones { @@ -7,8 +9,10 @@ namespace GestionIntegral.Api.Services.Suscripciones { Task<(bool Exito, string? Mensaje, LoteDeEnvioResumenDto? ResultadoEnvio)> GenerarFacturacionMensual(int anio, int mes, int idUsuario); Task> ObtenerHistorialLotesEnvio(int? anio, int? mes); - Task> ObtenerResumenesDeCuentaPorPeriodo(int anio, int mes, string? nombreSuscriptor, string? estadoPago, string? estadoFacturacion); + Task> ObtenerResumenesDeCuentaPorPeriodo( + int anio, int mes, string? nombreSuscriptor, string? estadoPago, string? estadoFacturacion, string? tipoFactura); Task<(bool Exito, string? Error, string? EmailDestino)> EnviarFacturaPdfPorEmail(int idFactura, int idUsuario); Task<(bool Exito, string? Error)> ActualizarNumeroFactura(int idFactura, string numeroFactura, int idUsuario); + Task CalcularImporteParaSuscripcion(Suscripcion suscripcion, int anio, int mes, IDbTransaction transaction); } } \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Services/Suscripciones/SuscripcionService.cs b/Backend/GestionIntegral.Api/Services/Suscripciones/SuscripcionService.cs index 4fd6e4f..32a2874 100644 --- a/Backend/GestionIntegral.Api/Services/Suscripciones/SuscripcionService.cs +++ b/Backend/GestionIntegral.Api/Services/Suscripciones/SuscripcionService.cs @@ -3,12 +3,8 @@ using GestionIntegral.Api.Data.Repositories.Distribucion; using GestionIntegral.Api.Data.Repositories.Suscripciones; using GestionIntegral.Api.Dtos.Suscripciones; using GestionIntegral.Api.Models.Suscripciones; -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; using System.Data; -using System.Linq; -using System.Threading.Tasks; +using System.Globalization; namespace GestionIntegral.Api.Services.Suscripciones { @@ -18,23 +14,32 @@ namespace GestionIntegral.Api.Services.Suscripciones private readonly ISuscriptorRepository _suscriptorRepository; private readonly IPublicacionRepository _publicacionRepository; private readonly IPromocionRepository _promocionRepository; - private readonly DbConnectionFactory _connectionFactory; + private readonly IFacturaRepository _facturaRepository; + private readonly IFacturaDetalleRepository _facturaDetalleRepository; + private readonly IFacturacionService _facturacionService; private readonly ILogger _logger; + private readonly DbConnectionFactory _connectionFactory; public SuscripcionService( ISuscripcionRepository suscripcionRepository, - ISuscriptorRepository suscriptorRepository, - IPublicacionRepository publicacionRepository, - IPromocionRepository promocionRepository, - DbConnectionFactory connectionFactory, - ILogger logger) + ISuscriptorRepository suscriptorRepository, + IPublicacionRepository publicacionRepository, + IPromocionRepository promocionRepository, + IFacturaRepository facturaRepository, + IFacturaDetalleRepository facturaDetalleRepository, + IFacturacionService facturacionService, + ILogger logger, + DbConnectionFactory connectionFactory) { _suscripcionRepository = suscripcionRepository; _suscriptorRepository = suscriptorRepository; _publicacionRepository = publicacionRepository; _promocionRepository = promocionRepository; - _connectionFactory = connectionFactory; + _facturaRepository = facturaRepository; + _facturaDetalleRepository = facturaDetalleRepository; + _facturacionService = facturacionService; _logger = logger; + _connectionFactory = connectionFactory; } private PromocionDto MapPromocionToDto(Promocion promo) => new PromocionDto @@ -122,6 +127,53 @@ namespace GestionIntegral.Api.Services.Suscripciones var creada = await _suscripcionRepository.CreateAsync(nuevaSuscripcion, transaction); if (creada == null) throw new DataException("Error al crear la suscripción."); + var ultimoPeriodoFacturadoStr = await _facturaRepository.GetUltimoPeriodoFacturadoAsync(); + if (ultimoPeriodoFacturadoStr != null) + { + var ultimoPeriodo = DateTime.ParseExact(ultimoPeriodoFacturadoStr, "yyyy-MM", CultureInfo.InvariantCulture); + var periodoSuscripcion = new DateTime(creada.FechaInicio.Year, creada.FechaInicio.Month, 1); + + if (periodoSuscripcion <= ultimoPeriodo) + { + _logger.LogInformation("Suscripción en período ya cerrado detectada para Suscriptor {IdSuscriptor}. Generando factura de alta pro-rata.", creada.IdSuscriptor); + + decimal importeProporcional = await _facturacionService.CalcularImporteParaSuscripcion(creada, creada.FechaInicio.Year, creada.FechaInicio.Month, transaction); + + if (importeProporcional > 0) + { + var facturaDeAlta = new Factura + { + IdSuscriptor = creada.IdSuscriptor, + Periodo = creada.FechaInicio.ToString("yyyy-MM"), + FechaEmision = DateTime.Now.Date, + FechaVencimiento = DateTime.Now.AddDays(10).Date, // Vencimiento corto + ImporteBruto = importeProporcional, + ImporteFinal = importeProporcional, + EstadoPago = "Pendiente", + EstadoFacturacion = "Pendiente de Facturar", + TipoFactura = "Alta" // Marcamos la factura como de tipo "Alta" + }; + + var facturaCreada = await _facturaRepository.CreateAsync(facturaDeAlta, transaction); + if (facturaCreada == null) throw new DataException("No se pudo crear la factura de alta."); + + var publicacion = await _publicacionRepository.GetByIdSimpleAsync(creada.IdPublicacion); + var finDeMes = new DateTime(creada.FechaInicio.Year, creada.FechaInicio.Month, 1).AddMonths(1).AddDays(-1); + + await _facturaDetalleRepository.CreateAsync(new FacturaDetalle + { + IdFactura = facturaCreada.IdFactura, + IdSuscripcion = creada.IdSuscripcion, + Descripcion = $"Suscripción proporcional {publicacion?.Nombre} ({creada.FechaInicio:dd/MM} al {finDeMes:dd/MM})", + ImporteBruto = importeProporcional, + ImporteNeto = importeProporcional, + DescuentoAplicado = 0 + }, transaction); + + _logger.LogInformation("Factura de alta #{IdFactura} por ${Importe} generada para la nueva suscripción #{IdSuscripcion}.", facturaCreada.IdFactura, importeProporcional, creada.IdSuscripcion); + } + } + } transaction.Commit(); _logger.LogInformation("Suscripción ID {Id} creada por Usuario ID {UserId}.", creada.IdSuscripcion, idUsuario); return (await MapToDto(creada), null); diff --git a/Frontend/src/components/Modals/Suscripciones/PagoManualModal.tsx b/Frontend/src/components/Modals/Suscripciones/PagoManualModal.tsx index 2068d8b..8f4cc96 100644 --- a/Frontend/src/components/Modals/Suscripciones/PagoManualModal.tsx +++ b/Frontend/src/components/Modals/Suscripciones/PagoManualModal.tsx @@ -1,6 +1,6 @@ import React, { useState, useEffect } from 'react'; import { Modal, Box, Typography, TextField, Button, CircularProgress, Alert, FormControl, InputLabel, Select, MenuItem, type SelectChangeEvent, InputAdornment } from '@mui/material'; -import type { FacturaDto } from '../../../models/dtos/Suscripciones/FacturaDto'; +import type { FacturaConsolidadaDto } from '../../../models/dtos/Suscripciones/ResumenCuentaSuscriptorDto'; import type { CreatePagoDto } from '../../../models/dtos/Suscripciones/CreatePagoDto'; import type { FormaPagoDto } from '../../../models/dtos/Suscripciones/FormaPagoDto'; import formaPagoService from '../../../services/Suscripciones/formaPagoService'; @@ -23,17 +23,19 @@ interface PagoManualModalProps { open: boolean; onClose: () => void; onSubmit: (data: CreatePagoDto) => Promise; - factura: FacturaDto | null; + factura: FacturaConsolidadaDto | null; + nombreSuscriptor: string; // Se pasa el nombre del suscriptor como prop- errorMessage?: string | null; clearErrorMessage: () => void; } -const PagoManualModal: React.FC = ({ open, onClose, onSubmit, factura, errorMessage, clearErrorMessage }) => { +const PagoManualModal: React.FC = ({ open, onClose, onSubmit, factura, nombreSuscriptor, errorMessage, clearErrorMessage }) => { const [formData, setFormData] = useState>({}); const [formasDePago, setFormasDePago] = useState([]); const [loading, setLoading] = useState(false); const [loadingFormasPago, setLoadingFormasPago] = useState(false); const [localErrors, setLocalErrors] = useState<{ [key: string]: string | null }>({}); + const saldoPendiente = factura ? factura.importeFinal - factura.totalPagado : 0; useEffect(() => { const fetchFormasDePago = async () => { @@ -52,26 +54,24 @@ const PagoManualModal: React.FC = ({ open, onClose, onSubm fetchFormasDePago(); setFormData({ idFactura: factura.idFactura, - monto: factura.saldoPendiente, // Usar el saldo pendiente como valor por defecto + monto: saldoPendiente, fechaPago: new Date().toISOString().split('T')[0] }); setLocalErrors({}); } - }, [open, factura]); + }, [open, factura, saldoPendiente]); const validate = (): boolean => { const errors: { [key: string]: string | null } = {}; if (!formData.idFormaPago) errors.idFormaPago = "Seleccione una forma de pago."; if (!formData.fechaPago) errors.fechaPago = "La fecha de pago es obligatoria."; - + const monto = formData.monto ?? 0; - const saldo = factura?.saldoPendiente ?? 0; if (monto <= 0) { - errors.monto = "El monto debe ser mayor a cero."; - } else if (monto > saldo) { - // Usamos toFixed(2) para mostrar el formato de moneda correcto en el mensaje - errors.monto = `El monto no puede superar el saldo pendiente de $${saldo.toFixed(2)}.`; + errors.monto = "El monto debe ser mayor a cero."; + } else if (monto > saldoPendiente) { + errors.monto = `El monto no puede superar el saldo pendiente de $${saldoPendiente.toFixed(2)}.`; } setLocalErrors(errors); @@ -85,7 +85,7 @@ const PagoManualModal: React.FC = ({ open, onClose, onSubm if (localErrors[name]) setLocalErrors(prev => ({ ...prev, [name]: null })); if (errorMessage) clearErrorMessage(); }; - + const handleSelectChange = (e: SelectChangeEvent) => { const { name, value } = e.target; setFormData(prev => ({ ...prev, [name]: value })); @@ -117,29 +117,32 @@ const PagoManualModal: React.FC = ({ open, onClose, onSubm Registrar Pago Manual - - Saldo Pendiente: ${factura.saldoPendiente.toFixed(2)} + + Para: {nombreSuscriptor} + + + Saldo Pendiente: ${saldoPendiente.toFixed(2)} - - - Forma de Pago - - - $ }} inputProps={{ step: "0.01" }} /> - - + + + Forma de Pago + + + $ }} inputProps={{ step: "0.01" }} /> + + - {errorMessage && {errorMessage}} + {errorMessage && {errorMessage}} - - - - + + + + diff --git a/Frontend/src/models/dtos/Suscripciones/ResumenCuentaSuscriptorDto.ts b/Frontend/src/models/dtos/Suscripciones/ResumenCuentaSuscriptorDto.ts index 045205a..98f56f6 100644 --- a/Frontend/src/models/dtos/Suscripciones/ResumenCuentaSuscriptorDto.ts +++ b/Frontend/src/models/dtos/Suscripciones/ResumenCuentaSuscriptorDto.ts @@ -13,6 +13,7 @@ export interface FacturaConsolidadaDto { estadoFacturacion: string; numeroFactura?: string | null; totalPagado: number; + tipoFactura: 'Mensual' | 'Alta'; 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 69e0425..96711b1 100644 --- a/Frontend/src/pages/Suscripciones/ConsultaFacturasPage.tsx +++ b/Frontend/src/pages/Suscripciones/ConsultaFacturasPage.tsx @@ -26,6 +26,7 @@ const meses = [ const estadosPago = ['Pendiente', 'Pagada', 'Pagada Parcialmente', 'Rechazada', 'Anulada']; const estadosFacturacion = ['Pendiente de Facturar', 'Facturado']; +const tiposFactura = ['Mensual', 'Alta']; const SuscriptorRow: React.FC<{ resumen: ResumenCuentaSuscriptorDto; @@ -33,8 +34,6 @@ 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 ( @@ -43,20 +42,16 @@ const SuscriptorRow: React.FC<{ setOpen(!open)}>{open ? : } {resumen.nombreSuscriptor} - 0 ? 'error.main' : 'success.main' }}> - {formatCurrency(resumen.saldoPendienteTotal)} - + 0 ? 'error.main' : 'success.main' }}>{formatCurrency(resumen.saldoPendienteTotal)} de {formatCurrency(resumen.importeTotal)} - + - + {/* <-- Ajustado para la nueva columna */} - - Facturas del Período para {resumen.nombreSuscriptor} - + Facturas del Período para {resumen.nombreSuscriptor} @@ -64,6 +59,7 @@ const SuscriptorRow: React.FC<{ Importe Total Pagado Saldo + Tipo Factura Estado Pago Estado Facturación Nro. Factura @@ -77,35 +73,17 @@ const SuscriptorRow: React.FC<{ {factura.nombreEmpresa} {formatCurrency(factura.importeFinal)} - - {formatCurrency(factura.totalPagado)} - - 0 ? 'error.main' : 'inherit' }}> - {formatCurrency(saldo)} - + {formatCurrency(factura.totalPagado)} + 0 ? 'error.main' : 'inherit' }}>{formatCurrency(saldo)} - + + {factura.numeroFactura || '-'} - handleMenuOpen(e, factura)} disabled={factura.estadoPago === 'Anulada'}> - - - - handleOpenHistorial(factura)}> - - - + handleMenuOpen(e, factura)} disabled={factura.estadoPago === 'Anulada'}> + handleOpenHistorial(factura)}> ); @@ -135,6 +113,7 @@ const ConsultaFacturasPage: React.FC = () => { const [filtroNombre, setFiltroNombre] = useState(''); const [filtroEstadoPago, setFiltroEstadoPago] = useState(''); const [filtroEstadoFacturacion, setFiltroEstadoFacturacion] = useState(''); + const [filtroTipoFactura, setFiltroTipoFactura] = useState(''); const [selectedFactura, setSelectedFactura] = useState(null); const [anchorEl, setAnchorEl] = useState(null); @@ -154,7 +133,8 @@ const ConsultaFacturasPage: React.FC = () => { selectedMes, filtroNombre || undefined, filtroEstadoPago || undefined, - filtroEstadoFacturacion || undefined + filtroEstadoFacturacion || undefined, + filtroTipoFactura || undefined ); setResumenes(data); } catch (err) { @@ -163,7 +143,7 @@ const ConsultaFacturasPage: React.FC = () => { } finally { setLoading(false); } - }, [selectedAnio, selectedMes, puedeConsultar, filtroNombre, filtroEstadoPago, filtroEstadoFacturacion]); + }, [selectedAnio, selectedMes, puedeConsultar, filtroNombre, filtroEstadoPago, filtroEstadoFacturacion, filtroTipoFactura]); useEffect(() => { const timer = setTimeout(() => { @@ -251,6 +231,17 @@ const ConsultaFacturasPage: React.FC = () => { setFiltroNombre(e.target.value)} sx={{ flexGrow: 1, minWidth: '200px' }} /> Estado de Pago Estado de Facturación + + Tipo de Factura + + @@ -259,7 +250,7 @@ const ConsultaFacturasPage: React.FC = () => {
- SuscriptorSaldo Total / Importe Total + SuscriptorSaldo Total / Importe Total {loading ? () : resumenes.length === 0 ? (No hay facturas para el período seleccionado.) @@ -285,22 +276,9 @@ const ConsultaFacturasPage: React.FC = () => { open={pagoModalOpen} onClose={handleClosePagoModal} onSubmit={handleSubmitPagoModal} - factura={ - selectedFactura ? { - 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.importeFinal - selectedFactura.totalPagado, - totalPagado: selectedFactura.totalPagado, - idSuscriptor: resumenes.find(res => res.facturas.some(f => f.idFactura === selectedFactura.idFactura))?.idSuscriptor || 0, - periodo: '', - fechaEmision: '', - fechaVencimiento: '', - estadoPago: selectedFactura.estadoPago, - estadoFacturacion: selectedFactura.estadoFacturacion, - numeroFactura: selectedFactura.numeroFactura, - detalles: selectedFactura.detalles, - } : null + factura={selectedFactura} + nombreSuscriptor={ + resumenes.find(r => r.idSuscriptor === selectedFactura?.idSuscriptor)?.nombreSuscriptor || '' } errorMessage={apiError} clearErrorMessage={() => setApiError(null)} diff --git a/Frontend/src/services/Suscripciones/facturacionService.ts b/Frontend/src/services/Suscripciones/facturacionService.ts index 97e0d76..b21ea0d 100644 --- a/Frontend/src/services/Suscripciones/facturacionService.ts +++ b/Frontend/src/services/Suscripciones/facturacionService.ts @@ -19,11 +19,16 @@ const procesarArchivoRespuesta = async (archivo: File): Promise => { +const getResumenesDeCuentaPorPeriodo = async ( + anio: number, mes: number, + nombreSuscriptor?: string, estadoPago?: string, estadoFacturacion?: string, + tipoFactura?: string +): Promise => { const params = new URLSearchParams(); if (nombreSuscriptor) params.append('nombreSuscriptor', nombreSuscriptor); if (estadoPago) params.append('estadoPago', estadoPago); if (estadoFacturacion) params.append('estadoFacturacion', estadoFacturacion); + if (tipoFactura) params.append('tipoFactura', tipoFactura); const queryString = params.toString(); const url = `${API_URL}/${anio}/${mes}${queryString ? `?${queryString}` : ''}`; @@ -81,10 +86,10 @@ const getHistorialLotesEnvio = async (anio?: number, mes?: number): Promise(url); return response.data; };