using Dapper; using GestionIntegral.Api.Data; using GestionIntegral.Api.Models.Suscripciones; using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.Data; using System.Linq; using System.Text; using System.Threading.Tasks; namespace GestionIntegral.Api.Data.Repositories.Suscripciones { public class FacturaRepository : IFacturaRepository { private readonly DbConnectionFactory _connectionFactory; private readonly ILogger _logger; public FacturaRepository(DbConnectionFactory connectionFactory, ILogger logger) { _connectionFactory = connectionFactory; _logger = logger; } public async Task GetByIdAsync(int idFactura) { const string sql = "SELECT * FROM dbo.susc_Facturas WHERE IdFactura = @idFactura;"; using var connection = _connectionFactory.CreateConnection(); return await connection.QuerySingleOrDefaultAsync(sql, new { idFactura }); } public async Task> GetByPeriodoAsync(string periodo) { const string sql = "SELECT * FROM dbo.susc_Facturas WHERE Periodo = @Periodo ORDER BY IdFactura;"; using var connection = _connectionFactory.CreateConnection(); return await connection.QueryAsync(sql, new { Periodo = periodo }); } public async Task GetBySuscriptorYPeriodoAsync(int idSuscriptor, string periodo, IDbTransaction transaction) { const string sql = "SELECT TOP 1 * FROM dbo.susc_Facturas WHERE IdSuscriptor = @IdSuscriptor AND Periodo = @Periodo;"; if (transaction == null || transaction.Connection == null) { throw new ArgumentNullException(nameof(transaction), "La transacción o su conexión no pueden ser nulas."); } return await transaction.Connection.QuerySingleOrDefaultAsync(sql, new { idSuscriptor, Periodo = periodo }, transaction); } public async Task> GetListBySuscriptorYPeriodoAsync(int idSuscriptor, string periodo) { const string sql = "SELECT * FROM dbo.susc_Facturas WHERE IdSuscriptor = @IdSuscriptor AND Periodo = @Periodo;"; using var connection = _connectionFactory.CreateConnection(); return await connection.QueryAsync(sql, new { idSuscriptor, Periodo = periodo }); } public async Task CreateAsync(Factura nuevaFactura, IDbTransaction transaction) { if (transaction == null || transaction.Connection == null) { throw new ArgumentNullException(nameof(transaction), "La transacción o su conexión no pueden ser nulas."); } const string sqlInsert = @" INSERT INTO dbo.susc_Facturas (IdSuscriptor, Periodo, FechaEmision, FechaVencimiento, ImporteBruto, DescuentoAplicado, ImporteFinal, EstadoPago, EstadoFacturacion, TipoFactura) OUTPUT INSERTED.* VALUES (@IdSuscriptor, @Periodo, @FechaEmision, @FechaVencimiento, @ImporteBruto, @DescuentoAplicado, @ImporteFinal, @EstadoPago, @EstadoFacturacion, @TipoFactura);"; return await transaction.Connection.QuerySingleAsync(sqlInsert, nuevaFactura, transaction); } public async Task UpdateEstadoPagoAsync(int idFactura, string nuevoEstadoPago, IDbTransaction transaction) { if (transaction == null || transaction.Connection == null) { throw new ArgumentNullException(nameof(transaction), "La transacción o su conexión no pueden ser nulas."); } const string sql = "UPDATE dbo.susc_Facturas SET EstadoPago = @NuevoEstadoPago WHERE IdFactura = @IdFactura;"; var rowsAffected = await transaction.Connection.ExecuteAsync(sql, new { NuevoEstadoPago = nuevoEstadoPago, idFactura }, transaction); return rowsAffected == 1; } public async Task UpdateNumeroFacturaAsync(int idFactura, string numeroFactura, IDbTransaction transaction) { if (transaction == null || transaction.Connection == null) { throw new ArgumentNullException(nameof(transaction), "La transacción o su conexión no pueden ser nulas."); } const string sql = @" UPDATE dbo.susc_Facturas SET NumeroFactura = @NumeroFactura, EstadoFacturacion = 'Facturado' WHERE IdFactura = @IdFactura;"; var rowsAffected = await transaction.Connection.ExecuteAsync(sql, new { NumeroFactura = numeroFactura, idFactura }, transaction); return rowsAffected == 1; } public async Task UpdateLoteDebitoAsync(IEnumerable idsFacturas, int idLoteDebito, IDbTransaction transaction) { if (transaction == null || transaction.Connection == null) { throw new ArgumentNullException(nameof(transaction), "La transacción o su conexión no pueden ser nulas."); } const string sql = "UPDATE dbo.susc_Facturas SET IdLoteDebito = @IdLoteDebito WHERE IdFactura IN @IdsFacturas;"; var rowsAffected = await transaction.Connection.ExecuteAsync(sql, new { IdLoteDebito = idLoteDebito, IdsFacturas = idsFacturas }, transaction); return rowsAffected == idsFacturas.Count(); } public async Task> GetByPeriodoEnrichedAsync( string periodo, string? nombreSuscriptor, string? estadoPago, string? estadoFacturacion, string? tipoFactura) { var sqlBuilder = new StringBuilder(@" WITH FacturaConEmpresa AS ( -- Esta subconsulta obtiene el IdEmpresa para cada factura basándose en la primera suscripción que encuentra en sus detalles. -- Esto es seguro porque nuestra lógica de negocio asegura que todos los detalles de una factura pertenecen a la misma empresa. SELECT f.IdFactura, (SELECT TOP 1 p.Id_Empresa FROM dbo.susc_FacturaDetalles fd JOIN dbo.susc_Suscripciones s ON fd.IdSuscripcion = s.IdSuscripcion JOIN dbo.dist_dtPublicaciones p ON s.IdPublicacion = p.Id_Publicacion WHERE fd.IdFactura = f.IdFactura) AS IdEmpresa FROM dbo.susc_Facturas f WHERE f.Periodo = @Periodo ) SELECT f.*, s.NombreCompleto AS NombreSuscriptor, fce.IdEmpresa, (SELECT ISNULL(SUM(Monto), 0) FROM dbo.susc_Pagos pg WHERE pg.IdFactura = f.IdFactura AND pg.Estado = 'Aprobado') AS TotalPagado FROM dbo.susc_Facturas f JOIN dbo.susc_Suscriptores s ON f.IdSuscriptor = s.IdSuscriptor JOIN FacturaConEmpresa fce ON f.IdFactura = fce.IdFactura WHERE f.Periodo = @Periodo"); var parameters = new DynamicParameters(); parameters.Add("Periodo", periodo); if (!string.IsNullOrWhiteSpace(nombreSuscriptor)) { sqlBuilder.Append(" AND s.NombreCompleto LIKE @NombreSuscriptor"); parameters.Add("NombreSuscriptor", $"%{nombreSuscriptor}%"); } if (!string.IsNullOrWhiteSpace(estadoPago)) { sqlBuilder.Append(" AND f.EstadoPago = @EstadoPago"); parameters.Add("EstadoPago", estadoPago); } if (!string.IsNullOrWhiteSpace(estadoFacturacion)) { sqlBuilder.Append(" AND f.EstadoFacturacion = @EstadoFacturacion"); 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 { using var connection = _connectionFactory.CreateConnection(); var result = await connection.QueryAsync( sqlBuilder.ToString(), (factura, suscriptor, idEmpresa, totalPagado) => (factura, suscriptor, idEmpresa, totalPagado), parameters, splitOn: "NombreSuscriptor,IdEmpresa,TotalPagado" ); return result; } catch (Exception ex) { _logger.LogError(ex, "Error al obtener facturas enriquecidas para el período {Periodo}", periodo); return Enumerable.Empty<(Factura, string, int, decimal)>(); } } public async Task UpdateEstadoYMotivoAsync(int idFactura, string nuevoEstadoPago, string? motivoRechazo, IDbTransaction transaction) { if (transaction == null || transaction.Connection == null) { throw new ArgumentNullException(nameof(transaction), "La transacción o su conexión no pueden ser nulas."); } const string sql = @" UPDATE dbo.susc_Facturas SET EstadoPago = @NuevoEstadoPago, MotivoRechazo = @MotivoRechazo WHERE IdFactura = @IdFactura;"; var rowsAffected = await transaction.Connection.ExecuteAsync(sql, new { NuevoEstadoPago = nuevoEstadoPago, MotivoRechazo = motivoRechazo, idFactura }, transaction); return rowsAffected == 1; } public async Task GetUltimoPeriodoFacturadoAsync() { const string sql = "SELECT TOP 1 Periodo FROM dbo.susc_Facturas ORDER BY Periodo DESC;"; using var connection = _connectionFactory.CreateConnection(); return await connection.QuerySingleOrDefaultAsync(sql); } public async Task> GetFacturasConEmpresaAsync(int idSuscriptor, string periodo) { // Esta consulta es más robusta y eficiente. Obtiene la factura y el nombre de la empresa en una sola llamada. const string sql = @" SELECT f.*, e.Nombre AS NombreEmpresa FROM dbo.susc_Facturas f OUTER APPLY ( SELECT TOP 1 emp.Nombre FROM dbo.susc_FacturaDetalles fd JOIN dbo.susc_Suscripciones s ON fd.IdSuscripcion = s.IdSuscripcion JOIN dbo.dist_dtPublicaciones p ON s.IdPublicacion = p.Id_Publicacion JOIN dbo.dist_dtEmpresas emp ON p.Id_Empresa = emp.Id_Empresa WHERE fd.IdFactura = f.IdFactura ) e WHERE f.IdSuscriptor = @IdSuscriptor AND f.Periodo = @Periodo;"; try { using var connection = _connectionFactory.CreateConnection(); var result = await connection.QueryAsync( sql, (factura, nombreEmpresa) => (factura, nombreEmpresa ?? "N/A"), // Asignamos "N/A" si no encuentra empresa new { IdSuscriptor = idSuscriptor, Periodo = periodo }, splitOn: "NombreEmpresa" ); return result; } catch (Exception ex) { _logger.LogError(ex, "Error al obtener facturas con empresa para suscriptor {IdSuscriptor} y período {Periodo}", idSuscriptor, periodo); return Enumerable.Empty<(Factura, string)>(); } } public async Task> GetFacturasPagadasPendientesDeFacturar(string periodo) { // Consulta simplificada pero robusta. const string sql = @" SELECT * FROM dbo.susc_Facturas WHERE Periodo = @Periodo AND EstadoPago = 'Pagada' AND EstadoFacturacion = 'Pendiente de Facturar';"; try { using var connection = _connectionFactory.CreateConnection(); return await connection.QueryAsync(sql, new { Periodo = periodo }); } catch (Exception ex) { _logger.LogError(ex, "Error al obtener facturas pagadas pendientes de facturar para el período {Periodo}", periodo); return Enumerable.Empty(); } } public async Task> GetByIdsAsync(IEnumerable ids) { if (ids == null || !ids.Any()) { return Enumerable.Empty(); } const string sql = "SELECT * FROM dbo.susc_Facturas WHERE IdFactura IN @Ids;"; using var connection = _connectionFactory.CreateConnection(); return await connection.QueryAsync(sql, new { Ids = ids }); } } }