using GestionIntegral.Api.Data; using GestionIntegral.Api.Data.Repositories.Distribucion; using GestionIntegral.Api.Data.Repositories.Impresion; using GestionIntegral.Api.Dtos.Auditoria; using GestionIntegral.Api.Dtos.Impresion; using GestionIntegral.Api.Models.Distribucion; // Para Publicacion, PubliSeccion using GestionIntegral.Api.Models.Impresion; // Para RegTirada, RegPublicacionSeccion using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.Data; using System.Linq; using System.Threading.Tasks; namespace GestionIntegral.Api.Services.Impresion { public class TiradaService : ITiradaService { private readonly IRegTiradaRepository _regTiradaRepository; private readonly IRegPublicacionSeccionRepository _regPublicacionSeccionRepository; private readonly IPublicacionRepository _publicacionRepository; private readonly IPlantaRepository _plantaRepository; private readonly IPubliSeccionRepository _publiSeccionRepository; // Para validar IDs de sección private readonly DbConnectionFactory _connectionFactory; private readonly ILogger _logger; public TiradaService( IRegTiradaRepository regTiradaRepository, IRegPublicacionSeccionRepository regPublicacionSeccionRepository, IPublicacionRepository publicacionRepository, IPlantaRepository plantaRepository, IPubliSeccionRepository publiSeccionRepository, DbConnectionFactory connectionFactory, ILogger logger) { _regTiradaRepository = regTiradaRepository; _regPublicacionSeccionRepository = regPublicacionSeccionRepository; _publicacionRepository = publicacionRepository; _plantaRepository = plantaRepository; _publiSeccionRepository = publiSeccionRepository; _connectionFactory = connectionFactory; _logger = logger; } public async Task> ObtenerTiradasAsync(DateTime? fecha, int? idPublicacion, int? idPlanta) { var tiradasPrincipales = await _regTiradaRepository.GetByCriteriaAsync(fecha, idPublicacion, idPlanta); var resultado = new List(); foreach (var tiradaP in tiradasPrincipales) { var publicacion = await _publicacionRepository.GetByIdSimpleAsync(tiradaP.IdPublicacion); var planta = await _plantaRepository.GetByIdAsync(tiradaP.IdPlanta); var seccionesImpresas = await _regPublicacionSeccionRepository.GetByFechaPublicacionPlantaAsync(tiradaP.Fecha, tiradaP.IdPublicacion, tiradaP.IdPlanta); var detallesSeccionDto = new List(); int totalPaginas = 0; foreach (var seccionImp in seccionesImpresas) { var seccionInfo = await _publiSeccionRepository.GetByIdAsync(seccionImp.IdSeccion); detallesSeccionDto.Add(new DetalleSeccionEnListadoDto { IdSeccion = seccionImp.IdSeccion, NombreSeccion = seccionInfo?.Nombre ?? "Sección Desconocida", CantPag = seccionImp.CantPag, IdRegPublicacionSeccion = seccionImp.IdTirada // Este es el PK de bob_RegPublicaciones }); totalPaginas += seccionImp.CantPag; } resultado.Add(new TiradaDto { IdRegistroTirada = tiradaP.IdRegistro, IdPublicacion = tiradaP.IdPublicacion, NombrePublicacion = publicacion?.Nombre ?? "Publicación Desconocida", Fecha = tiradaP.Fecha.ToString("yyyy-MM-dd"), IdPlanta = tiradaP.IdPlanta, NombrePlanta = planta?.Nombre ?? "Planta Desconocida", Ejemplares = tiradaP.Ejemplares, SeccionesImpresas = detallesSeccionDto, TotalPaginasSumadas = totalPaginas }); } return resultado; } public async Task<(TiradaDto? TiradaCreada, string? Error)> RegistrarTiradaCompletaAsync(CreateTiradaRequestDto createDto, int idUsuario) { // Validaciones previas var publicacion = await _publicacionRepository.GetByIdSimpleAsync(createDto.IdPublicacion); if (publicacion == null) return (null, "La publicación especificada no existe."); var planta = await _plantaRepository.GetByIdAsync(createDto.IdPlanta); if (planta == null) return (null, "La planta especificada no existe."); // Validar que no exista ya una tirada para esa Publicación, Fecha y Planta // (bob_RegTiradas debería ser único por estos campos) if (await _regTiradaRepository.GetByFechaPublicacionPlantaAsync(createDto.Fecha.Date, createDto.IdPublicacion, createDto.IdPlanta) != null) { return (null, $"Ya existe una tirada registrada para la publicación '{publicacion.Nombre}' en la planta '{planta.Nombre}' para la fecha {createDto.Fecha:dd/MM/yyyy}."); } // Validar secciones foreach (var seccionDto in createDto.Secciones) { var seccionDb = await _publiSeccionRepository.GetByIdAsync(seccionDto.IdSeccion); if (seccionDb == null || seccionDb.IdPublicacion != createDto.IdPublicacion) return (null, $"La sección con ID {seccionDto.IdSeccion} no es válida o no pertenece a la publicación seleccionada."); if (!seccionDb.Estado) // Asumiendo que solo se pueden tirar secciones activas return (null, $"La sección '{seccionDb.Nombre}' no está activa y no puede incluirse en la tirada."); } 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 { // 1. Crear registro en bob_RegTiradas (total de ejemplares) var nuevaRegTirada = new RegTirada { IdPublicacion = createDto.IdPublicacion, Fecha = createDto.Fecha.Date, IdPlanta = createDto.IdPlanta, Ejemplares = createDto.Ejemplares }; var regTiradaCreada = await _regTiradaRepository.CreateAsync(nuevaRegTirada, idUsuario, transaction); if (regTiradaCreada == null) throw new DataException("Error al registrar el total de la tirada."); // 2. Crear registros en bob_RegPublicaciones (detalle de secciones y páginas) var seccionesImpresasDto = new List(); int totalPaginasSumadas = 0; foreach (var seccionDto in createDto.Secciones) { var nuevaRegPubSeccion = new RegPublicacionSeccion { IdPublicacion = createDto.IdPublicacion, IdSeccion = seccionDto.IdSeccion, CantPag = seccionDto.CantPag, Fecha = createDto.Fecha.Date, IdPlanta = createDto.IdPlanta }; var seccionCreadaEnTirada = await _regPublicacionSeccionRepository.CreateAsync(nuevaRegPubSeccion, idUsuario, transaction); if (seccionCreadaEnTirada == null) throw new DataException($"Error al registrar la sección ID {seccionDto.IdSeccion} en la tirada."); var seccionInfo = await _publiSeccionRepository.GetByIdAsync(seccionDto.IdSeccion); // Para obtener nombre seccionesImpresasDto.Add(new DetalleSeccionEnListadoDto { IdSeccion = seccionCreadaEnTirada.IdSeccion, NombreSeccion = seccionInfo?.Nombre ?? "N/A", CantPag = seccionCreadaEnTirada.CantPag, IdRegPublicacionSeccion = seccionCreadaEnTirada.IdTirada }); totalPaginasSumadas += seccionCreadaEnTirada.CantPag; } transaction.Commit(); _logger.LogInformation("Tirada completa registrada para Pub ID {IdPub}, Fecha {Fecha}, Planta ID {IdPlanta} por Usuario ID {UserId}.", createDto.IdPublicacion, createDto.Fecha.Date, createDto.IdPlanta, idUsuario); return (new TiradaDto { IdRegistroTirada = regTiradaCreada.IdRegistro, IdPublicacion = regTiradaCreada.IdPublicacion, NombrePublicacion = publicacion.Nombre, Fecha = regTiradaCreada.Fecha.ToString("yyyy-MM-dd"), IdPlanta = regTiradaCreada.IdPlanta, NombrePlanta = planta.Nombre, Ejemplares = regTiradaCreada.Ejemplares, SeccionesImpresas = seccionesImpresasDto, TotalPaginasSumadas = totalPaginasSumadas }, null); } catch (Exception ex) { try { transaction.Rollback(); } catch { } _logger.LogError(ex, "Error RegistrarTiradaCompletaAsync para Pub ID {IdPub}, Fecha {Fecha}", createDto.IdPublicacion, createDto.Fecha); return (null, $"Error interno: {ex.Message}"); } } public async Task<(TiradaDto? TiradaActualizada, string? Error)> ModificarTiradaCompletaAsync(DateTime fecha, int idPublicacion, int idPlanta, UpdateTiradaRequestDto updateDto, int idUsuario) { // 1. Validar que la tirada a modificar exista. var tiradaExistente = await _regTiradaRepository.GetByFechaPublicacionPlantaAsync(fecha.Date, idPublicacion, idPlanta); if (tiradaExistente == null) { return (null, "No se encontró la tirada que intenta modificar."); } // 2. Validaciones de DTO (secciones válidas, etc.) var idsSeccionesUnicasDto = updateDto.Secciones.Select(s => s.IdSeccion).Distinct(); if (idsSeccionesUnicasDto.Any()) // Solo validar si se enviaron secciones { var seccionesValidasDb = await _publiSeccionRepository.GetByIdsAndPublicacionAsync(idsSeccionesUnicasDto, idPublicacion, soloActivas: true); if (seccionesValidasDb.Count() != idsSeccionesUnicasDto.Count()) { var idsEncontrados = seccionesValidasDb.Select(s => s.IdSeccion).ToHashSet(); var idsFaltantes = string.Join(", ", idsSeccionesUnicasDto.Where(id => !idsEncontrados.Contains(id))); return (null, $"Las siguientes secciones no son válidas, no pertenecen a la publicación o no están activas: {idsFaltantes}."); } } 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 { // 3. Actualizar registro principal (bob_RegTiradas) si cambió el número de ejemplares if (tiradaExistente.Ejemplares != updateDto.Ejemplares) { tiradaExistente.Ejemplares = updateDto.Ejemplares; bool actualizado = await _regTiradaRepository.UpdateAsync(tiradaExistente, idUsuario, transaction); if (!actualizado) throw new DataException("No se pudo actualizar el registro principal de la tirada."); } // 4. Lógica de reconciliación de secciones // 4.1. Obtener estado actual de las secciones en la BBDD var seccionesActualesDb = await _regPublicacionSeccionRepository.GetByFechaPublicacionPlantaAsync(fecha.Date, idPublicacion, idPlanta); // 4.2. Procesar Adiciones y Actualizaciones foreach (var seccionDto in updateDto.Secciones) { if (seccionDto.IdRegPublicacionSeccion == 0) // Es una nueva sección { var nuevaRegPubSeccion = new RegPublicacionSeccion { IdPublicacion = idPublicacion, IdSeccion = seccionDto.IdSeccion, CantPag = seccionDto.CantPag, Fecha = fecha.Date, IdPlanta = idPlanta }; await _regPublicacionSeccionRepository.CreateAsync(nuevaRegPubSeccion, idUsuario, transaction); } else // Es una sección existente, verificar si hay cambios { var seccionDb = seccionesActualesDb.FirstOrDefault(s => s.IdTirada == seccionDto.IdRegPublicacionSeccion); if (seccionDb == null) throw new InvalidOperationException($"La sección con ID de registro {seccionDto.IdRegPublicacionSeccion} no pertenece a esta tirada."); if (seccionDb.CantPag != seccionDto.CantPag) // Solo actualizamos si cambió la cantidad de páginas { seccionDb.CantPag = seccionDto.CantPag; await _regPublicacionSeccionRepository.UpdateAsync(seccionDb, idUsuario, transaction); } } } // 4.3. Procesar Eliminaciones var idsSeccionesEntrantes = updateDto.Secciones .Where(s => s.IdRegPublicacionSeccion != 0) .Select(s => s.IdRegPublicacionSeccion) .ToHashSet(); var seccionesAEliminar = seccionesActualesDb .Where(s => !idsSeccionesEntrantes.Contains(s.IdTirada)); foreach (var seccionParaBorrar in seccionesAEliminar) { await _regPublicacionSeccionRepository.DeleteByIdAsync(seccionParaBorrar.IdTirada, idUsuario, transaction); } transaction.Commit(); _logger.LogInformation("Tirada completa modificada (granular) para Pub ID {IdPub}, Fecha {Fecha}, por Usuario ID {UserId}.", idPublicacion, fecha.Date, idUsuario); // 5. Devolver el DTO actualizado (tendríamos que volver a consultar o construirlo) var tiradaActualizadaResult = await ObtenerTiradasAsync(fecha.Date, idPublicacion, idPlanta); return (tiradaActualizadaResult.FirstOrDefault(), null); } catch (Exception ex) { try { transaction.Rollback(); } catch { } _logger.LogError(ex, "Error en ModificarTiradaCompletaAsync (granular) para Pub ID {IdPub}, Fecha {Fecha}", idPublicacion, fecha); return (null, $"Error interno al modificar la tirada: {ex.Message}"); } } public async Task<(bool Exito, string? Error)> EliminarTiradaCompletaAsync(DateTime fecha, int idPublicacion, int idPlanta, int idUsuario) { // Verificar que la tirada principal exista var tiradaPrincipal = await _regTiradaRepository.GetByFechaPublicacionPlantaAsync(fecha.Date, idPublicacion, idPlanta); if (tiradaPrincipal == null) { return (false, "No se encontró una tirada principal para los criterios especificados."); } 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 { // 1. Eliminar detalles de secciones de bob_RegPublicaciones // El método ya guarda en historial await _regPublicacionSeccionRepository.DeleteByFechaPublicacionPlantaAsync(fecha.Date, idPublicacion, idPlanta, idUsuario, transaction); _logger.LogInformation("Secciones de tirada eliminadas para Fecha: {Fecha}, PubID: {IdPublicacion}, PlantaID: {IdPlanta}", fecha.Date, idPublicacion, idPlanta); // 2. Eliminar el registro principal de bob_RegTiradas // El método ya guarda en historial bool principalEliminado = await _regTiradaRepository.DeleteByFechaPublicacionPlantaAsync(fecha.Date, idPublicacion, idPlanta, idUsuario, transaction); // bool principalEliminado = await _regTiradaRepository.DeleteAsync(tiradaPrincipal.IdRegistro, idUsuario, transaction); // Alternativa si ya tienes el IdRegistro if (!principalEliminado && tiradaPrincipal != null) // Si DeleteByFechaPublicacionPlantaAsync devuelve false porque no encontró nada (raro si pasó la validación) { _logger.LogWarning("No se eliminó el registro principal de tirada (bob_RegTiradas) para Fecha: {Fecha}, PubID: {IdPublicacion}, PlantaID: {IdPlanta}. Pudo haber sido eliminado concurrentemente.", fecha.Date, idPublicacion, idPlanta); // Decidir si esto es un error que debe hacer rollback } _logger.LogInformation("Registro principal de tirada eliminado para Fecha: {Fecha}, PubID: {IdPublicacion}, PlantaID: {IdPlanta}", fecha.Date, idPublicacion, idPlanta); transaction.Commit(); return (true, null); } catch (Exception ex) { try { transaction.Rollback(); } catch { } _logger.LogError(ex, "Error EliminarTiradaCompletaAsync para Fecha {Fecha}, PubID {IdPub}, PlantaID {IdPlanta}", fecha.Date, idPublicacion, idPlanta); return (false, $"Error interno al eliminar la tirada: {ex.Message}"); } } public async Task> ObtenerRegTiradasHistorialAsync( DateTime? fechaDesde, DateTime? fechaHasta, int? idUsuarioModifico, string? tipoModificacion, int? idRegistroAfectado, int? idPublicacionFiltro, int? idPlantaFiltro, DateTime? fechaTiradaFiltro) { var historialData = await _regTiradaRepository.GetRegTiradasHistorialAsync(fechaDesde, fechaHasta, idUsuarioModifico, tipoModificacion, idRegistroAfectado, idPublicacionFiltro, idPlantaFiltro, fechaTiradaFiltro); return historialData.Select(h => new RegTiradaHistorialDto { Id_Registro = h.Historial.Id_Registro, Ejemplares = h.Historial.Ejemplares, Id_Publicacion = h.Historial.Id_Publicacion, NombrePublicacion = h.NombrePublicacion, Fecha = h.Historial.Fecha, Id_Planta = h.Historial.Id_Planta, NombrePlanta = h.NombrePlanta, Id_Usuario = h.Historial.Id_Usuario, NombreUsuarioModifico = h.NombreUsuarioModifico, FechaMod = h.Historial.FechaMod, TipoMod = h.Historial.TipoMod }).ToList(); } public async Task> ObtenerRegSeccionesTiradaHistorialAsync( DateTime? fechaDesde, DateTime? fechaHasta, int? idUsuarioModifico, string? tipoModificacion, int? idTiradaAfectada, int? idPublicacionFiltro, int? idSeccionFiltro, int? idPlantaFiltro, DateTime? fechaTiradaFiltro) { var historialData = await _regTiradaRepository.GetRegSeccionesTiradaHistorialAsync(fechaDesde, fechaHasta, idUsuarioModifico, tipoModificacion, idTiradaAfectada, idPublicacionFiltro, idSeccionFiltro, idPlantaFiltro, fechaTiradaFiltro); return historialData.Select(h => new RegSeccionTiradaHistorialDto { Id_Tirada = h.Historial.Id_Tirada, Id_Publicacion = h.Historial.Id_Publicacion, NombrePublicacion = h.NombrePublicacion, Id_Seccion = h.Historial.Id_Seccion, NombreSeccion = h.NombreSeccion, CantPag = h.Historial.CantPag, Fecha = h.Historial.Fecha, Id_Planta = h.Historial.Id_Planta, NombrePlanta = h.NombrePlanta, Id_Usuario = h.Historial.Id_Usuario, NombreUsuarioModifico = h.NombreUsuarioModifico, FechaMod = h.Historial.FechaMod, TipoMod = h.Historial.TipoMod }).ToList(); } } }