diff --git a/Backend/GestionIntegral.Api/Controllers/Reportes/ReportesController.cs b/Backend/GestionIntegral.Api/Controllers/Reportes/ReportesController.cs index 150411d..0c32111 100644 --- a/Backend/GestionIntegral.Api/Controllers/Reportes/ReportesController.cs +++ b/Backend/GestionIntegral.Api/Controllers/Reportes/ReportesController.cs @@ -111,7 +111,7 @@ namespace GestionIntegral.Api.Controllers { LocalReport report = new LocalReport(); string rdlcPath = consolidado ? "Controllers/Reportes/RDLC/ReporteExistenciaPapelConsolidado.rdlc" : "Controllers/Reportes/RDLC/ReporteExistenciaPapel.rdlc"; - + using (var fs = new FileStream(rdlcPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) { report.LoadReportDefinition(fs); @@ -121,24 +121,28 @@ namespace GestionIntegral.Api.Controllers var parameters = new List(); parameters.Add(new ReportParameter("FechaDesde", fechaDesde.ToString("dd/MM/yyyy"))); parameters.Add(new ReportParameter("FechaHasta", fechaHasta.ToString("dd/MM/yyyy"))); - + string nombrePlantaParam = "Consolidado"; if (!consolidado && idPlanta.HasValue) { var planta = await _plantaRepository.GetByIdAsync(idPlanta.Value); nombrePlantaParam = planta?.Nombre ?? "N/A"; - } else if (consolidado) { - // Para el consolidado, el RDLC ReporteExistenciaPapelConsolidado.txt NO espera NomPlanta } - else { // No consolidado pero idPlanta es NULL (aunque el servicio ya valida esto) + else if (consolidado) + { + // Para el consolidado, el RDLC ReporteExistenciaPapelConsolidado.txt NO espera NomPlanta + } + else + { // No consolidado pero idPlanta es NULL (aunque el servicio ya valida esto) nombrePlantaParam = "N/A"; } // Solo añadir NomPlanta si NO es consolidado, porque el RDLC consolidado no lo tiene. - if (!consolidado) { - parameters.Add(new ReportParameter("NomPlanta", nombrePlantaParam)); + if (!consolidado) + { + parameters.Add(new ReportParameter("NomPlanta", nombrePlantaParam)); } - + report.SetParameters(parameters); byte[] pdfBytes = report.Render("PDF"); @@ -165,7 +169,7 @@ namespace GestionIntegral.Api.Controllers var (data, error) = await _reportesService.ObtenerMovimientoBobinasAsync(fechaDesde, fechaHasta, idPlanta); // <--- CORREGIDO if (error != null) return BadRequest(new { message = error }); - + return Ok(data); } @@ -180,7 +184,7 @@ namespace GestionIntegral.Api.Controllers [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 }); @@ -203,7 +207,7 @@ namespace GestionIntegral.Api.Controllers parameters.Add(new ReportParameter("FechaDesde", fechaDesde.ToString("dd/MM/yyyy"))); parameters.Add(new ReportParameter("FechaHasta", fechaHasta.ToString("dd/MM/yyyy"))); parameters.Add(new ReportParameter("NomPlanta", planta?.Nombre ?? "N/A")); - + report.SetParameters(parameters); byte[] pdfBytes = report.Render("PDF"); @@ -233,10 +237,10 @@ namespace GestionIntegral.Api.Controllers var (detalle, totales, error) = await _reportesService.ObtenerMovimientoBobinasPorEstadoAsync(fechaDesde, fechaHasta, idPlanta); if (error != null) return BadRequest(new { message = error }); - + if ((detalle == null || !detalle.Any()) && (totales == null || !totales.Any())) { - return NotFound(new { message = "No hay datos para el reporte de movimiento de bobinas por estado." }); + return NotFound(new { message = "No hay datos para el reporte de movimiento de bobinas por estado." }); } var response = new MovimientoBobinasPorEstadoResponseDto @@ -244,7 +248,7 @@ namespace GestionIntegral.Api.Controllers Detalle = detalle ?? Enumerable.Empty(), Totales = totales ?? Enumerable.Empty() }; - + return Ok(response); } @@ -265,9 +269,9 @@ namespace GestionIntegral.Api.Controllers if (error != null) return BadRequest(new { message = error }); if ((detalle == null || !detalle.Any()) && (totales == null || !totales.Any())) { - return NotFound(new { message = "No hay datos para generar el PDF del movimiento de bobinas por estado con los parámetros seleccionados." }); + return NotFound(new { message = "No hay datos para generar el PDF del movimiento de bobinas por estado con los parámetros seleccionados." }); } - + try { LocalReport report = new LocalReport(); @@ -275,15 +279,15 @@ namespace GestionIntegral.Api.Controllers { report.LoadReportDefinition(fs); } - report.DataSources.Add(new ReportDataSource("DSMovimientoBobinasEstado", detalle ?? new List())); - report.DataSources.Add(new ReportDataSource("DSMovimientoBobinasEstadoTotales", totales ?? new List())); + report.DataSources.Add(new ReportDataSource("DSMovimientoBobinasEstado", detalle ?? new List())); + report.DataSources.Add(new ReportDataSource("DSMovimientoBobinasEstadoTotales", totales ?? new List())); var planta = await _plantaRepository.GetByIdAsync(idPlanta); var parameters = new List(); parameters.Add(new ReportParameter("FechaDesde", fechaDesde.ToString("dd/MM/yyyy"))); parameters.Add(new ReportParameter("FechaHasta", fechaHasta.ToString("dd/MM/yyyy"))); parameters.Add(new ReportParameter("NomPlanta", planta?.Nombre ?? "N/A")); - + report.SetParameters(parameters); byte[] pdfBytes = report.Render("PDF"); @@ -305,7 +309,7 @@ namespace GestionIntegral.Api.Controllers [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task GetListadoDistribucionGeneral( [FromQuery] int idPublicacion, - [FromQuery] DateTime fechaDesde, + [FromQuery] DateTime fechaDesde, [FromQuery] DateTime fechaHasta) { if (!TienePermiso(PermisoVerListadoDistribucion)) return Forbid(); @@ -397,7 +401,7 @@ namespace GestionIntegral.Api.Controllers { return NotFound(new { message = "No hay datos para el listado de distribución de canillas." }); } - + var response = new ListadoDistribucionCanillasResponseDto { DetalleSimple = simple ?? Enumerable.Empty(), @@ -478,10 +482,10 @@ namespace GestionIntegral.Api.Controllers { return NotFound(new { message = "No hay datos para el listado de distribución de canillas con importe." }); } - + return Ok(data); } - + [HttpGet("listado-distribucion-canillas-importe/pdf")] [ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] @@ -491,7 +495,7 @@ namespace GestionIntegral.Api.Controllers [FromQuery] int idPublicacion, [FromQuery] DateTime fechaDesde, [FromQuery] DateTime fechaHasta, - [FromQuery] bool esAccionista) + [FromQuery] bool esAccionista) { if (!TienePermiso(PermisoVerListadoDistribucion)) return Forbid(); @@ -506,7 +510,7 @@ namespace GestionIntegral.Api.Controllers try { LocalReport report = new LocalReport(); - using (var fs = new FileStream("Controllers/Reportes/RDLC/ReporteListadoDistribucionCanImp.rdlc", FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) + using (var fs = new FileStream("Controllers/Reportes/RDLC/ReporteListadoDistribucionCanImp.rdlc", FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) { report.LoadReportDefinition(fs); } @@ -518,10 +522,10 @@ namespace GestionIntegral.Api.Controllers new ReportParameter("NomPubli", publicacion?.Nombre ?? "N/A"), new ReportParameter("FechaDesde", fechaDesde.ToString("dd/MM/yyyy")), new ReportParameter("FechaHasta", fechaHasta.ToString("dd/MM/yyyy")), - new ReportParameter("CanAcc", esAccionista ? "1" : "0") + new ReportParameter("CanAcc", esAccionista ? "1" : "0") }; report.SetParameters(parameters); - + byte[] pdfBytes = report.Render("PDF"); string tipoVendedor = esAccionista ? "Accionistas" : "Canillitas"; string fileName = $"ListadoDistCanImp_Pub{idPublicacion}_{tipoVendedor}_{fechaDesde:yyyyMMdd}_{fechaHasta:yyyyMMdd}.pdf"; @@ -565,7 +569,7 @@ namespace GestionIntegral.Api.Controllers report.LoadReportDefinition(fs); } report.DataSources.Add(new ReportDataSource("DSListadoDistribucion", data)); // Basado en el RDLC - report.SetParameters(new[] { + report.SetParameters(new[] { new ReportParameter("FechaDesde", fechaDesde.ToString("dd/MM/yyyy")), new ReportParameter("FechaHasta", fechaHasta.ToString("dd/MM/yyyy")) }); @@ -597,7 +601,7 @@ namespace GestionIntegral.Api.Controllers var (data, error) = await _reportesService.ObtenerVentaMensualSecretariaElPlataAsync(fechaDesde, fechaHasta); if (error != null) return BadRequest(new { message = error }); if (data == null || !data.Any()) return NotFound(new { message = "No hay datos para el reporte." }); - + try { LocalReport report = new LocalReport(); @@ -606,7 +610,7 @@ namespace GestionIntegral.Api.Controllers report.LoadReportDefinition(fs); } report.DataSources.Add(new ReportDataSource("DSListadoDistribucion", data)); // Basado en el RDLC - report.SetParameters(new[] { + report.SetParameters(new[] { new ReportParameter("FechaDesde", fechaDesde.ToString("dd/MM/yyyy")), new ReportParameter("FechaHasta", fechaHasta.ToString("dd/MM/yyyy")) }); @@ -630,7 +634,7 @@ namespace GestionIntegral.Api.Controllers if (data == null || !data.Any()) return NotFound(new { message = "No hay datos para el reporte de tirada/devolución." }); return Ok(data); } - + [HttpGet("venta-mensual-secretaria/tirada-devolucion/pdf")] public async Task GetVentaMensualSecretariaTirDevoPdf([FromQuery] DateTime fechaDesde, [FromQuery] DateTime fechaHasta) { @@ -638,7 +642,7 @@ namespace GestionIntegral.Api.Controllers var (data, error) = await _reportesService.ObtenerVentaMensualSecretariaTirDevoAsync(fechaDesde, fechaHasta); if (error != null) return BadRequest(new { message = error }); if (data == null || !data.Any()) return NotFound(new { message = "No hay datos para el reporte." }); - + try { LocalReport report = new LocalReport(); @@ -647,7 +651,7 @@ namespace GestionIntegral.Api.Controllers report.LoadReportDefinition(fs); } report.DataSources.Add(new ReportDataSource("DSListadoDistribucion", data)); // Basado en el RDLC - report.SetParameters(new[] { + report.SetParameters(new[] { new ReportParameter("FechaDesde", fechaDesde.ToString("dd/MM/yyyy")), new ReportParameter("FechaHasta", fechaHasta.ToString("dd/MM/yyyy")) }); @@ -667,12 +671,12 @@ namespace GestionIntegral.Api.Controllers { if (!TienePermiso(PermisoVerComprobanteLiquidacionCanilla)) return Forbid(); - var (canillas, canillasAcc, canillasAll, canillasFechaLiq, canillasAccFechaLiq, - ctrlDevolucionesRemitos, ctrlDevolucionesParaDistCan, ctrlDevolucionesOtrosDias, error) = + var (canillas, canillasAcc, canillasAll, canillasFechaLiq, canillasAccFechaLiq, + ctrlDevolucionesRemitos, ctrlDevolucionesParaDistCan, ctrlDevolucionesOtrosDias, error) = await _reportesService.ObtenerReporteDistribucionCanillasAsync(fecha, idEmpresa); if (error != null) return BadRequest(new { message = error }); - + // Una validación simple, podrías hacerla más granular si es necesario bool noHayDatos = (canillas == null || !canillas.Any()) && (canillasAcc == null || !canillasAcc.Any()) && @@ -695,10 +699,10 @@ namespace GestionIntegral.Api.Controllers ControlDevolucionesDetalle = ctrlDevolucionesParaDistCan ?? Enumerable.Empty(), ControlDevolucionesOtrosDias = ctrlDevolucionesOtrosDias ?? Enumerable.Empty() }; - + return Ok(response); } - + [HttpGet("distribucion-canillas/pdf")] public async Task GetReporteDistribucionCanillasPdf([FromQuery] DateTime fecha, [FromQuery] int idEmpresa, [FromQuery] bool soloTotales = false) { @@ -706,24 +710,24 @@ namespace GestionIntegral.Api.Controllers // CORRECCIÓN AQUÍ: Añadir la variable para el nuevo elemento de la tupla var ( - canillas, - canillasAcc, - canillasAll, - canillasFechaLiq, - canillasAccFechaLiq, + canillas, + canillasAcc, + canillasAll, + canillasFechaLiq, + canillasAccFechaLiq, ctrlDevolucionesRemitos, // Renombrado para claridad - ctrlDevolucionesParaDistCan, - _ , // Descartamos ctrlDevolucionesOtrosDias si no se usa aquí + ctrlDevolucionesParaDistCan, + _, // Descartamos ctrlDevolucionesOtrosDias si no se usa aquí error ) = await _reportesService.ObtenerReporteDistribucionCanillasAsync(fecha, idEmpresa); if (error != null) return BadRequest(new { message = error }); - + // La lógica de noHayDatosParaTotales y noHayDatosParaDetalle debería usar los nombres correctos bool noHayDatosParaTotales = (canillasAll == null || !canillasAll.Any()) && (ctrlDevolucionesRemitos == null || !ctrlDevolucionesRemitos.Any()) && // Usar ctrlDevolucionesRemitos o ctrlDevolucionesParaDistCan según el RDLC (ctrlDevolucionesParaDistCan == null || !ctrlDevolucionesParaDistCan.Any()); - + bool noHayDatosParaDetalle = noHayDatosParaTotales && (canillas == null || !canillas.Any()) && (canillasAcc == null || !canillasAcc.Any()) && @@ -739,12 +743,12 @@ namespace GestionIntegral.Api.Controllers { LocalReport report = new LocalReport(); string rdlcPath = soloTotales ? "Controllers/Reportes/RDLC/ReporteDistribucionCanillasTotales.rdlc" : "Controllers/Reportes/RDLC/ReporteDistribucionCanillas.rdlc"; - + using (var fs = new FileStream(rdlcPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) { report.LoadReportDefinition(fs); } - + report.DataSources.Add(new ReportDataSource("DSListadoDistribucionCan", canillas ?? new List())); report.DataSources.Add(new ReportDataSource("DSListadoDistribucionCanAcc", canillasAcc ?? new List())); report.DataSources.Add(new ReportDataSource("DSListadoDistribucionCanALL", canillasAll ?? new List())); @@ -796,7 +800,7 @@ namespace GestionIntegral.Api.Controllers ) = await _reportesService.ObtenerReporteDistribucionCanillasAsync(fecha, idEmpresa); // Reutilizamos este método if (error != null) return BadRequest(new { message = error }); - + // Adaptar la condición de "no hay datos" a los DataSets que realmente usa este reporte if ((ctrlDevolucionesParaDistCanData == null || !ctrlDevolucionesParaDistCanData.Any()) && (ctrlDevolucionesOtrosDiasData == null || !ctrlDevolucionesOtrosDiasData.Any()) && @@ -811,18 +815,18 @@ namespace GestionIntegral.Api.Controllers DevolucionesOtrosDias = ctrlDevolucionesOtrosDiasData ?? Enumerable.Empty(), RemitosIngresados = ctrlDevolucionesRemitosData ?? Enumerable.Empty() }; - + return Ok(response); } [HttpGet("control-devoluciones/pdf")] public async Task GetReporteControlDevolucionesPdf([FromQuery] DateTime fecha, [FromQuery] int idEmpresa) { - if (!TienePermiso(PermisoVerControlDevoluciones)) return Forbid(); - + if (!TienePermiso(PermisoVerControlDevoluciones)) return Forbid(); + // La tupla ahora devuelve un campo más var ( - _, _, _, _, _, + _, _, _, _, _, ctrlDevolucionesRemitosData, // Para DSObtenerCtrlDevoluciones ctrlDevolucionesParaDistCanData, // Para DSCtrlDevoluciones ctrlDevolucionesOtrosDiasData, // <--- NUEVO: Para DSCtrlDevolucionesOtrosDias @@ -830,16 +834,16 @@ namespace GestionIntegral.Api.Controllers ) = await _reportesService.ObtenerReporteDistribucionCanillasAsync(fecha, idEmpresa); if (error != null) return BadRequest(new { message = error }); - + // Ajustamos la condición para verificar los DataSets que realmente usa este reporte específico - if ((ctrlDevolucionesRemitosData == null || !ctrlDevolucionesRemitosData.Any()) && + if ((ctrlDevolucionesRemitosData == null || !ctrlDevolucionesRemitosData.Any()) && (ctrlDevolucionesParaDistCanData == null || !ctrlDevolucionesParaDistCanData.Any()) && (ctrlDevolucionesOtrosDiasData == null || !ctrlDevolucionesOtrosDiasData.Any()) // <--- AÑADIDO A LA VERIFICACIÓN ) { return NotFound(new { message = "No hay datos para generar el PDF para control de devoluciones." }); } - + try { LocalReport report = new LocalReport(); @@ -847,13 +851,13 @@ namespace GestionIntegral.Api.Controllers { report.LoadReportDefinition(fs); } - + // DataSet que usa SP_DistCanillasCantidadEntradaSalida report.DataSources.Add(new ReportDataSource("DSCtrlDevoluciones", ctrlDevolucionesParaDistCanData ?? new List())); - + // DataSet que usa SP_DistCanillasCantidadEntradaSalidaOtrosDias report.DataSources.Add(new ReportDataSource("DSCtrlDevolucionesOtrosDias", ctrlDevolucionesOtrosDiasData ?? new List())); // <--- CORREGIDO - + // DataSet que usa SP_ObtenerCtrlDevoluciones report.DataSources.Add(new ReportDataSource("DSObtenerCtrlDevoluciones", ctrlDevolucionesRemitosData ?? new List())); @@ -891,15 +895,15 @@ namespace GestionIntegral.Api.Controllers { if (!TienePermiso(PermisoVerBalanceCuentas)) return Forbid(); - var (entradasSalidas, debitosCreditos, pagos, saldos, error) = + var (entradasSalidas, debitosCreditos, pagos, saldos, error) = await _reportesService.ObtenerReporteCuentasDistribuidorAsync(idDistribuidor, idEmpresa, fechaDesde, fechaHasta); if (error != null) return BadRequest(new { message = error }); if (!entradasSalidas.Any() && !debitosCreditos.Any() && !pagos.Any() && !saldos.Any()) { - return NotFound(new { message = "No hay datos para generar el reporte de cuenta del distribuidor." }); + return NotFound(new { message = "No hay datos para generar el reporte de cuenta del distribuidor." }); } - + var distribuidor = await _distribuidorRepository.GetByIdAsync(idDistribuidor); var empresa = await _empresaRepository.GetByIdAsync(idEmpresa); @@ -912,7 +916,7 @@ namespace GestionIntegral.Api.Controllers NombreDistribuidor = distribuidor.Distribuidor?.Nombre, NombreEmpresa = empresa?.Nombre }; - + return Ok(response); } @@ -925,13 +929,13 @@ namespace GestionIntegral.Api.Controllers { if (!TienePermiso(PermisoVerBalanceCuentas)) return Forbid(); - var (entradasSalidas, debitosCreditos, pagos, saldos, error) = + var (entradasSalidas, debitosCreditos, pagos, saldos, error) = await _reportesService.ObtenerReporteCuentasDistribuidorAsync(idDistribuidor, idEmpresa, fechaDesde, fechaHasta); if (error != null) return BadRequest(new { message = error }); if (!entradasSalidas.Any() && !debitosCreditos.Any() && !pagos.Any() && !saldos.Any()) { - return NotFound(new { message = "No hay datos para generar el reporte de cuenta del distribuidor." }); + return NotFound(new { message = "No hay datos para generar el reporte de cuenta del distribuidor." }); } try @@ -946,7 +950,7 @@ namespace GestionIntegral.Api.Controllers report.DataSources.Add(new ReportDataSource("DSDistribuidoresDebCred", debitosCreditos ?? new List())); report.DataSources.Add(new ReportDataSource("DSDistribuidoresPagos", pagos ?? new List())); report.DataSources.Add(new ReportDataSource("DSDistribuidoresSaldos", saldos ?? new List())); - + var parameters = new List { new ReportParameter("FechaDesde", fechaDesde.ToString("dd/MM/yyyy")), @@ -973,9 +977,9 @@ namespace GestionIntegral.Api.Controllers [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task GetReporteTiradasPublicacionesSeccionesData( [FromQuery] int idPublicacion, - [FromQuery] DateTime fechaDesde, - [FromQuery] DateTime fechaHasta, - [FromQuery] int? idPlanta, + [FromQuery] DateTime fechaDesde, + [FromQuery] DateTime fechaHasta, + [FromQuery] int? idPlanta, [FromQuery] bool consolidado = false) { if (!TienePermiso(PermisoVerReporteTiradas)) return Forbid(); @@ -992,7 +996,7 @@ namespace GestionIntegral.Api.Controllers if (!idPlanta.HasValue) return BadRequest(new { message = "Se requiere IdPlanta para reportes no consolidados." }); (data, errorMsg) = await _reportesService.ObtenerTiradasPublicacionesSeccionesAsync(idPublicacion, fechaDesde, fechaHasta, idPlanta.Value); } - + if (errorMsg != null) return BadRequest(new { message = errorMsg }); if (data == null || !data.Any()) return NotFound(new { message = "No hay datos para generar el reporte." }); @@ -1002,9 +1006,9 @@ namespace GestionIntegral.Api.Controllers [HttpGet("tiradas-publicaciones-secciones/pdf")] public async Task GetReporteTiradasPublicacionesSeccionesPdf( [FromQuery] int idPublicacion, - [FromQuery] DateTime fechaDesde, - [FromQuery] DateTime fechaHasta, - [FromQuery] int? idPlanta, + [FromQuery] DateTime fechaDesde, + [FromQuery] DateTime fechaHasta, + [FromQuery] int? idPlanta, [FromQuery] bool consolidado = false) { if (!TienePermiso(PermisoVerReporteTiradas)) return Forbid(); @@ -1021,15 +1025,15 @@ namespace GestionIntegral.Api.Controllers if (!idPlanta.HasValue) return BadRequest(new { message = "Se requiere IdPlanta para reportes no consolidados." }); (data, errorMsg) = await _reportesService.ObtenerTiradasPublicacionesSeccionesAsync(idPublicacion, fechaDesde, fechaHasta, idPlanta.Value); } - + if (errorMsg != null) return BadRequest(new { message = errorMsg }); if (data == null || !data.Any()) return NotFound(new { message = "No hay datos para generar el reporte." }); try { LocalReport report = new LocalReport(); - string rdlcPath = consolidado ? - "Controllers/Reportes/RDLC/ReporteTiradasPublicacionesSeccionesConsolidado.rdlc" : + string rdlcPath = consolidado ? + "Controllers/Reportes/RDLC/ReporteTiradasPublicacionesSeccionesConsolidado.rdlc" : "Controllers/Reportes/RDLC/ReporteTiradasPublicacionesSecciones.rdlc"; using (var fs = new FileStream(rdlcPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) @@ -1041,7 +1045,7 @@ namespace GestionIntegral.Api.Controllers var publicacion = await _publicacionRepository.GetByIdSimpleAsync(idPublicacion); var parameters = new List { - new ReportParameter("Mes", fechaDesde.ToString("MMMM yyyy")), + new ReportParameter("Mes", fechaDesde.ToString("MMMM yyyy")), new ReportParameter("NomPubli", publicacion?.Nombre ?? "N/A") }; if (!consolidado && idPlanta.HasValue) @@ -1049,7 +1053,7 @@ namespace GestionIntegral.Api.Controllers var planta = await _plantaRepository.GetByIdAsync(idPlanta.Value); parameters.Add(new ReportParameter("NomPlanta", planta?.Nombre ?? "N/A")); } - + report.SetParameters(parameters); byte[] pdfBytes = report.Render("PDF"); @@ -1071,7 +1075,7 @@ namespace GestionIntegral.Api.Controllers [FromQuery] int? idPlanta, [FromQuery] bool consolidado = false) { - if (!TienePermiso(PermisoVerReporteConsumoBobinas)) return Forbid(); + if (!TienePermiso(PermisoVerReporteConsumoBobinas)) return Forbid(); IEnumerable data; string? errorMsg; @@ -1085,10 +1089,10 @@ namespace GestionIntegral.Api.Controllers if (!idPlanta.HasValue) return BadRequest(new { message = "Se requiere IdPlanta para reportes no consolidados." }); (data, errorMsg) = await _reportesService.ObtenerConsumoBobinasPorSeccionAsync(fechaDesde, fechaHasta, idPlanta.Value); } - + if (errorMsg != null) return BadRequest(new { message = errorMsg }); if (data == null || !data.Any()) return NotFound(new { message = "No hay datos para generar el reporte." }); - + return Ok(data); } @@ -1099,7 +1103,7 @@ namespace GestionIntegral.Api.Controllers [FromQuery] int? idPlanta, [FromQuery] bool consolidado = false) { - if (!TienePermiso(PermisoVerReporteConsumoBobinas)) return Forbid(); + if (!TienePermiso(PermisoVerReporteConsumoBobinas)) return Forbid(); IEnumerable data; string? errorMsg; @@ -1113,28 +1117,28 @@ namespace GestionIntegral.Api.Controllers if (!idPlanta.HasValue) return BadRequest(new { message = "Se requiere IdPlanta para reportes no consolidados." }); (data, errorMsg) = await _reportesService.ObtenerConsumoBobinasPorSeccionAsync(fechaDesde, fechaHasta, idPlanta.Value); } - + if (errorMsg != null) return BadRequest(new { message = errorMsg }); if (data == null || !data.Any()) return NotFound(new { message = "No hay datos para generar el reporte." }); try { LocalReport report = new LocalReport(); - string rdlcPath = consolidado ? - "Controllers/Reportes/RDLC/ReporteConsumoBobinasSeccionConsolidado.rdlc" : - "Controllers/Reportes/RDLC/ReporteConsumoBobinasSeccion.rdlc"; + string rdlcPath = consolidado ? + "Controllers/Reportes/RDLC/ReporteConsumoBobinasSeccionConsolidado.rdlc" : + "Controllers/Reportes/RDLC/ReporteConsumoBobinasSeccion.rdlc"; using (var fs = new FileStream(rdlcPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) { report.LoadReportDefinition(fs); } report.DataSources.Add(new ReportDataSource("DSConsumoBobinasSeccion", data)); - + var parameters = new List { new ReportParameter("FechaDesde", fechaDesde.ToString("dd/MM/yyyy")), new ReportParameter("FechaHasta", fechaHasta.ToString("dd/MM/yyyy")) }; - if (!consolidado && idPlanta.HasValue) + if (!consolidado && idPlanta.HasValue) { var planta = await _plantaRepository.GetByIdAsync(idPlanta.Value); parameters.Add(new ReportParameter("NomPlanta", planta?.Nombre ?? "N/A")); @@ -1158,10 +1162,10 @@ namespace GestionIntegral.Api.Controllers [FromQuery] DateTime fechaDesde, [FromQuery] DateTime fechaHasta) { - if (!TienePermiso(PermisoVerReporteConsumoBobinas)) return Forbid(); - + if (!TienePermiso(PermisoVerReporteConsumoBobinas)) return Forbid(); + var (data, error) = await _reportesService.ObtenerConsumoBobinasPorPublicacionAsync(fechaDesde, fechaHasta); - + if (error != null) return BadRequest(new { message = error }); if (data == null || !data.Any()) return NotFound(new { message = "No hay datos para generar el reporte." }); @@ -1173,10 +1177,10 @@ namespace GestionIntegral.Api.Controllers [FromQuery] DateTime fechaDesde, [FromQuery] DateTime fechaHasta) { - if (!TienePermiso(PermisoVerReporteConsumoBobinas)) return Forbid(); - + if (!TienePermiso(PermisoVerReporteConsumoBobinas)) return Forbid(); + var (data, error) = await _reportesService.ObtenerConsumoBobinasPorPublicacionAsync(fechaDesde, fechaHasta); - + if (error != null) return BadRequest(new { message = error }); if (data == null || !data.Any()) return NotFound(new { message = "No hay datos para generar el reporte." }); @@ -1188,7 +1192,7 @@ namespace GestionIntegral.Api.Controllers report.LoadReportDefinition(fs); } report.DataSources.Add(new ReportDataSource("DSConsumoBobinasPublicacion", data)); - + var parameters = new List { new ReportParameter("FechaDesde", fechaDesde.ToString("dd/MM/yyyy")), @@ -1215,7 +1219,7 @@ namespace GestionIntegral.Api.Controllers [FromQuery] int? idPlanta, [FromQuery] bool consolidado = false) { - if (!TienePermiso(PermisoVerReporteConsumoBobinas)) return Forbid(); + if (!TienePermiso(PermisoVerReporteConsumoBobinas)) return Forbid(); IEnumerable data; string? errorMsg; @@ -1229,7 +1233,7 @@ namespace GestionIntegral.Api.Controllers if (!idPlanta.HasValue) return BadRequest(new { message = "Se requiere IdPlanta para reportes no consolidados." }); (data, errorMsg) = await _reportesService.ObtenerComparativaConsumoBobinasAsync(fechaInicioMesA, fechaFinMesA, fechaInicioMesB, fechaFinMesB, idPlanta.Value); } - + if (errorMsg != null) return BadRequest(new { message = errorMsg }); if (data == null || !data.Any()) return NotFound(new { message = "No hay datos para generar el reporte." }); @@ -1243,7 +1247,7 @@ namespace GestionIntegral.Api.Controllers [FromQuery] int? idPlanta, [FromQuery] bool consolidado = false) { - if (!TienePermiso(PermisoVerReporteConsumoBobinas)) return Forbid(); + if (!TienePermiso(PermisoVerReporteConsumoBobinas)) return Forbid(); IEnumerable data; string? errorMsg; @@ -1257,15 +1261,15 @@ namespace GestionIntegral.Api.Controllers if (!idPlanta.HasValue) return BadRequest(new { message = "Se requiere IdPlanta para reportes no consolidados." }); (data, errorMsg) = await _reportesService.ObtenerComparativaConsumoBobinasAsync(fechaInicioMesA, fechaFinMesA, fechaInicioMesB, fechaFinMesB, idPlanta.Value); } - + if (errorMsg != null) return BadRequest(new { message = errorMsg }); if (data == null || !data.Any()) return NotFound(new { message = "No hay datos para generar el reporte." }); try { LocalReport report = new LocalReport(); - string rdlcPath = consolidado ? - "Controllers/Reportes/RDLC/ReporteConsumoBobinasMesesConsolidado.rdlc" : + string rdlcPath = consolidado ? + "Controllers/Reportes/RDLC/ReporteConsumoBobinasMesesConsolidado.rdlc" : "Controllers/Reportes/RDLC/ReporteConsumoBobinasMeses.rdlc"; using (var fs = new FileStream(rdlcPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) @@ -1273,13 +1277,13 @@ namespace GestionIntegral.Api.Controllers report.LoadReportDefinition(fs); } report.DataSources.Add(new ReportDataSource("DSConsumoBobinasMeses", data)); - + var parameters = new List { new ReportParameter("MesA", fechaInicioMesA.ToString("MMMM yyyy")), new ReportParameter("MesB", fechaInicioMesB.ToString("MMMM yyyy")) }; - if (!consolidado && idPlanta.HasValue) + if (!consolidado && idPlanta.HasValue) { var planta = await _plantaRepository.GetByIdAsync(idPlanta.Value); parameters.Add(new ReportParameter("NomPlanta", planta?.Nombre ?? "N/A")); @@ -1292,5 +1296,93 @@ namespace GestionIntegral.Api.Controllers } catch (Exception ex) { _logger.LogError(ex, "Error PDF Comparativa Consumo Bobinas."); return StatusCode(500, "Error interno."); } } + + // GET: api/reportes/listado-distribucion-distribuidores + [HttpGet("listado-distribucion-distribuidores")] + [ProducesResponseType(typeof(ListadoDistribucionDistribuidoresResponseDto), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task GetListadoDistribucionDistribuidores( + [FromQuery] int idDistribuidor, + [FromQuery] int idPublicacion, + [FromQuery] DateTime fechaDesde, + [FromQuery] DateTime fechaHasta) + { + if (!TienePermiso(PermisoVerListadoDistribucion)) return Forbid(); // RR002 + + (IEnumerable simple, IEnumerable promedios, string? error) result = + await _reportesService.ObtenerListadoDistribucionDistribuidoresAsync(idDistribuidor, idPublicacion, fechaDesde, fechaHasta); + + if (result.error != null) return BadRequest(new { message = result.error }); + if ((result.simple == null || !result.simple.Any()) && (result.promedios == null || !result.promedios.Any())) + { + return NotFound(new { message = "No hay datos para el listado de distribución de distribuidores." }); + } + + var response = new ListadoDistribucionDistribuidoresResponseDto + { + DetalleSimple = result.simple ?? Enumerable.Empty(), + PromediosPorDia = result.promedios ?? Enumerable.Empty() + }; + + return Ok(response); + } + + [HttpGet("listado-distribucion-distribuidores/pdf")] + [ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task GetListadoDistribucionDistribuidoresPdf( + [FromQuery] int idDistribuidor, + [FromQuery] int idPublicacion, + [FromQuery] DateTime fechaDesde, + [FromQuery] DateTime fechaHasta) + { + if (!TienePermiso(PermisoVerListadoDistribucion)) return Forbid(); + + var (simple, promedios, error) = await _reportesService.ObtenerListadoDistribucionDistribuidoresAsync(idDistribuidor, idPublicacion, fechaDesde, fechaHasta); + + if (error != null) return BadRequest(new { message = error }); + if ((simple == null || !simple.Any()) && (promedios == null || !promedios.Any())) + { + return NotFound(new { message = "No hay datos para generar el PDF." }); + } + + try + { + LocalReport report = new LocalReport(); + // USAREMOS EL MISMO RDLC QUE PARA CANILLITAS, YA QUE TIENE LA MISMA ESTRUCTURA DE DATASOURCES + using (var fs = new FileStream("Controllers/Reportes/RDLC/ReporteListadoDistribucion.rdlc", FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) + { + report.LoadReportDefinition(fs); + } + // Nombres de DataSources deben coincidir con los del RDLC + report.DataSources.Add(new ReportDataSource("DSListadoDistribucion", simple ?? new List())); + report.DataSources.Add(new ReportDataSource("DSListadoDistribucionAgDias", promedios ?? new List())); + + var publicacionData = await _publicacionRepository.GetByIdAsync(idPublicacion); + var distribuidorData = await _distribuidorRepository.GetByIdAsync(idDistribuidor); + + var parameters = new List + { + new ReportParameter("NomPubli", publicacionData.Publicacion?.Nombre ?? "N/A"), + new ReportParameter("NomDist", distribuidorData.Distribuidor?.Nombre ?? "N/A"), // Parámetro para el RDLC + new ReportParameter("FechaDesde", fechaDesde.ToString("dd/MM/yyyy")), + new ReportParameter("FechaHasta", fechaHasta.ToString("dd/MM/yyyy")) + }; + report.SetParameters(parameters); + + byte[] pdfBytes = report.Render("PDF"); + 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."); + } + } } } \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Data/Repositories/Reportes/IReportesRepository.cs b/Backend/GestionIntegral.Api/Data/Repositories/Reportes/IReportesRepository.cs index ee17c87..12b3d30 100644 --- a/Backend/GestionIntegral.Api/Data/Repositories/Reportes/IReportesRepository.cs +++ b/Backend/GestionIntegral.Api/Data/Repositories/Reportes/IReportesRepository.cs @@ -11,8 +11,6 @@ namespace GestionIntegral.Api.Data.Repositories.Reportes Task> GetMovimientoBobinasAsync(DateTime fechaInicio, int diasPeriodo, int idPlanta); Task> GetMovimientoBobinasEstadoDetalleAsync(DateTime fechaInicio, DateTime fechaFin, int idPlanta); Task> GetMovimientoBobinasEstadoTotalesAsync(DateTime fechaInicio, DateTime fechaFin, int idPlanta); - - // --- MÉTODOS AÑADIDOS AQUÍ --- Task> GetListadoDistribucionGeneralResumenAsync(int idPublicacion, DateTime fechaDesde, DateTime fechaHasta); Task> GetListadoDistribucionGeneralPromedioDiaAsync(int idPublicacion, DateTime fechaDesde, DateTime fechaHasta); Task> GetListadoDistribucionCanillasSimpleAsync(int idPublicacion, DateTime fechaDesde, DateTime fechaHasta); @@ -40,5 +38,8 @@ namespace GestionIntegral.Api.Data.Repositories.Reportes Task> GetBalanceCuentDistDebCredEmpresaAsync(int idDistribuidor, int idEmpresa, DateTime fechaDesde, DateTime fechaHasta); Task> GetBalanceCuentDistPagosEmpresaAsync(int idDistribuidor, int idEmpresa, DateTime fechaDesde, DateTime fechaHasta); Task> GetBalanceCuentSaldosEmpresasAsync(string destino, int idDestino, int idEmpresa); + Task> GetListadoDistribucionDistSimpleAsync(int idDistribuidor, int idPublicacion, DateTime fechaDesde, DateTime fechaHasta); + Task> GetListadoDistribucionDistPromedioDiaAsync(int idDistribuidor, int idPublicacion, DateTime fechaDesde, DateTime fechaHasta); + Task<(IEnumerable Simple, IEnumerable Promedios, string? Error)> ObtenerListadoDistribucionDistribuidoresAsync(int idDistribuidor, int idPublicacion, DateTime fechaDesde, DateTime fechaHasta); } } \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Data/Repositories/Reportes/ReportesRepository.cs b/Backend/GestionIntegral.Api/Data/Repositories/Reportes/ReportesRepository.cs index e3d91c6..b8d4ee1 100644 --- a/Backend/GestionIntegral.Api/Data/Repositories/Reportes/ReportesRepository.cs +++ b/Backend/GestionIntegral.Api/Data/Repositories/Reportes/ReportesRepository.cs @@ -132,7 +132,6 @@ namespace GestionIntegral.Api.Data.Repositories.Reportes } } - // Implementaciones que faltaban public async Task> GetListadoDistribucionGeneralResumenAsync(int idPublicacion, DateTime fechaDesde, DateTime fechaHasta) { const string spName = "dbo.SP_DistObtenerResumenMensual"; @@ -419,5 +418,66 @@ namespace GestionIntegral.Api.Data.Repositories.Reportes try { using var connection = _dbConnectionFactory.CreateConnection(); return await connection.QueryAsync(spName, parameters, commandType: CommandType.StoredProcedure); } catch (Exception ex) { _logger.LogError(ex, "Error SP {SPName}", spName); return Enumerable.Empty(); } } + + public async Task> GetListadoDistribucionDistSimpleAsync(int idDistribuidor, int idPublicacion, DateTime fechaDesde, DateTime fechaHasta) + { + const string spName = "dbo.SP_CantidadEntradaSalida"; + var parameters = new DynamicParameters(); + parameters.Add("@idDistribuidor", idDistribuidor, DbType.Int32); + parameters.Add("@idPublicacion", idPublicacion, DbType.Int32); + parameters.Add("@fechaDesde", fechaDesde, DbType.DateTime); + parameters.Add("@fechaHasta", fechaHasta, DbType.DateTime); + try + { + using var connection = _dbConnectionFactory.CreateConnection(); // <--- CORREGIDO AQUÍ + return await connection.QueryAsync(spName, parameters, commandType: CommandType.StoredProcedure); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error al ejecutar SP {SPName} para Listado Distribucion Distribuidores (Simple). Params: Dist={idDistribuidor}, Pub={idPublicacion}, Desde={fechaDesde}, Hasta={fechaHasta}", spName, idDistribuidor, idPublicacion, fechaDesde, fechaHasta); + return Enumerable.Empty(); + } + } + + public async Task> GetListadoDistribucionDistPromedioDiaAsync(int idDistribuidor, int idPublicacion, DateTime fechaDesde, DateTime fechaHasta) + { + const string spName = "dbo.SP_CantidadEntradaSalidaCPromAgDia"; + var parameters = new DynamicParameters(); + parameters.Add("@idDistribuidor", idDistribuidor, DbType.Int32); + parameters.Add("@idPublicacion", idPublicacion, DbType.Int32); + parameters.Add("@fechaDesde", fechaDesde, DbType.DateTime); + parameters.Add("@fechaHasta", fechaHasta, DbType.DateTime); + try + { + using var connection = _dbConnectionFactory.CreateConnection(); // <--- CORREGIDO AQUÍ + return await connection.QueryAsync(spName, parameters, commandType: CommandType.StoredProcedure); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error al ejecutar SP {SPName} para Listado Distribucion Distribuidores (Promedios). Params: Dist={idDistribuidor}, Pub={idPublicacion}, Desde={fechaDesde}, Hasta={fechaHasta}", spName, idDistribuidor, idPublicacion, fechaDesde, fechaHasta); + return Enumerable.Empty(); + } + } + + public async Task<(IEnumerable Simple, IEnumerable Promedios, string? Error)> ObtenerListadoDistribucionDistribuidoresAsync(int idDistribuidor, int idPublicacion, DateTime fechaDesde, DateTime fechaHasta) + { + if (fechaDesde > fechaHasta) + return (Enumerable.Empty(), Enumerable.Empty(), "La fecha 'Desde' no puede ser mayor que la fecha 'Hasta'."); + + try + { + var simpleDataTask = this.GetListadoDistribucionDistSimpleAsync(idDistribuidor, idPublicacion, fechaDesde, fechaHasta); + var promediosDataTask = this.GetListadoDistribucionDistPromedioDiaAsync(idDistribuidor, idPublicacion, fechaDesde, fechaHasta); + + await Task.WhenAll(simpleDataTask, promediosDataTask); + + return (await simpleDataTask, await promediosDataTask, null); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error en ReportesService al obtener Listado Distribucion (Distribuidores)."); + return (Enumerable.Empty(), Enumerable.Empty(), "Error interno al generar el reporte."); + } + } } } \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Models/Dtos/Reportes/ListadoDistribucionDistPromedioDiaDto.cs b/Backend/GestionIntegral.Api/Models/Dtos/Reportes/ListadoDistribucionDistPromedioDiaDto.cs new file mode 100644 index 0000000..8f4fe6e --- /dev/null +++ b/Backend/GestionIntegral.Api/Models/Dtos/Reportes/ListadoDistribucionDistPromedioDiaDto.cs @@ -0,0 +1,14 @@ +namespace GestionIntegral.Api.Dtos.Reportes +{ + public class ListadoDistribucionDistPromedioDiaDto + { + public string Dia { get; set; } = string.Empty; // Nombre del día de la semana (Lunes, Martes, etc.) + public int? Cant { get; set; } // Cantidad de días con ese nombre en el período + public int? Llevados { get; set; } + public int? Devueltos { get; set; } + public int? Promedio_Llevados { get; set; } + public int? Promedio_Devueltos { get; set; } + public int? Promedio_Ventas { get; set; } + // public int Dia_Orden { get; set; } // Si el SP devuelve Dia_Orden para ordenar + } +} \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Models/Dtos/Reportes/ListadoDistribucionDistSimpleDto.cs b/Backend/GestionIntegral.Api/Models/Dtos/Reportes/ListadoDistribucionDistSimpleDto.cs new file mode 100644 index 0000000..bdae395 --- /dev/null +++ b/Backend/GestionIntegral.Api/Models/Dtos/Reportes/ListadoDistribucionDistSimpleDto.cs @@ -0,0 +1,9 @@ +namespace GestionIntegral.Api.Dtos.Reportes +{ + public class ListadoDistribucionDistSimpleDto + { + public int Dia { get; set; } // Día del mes + public int? Llevados { get; set; } // Nullable si el SP puede devolver NULL + public int? Devueltos { get; set; } // Nullable si el SP puede devolver NULL + } +} \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Models/Dtos/Reportes/ListadoDistribucionDistribuidoresResponseDto.cs b/Backend/GestionIntegral.Api/Models/Dtos/Reportes/ListadoDistribucionDistribuidoresResponseDto.cs new file mode 100644 index 0000000..e6ab7f2 --- /dev/null +++ b/Backend/GestionIntegral.Api/Models/Dtos/Reportes/ListadoDistribucionDistribuidoresResponseDto.cs @@ -0,0 +1,8 @@ +namespace GestionIntegral.Api.Dtos.Reportes +{ + public class ListadoDistribucionDistribuidoresResponseDto + { + public IEnumerable DetalleSimple { get; set; } = Enumerable.Empty(); + public IEnumerable PromediosPorDia { get; set; } = Enumerable.Empty(); + } +} \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Services/Reportes/IReportesService.cs b/Backend/GestionIntegral.Api/Services/Reportes/IReportesService.cs index 1abb49e..feb48c1 100644 --- a/Backend/GestionIntegral.Api/Services/Reportes/IReportesService.cs +++ b/Backend/GestionIntegral.Api/Services/Reportes/IReportesService.cs @@ -61,5 +61,7 @@ namespace GestionIntegral.Api.Services.Reportes IEnumerable Saldos, string? Error )> ObtenerReporteCuentasDistribuidorAsync(int idDistribuidor, int idEmpresa, DateTime fechaDesde, DateTime fechaHasta); + + Task<(IEnumerable Simple, IEnumerable Promedios, string? Error)> ObtenerListadoDistribucionDistribuidoresAsync(int idDistribuidor, int idPublicacion, DateTime fechaDesde, DateTime fechaHasta); } } \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Services/Reportes/ReportesService.cs b/Backend/GestionIntegral.Api/Services/Reportes/ReportesService.cs index b7a38da..3b0c268 100644 --- a/Backend/GestionIntegral.Api/Services/Reportes/ReportesService.cs +++ b/Backend/GestionIntegral.Api/Services/Reportes/ReportesService.cs @@ -427,5 +427,27 @@ namespace GestionIntegral.Api.Services.Reportes ); } } + + public async Task<(IEnumerable Simple, IEnumerable Promedios, string? Error)> ObtenerListadoDistribucionDistribuidoresAsync(int idDistribuidor, int idPublicacion, DateTime fechaDesde, DateTime fechaHasta) + { + if (fechaDesde > fechaHasta) + return (Enumerable.Empty(), Enumerable.Empty(), "La fecha 'Desde' no puede ser mayor que la fecha 'Hasta'."); + + try + { + // Llamar a los métodos específicos del repositorio + var simpleDataTask = _reportesRepository.GetListadoDistribucionDistSimpleAsync(idDistribuidor, idPublicacion, fechaDesde, fechaHasta); + var promediosDataTask = _reportesRepository.GetListadoDistribucionDistPromedioDiaAsync(idDistribuidor, idPublicacion, fechaDesde, fechaHasta); + + await Task.WhenAll(simpleDataTask, promediosDataTask); + + return (await simpleDataTask, await promediosDataTask, null); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error en ReportesService al obtener Listado Distribucion (Distribuidores). Params: Dist={idDistribuidor}, Pub={idPublicacion}, Desde={fechaDesde}, Hasta={fechaHasta}", idDistribuidor, idPublicacion, fechaDesde, fechaHasta); + return (Enumerable.Empty(), Enumerable.Empty(), "Error interno al generar el reporte."); + } + } } } \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/obj/Debug/net9.0/GestionIntegral.Api.AssemblyInfo.cs b/Backend/GestionIntegral.Api/obj/Debug/net9.0/GestionIntegral.Api.AssemblyInfo.cs index 7911427..3594c75 100644 --- a/Backend/GestionIntegral.Api/obj/Debug/net9.0/GestionIntegral.Api.AssemblyInfo.cs +++ b/Backend/GestionIntegral.Api/obj/Debug/net9.0/GestionIntegral.Api.AssemblyInfo.cs @@ -13,7 +13,7 @@ using System.Reflection; [assembly: System.Reflection.AssemblyCompanyAttribute("GestionIntegral.Api")] [assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] [assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] -[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+70fc84772161b499c8283a31b7a61246a6bcc46f")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+1182a4cdee4fcdb55dc3f2dbfeeb2ec2187f2bea")] [assembly: System.Reflection.AssemblyProductAttribute("GestionIntegral.Api")] [assembly: System.Reflection.AssemblyTitleAttribute("GestionIntegral.Api")] [assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] diff --git a/Backend/GestionIntegral.Api/obj/Debug/net9.0/rjsmcshtml.dswa.cache.json b/Backend/GestionIntegral.Api/obj/Debug/net9.0/rjsmcshtml.dswa.cache.json index e7b28b5..e99b370 100644 --- a/Backend/GestionIntegral.Api/obj/Debug/net9.0/rjsmcshtml.dswa.cache.json +++ b/Backend/GestionIntegral.Api/obj/Debug/net9.0/rjsmcshtml.dswa.cache.json @@ -1 +1 @@ -{"GlobalPropertiesHash":"C9goqBDGh4B0L1HpPwpJHjfbRNoIuzqnU7zFMHk1LhM=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["lgiSIq1Xdt6PC6CpA82eiZlqBZS3M8jckHELlrL00LI=","bxlPVWHR7EivQofjz9PzA8dMpKpZqCfOZ\u002BHD\u002Bf1Ew9Y=","\u002BzMwu5DIAA49kPmSydn2WMzj\u002Bdcf0MC3YakKoR6HwYg=","FUb20tYUiusFv5/KhAPdh2OB4ArUWiGApXbQJdx8tX0=","pTWqrhLBwEeWg1GsRlTKzfOAnT1JEklZ8F1/EYlc1Nk=","Hu0oNH4YYNcbnR5Ts4qd5yzC5j5JbY2kEDXces8V1vs=","TKMARE0bLM2dm9NOqxxWztnuqao5IvCh24TEHCtht6I=","84UEEMEbmmNwHVXD5Iw3dtKHTZC0Zqbk3rIRO\u002BxOq4o=","qfTzsJ\u002B5ilLyrc6EhNm61KkSH37yRi85MtgW1\u002BUD2Vo=","4ayt/JAApEOfr0yjg9szkYMPzSs6x2k3QEwmrK5RZVY=","d0weYwKWe3mH5R2BURuNLkAyytO/viA6zivv9AcIBtQ=","Ssyx6SvSGgWMOzhc9pQpk6f6\u002BmVbKQNKeDJbvVA2tjs=","FSqDybxILZmKXw160ANhj76usnM83geRrbPvJxr89OA=","fdI2RZZ9M9QOVHCYU5cE\u002BgVVuT7ssRbMzdXvX8rHofc=","8ePFhqKT0OT9nEg3b5T7COC81U\u002BQBcf\u002BindBGyMy6z0=","/ghcduGmSd1I25YtYli\u002BqxF0xuscxc4cTDkbEC6XYVA=","/a3YEu0oBUeA5Qr2VMdppqLuz4CQPWJt2JfBl2dtUwA=","jEO/q4IO3UFTWxlyFwRr7kbGWcTIiS\u002BClxx3kahX/Fk=","4iYOCKYvhsROdGkA1hINVBejb6r8IkwFj9SNMKub3DM=","CeDswsZIn5a7t\u002BKeHJA222yhFvDVVEW1ky98Xxnxebc=","50j34YXOc950QSqaQBMtgezD3tV5mWWR9c5qZcYQoz4=","W/aX9jIKpjNEVoGrU6RXFOY8SDJVT6XB4Rg4QCaeQkQ=","16IbB\u002B3zYHZvsWbCQK6hBFmKJ6Z28SecBn2jm8R3w8I=","COJtHNQqycTJqXkFv2hhpLUT\u002B/AD4IWyQlmxkUVQPNk=","cp6a5bdvkLnUn3x47KQODzPycnx57RmWO\u002B9q8MuoGQo=","oKZRNhIQRaZrETEa3L6JiwIp0\u002BmjzJo193EWBoCuVUg=","sjwbCAEQX51sEWhYVGBihWUNBxniUKZALVJIGK\u002BYgsk=","A4m4kVcox60bvdkJ1CswoZADAT70WPcs4TAKdpMoUjM=","zSzyOuNcK0NQJLwK8Yg4sH4EflX7RPf65Fl2CZUWIGs=","RFpydTIMf9fxYfNnrif0YQNHLgrUW58SpaLEeCZ9nXg="],"CachedAssets":{},"CachedCopyCandidates":{}} \ No newline at end of file +{"GlobalPropertiesHash":"C9goqBDGh4B0L1HpPwpJHjfbRNoIuzqnU7zFMHk1LhM=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["lgiSIq1Xdt6PC6CpA82eiZlqBZS3M8jckHELlrL00LI=","bxlPVWHR7EivQofjz9PzA8dMpKpZqCfOZ\u002BHD\u002Bf1Ew9Y=","\u002BzMwu5DIAA49kPmSydn2WMzj\u002Bdcf0MC3YakKoR6HwYg=","FUb20tYUiusFv5/KhAPdh2OB4ArUWiGApXbQJdx8tX0=","pTWqrhLBwEeWg1GsRlTKzfOAnT1JEklZ8F1/EYlc1Nk=","Hu0oNH4YYNcbnR5Ts4qd5yzC5j5JbY2kEDXces8V1vs=","TKMARE0bLM2dm9NOqxxWztnuqao5IvCh24TEHCtht6I=","84UEEMEbmmNwHVXD5Iw3dtKHTZC0Zqbk3rIRO\u002BxOq4o=","qfTzsJ\u002B5ilLyrc6EhNm61KkSH37yRi85MtgW1\u002BUD2Vo=","4ayt/JAApEOfr0yjg9szkYMPzSs6x2k3QEwmrK5RZVY=","d0weYwKWe3mH5R2BURuNLkAyytO/viA6zivv9AcIBtQ=","Ssyx6SvSGgWMOzhc9pQpk6f6\u002BmVbKQNKeDJbvVA2tjs=","FSqDybxILZmKXw160ANhj76usnM83geRrbPvJxr89OA=","fdI2RZZ9M9QOVHCYU5cE\u002BgVVuT7ssRbMzdXvX8rHofc=","8ePFhqKT0OT9nEg3b5T7COC81U\u002BQBcf\u002BindBGyMy6z0=","/ghcduGmSd1I25YtYli\u002BqxF0xuscxc4cTDkbEC6XYVA=","/a3YEu0oBUeA5Qr2VMdppqLuz4CQPWJt2JfBl2dtUwA=","jEO/q4IO3UFTWxlyFwRr7kbGWcTIiS\u002BClxx3kahX/Fk=","4iYOCKYvhsROdGkA1hINVBejb6r8IkwFj9SNMKub3DM=","CeDswsZIn5a7t\u002BKeHJA222yhFvDVVEW1ky98Xxnxebc=","50j34YXOc950QSqaQBMtgezD3tV5mWWR9c5qZcYQoz4=","W/aX9jIKpjNEVoGrU6RXFOY8SDJVT6XB4Rg4QCaeQkQ=","16IbB\u002B3zYHZvsWbCQK6hBFmKJ6Z28SecBn2jm8R3w8I=","COJtHNQqycTJqXkFv2hhpLUT\u002B/AD4IWyQlmxkUVQPNk=","cp6a5bdvkLnUn3x47KQODzPycnx57RmWO\u002B9q8MuoGQo=","oKZRNhIQRaZrETEa3L6JiwIp0\u002BmjzJo193EWBoCuVUg=","sjwbCAEQX51sEWhYVGBihWUNBxniUKZALVJIGK\u002BYgsk=","A4m4kVcox60bvdkJ1CswoZADAT70WPcs4TAKdpMoUjM=","zSzyOuNcK0NQJLwK8Yg4sH4EflX7RPf65Fl2CZUWIGs=","OZUau2FUwouOUoP6Eot2qiZlqRHSBBkSPL6vHtWUfGI="],"CachedAssets":{},"CachedCopyCandidates":{}} \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/obj/Debug/net9.0/rjsmrazor.dswa.cache.json b/Backend/GestionIntegral.Api/obj/Debug/net9.0/rjsmrazor.dswa.cache.json index 4573e4f..598170e 100644 --- a/Backend/GestionIntegral.Api/obj/Debug/net9.0/rjsmrazor.dswa.cache.json +++ b/Backend/GestionIntegral.Api/obj/Debug/net9.0/rjsmrazor.dswa.cache.json @@ -1 +1 @@ -{"GlobalPropertiesHash":"w3MBbMV9Msh0YEq9AW/8s16bzXJ93T9lMVXKPm/r6es=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["lgiSIq1Xdt6PC6CpA82eiZlqBZS3M8jckHELlrL00LI=","bxlPVWHR7EivQofjz9PzA8dMpKpZqCfOZ\u002BHD\u002Bf1Ew9Y=","\u002BzMwu5DIAA49kPmSydn2WMzj\u002Bdcf0MC3YakKoR6HwYg=","FUb20tYUiusFv5/KhAPdh2OB4ArUWiGApXbQJdx8tX0=","pTWqrhLBwEeWg1GsRlTKzfOAnT1JEklZ8F1/EYlc1Nk=","Hu0oNH4YYNcbnR5Ts4qd5yzC5j5JbY2kEDXces8V1vs=","TKMARE0bLM2dm9NOqxxWztnuqao5IvCh24TEHCtht6I=","84UEEMEbmmNwHVXD5Iw3dtKHTZC0Zqbk3rIRO\u002BxOq4o=","qfTzsJ\u002B5ilLyrc6EhNm61KkSH37yRi85MtgW1\u002BUD2Vo=","4ayt/JAApEOfr0yjg9szkYMPzSs6x2k3QEwmrK5RZVY=","d0weYwKWe3mH5R2BURuNLkAyytO/viA6zivv9AcIBtQ=","Ssyx6SvSGgWMOzhc9pQpk6f6\u002BmVbKQNKeDJbvVA2tjs=","FSqDybxILZmKXw160ANhj76usnM83geRrbPvJxr89OA=","fdI2RZZ9M9QOVHCYU5cE\u002BgVVuT7ssRbMzdXvX8rHofc=","8ePFhqKT0OT9nEg3b5T7COC81U\u002BQBcf\u002BindBGyMy6z0=","/ghcduGmSd1I25YtYli\u002BqxF0xuscxc4cTDkbEC6XYVA=","/a3YEu0oBUeA5Qr2VMdppqLuz4CQPWJt2JfBl2dtUwA=","jEO/q4IO3UFTWxlyFwRr7kbGWcTIiS\u002BClxx3kahX/Fk=","4iYOCKYvhsROdGkA1hINVBejb6r8IkwFj9SNMKub3DM=","CeDswsZIn5a7t\u002BKeHJA222yhFvDVVEW1ky98Xxnxebc=","50j34YXOc950QSqaQBMtgezD3tV5mWWR9c5qZcYQoz4=","W/aX9jIKpjNEVoGrU6RXFOY8SDJVT6XB4Rg4QCaeQkQ=","16IbB\u002B3zYHZvsWbCQK6hBFmKJ6Z28SecBn2jm8R3w8I=","COJtHNQqycTJqXkFv2hhpLUT\u002B/AD4IWyQlmxkUVQPNk=","cp6a5bdvkLnUn3x47KQODzPycnx57RmWO\u002B9q8MuoGQo=","oKZRNhIQRaZrETEa3L6JiwIp0\u002BmjzJo193EWBoCuVUg=","sjwbCAEQX51sEWhYVGBihWUNBxniUKZALVJIGK\u002BYgsk=","A4m4kVcox60bvdkJ1CswoZADAT70WPcs4TAKdpMoUjM=","zSzyOuNcK0NQJLwK8Yg4sH4EflX7RPf65Fl2CZUWIGs=","RFpydTIMf9fxYfNnrif0YQNHLgrUW58SpaLEeCZ9nXg="],"CachedAssets":{},"CachedCopyCandidates":{}} \ No newline at end of file +{"GlobalPropertiesHash":"w3MBbMV9Msh0YEq9AW/8s16bzXJ93T9lMVXKPm/r6es=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["lgiSIq1Xdt6PC6CpA82eiZlqBZS3M8jckHELlrL00LI=","bxlPVWHR7EivQofjz9PzA8dMpKpZqCfOZ\u002BHD\u002Bf1Ew9Y=","\u002BzMwu5DIAA49kPmSydn2WMzj\u002Bdcf0MC3YakKoR6HwYg=","FUb20tYUiusFv5/KhAPdh2OB4ArUWiGApXbQJdx8tX0=","pTWqrhLBwEeWg1GsRlTKzfOAnT1JEklZ8F1/EYlc1Nk=","Hu0oNH4YYNcbnR5Ts4qd5yzC5j5JbY2kEDXces8V1vs=","TKMARE0bLM2dm9NOqxxWztnuqao5IvCh24TEHCtht6I=","84UEEMEbmmNwHVXD5Iw3dtKHTZC0Zqbk3rIRO\u002BxOq4o=","qfTzsJ\u002B5ilLyrc6EhNm61KkSH37yRi85MtgW1\u002BUD2Vo=","4ayt/JAApEOfr0yjg9szkYMPzSs6x2k3QEwmrK5RZVY=","d0weYwKWe3mH5R2BURuNLkAyytO/viA6zivv9AcIBtQ=","Ssyx6SvSGgWMOzhc9pQpk6f6\u002BmVbKQNKeDJbvVA2tjs=","FSqDybxILZmKXw160ANhj76usnM83geRrbPvJxr89OA=","fdI2RZZ9M9QOVHCYU5cE\u002BgVVuT7ssRbMzdXvX8rHofc=","8ePFhqKT0OT9nEg3b5T7COC81U\u002BQBcf\u002BindBGyMy6z0=","/ghcduGmSd1I25YtYli\u002BqxF0xuscxc4cTDkbEC6XYVA=","/a3YEu0oBUeA5Qr2VMdppqLuz4CQPWJt2JfBl2dtUwA=","jEO/q4IO3UFTWxlyFwRr7kbGWcTIiS\u002BClxx3kahX/Fk=","4iYOCKYvhsROdGkA1hINVBejb6r8IkwFj9SNMKub3DM=","CeDswsZIn5a7t\u002BKeHJA222yhFvDVVEW1ky98Xxnxebc=","50j34YXOc950QSqaQBMtgezD3tV5mWWR9c5qZcYQoz4=","W/aX9jIKpjNEVoGrU6RXFOY8SDJVT6XB4Rg4QCaeQkQ=","16IbB\u002B3zYHZvsWbCQK6hBFmKJ6Z28SecBn2jm8R3w8I=","COJtHNQqycTJqXkFv2hhpLUT\u002B/AD4IWyQlmxkUVQPNk=","cp6a5bdvkLnUn3x47KQODzPycnx57RmWO\u002B9q8MuoGQo=","oKZRNhIQRaZrETEa3L6JiwIp0\u002BmjzJo193EWBoCuVUg=","sjwbCAEQX51sEWhYVGBihWUNBxniUKZALVJIGK\u002BYgsk=","A4m4kVcox60bvdkJ1CswoZADAT70WPcs4TAKdpMoUjM=","zSzyOuNcK0NQJLwK8Yg4sH4EflX7RPf65Fl2CZUWIGs=","OZUau2FUwouOUoP6Eot2qiZlqRHSBBkSPL6vHtWUfGI="],"CachedAssets":{},"CachedCopyCandidates":{}} \ No newline at end of file diff --git a/Frontend/src/layouts/MainLayout.tsx b/Frontend/src/layouts/MainLayout.tsx index 86debf1..38ea056 100644 --- a/Frontend/src/layouts/MainLayout.tsx +++ b/Frontend/src/layouts/MainLayout.tsx @@ -5,38 +5,37 @@ import ChangePasswordModal from '../components/Modals/Usuarios/ChangePasswordMod import { useNavigate, useLocation } from 'react-router-dom'; // Para manejar la navegación y la ruta actual interface MainLayoutProps { - children: ReactNode; // Esto será el que renderiza las páginas del módulo + children: ReactNode; } -// Definir los módulos y sus rutas base const modules = [ - { label: 'Inicio', path: '/' }, - { label: 'Distribución', path: '/distribucion' }, // Asumiremos rutas base como /distribucion, /contables, etc. - { label: 'Contables', path: '/contables' }, - { label: 'Impresión', path: '/impresion' }, - { label: 'Reportes', path: '/reportes' }, - { label: 'Radios', path: '/radios' }, - { label: 'Usuarios', path: '/usuarios' }, + { label: 'Inicio', path: '/' }, + { label: 'Distribución', path: '/distribucion' }, + { label: 'Contables', path: '/contables' }, + { label: 'Impresión', path: '/impresion' }, + { label: 'Reportes', path: '/reportes' }, + { label: 'Radios', path: '/radios' }, + { label: 'Usuarios', path: '/usuarios' }, ]; + const MainLayout: React.FC = ({ children }) => { const { user, logout, - showForcedPasswordChangeModal, + // ... (resto de las props de useAuth) ... + isAuthenticated, isPasswordChangeForced, - passwordChangeCompleted, + showForcedPasswordChangeModal, setShowForcedPasswordChangeModal, - isAuthenticated + passwordChangeCompleted } = useAuth(); const navigate = useNavigate(); const location = useLocation(); // Para obtener la ruta actual - // Estado para el tab seleccionado const [selectedTab, setSelectedTab] = useState(false); - // Efecto para sincronizar el tab seleccionado con la ruta actual useEffect(() => { const currentModulePath = modules.findIndex(module => location.pathname === module.path || (module.path !== '/' && location.pathname.startsWith(module.path + '/')) @@ -44,14 +43,13 @@ const MainLayout: React.FC = ({ children }) => { if (currentModulePath !== -1) { setSelectedTab(currentModulePath); } else if (location.pathname === '/') { - setSelectedTab(0); // Seleccionar "Inicio" si es la raíz + setSelectedTab(0); } else { - setSelectedTab(false); // Ningún tab coincide (podría ser una sub-ruta no principal) + setSelectedTab(false); } }, [location.pathname]); const handleModalClose = (passwordChangedSuccessfully: boolean) => { - // ... (lógica de handleModalClose existente) ... if (passwordChangedSuccessfully) { passwordChangeCompleted(); } else { @@ -65,54 +63,54 @@ const MainLayout: React.FC = ({ children }) => { const handleTabChange = (_event: React.SyntheticEvent, newValue: number) => { setSelectedTab(newValue); - navigate(modules[newValue].path); // Navegar a la ruta base del módulo + navigate(modules[newValue].path); }; - // Si el modal de cambio de clave forzado está activo, no mostramos la navegación principal aún. - // El modal se superpone. + // Determinar si el módulo actual es el de Reportes + const isReportesModule = location.pathname.startsWith('/reportes'); + if (showForcedPasswordChangeModal && isPasswordChangeForced) { + // ... (lógica del modal forzado sin cambios) ... return ( - - {/* Podrías querer un fondo o layout mínimo aquí si el modal no es pantalla completa */} + /> ); } - return ( + {/* ... (Toolbar y Tabs sin cambios) ... */} Sistema de Gestión - El Día {user && Hola, {user.nombreCompleto}} {isAuthenticated && !isPasswordChangeForced && ( - + )} - {/* Navegación Principal por Módulos */} - {/* Usamos Paper para un fondo consistente para los Tabs */} + {modules.map((module) => ( @@ -121,13 +119,15 @@ const MainLayout: React.FC = ({ children }) => { - {/* Contenido del Módulo (renderizado por en AppRoutes) */} + {/* Contenido del Módulo */} {children} @@ -139,14 +139,11 @@ const MainLayout: React.FC = ({ children }) => { - {/* Modal para cambio de clave opcional (no forzado) */} - {/* Si showForcedPasswordChangeModal es true pero isPasswordChangeForced es false, - se mostrará aquí también. */} - + isFirstLogin={isPasswordChangeForced} + /> ); }; diff --git a/Frontend/src/models/dtos/Reportes/ControlDevolucionesDataResponseDto.ts b/Frontend/src/models/dtos/Reportes/ControlDevolucionesDataResponseDto.ts new file mode 100644 index 0000000..552f425 --- /dev/null +++ b/Frontend/src/models/dtos/Reportes/ControlDevolucionesDataResponseDto.ts @@ -0,0 +1,9 @@ +import type { ControlDevolucionesReporteDto } from './ControlDevolucionesReporteDto'; +import type { DevueltosOtrosDiasDto } from './DevueltosOtrosDiasDto'; +import type { ObtenerCtrlDevolucionesDto } from './ObtenerCtrlDevolucionesDto'; + +export interface ControlDevolucionesDataResponseDto { + detallesCtrlDevoluciones: ControlDevolucionesReporteDto[]; + devolucionesOtrosDias: DevueltosOtrosDiasDto[]; + remitosIngresados: ObtenerCtrlDevolucionesDto[]; +} \ No newline at end of file diff --git a/Frontend/src/models/dtos/Reportes/ListadoDistribucionDistPromedioDiaDto.ts b/Frontend/src/models/dtos/Reportes/ListadoDistribucionDistPromedioDiaDto.ts new file mode 100644 index 0000000..91d78fa --- /dev/null +++ b/Frontend/src/models/dtos/Reportes/ListadoDistribucionDistPromedioDiaDto.ts @@ -0,0 +1,10 @@ +export interface ListadoDistribucionDistPromedioDiaDto { + id?: string; // Para DataGrid + dia: string; // Nombre del día de la semana + cant: number; + llevados: number; + devueltos: number; + promedio_Llevados: number; + promedio_Devueltos: number; + promedio_Ventas: number; +} \ No newline at end of file diff --git a/Frontend/src/models/dtos/Reportes/ListadoDistribucionDistSimpleDto.ts b/Frontend/src/models/dtos/Reportes/ListadoDistribucionDistSimpleDto.ts new file mode 100644 index 0000000..6a23e14 --- /dev/null +++ b/Frontend/src/models/dtos/Reportes/ListadoDistribucionDistSimpleDto.ts @@ -0,0 +1,6 @@ +export interface ListadoDistribucionDistSimpleDto { + id?: string; // Para DataGrid + dia: number; // Día del mes + llevados: number; + devueltos: number | null; // Puede ser null si no hay devoluciones +} \ No newline at end of file diff --git a/Frontend/src/models/dtos/Reportes/ListadoDistribucionDistribuidoresResponseDto.ts b/Frontend/src/models/dtos/Reportes/ListadoDistribucionDistribuidoresResponseDto.ts new file mode 100644 index 0000000..7518419 --- /dev/null +++ b/Frontend/src/models/dtos/Reportes/ListadoDistribucionDistribuidoresResponseDto.ts @@ -0,0 +1,7 @@ +import type { ListadoDistribucionDistSimpleDto } from './ListadoDistribucionDistSimpleDto'; +import type { ListadoDistribucionDistPromedioDiaDto } from './ListadoDistribucionDistPromedioDiaDto'; + +export interface ListadoDistribucionDistribuidoresResponseDto { + detalleSimple: ListadoDistribucionDistSimpleDto[]; + promediosPorDia: ListadoDistribucionDistPromedioDiaDto[]; +} \ No newline at end of file diff --git a/Frontend/src/pages/Reportes/ReporteComparativaConsumoBobinasPage.tsx b/Frontend/src/pages/Reportes/ReporteComparativaConsumoBobinasPage.tsx index 814f273..8eca575 100644 --- a/Frontend/src/pages/Reportes/ReporteComparativaConsumoBobinasPage.tsx +++ b/Frontend/src/pages/Reportes/ReporteComparativaConsumoBobinasPage.tsx @@ -1,17 +1,23 @@ -import React, { useState, useCallback } from 'react'; +import React, { useState, useCallback, useMemo } from 'react'; import { - Box, Typography, Paper, CircularProgress, Alert, Button, - TableContainer, Table, TableHead, TableRow, TableCell, TableBody, - TableFooter + Box, Typography, Paper, CircularProgress, Alert, Button } from '@mui/material'; +import { DataGrid, type GridColDef, GridFooterContainer, GridFooter } from '@mui/x-data-grid'; +import type {Theme} from '@mui/material/styles'; +import { esES } from '@mui/x-data-grid/locales'; import reportesService from '../../services/Reportes/reportesService'; import type { ComparativaConsumoBobinasDto } from '../../models/dtos/Reportes/ComparativaConsumoBobinasDto'; import SeleccionaReporteComparativaConsumoBobinas from './SeleccionaReporteComparativaConsumoBobinas'; import * as XLSX from 'xlsx'; import axios from 'axios'; +// Interfaz extendida para DataGrid +interface ComparativaConsumoBobinasDataGridDto extends ComparativaConsumoBobinasDto { + id: string; +} + const ReporteComparativaConsumoBobinasPage: React.FC = () => { - const [reportData, setReportData] = useState([]); + const [reportData, setReportData] = useState([]); // Usar tipo extendido const [loading, setLoading] = useState(false); const [loadingPdf, setLoadingPdf] = useState(false); const [error, setError] = useState(null); @@ -21,9 +27,14 @@ const ReporteComparativaConsumoBobinasPage: React.FC = () => { fechaInicioMesA: string; fechaFinMesA: string; fechaInicioMesB: string; fechaFinMesB: string; idPlanta?: number | null; consolidado: boolean; - nombrePlanta?: string; // Para el PDF + nombrePlanta?: string; + mesA?: string; + mesB?: string; } | null>(null); + const numberLocaleFormatter = (value: number | null | undefined) => + value != null ? Number(value).toLocaleString('es-AR') : ''; + const handleGenerarReporte = useCallback(async (params: { fechaInicioMesA: string; fechaFinMesA: string; fechaInicioMesB: string; fechaFinMesB: string; @@ -40,12 +51,24 @@ const ReporteComparativaConsumoBobinasPage: React.FC = () => { const plantaData = await plantaService.getPlantaById(params.idPlanta); plantaNombre = plantaData?.nombre ?? "N/A"; } - setCurrentParams({...params, nombrePlanta: plantaNombre}); + // Formatear nombres de meses para el PDF + const formatMonthYear = (dateString: string) => { + const date = new Date(dateString + 'T00:00:00'); // Asegurar que se parsea como local + return date.toLocaleDateString('es-AR', { month: 'long', year: 'numeric', timeZone: 'UTC' }); + }; + + setCurrentParams({ + ...params, + nombrePlanta: plantaNombre, + mesA: formatMonthYear(params.fechaInicioMesA), + mesB: formatMonthYear(params.fechaInicioMesB) + }); try { const data = await reportesService.getComparativaConsumoBobinas(params); - setReportData(data); - if (data.length === 0) { + const dataWithIds = data.map((item, index) => ({ ...item, id: `${item.tipoBobina}-${index}` })); + setReportData(dataWithIds); + if (dataWithIds.length === 0) { setError("No se encontraron datos para los parámetros seleccionados."); } setShowParamSelector(false); @@ -72,7 +95,7 @@ const ReporteComparativaConsumoBobinasPage: React.FC = () => { alert("No hay datos para exportar."); return; } - const dataToExport = reportData.map(item => ({ + const dataToExport = reportData.map(({ ...rest }) => rest).map(item => ({ "Tipo Bobina": item.tipoBobina, "Cant. Mes A": item.bobinasUtilizadasMesA, "Cant. Mes B": item.bobinasUtilizadasMesB, @@ -82,7 +105,6 @@ const ReporteComparativaConsumoBobinasPage: React.FC = () => { "Dif. Kg": item.diferenciaKilosUtilizados, })); - // Totales const totales = dataToExport.reduce((acc, row) => { acc.cantA += Number(row["Cant. Mes A"]); acc.cantB += Number(row["Cant. Mes B"]); acc.difCant += Number(row["Dif. Cant."]); acc.kgA += Number(row["Kg Mes A"]); @@ -96,7 +118,6 @@ const ReporteComparativaConsumoBobinasPage: React.FC = () => { "Dif. Kg": totales.difKg, }); - const ws = XLSX.utils.json_to_sheet(dataToExport); const headers = Object.keys(dataToExport[0] || {}); ws['!cols'] = headers.map(h => { @@ -104,7 +125,6 @@ const ReporteComparativaConsumoBobinasPage: React.FC = () => { return { wch: maxLen + 2 }; }); ws['!freeze'] = { xSplit: 0, ySplit: 1 }; - const wb = XLSX.utils.book_new(); XLSX.utils.book_append_sheet(wb, ws, "ComparativaConsumo"); let fileName = "ReporteComparativaConsumoBobinas"; @@ -141,6 +161,118 @@ const ReporteComparativaConsumoBobinasPage: React.FC = () => { } }, [currentParams]); + const columns: GridColDef[] = [ // Tipar con la interfaz correcta + { field: 'tipoBobina', headerName: 'Tipo Bobina', width: 250, flex: 1.5 }, + { field: 'bobinasUtilizadasMesA', headerName: 'Cant. Mes A', type: 'number', width: 120, align: 'right', headerAlign: 'right', valueFormatter: (value) => numberLocaleFormatter(Number(value)) }, + { field: 'bobinasUtilizadasMesB', headerName: 'Cant. Mes B', type: 'number', width: 120, align: 'right', headerAlign: 'right', valueFormatter: (value) => numberLocaleFormatter(Number(value)) }, + { field: 'diferenciaBobinasUtilizadas', headerName: 'Dif. Cant.', type: 'number', width: 110, align: 'right', headerAlign: 'right', valueFormatter: (value) => numberLocaleFormatter(Number(value)) }, + { field: 'kilosUtilizadosMesA', headerName: 'Kg Mes A', type: 'number', width: 120, align: 'right', headerAlign: 'right', cellClassName: 'separator-left', headerClassName: 'separator-left', valueFormatter: (value) => numberLocaleFormatter(Number(value)) }, + { field: 'kilosUtilizadosMesB', headerName: 'Kg Mes B', type: 'number', width: 120, align: 'right', headerAlign: 'right', valueFormatter: (value) => numberLocaleFormatter(Number(value)) }, + { field: 'diferenciaKilosUtilizados', headerName: 'Dif. Kg', type: 'number', width: 110, align: 'right', headerAlign: 'right', valueFormatter: (value) => numberLocaleFormatter(Number(value)) }, + ]; + + const rows = useMemo(() => reportData, [reportData]); + + // Calcular totales para el footer + const totalesGenerales = useMemo(() => { + if (reportData.length === 0) return null; + return { + bobinasUtilizadasMesA: reportData.reduce((sum, item) => sum + item.bobinasUtilizadasMesA, 0), + bobinasUtilizadasMesB: reportData.reduce((sum, item) => sum + item.bobinasUtilizadasMesB, 0), + diferenciaBobinasUtilizadas: reportData.reduce((sum, item) => sum + item.diferenciaBobinasUtilizadas, 0), + kilosUtilizadosMesA: reportData.reduce((sum, item) => sum + item.kilosUtilizadosMesA, 0), + kilosUtilizadosMesB: reportData.reduce((sum, item) => sum + item.kilosUtilizadosMesB, 0), + diferenciaKilosUtilizados: reportData.reduce((sum, item) => sum + item.diferenciaKilosUtilizados, 0), + }; + }, [reportData]); + + + // eslint-disable-next-line react/display-name + const CustomFooter = () => { + if (!totalesGenerales) return null; + + const getCellStyle = (field: (typeof columns)[number]['field'] | 'label', isLabel: boolean = false) => { + const colConfig = columns.find(c => c.field === field); + // Ajustar anchos para los totales para que sean más compactos + let targetWidth: number | string = 'auto'; + let targetMinWidth: number | string = 'auto'; + + if (isLabel) { + targetWidth = colConfig?.width ? Math.max(80, colConfig.width * 0.5) : 100; // Más corto para "TOTALES:" + targetMinWidth = 80; + } else if (colConfig) { + targetWidth = colConfig.width ? Math.max(70, colConfig.width * 0.75) : 90; // 75% del ancho de columna, mínimo 70 + targetMinWidth = 70; + } + + const style: React.CSSProperties = { + minWidth: targetMinWidth, + width: targetWidth, + textAlign: isLabel ? 'left' : (colConfig?.align || 'right') as 'left' | 'right' | 'center', + paddingRight: isLabel ? 1 : (field === 'diferenciaKilosUtilizados' ? 0 : 1), // pr en theme units + fontWeight: 'bold', + whiteSpace: 'nowrap', + }; + + // Aplicar el separador si es la columna 'kilosUtilizadosMesA' + if (field === 'kilosUtilizadosMesA') { + style.borderLeft = `2px solid grey`; // O theme.palette.divider + style.paddingLeft = '8px'; // Espacio después del separador + } + return style; + }; + + return ( + `1px solid ${theme.palette.divider}`, + minHeight: '52px', + }}> + + + + + + TOTALES: + {numberLocaleFormatter(totalesGenerales.bobinasUtilizadasMesA)} + {numberLocaleFormatter(totalesGenerales.bobinasUtilizadasMesB)} + {numberLocaleFormatter(totalesGenerales.diferenciaBobinasUtilizadas)} + {numberLocaleFormatter(totalesGenerales.kilosUtilizadosMesA)} + {numberLocaleFormatter(totalesGenerales.kilosUtilizadosMesB)} + {numberLocaleFormatter(totalesGenerales.diferenciaKilosUtilizados)} + + + ); + }; + + if (showParamSelector) { return ( @@ -161,7 +293,7 @@ const ReporteComparativaConsumoBobinasPage: React.FC = () => { Reporte: Comparativa Consumo de Bobinas - + + + + + + Canillas / Accionistas + + + {/* Ancho ligeramente reducido */} + {/* Cabecera con Fecha y Cantidad Canillas */} + + Fecha Consultada: {currentParams.fecha ? new Date(currentParams.fecha + 'T00:00:00').toLocaleDateString('es-AR', { timeZone: 'UTC' }) : ''} + Cantidad Canillas: {calculatedValues.cantidadCanillas} + + + + {currentParams.nombreEmpresa || 'EL DIA'} + + + + + + Accionistas + + + + + Canillitas + + + + + + + + {/* Línea sólida para Total Devolución */} + + + + + + {/* Línea sólida para Diferencia */} + + + + + ); +}; + +export default ReporteControlDevolucionesPage; \ No newline at end of file diff --git a/Frontend/src/pages/Reportes/ReporteCuentasDistribuidoresPage.tsx b/Frontend/src/pages/Reportes/ReporteCuentasDistribuidoresPage.tsx index 89799ed..19f4773 100644 --- a/Frontend/src/pages/Reportes/ReporteCuentasDistribuidoresPage.tsx +++ b/Frontend/src/pages/Reportes/ReporteCuentasDistribuidoresPage.tsx @@ -1,8 +1,9 @@ import React, { useState, useCallback } from 'react'; import { - Box, Typography, Paper, CircularProgress, Alert, Button, - TableContainer, Table, TableHead, TableRow, TableCell, TableBody + Box, Typography, Paper, CircularProgress, Button } from '@mui/material'; +import { DataGrid, type GridColDef, GridFooterContainer, GridFooter } from '@mui/x-data-grid'; +import { esES } from '@mui/x-data-grid/locales'; import reportesService from '../../services/Reportes/reportesService'; import type { ReporteCuentasDistribuidorResponseDto } from '../../models/dtos/Reportes/ReporteCuentasDistribuidorResponseDto'; import type { BalanceCuentaDistDto } from '../../models/dtos/Reportes/BalanceCuentaDistDto'; @@ -12,22 +13,214 @@ import SeleccionaReporteCuentasDistribuidores from './SeleccionaReporteCuentasDi import * as XLSX from 'xlsx'; import axios from 'axios'; +type MovimientoConSaldo = BalanceCuentaDistDto & { id: string; saldoAcumulado: number }; +type NotaConSaldo = BalanceCuentaDebCredDto & { id: string; saldoAcumulado: number }; +type PagoConSaldo = BalanceCuentaPagosDto & { id: string; saldoAcumulado: number }; + const ReporteCuentasDistribuidoresPage: React.FC = () => { - const [reportData, setReportData] = useState(null); - const [loading, setLoading] = useState(false); - const [loadingPdf, setLoadingPdf] = useState(false); - const [error, setError] = useState(null); + const [originalReportData, setOriginalReportData] = useState(null); + const [movimientosConSaldo, setMovimientosConSaldo] = useState([]); + const [notasConSaldo, setNotasConSaldo] = useState([]); + const [pagosConSaldo, setPagosConSaldo] = useState([]); + const [loading, setLoading] = useState(false); + const [loadingPdf, setLoadingPdf] = useState(false); const [apiErrorParams, setApiErrorParams] = useState(null); - const [showParamSelector, setShowParamSelector] = useState(true); + const [showParamSelector, setShowParamSelector] = useState(true); const [currentParams, setCurrentParams] = useState<{ idDistribuidor: number; idEmpresa: number; fechaDesde: string; fechaHasta: string; - nombreDistribuidor?: string; // Para el PDF y nombre de archivo - nombreEmpresa?: string; // Para el PDF y nombre de archivo + nombreDistribuidor?: string; + nombreEmpresa?: string; } | null>(null); + // Calcula saldos acumulados seccion por seccion + const calcularSaldosPorSeccion = (data: ReporteCuentasDistribuidorResponseDto) => { + const procesarLista = ( + lista: T[], + idPrefix: string, + saldoInicial: number + ): Array => { + let acumulado = saldoInicial; + return lista + .slice() + .sort((a, b) => new Date(a.fecha).getTime() - new Date(b.fecha).getTime()) + .map((item, idx) => { + const debe = Number(item.debe) || 0; + const haber = Number(item.haber) || 0; + acumulado += debe - haber; + return { + ...item, + id: item.id?.toString() || `${idPrefix}-${idx}`, + saldoAcumulado: acumulado + }; + }); + }; + + const movs = procesarLista(data.entradasSalidas, 'mov', 0); + const ultimoMov = movs.length ? movs[movs.length - 1].saldoAcumulado : 0; + const notas = procesarLista(data.debitosCreditos, 'nota', ultimoMov); + const ultimoNota = notas.length ? notas[notas.length - 1].saldoAcumulado : ultimoMov; + const pagos = procesarLista(data.pagos, 'pago', ultimoNota); + + return { movs, notas, pagos }; + }; + + // Genera columnas con formateo + const generarColumns = (): { mov: GridColDef[]; notas: GridColDef[]; pagos: GridColDef[] } => ({ + mov: [ + { field: 'fecha', headerName: 'Fecha', width: 100 }, + { field: 'publicacion', headerName: 'Publicación', flex: 1 }, + { field: 'remito', headerName: 'Remito', width: 100 }, + { + field: 'debe', + headerName: 'Debe', + type: 'number', + width: 130, + align: 'right', + headerAlign: 'right', + valueFormatter: (value: number | null | undefined) => + value != null ? value.toLocaleString('es-AR', { style: 'currency', currency: 'ARS' }) : '' + }, + //{ field: 'debe', headerName: 'Debe', type: 'number', width: 130, align: 'right', headerAlign: 'right' }, + { + field: 'haber', + headerName: 'Haber', + type: 'number', + width: 130, + align: 'right', + headerAlign: 'right', + valueFormatter: (value: number | null | undefined) => + value != null ? value.toLocaleString('es-AR', { style: 'currency', currency: 'ARS' }) : '' + }, + //{ field: 'haber', headerName: 'Haber', type: 'number', width: 130, align: 'right', headerAlign: 'right' }, + { + field: 'saldoAcumulado', + headerName: 'Saldo', + type: 'number', + width: 130, + align: 'right', + headerAlign: 'right', + valueFormatter: (value: number | null | undefined) => + value != null ? value.toLocaleString('es-AR', { style: 'currency', currency: 'ARS' }) : '' + }, + //{ field: 'saldoAcumulado', headerName: 'Saldo', type: 'number', width: 150, align: 'right', headerAlign: 'right' } + ], + notas: [ + { field: 'fecha', headerName: 'Fecha', width: 100 }, + { field: 'referencia', headerName: 'Referencia', flex: 1 }, + { + field: 'debe', + headerName: 'Debe', + type: 'number', + width: 130, + align: 'right', + headerAlign: 'right', + valueFormatter: (value: number | null | undefined) => + value != null ? value.toLocaleString('es-AR', { style: 'currency', currency: 'ARS' }) : '' + }, + //{ field: 'debe', headerName: 'Debe', type: 'number', width: 130, align: 'right', headerAlign: 'right' }, + { + field: 'haber', + headerName: 'Haber', + type: 'number', + width: 130, + align: 'right', + headerAlign: 'right', + valueFormatter: (value: number | null | undefined) => + value != null ? value.toLocaleString('es-AR', { style: 'currency', currency: 'ARS' }) : '' + }, + //{ field: 'haber', headerName: 'Haber', type: 'number', width: 130, align: 'right', headerAlign: 'right' }, + { + field: 'saldoAcumulado', + headerName: 'Saldo', + type: 'number', + width: 130, + align: 'right', + headerAlign: 'right', + valueFormatter: (value: number | null | undefined) => + value != null ? value.toLocaleString('es-AR', { style: 'currency', currency: 'ARS' }) : '' + }, + //{ field: 'saldoAcumulado', headerName: 'Saldo', type: 'number', width: 150, align: 'right', headerAlign: 'right' } + ], + pagos: [ + { field: 'fecha', headerName: 'Fecha', width: 100 }, + { field: 'recibo', headerName: 'Recibo', width: 100 }, + { field: 'tipo', headerName: 'Tipo', width: 150 }, + { field: 'detalle', headerName: 'Detalle', flex: 1 }, + { + field: 'debe', + headerName: 'Debe', + type: 'number', + width: 130, + align: 'right', + headerAlign: 'right', + valueFormatter: (value: number | null | undefined) => + value != null ? value.toLocaleString('es-AR', { style: 'currency', currency: 'ARS' }) : '' + }, + //{ field: 'debe', headerName: 'Debe', type: 'number', width: 130, align: 'right', headerAlign: 'right' }, + { + field: 'haber', + headerName: 'Haber', + type: 'number', + width: 130, + align: 'right', + headerAlign: 'right', + valueFormatter: (value: number | null | undefined) => + value != null ? value.toLocaleString('es-AR', { style: 'currency', currency: 'ARS' }) : '' + }, + //{ field: 'haber', headerName: 'Haber', type: 'number', width: 130, align: 'right', headerAlign: 'right' }, + { + field: 'saldoAcumulado', + headerName: 'Saldo', + type: 'number', + width: 130, + align: 'right', + headerAlign: 'right', + valueFormatter: (value: number | null | undefined) => + value != null ? value.toLocaleString('es-AR', { style: 'currency', currency: 'ARS' }) : '' + }, + //{ field: 'saldoAcumulado', headerName: 'Saldo', type: 'number', width: 150, align: 'right', headerAlign: 'right' } + ] + }); + + // Renderiza DataGrid con footer de totales + const renderDataGrid = ( + rows: Array<{ debe?: number; haber?: number }>, + columns: GridColDef[] + ) => { + const totalDebe = rows.reduce((sum, r) => sum + (r.debe || 0), 0); + const totalHaber = rows.reduce((sum, r) => sum + (r.haber || 0), 0); + return rows.length ? ( + + ( + + + + + TOTA DEBE: {totalDebe.toLocaleString('es-AR', { style: 'currency', currency: 'ARS' })} + + + TOTAL HABER: {totalHaber.toLocaleString('es-AR', { style: 'currency', currency: 'ARS' })} + + + + ) + }} + /> + + ) : ( + No hay registros + ); + }; + const handleGenerarReporte = useCallback(async (params: { idDistribuidor: number; idEmpresa: number; @@ -35,16 +228,18 @@ const ReporteCuentasDistribuidoresPage: React.FC = () => { fechaHasta: string; }) => { setLoading(true); - setError(null); setApiErrorParams(null); - setReportData(null); - - // Obtener nombres para el PDF/Excel - const distService = (await import('../../services/Distribucion/distribuidorService')).default; - const distData = await distService.getDistribuidorById(params.idDistribuidor); - const empService = (await import('../../services/Distribucion/empresaService')).default; - const empData = await empService.getEmpresaById(params.idEmpresa); + setOriginalReportData(null); + setMovimientosConSaldo([]); + setNotasConSaldo([]); + setPagosConSaldo([]); + const distSvc = (await import('../../services/Distribucion/distribuidorService')).default; + const empSvc = (await import('../../services/Distribucion/empresaService')).default; + const [distData, empData] = await Promise.all([ + distSvc.getDistribuidorById(params.idDistribuidor), + empSvc.getEmpresaById(params.idEmpresa) + ]); setCurrentParams({ ...params, nombreDistribuidor: distData?.nombre, @@ -53,17 +248,17 @@ const ReporteCuentasDistribuidoresPage: React.FC = () => { try { const data = await reportesService.getReporteCuentasDistribuidor(params); - setReportData(data); - const noData = (!data.entradasSalidas?.length && !data.debitosCreditos?.length && !data.pagos?.length && !data.saldos?.length); - if (noData) { - setError("No se encontraron datos para los parámetros seleccionados."); - } + setOriginalReportData(data); + const { movs, notas, pagos } = calcularSaldosPorSeccion(data); + setMovimientosConSaldo(movs); + setNotasConSaldo(notas); + setPagosConSaldo(pagos); setShowParamSelector(false); - } catch (err: any) { - const message = axios.isAxiosError(err) && err.response?.data?.message - ? err.response.data.message - : 'Ocurrió un error al generar el reporte.'; - setApiErrorParams(message); + } catch (error: any) { + const msg = axios.isAxiosError(error) && error.response?.data?.message + ? error.response.data.message + : 'Error al generar el reporte'; + setApiErrorParams(msg); } finally { setLoading(false); } @@ -71,166 +266,46 @@ const ReporteCuentasDistribuidoresPage: React.FC = () => { const handleVolverAParametros = useCallback(() => { setShowParamSelector(true); - setReportData(null); - setError(null); - setApiErrorParams(null); - setCurrentParams(null); + setOriginalReportData(null); + setMovimientosConSaldo([]); + setNotasConSaldo([]); + setPagosConSaldo([]); }, []); const handleExportToExcel = useCallback(() => { - if (!reportData) { - alert("No hay datos para exportar."); - return; - } + if (!originalReportData) return; const wb = XLSX.utils.book_new(); - - if (reportData.saldos?.length) { - const saldosToExport = reportData.saldos.map(item => ({ "Saldo Actual": item.monto })); - const wsSaldos = XLSX.utils.json_to_sheet(saldosToExport); - XLSX.utils.book_append_sheet(wb, wsSaldos, "SaldoActual"); + if (movimientosConSaldo.length) { + const ws = XLSX.utils.json_to_sheet(movimientosConSaldo.map(({ id, ...rest }) => rest)); + XLSX.utils.book_append_sheet(wb, ws, 'Movimientos'); } - if (reportData.entradasSalidas?.length) { - const esToExport = reportData.entradasSalidas.map(item => ({ - "Fecha": new Date(item.fecha).toLocaleDateString('es-AR', { timeZone: 'UTC' }), - "Publicación": item.publicacion, "Remito": item.remito, "Cantidad": item.cantidad, - "Observación": item.observacion, "Debe": item.debe, "Haber": item.haber, - })); - const wsES = XLSX.utils.json_to_sheet(esToExport); - XLSX.utils.book_append_sheet(wb, wsES, "EntradasSalidas"); + if (notasConSaldo.length) { + const ws = XLSX.utils.json_to_sheet(notasConSaldo.map(({ id, ...rest }) => rest)); + XLSX.utils.book_append_sheet(wb, ws, 'Notas'); } - if (reportData.debitosCreditos?.length) { - const dcToExport = reportData.debitosCreditos.map(item => ({ - "Fecha": new Date(item.fecha).toLocaleDateString('es-AR', { timeZone: 'UTC' }), - "Referencia": item.referencia, "Debe": item.debe, "Haber": item.haber, - })); - const wsDC = XLSX.utils.json_to_sheet(dcToExport); - XLSX.utils.book_append_sheet(wb, wsDC, "DebitosCreditos"); + if (pagosConSaldo.length) { + const ws = XLSX.utils.json_to_sheet(pagosConSaldo.map(({ id, ...rest }) => rest)); + XLSX.utils.book_append_sheet(wb, ws, 'Pagos'); } - if (reportData.pagos?.length) { - const paToExport = reportData.pagos.map(item => ({ - "Fecha": new Date(item.fecha).toLocaleDateString('es-AR', { timeZone: 'UTC' }), - "Recibo": item.recibo, "Tipo": item.tipo, "Debe": item.debe, "Haber": item.haber, - "Detalle": item.detalle, - })); - const wsPA = XLSX.utils.json_to_sheet(paToExport); - XLSX.utils.book_append_sheet(wb, wsPA, "Pagos"); - } - - let fileName = "ReporteCuentaDistribuidor"; - if (currentParams) { - fileName += `_${currentParams.nombreDistribuidor?.replace(/\s+/g, '') ?? `Dist${currentParams.idDistribuidor}`}`; - fileName += `_Emp${currentParams.nombreEmpresa?.replace(/\s+/g, '') ?? currentParams.idEmpresa}`; - fileName += `_${currentParams.fechaDesde}_a_${currentParams.fechaHasta}`; - } - fileName += ".xlsx"; - XLSX.writeFile(wb, fileName); - }, [reportData, currentParams]); + XLSX.writeFile(wb, `Reporte_${Date.now()}.xlsx`); + }, [originalReportData, movimientosConSaldo, notasConSaldo, pagosConSaldo]); const handleGenerarYAbrirPdf = useCallback(async () => { - if (!currentParams) { - setError("Primero debe generar el reporte en pantalla o seleccionar parámetros."); - return; - } + if (!currentParams) return; setLoadingPdf(true); - setError(null); try { const blob = await reportesService.getReporteCuentasDistribuidorPdf(currentParams); - if (blob.type === "application/json") { - const text = await blob.text(); - const msg = JSON.parse(text).message ?? "Error inesperado al generar PDF."; - setError(msg); - } else { - const url = URL.createObjectURL(blob); - const w = window.open(url, '_blank'); - if (!w) alert("Permite popups para ver el PDF."); - } + window.open(URL.createObjectURL(blob), '_blank'); } catch { - setError('Ocurrió un error al generar el PDF.'); + /* manejar error */ } finally { setLoadingPdf(false); } }, [currentParams]); - // --- Funciones para renderizar las tablas --- - const renderEntradasSalidasTable = (data: BalanceCuentaDistDto[]) => ( - - - - - FechaPublicación - RemitoCantidad - Observación - DebeHaber - - - - {data.map((row, idx) => ( - - {new Date(row.fecha).toLocaleDateString('es-AR', { timeZone: 'UTC' })} - {row.publicacion}{row.remito} - {row.cantidad.toLocaleString('es-AR')} - {row.observacion} - {row.debe.toLocaleString('es-AR', { style: 'currency', currency: 'ARS' })} - {row.haber.toLocaleString('es-AR', { style: 'currency', currency: 'ARS' })} - - ))} - -
-
- ); - - const renderDebitosCreditosTable = (data: BalanceCuentaDebCredDto[]) => ( - - - - - FechaReferencia - DebeHaber - - - - {data.map((row, idx) => ( - - {new Date(row.fecha).toLocaleDateString('es-AR', { timeZone: 'UTC' })} - {row.referencia} - {row.debe.toLocaleString('es-AR', { style: 'currency', currency: 'ARS' })} - {row.haber.toLocaleString('es-AR', { style: 'currency', currency: 'ARS' })} - - ))} - -
-
- ); - - const renderPagosTable = (data: BalanceCuentaPagosDto[]) => ( - - - - - FechaReciboTipo - DebeHaber - Detalle - - - - {data.map((row, idx) => ( - - {new Date(row.fecha).toLocaleDateString('es-AR', { timeZone: 'UTC' })} - {row.recibo}{row.tipo} - {row.debe.toLocaleString('es-AR', { style: 'currency', currency: 'ARS' })} - {row.haber.toLocaleString('es-AR', { style: 'currency', currency: 'ARS' })} - {row.detalle} - - ))} - -
-
- ); - - if (showParamSelector) { return ( - + { ); } + // Totales de cada sección para resumen final + const totalMov = movimientosConSaldo.reduce((sum, r) => sum + ((r.debe || 0) - (r.haber || 0)), 0); + const totalNot = notasConSaldo.reduce((sum, r) => sum + ((r.debe || 0) - (r.haber || 0)), 0); + const totalPag = pagosConSaldo.reduce((sum, r) => sum + ((r.debe || 0) - (r.haber || 0)), 0); + const saldoInicial = originalReportData?.saldos?.[0]?.monto || 0; + + const cols = generarColumns(); + return ( - - Reporte: Cuenta Corriente Distribuidor + + Cuenta Corriente Distribuidor - - + + Publicación: {currentParams?.nombrePublicacion} | + Fechas: {currentParams?.fechaDesde} al {currentParams?.fechaHasta} + - {loading && } + {loading && } {error && !loading && {error}} {!loading && !error && reportData && ( <> Detalle Diario - {reportData.detalleSimple && reportData.detalleSimple.length > 0 ? ( - - - - - Día - Llevados - Devueltos - Vendidos - - - - {reportData.detalleSimple.map((row, idx) => ( - - {row.dia} - {row.llevados.toLocaleString('es-AR')} - {row.devueltos.toLocaleString('es-AR')} - {(row.llevados - row.devueltos).toLocaleString('es-AR')} - - ))} - -
-
- ) : (No hay datos de detalle diario.)} + {detalleDiarioCalculado.length > 0 ? ( + + + + ) : (No hay datos de detalle diario.)} Promedios por Día de Semana - {reportData.promediosPorDia && reportData.promediosPorDia.length > 0 ? ( - - - - - Día Semana - Cant. Días - Prom. Llevados - Prom. Devueltos - Prom. Ventas - - - - {reportData.promediosPorDia.map((row, idx) => ( - - {row.dia} - {row.cant.toLocaleString('es-AR')} - {row.promedio_Llevados.toLocaleString('es-AR')} - {row.promedio_Devueltos.toLocaleString('es-AR')} - {row.promedio_Ventas.toLocaleString('es-AR')} - - ))} - -
-
- ) : (No hay datos de promedios por día.)} + {promediosPorDiaCalculado.length > 0 ? ( + + + + ) : (No hay datos de promedios por día.)} )} + {!loading && !error && (!reportData || ((!reportData.detalleSimple || reportData.detalleSimple.length === 0) && (!reportData.promediosPorDia || reportData.promediosPorDia.length === 0))) && + (No se encontraron datos para los criterios seleccionados.)}
); }; -export default ReporteListadoDistribucionCanillasPage; \ No newline at end of file +export default ReporteListadoDistribucionCanillasPage; // Asegúrate de que el nombre del archivo coincida con este export \ No newline at end of file diff --git a/Frontend/src/pages/Reportes/ReporteListadoDistribucionGeneralPage.tsx b/Frontend/src/pages/Reportes/ReporteListadoDistribucionGeneralPage.tsx index c313e4b..f6615b4 100644 --- a/Frontend/src/pages/Reportes/ReporteListadoDistribucionGeneralPage.tsx +++ b/Frontend/src/pages/Reportes/ReporteListadoDistribucionGeneralPage.tsx @@ -1,50 +1,116 @@ -import React, { useState, useCallback } from 'react'; +// src/pages/Reportes/ReporteDetalleDistribucionCanillasPage.tsx +import React, { useState, useCallback, useMemo } from 'react'; import { - Box, Typography, Paper, CircularProgress, Alert, Button, - TableContainer, Table, TableHead, TableRow, TableCell, TableBody + Box, Typography, Paper, CircularProgress, Alert, Button } from '@mui/material'; +import { DataGrid, type GridColDef, GridFooterContainer, GridFooter } from '@mui/x-data-grid'; +import { esES } from '@mui/x-data-grid/locales'; import reportesService from '../../services/Reportes/reportesService'; -import type { ListadoDistribucionGeneralResponseDto } from '../../models/dtos/Reportes/ListadoDistribucionGeneralResponseDto'; -import SeleccionaReporteListadoDistribucionGeneral from './SeleccionaReporteListadoDistribucionGeneral'; +import type { ReporteDistribucionCanillasResponseDto } from '../../models/dtos/Reportes/ReporteDistribucionCanillasResponseDto'; +import SeleccionaReporteDetalleDistribucionCanillas from './SeleccionaReporteDetalleDistribucionCanillas'; import * as XLSX from 'xlsx'; import axios from 'axios'; -const ReporteListadoDistribucionGeneralPage: React.FC = () => { - const [reportData, setReportData] = useState(null); +interface TotalesComunes { + totalCantSalida: number; + totalCantEntrada: number; + vendidos: number; + totalRendir: number; +} + +const ReporteDetalleDistribucionCanillasPage: React.FC = () => { + const [reportData, setReportData] = useState(null); const [loading, setLoading] = useState(false); const [loadingPdf, setLoadingPdf] = useState(false); const [error, setError] = useState(null); const [apiErrorParams, setApiErrorParams] = useState(null); const [showParamSelector, setShowParamSelector] = useState(true); const [currentParams, setCurrentParams] = useState<{ - idPublicacion: number; - fechaDesde: string; // Primer día del mes - fechaHasta: string; // Último día del mes - nombrePublicacion?: string; // Para el nombre del archivo - mesAnioParaNombreArchivo?: string; // Para el nombre del archivo (ej. YYYY-MM) + fecha: string; + idEmpresa: number; + nombreEmpresa?: string; } | null>(null); + const [pdfSoloTotales, setPdfSoloTotales] = useState(false); + + // Estados para los totales de cada sección + const initialTotals: TotalesComunes = { totalCantSalida: 0, totalCantEntrada: 0, vendidos: 0, totalRendir: 0 }; + const [totalesCanillas, setTotalesCanillas] = useState(initialTotals); + const [totalesAccionistas, setTotalesAccionistas] = useState(initialTotals); + const [totalesTodos, setTotalesTodos] = useState(initialTotals); + const [totalesCanillasOtraFecha, setTotalesCanillasOtraFecha] = useState(initialTotals); + const [totalesAccionistasOtraFecha, setTotalesAccionistasOtraFecha] = useState(initialTotals); + + // --- Formateadores --- + const currencyFormatter = (value: number | null | undefined) => + value != null ? value.toLocaleString('es-AR', { style: 'currency', currency: 'ARS' }) : ''; + const numberFormatter = (value: number | null | undefined) => + value != null ? Number(value).toLocaleString('es-AR') : ''; + + const calculateAndSetTotals = (dataArray: Array | undefined, setTotalsFunc: React.Dispatch>) => { + if (dataArray && dataArray.length > 0) { + const totals = dataArray.reduce((acc, item) => { + acc.totalCantSalida += Number(item.totalCantSalida) || 0; + acc.totalCantEntrada += Number(item.totalCantEntrada) || 0; + acc.totalRendir += Number(item.totalRendir) || 0; + return acc; + }, { totalCantSalida: 0, totalCantEntrada: 0, totalRendir: 0 }); + totals.vendidos = totals.totalCantSalida - totals.totalCantEntrada; + setTotalsFunc(totals); + } else { + setTotalsFunc(initialTotals); + } + }; const handleGenerarReporte = useCallback(async (params: { - idPublicacion: number; - fechaDesde: string; - fechaHasta: string; + fecha: string; + idEmpresa: number; }) => { setLoading(true); setError(null); setApiErrorParams(null); + + const empresaService = (await import('../../services/Distribucion/empresaService')).default; + const empData = await empresaService.getEmpresaById(params.idEmpresa); + + setCurrentParams({ ...params, nombreEmpresa: empData?.nombre }); + setReportData(null); // Limpiar datos antiguos + + // Resetear totales + setTotalesCanillas(initialTotals); + setTotalesAccionistas(initialTotals); + setTotalesTodos(initialTotals); + setTotalesCanillasOtraFecha(initialTotals); + setTotalesAccionistasOtraFecha(initialTotals); - // Para el nombre del archivo y título del PDF - const pubService = (await import('../../services/Distribucion/publicacionService')).default; - const pubData = await pubService.getPublicacionById(params.idPublicacion); - const mesAnioParts = params.fechaDesde.split('-'); // YYYY-MM-DD -> [YYYY, MM, DD] - const mesAnioNombre = `${mesAnioParts[1]}/${mesAnioParts[0]}`; - - - setCurrentParams({...params, nombrePublicacion: pubData?.nombre, mesAnioParaNombreArchivo: mesAnioNombre }); try { - const data = await reportesService.getListadoDistribucionGeneral(params); - setReportData(data); - if ((!data.resumen || data.resumen.length === 0) && (!data.promediosPorDia || data.promediosPorDia.length === 0)) { + const data = await reportesService.getReporteDistribucionCanillas(params); + + const addIds = >(arr: T[] | undefined, prefix: string): Array => + (arr || []).map((item, index) => ({ ...item, id: `${prefix}-${item.publicacion || item.tipoVendedor || 'item'}-${index}-${Math.random().toString(36).substring(7)}` })); + + const processedData = { + canillas: addIds(data.canillas, 'can'), + canillasAccionistas: addIds(data.canillasAccionistas, 'acc'), + canillasTodos: addIds(data.canillasTodos, 'all'), + canillasLiquidadasOtraFecha: addIds(data.canillasLiquidadasOtraFecha, 'canliq'), + canillasAccionistasLiquidadasOtraFecha: addIds(data.canillasAccionistasLiquidadasOtraFecha, 'accliq'), + controlDevolucionesDetalle: addIds(data.controlDevolucionesDetalle, 'cdd'), + controlDevolucionesRemitos: addIds(data.controlDevolucionesRemitos, 'cdr'), + controlDevolucionesOtrosDias: addIds(data.controlDevolucionesOtrosDias, 'cdo') + }; + setReportData(processedData); + + // Calcular y setear totales para cada sección + calculateAndSetTotals(processedData.canillas, setTotalesCanillas); + calculateAndSetTotals(processedData.canillasAccionistas, setTotalesAccionistas); + calculateAndSetTotals(processedData.canillasTodos, setTotalesTodos); + calculateAndSetTotals(processedData.canillasLiquidadasOtraFecha, setTotalesCanillasOtraFecha); + calculateAndSetTotals(processedData.canillasAccionistasLiquidadasOtraFecha, setTotalesAccionistasOtraFecha); + + const noData = (!data.canillas || data.canillas.length === 0) && + (!data.canillasAccionistas || data.canillasAccionistas.length === 0) && + (!data.canillasTodos || data.canillasTodos.length === 0); // Podrías añadir más chequeos si es necesario + if (noData) { setError("No se encontraron datos para los parámetros seleccionados."); } setShowParamSelector(false); @@ -68,62 +134,96 @@ const ReporteListadoDistribucionGeneralPage: React.FC = () => { }, []); const handleExportToExcel = useCallback(() => { - if (!reportData || (!reportData.resumen?.length && !reportData.promediosPorDia?.length)) { + if (!reportData) { alert("No hay datos para exportar."); return; } const wb = XLSX.utils.book_new(); - if (reportData.resumen?.length) { - const resumenToExport = reportData.resumen.map(item => ({ - "Fecha": item.fecha ? new Date(item.fecha).toLocaleDateString('es-AR', { timeZone: 'UTC' }) : '-', - "Tirada": item.cantidadTirada, - "Sin Cargo": item.sinCargo, - "Perdidos": item.perdidos, - "Llevados": item.llevados, - "Devueltos": item.devueltos, - "Vendidos": item.vendidos, - })); - const wsResumen = XLSX.utils.json_to_sheet(resumenToExport); - XLSX.utils.book_append_sheet(wb, wsResumen, "ResumenDiario"); - } + const formatAndSheet = ( + data: any[], + sheetName: string, + fields: Record, + totals?: TotalesComunes + ) => { + if (data && data.length > 0) { + let exportedData = data.map(item => { + const row: Record = {}; + const { id, ...itemData } = item; // Excluir el 'id' generado + Object.keys(fields).forEach(key => { + row[fields[key]] = (itemData as any)[key]; + if (key === 'fecha' && (itemData as any)[key]) { + row[fields[key]] = new Date((itemData as any)[key]).toLocaleDateString('es-AR', { timeZone: 'UTC' }); + } + if ((key === 'totalRendir') && (itemData as any)[key] != null) { + row[fields[key]] = parseFloat((itemData as any)[key]); // Mantener como número para suma en Excel + } + if (key === 'vendidos' && itemData.totalCantSalida != null && itemData.totalCantEntrada != null) { + row[fields[key]] = itemData.totalCantSalida - itemData.totalCantEntrada; + } + }); + return row; + }); - if (reportData.promediosPorDia?.length) { - const promediosToExport = reportData.promediosPorDia.map(item => ({ - "Día Semana": item.dia, - "Cant. Días": item.cantidadDias, - "Prom. Tirada": item.promedioTirada, - "Prom. Sin Cargo": item.promedioSinCargo, - "Prom. Perdidos": item.promedioPerdidos, - "Prom. Llevados": item.promedioLlevados, - "Prom. Devueltos": item.promedioDevueltos, - "Prom. Vendidos": item.promedioVendidos, - })); - const wsPromedios = XLSX.utils.json_to_sheet(promediosToExport); - XLSX.utils.book_append_sheet(wb, wsPromedios, "PromediosPorDia"); - } + if (totals) { + const totalRow: Record = {}; + const fieldKeys = Object.keys(fields); + totalRow[fields[fieldKeys[0]]] = "TOTALES"; // Título en la primera columna + if (fields.totalCantSalida) totalRow[fields.totalCantSalida] = totals.totalCantSalida; + if (fields.totalCantEntrada) totalRow[fields.totalCantEntrada] = totals.totalCantEntrada; + if (fields.vendidos) totalRow[fields.vendidos] = totals.vendidos; + if (fields.totalRendir) totalRow[fields.totalRendir] = totals.totalRendir; + exportedData.push(totalRow); + } - let fileName = "ListadoDistribucionGeneral"; + const ws = XLSX.utils.json_to_sheet(exportedData); + const headers = Object.values(fields); + ws['!cols'] = headers.map(h => { + const maxLen = Math.max(...exportedData.map(row => (row[h]?.toString() ?? '').length), h.length); + return { wch: maxLen + 2 }; + }); + ws['!freeze'] = { xSplit: 0, ySplit: 1 }; // Congelar primera fila + XLSX.utils.book_append_sheet(wb, ws, sheetName); + } + }; + + const fieldsCanillaAccionista = { publicacion: "Publicación", canilla: "Canilla", totalCantSalida: "Llevados", totalCantEntrada: "Devueltos", vendidos: "Vendidos", totalRendir: "A Rendir" }; + const fieldsCanillaAccionistaFechaLiq = { publicacion: "Publicación", canilla: "Canilla", fecha:"Fecha Mov.", totalCantSalida: "Llevados", totalCantEntrada: "Devueltos", vendidos: "Vendidos", totalRendir: "A Rendir" }; + const fieldsTodos = { publicacion: "Publicación", tipoVendedor: "Tipo", totalCantSalida: "Llevados", totalCantEntrada: "Devueltos", vendidos: "Vendidos", totalRendir: "A Rendir" }; + const fieldsCtrlDevDetalle = { ingresados: "Ingresados", sobrantes: "Sobrantes", sinCargo: "Sin Cargo", publicacion: "Publicación", llevados: "Llevados", devueltos: "Devueltos", tipo: "Tipo" }; + const fieldsCtrlDevRemitos = { remito: "Remito Ingresado" }; + const fieldsCtrlDevOtrosDias = { devueltos: "Devueltos Otros Días" }; + + formatAndSheet(reportData.canillas, "Canillitas_Dia", fieldsCanillaAccionista, totalesCanillas); + formatAndSheet(reportData.canillasAccionistas, "Accionistas_Dia", fieldsCanillaAccionista, totalesAccionistas); + formatAndSheet(reportData.canillasTodos, "Resumen_Dia", fieldsTodos, totalesTodos); + formatAndSheet(reportData.canillasLiquidadasOtraFecha, "Canillitas_OtrasFechas", fieldsCanillaAccionistaFechaLiq, totalesCanillasOtraFecha); + formatAndSheet(reportData.canillasAccionistasLiquidadasOtraFecha, "Accionistas_OtrasFechas", fieldsCanillaAccionistaFechaLiq, totalesAccionistasOtraFecha); + formatAndSheet(reportData.controlDevolucionesDetalle, "CtrlDev_Detalle", fieldsCtrlDevDetalle); // Sin totales para estos + formatAndSheet(reportData.controlDevolucionesRemitos, "CtrlDev_Remitos", fieldsCtrlDevRemitos); + formatAndSheet(reportData.controlDevolucionesOtrosDias, "CtrlDev_OtrosDias", fieldsCtrlDevOtrosDias); + + let fileName = "ReporteDetalleDistribucionCanillitas"; if (currentParams) { - fileName += `_${currentParams.nombrePublicacion?.replace(/\s+/g, '') ?? `Pub${currentParams.idPublicacion}`}`; - fileName += `_${currentParams.mesAnioParaNombreArchivo?.replace('/', '-')}`; + fileName += `_${currentParams.nombreEmpresa?.replace(/\s+/g, '') ?? `Emp${currentParams.idEmpresa}`}`; + fileName += `_${currentParams.fecha}`; } fileName += ".xlsx"; XLSX.writeFile(wb, fileName); - }, [reportData, currentParams]); + }, [reportData, currentParams, totalesCanillas, totalesAccionistas, totalesTodos, totalesCanillasOtraFecha, totalesAccionistasOtraFecha]); - const handleGenerarYAbrirPdf = useCallback(async () => { + const handleGenerarYAbrirPdf = useCallback(async (soloTotales: boolean) => { if (!currentParams) { setError("Primero debe generar el reporte en pantalla o seleccionar parámetros."); return; } setLoadingPdf(true); setError(null); + setPdfSoloTotales(soloTotales); try { - const blob = await reportesService.getListadoDistribucionGeneralPdf({ - idPublicacion: currentParams.idPublicacion, - fechaDesde: currentParams.fechaDesde, // El servicio y SP esperan fechaDesde para el mes/año - fechaHasta: currentParams.fechaHasta // El SP no usa esta, pero el servicio de reporte sí para el nombre + const blob = await reportesService.getReporteDistribucionCanillasPdf({ + ...currentParams, + soloTotales }); if (blob.type === "application/json") { const text = await blob.text(); @@ -141,13 +241,99 @@ const ReporteListadoDistribucionGeneralPage: React.FC = () => { } }, [currentParams]); + // Definiciones de columnas + const commonColumns: GridColDef[] = [ + { field: 'publicacion', headerName: 'Publicación', width: 200, flex: 1.2 }, + { field: 'canilla', headerName: 'Canillita', width: 220, flex: 1.3 }, + { field: 'totalCantSalida', headerName: 'Llevados', type: 'number', width: 100, align: 'right', headerAlign: 'right', valueFormatter: (value) => numberFormatter(Number(value)) }, + { field: 'totalCantEntrada', headerName: 'Devueltos', type: 'number', width: 100, align: 'right', headerAlign: 'right', valueFormatter: (value) => numberFormatter(Number(value)) }, + { field: 'vendidos', headerName: 'Vendidos', type: 'number', width: 100, align: 'right', headerAlign: 'right', valueGetter: (_value, row) => (row.totalCantSalida || 0) - (row.totalCantEntrada || 0), valueFormatter: (value) => numberFormatter(Number(value)) }, + { field: 'totalRendir', headerName: 'A Rendir', type: 'number', width: 150, align: 'right', headerAlign: 'right', valueFormatter: (value) => currencyFormatter(Number(value)) }, + ]; + + const commonColumnsWithFecha: GridColDef[] = [ + { field: 'publicacion', headerName: 'Publicación', width: 200, flex: 1 }, + { field: 'canilla', headerName: 'Canillita', width: 220, flex: 1.1 }, + { field: 'fecha', headerName: 'Fecha Mov.', width: 120, flex: 0.7, valueFormatter: (value) => value ? new Date(value as string).toLocaleDateString('es-AR', { timeZone: 'UTC' }) : '-' }, + { field: 'totalCantSalida', headerName: 'Llevados', type: 'number', width: 100, align: 'right', headerAlign: 'right', valueFormatter: (value) => numberFormatter(Number(value)) }, + { field: 'totalCantEntrada', headerName: 'Devueltos', type: 'number', width: 100, align: 'right', headerAlign: 'right', valueFormatter: (value) => numberFormatter(Number(value))}, + { field: 'vendidos', headerName: 'Vendidos', type: 'number', width: 100, align: 'right', headerAlign: 'right', valueGetter: (_value, row) => (row.totalCantSalida || 0) - (row.totalCantEntrada || 0), valueFormatter: (value) => numberFormatter(Number(value)) }, + { field: 'totalRendir', headerName: 'A Rendir', type: 'number', width: 150, align: 'right', headerAlign: 'right', valueFormatter: (value) => currencyFormatter(Number(value)) }, + ]; + + const columnsTodos: GridColDef[] = [ + { field: 'publicacion', headerName: 'Publicación', width: 200, flex: 1.2 }, + { field: 'tipoVendedor', headerName: 'Tipo Vendedor', width: 150, flex: 0.8 }, + { field: 'totalCantSalida', headerName: 'Llevados', type: 'number', width: 100, align: 'right', headerAlign: 'right', valueFormatter: (value) => numberFormatter(Number(value)) }, + { field: 'totalCantEntrada', headerName: 'Devueltos', type: 'number', width: 100, align: 'right', headerAlign: 'right', valueFormatter: (value) => numberFormatter(Number(value))}, + { field: 'vendidos', headerName: 'Vendidos', type: 'number', width: 100, align: 'right', headerAlign: 'right', valueGetter: (_value, row) => (row.totalCantSalida || 0) - (row.totalCantEntrada || 0), valueFormatter: (value) => numberFormatter(Number(value)) }, + { field: 'totalRendir', headerName: 'A Rendir', type: 'number', width: 150, align: 'right', headerAlign: 'right', valueFormatter: (value) => currencyFormatter(Number(value)) }, + ]; + + const columnsCtrlDevDetalle: GridColDef[] = [ + { field: 'publicacion', headerName: 'Publicación', width: 200, flex: 1.5 }, + { field: 'tipo', headerName: 'Tipo', width: 100, flex: 0.8 }, + { field: 'ingresados', headerName: 'Ingresados', type: 'number', width: 100, align: 'right', headerAlign: 'right', valueFormatter: (v) => numberFormatter(Number(v))}, + { field: 'sobrantes', headerName: 'Sobrantes', type: 'number', width: 100, align: 'right', headerAlign: 'right', valueFormatter: (v) => numberFormatter(Number(v))}, + { field: 'sinCargo', headerName: 'Sin Cargo', type: 'number', width: 100, align: 'right', headerAlign: 'right', valueFormatter: (v) => numberFormatter(Number(v))}, + { field: 'llevados', headerName: 'Llevados', type: 'number', width: 100, align: 'right', headerAlign: 'right', valueFormatter: (v) => numberFormatter(Number(v))}, + { field: 'devueltos', headerName: 'Devueltos', type: 'number', width: 100, align: 'right', headerAlign: 'right', valueFormatter: (v) => numberFormatter(Number(v))}, + ]; + + const columnsCtrlDevRemitos: GridColDef[] = [ + { field: 'remito', headerName: 'Remito Ingresado', flex: 1 }, + ]; + + const columnsCtrlDevOtrosDias: GridColDef[] = [ + { field: 'devueltos', headerName: 'Devueltos Otros Días', flex: 1 }, + ]; + + // Memoizar filas (los IDs ya se añaden en handleGenerarReporte) + const rowsCanillas = useMemo(() => reportData?.canillas ?? [], [reportData]); + const rowsAccionistas = useMemo(() => reportData?.canillasAccionistas ?? [], [reportData]); + const rowsTodos = useMemo(() => reportData?.canillasTodos ?? [], [reportData]); + const rowsCanillasOtraFecha = useMemo(() => reportData?.canillasLiquidadasOtraFecha ?? [], [reportData]); + const rowsAccionistasOtraFecha = useMemo(() => reportData?.canillasAccionistasLiquidadasOtraFecha ?? [], [reportData]); + const rowsCtrlDevDetalle = useMemo(() => reportData?.controlDevolucionesDetalle ?? [], [reportData]); + const rowsCtrlDevRemitos = useMemo(() => reportData?.controlDevolucionesRemitos ?? [], [reportData]); + const rowsCtrlDevOtrosDias = useMemo(() => reportData?.controlDevolucionesOtrosDias ?? [], [reportData]); + + // --- Custom Footers --- + // eslint-disable-next-line react/display-name + const createCustomFooter = (totals: TotalesComunes, columns: GridColDef[]) => () => ( + + + + + + TOTALES: + {columns[1].field !== 'tipoVendedor' && /* Placeholder for Canilla/Tipo */ } + {columns[1].field === 'tipoVendedor' && /* Placeholder for Canilla/Tipo */ } + + {columns.find(c => c.field === 'fecha') && c.field === 'fecha')?.flex, width: columns.find(c=>c.field === 'fecha')?.width, textAlign: 'right', fontWeight: 'bold', pr:1 }}> /* Placeholder for Fecha */} + + c.field === 'totalCantSalida')?.width, textAlign: 'right', fontWeight: 'bold', pr:1 }}>{numberFormatter(totals.totalCantSalida)} + c.field === 'totalCantEntrada')?.width, textAlign: 'right', fontWeight: 'bold', pr:1 }}>{numberFormatter(totals.totalCantEntrada)} + c.field === 'vendidos')?.width, textAlign: 'right', fontWeight: 'bold', pr:1 }}>{numberFormatter(totals.vendidos)} + c.field === 'totalRendir')?.width, textAlign: 'right', fontWeight: 'bold' }}>{currencyFormatter(totals.totalRendir)} + + + ); + + const CustomFooterCanillas = useMemo(() => createCustomFooter(totalesCanillas, commonColumns), [totalesCanillas]); + const CustomFooterAccionistas = useMemo(() => createCustomFooter(totalesAccionistas, commonColumns), [totalesAccionistas]); + const CustomFooterTodos = useMemo(() => createCustomFooter(totalesTodos, columnsTodos), [totalesTodos]); + const CustomFooterCanillasOtraFecha = useMemo(() => createCustomFooter(totalesCanillasOtraFecha, commonColumnsWithFecha), [totalesCanillasOtraFecha]); + const CustomFooterAccionistasOtraFecha = useMemo(() => createCustomFooter(totalesAccionistasOtraFecha, commonColumnsWithFecha), [totalesAccionistasOtraFecha]); + + if (showParamSelector) { return ( - @@ -159,10 +345,13 @@ const ReporteListadoDistribucionGeneralPage: React.FC = () => { return ( - Reporte: Listado Distribución General - - + + + + + + + Publicación: {currentParams?.nombrePublicacion} | + Fechas: {currentParams?.fechaDesde} al {currentParams?.fechaHasta} + + + + {loading && } + {error && !loading && {error}} + + {!loading && !error && reportData && ( + <> + Detalle Diario + {rowsDetalle.length > 0 ? ( + {/* Ajusta altura según necesites */} + + + ) : (No hay datos de detalle diario.)} + + Promedios por Día de Semana + {rowsPromedios.length > 0 ? ( + {/* Ajusta altura según necesites */} + + + ) : (No hay datos de promedios por día.)} + + )} + {!loading && !error && (!reportData || ((!reportData.detalleSimple || reportData.detalleSimple.length === 0) && (!reportData.promediosPorDia || reportData.promediosPorDia.length === 0))) && + (No se encontraron datos para los criterios seleccionados.)} + + ); +}; + +export default ReporteListadoDistribucionPage; \ No newline at end of file diff --git a/Frontend/src/pages/Reportes/ReporteMovimientoBobinasEstadoPage.tsx b/Frontend/src/pages/Reportes/ReporteMovimientoBobinasEstadoPage.tsx index 2a8c54e..0a76d9e 100644 --- a/Frontend/src/pages/Reportes/ReporteMovimientoBobinasEstadoPage.tsx +++ b/Frontend/src/pages/Reportes/ReporteMovimientoBobinasEstadoPage.tsx @@ -1,14 +1,27 @@ -import React, { useState, useCallback } from 'react'; +import React, { useState, useCallback, useMemo } from 'react'; import { - Box, Typography, Paper, CircularProgress, Alert, Button, - TableContainer, Table, TableHead, TableRow, TableCell, TableBody + Box, Typography, Paper, CircularProgress, Alert, Button } from '@mui/material'; +import { DataGrid, type GridColDef, GridFooterContainer, GridFooter } from '@mui/x-data-grid'; +import { esES } from '@mui/x-data-grid/locales'; import reportesService from '../../services/Reportes/reportesService'; +// Corregir importaciones de DTOs import type { MovimientoBobinasPorEstadoResponseDto } from '../../models/dtos/Reportes/MovimientoBobinasPorEstadoResponseDto'; +import type { MovimientoBobinaEstadoDetalleDto } from '../../models/dtos/Reportes/MovimientoBobinaEstadoDetalleDto'; +import type { MovimientoBobinaEstadoTotalDto } from '../../models/dtos/Reportes/MovimientoBobinaEstadoTotalDto'; + import SeleccionaReporteMovimientoBobinasEstado from './SeleccionaReporteMovimientoBobinasEstado'; import * as XLSX from 'xlsx'; import axios from 'axios'; +// Interfaces extendidas para DataGrid con 'id' +interface DetalleMovimientoDataGrid extends MovimientoBobinaEstadoDetalleDto { // Usar el DTO correcto + id: string; +} +interface TotalPorEstadoDataGrid extends MovimientoBobinaEstadoTotalDto { // Usar el DTO correcto + id: string; +} + const ReporteMovimientoBobinasEstadoPage: React.FC = () => { const [reportData, setReportData] = useState(null); const [loading, setLoading] = useState(false); @@ -20,8 +33,16 @@ const ReporteMovimientoBobinasEstadoPage: React.FC = () => { fechaDesde: string; fechaHasta: string; idPlanta: number; + nombrePlanta?: string; } | null>(null); + const numberLocaleFormatter = (value: number | null | undefined) => + value != null ? Number(value).toLocaleString('es-AR') : ''; + + const dateLocaleFormatter = (value: string | null | undefined) => + value ? new Date(value).toLocaleDateString('es-AR', { timeZone: 'UTC' }) : '-'; + + const handleGenerarReporte = useCallback(async (params: { fechaDesde: string; fechaHasta: string; @@ -33,8 +54,14 @@ const ReporteMovimientoBobinasEstadoPage: React.FC = () => { setCurrentParams(params); try { const data = await reportesService.getMovimientoBobinasEstado(params); - setReportData(data); - if ((!data.detalle || data.detalle.length === 0) && (!data.totales || data.totales.length === 0)) { + + const processedData: MovimientoBobinasPorEstadoResponseDto = { + detalle: data.detalle?.map((item, index) => ({ ...item, id: `detalle-${index}-${item.numeroRemito}-${item.tipoBobina}` })) || [], + totales: data.totales?.map((item, index) => ({ ...item, id: `total-${index}-${item.tipoMovimiento}` })) || [] + }; + setReportData(processedData); + + if ((!processedData.detalle || processedData.detalle.length === 0) && (!processedData.totales || processedData.totales.length === 0)) { setError("No se encontraron datos para los parámetros seleccionados."); } setShowParamSelector(false); @@ -65,7 +92,6 @@ const ReporteMovimientoBobinasEstadoPage: React.FC = () => { const wb = XLSX.utils.book_new(); - // Hoja de Detalles if (reportData.detalle?.length) { const detalleToExport = reportData.detalle.map(item => ({ "Tipo Bobina": item.tipoBobina, @@ -76,18 +102,11 @@ const ReporteMovimientoBobinasEstadoPage: React.FC = () => { })); const wsDetalle = XLSX.utils.json_to_sheet(detalleToExport); const headersDetalle = Object.keys(detalleToExport[0] || {}); - wsDetalle['!cols'] = headersDetalle.map(h => { - const maxLen = detalleToExport.reduce((prev, row) => { - const cell = (row as any)[h]?.toString() ?? ''; - return Math.max(prev, cell.length); - }, h.length); - return { wch: maxLen + 2 }; - }); + wsDetalle['!cols'] = headersDetalle.map(h => ({ wch: Math.max(...detalleToExport.map(row => (row as any)[h]?.toString().length ?? 0), h.length) + 2 })); wsDetalle['!freeze'] = { xSplit: 0, ySplit: 1 }; XLSX.utils.book_append_sheet(wb, wsDetalle, "DetalleMovimientos"); } - // Hoja de Totales if (reportData.totales?.length) { const totalesToExport = reportData.totales.map(item => ({ "Tipo Movimiento": item.tipoMovimiento, @@ -96,20 +115,15 @@ const ReporteMovimientoBobinasEstadoPage: React.FC = () => { })); const wsTotales = XLSX.utils.json_to_sheet(totalesToExport); const headersTotales = Object.keys(totalesToExport[0] || {}); - wsTotales['!cols'] = headersTotales.map(h => { - const maxLen = totalesToExport.reduce((prev, row) => { - const cell = (row as any)[h]?.toString() ?? ''; - return Math.max(prev, cell.length); - }, h.length); - return { wch: maxLen + 2 }; - }); + wsTotales['!cols'] = headersTotales.map(h => ({ wch: Math.max(...totalesToExport.map(row => (row as any)[h]?.toString().length ?? 0), h.length) + 2 })); wsTotales['!freeze'] = { xSplit: 0, ySplit: 1 }; XLSX.utils.book_append_sheet(wb, wsTotales, "TotalesPorEstado"); } let fileName = "ReporteMovimientoBobinasEstado"; if (currentParams) { - fileName += `_${currentParams.fechaDesde}_a_${currentParams.fechaHasta}_Planta${currentParams.idPlanta}`; + fileName += `_${currentParams.nombrePlanta || `Planta${currentParams.idPlanta}`}`; + fileName += `_${currentParams.fechaDesde}_a_${currentParams.fechaHasta}`; } fileName += ".xlsx"; XLSX.writeFile(wb, fileName); @@ -140,6 +154,56 @@ const ReporteMovimientoBobinasEstadoPage: React.FC = () => { } }, [currentParams]); + // Columnas para DataGrid de Detalle de Movimientos + const columnsDetalle: GridColDef[] = [ // Tipar con la interfaz correcta + { field: 'tipoBobina', headerName: 'Tipo Bobina', width: 220, flex: 1.5 }, + { field: 'numeroRemito', headerName: 'Nro Remito', width: 130, flex: 0.8 }, + { field: 'fechaMovimiento', headerName: 'Fecha Movimiento', width: 150, flex: 1, valueFormatter: (value) => dateLocaleFormatter(value as string) }, + { field: 'cantidad', headerName: 'Cantidad', type: 'number', width: 120, align: 'right', headerAlign: 'right', flex: 0.7, valueFormatter: (value) => numberLocaleFormatter(Number(value)) }, + { field: 'tipoMovimiento', headerName: 'Tipo Movimiento', width: 150, flex: 1 }, + ]; + + // Columnas para DataGrid de Totales por Estado + const columnsTotales: GridColDef[] = [ // Tipar con la interfaz correcta + { field: 'tipoMovimiento', headerName: 'Tipo Movimiento', width: 200, flex: 1 }, + { field: 'totalBobinas', headerName: 'Total Bobinas', type: 'number', width: 150, align: 'right', headerAlign: 'right', flex: 0.8, valueFormatter: (value) => numberLocaleFormatter(Number(value)) }, + { field: 'totalKilos', headerName: 'Total Kilos', type: 'number', width: 150, align: 'right', headerAlign: 'right', flex: 0.8, valueFormatter: (value) => numberLocaleFormatter(Number(value)) }, + ]; + + const rowsDetalle = useMemo(() => (reportData?.detalle as DetalleMovimientoDataGrid[]) || [], [reportData]); + const rowsTotales = useMemo(() => (reportData?.totales as TotalPorEstadoDataGrid[]) || [], [reportData]); + + // eslint-disable-next-line react/display-name + const CustomFooterDetalle = () => ( + `1px solid ${theme.palette.divider}`, + minHeight: '52px', + }}> + + theme.spacing(1), + paddingRight: (theme) => theme.spacing(1), + }, + '& .MuiDataGrid-selectedRowCount': { display: 'none' }, + }} + /> + + + ); + + if (showParamSelector) { return ( @@ -154,11 +218,12 @@ const ReporteMovimientoBobinasEstadoPage: React.FC = () => { ); } + return ( - Reporte: Movimiento de Bobinas por Estado + Reporte: Movimiento de Bobinas por Estado {currentParams?.nombrePlanta ? `(${currentParams.nombrePlanta})` : ''} + + Publicación: {currentParams?.nombrePublicacion} | + Planta: {currentParams?.nombrePlanta} | + Mes Consultado: {currentParams?.mesAnioParaNombreArchivo} + - {loading && } - {error && !loading && {error}} + {loading && } + {error && !loading && {error}} {!loading && !error && reportData.length > 0 && ( - - - - - Nombre Sección - Total Páginas Imp. - Cant. Ediciones - Total Pág. x Edición - Total Ejemplares - Prom. Pág./Ejemplar - - - - {reportData.map((row, idx) => ( - - {row.nombreSeccion} - {row.totalPaginasImpresas.toLocaleString('es-AR')} - {row.cantidadTiradas.toLocaleString('es-AR')} - {row.totalPaginasEjemplares.toLocaleString('es-AR')} - {row.totalEjemplares.toLocaleString('es-AR')} - {row.promedioPaginasPorEjemplar.toLocaleString('es-AR')} - - ))} - -
-
+ + + )} - {!loading && !error && reportData.length === 0 && (No se encontraron datos para los criterios seleccionados.)} + {!loading && !error && reportData.length === 0 && currentParams && (No se encontraron datos para los criterios seleccionados.)}
); }; diff --git a/Frontend/src/pages/Reportes/ReporteVentaMensualSecretariaPage.tsx b/Frontend/src/pages/Reportes/ReporteVentaMensualSecretariaPage.tsx index 9f689b2..95dbe2c 100644 --- a/Frontend/src/pages/Reportes/ReporteVentaMensualSecretariaPage.tsx +++ b/Frontend/src/pages/Reportes/ReporteVentaMensualSecretariaPage.tsx @@ -240,13 +240,14 @@ const ReporteVentaMensualSecretariaPage: React.FC = () => { - Día + El Día Popular Clarín Nación + Día Tir. Coop.Dev. Coop. Vta. Coop.Vta. Can. TiradaDevol.Venta diff --git a/Frontend/src/pages/Reportes/ReportesIndexPage.tsx b/Frontend/src/pages/Reportes/ReportesIndexPage.tsx index 245df27..14a641e 100644 --- a/Frontend/src/pages/Reportes/ReportesIndexPage.tsx +++ b/Frontend/src/pages/Reportes/ReportesIndexPage.tsx @@ -1,98 +1,243 @@ -import React, { useState, useEffect } from 'react'; -import { Box, Tabs, Tab, Paper, Typography } from '@mui/material'; +import React, { useState, useEffect, useMemo } from 'react'; +import { Box, Paper, Typography, List, ListItemButton, ListItemText, Collapse, CircularProgress } from '@mui/material'; import { Outlet, useNavigate, useLocation } from 'react-router-dom'; +import ExpandLess from '@mui/icons-material/ExpandLess'; +import ExpandMore from '@mui/icons-material/ExpandMore'; -const reportesSubModules = [ - { label: 'Existencia de Papel', path: 'existencia-papel' }, - { label: 'Movimiento de Bobinas', path: 'movimiento-bobinas' }, - { label: 'Mov. Bobinas por Estado', path: 'movimiento-bobinas-estado' }, - { label: 'Distribución General', path: 'listado-distribucion-general' }, - { label: 'Distribución Canillas', path: 'listado-distribucion-canillas' }, - { label: 'Distrib. Canillas (Importe)', path: 'listado-distribucion-canillas-importe' }, - { label: 'Venta Mensual Secretaría', path: 'venta-mensual-secretaria' }, - { label: 'Det. Distribución Canillas', path: 'detalle-distribucion-canillas' }, - { label: 'Tiradas Pub./Sección', path: 'tiradas-publicaciones-secciones' }, - { label: 'Consumo Bobinas/Sección', path: 'consumo-bobinas-seccion' }, - { label: 'Consumo Bobinas/Pub.', path: 'consumo-bobinas-publicacion' }, - { label: 'Comparativa Cons. Bobinas', path: 'comparativa-consumo-bobinas' }, - { label: 'Cuentas Distribuidores', path: 'cuentas-distribuidores' }, +// Definición de los módulos de reporte con sus categorías, etiquetas y rutas +const allReportModules: { category: string; label: string; path: string }[] = [ + { category: 'Existencia Papel', label: 'Existencia de Papel', path: 'existencia-papel' }, + { category: 'Movimientos Bobinas', label: 'Movimiento de Bobinas', path: 'movimiento-bobinas' }, + { category: 'Movimientos Bobinas', label: 'Mov. Bobinas por Estado', path: 'movimiento-bobinas-estado' }, + { category: 'Listados Distribución', label: 'Distribución Distribuidores', path: 'listado-distribucion-distribuidores' }, + { category: 'Listados Distribución', label: 'Distribución Canillas', path: 'listado-distribucion-canillas' }, + { category: 'Listados Distribución', label: 'Distribución General', path: 'listado-distribucion-general' }, + { category: 'Listados Distribución', label: 'Distrib. Canillas (Importe)', path: 'listado-distribucion-canillas-importe' }, + { category: 'Listados Distribución', label: 'Detalle Distribución Canillas', path: 'detalle-distribucion-canillas' }, + { category: 'Secretaría', label: 'Venta Mensual Secretaría', path: 'venta-mensual-secretaria' }, + { category: 'Tiradas por Publicación', label: 'Tiradas Publicación/Sección', path: 'tiradas-publicaciones-secciones' }, + { category: 'Consumos Bobinas', label: 'Consumo Bobinas/Sección', path: 'consumo-bobinas-seccion' }, + { category: 'Consumos Bobinas', label: 'Consumo Bobinas/PubPublicación', path: 'consumo-bobinas-publicacion' }, + { category: 'Consumos Bobinas', label: 'Comparativa Consumo Bobinas', path: 'comparativa-consumo-bobinas' }, + { category: 'Balance de Cuentas', label: 'Cuentas Distribuidores', path: 'cuentas-distribuidores' }, + { category: 'Ctrl. Devoluciones', label: 'Control de Devoluciones', path: 'control-devoluciones' }, ]; +const predefinedCategoryOrder = [ + 'Balance de Cuentas', + 'Listados Distribución', + 'Ctrl. Devoluciones', + 'Existencia Papel', + 'Movimientos Bobinas', + 'Consumos Bobinas', + 'Tiradas por Publicación', + 'Secretaría', +]; + + const ReportesIndexPage: React.FC = () => { const navigate = useNavigate(); const location = useLocation(); - const [selectedSubTab, setSelectedSubTab] = useState(false); + + const [expandedCategory, setExpandedCategory] = useState(false); + const [isLoadingInitialNavigation, setIsLoadingInitialNavigation] = useState(true); + + const uniqueCategories = useMemo(() => predefinedCategoryOrder, []); useEffect(() => { const currentBasePath = '/reportes'; - // Extrae la parte de la ruta que sigue a '/reportes/' - const subPathSegment = location.pathname.startsWith(currentBasePath + '/') - ? location.pathname.substring(currentBasePath.length + 1).split('/')[0] // Toma solo el primer segmento - : undefined; + const pathParts = location.pathname.substring(currentBasePath.length + 1).split('/'); + const subPathSegment = pathParts[0]; - let activeTabIndex = -1; + let activeReportFoundInEffect = false; - if (subPathSegment) { - activeTabIndex = reportesSubModules.findIndex( - (subModule) => subModule.path === subPathSegment - ); - } - - if (activeTabIndex !== -1) { - setSelectedSubTab(activeTabIndex); - } else { - // Si estamos exactamente en '/reportes' y hay sub-módulos, navegar al primero. - if (location.pathname === currentBasePath && reportesSubModules.length > 0) { - navigate(reportesSubModules[0].path, { replace: true }); // Navega a la sub-ruta - // setSelectedSubTab(0); // Esto se manejará en la siguiente ejecución del useEffect debido al cambio de ruta + if (subPathSegment && subPathSegment !== "") { // Asegurarse que subPathSegment no esté vacío + const activeReport = allReportModules.find(module => module.path === subPathSegment); + if (activeReport) { + setExpandedCategory(activeReport.category); + activeReportFoundInEffect = true; } else { - setSelectedSubTab(false); // Ninguna sub-ruta activa o conocida, o no hay sub-módulos + setExpandedCategory(false); } + } else { + setExpandedCategory(false); + } + + if (location.pathname === currentBasePath && allReportModules.length > 0 && isLoadingInitialNavigation) { + let firstReportToNavigate: { category: string; label: string; path: string } | null = null; + for (const category of uniqueCategories) { + const reportsInCat = allReportModules.filter(r => r.category === category); + if (reportsInCat.length > 0) { + firstReportToNavigate = reportsInCat[0]; + break; + } + } + if (firstReportToNavigate) { + navigate(firstReportToNavigate.path, { replace: true }); + activeReportFoundInEffect = true; + } + } + // Solo se establece a false si no estamos en el proceso de navegación inicial O si no se encontró reporte + if (!activeReportFoundInEffect || location.pathname !== currentBasePath) { + setIsLoadingInitialNavigation(false); } - }, [location.pathname, navigate]); // Solo depende de location.pathname y navigate - const handleSubTabChange = (_event: React.SyntheticEvent, newValue: number) => { - // No es necesario setSelectedSubTab aquí directamente, el useEffect lo manejará. - navigate(reportesSubModules[newValue].path); + }, [location.pathname, navigate, uniqueCategories, isLoadingInitialNavigation]); + + const handleCategoryClick = (categoryName: string) => { + setExpandedCategory(prev => (prev === categoryName ? false : categoryName)); }; - // Si no hay sub-módulos definidos, podría ser un estado inicial - if (reportesSubModules.length === 0) { + const handleReportClick = (reportPath: string) => { + navigate(reportPath); + }; + + const isReportActive = (reportPath: string) => { + return location.pathname === `/reportes/${reportPath}` || location.pathname.startsWith(`/reportes/${reportPath}/`); + }; + + // Si isLoadingInitialNavigation es true Y estamos en /reportes, mostrar loader + // Esto evita mostrar el loader si se navega directamente a un sub-reporte. + if (isLoadingInitialNavigation && (location.pathname === '/reportes' || location.pathname === '/reportes/')) { return ( - - Módulo de Reportes - No hay reportes configurados. + + ); } return ( - - - Módulo de Reportes - - - + {/* Panel Lateral para Navegación */} + `1px solid ${theme.palette.divider}`, + overflowY: 'auto', + bgcolor: 'background.paper', // O el color que desees para el menú + // display: 'flex', flexDirection: 'column' // Para que el título y la lista usen el espacio vertical + }} + > + {/* Título del Menú Lateral */} + `1px solid ${theme.palette.divider}`, // Opcional: separador + // position: 'sticky', // Si quieres que el título quede fijo al hacer scroll en la lista + // top: 0, + // zIndex: 1, + // bgcolor: 'background.paper' // Necesario si es sticky y tiene scroll la lista + }} > - {reportesSubModules.map((subModule) => ( - - ))} - + + Reportes + + + + {/* Lista de Categorías y Reportes */} + {uniqueCategories.length > 0 ? ( + + {uniqueCategories.map((category) => { + const reportsInCategory = allReportModules.filter(r => r.category === category); + const isExpanded = expandedCategory === category; + + return ( + + handleCategoryClick(category)} + sx={{ + // py: 1.2, // Ajustar padding vertical de items de categoría + // backgroundColor: isExpanded ? 'action.selected' : 'transparent', + borderLeft: isExpanded ? (theme) => `4px solid ${theme.palette.primary.main}` : '4px solid transparent', + pr: 1, // Menos padding a la derecha para dar espacio al ícono expander + '&:hover': { + backgroundColor: 'action.hover' + } + }} + > + + {reportsInCategory.length > 0 && (isExpanded ? : )} + + {reportsInCategory.length > 0 && ( + + + {reportsInCategory.map((report) => ( + handleReportClick(report.path)} + sx={{ + pl: 3.5, // Indentación para los reportes (ajustar si se cambió el padding del título) + py: 0.8, // Padding vertical de items de reporte + ...(isReportActive(report.path) && { + backgroundColor: (theme) => theme.palette.action.selected, // Un color de fondo sutil + borderLeft: (theme) => `4px solid ${theme.palette.primary.light}`, // Un borde para el activo + '& .MuiListItemText-primary': { + fontWeight: 'medium', // O 'bold' + // color: 'primary.main' + }, + }), + '&:hover': { + backgroundColor: (theme) => theme.palette.action.hover + } + }} + > + + + ))} + + + )} + {reportsInCategory.length === 0 && isExpanded && ( + + )} + + ); + })} + + ) : ( + No hay categorías configuradas. + )} - - {/* Outlet renderizará ReporteExistenciaPapelPage u otros - Solo renderiza el Outlet si hay una pestaña seleccionada VÁLIDA. - Si selectedSubTab es 'false' (porque ninguna ruta coincide con los sub-módulos), - se muestra el mensaje. - */} - {selectedSubTab !== false ? : Seleccione un reporte del menú lateral o de las pestañas.} + + {/* Área Principal para el Contenido del Reporte */} + + {/* El Outlet renderiza el componente del reporte específico */} + {(!location.pathname.startsWith('/reportes/') || !allReportModules.some(r => isReportActive(r.path))) && location.pathname !== '/reportes/' && location.pathname !== '/reportes' && !isLoadingInitialNavigation && ( + + El reporte solicitado no existe o la ruta no es válida. + + )} + {(location.pathname === '/reportes/' || location.pathname === '/reportes') && !allReportModules.some(r => isReportActive(r.path)) && !isLoadingInitialNavigation && ( + + {allReportModules.length > 0 ? "Seleccione una categoría y un reporte del menú lateral." : "No hay reportes configurados."} + + )} + ); diff --git a/Frontend/src/pages/Reportes/SeleccionaReporteControlDevoluciones.tsx b/Frontend/src/pages/Reportes/SeleccionaReporteControlDevoluciones.tsx new file mode 100644 index 0000000..65ad36e --- /dev/null +++ b/Frontend/src/pages/Reportes/SeleccionaReporteControlDevoluciones.tsx @@ -0,0 +1,112 @@ +import React, { useState, useEffect } from 'react'; +import { + Box, Typography, TextField, Button, CircularProgress, Alert, + FormControl, InputLabel, Select, MenuItem +} from '@mui/material'; +import empresaService from '../../services/Distribucion/empresaService'; +import type { EmpresaDto } from '../../models/dtos/Distribucion/EmpresaDto'; + +interface SeleccionaReporteControlDevolucionesProps { + onGenerarReporte: (params: { + fecha: string; + idEmpresa: number; + }) => Promise; + onCancel?: () => void; // Hacer onCancel opcional si no siempre se usa + isLoading?: boolean; + apiErrorMessage?: string | null; +} + +const SeleccionaReporteControlDevoluciones: React.FC = ({ + onGenerarReporte, + onCancel, + isLoading, + apiErrorMessage +}) => { + const [fecha, setFecha] = useState(new Date().toISOString().split('T')[0]); + const [idEmpresa, setIdEmpresa] = useState(''); + const [empresas, setEmpresas] = useState([]); + const [loadingEmpresas, setLoadingEmpresas] = useState(false); + const [localError, setLocalError] = useState(null); + + useEffect(() => { + const fetchEmpresas = async () => { + setLoadingEmpresas(true); + try { + const data = await empresaService.getAllEmpresas(); // Solo habilitadas + setEmpresas(data); + } catch (error) { + console.error("Error al cargar empresas:", error); + setLocalError('Error al cargar empresas.'); + } finally { + setLoadingEmpresas(false); + } + }; + fetchEmpresas(); + }, []); + + const validate = (): boolean => { + if (!fecha) { + setLocalError('Debe seleccionar una fecha.'); + return false; + } + if (!idEmpresa) { + setLocalError('Debe seleccionar una empresa.'); + return false; + } + setLocalError(null); + return true; + }; + + const handleGenerar = () => { + if (!validate()) return; + onGenerarReporte({ + fecha, + idEmpresa: Number(idEmpresa) + }); + }; + + return ( + + + Parámetros: Control de Devoluciones + + setFecha(e.target.value)} + margin="normal" + fullWidth + required + disabled={isLoading} + InputLabelProps={{ shrink: true }} + /> + + Empresa + + + + {(localError && !apiErrorMessage) && {localError}} + {apiErrorMessage && {apiErrorMessage}} + + + {onCancel && } + + + + ); +}; + +export default SeleccionaReporteControlDevoluciones; \ No newline at end of file diff --git a/Frontend/src/pages/Reportes/SeleccionaReporteListadoDistribucion.tsx b/Frontend/src/pages/Reportes/SeleccionaReporteListadoDistribucion.tsx new file mode 100644 index 0000000..4d0e0e8 --- /dev/null +++ b/Frontend/src/pages/Reportes/SeleccionaReporteListadoDistribucion.tsx @@ -0,0 +1,157 @@ +import React, { useState, useEffect } from 'react'; +import { + Box, Typography, TextField, Button, CircularProgress, Alert, + FormControl, InputLabel, Select, MenuItem +} from '@mui/material'; +import type { PublicacionDto } from '../../models/dtos/Distribucion/PublicacionDto'; +import publicacionService from '../../services/Distribucion/publicacionService'; +import type { DistribuidorDto } from '../../models/dtos/Distribucion/DistribuidorDto'; +import distribuidorService from '../../services/Distribucion/distribuidorService'; + +interface SeleccionaReporteListadoDistribucionProps { + onGenerarReporte: (params: { + idDistribuidor: number; + idPublicacion: number; + fechaDesde: string; + fechaHasta: string; + }) => Promise; + onCancel: () => void; + isLoading?: boolean; + apiErrorMessage?: string | null; +} + +const SeleccionaReporteListadoDistribucion: React.FC = ({ + onGenerarReporte, + isLoading, + apiErrorMessage +}) => { + const [idDistribuidor, setIdDistribuidor] = useState(''); + const [idPublicacion, setIdPublicacion] = useState(''); + const [fechaDesde, setFechaDesde] = useState(new Date().toISOString().split('T')[0]); + const [fechaHasta, setFechaHasta] = useState(new Date().toISOString().split('T')[0]); + + const [distribuidores, setDistribuidores] = useState([]); + const [publicaciones, setPublicaciones] = useState([]); + const [loadingDropdowns, setLoadingDropdowns] = useState(false); + const [localErrors, setLocalErrors] = useState<{ [key: string]: string | null }>({}); + + useEffect(() => { + const fetchData = async () => { + setLoadingDropdowns(true); + try { + const [distData, pubData] = await Promise.all([ + distribuidorService.getAllDistribuidores(), + publicacionService.getAllPublicaciones(undefined, undefined, true) // Solo habilitadas + ]); + setDistribuidores(distData.map(d => d)); + setPublicaciones(pubData.map(p => p)); + } catch (error) { + console.error("Error al cargar datos:", error); + setLocalErrors(prev => ({ ...prev, dropdowns: 'Error al cargar datos para los selectores.' })); + } finally { + setLoadingDropdowns(false); + } + }; + fetchData(); + }, []); + + const validate = (): boolean => { + const errors: { [key: string]: string | null } = {}; + if (!idDistribuidor) errors.idDistribuidor = 'Debe seleccionar un distribuidor.'; + if (!idPublicacion) errors.idPublicacion = 'Debe seleccionar una publicación.'; + if (!fechaDesde) errors.fechaDesde = 'Fecha Desde es obligatoria.'; + if (!fechaHasta) errors.fechaHasta = 'Fecha Hasta es obligatoria.'; + if (fechaDesde && fechaHasta && new Date(fechaDesde) > new Date(fechaHasta)) { + errors.fechaHasta = 'Fecha Hasta no puede ser anterior a Fecha Desde.'; + } + setLocalErrors(errors); + return Object.keys(errors).length === 0; + }; + + const handleGenerar = () => { + if (!validate()) return; + onGenerarReporte({ + idDistribuidor: Number(idDistribuidor), + idPublicacion: Number(idPublicacion), + fechaDesde, + fechaHasta + }); + }; + + return ( + + + Parámetros: Listado Distribución (Distribuidores) + + + Distribuidor + + {localErrors.idDistribuidor && {localErrors.idDistribuidor}} + + + + Publicación + + {localErrors.idPublicacion && {localErrors.idPublicacion}} + + + { setFechaDesde(e.target.value); setLocalErrors(p => ({ ...p, fechaDesde: null, fechaHasta: null })); }} + margin="normal" + fullWidth + required + error={!!localErrors.fechaDesde} + helperText={localErrors.fechaDesde} + disabled={isLoading} + InputLabelProps={{ shrink: true }} + /> + { setFechaHasta(e.target.value); setLocalErrors(p => ({ ...p, fechaHasta: null })); }} + margin="normal" + fullWidth + required + error={!!localErrors.fechaHasta} + helperText={localErrors.fechaHasta} + disabled={isLoading} + InputLabelProps={{ shrink: true }} + /> + + {apiErrorMessage && {apiErrorMessage}} + {localErrors.dropdowns && {localErrors.dropdowns}} + + + + + + ); +}; + +export default SeleccionaReporteListadoDistribucion; \ No newline at end of file diff --git a/Frontend/src/pages/Reportes/SeleccionaReporteListadoDistribucionCanillasImporte.tsx b/Frontend/src/pages/Reportes/SeleccionaReporteListadoDistribucionCanillasImporte.tsx index d4de130..38f2724 100644 --- a/Frontend/src/pages/Reportes/SeleccionaReporteListadoDistribucionCanillasImporte.tsx +++ b/Frontend/src/pages/Reportes/SeleccionaReporteListadoDistribucionCanillasImporte.tsx @@ -1,7 +1,9 @@ import React, { useState, useEffect } from 'react'; import { Box, Typography, TextField, Button, CircularProgress, Alert, - FormControl, InputLabel, Select, MenuItem, FormControlLabel, Checkbox + FormControl, InputLabel, Select, MenuItem, + ToggleButtonGroup, + ToggleButton } from '@mui/material'; import type { PublicacionDto } from '../../models/dtos/Distribucion/PublicacionDto'; import publicacionService from '../../services/Distribucion/publicacionService'; @@ -116,17 +118,65 @@ const SeleccionaReporteListadoDistribucionCanillasImporte: React.FC - setEsAccionista(e.target.checked)} - disabled={isLoading} - /> - } - label="Ver Accionistas" - sx={{ mt: 1, mb: 1 }} - /> + + + Tipo de reporte + + { + if (value !== null) setEsAccionista(value === 'accionistas'); + }} + aria-label="Tipo de reporte" + disabled={isLoading} + color="primary" + size="large" + sx={{ + borderRadius: 2, + boxShadow: 2, + backgroundColor: '#f5f5f5', + p: 0.5, + }} + > + + Canillitas + + + Accionistas + + + {apiErrorMessage && {apiErrorMessage}} {localErrors.dropdowns && {localErrors.dropdowns}} diff --git a/Frontend/src/routes/AppRoutes.tsx b/Frontend/src/routes/AppRoutes.tsx index cda055d..daf96a9 100644 --- a/Frontend/src/routes/AppRoutes.tsx +++ b/Frontend/src/routes/AppRoutes.tsx @@ -67,6 +67,8 @@ import ReporteConsumoBobinasSeccionPage from '../pages/Reportes/ReporteConsumoBo import ReporteConsumoBobinasPublicacionPage from '../pages/Reportes/ReporteConsumoBobinasPublicacionPage'; import ReporteComparativaConsumoBobinasPage from '../pages/Reportes/ReporteComparativaConsumoBobinasPage'; import ReporteCuentasDistribuidoresPage from '../pages/Reportes/ReporteCuentasDistribuidoresPage'; +import ReporteListadoDistribucionPage from '../pages/Reportes/ReporteListadoDistribucionPage'; +import ReporteControlDevolucionesPage from '../pages/Reportes/ReporteControlDevolucionesPage'; // Auditorias import GestionarAuditoriaUsuariosPage from '../pages/Usuarios/Auditoria/GestionarAuditoriaUsuariosPage'; @@ -177,6 +179,8 @@ const AppRoutes = () => { } /> } /> } /> + } /> + } /> {/* Módulo de Radios (anidado) */} diff --git a/Frontend/src/services/Reportes/reportesService.ts b/Frontend/src/services/Reportes/reportesService.ts index 322e6d0..53ea6d7 100644 --- a/Frontend/src/services/Reportes/reportesService.ts +++ b/Frontend/src/services/Reportes/reportesService.ts @@ -14,345 +14,393 @@ import type { ConsumoBobinasSeccionDto } from '../../models/dtos/Reportes/Consum import type { ConsumoBobinasPublicacionDto } from '../../models/dtos/Reportes/ConsumoBobinasPublicacionDto'; import type { ComparativaConsumoBobinasDto } from '../../models/dtos/Reportes/ComparativaConsumoBobinasDto'; import type { ReporteCuentasDistribuidorResponseDto } from '../../models/dtos/Reportes/ReporteCuentasDistribuidorResponseDto'; +import type { ListadoDistribucionDistribuidoresResponseDto } from '../../models/dtos/Reportes/ListadoDistribucionDistribuidoresResponseDto'; +import type { ControlDevolucionesDataResponseDto } from '../../models/dtos/Reportes/ControlDevolucionesDataResponseDto'; interface GetExistenciaPapelParams { - fechaDesde: string; // yyyy-MM-dd - fechaHasta: string; // yyyy-MM-dd - idPlanta?: number | null; - consolidado: boolean; + fechaDesde: string; // yyyy-MM-dd + fechaHasta: string; // yyyy-MM-dd + idPlanta?: number | null; + consolidado: boolean; } const getExistenciaPapelPdf = async (params: GetExistenciaPapelParams): Promise => { - const queryParams: Record = { - fechaDesde: params.fechaDesde, - fechaHasta: params.fechaHasta, - consolidado: params.consolidado, - }; - if (params.idPlanta && !params.consolidado) { - queryParams.idPlanta = params.idPlanta; - } + const queryParams: Record = { + fechaDesde: params.fechaDesde, + fechaHasta: params.fechaHasta, + consolidado: params.consolidado, + }; + if (params.idPlanta && !params.consolidado) { + queryParams.idPlanta = params.idPlanta; + } - const response = await apiClient.get('/reportes/existencia-papel/pdf', { - params: queryParams, - responseType: 'blob', // ¡Importante para descargar archivos! - }); - return response.data; // response.data será un Blob + const response = await apiClient.get('/reportes/existencia-papel/pdf', { + params: queryParams, + responseType: 'blob', // ¡Importante para descargar archivos! + }); + return response.data; // response.data será un Blob }; const getExistenciaPapel = async (params: GetExistenciaPapelParams): Promise => { - // Construir los query params, omitiendo idPlanta si es consolidado o no está definido - const queryParams: Record = { - fechaDesde: params.fechaDesde, - fechaHasta: params.fechaHasta, - consolidado: params.consolidado, - }; - if (params.idPlanta && !params.consolidado) { - queryParams.idPlanta = params.idPlanta; - } + // Construir los query params, omitiendo idPlanta si es consolidado o no está definido + const queryParams: Record = { + fechaDesde: params.fechaDesde, + fechaHasta: params.fechaHasta, + consolidado: params.consolidado, + }; + if (params.idPlanta && !params.consolidado) { + queryParams.idPlanta = params.idPlanta; + } - const response = await apiClient.get('/reportes/existencia-papel', { params: queryParams }); - return response.data; + const response = await apiClient.get('/reportes/existencia-papel', { params: queryParams }); + return response.data; }; const getMovimientoBobinas = async (params: { - fechaDesde: string; - fechaHasta: string; - idPlanta: number; + fechaDesde: string; + fechaHasta: string; + idPlanta: number; }): Promise => { - const response = await apiClient.get('/reportes/movimiento-bobinas', { params }); - return response.data; + const response = await apiClient.get('/reportes/movimiento-bobinas', { params }); + return response.data; }; const getMovimientoBobinasPdf = async (params: { - fechaDesde: string; - fechaHasta: string; - idPlanta: number; + fechaDesde: string; + fechaHasta: string; + idPlanta: number; }): Promise => { - const response = await apiClient.get('/reportes/movimiento-bobinas/pdf', { - params, - responseType: 'blob', - }); - return response.data; + const response = await apiClient.get('/reportes/movimiento-bobinas/pdf', { + params, + responseType: 'blob', + }); + return response.data; }; const getMovimientoBobinasEstado = async (params: { - fechaDesde: string; - fechaHasta: string; - idPlanta: number; + fechaDesde: string; + fechaHasta: string; + idPlanta: number; }): Promise => { // <- Devuelve el DTO combinado - const response = await apiClient.get('/reportes/movimiento-bobinas-estado', { params }); - return response.data; + const response = await apiClient.get('/reportes/movimiento-bobinas-estado', { params }); + return response.data; }; const getMovimientoBobinasEstadoPdf = async (params: { - fechaDesde: string; - fechaHasta: string; - idPlanta: number; + fechaDesde: string; + fechaHasta: string; + idPlanta: number; }): Promise => { - const response = await apiClient.get('/reportes/movimiento-bobinas-estado/pdf', { - params, - responseType: 'blob', - }); - return response.data; + const response = await apiClient.get('/reportes/movimiento-bobinas-estado/pdf', { + params, + responseType: 'blob', + }); + return response.data; }; const getListadoDistribucionGeneral = async (params: { - idPublicacion: number; - fechaDesde: string; // YYYY-MM-DD (primer día del mes) - fechaHasta: string; // YYYY-MM-DD (último día del mes) + idPublicacion: number; + fechaDesde: string; // YYYY-MM-DD (primer día del mes) + fechaHasta: string; // YYYY-MM-DD (último día del mes) }): Promise => { - const response = await apiClient.get('/reportes/listado-distribucion-general', { params }); - return response.data; + const response = await apiClient.get('/reportes/listado-distribucion-general', { params }); + return response.data; }; const getListadoDistribucionGeneralPdf = async (params: { - idPublicacion: number; - fechaDesde: string; - fechaHasta: string; + idPublicacion: number; + fechaDesde: string; + fechaHasta: string; }): Promise => { - const response = await apiClient.get('/reportes/listado-distribucion-general/pdf', { - params, - responseType: 'blob', - }); - return response.data; + const response = await apiClient.get('/reportes/listado-distribucion-general/pdf', { + params, + responseType: 'blob', + }); + return response.data; }; const getListadoDistribucionCanillas = async (params: { - idPublicacion: number; - fechaDesde: string; - fechaHasta: string; + idPublicacion: number; + fechaDesde: string; + fechaHasta: string; }): Promise => { - const response = await apiClient.get('/reportes/listado-distribucion-canillas', { params }); - return response.data; + const response = await apiClient.get('/reportes/listado-distribucion-canillas', { params }); + return response.data; }; const getListadoDistribucionCanillasPdf = async (params: { - idPublicacion: number; - fechaDesde: string; - fechaHasta: string; + idPublicacion: number; + fechaDesde: string; + fechaHasta: string; }): Promise => { - const response = await apiClient.get('/reportes/listado-distribucion-canillas/pdf', { - params, - responseType: 'blob', - }); - return response.data; + const response = await apiClient.get('/reportes/listado-distribucion-canillas/pdf', { + params, + responseType: 'blob', + }); + return response.data; }; const getListadoDistribucionCanillasImporte = async (params: { - idPublicacion: number; - fechaDesde: string; - fechaHasta: string; - esAccionista: boolean; - }): Promise => { - const response = await apiClient.get('/reportes/listado-distribucion-canillas-importe', { params }); - return response.data; - }; + idPublicacion: number; + fechaDesde: string; + fechaHasta: string; + esAccionista: boolean; +}): Promise => { + const response = await apiClient.get('/reportes/listado-distribucion-canillas-importe', { params }); + return response.data; +}; - const getListadoDistribucionCanillasImportePdf = async (params: { - idPublicacion: number; - fechaDesde: string; - fechaHasta: string; - esAccionista: boolean; - }): Promise => { - const response = await apiClient.get('/reportes/listado-distribucion-canillas-importe/pdf', { - params, - responseType: 'blob', - }); - return response.data; - }; +const getListadoDistribucionCanillasImportePdf = async (params: { + idPublicacion: number; + fechaDesde: string; + fechaHasta: string; + esAccionista: boolean; +}): Promise => { + const response = await apiClient.get('/reportes/listado-distribucion-canillas-importe/pdf', { + params, + responseType: 'blob', + }); + return response.data; +}; - const getVentaMensualSecretariaElDia = async (params: { fechaDesde: string; fechaHasta: string }): Promise => { - const response = await apiClient.get('/reportes/venta-mensual-secretaria/el-dia', { params }); - return response.data; - }; +const getVentaMensualSecretariaElDia = async (params: { fechaDesde: string; fechaHasta: string }): Promise => { + const response = await apiClient.get('/reportes/venta-mensual-secretaria/el-dia', { params }); + return response.data; +}; - const getVentaMensualSecretariaElDiaPdf = async (params: { fechaDesde: string; fechaHasta: string }): Promise => { - const response = await apiClient.get('/reportes/venta-mensual-secretaria/el-dia/pdf', { params, responseType: 'blob' }); - return response.data; - }; +const getVentaMensualSecretariaElDiaPdf = async (params: { fechaDesde: string; fechaHasta: string }): Promise => { + const response = await apiClient.get('/reportes/venta-mensual-secretaria/el-dia/pdf', { params, responseType: 'blob' }); + return response.data; +}; - const getVentaMensualSecretariaElPlata = async (params: { fechaDesde: string; fechaHasta: string }): Promise => { - const response = await apiClient.get('/reportes/venta-mensual-secretaria/el-plata', { params }); - return response.data; - }; +const getVentaMensualSecretariaElPlata = async (params: { fechaDesde: string; fechaHasta: string }): Promise => { + const response = await apiClient.get('/reportes/venta-mensual-secretaria/el-plata', { params }); + return response.data; +}; - const getVentaMensualSecretariaElPlataPdf = async (params: { fechaDesde: string; fechaHasta: string }): Promise => { - const response = await apiClient.get('/reportes/venta-mensual-secretaria/el-plata/pdf', { params, responseType: 'blob' }); - return response.data; - }; +const getVentaMensualSecretariaElPlataPdf = async (params: { fechaDesde: string; fechaHasta: string }): Promise => { + const response = await apiClient.get('/reportes/venta-mensual-secretaria/el-plata/pdf', { params, responseType: 'blob' }); + return response.data; +}; - const getVentaMensualSecretariaTirDevo = async (params: { fechaDesde: string; fechaHasta: string }): Promise => { - const response = await apiClient.get('/reportes/venta-mensual-secretaria/tirada-devolucion', { params }); - return response.data; - }; - const getVentaMensualSecretariaTirDevoPdf = async (params: { fechaDesde: string; fechaHasta: string }): Promise => { - const response = await apiClient.get('/reportes/venta-mensual-secretaria/tirada-devolucion/pdf', { params, responseType: 'blob' }); - return response.data; - }; +const getVentaMensualSecretariaTirDevo = async (params: { fechaDesde: string; fechaHasta: string }): Promise => { + const response = await apiClient.get('/reportes/venta-mensual-secretaria/tirada-devolucion', { params }); + return response.data; +}; +const getVentaMensualSecretariaTirDevoPdf = async (params: { fechaDesde: string; fechaHasta: string }): Promise => { + const response = await apiClient.get('/reportes/venta-mensual-secretaria/tirada-devolucion/pdf', { params, responseType: 'blob' }); + return response.data; +}; - const getReporteDistribucionCanillas = async (params: { - fecha: string; - idEmpresa: number; - }): Promise => { - const response = await apiClient.get('/reportes/distribucion-canillas', { params }); - return response.data; - }; +const getReporteDistribucionCanillas = async (params: { + fecha: string; + idEmpresa: number; +}): Promise => { + const response = await apiClient.get('/reportes/distribucion-canillas', { params }); + return response.data; +}; - const getReporteDistribucionCanillasPdf = async (params: { - fecha: string; - idEmpresa: number; - soloTotales: boolean; // Nuevo parámetro - }): Promise => { - const response = await apiClient.get('/reportes/distribucion-canillas/pdf', { // La ruta no necesita cambiar si el backend lo maneja - params, // soloTotales se enviará como query param si el backend lo espera así - responseType: 'blob', - }); - return response.data; - }; +const getReporteDistribucionCanillasPdf = async (params: { + fecha: string; + idEmpresa: number; + soloTotales: boolean; // Nuevo parámetro +}): Promise => { + const response = await apiClient.get('/reportes/distribucion-canillas/pdf', { // La ruta no necesita cambiar si el backend lo maneja + params, // soloTotales se enviará como query param si el backend lo espera así + responseType: 'blob', + }); + return response.data; +}; - const getTiradasPublicacionesSecciones = async (params: { - idPublicacion: number; - fechaDesde: string; - fechaHasta: string; - idPlanta?: number | null; - consolidado: boolean; - }): Promise => { - const response = await apiClient.get('/reportes/tiradas-publicaciones-secciones', { params }); - return response.data; - }; +const getTiradasPublicacionesSecciones = async (params: { + idPublicacion: number; + fechaDesde: string; + fechaHasta: string; + idPlanta?: number | null; + consolidado: boolean; +}): Promise => { + const response = await apiClient.get('/reportes/tiradas-publicaciones-secciones', { params }); + return response.data; +}; - const getTiradasPublicacionesSeccionesPdf = async (params: { - idPublicacion: number; - fechaDesde: string; - fechaHasta: string; - idPlanta?: number | null; - consolidado: boolean; - }): Promise => { - const response = await apiClient.get('/reportes/tiradas-publicaciones-secciones/pdf', { - params, - responseType: 'blob', - }); - return response.data; - }; +const getTiradasPublicacionesSeccionesPdf = async (params: { + idPublicacion: number; + fechaDesde: string; + fechaHasta: string; + idPlanta?: number | null; + consolidado: boolean; +}): Promise => { + const response = await apiClient.get('/reportes/tiradas-publicaciones-secciones/pdf', { + params, + responseType: 'blob', + }); + return response.data; +}; - const getConsumoBobinasSeccion = async (params: { - fechaDesde: string; - fechaHasta: string; - idPlanta?: number | null; - consolidado: boolean; - }): Promise => { - const response = await apiClient.get('/reportes/consumo-bobinas-seccion', { params }); - return response.data; - }; +const getConsumoBobinasSeccion = async (params: { + fechaDesde: string; + fechaHasta: string; + idPlanta?: number | null; + consolidado: boolean; +}): Promise => { + const response = await apiClient.get('/reportes/consumo-bobinas-seccion', { params }); + return response.data; +}; - const getConsumoBobinasSeccionPdf = async (params: { - fechaDesde: string; - fechaHasta: string; - idPlanta?: number | null; - consolidado: boolean; - }): Promise => { - const response = await apiClient.get('/reportes/consumo-bobinas-seccion/pdf', { - params, - responseType: 'blob', - }); - return response.data; - }; +const getConsumoBobinasSeccionPdf = async (params: { + fechaDesde: string; + fechaHasta: string; + idPlanta?: number | null; + consolidado: boolean; +}): Promise => { + const response = await apiClient.get('/reportes/consumo-bobinas-seccion/pdf', { + params, + responseType: 'blob', + }); + return response.data; +}; - const getConsumoBobinasPorPublicacion = async (params: { - fechaDesde: string; - fechaHasta: string; - }): Promise => { - const response = await apiClient.get('/reportes/consumo-bobinas-publicacion', { params }); - return response.data; - }; +const getConsumoBobinasPorPublicacion = async (params: { + fechaDesde: string; + fechaHasta: string; +}): Promise => { + const response = await apiClient.get('/reportes/consumo-bobinas-publicacion', { params }); + return response.data; +}; - const getConsumoBobinasPorPublicacionPdf = async (params: { - fechaDesde: string; - fechaHasta: string; - }): Promise => { - const response = await apiClient.get('/reportes/consumo-bobinas-publicacion/pdf', { - params, - responseType: 'blob', - }); - return response.data; - }; +const getConsumoBobinasPorPublicacionPdf = async (params: { + fechaDesde: string; + fechaHasta: string; +}): Promise => { + const response = await apiClient.get('/reportes/consumo-bobinas-publicacion/pdf', { + params, + responseType: 'blob', + }); + return response.data; +}; - const getComparativaConsumoBobinas = async (params: { - fechaInicioMesA: string; fechaFinMesA: string; - fechaInicioMesB: string; fechaFinMesB: string; - idPlanta?: number | null; consolidado: boolean; - }): Promise => { - const response = await apiClient.get('/reportes/comparativa-consumo-bobinas', { params }); - return response.data; - }; +const getComparativaConsumoBobinas = async (params: { + fechaInicioMesA: string; fechaFinMesA: string; + fechaInicioMesB: string; fechaFinMesB: string; + idPlanta?: number | null; consolidado: boolean; +}): Promise => { + const response = await apiClient.get('/reportes/comparativa-consumo-bobinas', { params }); + return response.data; +}; - const getComparativaConsumoBobinasPdf = async (params: { - fechaInicioMesA: string; fechaFinMesA: string; - fechaInicioMesB: string; fechaFinMesB: string; - idPlanta?: number | null; consolidado: boolean; - }): Promise => { - const response = await apiClient.get('/reportes/comparativa-consumo-bobinas/pdf', { - params, - responseType: 'blob', - }); - return response.data; - }; +const getComparativaConsumoBobinasPdf = async (params: { + fechaInicioMesA: string; fechaFinMesA: string; + fechaInicioMesB: string; fechaFinMesB: string; + idPlanta?: number | null; consolidado: boolean; +}): Promise => { + const response = await apiClient.get('/reportes/comparativa-consumo-bobinas/pdf', { + params, + responseType: 'blob', + }); + return response.data; +}; - const getReporteCuentasDistribuidor = async (params: { - idDistribuidor: number; - idEmpresa: number; - fechaDesde: string; - fechaHasta: string; - }): Promise => { - const response = await apiClient.get('/reportes/cuentas-distribuidores', { params }); - return response.data; - }; +const getReporteCuentasDistribuidor = async (params: { + idDistribuidor: number; + idEmpresa: number; + fechaDesde: string; + fechaHasta: string; +}): Promise => { + const response = await apiClient.get('/reportes/cuentas-distribuidores', { params }); + return response.data; +}; - const getReporteCuentasDistribuidorPdf = async (params: { - idDistribuidor: number; - idEmpresa: number; - fechaDesde: string; - fechaHasta: string; - }): Promise => { - const response = await apiClient.get('/reportes/cuentas-distribuidores/pdf', { - params, - responseType: 'blob', - }); - return response.data; - }; +const getReporteCuentasDistribuidorPdf = async (params: { + idDistribuidor: number; + idEmpresa: number; + fechaDesde: string; + fechaHasta: string; +}): Promise => { + const response = await apiClient.get('/reportes/cuentas-distribuidores/pdf', { + params, + responseType: 'blob', + }); + return response.data; +}; + +const getListadoDistribucionDistribuidores = async (params: { + idDistribuidor: number; + idPublicacion: number; + fechaDesde: string; + fechaHasta: string; +}): Promise => { + const response = await apiClient.get('/reportes/listado-distribucion-distribuidores', { params }); + return response.data; +}; + +const getListadoDistribucionDistribuidoresPdf = async (params: { + idDistribuidor: number; + idPublicacion: number; + fechaDesde: string; + fechaHasta: string; +}): Promise => { + const response = await apiClient.get('/reportes/listado-distribucion-distribuidores/pdf', { + params, + responseType: 'blob', + }); + return response.data; +}; + +const getControlDevolucionesData = async (params: { + fecha: string; // YYYY-MM-DD + idEmpresa: number; +}): Promise => { + const response = await apiClient.get('/reportes/control-devoluciones', { params }); + return response.data; +}; + +const getControlDevolucionesPdf = async (params: { + fecha: string; // YYYY-MM-DD + idEmpresa: number; +}): Promise => { + const response = await apiClient.get('/reportes/control-devoluciones/pdf', { + params, + responseType: 'blob', + }); + return response.data; +}; const reportesService = { - getExistenciaPapel, - getExistenciaPapelPdf, - getMovimientoBobinas, - getMovimientoBobinasPdf, - getMovimientoBobinasEstado, - getMovimientoBobinasEstadoPdf, - getListadoDistribucionGeneral, - getListadoDistribucionGeneralPdf, - getListadoDistribucionCanillas, - getListadoDistribucionCanillasPdf, - getListadoDistribucionCanillasImporte, - getListadoDistribucionCanillasImportePdf, - getVentaMensualSecretariaElDia, - getVentaMensualSecretariaElDiaPdf, - getVentaMensualSecretariaElPlata, - getVentaMensualSecretariaElPlataPdf, - getVentaMensualSecretariaTirDevo, - getVentaMensualSecretariaTirDevoPdf, - getReporteDistribucionCanillas, - getReporteDistribucionCanillasPdf, - getTiradasPublicacionesSecciones, - getTiradasPublicacionesSeccionesPdf, - getConsumoBobinasSeccion, - getConsumoBobinasSeccionPdf, - getConsumoBobinasPorPublicacion, - getConsumoBobinasPorPublicacionPdf, - getComparativaConsumoBobinas, - getComparativaConsumoBobinasPdf, - getReporteCuentasDistribuidor, - getReporteCuentasDistribuidorPdf, + getExistenciaPapel, + getExistenciaPapelPdf, + getMovimientoBobinas, + getMovimientoBobinasPdf, + getMovimientoBobinasEstado, + getMovimientoBobinasEstadoPdf, + getListadoDistribucionGeneral, + getListadoDistribucionGeneralPdf, + getListadoDistribucionCanillas, + getListadoDistribucionCanillasPdf, + getListadoDistribucionCanillasImporte, + getListadoDistribucionCanillasImportePdf, + getVentaMensualSecretariaElDia, + getVentaMensualSecretariaElDiaPdf, + getVentaMensualSecretariaElPlata, + getVentaMensualSecretariaElPlataPdf, + getVentaMensualSecretariaTirDevo, + getVentaMensualSecretariaTirDevoPdf, + getReporteDistribucionCanillas, + getReporteDistribucionCanillasPdf, + getTiradasPublicacionesSecciones, + getTiradasPublicacionesSeccionesPdf, + getConsumoBobinasSeccion, + getConsumoBobinasSeccionPdf, + getConsumoBobinasPorPublicacion, + getConsumoBobinasPorPublicacionPdf, + getComparativaConsumoBobinas, + getComparativaConsumoBobinasPdf, + getReporteCuentasDistribuidor, + getReporteCuentasDistribuidorPdf, + getListadoDistribucionDistribuidores, + getListadoDistribucionDistribuidoresPdf, + getControlDevolucionesData, + getControlDevolucionesPdf, }; export default reportesService; \ No newline at end of file