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 RecargoZonaService : IRecargoZonaService { private readonly IRecargoZonaRepository _recargoZonaRepository; private readonly IPublicacionRepository _publicacionRepository; private readonly IZonaRepository _zonaRepository; private readonly DbConnectionFactory _connectionFactory; private readonly ILogger _logger; public RecargoZonaService( IRecargoZonaRepository recargoZonaRepository, IPublicacionRepository publicacionRepository, IZonaRepository zonaRepository, DbConnectionFactory connectionFactory, ILogger logger) { _recargoZonaRepository = recargoZonaRepository; _publicacionRepository = publicacionRepository; _zonaRepository = zonaRepository; _connectionFactory = connectionFactory; _logger = logger; } private RecargoZonaDto MapToDto((RecargoZona Recargo, string NombreZona) data) => new RecargoZonaDto { IdRecargo = data.Recargo.IdRecargo, IdPublicacion = data.Recargo.IdPublicacion, IdZona = data.Recargo.IdZona, NombreZona = data.NombreZona, VigenciaD = data.Recargo.VigenciaD.ToString("yyyy-MM-dd"), VigenciaH = data.Recargo.VigenciaH?.ToString("yyyy-MM-dd"), Valor = data.Recargo.Valor }; // Helper para mapear cuando solo tenemos el RecargoZona y necesitamos buscar NombreZona private async Task MapToDtoWithZonaLookup(RecargoZona recargo) { var zona = await _zonaRepository.GetByIdAsync(recargo.IdZona); // zonaRepository.GetByIdAsync devuelve ZonaDto return new RecargoZonaDto { IdRecargo = recargo.IdRecargo, IdPublicacion = recargo.IdPublicacion, IdZona = recargo.IdZona, NombreZona = zona?.Nombre ?? "Zona Desconocida/Inactiva", // Usar la propiedad Nombre del ZonaDto VigenciaD = recargo.VigenciaD.ToString("yyyy-MM-dd"), VigenciaH = recargo.VigenciaH?.ToString("yyyy-MM-dd"), Valor = recargo.Valor }; } public async Task> ObtenerPorPublicacionIdAsync(int idPublicacion) { var recargosData = await _recargoZonaRepository.GetByPublicacionIdAsync(idPublicacion); return recargosData .Select(rd => MapToDto(rd)) // MapToDto ahora devuelve RecargoZonaDto? .Where(dto => dto != null) .Select(dto => dto!); // Cast no nulo } public async Task ObtenerPorIdAsync(int idRecargo) { var recargo = await _recargoZonaRepository.GetByIdAsync(idRecargo); if (recargo == null) return null; var zona = await _zonaRepository.GetByIdAsync(recargo.IdZona); // Obtiene ZonaDto return new RecargoZonaDto { IdRecargo = recargo.IdRecargo, IdPublicacion = recargo.IdPublicacion, IdZona = recargo.IdZona, NombreZona = zona?.Nombre ?? "Zona Desconocida/Inactiva", VigenciaD = recargo.VigenciaD.ToString("yyyy-MM-dd"), VigenciaH = recargo.VigenciaH?.ToString("yyyy-MM-dd"), Valor = recargo.Valor }; } public async Task<(RecargoZonaDto? Recargo, string? Error)> CrearAsync(CreateRecargoZonaDto createDto, int idUsuario) { if (await _publicacionRepository.GetByIdSimpleAsync(createDto.IdPublicacion) == null) return (null, "La publicación especificada no existe."); var zona = await _zonaRepository.GetByIdAsync(createDto.IdZona); // Devuelve ZonaDto if (zona == null) return (null, "La zona especificada no existe o no está activa."); // Usar createDto.VigenciaD directamente que ya es DateTime var recargoActivo = await _recargoZonaRepository.GetActiveByPublicacionZonaAndDateAsync(createDto.IdPublicacion, createDto.IdZona, createDto.VigenciaD.Date); if (recargoActivo != null) { return (null, $"Ya existe un recargo activo para esta publicación y zona en la fecha {createDto.VigenciaD:dd/MM/yyyy}. Primero debe cerrar el período anterior."); } var nuevoRecargo = new RecargoZona { IdPublicacion = createDto.IdPublicacion, IdZona = createDto.IdZona, VigenciaD = createDto.VigenciaD.Date, VigenciaH = null, Valor = createDto.Valor }; 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 recargoAnterior = await _recargoZonaRepository.GetPreviousActiveRecargoAsync(createDto.IdPublicacion, createDto.IdZona, nuevoRecargo.VigenciaD, transaction); if (recargoAnterior != null) { if (recargoAnterior.VigenciaD.Date >= nuevoRecargo.VigenciaD.Date) // Comparar solo fechas { transaction.Rollback(); return (null, $"La fecha de inicio del nuevo recargo ({nuevoRecargo.VigenciaD:dd/MM/yyyy}) no puede ser anterior o igual a la del último recargo vigente ({recargoAnterior.VigenciaD:dd/MM/yyyy}) para esta zona."); } recargoAnterior.VigenciaH = nuevoRecargo.VigenciaD.AddDays(-1); await _recargoZonaRepository.UpdateAsync(recargoAnterior, idUsuario, transaction); } var recargoCreado = await _recargoZonaRepository.CreateAsync(nuevoRecargo, idUsuario, transaction); if (recargoCreado == null) throw new DataException("Error al crear el recargo."); transaction.Commit(); _logger.LogInformation("Recargo ID {Id} creado por Usuario ID {UserId}.", recargoCreado.IdRecargo, idUsuario); // Pasar el nombre de la zona ya obtenido return (new RecargoZonaDto { IdRecargo = recargoCreado.IdRecargo, IdPublicacion = recargoCreado.IdPublicacion, IdZona = recargoCreado.IdZona, NombreZona = zona.Nombre, VigenciaD = recargoCreado.VigenciaD.ToString("yyyy-MM-dd"), VigenciaH = recargoCreado.VigenciaH?.ToString("yyyy-MM-dd"), Valor = recargoCreado.Valor }, null); } catch (Exception ex) { try { transaction.Rollback(); } catch { } _logger.LogError(ex, "Error CrearAsync RecargoZona para Pub ID {IdPub}, Zona ID {IdZona}", createDto.IdPublicacion, createDto.IdZona); return (null, $"Error interno: {ex.Message}"); } } public async Task<(bool Exito, string? Error)> ActualizarAsync(int idRecargo, UpdateRecargoZonaDto 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 recargoExistente = await _recargoZonaRepository.GetByIdAsync(idRecargo); if (recargoExistente == null) return (false, "Recargo por zona no encontrado."); if (updateDto.VigenciaH.HasValue && updateDto.VigenciaH.Value.Date < recargoExistente.VigenciaD.Date) return (false, "Vigencia Hasta no puede ser anterior a Vigencia Desde."); if (updateDto.VigenciaH.HasValue) { var recargosDeLaPublicacion = await _recargoZonaRepository.GetByPublicacionIdAsync(recargoExistente.IdPublicacion); var recargosPosteriores = recargosDeLaPublicacion .Where(r => r.Recargo.IdZona == recargoExistente.IdZona && r.Recargo.IdRecargo != idRecargo && r.Recargo.VigenciaD.Date <= updateDto.VigenciaH.Value.Date && r.Recargo.VigenciaD.Date > recargoExistente.VigenciaD.Date); if(recargosPosteriores.Any()) { return (false, "No se puede cerrar este período porque existen recargos posteriores para esta zona que se solaparían."); } } recargoExistente.Valor = updateDto.Valor; if (updateDto.VigenciaH.HasValue) { recargoExistente.VigenciaH = updateDto.VigenciaH.Value.Date; } var actualizado = await _recargoZonaRepository.UpdateAsync(recargoExistente, idUsuario, transaction); if (!actualizado) throw new DataException("Error al actualizar recargo."); transaction.Commit(); _logger.LogInformation("Recargo ID {Id} actualizado por Usuario ID {UserId}.", idRecargo, idUsuario); return (true, null); } catch (KeyNotFoundException) { try { transaction.Rollback(); } catch { } return (false, "Recargo no encontrado."); } catch (Exception ex) { try { transaction.Rollback(); } catch { } _logger.LogError(ex, "Error ActualizarAsync RecargoZona ID: {Id}", idRecargo); return (false, $"Error interno: {ex.Message}"); } } public async Task<(bool Exito, string? Error)> EliminarAsync(int idRecargo, 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 recargoAEliminar = await _recargoZonaRepository.GetByIdAsync(idRecargo); if (recargoAEliminar == null) return (false, "Recargo no encontrado."); if (recargoAEliminar.VigenciaH == null) { var todosRecargosPubZonaData = await _recargoZonaRepository.GetByPublicacionIdAsync(recargoAEliminar.IdPublicacion); var todosRecargosPubZona = todosRecargosPubZonaData .Where(r => r.Recargo.IdZona == recargoAEliminar.IdZona) .Select(r => r.Recargo) .OrderByDescending(r => r.VigenciaD).ToList(); var indiceActual = todosRecargosPubZona.FindIndex(r => r.IdRecargo == idRecargo); if(indiceActual != -1 && (indiceActual + 1) < todosRecargosPubZona.Count) { var recargoAnteriorDirecto = todosRecargosPubZona[indiceActual + 1]; if(recargoAnteriorDirecto.VigenciaH.HasValue && recargoAnteriorDirecto.VigenciaH.Value.Date == recargoAEliminar.VigenciaD.AddDays(-1).Date) { recargoAnteriorDirecto.VigenciaH = null; await _recargoZonaRepository.UpdateAsync(recargoAnteriorDirecto, idUsuario, transaction); _logger.LogInformation("Recargo anterior ID {IdRecargoAnterior} reabierto tras eliminación de Recargo ID {IdRecargoEliminado}.", recargoAnteriorDirecto.IdRecargo, idRecargo); } } } var eliminado = await _recargoZonaRepository.DeleteAsync(idRecargo, idUsuario, transaction); if (!eliminado) throw new DataException("Error al eliminar recargo."); transaction.Commit(); _logger.LogInformation("Recargo ID {Id} eliminado por Usuario ID {UserId}.", idRecargo, idUsuario); return (true, null); } catch (KeyNotFoundException) { try { transaction.Rollback(); } catch { } return (false, "Recargo no encontrado."); } catch (Exception ex) { try { transaction.Rollback(); } catch { } _logger.LogError(ex, "Error EliminarAsync RecargoZona ID: {Id}", idRecargo); return (false, $"Error interno: {ex.Message}"); } } } }