using GestionIntegral.Api.Data; using GestionIntegral.Api.Data.Repositories.Distribucion; using GestionIntegral.Api.Dtos.Distribucion; using GestionIntegral.Api.Models.Distribucion; using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.Data; using System.Linq; using System.Threading.Tasks; namespace GestionIntegral.Api.Services.Distribucion { public class PorcPagoService : IPorcPagoService { private readonly IPorcPagoRepository _porcPagoRepository; private readonly IPublicacionRepository _publicacionRepository; private readonly IDistribuidorRepository _distribuidorRepository; private readonly DbConnectionFactory _connectionFactory; private readonly ILogger _logger; public PorcPagoService( IPorcPagoRepository porcPagoRepository, IPublicacionRepository publicacionRepository, IDistribuidorRepository distribuidorRepository, DbConnectionFactory connectionFactory, ILogger logger) { _porcPagoRepository = porcPagoRepository; _publicacionRepository = publicacionRepository; _distribuidorRepository = distribuidorRepository; _connectionFactory = connectionFactory; _logger = logger; } private PorcPagoDto MapToDto((PorcPago PorcPago, string NombreDistribuidor) data) => new PorcPagoDto { IdPorcentaje = data.PorcPago.IdPorcentaje, IdPublicacion = data.PorcPago.IdPublicacion, IdDistribuidor = data.PorcPago.IdDistribuidor, NombreDistribuidor = data.NombreDistribuidor, VigenciaD = data.PorcPago.VigenciaD.ToString("yyyy-MM-dd"), VigenciaH = data.PorcPago.VigenciaH?.ToString("yyyy-MM-dd"), Porcentaje = data.PorcPago.Porcentaje }; private async Task MapToDtoWithLookup(PorcPago porcPago) { var distribuidorData = await _distribuidorRepository.GetByIdAsync(porcPago.IdDistribuidor); return new PorcPagoDto { IdPorcentaje = porcPago.IdPorcentaje, IdPublicacion = porcPago.IdPublicacion, IdDistribuidor = porcPago.IdDistribuidor, NombreDistribuidor = distribuidorData.Distribuidor?.Nombre ?? "Distribuidor Desconocido", VigenciaD = porcPago.VigenciaD.ToString("yyyy-MM-dd"), VigenciaH = porcPago.VigenciaH?.ToString("yyyy-MM-dd"), Porcentaje = porcPago.Porcentaje }; } public async Task> ObtenerPorPublicacionIdAsync(int idPublicacion) { var data = await _porcPagoRepository.GetByPublicacionIdAsync(idPublicacion); return data.Select(MapToDto); } public async Task ObtenerPorIdAsync(int idPorcentaje) { var porcPago = await _porcPagoRepository.GetByIdAsync(idPorcentaje); if (porcPago == null) return null; return await MapToDtoWithLookup(porcPago); } public async Task<(PorcPagoDto? PorcPago, string? Error)> CrearAsync(CreatePorcPagoDto createDto, int idUsuario) { if (await _publicacionRepository.GetByIdSimpleAsync(createDto.IdPublicacion) == null) return (null, "La publicación especificada no existe."); var distribuidorData = await _distribuidorRepository.GetByIdAsync(createDto.IdDistribuidor); if (distribuidorData.Distribuidor == null) return (null, "El distribuidor especificado no existe."); var porcPagoActivo = await _porcPagoRepository.GetActiveByPublicacionDistribuidorAndDateAsync(createDto.IdPublicacion, createDto.IdDistribuidor, createDto.VigenciaD.Date); if (porcPagoActivo != null) { return (null, $"Ya existe un porcentaje de pago activo para esta publicación y distribuidor en la fecha {createDto.VigenciaD:dd/MM/yyyy}. Cierre el período anterior primero."); } var nuevoPorcPago = new PorcPago { IdPublicacion = createDto.IdPublicacion, IdDistribuidor = createDto.IdDistribuidor, VigenciaD = createDto.VigenciaD.Date, VigenciaH = null, Porcentaje = createDto.Porcentaje }; using var connection = _connectionFactory.CreateConnection(); if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open(); using var transaction = connection.BeginTransaction(); try { var porcPagoAnterior = await _porcPagoRepository.GetPreviousActivePorcPagoAsync(createDto.IdPublicacion, createDto.IdDistribuidor, nuevoPorcPago.VigenciaD, transaction); if (porcPagoAnterior != null) { if (porcPagoAnterior.VigenciaD.Date >= nuevoPorcPago.VigenciaD.Date) { transaction.Rollback(); return (null, $"La fecha de inicio del nuevo porcentaje ({nuevoPorcPago.VigenciaD:dd/MM/yyyy}) no puede ser anterior o igual a la del último porcentaje vigente ({porcPagoAnterior.VigenciaD:dd/MM/yyyy}) para este distribuidor."); } porcPagoAnterior.VigenciaH = nuevoPorcPago.VigenciaD.AddDays(-1); await _porcPagoRepository.UpdateAsync(porcPagoAnterior, idUsuario, transaction); } var porcPagoCreado = await _porcPagoRepository.CreateAsync(nuevoPorcPago, idUsuario, transaction); if (porcPagoCreado == null) throw new DataException("Error al crear el porcentaje de pago."); transaction.Commit(); _logger.LogInformation("PorcPago ID {Id} creado por Usuario ID {UserId}.", porcPagoCreado.IdPorcentaje, idUsuario); return (new PorcPagoDto { // Construir DTO manualmente ya que tenemos NombreDistribuidor IdPorcentaje = porcPagoCreado.IdPorcentaje, IdPublicacion = porcPagoCreado.IdPublicacion, IdDistribuidor = porcPagoCreado.IdDistribuidor, NombreDistribuidor = distribuidorData.Distribuidor.Nombre, VigenciaD = porcPagoCreado.VigenciaD.ToString("yyyy-MM-dd"), VigenciaH = porcPagoCreado.VigenciaH?.ToString("yyyy-MM-dd"), Porcentaje = porcPagoCreado.Porcentaje }, null); } catch (Exception ex) { try { transaction.Rollback(); } catch { } _logger.LogError(ex, "Error CrearAsync PorcPago para Pub ID {IdPub}, Dist ID {IdDist}", createDto.IdPublicacion, createDto.IdDistribuidor); return (null, $"Error interno: {ex.Message}"); } } public async Task<(bool Exito, string? Error)> ActualizarAsync(int idPorcentaje, UpdatePorcPagoDto updateDto, int idUsuario) { using var connection = _connectionFactory.CreateConnection(); if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open(); using var transaction = connection.BeginTransaction(); try { var porcPagoExistente = await _porcPagoRepository.GetByIdAsync(idPorcentaje); if (porcPagoExistente == null) return (false, "Porcentaje de pago no encontrado."); if (updateDto.VigenciaH.HasValue && updateDto.VigenciaH.Value.Date < porcPagoExistente.VigenciaD.Date) return (false, "Vigencia Hasta no puede ser anterior a Vigencia Desde."); if (updateDto.VigenciaH.HasValue) { var porcentajesPubDist = await _porcPagoRepository.GetByPublicacionIdAsync(porcPagoExistente.IdPublicacion); var porcentajesPosteriores = porcentajesPubDist .Where(p => p.PorcPago.IdDistribuidor == porcPagoExistente.IdDistribuidor && p.PorcPago.IdPorcentaje != idPorcentaje && p.PorcPago.VigenciaD.Date <= updateDto.VigenciaH.Value.Date && p.PorcPago.VigenciaD.Date > porcPagoExistente.VigenciaD.Date); if(porcentajesPosteriores.Any()) { return (false, "No se puede cerrar este período porque existen porcentajes posteriores para este distribuidor que se solaparían."); } } porcPagoExistente.Porcentaje = updateDto.Porcentaje; if (updateDto.VigenciaH.HasValue) { porcPagoExistente.VigenciaH = updateDto.VigenciaH.Value.Date; } var actualizado = await _porcPagoRepository.UpdateAsync(porcPagoExistente, idUsuario, transaction); if (!actualizado) throw new DataException("Error al actualizar porcentaje de pago."); transaction.Commit(); _logger.LogInformation("PorcPago ID {Id} actualizado por Usuario ID {UserId}.", idPorcentaje, idUsuario); return (true, null); } catch (KeyNotFoundException) { try { transaction.Rollback(); } catch { } return (false, "Porcentaje no encontrado."); } catch (Exception ex) { try { transaction.Rollback(); } catch { } _logger.LogError(ex, "Error ActualizarAsync PorcPago ID: {Id}", idPorcentaje); return (false, $"Error interno: {ex.Message}"); } } public async Task<(bool Exito, string? Error)> EliminarAsync(int idPorcentaje, int idUsuario) { using var connection = _connectionFactory.CreateConnection(); if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open(); using var transaction = connection.BeginTransaction(); try { var porcPagoAEliminar = await _porcPagoRepository.GetByIdAsync(idPorcentaje); if (porcPagoAEliminar == null) return (false, "Porcentaje de pago no encontrado."); if (porcPagoAEliminar.VigenciaH == null) { var todosPorcPubDistData = await _porcPagoRepository.GetByPublicacionIdAsync(porcPagoAEliminar.IdPublicacion); var todosPorcPubDist = todosPorcPubDistData .Where(p => p.PorcPago.IdDistribuidor == porcPagoAEliminar.IdDistribuidor) .Select(p => p.PorcPago) .OrderByDescending(p => p.VigenciaD).ToList(); var indiceActual = todosPorcPubDist.FindIndex(p => p.IdPorcentaje == idPorcentaje); if(indiceActual != -1 && (indiceActual + 1) < todosPorcPubDist.Count) { var porcPagoAnteriorDirecto = todosPorcPubDist[indiceActual + 1]; if(porcPagoAnteriorDirecto.VigenciaH.HasValue && porcPagoAnteriorDirecto.VigenciaH.Value.Date == porcPagoAEliminar.VigenciaD.AddDays(-1).Date) { porcPagoAnteriorDirecto.VigenciaH = null; await _porcPagoRepository.UpdateAsync(porcPagoAnteriorDirecto, idUsuario, transaction); } } } var eliminado = await _porcPagoRepository.DeleteAsync(idPorcentaje, idUsuario, transaction); if (!eliminado) throw new DataException("Error al eliminar porcentaje de pago."); transaction.Commit(); _logger.LogInformation("PorcPago ID {Id} eliminado por Usuario ID {UserId}.", idPorcentaje, idUsuario); return (true, null); } catch (KeyNotFoundException) { try { transaction.Rollback(); } catch { } return (false, "Porcentaje no encontrado."); } catch (Exception ex) { try { transaction.Rollback(); } catch { } _logger.LogError(ex, "Error EliminarAsync PorcPago ID: {Id}", idPorcentaje); return (false, $"Error interno: {ex.Message}"); } } } }