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<ReportesController> _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";
        private const string PermisoVerReportesSecretaria = "RR012";

        public ReportesController(
            IReportesService reportesService,
            INovedadCanillaService novedadCanillaService,
            ILogger<ReportesController> 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<ExistenciaPapelDto>), StatusCodes.Status200OK)]
        [ProducesResponseType(StatusCodes.Status400BadRequest)]
        [ProducesResponseType(StatusCodes.Status403Forbidden)]
        [ProducesResponseType(StatusCodes.Status500InternalServerError)]
        public async Task<IActionResult> 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<IActionResult> 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<MovimientoBobinasDto>), StatusCodes.Status200OK)]
        [ProducesResponseType(StatusCodes.Status400BadRequest)]
        [ProducesResponseType(StatusCodes.Status403Forbidden)]
        public async Task<IActionResult> 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<IActionResult> 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<IActionResult> 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<MovimientoBobinaEstadoDetalleDto>(),
                Totales = totales ?? Enumerable.Empty<MovimientoBobinaEstadoTotalDto>()
            };

            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<IActionResult> 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<MovimientoBobinaEstadoDetalleDto>(),
                    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<IActionResult> 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<ListadoDistribucionGeneralResumenDto>(),
                PromediosPorDia = promedios ?? Enumerable.Empty<ListadoDistribucionGeneralPromedioDiaDto>()
            };

            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<IActionResult> 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<ListadoDistribucionGeneralResumenDto>(),
                    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<IActionResult> 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<ListadoDistribucionCanillasSimpleDto>(),
                PromediosPorDia = promedios ?? Enumerable.Empty<ListadoDistribucionCanillasPromedioDiaDto>()
            };

            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<IActionResult> 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<ListadoDistribucionCanillasSimpleDto>(),
                    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<ListadoDistribucionCanillasImporteDto>), StatusCodes.Status200OK)]
        [ProducesResponseType(StatusCodes.Status400BadRequest)]
        [ProducesResponseType(StatusCodes.Status403Forbidden)]
        [ProducesResponseType(StatusCodes.Status404NotFound)]
        public async Task<IActionResult> 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<IActionResult> 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<VentaMensualSecretariaElDiaDto>), StatusCodes.Status200OK)]
        [ProducesResponseType(StatusCodes.Status400BadRequest)]
        [ProducesResponseType(StatusCodes.Status403Forbidden)]
        [ProducesResponseType(StatusCodes.Status404NotFound)]
        public async Task<IActionResult> GetVentaMensualSecretariaElDia([FromQuery] DateTime fechaDesde, [FromQuery] DateTime fechaHasta)
        {
            if (!TienePermiso(PermisoVerReportesSecretaria)) 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<IActionResult> GetVentaMensualSecretariaElDiaPdf([FromQuery] DateTime fechaDesde, [FromQuery] DateTime fechaHasta)
        {
            if (!TienePermiso(PermisoVerReportesSecretaria)) 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<VentaMensualSecretariaElPlataDto>), StatusCodes.Status200OK)]
        [ProducesResponseType(StatusCodes.Status400BadRequest)]
        [ProducesResponseType(StatusCodes.Status403Forbidden)]
        [ProducesResponseType(StatusCodes.Status404NotFound)]
        public async Task<IActionResult> GetVentaMensualSecretariaElPlata([FromQuery] DateTime fechaDesde, [FromQuery] DateTime fechaHasta)
        {
            if (!TienePermiso(PermisoVerReportesSecretaria)) 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<IActionResult> GetVentaMensualSecretariaElPlataPdf([FromQuery] DateTime fechaDesde, [FromQuery] DateTime fechaHasta)
        {
            if (!TienePermiso(PermisoVerReportesSecretaria)) 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<VentaMensualSecretariaTirDevoDto>), StatusCodes.Status200OK)]
        [ProducesResponseType(StatusCodes.Status400BadRequest)]
        [ProducesResponseType(StatusCodes.Status403Forbidden)]
        [ProducesResponseType(StatusCodes.Status404NotFound)]
        public async Task<IActionResult> GetVentaMensualSecretariaTirDevo([FromQuery] DateTime fechaDesde, [FromQuery] DateTime fechaHasta)
        {
            if (!TienePermiso(PermisoVerReportesSecretaria)) 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<IActionResult> GetVentaMensualSecretariaTirDevoPdf([FromQuery] DateTime fechaDesde, [FromQuery] DateTime fechaHasta)
        {
            if (!TienePermiso(PermisoVerReportesSecretaria)) 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<IActionResult> GetReporteDistribucionCanillasData(
            [FromQuery] DateTime fecha,
            [FromQuery] int idEmpresa,
            [FromQuery] bool? esAccionista
        )
        {
            if (!TienePermiso(PermisoVerComprobanteLiquidacionCanilla)) return Forbid();

            // Pasar el nuevo parámetro al servicio
            var (canillas, canillasAcc, canillasAll, canillasFechaLiq, canillasAccFechaLiq,
                 ctrlDevolucionesRemitos, ctrlDevolucionesParaDistCan, ctrlDevolucionesOtrosDias, error) =
                await _reportesService.ObtenerReporteDistribucionCanillasAsync(fecha, idEmpresa, esAccionista);

            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<DetalleDistribucionCanillaDto>(),
                CanillasAccionistas = canillasAcc ?? Enumerable.Empty<DetalleDistribucionCanillaDto>(),
                CanillasTodos = canillasAll ?? Enumerable.Empty<DetalleDistribucionCanillaAllDto>(),
                CanillasLiquidadasOtraFecha = canillasFechaLiq ?? Enumerable.Empty<DetalleDistribucionCanillaDto>(),
                CanillasAccionistasLiquidadasOtraFecha = canillasAccFechaLiq ?? Enumerable.Empty<DetalleDistribucionCanillaDto>(),
                ControlDevolucionesRemitos = ctrlDevolucionesRemitos ?? Enumerable.Empty<ObtenerCtrlDevolucionesDto>(),
                ControlDevolucionesDetalle = ctrlDevolucionesParaDistCan ?? Enumerable.Empty<ControlDevolucionesReporteDto>(),
                ControlDevolucionesOtrosDias = ctrlDevolucionesOtrosDias ?? Enumerable.Empty<DevueltosOtrosDiasDto>()
            };

            return Ok(response);
        }

        [HttpGet("distribucion-canillas/pdf")]
        [ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)]
        [ProducesResponseType(StatusCodes.Status400BadRequest)]
        [ProducesResponseType(StatusCodes.Status403Forbidden)]
        [ProducesResponseType(StatusCodes.Status404NotFound)]
        public async Task<IActionResult> GetReporteDistribucionCanillasPdf(
            [FromQuery] DateTime fecha,
            [FromQuery] int idEmpresa,
            [FromQuery] bool? esAccionista,
            [FromQuery] bool soloTotales = false
        )
        {
            if (!TienePermiso(PermisoVerComprobanteLiquidacionCanilla)) return Forbid();

            // Pasar el nuevo parámetro al servicio
            var (
                canillas, canillasAcc, canillasAll, canillasFechaLiq, canillasAccFechaLiq,
                remitos, ctrlDevoluciones, _, error
            ) = await _reportesService.ObtenerReporteDistribucionCanillasAsync(fecha, idEmpresa, esAccionista);

            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<IActionResult> GetControlDevolucionesData([FromQuery] DateTime fecha, [FromQuery] int idEmpresa)
        {
            if (!TienePermiso(PermisoVerControlDevoluciones)) return Forbid();

            var (
                _, // canillas
                _, // canillasAcc
                _, // canillasAll
                _, // canillasFechaLiq
                _, // canillasAccFechaLiq
                ctrlDevolucionesRemitosData,
                ctrlDevolucionesParaDistCanData,
                ctrlDevolucionesOtrosDiasData,
                error
            ) = await _reportesService.ObtenerReporteDistribucionCanillasAsync(fecha, idEmpresa, null);

            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<ControlDevolucionesReporteDto>(),
                DevolucionesOtrosDias = ctrlDevolucionesOtrosDiasData ?? Enumerable.Empty<DevueltosOtrosDiasDto>(),
                RemitosIngresados = ctrlDevolucionesRemitosData ?? Enumerable.Empty<ObtenerCtrlDevolucionesDto>()
            };

            return Ok(response);
        }

        [HttpGet("control-devoluciones/pdf")]
        [ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)]
        [ProducesResponseType(StatusCodes.Status400BadRequest)]
        [ProducesResponseType(StatusCodes.Status403Forbidden)]
        [ProducesResponseType(StatusCodes.Status404NotFound)]
        public async Task<IActionResult> 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, null);

            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<ControlDevolucionesReporteDto>(),
                    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<IActionResult> GetReporteCuentasDistribuidoresData(
            [FromQuery] int idDistribuidor,
            [FromQuery] int idEmpresa,
            [FromQuery] DateTime fechaDesde,
            [FromQuery] DateTime fechaHasta)
        {
            if (!TienePermiso(PermisoVerBalanceCuentas)) return Forbid();

            var (entradasSalidas, debitosCreditos, pagos, saldoInicial, error) =
                await _reportesService.ObtenerReporteCuentasDistribuidorAsync(idDistribuidor, idEmpresa, fechaDesde, fechaHasta);

            if (error != null) return BadRequest(new { message = error });
            if (!entradasSalidas.Any() && !debitosCreditos.Any() && !pagos.Any() && saldoInicial == 0m)
            {
                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<BalanceCuentaDistDto>(),
                DebitosCreditos = debitosCreditos ?? Enumerable.Empty<BalanceCuentaDebCredDto>(),
                Pagos = pagos ?? Enumerable.Empty<BalanceCuentaPagosDto>(),
                SaldoInicial = saldoInicial,
                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<IActionResult> GetReporteCuentasDistribuidoresPdf(
            [FromQuery] int idDistribuidor,
            [FromQuery] int idEmpresa,
            [FromQuery] DateTime fechaDesde,
            [FromQuery] DateTime fechaHasta)
        {
            if (!TienePermiso(PermisoVerBalanceCuentas)) return Forbid();

            var (entradasSalidas, debitosCreditos, pagos, saldoInicial, error) =
                await _reportesService.ObtenerReporteCuentasDistribuidorAsync(idDistribuidor, idEmpresa, fechaDesde, fechaHasta);

            if (error != null) return BadRequest(new { message = error });
            if (!entradasSalidas.Any() && !debitosCreditos.Any() && !pagos.Any() && saldoInicial == 0m)
            {
                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,
                    SaldoInicial = saldoInicial,
                    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<TiradasPublicacionesSeccionesDto>), StatusCodes.Status200OK)]
        [ProducesResponseType(StatusCodes.Status400BadRequest)]
        [ProducesResponseType(StatusCodes.Status403Forbidden)]
        [ProducesResponseType(StatusCodes.Status404NotFound)]
        public async Task<IActionResult> GetReporteTiradasPublicacionesSeccionesData(
            [FromQuery] int idPublicacion,
            [FromQuery] DateTime fechaDesde,
            [FromQuery] DateTime fechaHasta,
            [FromQuery] int? idPlanta,
            [FromQuery] bool consolidado = false)
        {
            if (!TienePermiso(PermisoVerReporteTiradas)) return Forbid();

            IEnumerable<TiradasPublicacionesSeccionesDto> 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<IActionResult> GetReporteTiradasPublicacionesSeccionesPdf(
            [FromQuery] int idPublicacion,
            [FromQuery] DateTime fechaDesde,
            [FromQuery] DateTime fechaHasta,
            [FromQuery] int? idPlanta,
            [FromQuery] bool consolidado = false)
        {
            if (!TienePermiso(PermisoVerReporteTiradas)) return Forbid();

            IEnumerable<TiradasPublicacionesSeccionesDto> 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<ConsumoBobinasSeccionDto>), StatusCodes.Status200OK)]
        [ProducesResponseType(StatusCodes.Status400BadRequest)]
        [ProducesResponseType(StatusCodes.Status403Forbidden)]
        [ProducesResponseType(StatusCodes.Status404NotFound)]
        public async Task<IActionResult> GetReporteConsumoBobinasSeccionData(
            [FromQuery] DateTime fechaDesde,
            [FromQuery] DateTime fechaHasta,
            [FromQuery] int? idPlanta,
            [FromQuery] bool consolidado = false)
        {
            if (!TienePermiso(PermisoVerReporteConsumoBobinas)) return Forbid();

            IEnumerable<ConsumoBobinasSeccionDto> 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<IActionResult> GetReporteConsumoBobinasSeccionPdf(
            [FromQuery] DateTime fechaDesde,
            [FromQuery] DateTime fechaHasta,
            [FromQuery] int? idPlanta,
            [FromQuery] bool consolidado = false)
        {
            if (!TienePermiso(PermisoVerReporteConsumoBobinas)) return Forbid();

            IEnumerable<ConsumoBobinasSeccionDto> 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<ConsumoBobinasPublicacionDto>), StatusCodes.Status200OK)]
        [ProducesResponseType(StatusCodes.Status400BadRequest)]
        [ProducesResponseType(StatusCodes.Status403Forbidden)]
        [ProducesResponseType(StatusCodes.Status404NotFound)]
        public async Task<IActionResult> 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<IActionResult> 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<ComparativaConsumoBobinasDto>), StatusCodes.Status200OK)]
        [ProducesResponseType(StatusCodes.Status400BadRequest)]
        [ProducesResponseType(StatusCodes.Status403Forbidden)]
        [ProducesResponseType(StatusCodes.Status404NotFound)]
        public async Task<IActionResult> 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<ComparativaConsumoBobinasDto> 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<IActionResult> 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<ComparativaConsumoBobinasDto> 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<IActionResult> GetListadoDistribucionDistribuidores(
            [FromQuery] int idDistribuidor,
            [FromQuery] int idPublicacion,
            [FromQuery] DateTime fechaDesde,
            [FromQuery] DateTime fechaHasta)
        {
            if (!TienePermiso(PermisoVerListadoDistribucion)) return Forbid(); // RR002

            (IEnumerable<ListadoDistribucionDistSimpleDto> simple, IEnumerable<ListadoDistribucionDistPromedioDiaDto> 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<ListadoDistribucionDistSimpleDto>(),
                PromediosPorDia = result.promedios ?? Enumerable.Empty<ListadoDistribucionDistPromedioDiaDto>()
            };

            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<IActionResult> 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<ListadoDistribucionDistSimpleDto>(),
                    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<IActionResult> 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<NovedadesCanillasReporteDto>), StatusCodes.Status200OK)]
        [ProducesResponseType(StatusCodes.Status400BadRequest)]
        [ProducesResponseType(StatusCodes.Status403Forbidden)]
        [ProducesResponseType(StatusCodes.Status404NotFound)] // Si no hay datos
        [ProducesResponseType(StatusCodes.Status500InternalServerError)]
        public async Task<IActionResult> 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<NovedadesCanillasReporteDto>());
                }
                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<IActionResult> 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<NovedadesCanillasReporteDto>(),
                    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<CanillaGananciaReporteDto>), StatusCodes.Status200OK)]
        [ProducesResponseType(StatusCodes.Status400BadRequest)]
        [ProducesResponseType(StatusCodes.Status403Forbidden)]
        [ProducesResponseType(StatusCodes.Status404NotFound)]
        public async Task<IActionResult> 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<CanillaGananciaReporteDto>());
                }
                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<ListadoDistCanMensualDiariosDto>), StatusCodes.Status200OK)]
        [ProducesResponseType(StatusCodes.Status400BadRequest)]
        [ProducesResponseType(StatusCodes.Status403Forbidden)]
        public async Task<IActionResult> 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<ListadoDistCanMensualDiariosDto>());
        }

        [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<IActionResult> 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<ListadoDistCanMensualPubDto>), StatusCodes.Status200OK)]
        [ProducesResponseType(StatusCodes.Status400BadRequest)]
        [ProducesResponseType(StatusCodes.Status403Forbidden)]
        public async Task<IActionResult> 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<ListadoDistCanMensualPubDto>());
        }

        [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<IActionResult> 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<IActionResult> 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<IActionResult> 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<DistribucionSuscripcionDto>(), bajas ?? Enumerable.Empty<DistribucionSuscripcionDto>())
                {
                    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.");
            }
        }
    }
}