using GestionIntegral.Api.Services.Reportes; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using GestionIntegral.Api.Dtos.Reportes; using GestionIntegral.Api.Data.Repositories.Impresion; using GestionIntegral.Api.Data.Repositories.Distribucion; using GestionIntegral.Api.Services.Distribucion; using GestionIntegral.Api.Services.Pdf; using GestionIntegral.Api.Dtos.Reportes.ViewModels; using GestionIntegral.Api.Controllers.Reportes.PdfTemplates; using QuestPDF.Infrastructure; using System.Globalization; namespace GestionIntegral.Api.Controllers { [Route("api/[controller]")] [ApiController] [Authorize] public class ReportesController : ControllerBase { private readonly IReportesService _reportesService; private readonly ILogger _logger; private readonly IPlantaRepository _plantaRepository; private readonly IPublicacionRepository _publicacionRepository; private readonly IEmpresaRepository _empresaRepository; private readonly IDistribuidorRepository _distribuidorRepository; // Para obtener el nombre del distribuidor private readonly INovedadCanillaService _novedadCanillaService; private readonly IQuestPdfGenerator _pdfGenerator; // Permisos private const string PermisoVerReporteExistenciaPapel = "RR005"; private const string PermisoVerReporteMovimientoBobinas = "RR006"; private const string PermisoVerListadoDistribucion = "RR002"; private const string PermisoVerControlDevoluciones = "RR003"; private const string PermisoVerComprobanteLiquidacionCanilla = "MC005"; private const string PermisoVerBalanceCuentas = "RR001"; private const string PermisoVerReporteTiradas = "RR008"; private const string PermisoVerReporteConsumoBobinas = "RR007"; private const string PermisoVerReporteNovedadesCanillas = "RR004"; private const string PermisoVerReporteListadoDistMensual = "RR009"; private const string PermisoVerReporteFacturasPublicidad = "RR010"; private const string PermisoVerReporteDistSuscripciones = "RR011"; public ReportesController( IReportesService reportesService, INovedadCanillaService novedadCanillaService, ILogger logger, IPlantaRepository plantaRepository, IPublicacionRepository publicacionRepository, IEmpresaRepository empresaRepository, IDistribuidorRepository distribuidorRepository, IQuestPdfGenerator pdfGenerator) { _reportesService = reportesService; _novedadCanillaService = novedadCanillaService; _logger = logger; _plantaRepository = plantaRepository; _publicacionRepository = publicacionRepository; _empresaRepository = empresaRepository; _distribuidorRepository = distribuidorRepository; _pdfGenerator = pdfGenerator; } private bool TienePermiso(string codAcc) => User.IsInRole("SuperAdmin") || User.HasClaim(c => c.Type == "permission" && c.Value == codAcc); // GET: api/reportes/existencia-papel [HttpGet("existencia-papel")] [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] public async Task GetReporteExistenciaPapel( [FromQuery] DateTime fechaDesde, [FromQuery] DateTime fechaHasta, [FromQuery] int? idPlanta, [FromQuery] bool consolidado = false) { if (!TienePermiso(PermisoVerReporteExistenciaPapel)) { _logger.LogWarning("Acceso denegado a GetReporteExistenciaPapel. Usuario: {User}", User.Identity?.Name ?? "Desconocido"); return Forbid(); } var (data, error) = await _reportesService.ObtenerExistenciaPapelAsync(fechaDesde, fechaHasta, idPlanta, consolidado); // <--- CORREGIDO if (error != null) { return BadRequest(new { message = error }); } return Ok(data); } [HttpGet("existencia-papel/pdf")] [ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] public async Task GetReporteExistenciaPapelPdf( [FromQuery] DateTime fechaDesde, [FromQuery] DateTime fechaHasta, [FromQuery] int? idPlanta, [FromQuery] bool consolidado = false) { if (!TienePermiso(PermisoVerReporteExistenciaPapel)) return Forbid(); var (data, error) = await _reportesService.ObtenerExistenciaPapelAsync(fechaDesde, fechaHasta, idPlanta, consolidado); if (error != null) return BadRequest(new { message = error }); if (data == null || !data.Any()) return NotFound(new { message = "No hay datos para generar el PDF." }); try { IDocument document; // Se declara aquí if (consolidado) { var viewModel = new ExistenciaPapelConsolidadoViewModel { Existencias = data, FechaDesde = fechaDesde.ToString("dd/MM/yyyy"), FechaHasta = fechaHasta.ToString("dd/MM/yyyy") }; document = new ExistenciaPapelConsolidadoDocument(viewModel); } else { if (!idPlanta.HasValue) { return BadRequest(new { message = "El idPlanta es requerido para reportes no consolidados." }); } var planta = await _plantaRepository.GetByIdAsync(idPlanta.Value); var viewModel = new ExistenciaPapelViewModel { Existencias = data, NombrePlanta = planta?.Nombre ?? $"Planta ID {idPlanta.Value}", // Manejo por si no se encuentra FechaDesde = fechaDesde.ToString("dd/MM/yyyy"), FechaHasta = fechaHasta.ToString("dd/MM/yyyy") }; document = new ExistenciaPapelDocument(viewModel); } byte[] pdfBytes = await _pdfGenerator.GeneratePdfAsync(document); string fileName = $"ExistenciaPapel_{fechaDesde:yyyyMMdd}_{fechaHasta:yyyyMMdd}_{(consolidado ? "Consolidado" : $"Planta{idPlanta}")}.pdf"; return File(pdfBytes, "application/pdf", fileName); } catch (Exception ex) { _logger.LogError(ex, "Error al generar PDF con QuestPDF para Existencia de Papel."); return StatusCode(StatusCodes.Status500InternalServerError, "Error interno al generar el PDF del reporte."); } } [HttpGet("movimiento-bobinas")] [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status403Forbidden)] public async Task GetReporteMovimientoBobinas( [FromQuery] DateTime fechaDesde, [FromQuery] DateTime fechaHasta, [FromQuery] int idPlanta) { if (!TienePermiso(PermisoVerReporteMovimientoBobinas)) return Forbid(); var (data, error) = await _reportesService.ObtenerMovimientoBobinasAsync(fechaDesde, fechaHasta, idPlanta); // <--- CORREGIDO if (error != null) return BadRequest(new { message = error }); return Ok(data); } [HttpGet("movimiento-bobinas/pdf")] [ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task GetReporteMovimientoBobinasPdf( [FromQuery] DateTime fechaDesde, [FromQuery] DateTime fechaHasta, [FromQuery] int idPlanta) { if (!TienePermiso(PermisoVerReporteMovimientoBobinas)) return Forbid(); var (data, error) = await _reportesService.ObtenerMovimientoBobinasAsync(fechaDesde, fechaHasta, idPlanta); if (error != null) return BadRequest(new { message = error }); if (data == null || !data.Any()) { return NotFound(new { message = "No hay datos para generar el PDF del movimiento de bobinas." }); } try { var planta = await _plantaRepository.GetByIdAsync(idPlanta); var viewModel = new MovimientoBobinasViewModel { Movimientos = data, NombrePlanta = planta?.Nombre ?? $"Planta ID {idPlanta}", FechaDesde = fechaDesde.ToString("dd/MM/yyyy"), FechaHasta = fechaHasta.ToString("dd/MM/yyyy") }; var document = new MovimientoBobinasDocument(viewModel); byte[] pdfBytes = await _pdfGenerator.GeneratePdfAsync(document); string fileName = $"MovimientoBobinas_Planta{idPlanta}_{fechaDesde:yyyyMMdd}_{fechaHasta:yyyyMMdd}.pdf"; return File(pdfBytes, "application/pdf", fileName); } catch (Exception ex) { _logger.LogError(ex, "Error al generar PDF para Movimiento de Bobinas."); return StatusCode(StatusCodes.Status500InternalServerError, "Error interno al generar el PDF del reporte."); } } // GET: api/reportes/movimiento-bobinas-estado [HttpGet("movimiento-bobinas-estado")] // Endpoint para los datos JSON [ProducesResponseType(typeof(MovimientoBobinasPorEstadoResponseDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task GetReporteMovimientoBobinasPorEstado( [FromQuery] DateTime fechaDesde, [FromQuery] DateTime fechaHasta, [FromQuery] int idPlanta) { if (!TienePermiso(PermisoVerReporteMovimientoBobinas)) return Forbid(); // Reutilizar permiso var (detalle, totales, error) = await _reportesService.ObtenerMovimientoBobinasPorEstadoAsync(fechaDesde, fechaHasta, idPlanta); if (error != null) return BadRequest(new { message = error }); if ((detalle == null || !detalle.Any()) && (totales == null || !totales.Any())) { return NotFound(new { message = "No hay datos para el reporte de movimiento de bobinas por estado." }); } var response = new MovimientoBobinasPorEstadoResponseDto { Detalle = detalle ?? Enumerable.Empty(), Totales = totales ?? Enumerable.Empty() }; return Ok(response); } [HttpGet("movimiento-bobinas-estado/pdf")] [ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task GetReporteMovimientoBobinasEstadoPdf( [FromQuery] DateTime fechaDesde, [FromQuery] DateTime fechaHasta, [FromQuery] int idPlanta) { if (!TienePermiso(PermisoVerReporteMovimientoBobinas)) return Forbid(); var (detalle, totales, error) = await _reportesService.ObtenerMovimientoBobinasPorEstadoAsync(fechaDesde, fechaHasta, idPlanta); if (error != null) return BadRequest(new { message = error }); if ((detalle == null || !detalle.Any()) && (totales == null || !totales.Any())) { return NotFound(new { message = "No hay datos para generar el PDF." }); } try { var planta = await _plantaRepository.GetByIdAsync(idPlanta); var viewModel = new MovimientoBobinasEstadoViewModel { Detalles = detalle ?? Enumerable.Empty(), Totales = totales, NombrePlanta = planta?.Nombre ?? $"Planta ID {idPlanta}", FechaDesde = fechaDesde.ToString("dd/MM/yyyy"), FechaHasta = fechaHasta.ToString("dd/MM/yyyy") }; var document = new MovimientoBobinasEstadoDocument(viewModel); byte[] pdfBytes = await _pdfGenerator.GeneratePdfAsync(document); string fileName = $"MovimientoBobinasEstado_Planta{idPlanta}_{fechaDesde:yyyyMMdd}_{fechaHasta:yyyyMMdd}.pdf"; return File(pdfBytes, "application/pdf", fileName); } catch (Exception ex) { _logger.LogError(ex, "Error al generar PDF para Movimiento de Bobinas por Estado."); return StatusCode(StatusCodes.Status500InternalServerError, "Error interno al generar el PDF del reporte."); } } // GET: api/reportes/listado-distribucion-general [HttpGet("listado-distribucion-general")] [ProducesResponseType(typeof(ListadoDistribucionGeneralResponseDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task GetListadoDistribucionGeneral( [FromQuery] int idPublicacion, [FromQuery] DateTime fechaDesde, [FromQuery] DateTime fechaHasta) { if (!TienePermiso(PermisoVerListadoDistribucion)) return Forbid(); var (resumen, promedios, error) = await _reportesService.ObtenerListadoDistribucionGeneralAsync(idPublicacion, fechaDesde, fechaHasta); if (error != null) return BadRequest(new { message = error }); if ((resumen == null || !resumen.Any()) && (promedios == null || !promedios.Any())) { return NotFound(new { message = "No hay datos para el listado de distribución general." }); } var response = new ListadoDistribucionGeneralResponseDto { Resumen = resumen ?? Enumerable.Empty(), PromediosPorDia = promedios ?? Enumerable.Empty() }; return Ok(response); } [HttpGet("listado-distribucion-general/pdf")] [ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task GetListadoDistribucionGeneralPdf( [FromQuery] int idPublicacion, [FromQuery] DateTime fechaDesde, [FromQuery] DateTime fechaHasta) { if (!TienePermiso(PermisoVerListadoDistribucion)) return Forbid(); // El servicio ya devuelve la tupla con los dos conjuntos de datos. var (resumen, promedios, error) = await _reportesService.ObtenerListadoDistribucionGeneralAsync(idPublicacion, fechaDesde, fechaHasta); if (error != null) return BadRequest(new { message = error }); if ((resumen == null || !resumen.Any()) && (promedios == null || !promedios.Any())) { return NotFound(new { message = "No hay datos para generar el PDF." }); } try { var publicacion = await _publicacionRepository.GetByIdSimpleAsync(idPublicacion); var viewModel = new ListadoDistribucionGeneralViewModel { ResumenMensual = resumen ?? Enumerable.Empty(), PromediosPorDia = promedios, NombrePublicacion = publicacion?.Nombre ?? "N/A", MesConsultado = fechaDesde.ToString("MMMM 'de' yyyy", new CultureInfo("es-ES")) }; var document = new ListadoDistribucionGeneralDocument(viewModel); byte[] pdfBytes = await _pdfGenerator.GeneratePdfAsync(document); string fileName = $"ListadoDistribucionGeneral_Pub{idPublicacion}_{fechaDesde:yyyyMM}.pdf"; return File(pdfBytes, "application/pdf", fileName); } catch (Exception ex) { _logger.LogError(ex, "Error al generar PDF para Listado Distribucion General."); return StatusCode(StatusCodes.Status500InternalServerError, "Error interno al generar el PDF del reporte."); } } // GET: api/reportes/listado-distribucion-canillas [HttpGet("listado-distribucion-canillas")] [ProducesResponseType(typeof(ListadoDistribucionCanillasResponseDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task GetListadoDistribucionCanillas( [FromQuery] int idPublicacion, [FromQuery] DateTime fechaDesde, [FromQuery] DateTime fechaHasta) { if (!TienePermiso(PermisoVerListadoDistribucion)) return Forbid(); var (simple, promedios, error) = await _reportesService.ObtenerListadoDistribucionCanillasAsync(idPublicacion, fechaDesde, fechaHasta); if (error != null) return BadRequest(new { message = error }); if ((simple == null || !simple.Any()) && (promedios == null || !promedios.Any())) { return NotFound(new { message = "No hay datos para el listado de distribución de canillas." }); } var response = new ListadoDistribucionCanillasResponseDto { DetalleSimple = simple ?? Enumerable.Empty(), PromediosPorDia = promedios ?? Enumerable.Empty() }; return Ok(response); } [HttpGet("listado-distribucion-canillas/pdf")] [ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task GetListadoDistribucionCanillasPdf( [FromQuery] int idPublicacion, [FromQuery] DateTime fechaDesde, [FromQuery] DateTime fechaHasta) { if (!TienePermiso(PermisoVerListadoDistribucion)) return Forbid(); var (simple, promedios, error) = await _reportesService.ObtenerListadoDistribucionCanillasAsync(idPublicacion, fechaDesde, fechaHasta); if (error != null) return BadRequest(new { message = error }); if ((simple == null || !simple.Any()) && (promedios == null || !promedios.Any())) { return NotFound(new { message = "No hay datos para generar el PDF." }); } try { var publicacion = await _publicacionRepository.GetByIdSimpleAsync(idPublicacion); var viewModel = new ListadoDistribucionCanillasViewModel { DetalleDiario = simple ?? Enumerable.Empty(), PromediosPorDia = promedios, NombrePublicacion = publicacion?.Nombre ?? "N/A", FechaDesde = fechaDesde.ToString("dd/MM/yyyy"), FechaHasta = fechaHasta.ToString("dd/MM/yyyy") }; var document = new ListadoDistribucionCanillasDocument(viewModel); byte[] pdfBytes = await _pdfGenerator.GeneratePdfAsync(document); string fileName = $"ListadoDistribucionCanillas_Pub{idPublicacion}_{fechaDesde:yyyyMMdd}_{fechaHasta:yyyyMMdd}.pdf"; return File(pdfBytes, "application/pdf", fileName); } catch (Exception ex) { _logger.LogError(ex, "Error al generar PDF para Listado Distribucion Canillas."); return StatusCode(StatusCodes.Status500InternalServerError, "Error interno al generar el PDF del reporte."); } } // GET: api/reportes/listado-distribucion-canillas-importe [HttpGet("listado-distribucion-canillas-importe")] [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task GetListadoDistribucionCanillasConImporte( [FromQuery] int idPublicacion, [FromQuery] DateTime fechaDesde, [FromQuery] DateTime fechaHasta, [FromQuery] bool esAccionista) { if (!TienePermiso(PermisoVerListadoDistribucion)) return Forbid(); var (data, error) = await _reportesService.ObtenerListadoDistribucionCanillasConImporteAsync(idPublicacion, fechaDesde, fechaHasta, esAccionista); if (error != null) return BadRequest(new { message = error }); if (data == null || !data.Any()) { return NotFound(new { message = "No hay datos para el listado de distribución de canillas con importe." }); } return Ok(data); } [HttpGet("listado-distribucion-canillas-importe/pdf")] [ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task GetListadoDistribucionCanillasConImportePdf( [FromQuery] int idPublicacion, [FromQuery] DateTime fechaDesde, [FromQuery] DateTime fechaHasta, [FromQuery] bool esAccionista) { if (!TienePermiso(PermisoVerListadoDistribucion)) return Forbid(); var (data, error) = await _reportesService.ObtenerListadoDistribucionCanillasConImporteAsync(idPublicacion, fechaDesde, fechaHasta, esAccionista); if (error != null) return BadRequest(new { message = error }); if (data == null || !data.Any()) { return NotFound(new { message = "No hay datos para generar el PDF." }); } try { var publicacion = await _publicacionRepository.GetByIdSimpleAsync(idPublicacion); var viewModel = new ListadoDistCanillasImporteViewModel { Detalles = data, NombrePublicacion = publicacion?.Nombre ?? "N/A", TipoDestinatario = esAccionista ? "Accionistas" : "Canillitas", FechaDesde = fechaDesde.ToString("dd/MM/yyyy"), FechaHasta = fechaHasta.ToString("dd/MM/yyyy") }; var document = new ListadoDistCanillasImporteDocument(viewModel); byte[] pdfBytes = await _pdfGenerator.GeneratePdfAsync(document); string tipoVendedor = esAccionista ? "Accionistas" : "Canillitas"; string fileName = $"ListadoDistCanImp_Pub{idPublicacion}_{tipoVendedor}_{fechaDesde:yyyyMMdd}_{fechaHasta:yyyyMMdd}.pdf"; return File(pdfBytes, "application/pdf", fileName); } catch (Exception ex) { _logger.LogError(ex, "Error al generar PDF para Listado Distribucion Canillas con Importe."); return StatusCode(StatusCodes.Status500InternalServerError, "Error interno al generar el PDF del reporte."); } } // GET: api/reportes/venta-mensual-secretaria/el-dia [HttpGet("venta-mensual-secretaria/el-dia")] [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task GetVentaMensualSecretariaElDia([FromQuery] DateTime fechaDesde, [FromQuery] DateTime fechaHasta) { if (!TienePermiso(PermisoVerListadoDistribucion)) return Forbid(); // Asumiendo RR002 para todos estos var (data, error) = await _reportesService.ObtenerVentaMensualSecretariaElDiaAsync(fechaDesde, fechaHasta); if (error != null) return BadRequest(new { message = error }); if (data == null || !data.Any()) return NotFound(new { message = "No hay datos para el reporte de ventas 'El Día'." }); return Ok(data); } [HttpGet("venta-mensual-secretaria/el-dia/pdf")] [ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task GetVentaMensualSecretariaElDiaPdf([FromQuery] DateTime fechaDesde, [FromQuery] DateTime fechaHasta) { if (!TienePermiso(PermisoVerListadoDistribucion)) return Forbid(); var (data, error) = await _reportesService.ObtenerVentaMensualSecretariaElDiaAsync(fechaDesde, fechaHasta); if (error != null) return BadRequest(new { message = error }); if (data == null || !data.Any()) return NotFound(new { message = "No hay datos para el reporte." }); try { var viewModel = new VentaMensualSecretariaElDiaViewModel { VentasDiarias = data, FechaDesde = fechaDesde.ToString("dd/MM/yyyy"), FechaHasta = fechaHasta.ToString("dd/MM/yyyy") }; var document = new VentaMensualSecretariaElDiaDocument(viewModel); byte[] pdfBytes = await _pdfGenerator.GeneratePdfAsync(document); string fileName = $"VentaMensualSecretaria_ElDia_{fechaDesde:yyyyMMdd}_{fechaHasta:yyyyMMdd}.pdf"; return File(pdfBytes, "application/pdf", fileName); } catch (Exception ex) { _logger.LogError(ex, "Error PDF VentaMensualSecretariaElDia."); return StatusCode(500, "Error interno al generar el PDF del reporte."); } } // GET: api/reportes/venta-mensual-secretaria/el-plata [HttpGet("venta-mensual-secretaria/el-plata")] [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task GetVentaMensualSecretariaElPlata([FromQuery] DateTime fechaDesde, [FromQuery] DateTime fechaHasta) { if (!TienePermiso(PermisoVerListadoDistribucion)) return Forbid(); // Asumiendo RR002 var (data, error) = await _reportesService.ObtenerVentaMensualSecretariaElPlataAsync(fechaDesde, fechaHasta); if (error != null) return BadRequest(new { message = error }); if (data == null || !data.Any()) return NotFound(new { message = "No hay datos para el reporte de ventas 'El Plata'." }); return Ok(data); } [HttpGet("venta-mensual-secretaria/el-plata/pdf")] [ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task GetVentaMensualSecretariaElPlataPdf([FromQuery] DateTime fechaDesde, [FromQuery] DateTime fechaHasta) { if (!TienePermiso(PermisoVerListadoDistribucion)) return Forbid(); var (data, error) = await _reportesService.ObtenerVentaMensualSecretariaElPlataAsync(fechaDesde, fechaHasta); if (error != null) return BadRequest(new { message = error }); if (data == null || !data.Any()) return NotFound(new { message = "No hay datos para el reporte." }); try { var viewModel = new VentaMensualSecretariaElPlataViewModel { VentasDiarias = data, FechaDesde = fechaDesde.ToString("dd/MM/yyyy"), FechaHasta = fechaHasta.ToString("dd/MM/yyyy") }; var document = new VentaMensualSecretariaElPlataDocument(viewModel); byte[] pdfBytes = await _pdfGenerator.GeneratePdfAsync(document); string fileName = $"VentaMensualSecretaria_ElPlata_{fechaDesde:yyyyMMdd}_{fechaHasta:yyyyMMdd}.pdf"; return File(pdfBytes, "application/pdf", fileName); } catch (Exception ex) { _logger.LogError(ex, "Error PDF VentaMensualSecretariaElPlata."); return StatusCode(500, "Error interno al generar el PDF del reporte."); } } // GET: api/reportes/venta-mensual-secretaria/tirada-devolucion [HttpGet("venta-mensual-secretaria/tirada-devolucion")] [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task GetVentaMensualSecretariaTirDevo([FromQuery] DateTime fechaDesde, [FromQuery] DateTime fechaHasta) { if (!TienePermiso(PermisoVerListadoDistribucion)) return Forbid(); // Asumiendo RR002 var (data, error) = await _reportesService.ObtenerVentaMensualSecretariaTirDevoAsync(fechaDesde, fechaHasta); if (error != null) return BadRequest(new { message = error }); if (data == null || !data.Any()) return NotFound(new { message = "No hay datos para el reporte de tirada/devolución." }); return Ok(data); } [HttpGet("venta-mensual-secretaria/tirada-devolucion/pdf")] [ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task GetVentaMensualSecretariaTirDevoPdf([FromQuery] DateTime fechaDesde, [FromQuery] DateTime fechaHasta) { if (!TienePermiso(PermisoVerListadoDistribucion)) return Forbid(); var (data, error) = await _reportesService.ObtenerVentaMensualSecretariaTirDevoAsync(fechaDesde, fechaHasta); if (error != null) return BadRequest(new { message = error }); if (data == null || !data.Any()) return NotFound(new { message = "No hay datos para el reporte." }); try { var viewModel = new VentaMensualSecretariaTirDevoViewModel { VentasDiarias = data, FechaDesde = fechaDesde.ToString("dd/MM/yyyy"), FechaHasta = fechaHasta.ToString("dd/MM/yyyy") }; var document = new VentaMensualSecretariaTirDevoDocument(viewModel); byte[] pdfBytes = await _pdfGenerator.GeneratePdfAsync(document); string fileName = $"VentaMensualSecretaria_TirDevo_{fechaDesde:yyyyMMdd}_{fechaHasta:yyyyMMdd}.pdf"; return File(pdfBytes, "application/pdf", fileName); } catch (Exception ex) { _logger.LogError(ex, "Error PDF VentaMensualSecretariaTirDevo."); return StatusCode(500, "Error interno al generar el PDF del reporte."); } } // GET: api/reportes/distribucion-canillas [HttpGet("distribucion-canillas")] // Endpoint para los datos JSON [ProducesResponseType(typeof(ReporteDistribucionCanillasResponseDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task GetReporteDistribucionCanillasData([FromQuery] DateTime fecha, [FromQuery] int idEmpresa) { if (!TienePermiso(PermisoVerComprobanteLiquidacionCanilla)) return Forbid(); var (canillas, canillasAcc, canillasAll, canillasFechaLiq, canillasAccFechaLiq, ctrlDevolucionesRemitos, ctrlDevolucionesParaDistCan, ctrlDevolucionesOtrosDias, error) = await _reportesService.ObtenerReporteDistribucionCanillasAsync(fecha, idEmpresa); if (error != null) return BadRequest(new { message = error }); // Una validación simple, podrías hacerla más granular si es necesario bool noHayDatos = (canillas == null || !canillas.Any()) && (canillasAcc == null || !canillasAcc.Any()) && (canillasAll == null || !canillasAll.Any()) && (ctrlDevolucionesParaDistCan == null || !ctrlDevolucionesParaDistCan.Any()); // Podrías añadir más aquí if (noHayDatos) { return NotFound(new { message = "No hay datos para el reporte de distribución de canillas." }); } var response = new ReporteDistribucionCanillasResponseDto { Canillas = canillas ?? Enumerable.Empty(), CanillasAccionistas = canillasAcc ?? Enumerable.Empty(), CanillasTodos = canillasAll ?? Enumerable.Empty(), CanillasLiquidadasOtraFecha = canillasFechaLiq ?? Enumerable.Empty(), CanillasAccionistasLiquidadasOtraFecha = canillasAccFechaLiq ?? Enumerable.Empty(), ControlDevolucionesRemitos = ctrlDevolucionesRemitos ?? Enumerable.Empty(), ControlDevolucionesDetalle = ctrlDevolucionesParaDistCan ?? Enumerable.Empty(), ControlDevolucionesOtrosDias = ctrlDevolucionesOtrosDias ?? Enumerable.Empty() }; return Ok(response); } [HttpGet("distribucion-canillas/pdf")] [ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task GetReporteDistribucionCanillasPdf([FromQuery] DateTime fecha, [FromQuery] int idEmpresa, [FromQuery] bool soloTotales = false) { if (!TienePermiso(PermisoVerComprobanteLiquidacionCanilla)) return Forbid(); var ( canillas, canillasAcc, canillasAll, canillasFechaLiq, canillasAccFechaLiq, remitos, ctrlDevoluciones, _, error ) = await _reportesService.ObtenerReporteDistribucionCanillasAsync(fecha, idEmpresa); if (error != null) return BadRequest(new { message = error }); // Verificamos si hay datos suficientes para CUALQUIERA de los dos reportes. if (!canillas.Any() && !canillasAcc.Any() && !canillasAll.Any()) { return NotFound(new { message = "No hay datos de distribución para generar el PDF." }); } try { var empresa = await _empresaRepository.GetByIdAsync(idEmpresa); var viewModel = new DistribucionCanillasViewModel { Canillas = canillas, CanillasAccionistas = canillasAcc, CanillasTodos = canillasAll, CanillasLiquidadasOtraFecha = canillasFechaLiq, CanillasAccionistasLiquidadasOtraFecha = canillasAccFechaLiq, ControlDevolucionesDetalle = ctrlDevoluciones, RemitoIngresado = remitos.FirstOrDefault()?.Remito ?? 0, Empresa = empresa?.Nombre ?? "N/A", FechaConsultada = fecha.ToString("dd/MM/yyyy") }; IDocument document; string tipoReporte; if (soloTotales) { document = new DistribucionCanillasTotalesDocument(viewModel); tipoReporte = "Totales"; } else { document = new DistribucionCanillasDocument(viewModel); tipoReporte = "Detalle"; } byte[] pdfBytes = await _pdfGenerator.GeneratePdfAsync(document); string fileName = $"DistribucionCanillas_{tipoReporte}_Emp{idEmpresa}_{fecha:yyyyMMdd}.pdf"; return File(pdfBytes, "application/pdf", fileName); } catch (Exception ex) { _logger.LogError(ex, "Error al generar PDF para Reporte Distribucion Canillas."); return StatusCode(StatusCodes.Status500InternalServerError, "Error interno al generar el PDF del reporte."); } } // GET: api/reportes/control-devoluciones [HttpGet("control-devoluciones")] [ProducesResponseType(typeof(ControlDevolucionesDataResponseDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task GetControlDevolucionesData([FromQuery] DateTime fecha, [FromQuery] int idEmpresa) { if (!TienePermiso(PermisoVerControlDevoluciones)) return Forbid(); var ( _, // canillas _, // canillasAcc _, // canillasAll _, // canillasFechaLiq _, // canillasAccFechaLiq ctrlDevolucionesRemitosData, // Para SP_ObtenerCtrlDevoluciones -> DataSet "DSObtenerCtrlDevoluciones" ctrlDevolucionesParaDistCanData, // Para SP_DistCanillasCantidadEntradaSalida -> DataSet "DSCtrlDevoluciones" ctrlDevolucionesOtrosDiasData, // Para SP_DistCanillasCantidadEntradaSalidaOtrosDias -> DataSet "DSCtrlDevolucionesOtrosDias" error ) = await _reportesService.ObtenerReporteDistribucionCanillasAsync(fecha, idEmpresa); // Reutilizamos este método if (error != null) return BadRequest(new { message = error }); // Adaptar la condición de "no hay datos" a los DataSets que realmente usa este reporte if ((ctrlDevolucionesParaDistCanData == null || !ctrlDevolucionesParaDistCanData.Any()) && (ctrlDevolucionesOtrosDiasData == null || !ctrlDevolucionesOtrosDiasData.Any()) && (ctrlDevolucionesRemitosData == null || !ctrlDevolucionesRemitosData.Any())) { return NotFound(new { message = "No hay datos para generar el reporte de control de devoluciones." }); } var response = new ControlDevolucionesDataResponseDto { DetallesCtrlDevoluciones = ctrlDevolucionesParaDistCanData ?? Enumerable.Empty(), DevolucionesOtrosDias = ctrlDevolucionesOtrosDiasData ?? Enumerable.Empty(), RemitosIngresados = ctrlDevolucionesRemitosData ?? Enumerable.Empty() }; return Ok(response); } [HttpGet("control-devoluciones/pdf")] [ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task GetReporteControlDevolucionesPdf([FromQuery] DateTime fecha, [FromQuery] int idEmpresa) { if (!TienePermiso(PermisoVerControlDevoluciones)) return Forbid(); var ( _, _, _, _, _, // Datos no utilizados remitos, detalles, otrosDias, error ) = await _reportesService.ObtenerReporteDistribucionCanillasAsync(fecha, idEmpresa); if (error != null) return BadRequest(new { message = error }); if ((detalles == null || !detalles.Any()) && (otrosDias == null || !otrosDias.Any())) { return NotFound(new { message = "No hay datos para generar el PDF para control de devoluciones." }); } try { var empresa = await _empresaRepository.GetByIdAsync(idEmpresa); // La creación del ViewModel es ahora mucho más limpia var viewModel = new ControlDevolucionesViewModel { Detalles = detalles ?? Enumerable.Empty(), TotalDevolucionDiasAnteriores = otrosDias?.Sum(d => d.Devueltos) ?? 0, NombreEmpresa = empresa?.Nombre ?? "N/A", FechaConsultada = fecha.ToString("dd/MM/yyyy") }; var document = new ControlDevolucionesDocument(viewModel); byte[] pdfBytes = await _pdfGenerator.GeneratePdfAsync(document); string fileName = $"ControlDevoluciones_Emp{idEmpresa}_{fecha:yyyyMMdd}.pdf"; return File(pdfBytes, "application/pdf", fileName); } catch (Exception ex) { _logger.LogError(ex, "Error al generar PDF para Control Devoluciones."); return StatusCode(StatusCodes.Status500InternalServerError, "Error interno al generar el PDF del reporte."); } } // GET: api/reportes/cuentas-distribuidores [HttpGet("cuentas-distribuidores")] [ProducesResponseType(typeof(ReporteCuentasDistribuidorResponseDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task GetReporteCuentasDistribuidoresData( [FromQuery] int idDistribuidor, [FromQuery] int idEmpresa, [FromQuery] DateTime fechaDesde, [FromQuery] DateTime fechaHasta) { if (!TienePermiso(PermisoVerBalanceCuentas)) return Forbid(); var (entradasSalidas, debitosCreditos, pagos, saldos, error) = await _reportesService.ObtenerReporteCuentasDistribuidorAsync(idDistribuidor, idEmpresa, fechaDesde, fechaHasta); if (error != null) return BadRequest(new { message = error }); if (!entradasSalidas.Any() && !debitosCreditos.Any() && !pagos.Any() && !saldos.Any()) { return NotFound(new { message = "No hay datos para generar el reporte de cuenta del distribuidor." }); } var distribuidor = await _distribuidorRepository.GetByIdAsync(idDistribuidor); var empresa = await _empresaRepository.GetByIdAsync(idEmpresa); var response = new ReporteCuentasDistribuidorResponseDto { EntradasSalidas = entradasSalidas ?? Enumerable.Empty(), DebitosCreditos = debitosCreditos ?? Enumerable.Empty(), Pagos = pagos ?? Enumerable.Empty(), Saldos = saldos ?? Enumerable.Empty(), NombreDistribuidor = distribuidor.Distribuidor?.Nombre, NombreEmpresa = empresa?.Nombre }; return Ok(response); } [HttpGet("cuentas-distribuidores/pdf")] [ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task GetReporteCuentasDistribuidoresPdf( [FromQuery] int idDistribuidor, [FromQuery] int idEmpresa, [FromQuery] DateTime fechaDesde, [FromQuery] DateTime fechaHasta) { if (!TienePermiso(PermisoVerBalanceCuentas)) return Forbid(); var (entradasSalidas, debitosCreditos, pagos, saldos, error) = await _reportesService.ObtenerReporteCuentasDistribuidorAsync(idDistribuidor, idEmpresa, fechaDesde, fechaHasta); if (error != null) return BadRequest(new { message = error }); if (!entradasSalidas.Any() && !debitosCreditos.Any() && !pagos.Any()) { return NotFound(new { message = "No hay datos para generar el reporte de cuenta del distribuidor." }); } try { var distribuidor = await _distribuidorRepository.GetByIdAsync(idDistribuidor); var viewModel = new CuentasDistribuidorViewModel { Movimientos = entradasSalidas, Pagos = pagos, DebitosCreditos = debitosCreditos, SaldoDeCuenta = saldos.FirstOrDefault()?.Monto ?? 0, // <-- Se asigna a SaldoDeCuenta NombreDistribuidor = distribuidor.Distribuidor?.Nombre ?? $"Distribuidor ID {idDistribuidor}", FechaDesde = fechaDesde.ToString("dd/MM/yyyy"), FechaHasta = fechaHasta.ToString("dd/MM/yyyy"), }; var document = new CuentasDistribuidorDocument(viewModel); byte[] pdfBytes = await _pdfGenerator.GeneratePdfAsync(document); string fileName = $"CuentaDistribuidor_{idDistribuidor}_Emp{idEmpresa}_{fechaDesde:yyyyMMdd}_{fechaHasta:yyyyMMdd}.pdf"; return File(pdfBytes, "application/pdf", fileName); } catch (Exception ex) { _logger.LogError(ex, "Error al generar PDF para Cuentas Distribuidores."); return StatusCode(StatusCodes.Status500InternalServerError, "Error interno al generar el PDF del reporte."); } } // GET: api/reportes/tiradas-publicaciones-secciones [HttpGet("tiradas-publicaciones-secciones")] [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task GetReporteTiradasPublicacionesSeccionesData( [FromQuery] int idPublicacion, [FromQuery] DateTime fechaDesde, [FromQuery] DateTime fechaHasta, [FromQuery] int? idPlanta, [FromQuery] bool consolidado = false) { if (!TienePermiso(PermisoVerReporteTiradas)) return Forbid(); IEnumerable data; string? errorMsg; if (consolidado) { (data, errorMsg) = await _reportesService.ObtenerTiradasPublicacionesSeccionesConsolidadoAsync(idPublicacion, fechaDesde, fechaHasta); } else { if (!idPlanta.HasValue) return BadRequest(new { message = "Se requiere IdPlanta para reportes no consolidados." }); (data, errorMsg) = await _reportesService.ObtenerTiradasPublicacionesSeccionesAsync(idPublicacion, fechaDesde, fechaHasta, idPlanta.Value); } if (errorMsg != null) return BadRequest(new { message = errorMsg }); if (data == null || !data.Any()) return NotFound(new { message = "No hay datos para generar el reporte." }); return Ok(data); } [HttpGet("tiradas-publicaciones-secciones/pdf")] [ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task GetReporteTiradasPublicacionesSeccionesPdf( [FromQuery] int idPublicacion, [FromQuery] DateTime fechaDesde, [FromQuery] DateTime fechaHasta, [FromQuery] int? idPlanta, [FromQuery] bool consolidado = false) { if (!TienePermiso(PermisoVerReporteTiradas)) return Forbid(); IEnumerable data; string? errorMsg; if (consolidado) { (data, errorMsg) = await _reportesService.ObtenerTiradasPublicacionesSeccionesConsolidadoAsync(idPublicacion, fechaDesde, fechaHasta); } else { if (!idPlanta.HasValue) return BadRequest(new { message = "Se requiere IdPlanta para reportes no consolidados." }); (data, errorMsg) = await _reportesService.ObtenerTiradasPublicacionesSeccionesAsync(idPublicacion, fechaDesde, fechaHasta, idPlanta.Value); } if (errorMsg != null) return BadRequest(new { message = errorMsg }); if (data == null || !data.Any()) return NotFound(new { message = "No hay datos para generar el reporte." }); try { var publicacion = await _publicacionRepository.GetByIdSimpleAsync(idPublicacion); string? nombrePlanta = null; if (!consolidado && idPlanta.HasValue) { var planta = await _plantaRepository.GetByIdAsync(idPlanta.Value); nombrePlanta = planta?.Nombre; } var viewModel = new TiradasPublicacionesSeccionesViewModel { Detalles = data, NombrePublicacion = publicacion?.Nombre ?? "N/A", MesConsultado = fechaDesde.ToString("MMMM 'de' yyyy", new CultureInfo("es-ES")), NombrePlanta = nombrePlanta }; var document = new TiradasPublicacionesSeccionesDocument(viewModel); byte[] pdfBytes = await _pdfGenerator.GeneratePdfAsync(document); string fileName = $"TiradasSecciones_Pub{idPublicacion}_{(consolidado ? "Consolidado" : $"Planta{idPlanta}")}_{fechaDesde:yyyyMM}.pdf"; return File(pdfBytes, "application/pdf", fileName); } catch (Exception ex) { _logger.LogError(ex, "Error PDF Tiradas Publicaciones Secciones."); return StatusCode(500, "Error interno al generar el PDF del reporte."); } } // GET: api/reportes/consumo-bobinas-seccion [HttpGet("consumo-bobinas-seccion")] [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task GetReporteConsumoBobinasSeccionData( [FromQuery] DateTime fechaDesde, [FromQuery] DateTime fechaHasta, [FromQuery] int? idPlanta, [FromQuery] bool consolidado = false) { if (!TienePermiso(PermisoVerReporteConsumoBobinas)) return Forbid(); IEnumerable data; string? errorMsg; if (consolidado) { (data, errorMsg) = await _reportesService.ObtenerConsumoBobinasPorSeccionConsolidadoAsync(fechaDesde, fechaHasta); } else { if (!idPlanta.HasValue) return BadRequest(new { message = "Se requiere IdPlanta para reportes no consolidados." }); (data, errorMsg) = await _reportesService.ObtenerConsumoBobinasPorSeccionAsync(fechaDesde, fechaHasta, idPlanta.Value); } if (errorMsg != null) return BadRequest(new { message = errorMsg }); if (data == null || !data.Any()) return NotFound(new { message = "No hay datos para generar el reporte." }); return Ok(data); } [HttpGet("consumo-bobinas-seccion/pdf")] [ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task GetReporteConsumoBobinasSeccionPdf( [FromQuery] DateTime fechaDesde, [FromQuery] DateTime fechaHasta, [FromQuery] int? idPlanta, [FromQuery] bool consolidado = false) { if (!TienePermiso(PermisoVerReporteConsumoBobinas)) return Forbid(); IEnumerable data; string? errorMsg; if (consolidado) { (data, errorMsg) = await _reportesService.ObtenerConsumoBobinasPorSeccionConsolidadoAsync(fechaDesde, fechaHasta); } else { if (!idPlanta.HasValue) return BadRequest(new { message = "Se requiere IdPlanta para reportes no consolidados." }); (data, errorMsg) = await _reportesService.ObtenerConsumoBobinasPorSeccionAsync(fechaDesde, fechaHasta, idPlanta.Value); } if (errorMsg != null) return BadRequest(new { message = errorMsg }); if (data == null || !data.Any()) return NotFound(new { message = "No hay datos para generar el reporte." }); try { string? nombrePlanta = null; if (!consolidado && idPlanta.HasValue) { var planta = await _plantaRepository.GetByIdAsync(idPlanta.Value); nombrePlanta = planta?.Nombre; } var viewModel = new ConsumoBobinasSeccionViewModel { Detalles = data, NombrePlanta = nombrePlanta, FechaDesde = fechaDesde.ToString("dd/MM/yyyy"), FechaHasta = fechaHasta.ToString("dd/MM/yyyy") }; var document = new ConsumoBobinasSeccionDocument(viewModel); byte[] pdfBytes = await _pdfGenerator.GeneratePdfAsync(document); string fileName = $"ConsumoBobinasSeccion_{(consolidado ? "Consolidado" : $"Planta{idPlanta}")}_{fechaDesde:yyyyMMdd}_{fechaHasta:yyyyMMdd}.pdf"; return File(pdfBytes, "application/pdf", fileName); } catch (Exception ex) { _logger.LogError(ex, "Error PDF Consumo Bobinas por Seccion."); return StatusCode(500, "Error interno al generar el PDF del reporte."); } } // GET: api/reportes/consumo-bobinas-publicacion [HttpGet("consumo-bobinas-publicacion")] [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task GetReporteConsumoBobinasPublicacionData( [FromQuery] DateTime fechaDesde, [FromQuery] DateTime fechaHasta) { if (!TienePermiso(PermisoVerReporteConsumoBobinas)) return Forbid(); var (data, error) = await _reportesService.ObtenerConsumoBobinasPorPublicacionAsync(fechaDesde, fechaHasta); if (error != null) return BadRequest(new { message = error }); if (data == null || !data.Any()) return NotFound(new { message = "No hay datos para generar el reporte." }); return Ok(data); } [HttpGet("consumo-bobinas-publicacion/pdf")] [ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task GetReporteConsumoBobinasPublicacionPdf( [FromQuery] DateTime fechaDesde, [FromQuery] DateTime fechaHasta) { if (!TienePermiso(PermisoVerReporteConsumoBobinas)) return Forbid(); var (data, error) = await _reportesService.ObtenerConsumoBobinasPorPublicacionAsync(fechaDesde, fechaHasta); if (error != null) return BadRequest(new { message = error }); if (data == null || !data.Any()) return NotFound(new { message = "No hay datos para generar el reporte." }); try { var viewModel = new ConsumoBobinasPublicacionViewModel { Detalles = data, FechaDesde = fechaDesde.ToString("dd/MM/yyyy"), FechaHasta = fechaHasta.ToString("dd/MM/yyyy") }; var document = new ConsumoBobinasPublicacionDocument(viewModel); byte[] pdfBytes = await _pdfGenerator.GeneratePdfAsync(document); string fileName = $"ConsumoBobinasPublicacion_{fechaDesde:yyyyMMdd}_{fechaHasta:yyyyMMdd}.pdf"; return File(pdfBytes, "application/pdf", fileName); } catch (Exception ex) { _logger.LogError(ex, "Error PDF Consumo Bobinas por Publicacion."); return StatusCode(500, "Error interno al generar el PDF del reporte."); } } // GET: api/reportes/comparativa-consumo-bobinas [HttpGet("comparativa-consumo-bobinas")] [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task GetReporteComparativaConsumoBobinasData( [FromQuery] DateTime fechaInicioMesA, [FromQuery] DateTime fechaFinMesA, [FromQuery] DateTime fechaInicioMesB, [FromQuery] DateTime fechaFinMesB, [FromQuery] int? idPlanta, [FromQuery] bool consolidado = false) { if (!TienePermiso(PermisoVerReporteConsumoBobinas)) return Forbid(); IEnumerable data; string? errorMsg; if (consolidado) { (data, errorMsg) = await _reportesService.ObtenerComparativaConsumoBobinasConsolidadoAsync(fechaInicioMesA, fechaFinMesA, fechaInicioMesB, fechaFinMesB); } else { if (!idPlanta.HasValue) return BadRequest(new { message = "Se requiere IdPlanta para reportes no consolidados." }); (data, errorMsg) = await _reportesService.ObtenerComparativaConsumoBobinasAsync(fechaInicioMesA, fechaFinMesA, fechaInicioMesB, fechaFinMesB, idPlanta.Value); } if (errorMsg != null) return BadRequest(new { message = errorMsg }); if (data == null || !data.Any()) return NotFound(new { message = "No hay datos para generar el reporte." }); return Ok(data); } [HttpGet("comparativa-consumo-bobinas/pdf")] [ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task GetReporteComparativaConsumoBobinasPdf( [FromQuery] DateTime fechaInicioMesA, [FromQuery] DateTime fechaFinMesA, [FromQuery] DateTime fechaInicioMesB, [FromQuery] DateTime fechaFinMesB, [FromQuery] int? idPlanta, [FromQuery] bool consolidado = false) { if (!TienePermiso(PermisoVerReporteConsumoBobinas)) return Forbid(); IEnumerable data; string? errorMsg; if (consolidado) { (data, errorMsg) = await _reportesService.ObtenerComparativaConsumoBobinasConsolidadoAsync(fechaInicioMesA, fechaFinMesA, fechaInicioMesB, fechaFinMesB); } else { if (!idPlanta.HasValue) return BadRequest(new { message = "Se requiere IdPlanta para reportes no consolidados." }); (data, errorMsg) = await _reportesService.ObtenerComparativaConsumoBobinasAsync(fechaInicioMesA, fechaFinMesA, fechaInicioMesB, fechaFinMesB, idPlanta.Value); } if (errorMsg != null) return BadRequest(new { message = errorMsg }); if (data == null || !data.Any()) return NotFound(new { message = "No hay datos para generar el reporte." }); try { string? nombrePlanta = null; if (!consolidado && idPlanta.HasValue) { var planta = await _plantaRepository.GetByIdAsync(idPlanta.Value); nombrePlanta = planta?.Nombre; } var viewModel = new ComparativaConsumoBobinasViewModel { Detalles = data, NombrePlanta = nombrePlanta, MesA = fechaInicioMesA.ToString("MMMM yyyy", new CultureInfo("es-ES")), MesB = fechaInicioMesB.ToString("MMMM yyyy", new CultureInfo("es-ES")) }; var document = new ComparativaConsumoBobinasDocument(viewModel); byte[] pdfBytes = await _pdfGenerator.GeneratePdfAsync(document); string fileName = $"ComparativaConsumoBobinas_{(consolidado ? "Consolidado" : $"Planta{idPlanta}")}_{fechaInicioMesA:yyyyMM}_vs_{fechaInicioMesB:yyyyMM}.pdf"; return File(pdfBytes, "application/pdf", fileName); } catch (Exception ex) { _logger.LogError(ex, "Error PDF Comparativa Consumo Bobinas."); return StatusCode(500, "Error interno al generar el PDF del reporte."); } } // GET: api/reportes/listado-distribucion-distribuidores [HttpGet("listado-distribucion-distribuidores")] [ProducesResponseType(typeof(ListadoDistribucionDistribuidoresResponseDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task GetListadoDistribucionDistribuidores( [FromQuery] int idDistribuidor, [FromQuery] int idPublicacion, [FromQuery] DateTime fechaDesde, [FromQuery] DateTime fechaHasta) { if (!TienePermiso(PermisoVerListadoDistribucion)) return Forbid(); // RR002 (IEnumerable simple, IEnumerable promedios, string? error) result = await _reportesService.ObtenerListadoDistribucionDistribuidoresAsync(idDistribuidor, idPublicacion, fechaDesde, fechaHasta); if (result.error != null) return BadRequest(new { message = result.error }); if ((result.simple == null || !result.simple.Any()) && (result.promedios == null || !result.promedios.Any())) { return NotFound(new { message = "No hay datos para el listado de distribución de distribuidores." }); } var response = new ListadoDistribucionDistribuidoresResponseDto { DetalleSimple = result.simple ?? Enumerable.Empty(), PromediosPorDia = result.promedios ?? Enumerable.Empty() }; return Ok(response); } [HttpGet("listado-distribucion-distribuidores/pdf")] [ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task GetListadoDistribucionDistribuidoresPdf( [FromQuery] int idDistribuidor, [FromQuery] int idPublicacion, [FromQuery] DateTime fechaDesde, [FromQuery] DateTime fechaHasta) { if (!TienePermiso(PermisoVerListadoDistribucion)) return Forbid(); var (simple, promedios, error) = await _reportesService.ObtenerListadoDistribucionDistribuidoresAsync(idDistribuidor, idPublicacion, fechaDesde, fechaHasta); if (error != null) return BadRequest(new { message = error }); if ((simple == null || !simple.Any()) && (promedios == null || !promedios.Any())) { return NotFound(new { message = "No hay datos para generar el PDF." }); } try { var publicacionData = await _publicacionRepository.GetByIdSimpleAsync(idPublicacion); var distribuidorData = await _distribuidorRepository.GetByIdAsync(idDistribuidor); var viewModel = new ListadoDistribucionDistribuidoresViewModel { DetalleDiario = simple ?? Enumerable.Empty(), PromediosPorDia = promedios, NombrePublicacion = publicacionData?.Nombre ?? "N/A", NombreDistribuidor = distribuidorData.Distribuidor?.Nombre ?? "N/A", FechaDesde = fechaDesde.ToString("dd/MM/yyyy"), FechaHasta = fechaHasta.ToString("dd/MM/yyyy") }; var document = new ListadoDistribucionDistribuidoresDocument(viewModel); byte[] pdfBytes = await _pdfGenerator.GeneratePdfAsync(document); string fileName = $"ListadoDistribucion_Dist{idDistribuidor}_Pub{idPublicacion}_{fechaDesde:yyyyMMdd}_{fechaHasta:yyyyMMdd}.pdf"; return File(pdfBytes, "application/pdf", fileName); } catch (Exception ex) { _logger.LogError(ex, "Error al generar PDF para Listado Distribucion (Distribuidores)."); return StatusCode(StatusCodes.Status500InternalServerError, "Error interno al generar el PDF del reporte."); } } [HttpGet("ticket-liquidacion-canilla/pdf")] [ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task GetTicketLiquidacionCanillaPdf( [FromQuery] DateTime fecha, [FromQuery] int idCanilla, [FromQuery] bool esAccionista = false) { if (!TienePermiso(PermisoVerComprobanteLiquidacionCanilla)) return Forbid(); var (detalles, ganancias, error) = await _reportesService.ObtenerDatosTicketLiquidacionAsync(fecha, idCanilla); if (error != null) return BadRequest(new { message = error }); if (detalles == null || !detalles.Any()) { return NotFound(new { message = "No hay detalles de liquidación para generar el PDF." }); } try { var viewModel = new LiquidacionCanillaViewModel { Detalles = detalles, Ganancias = ganancias, FechaLiquidacion = fecha.ToString("dd/MM/yyyy"), EsAccionista = esAccionista }; var document = new LiquidacionCanillaDocument(viewModel); byte[] pdfBytes = await _pdfGenerator.GeneratePdfAsync(document); string tipo = esAccionista ? "Accionista" : "Canillita"; string fileName = $"TicketLiquidacion_{tipo}_{idCanilla}_{fecha:yyyyMMdd}.pdf"; return File(pdfBytes, "application/pdf", fileName); } catch (Exception ex) { _logger.LogError(ex, "Error al generar PDF para Ticket Liquidación Canilla. Fecha: {Fecha}, Canilla: {IdCanilla}", fecha, idCanilla); return StatusCode(StatusCodes.Status500InternalServerError, "Error interno al generar el PDF del ticket."); } } // GET: api/reportes/novedades-canillas // Obtiene los datos para el reporte de novedades de canillitas [HttpGet("novedades-canillas")] [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] // Si no hay datos [ProducesResponseType(StatusCodes.Status500InternalServerError)] public async Task GetReporteNovedadesCanillasData( [FromQuery] int idEmpresa, [FromQuery] DateTime fechaDesde, [FromQuery] DateTime fechaHasta) { if (!TienePermiso(PermisoVerReporteNovedadesCanillas)) { _logger.LogWarning("Acceso denegado a GetReporteNovedadesCanillasData. Usuario: {User}", User.Identity?.Name ?? "Desconocido"); return Forbid(); } if (fechaDesde > fechaHasta) { return BadRequest(new { message = "La fecha 'desde' no puede ser posterior a la fecha 'hasta'." }); } try { var reporteData = await _novedadCanillaService.ObtenerReporteNovedadesAsync(idEmpresa, fechaDesde, fechaHasta); if (reporteData == null || !reporteData.Any()) { // Devolver Ok con array vacío en lugar de NotFound para que el frontend pueda manejarlo como "sin datos" return Ok(Enumerable.Empty()); } return Ok(reporteData); } catch (Exception ex) { _logger.LogError(ex, "Error al generar datos para el reporte de novedades de canillitas. Empresa: {IdEmpresa}, Desde: {FechaDesde}, Hasta: {FechaHasta}", idEmpresa, fechaDesde, fechaHasta); return StatusCode(StatusCodes.Status500InternalServerError, new { message = "Error interno al generar el reporte de novedades." }); } } // GET: api/reportes/novedades-canillas/pdf // Genera el PDF del reporte de novedades de canillitas [HttpGet("novedades-canillas/pdf")] [ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] public async Task GetReporteNovedadesCanillasPdf( [FromQuery] int idEmpresa, [FromQuery] DateTime fechaDesde, [FromQuery] DateTime fechaHasta) { if (!TienePermiso(PermisoVerReporteNovedadesCanillas)) return Forbid(); if (fechaDesde > fechaHasta) return BadRequest(new { message = "La fecha 'desde' no puede ser posterior a la fecha 'hasta'." }); try { var novedadesDataTask = _novedadCanillaService.ObtenerReporteNovedadesAsync(idEmpresa, fechaDesde, fechaHasta); var gananciasDataTask = _novedadCanillaService.ObtenerReporteGananciasAsync(idEmpresa, fechaDesde, fechaHasta); await Task.WhenAll(novedadesDataTask, gananciasDataTask); var novedadesData = await novedadesDataTask; var gananciasData = await gananciasDataTask; if ((novedadesData == null || !novedadesData.Any()) && (gananciasData == null || !gananciasData.Any())) { return NotFound(new { message = "No hay datos para generar el PDF con los parámetros seleccionados." }); } var empresa = await _empresaRepository.GetByIdAsync(idEmpresa); var viewModel = new NovedadesCanillasViewModel { ResumenCanillas = gananciasData, DetallesNovedades = novedadesData ?? Enumerable.Empty(), NombreEmpresa = empresa?.Nombre ?? "N/A", FechaDesde = fechaDesde.ToString("dd/MM/yyyy"), FechaHasta = fechaHasta.ToString("dd/MM/yyyy") }; var document = new NovedadesCanillasDocument(viewModel); byte[] pdfBytes = await _pdfGenerator.GeneratePdfAsync(document); string fileName = $"ReporteNovedadesCanillas_Emp{idEmpresa}_{fechaDesde:yyyyMMdd}_{fechaHasta:yyyyMMdd}.pdf"; return File(pdfBytes, "application/pdf", fileName); } catch (Exception ex) { _logger.LogError(ex, "Error al generar PDF para el reporte de novedades de canillitas. Empresa: {IdEmpresa}", idEmpresa); return StatusCode(StatusCodes.Status500InternalServerError, new { message = $"Error interno al generar el PDF: {ex.Message}" }); } } // GET: api/reportes/novedades-canillas-ganancias [HttpGet("novedades-canillas-ganancias")] [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task GetReporteGananciasCanillasData( [FromQuery] int idEmpresa, [FromQuery] DateTime fechaDesde, [FromQuery] DateTime fechaHasta) { if (!TienePermiso(PermisoVerReporteNovedadesCanillas)) return Forbid(); // RR004 if (fechaDesde > fechaHasta) { return BadRequest(new { message = "La fecha 'desde' no puede ser posterior a la fecha 'hasta'." }); } try { var gananciasData = await _novedadCanillaService.ObtenerReporteGananciasAsync(idEmpresa, fechaDesde, fechaHasta); if (gananciasData == null || !gananciasData.Any()) { return Ok(Enumerable.Empty()); } return Ok(gananciasData); } catch (Exception ex) { _logger.LogError(ex, "Error al obtener datos de ganancias para el reporte de novedades. Empresa: {IdEmpresa}", idEmpresa); return StatusCode(StatusCodes.Status500InternalServerError, new { message = "Error interno al obtener datos de ganancias." }); } } // GET: api/reportes/listado-distribucion-mensual/diarios [HttpGet("listado-distribucion-mensual/diarios")] [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status403Forbidden)] public async Task GetListadoDistMensualDiarios( [FromQuery] DateTime fechaDesde, [FromQuery] DateTime fechaHasta, [FromQuery] bool esAccionista) { if (!TienePermiso(PermisoVerReporteListadoDistMensual)) return Forbid(); if (fechaDesde > fechaHasta) return BadRequest(new { message = "Fecha Desde no puede ser mayor a Fecha Hasta." }); var (data, error) = await _reportesService.ObtenerReporteMensualDiariosAsync(fechaDesde, fechaHasta, esAccionista); if (error != null) return BadRequest(new { message = error }); return Ok(data ?? Enumerable.Empty()); } [HttpGet("listado-distribucion-mensual/diarios/pdf")] [ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] public async Task GetListadoDistMensualDiariosPdf( [FromQuery] DateTime fechaDesde, [FromQuery] DateTime fechaHasta, [FromQuery] bool esAccionista) { if (!TienePermiso(PermisoVerReporteListadoDistMensual)) return Forbid(); if (fechaDesde > fechaHasta) return BadRequest(new { message = "Fecha Desde no puede ser mayor a Fecha Hasta." }); var (data, error) = await _reportesService.ObtenerReporteMensualDiariosAsync(fechaDesde, fechaHasta, esAccionista); if (error != null) return BadRequest(new { message = error }); if (data == null || !data.Any()) return NotFound(new { message = "No hay datos para generar el PDF." }); try { var viewModel = new ListadoDistCanMensualDiariosViewModel { Detalles = data, TipoDestinatario = esAccionista ? "Accionistas" : "Canillitas", FechaDesde = fechaDesde.ToString("dd/MM/yyyy"), FechaHasta = fechaHasta.ToString("dd/MM/yyyy") }; var document = new ListadoDistCanMensualDiariosDocument(viewModel); byte[] pdfBytes = await _pdfGenerator.GeneratePdfAsync(document); string tipoDesc = esAccionista ? "Accionistas" : "Canillitas"; string fileName = $"ListadoDistMensualDiarios_{tipoDesc}_{fechaDesde:yyyyMMdd}_{fechaHasta:yyyyMMdd}.pdf"; return File(pdfBytes, "application/pdf", fileName); } catch (Exception ex) { _logger.LogError(ex, "Error PDF ListadoDistMensualDiarios"); return StatusCode(500, "Error interno al generar el PDF del reporte."); } } // GET: api/reportes/listado-distribucion-mensual/publicaciones [HttpGet("listado-distribucion-mensual/publicaciones")] [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status403Forbidden)] public async Task GetListadoDistMensualPorPublicacion( [FromQuery] DateTime fechaDesde, [FromQuery] DateTime fechaHasta, [FromQuery] bool esAccionista) { if (!TienePermiso(PermisoVerReporteListadoDistMensual)) return Forbid(); if (fechaDesde > fechaHasta) return BadRequest(new { message = "Fecha Desde no puede ser mayor a Fecha Hasta." }); var (data, error) = await _reportesService.ObtenerReporteMensualPorPublicacionAsync(fechaDesde, fechaHasta, esAccionista); if (error != null) return BadRequest(new { message = error }); return Ok(data ?? Enumerable.Empty()); } [HttpGet("listado-distribucion-mensual/publicaciones/pdf")] [ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] public async Task GetListadoDistMensualPorPublicacionPdf( [FromQuery] DateTime fechaDesde, [FromQuery] DateTime fechaHasta, [FromQuery] bool esAccionista) { if (!TienePermiso(PermisoVerReporteListadoDistMensual)) return Forbid(); if (fechaDesde > fechaHasta) return BadRequest(new { message = "Fecha Desde no puede ser mayor a Fecha Hasta." }); var (data, error) = await _reportesService.ObtenerReporteMensualPorPublicacionAsync(fechaDesde, fechaHasta, esAccionista); if (error != null) return BadRequest(new { message = error }); if (data == null || !data.Any()) return NotFound(new { message = "No hay datos para generar el PDF." }); try { var viewModel = new ListadoDistCanMensualViewModel { Detalles = data, TipoDestinatario = esAccionista ? "Accionistas" : "Canillitas", FechaDesde = fechaDesde.ToString("dd/MM/yyyy"), FechaHasta = fechaHasta.ToString("dd/MM/yyyy") }; var document = new ListadoDistCanMensualDocument(viewModel); byte[] pdfBytes = await _pdfGenerator.GeneratePdfAsync(document); string tipoDesc = esAccionista ? "Accionistas" : "Canillitas"; string fileName = $"ListadoDistMensualPub_{tipoDesc}_{fechaDesde:yyyyMMdd}_{fechaHasta:yyyyMMdd}.pdf"; return File(pdfBytes, "application/pdf", fileName); } catch (Exception ex) { _logger.LogError(ex, "Error PDF ListadoDistMensualPorPublicacion"); return StatusCode(500, "Error interno al generar el PDF del reporte."); } } [HttpGet("suscripciones/facturas-para-publicidad/pdf")] [ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] public async Task GetReporteFacturasPublicidadPdf([FromQuery] int anio, [FromQuery] int mes) { if (!TienePermiso(PermisoVerReporteFacturasPublicidad)) return Forbid(); var (data, error) = await _reportesService.ObtenerFacturasParaReportePublicidad(anio, mes); if (error != null) return BadRequest(new { message = error }); if (data == null || !data.Any()) { return NotFound(new { message = "No hay facturas pagadas y pendientes de facturar para el período seleccionado." }); } try { // --- INICIO DE LA LÓGICA DE AGRUPACIÓN --- var datosAgrupados = data .GroupBy(f => f.IdEmpresa) .Select(g => new DatosEmpresaViewModel { NombreEmpresa = g.First().NombreEmpresa, Facturas = g.ToList() }) .OrderBy(e => e.NombreEmpresa); var viewModel = new FacturasPublicidadViewModel { DatosPorEmpresa = datosAgrupados, Periodo = new DateTime(anio, mes, 1).ToString("MMMM yyyy", new CultureInfo("es-ES")), FechaGeneracion = DateTime.Now.ToString("dd/MM/yyyy HH:mm") }; // --- FIN DE LA LÓGICA DE AGRUPACIÓN --- var document = new FacturasPublicidadDocument(viewModel); byte[] pdfBytes = await _pdfGenerator.GeneratePdfAsync(document); string fileName = $"ReportePublicidad_Suscripciones_{anio}-{mes:D2}.pdf"; return File(pdfBytes, "application/pdf", fileName); } catch (Exception ex) { _logger.LogError(ex, "Error al generar PDF para Reporte de Facturas a Publicidad."); return StatusCode(500, "Error interno al generar el PDF del reporte."); } } [HttpGet("suscripciones/distribucion/pdf")] [ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)] public async Task GetReporteDistribucionSuscripcionesPdf([FromQuery] DateTime fechaDesde, [FromQuery] DateTime fechaHasta) { if (!TienePermiso(PermisoVerReporteDistSuscripciones)) return Forbid(); var (altas, bajas, error) = await _reportesService.ObtenerReporteDistribucionSuscripcionesAsync(fechaDesde, fechaHasta); if (error != null) return BadRequest(new { message = error }); if ((altas == null || !altas.Any()) && (bajas == null || !bajas.Any())) { return NotFound(new { message = "No se encontraron suscripciones activas ni bajas para el período seleccionado." }); } try { var viewModel = new DistribucionSuscripcionesViewModel(altas ?? Enumerable.Empty(), bajas ?? Enumerable.Empty()) { FechaDesde = fechaDesde.ToString("dd/MM/yyyy"), FechaHasta = fechaHasta.ToString("dd/MM/yyyy"), FechaGeneracion = DateTime.Now.ToString("dd/MM/yyyy HH:mm") }; var document = new DistribucionSuscripcionesDocument(viewModel); byte[] pdfBytes = await _pdfGenerator.GeneratePdfAsync(document); string fileName = $"ReporteDistribucionSuscripciones_{fechaDesde:yyyyMMdd}_al_{fechaHasta:yyyyMMdd}.pdf"; return File(pdfBytes, "application/pdf", fileName); } catch (Exception ex) { _logger.LogError(ex, "Error al generar PDF para Reporte de Distribución de Suscripciones."); return StatusCode(500, "Error interno al generar el PDF del reporte."); } } } }