using GestionIntegral.Api.Data; using GestionIntegral.Api.Data.Repositories.Suscripciones; using GestionIntegral.Api.Data.Repositories.Usuarios; using GestionIntegral.Api.Dtos.Suscripciones; using GestionIntegral.Api.Models.Suscripciones; using System.Data; using GestionIntegral.Api.Data.Repositories.Distribucion; namespace GestionIntegral.Api.Services.Suscripciones { public class AjusteService : IAjusteService { private readonly IAjusteRepository _ajusteRepository; private readonly ISuscriptorRepository _suscriptorRepository; private readonly IUsuarioRepository _usuarioRepository; private readonly IEmpresaRepository _empresaRepository; private readonly IFacturaRepository _facturaRepository; private readonly DbConnectionFactory _connectionFactory; private readonly ILogger _logger; public AjusteService( IAjusteRepository ajusteRepository, ISuscriptorRepository suscriptorRepository, IUsuarioRepository usuarioRepository, IEmpresaRepository empresaRepository, IFacturaRepository facturaRepository, DbConnectionFactory connectionFactory, ILogger logger) { _ajusteRepository = ajusteRepository; _suscriptorRepository = suscriptorRepository; _usuarioRepository = usuarioRepository; _empresaRepository = empresaRepository; _facturaRepository = facturaRepository; _connectionFactory = connectionFactory; _logger = logger; } private async Task MapToDto(Ajuste ajuste) { if (ajuste == null) return null; var usuario = await _usuarioRepository.GetByIdAsync(ajuste.IdUsuarioAlta); var empresa = await _empresaRepository.GetByIdAsync(ajuste.IdEmpresa); return new AjusteDto { IdAjuste = ajuste.IdAjuste, IdSuscriptor = ajuste.IdSuscriptor, IdEmpresa = ajuste.IdEmpresa, NombreEmpresa = empresa?.Nombre ?? "N/A", FechaAjuste = ajuste.FechaAjuste.ToString("yyyy-MM-dd"), TipoAjuste = ajuste.TipoAjuste, Monto = ajuste.Monto, Motivo = ajuste.Motivo, Estado = ajuste.Estado, IdFacturaAplicado = ajuste.IdFacturaAplicado, FechaAlta = ajuste.FechaAlta.ToString("yyyy-MM-dd HH:mm"), NombreUsuarioAlta = usuario != null ? $"{usuario.Nombre} {usuario.Apellido}" : "N/A" }; } public async Task> ObtenerAjustesPorSuscriptor(int idSuscriptor, DateTime? fechaDesde, DateTime? fechaHasta) { var ajustes = await _ajusteRepository.GetAjustesPorSuscriptorAsync(idSuscriptor, fechaDesde, fechaHasta); if (!ajustes.Any()) { return Enumerable.Empty(); } // 1. Recolectar IDs de usuarios, empresas Y FACTURAS var idsUsuarios = ajustes.Select(a => a.IdUsuarioAlta).Distinct().ToList(); var idsEmpresas = ajustes.Select(a => a.IdEmpresa).Distinct().ToList(); var idsFacturas = ajustes.Where(a => a.IdFacturaAplicado.HasValue) .Select(a => a.IdFacturaAplicado!.Value) .Distinct().ToList(); // 2. Obtener todos los datos necesarios en consultas masivas var usuariosTask = _usuarioRepository.GetByIdsAsync(idsUsuarios); var empresasTask = _empresaRepository.GetAllAsync(null, null); var facturasTask = _facturaRepository.GetByIdsAsync(idsFacturas); await Task.WhenAll(usuariosTask, empresasTask, facturasTask); // 3. Convertir a diccionarios para búsqueda rápida var usuariosDict = (await usuariosTask).ToDictionary(u => u.Id); var empresasDict = (await empresasTask).ToDictionary(e => e.IdEmpresa); var facturasDict = (await facturasTask).ToDictionary(f => f.IdFactura); // 4. Mapear en memoria, ahora con la información de la factura disponible var dtos = ajustes.Select(ajuste => { usuariosDict.TryGetValue(ajuste.IdUsuarioAlta, out var usuario); empresasDict.TryGetValue(ajuste.IdEmpresa, out var empresa); // Buscar la factura en el diccionario si el ajuste está aplicado facturasDict.TryGetValue(ajuste.IdFacturaAplicado ?? 0, out var factura); return new AjusteDto { IdAjuste = ajuste.IdAjuste, IdSuscriptor = ajuste.IdSuscriptor, IdEmpresa = ajuste.IdEmpresa, NombreEmpresa = empresa?.Nombre ?? "N/A", FechaAjuste = ajuste.FechaAjuste.ToString("yyyy-MM-dd"), TipoAjuste = ajuste.TipoAjuste, Monto = ajuste.Monto, Motivo = ajuste.Motivo, Estado = ajuste.Estado, IdFacturaAplicado = ajuste.IdFacturaAplicado, NumeroFacturaAplicado = factura?.NumeroFactura, FechaAlta = ajuste.FechaAlta.ToString("yyyy-MM-dd HH:mm"), NombreUsuarioAlta = usuario != null ? $"{usuario.Nombre} {usuario.Apellido}" : "N/A" }; }); return dtos; } public async Task<(AjusteDto? Ajuste, string? Error)> CrearAjusteManual(CreateAjusteDto createDto, int idUsuario) { var suscriptor = await _suscriptorRepository.GetByIdAsync(createDto.IdSuscriptor); if (suscriptor == null) { return (null, "El suscriptor especificado no existe."); } var empresa = await _empresaRepository.GetByIdAsync(createDto.IdEmpresa); if (empresa == null) { return (null, "La empresa especificada no existe."); } var nuevoAjuste = new Ajuste { IdSuscriptor = createDto.IdSuscriptor, IdEmpresa = createDto.IdEmpresa, FechaAjuste = createDto.FechaAjuste.Date, TipoAjuste = createDto.TipoAjuste, Monto = createDto.Monto, Motivo = createDto.Motivo, IdUsuarioAlta = idUsuario }; using var connection = _connectionFactory.CreateConnection(); await (connection as System.Data.Common.DbConnection)!.OpenAsync(); using var transaction = connection.BeginTransaction(); try { var ajusteCreado = await _ajusteRepository.CreateAsync(nuevoAjuste, transaction); if (ajusteCreado == null) throw new DataException("Error al crear el registro de ajuste."); transaction.Commit(); _logger.LogInformation("Ajuste manual ID {IdAjuste} creado para Suscriptor ID {IdSuscriptor} por Usuario ID {IdUsuario}", ajusteCreado.IdAjuste, ajusteCreado.IdSuscriptor, idUsuario); var dto = await MapToDto(ajusteCreado); return (dto, null); } catch (Exception ex) { try { transaction.Rollback(); } catch { } _logger.LogError(ex, "Error al crear ajuste manual para Suscriptor ID {IdSuscriptor}", createDto.IdSuscriptor); return (null, "Error interno al registrar el ajuste."); } } public async Task<(bool Exito, string? Error)> AnularAjuste(int idAjuste, int idUsuario) { using var connection = _connectionFactory.CreateConnection(); await (connection as System.Data.Common.DbConnection)!.OpenAsync(); using var transaction = connection.BeginTransaction(); try { var ajuste = await _ajusteRepository.GetByIdAsync(idAjuste); if (ajuste == null) return (false, "Ajuste no encontrado."); if (ajuste.Estado != "Pendiente") return (false, $"No se puede anular un ajuste en estado '{ajuste.Estado}'."); var exito = await _ajusteRepository.AnularAjusteAsync(idAjuste, idUsuario, transaction); if (!exito) throw new DataException("No se pudo anular el ajuste."); transaction.Commit(); _logger.LogInformation("Ajuste ID {IdAjuste} anulado por Usuario ID {IdUsuario}", idAjuste, idUsuario); return (true, null); } catch (Exception ex) { try { transaction.Rollback(); } catch { } _logger.LogError(ex, "Error al anular ajuste ID {IdAjuste}", idAjuste); return (false, "Error interno al anular el ajuste."); } } public async Task<(bool Exito, string? Error)> ActualizarAjuste(int idAjuste, UpdateAjusteDto updateDto) { using var connection = _connectionFactory.CreateConnection(); await (connection as System.Data.Common.DbConnection)!.OpenAsync(); using var transaction = connection.BeginTransaction(); try { var ajuste = await _ajusteRepository.GetByIdAsync(idAjuste); if (ajuste == null) return (false, "Ajuste no encontrado."); if (ajuste.Estado != "Pendiente") return (false, $"No se puede modificar un ajuste en estado '{ajuste.Estado}'."); var empresa = await _empresaRepository.GetByIdAsync(updateDto.IdEmpresa); if (empresa == null) return (false, "La empresa especificada no existe."); ajuste.IdEmpresa = updateDto.IdEmpresa; ajuste.FechaAjuste = updateDto.FechaAjuste; ajuste.TipoAjuste = updateDto.TipoAjuste; ajuste.Monto = updateDto.Monto; ajuste.Motivo = updateDto.Motivo; var actualizado = await _ajusteRepository.UpdateAsync(ajuste, transaction); if (!actualizado) throw new DataException("La actualización falló o el ajuste ya no estaba pendiente."); transaction.Commit(); _logger.LogInformation("Ajuste ID {IdAjuste} actualizado.", idAjuste); return (true, null); } catch (Exception ex) { try { transaction.Rollback(); } catch { } _logger.LogError(ex, "Error al actualizar ajuste ID {IdAjuste}", idAjuste); return (false, "Error interno al actualizar el ajuste."); } } } }