From 19e7192a166cf020d4d10470f6e8af750c9d8b79 Mon Sep 17 00:00:00 2001 From: dmolinari Date: Tue, 29 Jul 2025 14:11:50 -0300 Subject: [PATCH] =?UTF-8?q?Feat:=20Se=20a=C3=B1aden=20las=20capas=20de=20m?= =?UTF-8?q?odelos=20y=20respositorios=20para=20el=20modulo=20de=20Suscripc?= =?UTF-8?q?iones?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Suscripciones/FacturaRepository.cs | 95 +++++++++ .../Suscripciones/FormaPagoRepository.cs | 54 +++++ .../Suscripciones/IFacturaRepository.cs | 16 ++ .../Suscripciones/IFormaPagoRepository.cs | 10 + .../Suscripciones/ILoteDebitoRepository.cs | 10 + .../Suscripciones/IPagoRepository.cs | 11 + .../Suscripciones/ISuscripcionRepository.cs | 14 ++ .../Suscripciones/ISuscriptorRepository.cs | 16 ++ .../Suscripciones/LoteDebitoRepository.cs | 43 ++++ .../Suscripciones/PagoRepository.cs | 58 ++++++ .../Suscripciones/SuscripcionRepository.cs | 115 +++++++++++ .../Suscripciones/SuscriptorRepository.cs | 195 ++++++++++++++++++ .../Models/Suscripciones/Factura.cs | 18 ++ .../Models/Suscripciones/FormaPago.cs | 10 + .../Models/Suscripciones/LoteDebito.cs | 13 ++ .../Models/Suscripciones/Pago.cs | 15 ++ .../Models/Suscripciones/Suscripcion.cs | 18 ++ .../Models/Suscripciones/Suscriptor.cs | 21 ++ 18 files changed, 732 insertions(+) create mode 100644 Backend/GestionIntegral.Api/Data/Repositories/Suscripciones/FacturaRepository.cs create mode 100644 Backend/GestionIntegral.Api/Data/Repositories/Suscripciones/FormaPagoRepository.cs create mode 100644 Backend/GestionIntegral.Api/Data/Repositories/Suscripciones/IFacturaRepository.cs create mode 100644 Backend/GestionIntegral.Api/Data/Repositories/Suscripciones/IFormaPagoRepository.cs create mode 100644 Backend/GestionIntegral.Api/Data/Repositories/Suscripciones/ILoteDebitoRepository.cs create mode 100644 Backend/GestionIntegral.Api/Data/Repositories/Suscripciones/IPagoRepository.cs create mode 100644 Backend/GestionIntegral.Api/Data/Repositories/Suscripciones/ISuscripcionRepository.cs create mode 100644 Backend/GestionIntegral.Api/Data/Repositories/Suscripciones/ISuscriptorRepository.cs create mode 100644 Backend/GestionIntegral.Api/Data/Repositories/Suscripciones/LoteDebitoRepository.cs create mode 100644 Backend/GestionIntegral.Api/Data/Repositories/Suscripciones/PagoRepository.cs create mode 100644 Backend/GestionIntegral.Api/Data/Repositories/Suscripciones/SuscripcionRepository.cs create mode 100644 Backend/GestionIntegral.Api/Data/Repositories/Suscripciones/SuscriptorRepository.cs create mode 100644 Backend/GestionIntegral.Api/Models/Suscripciones/Factura.cs create mode 100644 Backend/GestionIntegral.Api/Models/Suscripciones/FormaPago.cs create mode 100644 Backend/GestionIntegral.Api/Models/Suscripciones/LoteDebito.cs create mode 100644 Backend/GestionIntegral.Api/Models/Suscripciones/Pago.cs create mode 100644 Backend/GestionIntegral.Api/Models/Suscripciones/Suscripcion.cs create mode 100644 Backend/GestionIntegral.Api/Models/Suscripciones/Suscriptor.cs diff --git a/Backend/GestionIntegral.Api/Data/Repositories/Suscripciones/FacturaRepository.cs b/Backend/GestionIntegral.Api/Data/Repositories/Suscripciones/FacturaRepository.cs new file mode 100644 index 0000000..15ef425 --- /dev/null +++ b/Backend/GestionIntegral.Api/Data/Repositories/Suscripciones/FacturaRepository.cs @@ -0,0 +1,95 @@ +// Archivo: GestionIntegral.Api/Data/Repositories/Suscripciones/FacturaRepository.cs + +using Dapper; +using GestionIntegral.Api.Models.Suscripciones; +using System.Data; + +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 GetBySuscripcionYPeriodoAsync(int idSuscripcion, string periodo, IDbTransaction transaction) + { + const string sql = "SELECT TOP 1 * FROM dbo.susc_Facturas WHERE IdSuscripcion = @IdSuscripcion 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 { IdSuscripcion = idSuscripcion, Periodo = periodo }, transaction); + } + + 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 + (IdSuscripcion, Periodo, FechaEmision, FechaVencimiento, ImporteBruto, + DescuentoAplicado, ImporteFinal, Estado) + OUTPUT INSERTED.* + VALUES + (@IdSuscripcion, @Periodo, @FechaEmision, @FechaVencimiento, @ImporteBruto, + @DescuentoAplicado, @ImporteFinal, @Estado);"; + + return await transaction.Connection.QuerySingleAsync(sqlInsert, nuevaFactura, transaction); + } + + public async Task UpdateEstadoAsync(int idFactura, string nuevoEstado, 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 Estado = @NuevoEstado WHERE IdFactura = @IdFactura;"; + var rowsAffected = await transaction.Connection.ExecuteAsync(sql, new { NuevoEstado = nuevoEstado, IdFactura = 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, Estado = 'Pendiente de Cobro' WHERE IdFactura = @IdFactura;"; + var rowsAffected = await transaction.Connection.ExecuteAsync(sql, new { NumeroFactura = numeroFactura, IdFactura = 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, Estado = 'Enviada a Débito' WHERE IdFactura IN @IdsFacturas;"; + var rowsAffected = await transaction.Connection.ExecuteAsync(sql, new { IdLoteDebito = idLoteDebito, IdsFacturas = idsFacturas }, transaction); + return rowsAffected == idsFacturas.Count(); + } + } +} \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Data/Repositories/Suscripciones/FormaPagoRepository.cs b/Backend/GestionIntegral.Api/Data/Repositories/Suscripciones/FormaPagoRepository.cs new file mode 100644 index 0000000..f559317 --- /dev/null +++ b/Backend/GestionIntegral.Api/Data/Repositories/Suscripciones/FormaPagoRepository.cs @@ -0,0 +1,54 @@ +using Dapper; +using GestionIntegral.Api.Models.Suscripciones; + +namespace GestionIntegral.Api.Data.Repositories.Suscripciones +{ + public class FormaPagoRepository : IFormaPagoRepository + { + private readonly DbConnectionFactory _connectionFactory; + private readonly ILogger _logger; + + public FormaPagoRepository(DbConnectionFactory connectionFactory, ILogger logger) + { + _connectionFactory = connectionFactory; + _logger = logger; + } + + public async Task> GetAllAsync() + { + const string sql = @" + SELECT IdFormaPago, Nombre, RequiereCBU, Activo + FROM dbo.susc_FormasDePago + WHERE Activo = 1 + ORDER BY Nombre;"; + try + { + using var connection = _connectionFactory.CreateConnection(); + return await connection.QueryAsync(sql); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error al obtener todas las Formas de Pago activas."); + return Enumerable.Empty(); + } + } + + public async Task GetByIdAsync(int id) + { + const string sql = @" + SELECT IdFormaPago, Nombre, RequiereCBU, Activo + FROM dbo.susc_FormasDePago + WHERE IdFormaPago = @Id;"; + try + { + using var connection = _connectionFactory.CreateConnection(); + return await connection.QuerySingleOrDefaultAsync(sql, new { Id = id }); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error al obtener Forma de Pago por ID: {IdFormaPago}", id); + return null; + } + } + } +} \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Data/Repositories/Suscripciones/IFacturaRepository.cs b/Backend/GestionIntegral.Api/Data/Repositories/Suscripciones/IFacturaRepository.cs new file mode 100644 index 0000000..1c4a28b --- /dev/null +++ b/Backend/GestionIntegral.Api/Data/Repositories/Suscripciones/IFacturaRepository.cs @@ -0,0 +1,16 @@ +using GestionIntegral.Api.Models.Suscripciones; +using System.Data; + +namespace GestionIntegral.Api.Data.Repositories.Suscripciones +{ + public interface IFacturaRepository + { + Task GetByIdAsync(int idFactura); + Task> GetByPeriodoAsync(string periodo); + Task GetBySuscripcionYPeriodoAsync(int idSuscripcion, string periodo, IDbTransaction transaction); + Task CreateAsync(Factura nuevaFactura, IDbTransaction transaction); + Task UpdateEstadoAsync(int idFactura, string nuevoEstado, IDbTransaction transaction); + Task UpdateNumeroFacturaAsync(int idFactura, string numeroFactura, IDbTransaction transaction); + Task UpdateLoteDebitoAsync(IEnumerable idsFacturas, int idLoteDebito, IDbTransaction transaction); + } +} \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Data/Repositories/Suscripciones/IFormaPagoRepository.cs b/Backend/GestionIntegral.Api/Data/Repositories/Suscripciones/IFormaPagoRepository.cs new file mode 100644 index 0000000..ea60e0e --- /dev/null +++ b/Backend/GestionIntegral.Api/Data/Repositories/Suscripciones/IFormaPagoRepository.cs @@ -0,0 +1,10 @@ +using GestionIntegral.Api.Models.Suscripciones; + +namespace GestionIntegral.Api.Data.Repositories.Suscripciones +{ + public interface IFormaPagoRepository + { + Task> GetAllAsync(); + Task GetByIdAsync(int id); + } +} \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Data/Repositories/Suscripciones/ILoteDebitoRepository.cs b/Backend/GestionIntegral.Api/Data/Repositories/Suscripciones/ILoteDebitoRepository.cs new file mode 100644 index 0000000..c64dc2d --- /dev/null +++ b/Backend/GestionIntegral.Api/Data/Repositories/Suscripciones/ILoteDebitoRepository.cs @@ -0,0 +1,10 @@ +using GestionIntegral.Api.Models.Suscripciones; +using System.Data; + +namespace GestionIntegral.Api.Data.Repositories.Suscripciones +{ + public interface ILoteDebitoRepository + { + Task CreateAsync(LoteDebito nuevoLote, IDbTransaction transaction); + } +} \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Data/Repositories/Suscripciones/IPagoRepository.cs b/Backend/GestionIntegral.Api/Data/Repositories/Suscripciones/IPagoRepository.cs new file mode 100644 index 0000000..c6ee740 --- /dev/null +++ b/Backend/GestionIntegral.Api/Data/Repositories/Suscripciones/IPagoRepository.cs @@ -0,0 +1,11 @@ +using GestionIntegral.Api.Models.Suscripciones; +using System.Data; + +namespace GestionIntegral.Api.Data.Repositories.Suscripciones +{ + public interface IPagoRepository + { + Task> GetByFacturaIdAsync(int idFactura); + Task CreateAsync(Pago nuevoPago, IDbTransaction transaction); + } +} \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Data/Repositories/Suscripciones/ISuscripcionRepository.cs b/Backend/GestionIntegral.Api/Data/Repositories/Suscripciones/ISuscripcionRepository.cs new file mode 100644 index 0000000..7e421b2 --- /dev/null +++ b/Backend/GestionIntegral.Api/Data/Repositories/Suscripciones/ISuscripcionRepository.cs @@ -0,0 +1,14 @@ +using GestionIntegral.Api.Models.Suscripciones; +using System.Data; + +namespace GestionIntegral.Api.Data.Repositories.Suscripciones +{ + public interface ISuscripcionRepository + { + Task> GetBySuscriptorIdAsync(int idSuscriptor); + Task GetByIdAsync(int idSuscripcion); + Task CreateAsync(Suscripcion nuevaSuscripcion, IDbTransaction transaction); + Task UpdateAsync(Suscripcion suscripcionAActualizar, IDbTransaction transaction); + Task> GetAllActivasParaFacturacion(string periodo, IDbTransaction transaction); + } +} \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Data/Repositories/Suscripciones/ISuscriptorRepository.cs b/Backend/GestionIntegral.Api/Data/Repositories/Suscripciones/ISuscriptorRepository.cs new file mode 100644 index 0000000..a2e18d3 --- /dev/null +++ b/Backend/GestionIntegral.Api/Data/Repositories/Suscripciones/ISuscriptorRepository.cs @@ -0,0 +1,16 @@ +using GestionIntegral.Api.Models.Suscripciones; +using System.Data; + +namespace GestionIntegral.Api.Data.Repositories.Suscripciones +{ + public interface ISuscriptorRepository + { + Task> GetAllAsync(string? nombreFilter, string? nroDocFilter, bool soloActivos); + Task GetByIdAsync(int id); + Task CreateAsync(Suscriptor nuevoSuscriptor, IDbTransaction transaction); + Task UpdateAsync(Suscriptor suscriptorAActualizar, IDbTransaction transaction); + Task ToggleActivoAsync(int id, bool activar, int idUsuario, IDbTransaction transaction); + Task ExistsByDocumentoAsync(string tipoDocumento, string nroDocumento, int? excludeId = null); + Task IsInUseAsync(int id); + } +} \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Data/Repositories/Suscripciones/LoteDebitoRepository.cs b/Backend/GestionIntegral.Api/Data/Repositories/Suscripciones/LoteDebitoRepository.cs new file mode 100644 index 0000000..d6712a1 --- /dev/null +++ b/Backend/GestionIntegral.Api/Data/Repositories/Suscripciones/LoteDebitoRepository.cs @@ -0,0 +1,43 @@ +using Dapper; +using GestionIntegral.Api.Models.Suscripciones; +using System.Data; + +namespace GestionIntegral.Api.Data.Repositories.Suscripciones +{ + public class LoteDebitoRepository : ILoteDebitoRepository + { + private readonly DbConnectionFactory _connectionFactory; + private readonly ILogger _logger; + + public LoteDebitoRepository(DbConnectionFactory connectionFactory, ILogger logger) + { + _connectionFactory = connectionFactory; + _logger = logger; + } + + public async Task CreateAsync(LoteDebito nuevoLote, 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_LotesDebito + (FechaGeneracion, Periodo, NombreArchivo, ImporteTotal, CantidadRegistros, IdUsuarioGeneracion) + OUTPUT INSERTED.* + VALUES + (GETDATE(), @Periodo, @NombreArchivo, @ImporteTotal, @CantidadRegistros, @IdUsuarioGeneracion);"; + + try + { + return await transaction.Connection.QuerySingleAsync(sqlInsert, nuevoLote, transaction); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error al crear el registro de LoteDebito para el período {Periodo}", nuevoLote.Periodo); + return null; + } + } + } +} \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Data/Repositories/Suscripciones/PagoRepository.cs b/Backend/GestionIntegral.Api/Data/Repositories/Suscripciones/PagoRepository.cs new file mode 100644 index 0000000..e2f849b --- /dev/null +++ b/Backend/GestionIntegral.Api/Data/Repositories/Suscripciones/PagoRepository.cs @@ -0,0 +1,58 @@ +using Dapper; +using GestionIntegral.Api.Models.Suscripciones; +using System.Data; + +namespace GestionIntegral.Api.Data.Repositories.Suscripciones +{ + public class PagoRepository : IPagoRepository + { + private readonly DbConnectionFactory _connectionFactory; + private readonly ILogger _logger; + + public PagoRepository(DbConnectionFactory connectionFactory, ILogger logger) + { + _connectionFactory = connectionFactory; + _logger = logger; + } + + public async Task> GetByFacturaIdAsync(int idFactura) + { + const string sql = "SELECT * FROM dbo.susc_Pagos WHERE IdFactura = @IdFactura ORDER BY FechaPago DESC;"; + try + { + using var connection = _connectionFactory.CreateConnection(); + return await connection.QueryAsync(sql, new { idFactura }); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error al obtener pagos para la factura ID: {IdFactura}", idFactura); + return Enumerable.Empty(); + } + } + + public async Task CreateAsync(Pago nuevoPago, 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_Pagos + (IdFactura, FechaPago, IdFormaPago, Monto, Estado, Referencia, Observaciones, IdUsuarioRegistro) + OUTPUT INSERTED.* + VALUES + (@IdFactura, @FechaPago, @IdFormaPago, @Monto, @Estado, @Referencia, @Observaciones, @IdUsuarioRegistro);"; + + try + { + return await transaction.Connection.QuerySingleAsync(sqlInsert, nuevoPago, transaction); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error al registrar un nuevo Pago para la Factura ID: {IdFactura}", nuevoPago.IdFactura); + return null; + } + } + } +} \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Data/Repositories/Suscripciones/SuscripcionRepository.cs b/Backend/GestionIntegral.Api/Data/Repositories/Suscripciones/SuscripcionRepository.cs new file mode 100644 index 0000000..7f98165 --- /dev/null +++ b/Backend/GestionIntegral.Api/Data/Repositories/Suscripciones/SuscripcionRepository.cs @@ -0,0 +1,115 @@ +using Dapper; +using GestionIntegral.Api.Models.Suscripciones; +using System.Data; + +namespace GestionIntegral.Api.Data.Repositories.Suscripciones +{ + public class SuscripcionRepository : ISuscripcionRepository + { + private readonly DbConnectionFactory _connectionFactory; + private readonly ILogger _logger; + + public SuscripcionRepository(DbConnectionFactory connectionFactory, ILogger logger) + { + _connectionFactory = connectionFactory; + _logger = logger; + } + + public async Task GetByIdAsync(int idSuscripcion) + { + const string sql = "SELECT * FROM dbo.susc_Suscripciones WHERE IdSuscripcion = @IdSuscripcion;"; + try + { + using var connection = _connectionFactory.CreateConnection(); + return await connection.QuerySingleOrDefaultAsync(sql, new { IdSuscripcion = idSuscripcion }); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error al obtener Suscripción por ID: {IdSuscripcion}", idSuscripcion); + return null; + } + } + + public async Task> GetBySuscriptorIdAsync(int idSuscriptor) + { + const string sql = "SELECT * FROM dbo.susc_Suscripciones WHERE IdSuscriptor = @IdSuscriptor ORDER BY FechaInicio DESC;"; + try + { + using var connection = _connectionFactory.CreateConnection(); + return await connection.QueryAsync(sql, new { IdSuscriptor = idSuscriptor }); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error al obtener suscripciones para el suscriptor ID: {IdSuscriptor}", idSuscriptor); + return Enumerable.Empty(); + } + } + + public async Task> GetAllActivasParaFacturacion(string periodo, IDbTransaction transaction) + { + // Lógica para determinar el rango del período (ej. '2023-11') + var year = int.Parse(periodo.Split('-')[0]); + var month = int.Parse(periodo.Split('-')[1]); + var primerDiaMes = new DateTime(year, month, 1); + var ultimoDiaMes = primerDiaMes.AddMonths(1).AddDays(-1); + + const string sql = @" + SELECT s.* + FROM dbo.susc_Suscripciones s + JOIN dbo.susc_Suscriptores su ON s.IdSuscriptor = su.IdSuscriptor + WHERE s.Estado = 'Activa' + AND su.Activo = 1 + AND s.FechaInicio <= @UltimoDiaMes + AND (s.FechaFin IS NULL OR s.FechaFin >= @PrimerDiaMes);"; + + 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.QueryAsync(sql, new { PrimerDiaMes = primerDiaMes, UltimoDiaMes = ultimoDiaMes }, transaction); + } + + public async Task CreateAsync(Suscripcion nuevaSuscripcion, 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_Suscripciones + (IdSuscriptor, IdPublicacion, FechaInicio, FechaFin, Estado, DiasEntrega, + Observaciones, IdUsuarioAlta, FechaAlta) + OUTPUT INSERTED.* + VALUES + (@IdSuscriptor, @IdPublicacion, @FechaInicio, @FechaFin, @Estado, @DiasEntrega, + @Observaciones, @IdUsuarioAlta, GETDATE());"; + + return await transaction.Connection.QuerySingleAsync(sqlInsert, nuevaSuscripcion, transaction); + } + + public async Task UpdateAsync(Suscripcion suscripcion, 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 sqlUpdate = @" + UPDATE dbo.susc_Suscripciones SET + IdPublicacion = @IdPublicacion, + FechaInicio = @FechaInicio, + FechaFin = @FechaFin, + Estado = @Estado, + DiasEntrega = @DiasEntrega, + Observaciones = @Observaciones, + IdUsuarioMod = @IdUsuarioMod, + FechaMod = @FechaMod + WHERE IdSuscripcion = @IdSuscripcion;"; + + var rowsAffected = await transaction.Connection.ExecuteAsync(sqlUpdate, suscripcion, transaction); + return rowsAffected == 1; + } + } +} \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Data/Repositories/Suscripciones/SuscriptorRepository.cs b/Backend/GestionIntegral.Api/Data/Repositories/Suscripciones/SuscriptorRepository.cs new file mode 100644 index 0000000..72e8c28 --- /dev/null +++ b/Backend/GestionIntegral.Api/Data/Repositories/Suscripciones/SuscriptorRepository.cs @@ -0,0 +1,195 @@ +using Dapper; +using GestionIntegral.Api.Models.Suscripciones; +using System.Data; +using System.Text; + +namespace GestionIntegral.Api.Data.Repositories.Suscripciones +{ + public class SuscriptorRepository : ISuscriptorRepository + { + private readonly DbConnectionFactory _connectionFactory; + private readonly ILogger _logger; + + public SuscriptorRepository(DbConnectionFactory connectionFactory, ILogger logger) + { + _connectionFactory = connectionFactory; + _logger = logger; + } + + public async Task> GetAllAsync(string? nombreFilter, string? nroDocFilter, bool soloActivos) + { + var sqlBuilder = new StringBuilder(@" + SELECT IdSuscriptor, NombreCompleto, Email, Telefono, Direccion, TipoDocumento, + NroDocumento, CBU, IdFormaPagoPreferida, Observaciones, Activo, + IdUsuarioAlta, FechaAlta, IdUsuarioMod, FechaMod + FROM dbo.susc_Suscriptores WHERE 1=1"); + + var parameters = new DynamicParameters(); + + if (soloActivos) + { + sqlBuilder.Append(" AND Activo = 1"); + } + if (!string.IsNullOrWhiteSpace(nombreFilter)) + { + sqlBuilder.Append(" AND NombreCompleto LIKE @NombreFilter"); + parameters.Add("NombreFilter", $"%{nombreFilter}%"); + } + if (!string.IsNullOrWhiteSpace(nroDocFilter)) + { + sqlBuilder.Append(" AND NroDocumento LIKE @NroDocFilter"); + parameters.Add("NroDocFilter", $"%{nroDocFilter}%"); + } + sqlBuilder.Append(" ORDER BY NombreCompleto;"); + + try + { + using var connection = _connectionFactory.CreateConnection(); + return await connection.QueryAsync(sqlBuilder.ToString(), parameters); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error al obtener todos los Suscriptores."); + return Enumerable.Empty(); + } + } + + public async Task GetByIdAsync(int id) + { + const string sql = @" + SELECT IdSuscriptor, NombreCompleto, Email, Telefono, Direccion, TipoDocumento, + NroDocumento, CBU, IdFormaPagoPreferida, Observaciones, Activo, + IdUsuarioAlta, FechaAlta, IdUsuarioMod, FechaMod + FROM dbo.susc_Suscriptores WHERE IdSuscriptor = @Id;"; + try + { + using var connection = _connectionFactory.CreateConnection(); + return await connection.QuerySingleOrDefaultAsync(sql, new { Id = id }); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error al obtener Suscriptor por ID: {IdSuscriptor}", id); + return null; + } + } + + public async Task ExistsByDocumentoAsync(string tipoDocumento, string nroDocumento, int? excludeId = null) + { + var sqlBuilder = new StringBuilder("SELECT COUNT(1) FROM dbo.susc_Suscriptores WHERE TipoDocumento = @TipoDocumento AND NroDocumento = @NroDocumento"); + var parameters = new DynamicParameters(); + parameters.Add("TipoDocumento", tipoDocumento); + parameters.Add("NroDocumento", nroDocumento); + + if (excludeId.HasValue) + { + sqlBuilder.Append(" AND IdSuscriptor != @ExcludeId"); + parameters.Add("ExcludeId", excludeId.Value); + } + + try + { + using var connection = _connectionFactory.CreateConnection(); + return await connection.ExecuteScalarAsync(sqlBuilder.ToString(), parameters); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error en ExistsByDocumentoAsync para Suscriptor."); + return true; // Asumir que existe si hay error para prevenir duplicados. + } + } + + public async Task IsInUseAsync(int id) + { + // Un suscriptor está en uso si tiene suscripciones activas. + const string sql = "SELECT TOP 1 1 FROM dbo.susc_Suscripciones WHERE IdSuscriptor = @Id AND Estado = 'Activa'"; + try + { + using var connection = _connectionFactory.CreateConnection(); + var inUse = await connection.ExecuteScalarAsync(sql, new { Id = id }); + return inUse.HasValue && inUse.Value == 1; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error en IsInUseAsync para Suscriptor ID: {IdSuscriptor}", id); + return true; // Asumir en uso si hay error. + } + } + + public async Task CreateAsync(Suscriptor nuevoSuscriptor, 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_Suscriptores + (NombreCompleto, Email, Telefono, Direccion, TipoDocumento, NroDocumento, + CBU, IdFormaPagoPreferida, Observaciones, Activo, IdUsuarioAlta, FechaAlta) + OUTPUT INSERTED.* + VALUES + (@NombreCompleto, @Email, @Telefono, @Direccion, @TipoDocumento, @NroDocumento, + @CBU, @IdFormaPagoPreferida, @Observaciones, 1, @IdUsuarioAlta, GETDATE());"; + + var suscriptorCreado = await transaction.Connection.QuerySingleAsync( + sqlInsert, + nuevoSuscriptor, + transaction: transaction + ); + + // No se necesita historial para la creación, ya que la propia tabla tiene campos de auditoría. + // Si se necesitara una tabla _H, aquí iría el INSERT a esa tabla. + + return suscriptorCreado; + } + + public async Task UpdateAsync(Suscriptor suscriptor, IDbTransaction transaction) + { + // El servicio ya ha poblado IdUsuarioMod y FechaMod en la entidad. + const string sqlUpdate = @" + UPDATE dbo.susc_Suscriptores SET + NombreCompleto = @NombreCompleto, + Email = @Email, + Telefono = @Telefono, + Direccion = @Direccion, + TipoDocumento = @TipoDocumento, + NroDocumento = @NroDocumento, + CBU = @CBU, + IdFormaPagoPreferida = @IdFormaPagoPreferida, + Observaciones = @Observaciones, + IdUsuarioMod = @IdUsuarioMod, + FechaMod = @FechaMod + WHERE IdSuscriptor = @IdSuscriptor;"; + + if (transaction == null || transaction.Connection == null) + { + throw new ArgumentNullException(nameof(transaction), "La transacción o su conexión no pueden ser nulas."); + } + + var rowsAffected = await transaction.Connection.ExecuteAsync(sqlUpdate, suscriptor, transaction: transaction); + return rowsAffected == 1; + } + + public async Task ToggleActivoAsync(int id, bool activar, int idUsuario, IDbTransaction transaction) + { + const string sqlToggle = @" + UPDATE dbo.susc_Suscriptores SET + Activo = @Activar, + IdUsuarioMod = @IdUsuario, + FechaMod = GETDATE() + WHERE IdSuscriptor = @Id;"; + + if (transaction == null || transaction.Connection == null) + { + throw new ArgumentNullException(nameof(transaction), "La transacción o su conexión no pueden ser nulas."); + } + + var rowsAffected = await transaction.Connection.ExecuteAsync( + sqlToggle, + new { Activar = activar, IdUsuario = idUsuario, Id = id }, + transaction: transaction + ); + return rowsAffected == 1; + } + } +} \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Models/Suscripciones/Factura.cs b/Backend/GestionIntegral.Api/Models/Suscripciones/Factura.cs new file mode 100644 index 0000000..9307e23 --- /dev/null +++ b/Backend/GestionIntegral.Api/Models/Suscripciones/Factura.cs @@ -0,0 +1,18 @@ +namespace GestionIntegral.Api.Models.Suscripciones +{ + public class Factura + { + public int IdFactura { get; set; } + public int IdSuscripcion { get; set; } + public string Periodo { get; set; } = string.Empty; + public DateTime FechaEmision { get; set; } + public DateTime FechaVencimiento { get; set; } + public decimal ImporteBruto { get; set; } + public decimal DescuentoAplicado { get; set; } + public decimal ImporteFinal { get; set; } + public string Estado { get; set; } = string.Empty; + public string? NumeroFactura { get; set; } + public int? IdLoteDebito { get; set; } + public string? MotivoRechazo { get; set; } + } +} \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Models/Suscripciones/FormaPago.cs b/Backend/GestionIntegral.Api/Models/Suscripciones/FormaPago.cs new file mode 100644 index 0000000..5a3bbe5 --- /dev/null +++ b/Backend/GestionIntegral.Api/Models/Suscripciones/FormaPago.cs @@ -0,0 +1,10 @@ +namespace GestionIntegral.Api.Models.Suscripciones +{ + public class FormaPago + { + public int IdFormaPago { get; set; } + public string Nombre { get; set; } = string.Empty; + public bool RequiereCBU { get; set; } + public bool Activo { get; set; } + } +} \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Models/Suscripciones/LoteDebito.cs b/Backend/GestionIntegral.Api/Models/Suscripciones/LoteDebito.cs new file mode 100644 index 0000000..47339e5 --- /dev/null +++ b/Backend/GestionIntegral.Api/Models/Suscripciones/LoteDebito.cs @@ -0,0 +1,13 @@ +namespace GestionIntegral.Api.Models.Suscripciones +{ + public class LoteDebito + { + public int IdLoteDebito { get; set; } + public DateTime FechaGeneracion { get; set; } + public string Periodo { get; set; } = string.Empty; + public string NombreArchivo { get; set; } = string.Empty; + public decimal ImporteTotal { get; set; } + public int CantidadRegistros { get; set; } + public int IdUsuarioGeneracion { get; set; } + } +} \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Models/Suscripciones/Pago.cs b/Backend/GestionIntegral.Api/Models/Suscripciones/Pago.cs new file mode 100644 index 0000000..e675235 --- /dev/null +++ b/Backend/GestionIntegral.Api/Models/Suscripciones/Pago.cs @@ -0,0 +1,15 @@ +namespace GestionIntegral.Api.Models.Suscripciones +{ + public class Pago + { + public int IdPago { get; set; } + public int IdFactura { get; set; } + public DateTime FechaPago { get; set; } + public int IdFormaPago { get; set; } + public decimal Monto { get; set; } + public string Estado { get; set; } = string.Empty; + public string? Referencia { get; set; } + public string? Observaciones { get; set; } + public int IdUsuarioRegistro { get; set; } + } +} \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Models/Suscripciones/Suscripcion.cs b/Backend/GestionIntegral.Api/Models/Suscripciones/Suscripcion.cs new file mode 100644 index 0000000..580a9d1 --- /dev/null +++ b/Backend/GestionIntegral.Api/Models/Suscripciones/Suscripcion.cs @@ -0,0 +1,18 @@ +namespace GestionIntegral.Api.Models.Suscripciones +{ + public class Suscripcion + { + public int IdSuscripcion { get; set; } + public int IdSuscriptor { get; set; } + public int IdPublicacion { get; set; } + public DateTime FechaInicio { get; set; } + public DateTime? FechaFin { get; set; } + public string Estado { get; set; } = string.Empty; + public string DiasEntrega { get; set; } = string.Empty; + public string? Observaciones { get; set; } + public int IdUsuarioAlta { get; set; } + public DateTime FechaAlta { get; set; } + public int? IdUsuarioMod { get; set; } + public DateTime? FechaMod { get; set; } + } +} \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Models/Suscripciones/Suscriptor.cs b/Backend/GestionIntegral.Api/Models/Suscripciones/Suscriptor.cs new file mode 100644 index 0000000..cc6e1bb --- /dev/null +++ b/Backend/GestionIntegral.Api/Models/Suscripciones/Suscriptor.cs @@ -0,0 +1,21 @@ +namespace GestionIntegral.Api.Models.Suscripciones +{ + public class Suscriptor + { + public int IdSuscriptor { get; set; } + public string NombreCompleto { get; set; } = string.Empty; + public string? Email { get; set; } + public string? Telefono { get; set; } + public string Direccion { get; set; } = string.Empty; + public string TipoDocumento { get; set; } = string.Empty; + public string NroDocumento { get; set; } = string.Empty; + public string? CBU { get; set; } + public int IdFormaPagoPreferida { get; set; } + public string? Observaciones { get; set; } + public bool Activo { get; set; } + public int IdUsuarioAlta { get; set; } + public DateTime FechaAlta { get; set; } + public int? IdUsuarioMod { get; set; } + public DateTime? FechaMod { get; set; } + } +} \ No newline at end of file