2025-05-27 11:21:00 -03:00
using GestionIntegral.Api.Services.Reportes ;
using Microsoft.AspNetCore.Authorization ;
using Microsoft.AspNetCore.Mvc ;
using GestionIntegral.Api.Dtos.Reportes ;
2025-05-27 18:17:56 -03:00
using GestionIntegral.Api.Data.Repositories.Impresion ;
using GestionIntegral.Api.Data.Repositories.Distribucion ;
2025-06-06 18:33:09 -03:00
using GestionIntegral.Api.Services.Distribucion ;
2025-06-19 14:47:43 -03:00
using GestionIntegral.Api.Services.Pdf ;
using GestionIntegral.Api.Dtos.Reportes.ViewModels ;
2025-06-20 19:04:23 -03:00
using GestionIntegral.Api.Controllers.Reportes.PdfTemplates ;
using QuestPDF.Infrastructure ;
2025-06-24 12:52:37 -03:00
using System.Globalization ;
2025-05-27 11:21:00 -03:00
namespace GestionIntegral.Api.Controllers
{
[Route("api/[controller] ")]
[ApiController]
[Authorize]
public class ReportesController : ControllerBase
{
2025-05-28 16:01:59 -03:00
private readonly IReportesService _reportesService ;
2025-05-27 11:21:00 -03:00
private readonly ILogger < ReportesController > _logger ;
private readonly IPlantaRepository _plantaRepository ;
2025-05-27 18:17:56 -03:00
private readonly IPublicacionRepository _publicacionRepository ;
private readonly IEmpresaRepository _empresaRepository ;
private readonly IDistribuidorRepository _distribuidorRepository ; // Para obtener el nombre del distribuidor
2025-06-06 18:33:09 -03:00
private readonly INovedadCanillaService _novedadCanillaService ;
2025-06-20 19:04:23 -03:00
private readonly IQuestPdfGenerator _pdfGenerator ;
2025-05-27 18:17:56 -03:00
// Permisos
2025-05-27 11:21:00 -03:00
private const string PermisoVerReporteExistenciaPapel = "RR005" ;
2025-05-27 18:17:56 -03:00
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" ;
2025-06-06 18:33:09 -03:00
private const string PermisoVerReporteNovedadesCanillas = "RR004" ;
private const string PermisoVerReporteListadoDistMensual = "RR009" ;
2025-08-08 09:48:15 -03:00
private const string PermisoVerReporteFacturasPublicidad = "RR010" ;
Feat: Implementa Reporte de Distribución de Suscripciones y Refactoriza Gestión de Ajustes
Se introduce una nueva funcionalidad de reporte crucial para la logística y se realiza una refactorización mayor del sistema de ajustes para garantizar la correcta imputación contable.
### ✨ Nuevas Características
- **Nuevo Reporte de Distribución de Suscripciones (RR011):**
- Se añade un nuevo reporte en PDF que genera un listado de todas las suscripciones activas en un rango de fechas.
- El reporte está diseñado para el equipo de reparto, incluyendo datos clave como nombre del suscriptor, dirección, teléfono, días de entrega y observaciones.
- Se implementa el endpoint, la lógica de servicio, la consulta a la base de datos y el template de QuestPDF en el backend.
- Se crea la página correspondiente en el frontend (React) con su selector de fechas y se añade la ruta y el enlace en el menú de reportes.
### 🔄 Refactorización Mayor
- **Asociación de Ajustes a Empresas:**
- Se refactoriza por completo la entidad `Ajuste` para incluir una referencia obligatoria a una `IdEmpresa`.
- **Motivo:** Corregir un error crítico en la lógica de negocio donde los ajustes de un suscriptor se aplicaban a la primera factura generada, sin importar a qué empresa correspondía el ajuste.
- Se provee un script de migración SQL para actualizar el esquema de la base de datos (`susc_Ajustes`).
- Se actualizan todos los DTOs, repositorios y servicios (backend) para manejar la nueva relación.
- Se modifica el `FacturacionService` para que ahora aplique los ajustes pendientes correspondientes a cada empresa dentro de su bucle de facturación.
- Se actualiza el formulario de creación/edición de ajustes en el frontend (React) para incluir un selector de empresa obligatorio.
### ⚡️ Optimizaciones de Rendimiento
- **Solución de N+1 Queries:**
- Se optimiza el método `ObtenerTodos` en `SuscriptorService` para obtener todas las formas de pago en una única consulta en lugar de una por cada suscriptor.
- Se optimiza el método `ObtenerAjustesPorSuscriptor` en `AjusteService` para obtener todos los nombres de usuarios y empresas en dos consultas masivas, en lugar de N consultas individuales.
- Se añade el método `GetByIdsAsync` al `IUsuarioRepository` y su implementación para soportar esta optimización.
### 🐛 Corrección de Errores
- Se corrigen múltiples errores en el script de migración de base de datos (uso de `GO` dentro de transacciones, error de "columna inválida").
- Se soluciona un error de SQL (`INSERT` statement) en `AjusteRepository` que impedía la creación de nuevos ajustes.
- Se corrige un bug en el `AjusteService` que causaba que el nombre de la empresa apareciera como "N/A" en la UI debido a un mapeo incompleto en el método optimizado.
- Se corrige la lógica de generación de emails en `FacturacionService` para mostrar correctamente el nombre de la empresa en cada sección del resumen de cuenta.
2025-08-09 17:39:21 -03:00
private const string PermisoVerReporteDistSuscripciones = "RR011" ;
2025-05-27 11:21:00 -03:00
2025-05-27 18:17:56 -03:00
public ReportesController (
2025-06-06 18:33:09 -03:00
IReportesService reportesService ,
INovedadCanillaService novedadCanillaService ,
2025-05-27 18:17:56 -03:00
ILogger < ReportesController > logger ,
IPlantaRepository plantaRepository ,
IPublicacionRepository publicacionRepository ,
IEmpresaRepository empresaRepository ,
2025-06-19 14:47:43 -03:00
IDistribuidorRepository distribuidorRepository ,
2025-06-20 19:04:23 -03:00
IQuestPdfGenerator pdfGenerator )
2025-05-27 11:21:00 -03:00
{
2025-06-06 18:33:09 -03:00
_reportesService = reportesService ;
_novedadCanillaService = novedadCanillaService ;
2025-05-27 11:21:00 -03:00
_logger = logger ;
_plantaRepository = plantaRepository ;
2025-05-27 18:17:56 -03:00
_publicacionRepository = publicacionRepository ;
_empresaRepository = empresaRepository ;
2025-06-06 18:33:09 -03:00
_distribuidorRepository = distribuidorRepository ;
2025-06-20 19:04:23 -03:00
_pdfGenerator = pdfGenerator ;
2025-05-27 11:21:00 -03:00
}
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 ,
2025-05-27 18:17:56 -03:00
[FromQuery] int? idPlanta ,
[FromQuery] bool consolidado = false )
2025-05-27 11:21:00 -03:00
{
if ( ! TienePermiso ( PermisoVerReporteExistenciaPapel ) )
{
_logger . LogWarning ( "Acceso denegado a GetReporteExistenciaPapel. Usuario: {User}" , User . Identity ? . Name ? ? "Desconocido" ) ;
return Forbid ( ) ;
}
2025-05-27 18:17:56 -03:00
var ( data , error ) = await _reportesService . ObtenerExistenciaPapelAsync ( fechaDesde , fechaHasta , idPlanta , consolidado ) ; // <--- CORREGIDO
2025-05-27 11:21:00 -03:00
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 (
2025-06-24 12:52:37 -03:00
[FromQuery] DateTime fechaDesde ,
[FromQuery] DateTime fechaHasta ,
[FromQuery] int? idPlanta ,
[FromQuery] bool consolidado = false )
2025-05-27 11:21:00 -03:00
{
if ( ! TienePermiso ( PermisoVerReporteExistenciaPapel ) ) return Forbid ( ) ;
2025-06-19 14:47:43 -03:00
var ( data , error ) = await _reportesService . ObtenerExistenciaPapelAsync ( fechaDesde , fechaHasta , idPlanta , consolidado ) ;
2025-05-27 11:21:00 -03:00
2025-06-20 19:04:23 -03:00
if ( error ! = null ) return BadRequest ( new { message = error } ) ;
if ( data = = null | | ! data . Any ( ) ) return NotFound ( new { message = "No hay datos para generar el PDF." } ) ;
2025-05-27 11:21:00 -03:00
try
{
2025-06-20 19:04:23 -03:00
IDocument document ; // Se declara aquí
2025-06-19 14:47:43 -03:00
if ( consolidado )
2025-05-31 23:48:42 -03:00
{
2025-06-20 19:04:23 -03:00
var viewModel = new ExistenciaPapelConsolidadoViewModel
{
Existencias = data ,
FechaDesde = fechaDesde . ToString ( "dd/MM/yyyy" ) ,
FechaHasta = fechaHasta . ToString ( "dd/MM/yyyy" )
} ;
document = new ExistenciaPapelConsolidadoDocument ( viewModel ) ;
2025-05-31 23:48:42 -03:00
}
else
{
2025-06-20 19:04:23 -03:00
if ( ! idPlanta . HasValue )
2025-06-19 14:47:43 -03:00
{
2025-06-20 19:04:23 -03:00
return BadRequest ( new { message = "El idPlanta es requerido para reportes no consolidados." } ) ;
2025-06-19 14:47:43 -03:00
}
2025-05-27 11:21:00 -03:00
2025-06-20 19:04:23 -03:00
var planta = await _plantaRepository . GetByIdAsync ( idPlanta . Value ) ;
var viewModel = new ExistenciaPapelViewModel
2025-06-19 14:47:43 -03:00
{
2025-06-20 19:04:23 -03:00
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 ) ;
2025-05-31 23:48:42 -03:00
2025-05-27 11:21:00 -03:00
string fileName = $"ExistenciaPapel_{fechaDesde:yyyyMMdd}_{fechaHasta:yyyyMMdd}_{(consolidado ? " Consolidado " : $" Planta { idPlanta } ")}.pdf" ;
return File ( pdfBytes , "application/pdf" , fileName ) ;
}
catch ( Exception ex )
{
2025-06-20 19:04:23 -03:00
_logger . LogError ( ex , "Error al generar PDF con QuestPDF para Existencia de Papel." ) ;
2025-05-27 11:21:00 -03:00
return StatusCode ( StatusCodes . Status500InternalServerError , "Error interno al generar el PDF del reporte." ) ;
}
}
2025-05-27 18:17:56 -03:00
[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 } ) ;
2025-05-31 23:48:42 -03:00
2025-05-27 18:17:56 -03:00
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 ( ) ;
2025-05-31 23:48:42 -03:00
2025-06-24 12:52:37 -03:00
var ( data , error ) = await _reportesService . ObtenerMovimientoBobinasAsync ( fechaDesde , fechaHasta , idPlanta ) ;
2025-05-27 18:17:56 -03:00
if ( error ! = null ) return BadRequest ( new { message = error } ) ;
if ( data = = null | | ! data . Any ( ) )
{
2025-06-24 12:52:37 -03:00
return NotFound ( new { message = "No hay datos para generar el PDF del movimiento de bobinas." } ) ;
2025-05-27 18:17:56 -03:00
}
try
{
2025-06-24 12:52:37 -03:00
var planta = await _plantaRepository . GetByIdAsync ( idPlanta ) ;
var viewModel = new MovimientoBobinasViewModel
2025-05-27 18:17:56 -03:00
{
2025-06-24 12:52:37 -03:00
Movimientos = data ,
NombrePlanta = planta ? . Nombre ? ? $"Planta ID {idPlanta}" ,
FechaDesde = fechaDesde . ToString ( "dd/MM/yyyy" ) ,
FechaHasta = fechaHasta . ToString ( "dd/MM/yyyy" )
} ;
2025-05-27 18:17:56 -03:00
2025-06-24 12:52:37 -03:00
var document = new MovimientoBobinasDocument ( viewModel ) ;
2025-05-31 23:48:42 -03:00
2025-06-24 12:52:37 -03:00
byte [ ] pdfBytes = await _pdfGenerator . GeneratePdfAsync ( document ) ;
2025-05-27 18:17:56 -03:00
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." ) ;
}
}
2025-05-28 16:01:59 -03:00
// 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 } ) ;
2025-05-31 23:48:42 -03:00
2025-05-28 16:01:59 -03:00
if ( ( detalle = = null | | ! detalle . Any ( ) ) & & ( totales = = null | | ! totales . Any ( ) ) )
{
2025-05-31 23:48:42 -03:00
return NotFound ( new { message = "No hay datos para el reporte de movimiento de bobinas por estado." } ) ;
2025-05-28 16:01:59 -03:00
}
var response = new MovimientoBobinasPorEstadoResponseDto
{
Detalle = detalle ? ? Enumerable . Empty < MovimientoBobinaEstadoDetalleDto > ( ) ,
Totales = totales ? ? Enumerable . Empty < MovimientoBobinaEstadoTotalDto > ( )
} ;
2025-05-31 23:48:42 -03:00
2025-05-28 16:01:59 -03:00
return Ok ( response ) ;
}
2025-05-27 18:17:56 -03:00
[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 ( ) ;
2025-06-24 12:52:37 -03:00
var ( detalle , totales , error ) = await _reportesService . ObtenerMovimientoBobinasPorEstadoAsync ( fechaDesde , fechaHasta , idPlanta ) ;
2025-05-27 18:17:56 -03:00
if ( error ! = null ) return BadRequest ( new { message = error } ) ;
if ( ( detalle = = null | | ! detalle . Any ( ) ) & & ( totales = = null | | ! totales . Any ( ) ) )
{
2025-06-24 12:52:37 -03:00
return NotFound ( new { message = "No hay datos para generar el PDF." } ) ;
2025-05-27 18:17:56 -03:00
}
2025-05-31 23:48:42 -03:00
2025-05-27 18:17:56 -03:00
try
{
2025-06-24 12:52:37 -03:00
var planta = await _plantaRepository . GetByIdAsync ( idPlanta ) ;
var viewModel = new MovimientoBobinasEstadoViewModel
2025-05-27 18:17:56 -03:00
{
2025-06-24 12:52:37 -03:00
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" )
} ;
2025-05-27 18:17:56 -03:00
2025-06-24 12:52:37 -03:00
var document = new MovimientoBobinasEstadoDocument ( viewModel ) ;
2025-05-31 23:48:42 -03:00
2025-06-24 12:52:37 -03:00
byte [ ] pdfBytes = await _pdfGenerator . GeneratePdfAsync ( document ) ;
2025-05-27 18:17:56 -03:00
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." ) ;
}
}
2025-05-28 16:01:59 -03:00
// 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 ,
2025-05-31 23:48:42 -03:00
[FromQuery] DateTime fechaDesde ,
2025-05-28 16:01:59 -03:00
[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 ) ;
}
2025-05-27 18:17:56 -03:00
[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 ,
2025-06-24 12:52:37 -03:00
[FromQuery] DateTime fechaDesde ,
[FromQuery] DateTime fechaHasta )
2025-05-27 18:17:56 -03:00
{
if ( ! TienePermiso ( PermisoVerListadoDistribucion ) ) return Forbid ( ) ;
2025-06-24 12:52:37 -03:00
// El servicio ya devuelve la tupla con los dos conjuntos de datos.
2025-05-27 18:17:56 -03:00
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 ) ;
2025-06-24 12:52:37 -03:00
var viewModel = new ListadoDistribucionGeneralViewModel
2025-05-27 18:17:56 -03:00
{
2025-06-24 12:52:37 -03:00
ResumenMensual = resumen ? ? Enumerable . Empty < ListadoDistribucionGeneralResumenDto > ( ) ,
PromediosPorDia = promedios ,
NombrePublicacion = publicacion ? . Nombre ? ? "N/A" ,
MesConsultado = fechaDesde . ToString ( "MMMM 'de' yyyy" , new CultureInfo ( "es-ES" ) )
2025-05-27 18:17:56 -03:00
} ;
2025-06-24 12:52:37 -03:00
var document = new ListadoDistribucionGeneralDocument ( viewModel ) ;
byte [ ] pdfBytes = await _pdfGenerator . GeneratePdfAsync ( document ) ;
2025-05-27 18:17:56 -03:00
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." ) ;
}
}
2025-05-28 16:01:59 -03:00
// 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." } ) ;
}
2025-05-31 23:48:42 -03:00
2025-05-28 16:01:59 -03:00
var response = new ListadoDistribucionCanillasResponseDto
{
DetalleSimple = simple ? ? Enumerable . Empty < ListadoDistribucionCanillasSimpleDto > ( ) ,
PromediosPorDia = promedios ? ? Enumerable . Empty < ListadoDistribucionCanillasPromedioDiaDto > ( )
} ;
return Ok ( response ) ;
}
2025-05-27 18:17:56 -03:00
[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 ) ;
2025-06-24 12:52:37 -03:00
var viewModel = new ListadoDistribucionCanillasViewModel
2025-05-27 18:17:56 -03:00
{
2025-06-24 12:52:37 -03:00
DetalleDiario = simple ? ? Enumerable . Empty < ListadoDistribucionCanillasSimpleDto > ( ) ,
PromediosPorDia = promedios ,
NombrePublicacion = publicacion ? . Nombre ? ? "N/A" ,
FechaDesde = fechaDesde . ToString ( "dd/MM/yyyy" ) ,
FechaHasta = fechaHasta . ToString ( "dd/MM/yyyy" )
2025-05-27 18:17:56 -03:00
} ;
2025-06-24 12:52:37 -03:00
var document = new ListadoDistribucionCanillasDocument ( viewModel ) ;
byte [ ] pdfBytes = await _pdfGenerator . GeneratePdfAsync ( document ) ;
2025-05-27 18:17:56 -03:00
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." ) ;
}
}
2025-05-28 16:01:59 -03:00
// 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." } ) ;
}
2025-05-31 23:48:42 -03:00
2025-05-28 16:01:59 -03:00
return Ok ( data ) ;
}
2025-05-31 23:48:42 -03:00
2025-05-27 18:17:56 -03:00
[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 ,
2025-05-31 23:48:42 -03:00
[FromQuery] bool esAccionista )
2025-05-27 18:17:56 -03:00
{
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 ) ;
2025-06-24 12:52:37 -03:00
var viewModel = new ListadoDistCanillasImporteViewModel
2025-05-27 18:17:56 -03:00
{
2025-06-24 12:52:37 -03:00
Detalles = data ,
NombrePublicacion = publicacion ? . Nombre ? ? "N/A" ,
TipoDestinatario = esAccionista ? "Accionistas" : "Canillitas" ,
FechaDesde = fechaDesde . ToString ( "dd/MM/yyyy" ) ,
FechaHasta = fechaHasta . ToString ( "dd/MM/yyyy" )
2025-05-27 18:17:56 -03:00
} ;
2025-05-31 23:48:42 -03:00
2025-06-24 12:52:37 -03:00
var document = new ListadoDistCanillasImporteDocument ( viewModel ) ;
byte [ ] pdfBytes = await _pdfGenerator . GeneratePdfAsync ( document ) ;
2025-05-27 18:17:56 -03:00
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." ) ;
}
}
2025-05-28 16:01:59 -03:00
// 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 ( 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 ) ;
}
2025-05-27 18:17:56 -03:00
[HttpGet("venta-mensual-secretaria/el-dia/pdf")]
2025-06-24 12:52:37 -03:00
[ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
2025-05-27 18:17:56 -03:00
public async Task < IActionResult > GetVentaMensualSecretariaElDiaPdf ( [ FromQuery ] DateTime fechaDesde , [ FromQuery ] DateTime fechaHasta )
{
2025-06-24 12:52:37 -03:00
if ( ! TienePermiso ( PermisoVerListadoDistribucion ) ) return Forbid ( ) ;
2025-05-27 18:17:56 -03:00
var ( data , error ) = await _reportesService . ObtenerVentaMensualSecretariaElDiaAsync ( fechaDesde , fechaHasta ) ;
2025-06-24 12:52:37 -03:00
2025-05-27 18:17:56 -03:00
if ( error ! = null ) return BadRequest ( new { message = error } ) ;
if ( data = = null | | ! data . Any ( ) ) return NotFound ( new { message = "No hay datos para el reporte." } ) ;
try
{
2025-06-24 12:52:37 -03:00
var viewModel = new VentaMensualSecretariaElDiaViewModel
2025-05-27 18:17:56 -03:00
{
2025-06-24 12:52:37 -03:00
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." ) ;
}
2025-05-27 18:17:56 -03:00
}
2025-05-28 16:01:59 -03:00
// 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 ( 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 ) ;
}
2025-05-27 18:17:56 -03:00
[HttpGet("venta-mensual-secretaria/el-plata/pdf")]
2025-06-24 12:52:37 -03:00
[ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
2025-05-27 18:17:56 -03:00
public async Task < IActionResult > GetVentaMensualSecretariaElPlataPdf ( [ FromQuery ] DateTime fechaDesde , [ FromQuery ] DateTime fechaHasta )
{
2025-06-24 12:52:37 -03:00
if ( ! TienePermiso ( PermisoVerListadoDistribucion ) ) return Forbid ( ) ;
2025-05-27 18:17:56 -03:00
var ( data , error ) = await _reportesService . ObtenerVentaMensualSecretariaElPlataAsync ( fechaDesde , fechaHasta ) ;
2025-06-24 12:52:37 -03:00
2025-05-27 18:17:56 -03:00
if ( error ! = null ) return BadRequest ( new { message = error } ) ;
if ( data = = null | | ! data . Any ( ) ) return NotFound ( new { message = "No hay datos para el reporte." } ) ;
2025-05-31 23:48:42 -03:00
2025-05-27 18:17:56 -03:00
try
{
2025-06-24 12:52:37 -03:00
var viewModel = new VentaMensualSecretariaElPlataViewModel
2025-05-27 18:17:56 -03:00
{
2025-06-24 12:52:37 -03:00
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." ) ;
}
2025-05-27 18:17:56 -03:00
}
2025-05-28 16:01:59 -03:00
// 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 ( 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 ) ;
}
2025-05-31 23:48:42 -03:00
2025-05-27 18:17:56 -03:00
[HttpGet("venta-mensual-secretaria/tirada-devolucion/pdf")]
2025-06-24 12:52:37 -03:00
[ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
2025-05-27 18:17:56 -03:00
public async Task < IActionResult > GetVentaMensualSecretariaTirDevoPdf ( [ FromQuery ] DateTime fechaDesde , [ FromQuery ] DateTime fechaHasta )
{
2025-06-24 12:52:37 -03:00
if ( ! TienePermiso ( PermisoVerListadoDistribucion ) ) return Forbid ( ) ;
2025-05-27 18:17:56 -03:00
var ( data , error ) = await _reportesService . ObtenerVentaMensualSecretariaTirDevoAsync ( fechaDesde , fechaHasta ) ;
2025-06-24 12:52:37 -03:00
2025-05-27 18:17:56 -03:00
if ( error ! = null ) return BadRequest ( new { message = error } ) ;
if ( data = = null | | ! data . Any ( ) ) return NotFound ( new { message = "No hay datos para el reporte." } ) ;
2025-05-31 23:48:42 -03:00
2025-05-27 18:17:56 -03:00
try
{
2025-06-24 12:52:37 -03:00
var viewModel = new VentaMensualSecretariaTirDevoViewModel
2025-05-27 18:17:56 -03:00
{
2025-06-24 12:52:37 -03:00
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." ) ;
}
2025-05-27 18:17:56 -03:00
}
2025-05-28 16:01:59 -03:00
// 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 )
{
if ( ! TienePermiso ( PermisoVerComprobanteLiquidacionCanilla ) ) return Forbid ( ) ;
2025-05-31 23:48:42 -03:00
var ( canillas , canillasAcc , canillasAll , canillasFechaLiq , canillasAccFechaLiq ,
ctrlDevolucionesRemitos , ctrlDevolucionesParaDistCan , ctrlDevolucionesOtrosDias , error ) =
2025-05-28 16:01:59 -03:00
await _reportesService . ObtenerReporteDistribucionCanillasAsync ( fecha , idEmpresa ) ;
if ( error ! = null ) return BadRequest ( new { message = error } ) ;
2025-05-31 23:48:42 -03:00
2025-05-28 16:01:59 -03:00
// 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 > ( )
} ;
2025-05-31 23:48:42 -03:00
2025-05-28 16:01:59 -03:00
return Ok ( response ) ;
}
2025-05-31 23:48:42 -03:00
2025-05-27 18:17:56 -03:00
[HttpGet("distribucion-canillas/pdf")]
2025-06-24 12:52:37 -03:00
[ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
2025-05-27 18:17:56 -03:00
public async Task < IActionResult > GetReporteDistribucionCanillasPdf ( [ FromQuery ] DateTime fecha , [ FromQuery ] int idEmpresa , [ FromQuery ] bool soloTotales = false )
{
if ( ! TienePermiso ( PermisoVerComprobanteLiquidacionCanilla ) ) return Forbid ( ) ;
2025-05-28 16:01:59 -03:00
var (
2025-06-24 12:52:37 -03:00
canillas , canillasAcc , canillasAll , canillasFechaLiq , canillasAccFechaLiq ,
remitos , ctrlDevoluciones , _ , error
2025-05-28 16:01:59 -03:00
) = await _reportesService . ObtenerReporteDistribucionCanillasAsync ( fecha , idEmpresa ) ;
2025-05-27 18:17:56 -03:00
if ( error ! = null ) return BadRequest ( new { message = error } ) ;
2025-05-31 23:48:42 -03:00
2025-06-24 12:52:37 -03:00
// Verificamos si hay datos suficientes para CUALQUIERA de los dos reportes.
if ( ! canillas . Any ( ) & & ! canillasAcc . Any ( ) & & ! canillasAll . Any ( ) )
2025-05-27 18:17:56 -03:00
{
2025-06-24 12:52:37 -03:00
return NotFound ( new { message = "No hay datos de distribución para generar el PDF." } ) ;
2025-05-27 18:17:56 -03:00
}
try
{
2025-06-24 12:52:37 -03:00
var empresa = await _empresaRepository . GetByIdAsync ( idEmpresa ) ;
2025-05-31 23:48:42 -03:00
2025-06-24 12:52:37 -03:00
var viewModel = new DistribucionCanillasViewModel
2025-05-27 18:17:56 -03:00
{
2025-06-24 12:52:37 -03:00
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" )
} ;
2025-05-31 23:48:42 -03:00
2025-06-24 12:52:37 -03:00
IDocument document ;
string tipoReporte ;
2025-05-27 18:17:56 -03:00
2025-06-24 12:52:37 -03:00
if ( soloTotales )
2025-05-27 18:17:56 -03:00
{
2025-06-24 12:52:37 -03:00
document = new DistribucionCanillasTotalesDocument ( viewModel ) ;
tipoReporte = "Totales" ;
}
else
{
document = new DistribucionCanillasDocument ( viewModel ) ;
tipoReporte = "Detalle" ;
}
byte [ ] pdfBytes = await _pdfGenerator . GeneratePdfAsync ( document ) ;
2025-05-27 18:17:56 -03:00
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." ) ;
}
}
2025-05-28 16:01:59 -03:00
// 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 , // 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 } ) ;
2025-05-31 23:48:42 -03:00
2025-05-28 16:01:59 -03:00
// 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 > ( )
} ;
2025-05-31 23:48:42 -03:00
2025-05-28 16:01:59 -03:00
return Ok ( response ) ;
}
2025-05-27 18:17:56 -03:00
[HttpGet("control-devoluciones/pdf")]
2025-06-24 12:52:37 -03:00
[ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
2025-05-27 18:17:56 -03:00
public async Task < IActionResult > GetReporteControlDevolucionesPdf ( [ FromQuery ] DateTime fecha , [ FromQuery ] int idEmpresa )
{
2025-05-31 23:48:42 -03:00
if ( ! TienePermiso ( PermisoVerControlDevoluciones ) ) return Forbid ( ) ;
2025-05-27 18:17:56 -03:00
var (
2025-06-24 12:52:37 -03:00
_ , _ , _ , _ , _ , // Datos no utilizados
remitos , detalles , otrosDias , error
2025-05-27 18:17:56 -03:00
) = await _reportesService . ObtenerReporteDistribucionCanillasAsync ( fecha , idEmpresa ) ;
if ( error ! = null ) return BadRequest ( new { message = error } ) ;
2025-05-31 23:48:42 -03:00
2025-06-24 12:52:37 -03:00
if ( ( detalles = = null | | ! detalles . Any ( ) ) & & ( otrosDias = = null | | ! otrosDias . Any ( ) ) )
2025-05-27 18:17:56 -03:00
{
return NotFound ( new { message = "No hay datos para generar el PDF para control de devoluciones." } ) ;
}
2025-05-31 23:48:42 -03:00
2025-05-27 18:17:56 -03:00
try
{
var empresa = await _empresaRepository . GetByIdAsync ( idEmpresa ) ;
2025-06-24 12:52:37 -03:00
// La creación del ViewModel es ahora mucho más limpia
var viewModel = new ControlDevolucionesViewModel
2025-05-27 18:17:56 -03:00
{
2025-06-24 12:52:37 -03:00
Detalles = detalles ? ? Enumerable . Empty < ControlDevolucionesReporteDto > ( ) ,
TotalDevolucionDiasAnteriores = otrosDias ? . Sum ( d = > d . Devueltos ) ? ? 0 ,
NombreEmpresa = empresa ? . Nombre ? ? "N/A" ,
FechaConsultada = fecha . ToString ( "dd/MM/yyyy" )
2025-05-27 18:17:56 -03:00
} ;
2025-06-24 12:52:37 -03:00
var document = new ControlDevolucionesDocument ( viewModel ) ;
byte [ ] pdfBytes = await _pdfGenerator . GeneratePdfAsync ( document ) ;
2025-05-27 18:17:56 -03:00
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." ) ;
}
}
2025-05-28 16:01:59 -03:00
// 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 ( ) ;
2025-05-31 23:48:42 -03:00
var ( entradasSalidas , debitosCreditos , pagos , saldos , error ) =
2025-05-28 16:01:59 -03:00
await _reportesService . ObtenerReporteCuentasDistribuidorAsync ( idDistribuidor , idEmpresa , fechaDesde , fechaHasta ) ;
if ( error ! = null ) return BadRequest ( new { message = error } ) ;
if ( ! entradasSalidas . Any ( ) & & ! debitosCreditos . Any ( ) & & ! pagos . Any ( ) & & ! saldos . Any ( ) )
{
2025-05-31 23:48:42 -03:00
return NotFound ( new { message = "No hay datos para generar el reporte de cuenta del distribuidor." } ) ;
2025-05-28 16:01:59 -03:00
}
2025-05-31 23:48:42 -03:00
2025-05-28 16:01:59 -03:00
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 > ( ) ,
Saldos = saldos ? ? Enumerable . Empty < SaldoDto > ( ) ,
NombreDistribuidor = distribuidor . Distribuidor ? . Nombre ,
NombreEmpresa = empresa ? . Nombre
} ;
2025-05-31 23:48:42 -03:00
2025-05-28 16:01:59 -03:00
return Ok ( response ) ;
}
2025-05-27 18:17:56 -03:00
[HttpGet("cuentas-distribuidores/pdf")]
2025-06-24 12:52:37 -03:00
[ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
2025-05-27 18:17:56 -03:00
public async Task < IActionResult > GetReporteCuentasDistribuidoresPdf (
[FromQuery] int idDistribuidor ,
[FromQuery] int idEmpresa ,
[FromQuery] DateTime fechaDesde ,
[FromQuery] DateTime fechaHasta )
{
if ( ! TienePermiso ( PermisoVerBalanceCuentas ) ) return Forbid ( ) ;
2025-05-31 23:48:42 -03:00
var ( entradasSalidas , debitosCreditos , pagos , saldos , error ) =
2025-05-27 18:17:56 -03:00
await _reportesService . ObtenerReporteCuentasDistribuidorAsync ( idDistribuidor , idEmpresa , fechaDesde , fechaHasta ) ;
if ( error ! = null ) return BadRequest ( new { message = error } ) ;
2025-06-24 12:52:37 -03:00
if ( ! entradasSalidas . Any ( ) & & ! debitosCreditos . Any ( ) & & ! pagos . Any ( ) )
2025-05-27 18:17:56 -03:00
{
2025-05-31 23:48:42 -03:00
return NotFound ( new { message = "No hay datos para generar el reporte de cuenta del distribuidor." } ) ;
2025-05-27 18:17:56 -03:00
}
try
{
2025-06-24 12:52:37 -03:00
var distribuidor = await _distribuidorRepository . GetByIdAsync ( idDistribuidor ) ;
2025-05-27 18:17:56 -03:00
2025-06-24 12:52:37 -03:00
var viewModel = new CuentasDistribuidorViewModel
2025-06-18 15:32:22 -03:00
{
2025-06-24 12:52:37 -03:00
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" ) ,
2025-05-27 18:17:56 -03:00
} ;
2025-06-18 16:03:02 -03:00
2025-06-24 12:52:37 -03:00
var document = new CuentasDistribuidorDocument ( viewModel ) ;
byte [ ] pdfBytes = await _pdfGenerator . GeneratePdfAsync ( document ) ;
2025-05-27 18:17:56 -03:00
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." ) ;
}
}
2025-05-28 16:01:59 -03:00
// 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 ,
2025-05-31 23:48:42 -03:00
[FromQuery] DateTime fechaDesde ,
[FromQuery] DateTime fechaHasta ,
[FromQuery] int? idPlanta ,
2025-05-28 16:01:59 -03:00
[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 ) ;
}
2025-05-31 23:48:42 -03:00
2025-05-28 16:01:59 -03:00
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 ) ;
}
2025-05-27 18:17:56 -03:00
[HttpGet("tiradas-publicaciones-secciones/pdf")]
2025-06-24 12:52:37 -03:00
[ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
2025-05-27 18:17:56 -03:00
public async Task < IActionResult > GetReporteTiradasPublicacionesSeccionesPdf (
[FromQuery] int idPublicacion ,
2025-05-31 23:48:42 -03:00
[FromQuery] DateTime fechaDesde ,
[FromQuery] DateTime fechaHasta ,
[FromQuery] int? idPlanta ,
2025-05-27 18:17:56 -03:00
[FromQuery] bool consolidado = false )
{
if ( ! TienePermiso ( PermisoVerReporteTiradas ) ) return Forbid ( ) ;
IEnumerable < TiradasPublicacionesSeccionesDto > data ;
2025-06-24 12:52:37 -03:00
string? errorMsg ;
2025-05-27 18:17:56 -03:00
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 ) ;
}
2025-05-31 23:48:42 -03:00
2025-05-27 18:17:56 -03:00
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
{
2025-06-24 12:52:37 -03:00
var publicacion = await _publicacionRepository . GetByIdSimpleAsync ( idPublicacion ) ;
2025-05-27 18:17:56 -03:00
2025-06-24 12:52:37 -03:00
string? nombrePlanta = null ;
if ( ! consolidado & & idPlanta . HasValue )
2025-05-27 18:17:56 -03:00
{
2025-06-24 12:52:37 -03:00
var planta = await _plantaRepository . GetByIdAsync ( idPlanta . Value ) ;
nombrePlanta = planta ? . Nombre ;
2025-05-27 18:17:56 -03:00
}
2025-06-24 12:52:37 -03:00
var viewModel = new TiradasPublicacionesSeccionesViewModel
2025-05-27 18:17:56 -03:00
{
2025-06-24 12:52:37 -03:00
Detalles = data ,
NombrePublicacion = publicacion ? . Nombre ? ? "N/A" ,
MesConsultado = fechaDesde . ToString ( "MMMM 'de' yyyy" , new CultureInfo ( "es-ES" ) ) ,
NombrePlanta = nombrePlanta
2025-05-27 18:17:56 -03:00
} ;
2025-05-31 23:48:42 -03:00
2025-06-24 12:52:37 -03:00
var document = new TiradasPublicacionesSeccionesDocument ( viewModel ) ;
byte [ ] pdfBytes = await _pdfGenerator . GeneratePdfAsync ( document ) ;
2025-05-27 18:17:56 -03:00
string fileName = $"TiradasSecciones_Pub{idPublicacion}_{(consolidado ? " Consolidado " : $" Planta { idPlanta } ")}_{fechaDesde:yyyyMM}.pdf" ;
return File ( pdfBytes , "application/pdf" , fileName ) ;
}
2025-06-24 12:52:37 -03:00
catch ( Exception ex )
{
_logger . LogError ( ex , "Error PDF Tiradas Publicaciones Secciones." ) ;
return StatusCode ( 500 , "Error interno al generar el PDF del reporte." ) ;
}
2025-05-27 18:17:56 -03:00
}
2025-05-28 16:01:59 -03:00
// 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 )
{
2025-05-31 23:48:42 -03:00
if ( ! TienePermiso ( PermisoVerReporteConsumoBobinas ) ) return Forbid ( ) ;
2025-05-28 16:01:59 -03:00
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 ) ;
}
2025-05-31 23:48:42 -03:00
2025-05-28 16:01:59 -03:00
if ( errorMsg ! = null ) return BadRequest ( new { message = errorMsg } ) ;
if ( data = = null | | ! data . Any ( ) ) return NotFound ( new { message = "No hay datos para generar el reporte." } ) ;
2025-05-31 23:48:42 -03:00
2025-05-28 16:01:59 -03:00
return Ok ( data ) ;
}
2025-05-27 18:17:56 -03:00
[HttpGet("consumo-bobinas-seccion/pdf")]
2025-06-24 12:52:37 -03:00
[ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
2025-05-27 18:17:56 -03:00
public async Task < IActionResult > GetReporteConsumoBobinasSeccionPdf (
[FromQuery] DateTime fechaDesde ,
[FromQuery] DateTime fechaHasta ,
[FromQuery] int? idPlanta ,
[FromQuery] bool consolidado = false )
{
2025-05-31 23:48:42 -03:00
if ( ! TienePermiso ( PermisoVerReporteConsumoBobinas ) ) return Forbid ( ) ;
2025-05-27 18:17:56 -03:00
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 ) ;
}
2025-05-31 23:48:42 -03:00
2025-05-27 18:17:56 -03:00
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
{
2025-06-24 12:52:37 -03:00
string? nombrePlanta = null ;
if ( ! consolidado & & idPlanta . HasValue )
2025-05-27 18:17:56 -03:00
{
2025-06-24 12:52:37 -03:00
var planta = await _plantaRepository . GetByIdAsync ( idPlanta . Value ) ;
nombrePlanta = planta ? . Nombre ;
2025-05-27 18:17:56 -03:00
}
2025-05-31 23:48:42 -03:00
2025-06-24 12:52:37 -03:00
var viewModel = new ConsumoBobinasSeccionViewModel
2025-05-27 18:17:56 -03:00
{
2025-06-24 12:52:37 -03:00
Detalles = data ,
NombrePlanta = nombrePlanta ,
FechaDesde = fechaDesde . ToString ( "dd/MM/yyyy" ) ,
FechaHasta = fechaHasta . ToString ( "dd/MM/yyyy" )
2025-05-27 18:17:56 -03:00
} ;
2025-06-24 12:52:37 -03:00
var document = new ConsumoBobinasSeccionDocument ( viewModel ) ;
byte [ ] pdfBytes = await _pdfGenerator . GeneratePdfAsync ( document ) ;
2025-05-27 18:17:56 -03:00
string fileName = $"ConsumoBobinasSeccion_{(consolidado ? " Consolidado " : $" Planta { idPlanta } ")}_{fechaDesde:yyyyMMdd}_{fechaHasta:yyyyMMdd}.pdf" ;
return File ( pdfBytes , "application/pdf" , fileName ) ;
}
2025-06-24 12:52:37 -03:00
catch ( Exception ex )
{
_logger . LogError ( ex , "Error PDF Consumo Bobinas por Seccion." ) ;
return StatusCode ( 500 , "Error interno al generar el PDF del reporte." ) ;
}
2025-05-27 18:17:56 -03:00
}
2025-05-28 16:01:59 -03:00
// 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 )
{
2025-05-31 23:48:42 -03:00
if ( ! TienePermiso ( PermisoVerReporteConsumoBobinas ) ) return Forbid ( ) ;
2025-05-28 16:01:59 -03:00
var ( data , error ) = await _reportesService . ObtenerConsumoBobinasPorPublicacionAsync ( fechaDesde , fechaHasta ) ;
2025-05-31 23:48:42 -03:00
2025-05-28 16:01:59 -03:00
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 ) ;
}
2025-05-27 18:17:56 -03:00
[HttpGet("consumo-bobinas-publicacion/pdf")]
2025-06-24 12:52:37 -03:00
[ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
2025-05-27 18:17:56 -03:00
public async Task < IActionResult > GetReporteConsumoBobinasPublicacionPdf (
[FromQuery] DateTime fechaDesde ,
[FromQuery] DateTime fechaHasta )
{
2025-05-31 23:48:42 -03:00
if ( ! TienePermiso ( PermisoVerReporteConsumoBobinas ) ) return Forbid ( ) ;
2025-05-27 18:17:56 -03:00
var ( data , error ) = await _reportesService . ObtenerConsumoBobinasPorPublicacionAsync ( fechaDesde , fechaHasta ) ;
2025-05-31 23:48:42 -03:00
2025-05-27 18:17:56 -03:00
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
{
2025-06-24 12:52:37 -03:00
var viewModel = new ConsumoBobinasPublicacionViewModel
2025-05-27 18:17:56 -03:00
{
2025-06-24 12:52:37 -03:00
Detalles = data ,
FechaDesde = fechaDesde . ToString ( "dd/MM/yyyy" ) ,
FechaHasta = fechaHasta . ToString ( "dd/MM/yyyy" )
2025-05-27 18:17:56 -03:00
} ;
2025-06-24 12:52:37 -03:00
var document = new ConsumoBobinasPublicacionDocument ( viewModel ) ;
byte [ ] pdfBytes = await _pdfGenerator . GeneratePdfAsync ( document ) ;
2025-05-27 18:17:56 -03:00
string fileName = $"ConsumoBobinasPublicacion_{fechaDesde:yyyyMMdd}_{fechaHasta:yyyyMMdd}.pdf" ;
return File ( pdfBytes , "application/pdf" , fileName ) ;
}
2025-06-24 12:52:37 -03:00
catch ( Exception ex )
{
_logger . LogError ( ex , "Error PDF Consumo Bobinas por Publicacion." ) ;
return StatusCode ( 500 , "Error interno al generar el PDF del reporte." ) ;
}
2025-05-27 18:17:56 -03:00
}
2025-05-28 16:01:59 -03:00
// 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 )
{
2025-05-31 23:48:42 -03:00
if ( ! TienePermiso ( PermisoVerReporteConsumoBobinas ) ) return Forbid ( ) ;
2025-05-28 16:01:59 -03:00
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 ) ;
}
2025-05-31 23:48:42 -03:00
2025-05-28 16:01:59 -03:00
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 ) ;
}
2025-05-27 18:17:56 -03:00
[HttpGet("comparativa-consumo-bobinas/pdf")]
2025-06-24 12:52:37 -03:00
[ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
2025-05-27 18:17:56 -03:00
public async Task < IActionResult > GetReporteComparativaConsumoBobinasPdf (
[FromQuery] DateTime fechaInicioMesA , [ FromQuery ] DateTime fechaFinMesA ,
[FromQuery] DateTime fechaInicioMesB , [ FromQuery ] DateTime fechaFinMesB ,
[FromQuery] int? idPlanta ,
[FromQuery] bool consolidado = false )
{
2025-05-31 23:48:42 -03:00
if ( ! TienePermiso ( PermisoVerReporteConsumoBobinas ) ) return Forbid ( ) ;
2025-05-27 18:17:56 -03:00
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 ) ;
}
2025-05-31 23:48:42 -03:00
2025-05-27 18:17:56 -03:00
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
{
2025-06-24 12:52:37 -03:00
string? nombrePlanta = null ;
if ( ! consolidado & & idPlanta . HasValue )
2025-05-27 18:17:56 -03:00
{
2025-06-24 12:52:37 -03:00
var planta = await _plantaRepository . GetByIdAsync ( idPlanta . Value ) ;
nombrePlanta = planta ? . Nombre ;
2025-05-27 18:17:56 -03:00
}
2025-05-31 23:48:42 -03:00
2025-06-24 12:52:37 -03:00
var viewModel = new ComparativaConsumoBobinasViewModel
2025-05-27 18:17:56 -03:00
{
2025-06-24 12:52:37 -03:00
Detalles = data ,
NombrePlanta = nombrePlanta ,
MesA = fechaInicioMesA . ToString ( "MMMM yyyy" , new CultureInfo ( "es-ES" ) ) ,
MesB = fechaInicioMesB . ToString ( "MMMM yyyy" , new CultureInfo ( "es-ES" ) )
2025-05-27 18:17:56 -03:00
} ;
2025-06-24 12:52:37 -03:00
var document = new ComparativaConsumoBobinasDocument ( viewModel ) ;
byte [ ] pdfBytes = await _pdfGenerator . GeneratePdfAsync ( document ) ;
2025-05-27 18:17:56 -03:00
string fileName = $"ComparativaConsumoBobinas_{(consolidado ? " Consolidado " : $" Planta { idPlanta } ")}_{fechaInicioMesA:yyyyMM}_vs_{fechaInicioMesB:yyyyMM}.pdf" ;
return File ( pdfBytes , "application/pdf" , fileName ) ;
}
2025-06-24 12:52:37 -03:00
catch ( Exception ex )
{
_logger . LogError ( ex , "Error PDF Comparativa Consumo Bobinas." ) ;
return StatusCode ( 500 , "Error interno al generar el PDF del reporte." ) ;
}
2025-05-27 18:17:56 -03:00
}
2025-05-31 23:48:42 -03:00
// 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
{
2025-06-24 12:52:37 -03:00
var publicacionData = await _publicacionRepository . GetByIdSimpleAsync ( idPublicacion ) ;
2025-05-31 23:48:42 -03:00
var distribuidorData = await _distribuidorRepository . GetByIdAsync ( idDistribuidor ) ;
2025-06-24 12:52:37 -03:00
var viewModel = new ListadoDistribucionDistribuidoresViewModel
2025-05-31 23:48:42 -03:00
{
2025-06-24 12:52:37 -03:00
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" )
2025-05-31 23:48:42 -03:00
} ;
2025-06-24 12:52:37 -03:00
var document = new ListadoDistribucionDistribuidoresDocument ( viewModel ) ;
byte [ ] pdfBytes = await _pdfGenerator . GeneratePdfAsync ( document ) ;
2025-05-31 23:48:42 -03:00
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." ) ;
}
}
2025-06-03 13:45:20 -03:00
[HttpGet("ticket-liquidacion-canilla/pdf")]
[ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task < IActionResult > GetTicketLiquidacionCanillaPdf (
2025-06-24 12:52:37 -03:00
[FromQuery] DateTime fecha ,
[FromQuery] int idCanilla ,
[FromQuery] bool esAccionista = false )
2025-06-03 13:45:20 -03:00
{
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
{
2025-06-24 12:52:37 -03:00
var viewModel = new LiquidacionCanillaViewModel
2025-06-03 13:45:20 -03:00
{
2025-06-24 12:52:37 -03:00
Detalles = detalles ,
Ganancias = ganancias ,
FechaLiquidacion = fecha . ToString ( "dd/MM/yyyy" ) ,
EsAccionista = esAccionista
} ;
2025-06-03 13:45:20 -03:00
2025-06-24 12:52:37 -03:00
var document = new LiquidacionCanillaDocument ( viewModel ) ;
byte [ ] pdfBytes = await _pdfGenerator . GeneratePdfAsync ( document ) ;
2025-06-03 13:45:20 -03:00
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." ) ;
}
}
2025-06-06 18:33:09 -03:00
// 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 ,
2025-06-24 12:52:37 -03:00
[FromQuery] DateTime fechaDesde ,
[FromQuery] DateTime fechaHasta )
2025-06-06 18:33:09 -03:00
{
2025-06-24 12:52:37 -03:00
if ( ! TienePermiso ( PermisoVerReporteNovedadesCanillas ) ) return Forbid ( ) ;
if ( fechaDesde > fechaHasta ) return BadRequest ( new { message = "La fecha 'desde' no puede ser posterior a la fecha 'hasta'." } ) ;
2025-06-06 18:33:09 -03:00
try
{
2025-06-24 12:52:37 -03:00
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 ;
2025-06-06 18:33:09 -03:00
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 ) ;
2025-06-24 12:52:37 -03:00
var viewModel = new NovedadesCanillasViewModel
2025-06-06 18:33:09 -03:00
{
2025-06-24 12:52:37 -03:00
ResumenCanillas = gananciasData ,
DetallesNovedades = novedadesData ? ? Enumerable . Empty < NovedadesCanillasReporteDto > ( ) ,
NombreEmpresa = empresa ? . Nombre ? ? "N/A" ,
FechaDesde = fechaDesde . ToString ( "dd/MM/yyyy" ) ,
FechaHasta = fechaHasta . ToString ( "dd/MM/yyyy" )
} ;
2025-06-06 18:33:09 -03:00
2025-06-24 12:52:37 -03:00
var document = new NovedadesCanillasDocument ( viewModel ) ;
byte [ ] pdfBytes = await _pdfGenerator . GeneratePdfAsync ( document ) ;
2025-06-06 18:33:09 -03:00
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}" } ) ;
}
}
2025-06-24 12:52:37 -03:00
2025-06-06 18:33:09 -03:00
// 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")]
2025-06-24 12:52:37 -03:00
[ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
2025-06-06 18:33:09 -03:00
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 ) ;
2025-06-24 12:52:37 -03:00
2025-06-06 18:33:09 -03:00
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
{
2025-06-24 12:52:37 -03:00
var viewModel = new ListadoDistCanMensualDiariosViewModel
2025-06-06 18:33:09 -03:00
{
2025-06-24 12:52:37 -03:00
Detalles = data ,
TipoDestinatario = esAccionista ? "Accionistas" : "Canillitas" ,
FechaDesde = fechaDesde . ToString ( "dd/MM/yyyy" ) ,
FechaHasta = fechaHasta . ToString ( "dd/MM/yyyy" )
} ;
2025-06-06 18:33:09 -03:00
2025-06-24 12:52:37 -03:00
var document = new ListadoDistCanMensualDiariosDocument ( viewModel ) ;
byte [ ] pdfBytes = await _pdfGenerator . GeneratePdfAsync ( document ) ;
2025-06-06 18:33:09 -03:00
string tipoDesc = esAccionista ? "Accionistas" : "Canillitas" ;
2025-06-24 12:52:37 -03:00
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." ) ;
2025-06-06 18:33:09 -03:00
}
}
// 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")]
2025-06-24 12:52:37 -03:00
[ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
2025-06-06 18:33:09 -03:00
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 ) ;
2025-06-24 12:52:37 -03:00
2025-06-06 18:33:09 -03:00
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
{
2025-06-24 12:52:37 -03:00
var viewModel = new ListadoDistCanMensualViewModel
2025-06-06 18:33:09 -03:00
{
2025-06-24 12:52:37 -03:00
Detalles = data ,
TipoDestinatario = esAccionista ? "Accionistas" : "Canillitas" ,
FechaDesde = fechaDesde . ToString ( "dd/MM/yyyy" ) ,
FechaHasta = fechaHasta . ToString ( "dd/MM/yyyy" )
} ;
2025-06-06 18:33:09 -03:00
2025-06-24 12:52:37 -03:00
var document = new ListadoDistCanMensualDocument ( viewModel ) ;
byte [ ] pdfBytes = await _pdfGenerator . GeneratePdfAsync ( document ) ;
2025-06-06 18:33:09 -03:00
string tipoDesc = esAccionista ? "Accionistas" : "Canillitas" ;
2025-06-24 12:52:37 -03:00
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." ) ;
2025-06-06 18:33:09 -03:00
}
}
2025-08-08 09:48:15 -03:00
[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." ) ;
}
}
Feat: Implementa Reporte de Distribución de Suscripciones y Refactoriza Gestión de Ajustes
Se introduce una nueva funcionalidad de reporte crucial para la logística y se realiza una refactorización mayor del sistema de ajustes para garantizar la correcta imputación contable.
### ✨ Nuevas Características
- **Nuevo Reporte de Distribución de Suscripciones (RR011):**
- Se añade un nuevo reporte en PDF que genera un listado de todas las suscripciones activas en un rango de fechas.
- El reporte está diseñado para el equipo de reparto, incluyendo datos clave como nombre del suscriptor, dirección, teléfono, días de entrega y observaciones.
- Se implementa el endpoint, la lógica de servicio, la consulta a la base de datos y el template de QuestPDF en el backend.
- Se crea la página correspondiente en el frontend (React) con su selector de fechas y se añade la ruta y el enlace en el menú de reportes.
### 🔄 Refactorización Mayor
- **Asociación de Ajustes a Empresas:**
- Se refactoriza por completo la entidad `Ajuste` para incluir una referencia obligatoria a una `IdEmpresa`.
- **Motivo:** Corregir un error crítico en la lógica de negocio donde los ajustes de un suscriptor se aplicaban a la primera factura generada, sin importar a qué empresa correspondía el ajuste.
- Se provee un script de migración SQL para actualizar el esquema de la base de datos (`susc_Ajustes`).
- Se actualizan todos los DTOs, repositorios y servicios (backend) para manejar la nueva relación.
- Se modifica el `FacturacionService` para que ahora aplique los ajustes pendientes correspondientes a cada empresa dentro de su bucle de facturación.
- Se actualiza el formulario de creación/edición de ajustes en el frontend (React) para incluir un selector de empresa obligatorio.
### ⚡️ Optimizaciones de Rendimiento
- **Solución de N+1 Queries:**
- Se optimiza el método `ObtenerTodos` en `SuscriptorService` para obtener todas las formas de pago en una única consulta en lugar de una por cada suscriptor.
- Se optimiza el método `ObtenerAjustesPorSuscriptor` en `AjusteService` para obtener todos los nombres de usuarios y empresas en dos consultas masivas, en lugar de N consultas individuales.
- Se añade el método `GetByIdsAsync` al `IUsuarioRepository` y su implementación para soportar esta optimización.
### 🐛 Corrección de Errores
- Se corrigen múltiples errores en el script de migración de base de datos (uso de `GO` dentro de transacciones, error de "columna inválida").
- Se soluciona un error de SQL (`INSERT` statement) en `AjusteRepository` que impedía la creación de nuevos ajustes.
- Se corrige un bug en el `AjusteService` que causaba que el nombre de la empresa apareciera como "N/A" en la UI debido a un mapeo incompleto en el método optimizado.
- Se corrige la lógica de generación de emails en `FacturacionService` para mostrar correctamente el nombre de la empresa en cada sección del resumen de cuenta.
2025-08-09 17:39:21 -03:00
[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 ( ) ;
Feat: Implementa auditoría de envíos masivos y mejora de procesos
Se introduce un sistema completo para auditar los envíos masivos de correos durante el cierre mensual y se refactoriza la interfaz de usuario de procesos para una mayor claridad y escalabilidad. Además, se mejora la lógica de negocio para la gestión de bajas de suscripciones.
### ✨ Nuevas Características
- **Auditoría de Envíos Masivos (Cierre Mensual):**
- Se crea una nueva tabla `com_LotesDeEnvio` para registrar cada ejecución del proceso de facturación mensual.
- El `FacturacionService` ahora crea un "lote" al iniciar el cierre, registra el resultado de cada envío de email individual asociándolo a dicho lote, y actualiza las estadísticas finales (enviados, fallidos) al terminar.
- Se implementa un nuevo `LotesEnvioController` con un endpoint para consultar los detalles de cualquier lote de envío histórico.
### 🔄 Refactorización y Mejoras
- **Rediseño de la Página de Procesos:**
- La antigua página "Facturación" se renombra a `CierreYProcesosPage` y se rediseña completamente utilizando una interfaz de Pestañas (Tabs).
- **Pestaña "Procesos Mensuales":** Aisla las acciones principales (Generar Cierre, Archivo de Débito, Procesar Respuesta), mostrando un resumen del resultado del último envío.
- **Pestaña "Historial de Cierres":** Muestra una tabla con todos los lotes de envío pasados y permite al usuario ver los detalles de cada uno en un modal.
- **Filtros para el Historial de Cierres:**
- Se añaden filtros por Mes y Año a la pestaña de "Historial de Cierres", permitiendo al usuario buscar y auditar procesos pasados de manera eficiente. El filtrado se realiza en el backend para un rendimiento óptimo.
- **Lógica de `FechaFin` Obligatoria para Bajas:**
- Se implementa una regla de negocio crucial: al cambiar el estado de una suscripción a "Pausada" o "Cancelada", ahora es obligatorio establecer una `FechaFin`.
- **Frontend:** El modal de suscripciones ahora gestiona esto automáticamente, haciendo el campo `FechaFin` requerido y visible según el estado seleccionado.
- **Backend:** Se añade una validación en `SuscripcionService` como segunda capa de seguridad para garantizar la integridad de los datos.
### 🐛 Corrección de Errores
- **Reporte de Distribución:** Se corrigió un bug en la generación del PDF donde la columna de fecha no mostraba la "Fecha de Baja" para las suscripciones finalizadas. Ahora se muestra la fecha correcta según la sección (Altas o Bajas).
- **Errores de Compilación y Dependencias:** Se solucionaron varios errores de compilación en el backend, principalmente relacionados con la falta de registro de los nuevos repositorios (`ILoteDeEnvioRepository`, `IEmailLogService`, etc.) en el contenedor de inyección de dependencias (`Program.cs`).
- **Errores de Tipado en Frontend:** Se corrigieron múltiples errores de TypeScript en `CierreYProcesosPage` debidos a la inconsistencia entre `PascalCase` (C#) y `camelCase` (JSON/TypeScript), asegurando un mapeo correcto de los datos de la API.
2025-08-11 11:14:03 -03:00
var ( altas , bajas , error ) = await _reportesService . ObtenerReporteDistribucionSuscripcionesAsync ( fechaDesde , fechaHasta ) ;
Feat: Implementa Reporte de Distribución de Suscripciones y Refactoriza Gestión de Ajustes
Se introduce una nueva funcionalidad de reporte crucial para la logística y se realiza una refactorización mayor del sistema de ajustes para garantizar la correcta imputación contable.
### ✨ Nuevas Características
- **Nuevo Reporte de Distribución de Suscripciones (RR011):**
- Se añade un nuevo reporte en PDF que genera un listado de todas las suscripciones activas en un rango de fechas.
- El reporte está diseñado para el equipo de reparto, incluyendo datos clave como nombre del suscriptor, dirección, teléfono, días de entrega y observaciones.
- Se implementa el endpoint, la lógica de servicio, la consulta a la base de datos y el template de QuestPDF en el backend.
- Se crea la página correspondiente en el frontend (React) con su selector de fechas y se añade la ruta y el enlace en el menú de reportes.
### 🔄 Refactorización Mayor
- **Asociación de Ajustes a Empresas:**
- Se refactoriza por completo la entidad `Ajuste` para incluir una referencia obligatoria a una `IdEmpresa`.
- **Motivo:** Corregir un error crítico en la lógica de negocio donde los ajustes de un suscriptor se aplicaban a la primera factura generada, sin importar a qué empresa correspondía el ajuste.
- Se provee un script de migración SQL para actualizar el esquema de la base de datos (`susc_Ajustes`).
- Se actualizan todos los DTOs, repositorios y servicios (backend) para manejar la nueva relación.
- Se modifica el `FacturacionService` para que ahora aplique los ajustes pendientes correspondientes a cada empresa dentro de su bucle de facturación.
- Se actualiza el formulario de creación/edición de ajustes en el frontend (React) para incluir un selector de empresa obligatorio.
### ⚡️ Optimizaciones de Rendimiento
- **Solución de N+1 Queries:**
- Se optimiza el método `ObtenerTodos` en `SuscriptorService` para obtener todas las formas de pago en una única consulta en lugar de una por cada suscriptor.
- Se optimiza el método `ObtenerAjustesPorSuscriptor` en `AjusteService` para obtener todos los nombres de usuarios y empresas en dos consultas masivas, en lugar de N consultas individuales.
- Se añade el método `GetByIdsAsync` al `IUsuarioRepository` y su implementación para soportar esta optimización.
### 🐛 Corrección de Errores
- Se corrigen múltiples errores en el script de migración de base de datos (uso de `GO` dentro de transacciones, error de "columna inválida").
- Se soluciona un error de SQL (`INSERT` statement) en `AjusteRepository` que impedía la creación de nuevos ajustes.
- Se corrige un bug en el `AjusteService` que causaba que el nombre de la empresa apareciera como "N/A" en la UI debido a un mapeo incompleto en el método optimizado.
- Se corrige la lógica de generación de emails en `FacturacionService` para mostrar correctamente el nombre de la empresa en cada sección del resumen de cuenta.
2025-08-09 17:39:21 -03:00
if ( error ! = null ) return BadRequest ( new { message = error } ) ;
Feat: Implementa auditoría de envíos masivos y mejora de procesos
Se introduce un sistema completo para auditar los envíos masivos de correos durante el cierre mensual y se refactoriza la interfaz de usuario de procesos para una mayor claridad y escalabilidad. Además, se mejora la lógica de negocio para la gestión de bajas de suscripciones.
### ✨ Nuevas Características
- **Auditoría de Envíos Masivos (Cierre Mensual):**
- Se crea una nueva tabla `com_LotesDeEnvio` para registrar cada ejecución del proceso de facturación mensual.
- El `FacturacionService` ahora crea un "lote" al iniciar el cierre, registra el resultado de cada envío de email individual asociándolo a dicho lote, y actualiza las estadísticas finales (enviados, fallidos) al terminar.
- Se implementa un nuevo `LotesEnvioController` con un endpoint para consultar los detalles de cualquier lote de envío histórico.
### 🔄 Refactorización y Mejoras
- **Rediseño de la Página de Procesos:**
- La antigua página "Facturación" se renombra a `CierreYProcesosPage` y se rediseña completamente utilizando una interfaz de Pestañas (Tabs).
- **Pestaña "Procesos Mensuales":** Aisla las acciones principales (Generar Cierre, Archivo de Débito, Procesar Respuesta), mostrando un resumen del resultado del último envío.
- **Pestaña "Historial de Cierres":** Muestra una tabla con todos los lotes de envío pasados y permite al usuario ver los detalles de cada uno en un modal.
- **Filtros para el Historial de Cierres:**
- Se añaden filtros por Mes y Año a la pestaña de "Historial de Cierres", permitiendo al usuario buscar y auditar procesos pasados de manera eficiente. El filtrado se realiza en el backend para un rendimiento óptimo.
- **Lógica de `FechaFin` Obligatoria para Bajas:**
- Se implementa una regla de negocio crucial: al cambiar el estado de una suscripción a "Pausada" o "Cancelada", ahora es obligatorio establecer una `FechaFin`.
- **Frontend:** El modal de suscripciones ahora gestiona esto automáticamente, haciendo el campo `FechaFin` requerido y visible según el estado seleccionado.
- **Backend:** Se añade una validación en `SuscripcionService` como segunda capa de seguridad para garantizar la integridad de los datos.
### 🐛 Corrección de Errores
- **Reporte de Distribución:** Se corrigió un bug en la generación del PDF donde la columna de fecha no mostraba la "Fecha de Baja" para las suscripciones finalizadas. Ahora se muestra la fecha correcta según la sección (Altas o Bajas).
- **Errores de Compilación y Dependencias:** Se solucionaron varios errores de compilación en el backend, principalmente relacionados con la falta de registro de los nuevos repositorios (`ILoteDeEnvioRepository`, `IEmailLogService`, etc.) en el contenedor de inyección de dependencias (`Program.cs`).
- **Errores de Tipado en Frontend:** Se corrigieron múltiples errores de TypeScript en `CierreYProcesosPage` debidos a la inconsistencia entre `PascalCase` (C#) y `camelCase` (JSON/TypeScript), asegurando un mapeo correcto de los datos de la API.
2025-08-11 11:14:03 -03:00
if ( ( altas = = null | | ! altas . Any ( ) ) & & ( bajas = = null | | ! bajas . Any ( ) ) )
Feat: Implementa Reporte de Distribución de Suscripciones y Refactoriza Gestión de Ajustes
Se introduce una nueva funcionalidad de reporte crucial para la logística y se realiza una refactorización mayor del sistema de ajustes para garantizar la correcta imputación contable.
### ✨ Nuevas Características
- **Nuevo Reporte de Distribución de Suscripciones (RR011):**
- Se añade un nuevo reporte en PDF que genera un listado de todas las suscripciones activas en un rango de fechas.
- El reporte está diseñado para el equipo de reparto, incluyendo datos clave como nombre del suscriptor, dirección, teléfono, días de entrega y observaciones.
- Se implementa el endpoint, la lógica de servicio, la consulta a la base de datos y el template de QuestPDF en el backend.
- Se crea la página correspondiente en el frontend (React) con su selector de fechas y se añade la ruta y el enlace en el menú de reportes.
### 🔄 Refactorización Mayor
- **Asociación de Ajustes a Empresas:**
- Se refactoriza por completo la entidad `Ajuste` para incluir una referencia obligatoria a una `IdEmpresa`.
- **Motivo:** Corregir un error crítico en la lógica de negocio donde los ajustes de un suscriptor se aplicaban a la primera factura generada, sin importar a qué empresa correspondía el ajuste.
- Se provee un script de migración SQL para actualizar el esquema de la base de datos (`susc_Ajustes`).
- Se actualizan todos los DTOs, repositorios y servicios (backend) para manejar la nueva relación.
- Se modifica el `FacturacionService` para que ahora aplique los ajustes pendientes correspondientes a cada empresa dentro de su bucle de facturación.
- Se actualiza el formulario de creación/edición de ajustes en el frontend (React) para incluir un selector de empresa obligatorio.
### ⚡️ Optimizaciones de Rendimiento
- **Solución de N+1 Queries:**
- Se optimiza el método `ObtenerTodos` en `SuscriptorService` para obtener todas las formas de pago en una única consulta en lugar de una por cada suscriptor.
- Se optimiza el método `ObtenerAjustesPorSuscriptor` en `AjusteService` para obtener todos los nombres de usuarios y empresas en dos consultas masivas, en lugar de N consultas individuales.
- Se añade el método `GetByIdsAsync` al `IUsuarioRepository` y su implementación para soportar esta optimización.
### 🐛 Corrección de Errores
- Se corrigen múltiples errores en el script de migración de base de datos (uso de `GO` dentro de transacciones, error de "columna inválida").
- Se soluciona un error de SQL (`INSERT` statement) en `AjusteRepository` que impedía la creación de nuevos ajustes.
- Se corrige un bug en el `AjusteService` que causaba que el nombre de la empresa apareciera como "N/A" en la UI debido a un mapeo incompleto en el método optimizado.
- Se corrige la lógica de generación de emails en `FacturacionService` para mostrar correctamente el nombre de la empresa en cada sección del resumen de cuenta.
2025-08-09 17:39:21 -03:00
{
Feat: Implementa auditoría de envíos masivos y mejora de procesos
Se introduce un sistema completo para auditar los envíos masivos de correos durante el cierre mensual y se refactoriza la interfaz de usuario de procesos para una mayor claridad y escalabilidad. Además, se mejora la lógica de negocio para la gestión de bajas de suscripciones.
### ✨ Nuevas Características
- **Auditoría de Envíos Masivos (Cierre Mensual):**
- Se crea una nueva tabla `com_LotesDeEnvio` para registrar cada ejecución del proceso de facturación mensual.
- El `FacturacionService` ahora crea un "lote" al iniciar el cierre, registra el resultado de cada envío de email individual asociándolo a dicho lote, y actualiza las estadísticas finales (enviados, fallidos) al terminar.
- Se implementa un nuevo `LotesEnvioController` con un endpoint para consultar los detalles de cualquier lote de envío histórico.
### 🔄 Refactorización y Mejoras
- **Rediseño de la Página de Procesos:**
- La antigua página "Facturación" se renombra a `CierreYProcesosPage` y se rediseña completamente utilizando una interfaz de Pestañas (Tabs).
- **Pestaña "Procesos Mensuales":** Aisla las acciones principales (Generar Cierre, Archivo de Débito, Procesar Respuesta), mostrando un resumen del resultado del último envío.
- **Pestaña "Historial de Cierres":** Muestra una tabla con todos los lotes de envío pasados y permite al usuario ver los detalles de cada uno en un modal.
- **Filtros para el Historial de Cierres:**
- Se añaden filtros por Mes y Año a la pestaña de "Historial de Cierres", permitiendo al usuario buscar y auditar procesos pasados de manera eficiente. El filtrado se realiza en el backend para un rendimiento óptimo.
- **Lógica de `FechaFin` Obligatoria para Bajas:**
- Se implementa una regla de negocio crucial: al cambiar el estado de una suscripción a "Pausada" o "Cancelada", ahora es obligatorio establecer una `FechaFin`.
- **Frontend:** El modal de suscripciones ahora gestiona esto automáticamente, haciendo el campo `FechaFin` requerido y visible según el estado seleccionado.
- **Backend:** Se añade una validación en `SuscripcionService` como segunda capa de seguridad para garantizar la integridad de los datos.
### 🐛 Corrección de Errores
- **Reporte de Distribución:** Se corrigió un bug en la generación del PDF donde la columna de fecha no mostraba la "Fecha de Baja" para las suscripciones finalizadas. Ahora se muestra la fecha correcta según la sección (Altas o Bajas).
- **Errores de Compilación y Dependencias:** Se solucionaron varios errores de compilación en el backend, principalmente relacionados con la falta de registro de los nuevos repositorios (`ILoteDeEnvioRepository`, `IEmailLogService`, etc.) en el contenedor de inyección de dependencias (`Program.cs`).
- **Errores de Tipado en Frontend:** Se corrigieron múltiples errores de TypeScript en `CierreYProcesosPage` debidos a la inconsistencia entre `PascalCase` (C#) y `camelCase` (JSON/TypeScript), asegurando un mapeo correcto de los datos de la API.
2025-08-11 11:14:03 -03:00
return NotFound ( new { message = "No se encontraron suscripciones activas ni bajas para el período seleccionado." } ) ;
Feat: Implementa Reporte de Distribución de Suscripciones y Refactoriza Gestión de Ajustes
Se introduce una nueva funcionalidad de reporte crucial para la logística y se realiza una refactorización mayor del sistema de ajustes para garantizar la correcta imputación contable.
### ✨ Nuevas Características
- **Nuevo Reporte de Distribución de Suscripciones (RR011):**
- Se añade un nuevo reporte en PDF que genera un listado de todas las suscripciones activas en un rango de fechas.
- El reporte está diseñado para el equipo de reparto, incluyendo datos clave como nombre del suscriptor, dirección, teléfono, días de entrega y observaciones.
- Se implementa el endpoint, la lógica de servicio, la consulta a la base de datos y el template de QuestPDF en el backend.
- Se crea la página correspondiente en el frontend (React) con su selector de fechas y se añade la ruta y el enlace en el menú de reportes.
### 🔄 Refactorización Mayor
- **Asociación de Ajustes a Empresas:**
- Se refactoriza por completo la entidad `Ajuste` para incluir una referencia obligatoria a una `IdEmpresa`.
- **Motivo:** Corregir un error crítico en la lógica de negocio donde los ajustes de un suscriptor se aplicaban a la primera factura generada, sin importar a qué empresa correspondía el ajuste.
- Se provee un script de migración SQL para actualizar el esquema de la base de datos (`susc_Ajustes`).
- Se actualizan todos los DTOs, repositorios y servicios (backend) para manejar la nueva relación.
- Se modifica el `FacturacionService` para que ahora aplique los ajustes pendientes correspondientes a cada empresa dentro de su bucle de facturación.
- Se actualiza el formulario de creación/edición de ajustes en el frontend (React) para incluir un selector de empresa obligatorio.
### ⚡️ Optimizaciones de Rendimiento
- **Solución de N+1 Queries:**
- Se optimiza el método `ObtenerTodos` en `SuscriptorService` para obtener todas las formas de pago en una única consulta en lugar de una por cada suscriptor.
- Se optimiza el método `ObtenerAjustesPorSuscriptor` en `AjusteService` para obtener todos los nombres de usuarios y empresas en dos consultas masivas, en lugar de N consultas individuales.
- Se añade el método `GetByIdsAsync` al `IUsuarioRepository` y su implementación para soportar esta optimización.
### 🐛 Corrección de Errores
- Se corrigen múltiples errores en el script de migración de base de datos (uso de `GO` dentro de transacciones, error de "columna inválida").
- Se soluciona un error de SQL (`INSERT` statement) en `AjusteRepository` que impedía la creación de nuevos ajustes.
- Se corrige un bug en el `AjusteService` que causaba que el nombre de la empresa apareciera como "N/A" en la UI debido a un mapeo incompleto en el método optimizado.
- Se corrige la lógica de generación de emails en `FacturacionService` para mostrar correctamente el nombre de la empresa en cada sección del resumen de cuenta.
2025-08-09 17:39:21 -03:00
}
try
{
Feat: Implementa auditoría de envíos masivos y mejora de procesos
Se introduce un sistema completo para auditar los envíos masivos de correos durante el cierre mensual y se refactoriza la interfaz de usuario de procesos para una mayor claridad y escalabilidad. Además, se mejora la lógica de negocio para la gestión de bajas de suscripciones.
### ✨ Nuevas Características
- **Auditoría de Envíos Masivos (Cierre Mensual):**
- Se crea una nueva tabla `com_LotesDeEnvio` para registrar cada ejecución del proceso de facturación mensual.
- El `FacturacionService` ahora crea un "lote" al iniciar el cierre, registra el resultado de cada envío de email individual asociándolo a dicho lote, y actualiza las estadísticas finales (enviados, fallidos) al terminar.
- Se implementa un nuevo `LotesEnvioController` con un endpoint para consultar los detalles de cualquier lote de envío histórico.
### 🔄 Refactorización y Mejoras
- **Rediseño de la Página de Procesos:**
- La antigua página "Facturación" se renombra a `CierreYProcesosPage` y se rediseña completamente utilizando una interfaz de Pestañas (Tabs).
- **Pestaña "Procesos Mensuales":** Aisla las acciones principales (Generar Cierre, Archivo de Débito, Procesar Respuesta), mostrando un resumen del resultado del último envío.
- **Pestaña "Historial de Cierres":** Muestra una tabla con todos los lotes de envío pasados y permite al usuario ver los detalles de cada uno en un modal.
- **Filtros para el Historial de Cierres:**
- Se añaden filtros por Mes y Año a la pestaña de "Historial de Cierres", permitiendo al usuario buscar y auditar procesos pasados de manera eficiente. El filtrado se realiza en el backend para un rendimiento óptimo.
- **Lógica de `FechaFin` Obligatoria para Bajas:**
- Se implementa una regla de negocio crucial: al cambiar el estado de una suscripción a "Pausada" o "Cancelada", ahora es obligatorio establecer una `FechaFin`.
- **Frontend:** El modal de suscripciones ahora gestiona esto automáticamente, haciendo el campo `FechaFin` requerido y visible según el estado seleccionado.
- **Backend:** Se añade una validación en `SuscripcionService` como segunda capa de seguridad para garantizar la integridad de los datos.
### 🐛 Corrección de Errores
- **Reporte de Distribución:** Se corrigió un bug en la generación del PDF donde la columna de fecha no mostraba la "Fecha de Baja" para las suscripciones finalizadas. Ahora se muestra la fecha correcta según la sección (Altas o Bajas).
- **Errores de Compilación y Dependencias:** Se solucionaron varios errores de compilación en el backend, principalmente relacionados con la falta de registro de los nuevos repositorios (`ILoteDeEnvioRepository`, `IEmailLogService`, etc.) en el contenedor de inyección de dependencias (`Program.cs`).
- **Errores de Tipado en Frontend:** Se corrigieron múltiples errores de TypeScript en `CierreYProcesosPage` debidos a la inconsistencia entre `PascalCase` (C#) y `camelCase` (JSON/TypeScript), asegurando un mapeo correcto de los datos de la API.
2025-08-11 11:14:03 -03:00
var viewModel = new DistribucionSuscripcionesViewModel ( altas ? ? Enumerable . Empty < DistribucionSuscripcionDto > ( ) , bajas ? ? Enumerable . Empty < DistribucionSuscripcionDto > ( ) )
Feat: Implementa Reporte de Distribución de Suscripciones y Refactoriza Gestión de Ajustes
Se introduce una nueva funcionalidad de reporte crucial para la logística y se realiza una refactorización mayor del sistema de ajustes para garantizar la correcta imputación contable.
### ✨ Nuevas Características
- **Nuevo Reporte de Distribución de Suscripciones (RR011):**
- Se añade un nuevo reporte en PDF que genera un listado de todas las suscripciones activas en un rango de fechas.
- El reporte está diseñado para el equipo de reparto, incluyendo datos clave como nombre del suscriptor, dirección, teléfono, días de entrega y observaciones.
- Se implementa el endpoint, la lógica de servicio, la consulta a la base de datos y el template de QuestPDF en el backend.
- Se crea la página correspondiente en el frontend (React) con su selector de fechas y se añade la ruta y el enlace en el menú de reportes.
### 🔄 Refactorización Mayor
- **Asociación de Ajustes a Empresas:**
- Se refactoriza por completo la entidad `Ajuste` para incluir una referencia obligatoria a una `IdEmpresa`.
- **Motivo:** Corregir un error crítico en la lógica de negocio donde los ajustes de un suscriptor se aplicaban a la primera factura generada, sin importar a qué empresa correspondía el ajuste.
- Se provee un script de migración SQL para actualizar el esquema de la base de datos (`susc_Ajustes`).
- Se actualizan todos los DTOs, repositorios y servicios (backend) para manejar la nueva relación.
- Se modifica el `FacturacionService` para que ahora aplique los ajustes pendientes correspondientes a cada empresa dentro de su bucle de facturación.
- Se actualiza el formulario de creación/edición de ajustes en el frontend (React) para incluir un selector de empresa obligatorio.
### ⚡️ Optimizaciones de Rendimiento
- **Solución de N+1 Queries:**
- Se optimiza el método `ObtenerTodos` en `SuscriptorService` para obtener todas las formas de pago en una única consulta en lugar de una por cada suscriptor.
- Se optimiza el método `ObtenerAjustesPorSuscriptor` en `AjusteService` para obtener todos los nombres de usuarios y empresas en dos consultas masivas, en lugar de N consultas individuales.
- Se añade el método `GetByIdsAsync` al `IUsuarioRepository` y su implementación para soportar esta optimización.
### 🐛 Corrección de Errores
- Se corrigen múltiples errores en el script de migración de base de datos (uso de `GO` dentro de transacciones, error de "columna inválida").
- Se soluciona un error de SQL (`INSERT` statement) en `AjusteRepository` que impedía la creación de nuevos ajustes.
- Se corrige un bug en el `AjusteService` que causaba que el nombre de la empresa apareciera como "N/A" en la UI debido a un mapeo incompleto en el método optimizado.
- Se corrige la lógica de generación de emails en `FacturacionService` para mostrar correctamente el nombre de la empresa en cada sección del resumen de cuenta.
2025-08-09 17:39:21 -03:00
{
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." ) ;
}
}
2025-05-27 11:21:00 -03:00
}
}