From 1182a4cdee4fcdb55dc3f2dbfeeb2ec2187f2bea Mon Sep 17 00:00:00 2001 From: eldiadmolinari Date: Thu, 29 May 2025 15:10:02 -0300 Subject: [PATCH] =?UTF-8?q?Final=20de=20creaci=C3=B3n=20de=20M=C3=B3dulos?= =?UTF-8?q?=20de=20Reportes.=20Se=20procede=20a=20testeos=20y=20ordenamien?= =?UTF-8?q?tos...?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../GestionIntegral.Api.AssemblyInfo.cs | 2 +- .../dtos/Reportes/BalanceCuentaDebCredDto.tsx | 6 + .../dtos/Reportes/BalanceCuentaDistDto.tsx | 10 + .../dtos/Reportes/BalanceCuentaPagosDto.tsx | 8 + .../Reportes/ComparativaConsumoBobinasDto.tsx | 9 + .../Reportes/ConsumoBobinasPublicacionDto.tsx | 6 + .../Reportes/ConsumoBobinasSeccionDto.tsx | 7 + .../ControlDevolucionesReporteDto.tsx | 10 + .../DetalleDistribucionCanillaAllDto.tsx | 7 + .../DetalleDistribucionCanillaDto.tsx | 8 + .../dtos/Reportes/DevueltosOtrosDiasDto.tsx | 3 + .../ListadoDistribucionCanillasImporteDto.ts | 8 + .../Reportes/ObtenerCtrlDevolucionesDto.tsx | 3 + .../ReporteCuentasDistribuidorResponseDto.ts | 14 + .../ReporteCuentasDistribuidorResponseDto.tsx | 13 + .../ReporteDistribucionCanillasResponseDto.ts | 17 + .../src/models/dtos/Reportes/SaldoDto.tsx | 3 + .../TiradasPublicacionesSeccionesDto.tsx | 8 + .../VentaMensualSecretariaElDiaDto.tsx | 9 + .../VentaMensualSecretariaElPlataDto.tsx | 9 + .../VentaMensualSecretariaTirDevoDto.tsx | 16 + .../ReporteComparativaConsumoBobinasPage.tsx | 225 ++++++++++++ .../ReporteConsumoBobinasPublicacionPage.tsx | 229 ++++++++++++ .../ReporteConsumoBobinasSeccionPage.tsx | 261 ++++++++++++++ .../ReporteCuentasDistribuidoresPage.tsx | 294 ++++++++++++++++ ...ReporteDetalleDistribucionCanillasPage.tsx | 248 +++++++++++++ ...ListadoDistribucionCanillasImportePage.tsx | 197 +++++++++++ ...porteTiradasPublicacionesSeccionesPage.tsx | 209 +++++++++++ .../ReporteVentaMensualSecretariaPage.tsx | 326 ++++++++++++++++++ .../src/pages/Reportes/ReportesIndexPage.tsx | 8 + ...ccionaReporteComparativaConsumoBobinas.tsx | 164 +++++++++ ...ccionaReporteConsumoBobinasPublicacion.tsx | 87 +++++ ...SeleccionaReporteConsumoBobinasSeccion.tsx | 155 +++++++++ ...SeleccionaReporteCuentasDistribuidores.tsx | 157 +++++++++ ...ionaReporteDetalleDistribucionCanillas.tsx | 125 +++++++ ...orteListadoDistribucionCanillasImporte.tsx | 143 ++++++++ ...naReporteTiradasPublicacionesSecciones.tsx | 163 +++++++++ .../SeleccionaReporteVentaMensual.tsx | 114 ++++++ Frontend/src/routes/AppRoutes.tsx | 16 + .../src/services/Reportes/reportesService.ts | 215 +++++++++++- 40 files changed, 3510 insertions(+), 2 deletions(-) create mode 100644 Frontend/src/models/dtos/Reportes/BalanceCuentaDebCredDto.tsx create mode 100644 Frontend/src/models/dtos/Reportes/BalanceCuentaDistDto.tsx create mode 100644 Frontend/src/models/dtos/Reportes/BalanceCuentaPagosDto.tsx create mode 100644 Frontend/src/models/dtos/Reportes/ComparativaConsumoBobinasDto.tsx create mode 100644 Frontend/src/models/dtos/Reportes/ConsumoBobinasPublicacionDto.tsx create mode 100644 Frontend/src/models/dtos/Reportes/ConsumoBobinasSeccionDto.tsx create mode 100644 Frontend/src/models/dtos/Reportes/ControlDevolucionesReporteDto.tsx create mode 100644 Frontend/src/models/dtos/Reportes/DetalleDistribucionCanillaAllDto.tsx create mode 100644 Frontend/src/models/dtos/Reportes/DetalleDistribucionCanillaDto.tsx create mode 100644 Frontend/src/models/dtos/Reportes/DevueltosOtrosDiasDto.tsx create mode 100644 Frontend/src/models/dtos/Reportes/ListadoDistribucionCanillasImporteDto.ts create mode 100644 Frontend/src/models/dtos/Reportes/ObtenerCtrlDevolucionesDto.tsx create mode 100644 Frontend/src/models/dtos/Reportes/ReporteCuentasDistribuidorResponseDto.ts create mode 100644 Frontend/src/models/dtos/Reportes/ReporteCuentasDistribuidorResponseDto.tsx create mode 100644 Frontend/src/models/dtos/Reportes/ReporteDistribucionCanillasResponseDto.ts create mode 100644 Frontend/src/models/dtos/Reportes/SaldoDto.tsx create mode 100644 Frontend/src/models/dtos/Reportes/TiradasPublicacionesSeccionesDto.tsx create mode 100644 Frontend/src/models/dtos/Reportes/VentaMensualSecretariaElDiaDto.tsx create mode 100644 Frontend/src/models/dtos/Reportes/VentaMensualSecretariaElPlataDto.tsx create mode 100644 Frontend/src/models/dtos/Reportes/VentaMensualSecretariaTirDevoDto.tsx create mode 100644 Frontend/src/pages/Reportes/ReporteComparativaConsumoBobinasPage.tsx create mode 100644 Frontend/src/pages/Reportes/ReporteConsumoBobinasPublicacionPage.tsx create mode 100644 Frontend/src/pages/Reportes/ReporteConsumoBobinasSeccionPage.tsx create mode 100644 Frontend/src/pages/Reportes/ReporteCuentasDistribuidoresPage.tsx create mode 100644 Frontend/src/pages/Reportes/ReporteDetalleDistribucionCanillasPage.tsx create mode 100644 Frontend/src/pages/Reportes/ReporteListadoDistribucionCanillasImportePage.tsx create mode 100644 Frontend/src/pages/Reportes/ReporteTiradasPublicacionesSeccionesPage.tsx create mode 100644 Frontend/src/pages/Reportes/ReporteVentaMensualSecretariaPage.tsx create mode 100644 Frontend/src/pages/Reportes/SeleccionaReporteComparativaConsumoBobinas.tsx create mode 100644 Frontend/src/pages/Reportes/SeleccionaReporteConsumoBobinasPublicacion.tsx create mode 100644 Frontend/src/pages/Reportes/SeleccionaReporteConsumoBobinasSeccion.tsx create mode 100644 Frontend/src/pages/Reportes/SeleccionaReporteCuentasDistribuidores.tsx create mode 100644 Frontend/src/pages/Reportes/SeleccionaReporteDetalleDistribucionCanillas.tsx create mode 100644 Frontend/src/pages/Reportes/SeleccionaReporteListadoDistribucionCanillasImporte.tsx create mode 100644 Frontend/src/pages/Reportes/SeleccionaReporteTiradasPublicacionesSecciones.tsx create mode 100644 Frontend/src/pages/Reportes/SeleccionaReporteVentaMensual.tsx 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 4397683..7911427 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+2273ebb1e018273a6e35d3f9ab0afe55ae1814bc")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+70fc84772161b499c8283a31b7a61246a6bcc46f")] [assembly: System.Reflection.AssemblyProductAttribute("GestionIntegral.Api")] [assembly: System.Reflection.AssemblyTitleAttribute("GestionIntegral.Api")] [assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] diff --git a/Frontend/src/models/dtos/Reportes/BalanceCuentaDebCredDto.tsx b/Frontend/src/models/dtos/Reportes/BalanceCuentaDebCredDto.tsx new file mode 100644 index 0000000..57c3027 --- /dev/null +++ b/Frontend/src/models/dtos/Reportes/BalanceCuentaDebCredDto.tsx @@ -0,0 +1,6 @@ +export interface BalanceCuentaDebCredDto { + fecha: string; + referencia?: string; + debe: number; + haber: number; +} \ No newline at end of file diff --git a/Frontend/src/models/dtos/Reportes/BalanceCuentaDistDto.tsx b/Frontend/src/models/dtos/Reportes/BalanceCuentaDistDto.tsx new file mode 100644 index 0000000..d86fb1d --- /dev/null +++ b/Frontend/src/models/dtos/Reportes/BalanceCuentaDistDto.tsx @@ -0,0 +1,10 @@ +export interface BalanceCuentaDistDto { + fecha: string; + publicacion: string; + distribuidor: string; + cantidad: number; + remito: string; + observacion?: string; + debe: number; + haber: number; +} \ No newline at end of file diff --git a/Frontend/src/models/dtos/Reportes/BalanceCuentaPagosDto.tsx b/Frontend/src/models/dtos/Reportes/BalanceCuentaPagosDto.tsx new file mode 100644 index 0000000..d0c4055 --- /dev/null +++ b/Frontend/src/models/dtos/Reportes/BalanceCuentaPagosDto.tsx @@ -0,0 +1,8 @@ +export interface BalanceCuentaPagosDto { + fecha: string; + recibo: number; + tipo: string; + debe: number; + haber: number; + detalle?: string; +} \ No newline at end of file diff --git a/Frontend/src/models/dtos/Reportes/ComparativaConsumoBobinasDto.tsx b/Frontend/src/models/dtos/Reportes/ComparativaConsumoBobinasDto.tsx new file mode 100644 index 0000000..b6c33f2 --- /dev/null +++ b/Frontend/src/models/dtos/Reportes/ComparativaConsumoBobinasDto.tsx @@ -0,0 +1,9 @@ +export interface ComparativaConsumoBobinasDto { + tipoBobina: string; + bobinasUtilizadasMesA: number; + bobinasUtilizadasMesB: number; + diferenciaBobinasUtilizadas: number; + kilosUtilizadosMesA: number; + kilosUtilizadosMesB: number; + diferenciaKilosUtilizados: number; +} \ No newline at end of file diff --git a/Frontend/src/models/dtos/Reportes/ConsumoBobinasPublicacionDto.tsx b/Frontend/src/models/dtos/Reportes/ConsumoBobinasPublicacionDto.tsx new file mode 100644 index 0000000..04ff977 --- /dev/null +++ b/Frontend/src/models/dtos/Reportes/ConsumoBobinasPublicacionDto.tsx @@ -0,0 +1,6 @@ +export interface ConsumoBobinasPublicacionDto { + nombrePlanta: string; + nombrePublicacion: string; + totalKilos: number; + cantidadBobinas: number; +} \ No newline at end of file diff --git a/Frontend/src/models/dtos/Reportes/ConsumoBobinasSeccionDto.tsx b/Frontend/src/models/dtos/Reportes/ConsumoBobinasSeccionDto.tsx new file mode 100644 index 0000000..ca1c02b --- /dev/null +++ b/Frontend/src/models/dtos/Reportes/ConsumoBobinasSeccionDto.tsx @@ -0,0 +1,7 @@ +export interface ConsumoBobinasSeccionDto { + nombrePublicacion: string; + nombreSeccion: string; + nombreBobina: string; + cantidadBobinas: number; + totalKilos: number; +} \ No newline at end of file diff --git a/Frontend/src/models/dtos/Reportes/ControlDevolucionesReporteDto.tsx b/Frontend/src/models/dtos/Reportes/ControlDevolucionesReporteDto.tsx new file mode 100644 index 0000000..316cdba --- /dev/null +++ b/Frontend/src/models/dtos/Reportes/ControlDevolucionesReporteDto.tsx @@ -0,0 +1,10 @@ +export interface ControlDevolucionesReporteDto { + ingresados: number; + sobrantes: number; + publicacion: string; + llevados: number; + devueltos: number; + tipo: string; // "Accionistas" o "Canillitas" + totalNoAccionistas: number; + sinCargo: number; +} \ No newline at end of file diff --git a/Frontend/src/models/dtos/Reportes/DetalleDistribucionCanillaAllDto.tsx b/Frontend/src/models/dtos/Reportes/DetalleDistribucionCanillaAllDto.tsx new file mode 100644 index 0000000..51bea5f --- /dev/null +++ b/Frontend/src/models/dtos/Reportes/DetalleDistribucionCanillaAllDto.tsx @@ -0,0 +1,7 @@ +export interface DetalleDistribucionCanillaAllDto { + publicacion: string; + totalCantSalida: number; + totalCantEntrada: number; + totalRendir: number; + tipoVendedor: string; +} \ No newline at end of file diff --git a/Frontend/src/models/dtos/Reportes/DetalleDistribucionCanillaDto.tsx b/Frontend/src/models/dtos/Reportes/DetalleDistribucionCanillaDto.tsx new file mode 100644 index 0000000..5d18042 --- /dev/null +++ b/Frontend/src/models/dtos/Reportes/DetalleDistribucionCanillaDto.tsx @@ -0,0 +1,8 @@ +export interface DetalleDistribucionCanillaDto { + publicacion: string; + canilla: string; + totalCantSalida: number; + totalCantEntrada: number; + totalRendir: number; + fecha?: string | null; // Opcional y nullable para adaptarse al backend +} \ No newline at end of file diff --git a/Frontend/src/models/dtos/Reportes/DevueltosOtrosDiasDto.tsx b/Frontend/src/models/dtos/Reportes/DevueltosOtrosDiasDto.tsx new file mode 100644 index 0000000..c4a2aa1 --- /dev/null +++ b/Frontend/src/models/dtos/Reportes/DevueltosOtrosDiasDto.tsx @@ -0,0 +1,3 @@ +export interface DevueltosOtrosDiasDto { + devueltos: number; +} \ No newline at end of file diff --git a/Frontend/src/models/dtos/Reportes/ListadoDistribucionCanillasImporteDto.ts b/Frontend/src/models/dtos/Reportes/ListadoDistribucionCanillasImporteDto.ts new file mode 100644 index 0000000..3ee1d31 --- /dev/null +++ b/Frontend/src/models/dtos/Reportes/ListadoDistribucionCanillasImporteDto.ts @@ -0,0 +1,8 @@ +export interface ListadoDistribucionCanillasImporteDto { + fecha: string; // SP devuelve FORMAT(..., 'dd/MM/yyyy') + llevados: number; + devueltos: number; + vendidos: number; + totalRendirPublicacion: number; // decimal en BD + totalRendirGeneral: number; // decimal en BD +} \ No newline at end of file diff --git a/Frontend/src/models/dtos/Reportes/ObtenerCtrlDevolucionesDto.tsx b/Frontend/src/models/dtos/Reportes/ObtenerCtrlDevolucionesDto.tsx new file mode 100644 index 0000000..38355fe --- /dev/null +++ b/Frontend/src/models/dtos/Reportes/ObtenerCtrlDevolucionesDto.tsx @@ -0,0 +1,3 @@ +export interface ObtenerCtrlDevolucionesDto { + remito: number; +} \ No newline at end of file diff --git a/Frontend/src/models/dtos/Reportes/ReporteCuentasDistribuidorResponseDto.ts b/Frontend/src/models/dtos/Reportes/ReporteCuentasDistribuidorResponseDto.ts new file mode 100644 index 0000000..9a2228b --- /dev/null +++ b/Frontend/src/models/dtos/Reportes/ReporteCuentasDistribuidorResponseDto.ts @@ -0,0 +1,14 @@ +import type { BalanceCuentaDebCredDto } from "./BalanceCuentaDebCredDto"; +import type { BalanceCuentaDistDto } from "./BalanceCuentaDistDto"; +import type { BalanceCuentaPagosDto } from "./BalanceCuentaPagosDto"; +import type { SaldoDto } from "./SaldoDto"; + +export interface ReporteCuentasDistribuidorResponseDto { + entradasSalidas: BalanceCuentaDistDto[]; + debitosCreditos: BalanceCuentaDebCredDto[]; + pagos: BalanceCuentaPagosDto[]; + saldos: SaldoDto[]; // Aunque SP_BalanceCuentSaldos devuelve una lista, para un distribuidor/empresa debería ser 1 solo. + // Se podría ajustar el DTO o el servicio para devolver solo el primer saldo. + nombreDistribuidor?: string; // Para el título del reporte + nombreEmpresa?: string; // Para el título del reporte +} \ No newline at end of file diff --git a/Frontend/src/models/dtos/Reportes/ReporteCuentasDistribuidorResponseDto.tsx b/Frontend/src/models/dtos/Reportes/ReporteCuentasDistribuidorResponseDto.tsx new file mode 100644 index 0000000..fb37d52 --- /dev/null +++ b/Frontend/src/models/dtos/Reportes/ReporteCuentasDistribuidorResponseDto.tsx @@ -0,0 +1,13 @@ +import type { BalanceCuentaDebCredDto } from "./BalanceCuentaDebCredDto"; +import type { BalanceCuentaDistDto } from "./BalanceCuentaDistDto"; +import type { BalanceCuentaPagosDto } from "./BalanceCuentaPagosDto"; +import type { SaldoDto } from "./SaldoDto"; + +export interface ReporteCuentasDistribuidorResponseDto { + entradasSalidas: BalanceCuentaDistDto[]; + debitosCreditos: BalanceCuentaDebCredDto[]; + pagos: BalanceCuentaPagosDto[]; + saldos: SaldoDto[]; + nombreDistribuidor?: string; + nombreEmpresa?: string; +} \ No newline at end of file diff --git a/Frontend/src/models/dtos/Reportes/ReporteDistribucionCanillasResponseDto.ts b/Frontend/src/models/dtos/Reportes/ReporteDistribucionCanillasResponseDto.ts new file mode 100644 index 0000000..4040dc6 --- /dev/null +++ b/Frontend/src/models/dtos/Reportes/ReporteDistribucionCanillasResponseDto.ts @@ -0,0 +1,17 @@ +import type { DetalleDistribucionCanillaDto } from './DetalleDistribucionCanillaDto'; +import type { DetalleDistribucionCanillaAllDto } from './DetalleDistribucionCanillaAllDto'; +import type { ObtenerCtrlDevolucionesDto } from './ObtenerCtrlDevolucionesDto'; +import type { ControlDevolucionesReporteDto } from './ControlDevolucionesReporteDto'; +import type { DevueltosOtrosDiasDto } from './DevueltosOtrosDiasDto'; + + +export interface ReporteDistribucionCanillasResponseDto { + canillas: DetalleDistribucionCanillaDto[]; + canillasAccionistas: DetalleDistribucionCanillaDto[]; + canillasTodos: DetalleDistribucionCanillaAllDto[]; + canillasLiquidadasOtraFecha: DetalleDistribucionCanillaDto[]; + canillasAccionistasLiquidadasOtraFecha: DetalleDistribucionCanillaDto[]; + controlDevolucionesRemitos: ObtenerCtrlDevolucionesDto[]; + controlDevolucionesDetalle: ControlDevolucionesReporteDto[]; + controlDevolucionesOtrosDias: DevueltosOtrosDiasDto[]; +} \ No newline at end of file diff --git a/Frontend/src/models/dtos/Reportes/SaldoDto.tsx b/Frontend/src/models/dtos/Reportes/SaldoDto.tsx new file mode 100644 index 0000000..d1a5035 --- /dev/null +++ b/Frontend/src/models/dtos/Reportes/SaldoDto.tsx @@ -0,0 +1,3 @@ +export interface SaldoDto { + monto: number; +} \ No newline at end of file diff --git a/Frontend/src/models/dtos/Reportes/TiradasPublicacionesSeccionesDto.tsx b/Frontend/src/models/dtos/Reportes/TiradasPublicacionesSeccionesDto.tsx new file mode 100644 index 0000000..a514ea5 --- /dev/null +++ b/Frontend/src/models/dtos/Reportes/TiradasPublicacionesSeccionesDto.tsx @@ -0,0 +1,8 @@ +export interface TiradasPublicacionesSeccionesDto { + nombreSeccion: string; + totalPaginasImpresas: number; + cantidadTiradas: number; + totalPaginasEjemplares: number; + totalEjemplares: number; + promedioPaginasPorEjemplar: number; +} diff --git a/Frontend/src/models/dtos/Reportes/VentaMensualSecretariaElDiaDto.tsx b/Frontend/src/models/dtos/Reportes/VentaMensualSecretariaElDiaDto.tsx new file mode 100644 index 0000000..b0c2f11 --- /dev/null +++ b/Frontend/src/models/dtos/Reportes/VentaMensualSecretariaElDiaDto.tsx @@ -0,0 +1,9 @@ +export interface VentaMensualSecretariaElDiaDto { + dia: number; + cantidadCanillas: number; + tirajes: number; + ventas: number; + accionistas: number; + totalCooperativa: number; + totalGeneral: number; +} \ No newline at end of file diff --git a/Frontend/src/models/dtos/Reportes/VentaMensualSecretariaElPlataDto.tsx b/Frontend/src/models/dtos/Reportes/VentaMensualSecretariaElPlataDto.tsx new file mode 100644 index 0000000..35d166f --- /dev/null +++ b/Frontend/src/models/dtos/Reportes/VentaMensualSecretariaElPlataDto.tsx @@ -0,0 +1,9 @@ +export interface VentaMensualSecretariaElPlataDto { + dia: number; + tiradaCoop: number; + devolucionCoop: number; + ventaCoop: number; + tiradaCan: number; + ventaCan: number; + total: number; +} \ No newline at end of file diff --git a/Frontend/src/models/dtos/Reportes/VentaMensualSecretariaTirDevoDto.tsx b/Frontend/src/models/dtos/Reportes/VentaMensualSecretariaTirDevoDto.tsx new file mode 100644 index 0000000..915deec --- /dev/null +++ b/Frontend/src/models/dtos/Reportes/VentaMensualSecretariaTirDevoDto.tsx @@ -0,0 +1,16 @@ +export interface VentaMensualSecretariaTirDevoDto { + dia: number; + tiradaCoop: number; + devolucionCoop: number; + ventaCoop: number; + ventaCan: number; + tiradaPopular: number; + devolucionPopular: number; + ventaPopular: number; + tiradaClarin: number; + devolucionClarin: number; + ventaClarin: number; + tiradaNacion: number; + devolucionNacion: number; + ventaNacion: number; +} \ No newline at end of file diff --git a/Frontend/src/pages/Reportes/ReporteComparativaConsumoBobinasPage.tsx b/Frontend/src/pages/Reportes/ReporteComparativaConsumoBobinasPage.tsx new file mode 100644 index 0000000..814f273 --- /dev/null +++ b/Frontend/src/pages/Reportes/ReporteComparativaConsumoBobinasPage.tsx @@ -0,0 +1,225 @@ +import React, { useState, useCallback } from 'react'; +import { + Box, Typography, Paper, CircularProgress, Alert, Button, + TableContainer, Table, TableHead, TableRow, TableCell, TableBody, + TableFooter +} from '@mui/material'; +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'; + +const ReporteComparativaConsumoBobinasPage: React.FC = () => { + const [reportData, setReportData] = useState([]); + 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<{ + fechaInicioMesA: string; fechaFinMesA: string; + fechaInicioMesB: string; fechaFinMesB: string; + idPlanta?: number | null; consolidado: boolean; + nombrePlanta?: string; // Para el PDF + } | null>(null); + + const handleGenerarReporte = useCallback(async (params: { + fechaInicioMesA: string; fechaFinMesA: string; + fechaInicioMesB: string; fechaFinMesB: string; + idPlanta?: number | null; consolidado: boolean; + }) => { + setLoading(true); + setError(null); + setApiErrorParams(null); + setReportData([]); + + let plantaNombre = "Consolidado"; + if (!params.consolidado && params.idPlanta) { + const plantaService = (await import('../../services/Impresion/plantaService')).default; + const plantaData = await plantaService.getPlantaById(params.idPlanta); + plantaNombre = plantaData?.nombre ?? "N/A"; + } + setCurrentParams({...params, nombrePlanta: plantaNombre}); + + try { + const data = await reportesService.getComparativaConsumoBobinas(params); + setReportData(data); + if (data.length === 0) { + setError("No se encontraron datos para los parámetros seleccionados."); + } + 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); + } finally { + setLoading(false); + } + }, []); + + const handleVolverAParametros = useCallback(() => { + setShowParamSelector(true); + setReportData([]); + setError(null); + setApiErrorParams(null); + setCurrentParams(null); + }, []); + + const handleExportToExcel = useCallback(() => { + if (reportData.length === 0) { + alert("No hay datos para exportar."); + return; + } + const dataToExport = reportData.map(item => ({ + "Tipo Bobina": item.tipoBobina, + "Cant. Mes A": item.bobinasUtilizadasMesA, + "Cant. Mes B": item.bobinasUtilizadasMesB, + "Dif. Cant.": item.diferenciaBobinasUtilizadas, + "Kg Mes A": item.kilosUtilizadosMesA, + "Kg Mes B": item.kilosUtilizadosMesB, + "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"]); + acc.kgB += Number(row["Kg Mes B"]); acc.difKg += Number(row["Dif. Kg"]); + return acc; + }, { cantA:0, cantB:0, difCant:0, kgA:0, kgB:0, difKg:0 }); + + dataToExport.push({ + "Tipo Bobina": "TOTALES", "Cant. Mes A": totales.cantA, "Cant. Mes B": totales.cantB, + "Dif. Cant.": totales.difCant, "Kg Mes A": totales.kgA, "Kg Mes B": totales.kgB, + "Dif. Kg": totales.difKg, + }); + + + const ws = XLSX.utils.json_to_sheet(dataToExport); + const headers = Object.keys(dataToExport[0] || {}); + ws['!cols'] = headers.map(h => { + const maxLen = Math.max(...dataToExport.map(row => (row as any)[h]?.toString().length ?? 0), h.length); + 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"; + if (currentParams) { + fileName += `_${currentParams.consolidado ? 'Consolidado' : `Planta${currentParams.idPlanta}`}`; + fileName += `_${currentParams.fechaInicioMesA}_vs_${currentParams.fechaInicioMesB}`; + } + fileName += ".xlsx"; + XLSX.writeFile(wb, fileName); + }, [reportData, currentParams]); + + const handleGenerarYAbrirPdf = useCallback(async () => { + if (!currentParams) { + setError("Primero debe generar el reporte en pantalla o seleccionar parámetros."); + return; + } + setLoadingPdf(true); + setError(null); + try { + const blob = await reportesService.getComparativaConsumoBobinasPdf(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."); + } + } catch { + setError('Ocurrió un error al generar el PDF.'); + } finally { + setLoadingPdf(false); + } + }, [currentParams]); + + if (showParamSelector) { + return ( + + + + + + ); + } + + return ( + + + Reporte: Comparativa Consumo de Bobinas + + + + + + + + {loading && } + {error && !loading && {error}} + + {!loading && !error && reportData.length > 0 && ( + + + + + Tipo Bobina + Cant. Mes A + Cant. Mes B + Dif. Cant. + Kg Mes A + Kg Mes B + Dif. Kg + + + + {reportData.map((row, idx) => ( + + {row.tipoBobina} + {row.bobinasUtilizadasMesA.toLocaleString('es-AR')} + {row.bobinasUtilizadasMesB.toLocaleString('es-AR')} + {row.diferenciaBobinasUtilizadas.toLocaleString('es-AR')} + {row.kilosUtilizadosMesA.toLocaleString('es-AR')} + {row.kilosUtilizadosMesB.toLocaleString('es-AR')} + {row.diferenciaKilosUtilizados.toLocaleString('es-AR')} + + ))} + + + + TOTALES: + {reportData.reduce((sum, item) => sum + item.bobinasUtilizadasMesA, 0).toLocaleString('es-AR')} + {reportData.reduce((sum, item) => sum + item.bobinasUtilizadasMesB, 0).toLocaleString('es-AR')} + {reportData.reduce((sum, item) => sum + item.diferenciaBobinasUtilizadas, 0).toLocaleString('es-AR')} + {reportData.reduce((sum, item) => sum + item.kilosUtilizadosMesA, 0).toLocaleString('es-AR')} + {reportData.reduce((sum, item) => sum + item.kilosUtilizadosMesB, 0).toLocaleString('es-AR')} + {reportData.reduce((sum, item) => sum + item.diferenciaKilosUtilizados, 0).toLocaleString('es-AR')} + + +
+
+ )} + {!loading && !error && reportData.length === 0 && (No se encontraron datos para los criterios seleccionados.)} +
+ ); +}; + +export default ReporteComparativaConsumoBobinasPage; \ No newline at end of file diff --git a/Frontend/src/pages/Reportes/ReporteConsumoBobinasPublicacionPage.tsx b/Frontend/src/pages/Reportes/ReporteConsumoBobinasPublicacionPage.tsx new file mode 100644 index 0000000..04d948e --- /dev/null +++ b/Frontend/src/pages/Reportes/ReporteConsumoBobinasPublicacionPage.tsx @@ -0,0 +1,229 @@ +import React, { useState, useCallback } from 'react'; +import { + Box, Typography, Paper, CircularProgress, Alert, Button, + TableContainer, Table, TableHead, TableRow, TableCell, TableBody, TableFooter +} from '@mui/material'; +import reportesService from '../../services/Reportes/reportesService'; +import type { ConsumoBobinasPublicacionDto } from '../../models/dtos/Reportes/ConsumoBobinasPublicacionDto'; +import SeleccionaReporteConsumoBobinasPublicacion from './SeleccionaReporteConsumoBobinasPublicacion'; +import * as XLSX from 'xlsx'; +import axios from 'axios'; + +// Helper para agrupar los datos por Planta +const groupDataByPlanta = (data: ConsumoBobinasPublicacionDto[]) => { + return data.reduce((acc, current) => { + const plantaKey = current.nombrePlanta; + if (!acc[plantaKey]) { + acc[plantaKey] = []; + } + acc[plantaKey].push(current); + return acc; + }, {} as Record); +}; + +const ReporteConsumoBobinasPublicacionPage: React.FC = () => { + const [reportData, setReportData] = useState([]); + 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<{ + fechaDesde: string; + fechaHasta: string; + } | null>(null); + + const handleGenerarReporte = useCallback(async (params: { + fechaDesde: string; + fechaHasta: string; + }) => { + setLoading(true); + setError(null); + setApiErrorParams(null); + setCurrentParams(params); + setReportData([]); + + try { + const data = await reportesService.getConsumoBobinasPorPublicacion(params); + setReportData(data); + if (data.length === 0) { + setError("No se encontraron datos para los parámetros seleccionados."); + } + 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); + } finally { + setLoading(false); + } + }, []); + + const handleVolverAParametros = useCallback(() => { + setShowParamSelector(true); + setReportData([]); + setError(null); + setApiErrorParams(null); + setCurrentParams(null); + }, []); + + const handleExportToExcel = useCallback(() => { + if (reportData.length === 0) { + alert("No hay datos para exportar."); + return; + } + const dataToExport = reportData.map(item => ({ + "Planta": item.nombrePlanta, + "Publicación": item.nombrePublicacion, + "Total Kilos": item.totalKilos, + "Cantidad Bobinas": item.cantidadBobinas, + })); + + // Calcular totales generales + const totalGeneralKilos = reportData.reduce((sum, item) => sum + item.totalKilos, 0); + const totalGeneralBobinas = reportData.reduce((sum, item) => sum + item.cantidadBobinas, 0); + + dataToExport.push({ + "Planta": "TOTAL GENERAL", + "Publicación": "", + "Total Kilos": totalGeneralKilos, + "Cantidad Bobinas": totalGeneralBobinas, + }); + + const ws = XLSX.utils.json_to_sheet(dataToExport); + const headers = Object.keys(dataToExport[0] || {}); + ws['!cols'] = headers.map(h => { + const maxLen = Math.max(...dataToExport.map(row => (row as any)[h]?.toString().length ?? 0), h.length); + return { wch: maxLen + 2 }; + }); + ws['!freeze'] = { xSplit: 0, ySplit: 1 }; + + const wb = XLSX.utils.book_new(); + XLSX.utils.book_append_sheet(wb, ws, "ConsumoBobinasPublicacion"); + let fileName = "ReporteConsumoBobinasPublicacion"; + if (currentParams) { + fileName += `_${currentParams.fechaDesde}_a_${currentParams.fechaHasta}`; + } + fileName += ".xlsx"; + XLSX.writeFile(wb, fileName); + }, [reportData, currentParams]); + + const handleGenerarYAbrirPdf = useCallback(async () => { + if (!currentParams) { + setError("Primero debe generar el reporte en pantalla o seleccionar parámetros."); + return; + } + setLoadingPdf(true); + setError(null); + try { + const blob = await reportesService.getConsumoBobinasPorPublicacionPdf(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."); + } + } catch { + setError('Ocurrió un error al generar el PDF.'); + } finally { + setLoadingPdf(false); + } + }, [currentParams]); + + const groupedData = reportData.length > 0 ? groupDataByPlanta(reportData) : {}; + const totalGeneralKilos = reportData.reduce((sum, item) => sum + item.totalKilos, 0); + const totalGeneralBobinas = reportData.reduce((sum, item) => sum + item.cantidadBobinas, 0); + + + if (showParamSelector) { + return ( + + + + + + ); + } + + return ( + + + Reporte: Consumo de Bobinas por Publicación + + + + + + + + {loading && } + {error && !loading && {error}} + + {!loading && !error && reportData.length > 0 && ( + + + + + Planta + Publicación + Total Kilos + Cant. Bobinas + + + + {Object.entries(groupedData).map(([plantaKey, publicaciones]) => { + const totalKilosPlanta = publicaciones.reduce((sum, item) => sum + item.totalKilos, 0); + const totalBobinasPlanta = publicaciones.reduce((sum, item) => sum + item.cantidadBobinas, 0); + return ( + + + {plantaKey} + + {publicaciones.map((pub, pubIdx) => ( + + {/* Columna Planta vacía para esta fila */} + {pub.nombrePublicacion} + {pub.totalKilos.toLocaleString('es-AR')} + {pub.cantidadBobinas.toLocaleString('es-AR')} + + ))} + {/* Fila de totales por planta */} + + Total Planta ({plantaKey}): + {totalKilosPlanta.toLocaleString('es-AR')} + {totalBobinasPlanta.toLocaleString('es-AR')} + + + )})} + + + + TOTAL GENERAL: + {totalGeneralKilos.toLocaleString('es-AR')} + {totalGeneralBobinas.toLocaleString('es-AR')} + + +
+
+ )} + {!loading && !error && reportData.length === 0 && (No se encontraron datos para los criterios seleccionados.)} +
+ ); +}; + +export default ReporteConsumoBobinasPublicacionPage; \ No newline at end of file diff --git a/Frontend/src/pages/Reportes/ReporteConsumoBobinasSeccionPage.tsx b/Frontend/src/pages/Reportes/ReporteConsumoBobinasSeccionPage.tsx new file mode 100644 index 0000000..045f1bd --- /dev/null +++ b/Frontend/src/pages/Reportes/ReporteConsumoBobinasSeccionPage.tsx @@ -0,0 +1,261 @@ +import React, { useState, useCallback } from 'react'; +import { + Box, Typography, Paper, CircularProgress, Alert, Button, + TableContainer, Table, TableHead, TableRow, TableCell, TableBody, TableFooter +} from '@mui/material'; +import reportesService from '../../services/Reportes/reportesService'; +import type { ConsumoBobinasSeccionDto } from '../../models/dtos/Reportes/ConsumoBobinasSeccionDto'; +import SeleccionaReporteConsumoBobinasSeccion from './SeleccionaReporteConsumoBobinasSeccion'; +import * as XLSX from 'xlsx'; +import axios from 'axios'; + +// Helper para agrupar los datos +const groupData = (data: ConsumoBobinasSeccionDto[]) => { + return data.reduce((acc, current) => { + const pubKey = current.nombrePublicacion; + if (!acc[pubKey]) { + acc[pubKey] = {}; + } + const secKey = current.nombreSeccion; + if (!acc[pubKey][secKey]) { + acc[pubKey][secKey] = []; + } + acc[pubKey][secKey].push(current); + return acc; + }, {} as Record>); +}; + + +const ReporteConsumoBobinasSeccionPage: React.FC = () => { + const [reportData, setReportData] = useState([]); + 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<{ + fechaDesde: string; + fechaHasta: string; + idPlanta?: number | null; + consolidado: boolean; + nombrePlanta?: string; // Para el PDF + } | null>(null); + + const handleGenerarReporte = useCallback(async (params: { + fechaDesde: string; + fechaHasta: string; + idPlanta?: number | null; + consolidado: boolean; + }) => { + setLoading(true); + setError(null); + setApiErrorParams(null); + setReportData([]); + + let plantaNombre = "Consolidado"; + if (!params.consolidado && params.idPlanta) { + const plantaService = (await import('../../services/Impresion/plantaService')).default; + const plantaData = await plantaService.getPlantaById(params.idPlanta); + plantaNombre = plantaData?.nombre ?? "N/A"; + } + setCurrentParams({...params, nombrePlanta: plantaNombre}); + + try { + const data = await reportesService.getConsumoBobinasSeccion(params); + setReportData(data); + if (data.length === 0) { + setError("No se encontraron datos para los parámetros seleccionados."); + } + 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); + } finally { + setLoading(false); + } + }, []); + + const handleVolverAParametros = useCallback(() => { + setShowParamSelector(true); + setReportData([]); + setError(null); + setApiErrorParams(null); + setCurrentParams(null); + }, []); + + const handleExportToExcel = useCallback(() => { + if (reportData.length === 0) { + alert("No hay datos para exportar."); + return; + } + // Mapeo plano para Excel, ya que el agrupamiento es más para visualización. + const dataToExport = reportData.map(item => ({ + "Publicación": item.nombrePublicacion, + "Sección": item.nombreSeccion, + "Tipo Bobina": item.nombreBobina, + "Cantidad Bobinas": item.cantidadBobinas, + "Total Kilos": item.totalKilos, + })); + + // Calcular totales generales + const totalGeneralBobinas = reportData.reduce((sum, item) => sum + item.cantidadBobinas, 0); + const totalGeneralKilos = reportData.reduce((sum, item) => sum + item.totalKilos, 0); + + dataToExport.push({ // Fila de totales + "Publicación": "TOTAL GENERAL", + "Sección": "", + "Tipo Bobina": "", + "Cantidad Bobinas": totalGeneralBobinas, + "Total Kilos": totalGeneralKilos, + }); + + + const ws = XLSX.utils.json_to_sheet(dataToExport); + const headers = Object.keys(dataToExport[0] || {}); + ws['!cols'] = headers.map(h => { + const maxLen = Math.max(...dataToExport.map(row => (row as any)[h]?.toString().length ?? 0), h.length); + return { wch: maxLen + 2 }; + }); + ws['!freeze'] = { xSplit: 0, ySplit: 1 }; + + const wb = XLSX.utils.book_new(); + XLSX.utils.book_append_sheet(wb, ws, "ConsumoBobinasSeccion"); + let fileName = "ReporteConsumoBobinasSeccion"; + if (currentParams) { + fileName += `_${currentParams.consolidado ? 'Consolidado' : `Planta${currentParams.idPlanta}`}`; + fileName += `_${currentParams.fechaDesde}_a_${currentParams.fechaHasta}`; + } + fileName += ".xlsx"; + XLSX.writeFile(wb, fileName); + }, [reportData, currentParams]); + + const handleGenerarYAbrirPdf = useCallback(async () => { + if (!currentParams) { + setError("Primero debe generar el reporte en pantalla o seleccionar parámetros."); + return; + } + setLoadingPdf(true); + setError(null); + try { + const blob = await reportesService.getConsumoBobinasSeccionPdf(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."); + } + } catch { + setError('Ocurrió un error al generar el PDF.'); + } finally { + setLoadingPdf(false); + } + }, [currentParams]); + + const groupedData = reportData.length > 0 ? groupData(reportData) : {}; + const totalGeneralBobinas = reportData.reduce((sum, item) => sum + item.cantidadBobinas, 0); + const totalGeneralKilos = reportData.reduce((sum, item) => sum + item.totalKilos, 0); + + if (showParamSelector) { + return ( + + + + + + ); + } + + return ( + + + Reporte: Consumo de Bobinas por Sección + + + + + + + + {loading && } + {error && !loading && {error}} + + {!loading && !error && reportData.length > 0 && ( + + + + + Publicación + Sección + Tipo Bobina + Cant. Bobinas + Total Kilos + + + + {Object.entries(groupedData).map(([pubKey, secciones]) => ( + + + {pubKey} + + {Object.entries(secciones).map(([secKey, bobinas]) => { + const totalBobinasSeccion = bobinas.reduce((sum, item) => sum + item.cantidadBobinas, 0); + const totalKilosSeccion = bobinas.reduce((sum, item) => sum + item.totalKilos, 0); + return ( + + + {/* Columna Publicación vacía para esta fila */} + {secKey} + + {bobinas.map((bob, bobIdx) => ( + + + + {bob.nombreBobina} + {bob.cantidadBobinas.toLocaleString('es-AR')} + {bob.totalKilos.toLocaleString('es-AR')} + + ))} + {/* Fila de totales por sección */} + + Total Sección ({secKey}): + {totalBobinasSeccion.toLocaleString('es-AR')} + {totalKilosSeccion.toLocaleString('es-AR')} + + + ); + })} + + ))} + + + + TOTAL GENERAL: + {totalGeneralBobinas.toLocaleString('es-AR')} + {totalGeneralKilos.toLocaleString('es-AR')} + + +
+
+ )} + {!loading && !error && reportData.length === 0 && (No se encontraron datos para los criterios seleccionados.)} +
+ ); +}; + +export default ReporteConsumoBobinasSeccionPage; \ No newline at end of file diff --git a/Frontend/src/pages/Reportes/ReporteCuentasDistribuidoresPage.tsx b/Frontend/src/pages/Reportes/ReporteCuentasDistribuidoresPage.tsx new file mode 100644 index 0000000..89799ed --- /dev/null +++ b/Frontend/src/pages/Reportes/ReporteCuentasDistribuidoresPage.tsx @@ -0,0 +1,294 @@ +import React, { useState, useCallback } from 'react'; +import { + Box, Typography, Paper, CircularProgress, Alert, Button, + TableContainer, Table, TableHead, TableRow, TableCell, TableBody +} from '@mui/material'; +import reportesService from '../../services/Reportes/reportesService'; +import type { ReporteCuentasDistribuidorResponseDto } from '../../models/dtos/Reportes/ReporteCuentasDistribuidorResponseDto'; +import type { BalanceCuentaDistDto } from '../../models/dtos/Reportes/BalanceCuentaDistDto'; +import type { BalanceCuentaDebCredDto } from '../../models/dtos/Reportes/BalanceCuentaDebCredDto'; +import type { BalanceCuentaPagosDto } from '../../models/dtos/Reportes/BalanceCuentaPagosDto'; +import SeleccionaReporteCuentasDistribuidores from './SeleccionaReporteCuentasDistribuidores'; +import * as XLSX from 'xlsx'; +import axios from 'axios'; + +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 [apiErrorParams, setApiErrorParams] = useState(null); + 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 + } | null>(null); + + const handleGenerarReporte = useCallback(async (params: { + idDistribuidor: number; + idEmpresa: number; + fechaDesde: string; + 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); + + setCurrentParams({ + ...params, + nombreDistribuidor: distData?.nombre, + nombreEmpresa: empData?.nombre + }); + + 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."); + } + 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); + } finally { + setLoading(false); + } + }, []); + + const handleVolverAParametros = useCallback(() => { + setShowParamSelector(true); + setReportData(null); + setError(null); + setApiErrorParams(null); + setCurrentParams(null); + }, []); + + const handleExportToExcel = useCallback(() => { + if (!reportData) { + alert("No hay datos para exportar."); + 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 (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 (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 (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]); + + const handleGenerarYAbrirPdf = useCallback(async () => { + if (!currentParams) { + setError("Primero debe generar el reporte en pantalla o seleccionar parámetros."); + 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."); + } + } catch { + setError('Ocurrió un error al generar el PDF.'); + } 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 ( + + + + + + ); + } + + return ( + + + Reporte: Cuenta Corriente Distribuidor + + + + + + + + {loading && } + {error && !loading && {error}} + + {!loading && !error && reportData && ( + <> + Saldo Actual + {reportData.saldos && reportData.saldos.length > 0 ? ( + + {reportData.saldos[0].monto.toLocaleString('es-AR', { style: 'currency', currency: 'ARS' })} + + ) : No hay saldo actual disponible.} + + Movimientos (Entradas/Salidas) + {reportData.entradasSalidas && reportData.entradasSalidas.length > 0 ? + renderEntradasSalidasTable(reportData.entradasSalidas) : No hay movimientos de entradas/salidas.} + + Notas de Débito/Crédito + {reportData.debitosCreditos && reportData.debitosCreditos.length > 0 ? + renderDebitosCreditosTable(reportData.debitosCreditos) : No hay notas de débito/crédito.} + + Pagos + {reportData.pagos && reportData.pagos.length > 0 ? + renderPagosTable(reportData.pagos) : No hay pagos registrados.} + + )} + {!loading && !error && (!reportData || (!reportData.entradasSalidas?.length && !reportData.debitosCreditos?.length && !reportData.pagos?.length && !reportData.saldos?.length)) && + (No se encontraron datos para los criterios seleccionados.)} + + ); +}; + +export default ReporteCuentasDistribuidoresPage; \ No newline at end of file diff --git a/Frontend/src/pages/Reportes/ReporteDetalleDistribucionCanillasPage.tsx b/Frontend/src/pages/Reportes/ReporteDetalleDistribucionCanillasPage.tsx new file mode 100644 index 0000000..acefd16 --- /dev/null +++ b/Frontend/src/pages/Reportes/ReporteDetalleDistribucionCanillasPage.tsx @@ -0,0 +1,248 @@ +import React, { useState, useCallback } from 'react'; +import { + Box, Typography, Paper, CircularProgress, Alert, Button, + TableContainer, Table, TableHead, TableRow, TableCell, TableBody +} from '@mui/material'; +import reportesService from '../../services/Reportes/reportesService'; +import type { ReporteDistribucionCanillasResponseDto } from '../../models/dtos/Reportes/ReporteDistribucionCanillasResponseDto'; +import type { DetalleDistribucionCanillaDto } from '../../models/dtos/Reportes/DetalleDistribucionCanillaDto'; +import type { DetalleDistribucionCanillaAllDto } from '../../models/dtos/Reportes/DetalleDistribucionCanillaAllDto'; +import SeleccionaReporteDetalleDistribucionCanillas from './SeleccionaReporteDetalleDistribucionCanillas'; +import * as XLSX from 'xlsx'; +import axios from 'axios'; + +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<{ + fecha: string; + idEmpresa: number; + nombreEmpresa?: string; + } | null>(null); + const [pdfSoloTotales, setPdfSoloTotales] = useState(false); // Estado para el tipo de PDF + + + const handleGenerarReporte = useCallback(async (params: { + 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); + + try { + const data = await reportesService.getReporteDistribucionCanillas(params); + setReportData(data); + const noData = (!data.canillas || data.canillas.length === 0) && + (!data.canillasAccionistas || data.canillasAccionistas.length === 0) && + (!data.canillasTodos || data.canillasTodos.length === 0) && + (!data.controlDevolucionesDetalle || data.controlDevolucionesDetalle.length === 0); + if (noData) { + setError("No se encontraron datos para los parámetros seleccionados."); + } + 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); + } finally { + setLoading(false); + } + }, []); + + const handleVolverAParametros = useCallback(() => { + setShowParamSelector(true); + setReportData(null); + setError(null); + setApiErrorParams(null); + setCurrentParams(null); + }, []); + + const handleExportToExcel = useCallback(() => { + if (!reportData) { + alert("No hay datos para exportar."); + return; + } + const wb = XLSX.utils.book_new(); + + const formatAndSheet = (data: any[], sheetName: string, fields: Record) => { + if (data && data.length > 0) { + const exportedData = data.map(item => { + const row: Record = {}; + Object.keys(fields).forEach(key => { + row[fields[key]] = item[key]; + if (key === 'fecha' && item[key]) { + row[fields[key]] = new Date(item[key]).toLocaleDateString('es-AR', { timeZone: 'UTC' }); + } + if ((key === 'totalRendir') && item[key] != null) { + row[fields[key]] = parseFloat(item[key]).toFixed(2); + } + }); + return row; + }); + 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 }; + XLSX.utils.book_append_sheet(wb, ws, sheetName); + } + }; + + formatAndSheet(reportData.canillas, "Canillitas_Dia", { publicacion: "Publicación", canilla: "Canilla", totalCantSalida: "Llevados", totalCantEntrada: "Devueltos", totalRendir: "A Rendir" }); + formatAndSheet(reportData.canillasAccionistas, "Accionistas_Dia", { publicacion: "Publicación", canilla: "Canilla", totalCantSalida: "Llevados", totalCantEntrada: "Devueltos", totalRendir: "A Rendir" }); + formatAndSheet(reportData.canillasTodos, "Resumen_Dia", { publicacion: "Publicación", tipoVendedor: "Tipo", totalCantSalida: "Llevados", totalCantEntrada: "Devueltos", totalRendir: "A Rendir" }); + formatAndSheet(reportData.canillasLiquidadasOtraFecha, "Canillitas_OtrasFechas", { publicacion: "Publicación", canilla: "Canilla", fecha:"Fecha Mov.", totalCantSalida: "Llevados", totalCantEntrada: "Devueltos", totalRendir: "A Rendir" }); + formatAndSheet(reportData.canillasAccionistasLiquidadasOtraFecha, "Accionistas_OtrasFechas", { publicacion: "Publicación", canilla: "Canilla", fecha:"Fecha Mov.", totalCantSalida: "Llevados", totalCantEntrada: "Devueltos", totalRendir: "A Rendir" }); + formatAndSheet(reportData.controlDevolucionesDetalle, "CtrlDev_Detalle", { ingresados: "Ingresados", sobrantes: "Sobrantes", sinCargo: "Sin Cargo", publicacion: "Publicación", llevados: "Llevados", devueltos: "Devueltos", tipo: "Tipo" }); + formatAndSheet(reportData.controlDevolucionesRemitos, "CtrlDev_Remitos", { remito: "Remito Ingresado" }); + formatAndSheet(reportData.controlDevolucionesOtrosDias, "CtrlDev_OtrosDias", { devueltos: "Devueltos Otros Días" }); + + + let fileName = "ReporteDistribucionCanillitas"; + if (currentParams) { + fileName += `_${currentParams.nombreEmpresa?.replace(/\s+/g, '') ?? `Emp${currentParams.idEmpresa}`}`; + fileName += `_${currentParams.fecha}`; + } + fileName += ".xlsx"; + XLSX.writeFile(wb, fileName); + }, [reportData, currentParams]); + + 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); // Guardar la opción para el nombre del archivo + try { + const blob = await reportesService.getReporteDistribucionCanillasPdf({ + ...currentParams, + soloTotales // Pasar el parámetro al servicio + }); + 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."); + } + } catch { + setError('Ocurrió un error al generar el PDF.'); + } finally { + setLoadingPdf(false); + } + }, [currentParams]); + + const renderTableData = (data: DetalleDistribucionCanillaDto[] | DetalleDistribucionCanillaAllDto[], title: string, isAllDto: boolean = false) => { + if (!data || data.length === 0) return No hay datos para {title.toLowerCase()}.; + return ( + <> + {title} + + + + + Publicación + {isAllDto ? 'Tipo Vendedor' : 'Canillita'} + { (data[0] as DetalleDistribucionCanillaDto).fecha && Fecha Mov. } + Llevados + Devueltos + Vendidos + A Rendir + + + + {data.map((row, idx) => { + const item = row as any; // Para acceso dinámico + const fechaMov = item.fecha ? new Date(item.fecha).toLocaleDateString('es-AR', { timeZone: 'UTC' }) : null; + return ( + + {item.publicacion} + {isAllDto ? item.tipoVendedor : item.canilla} + { fechaMov && {fechaMov} } + {item.totalCantSalida.toLocaleString('es-AR')} + {item.totalCantEntrada.toLocaleString('es-AR')} + {(item.totalCantSalida - item.totalCantEntrada).toLocaleString('es-AR')} + {item.totalRendir.toLocaleString('es-AR', { style: 'currency', currency: 'ARS' })} + + )})} + +
+
+ + ); + }; + + if (showParamSelector) { + return ( + + + + + + ); + } + + return ( + + + Reporte: Detalle Distribución Canillitas + + + + + + + + + {loading && } + {error && !loading && {error}} + + {!loading && !error && reportData && ( + <> + {renderTableData(reportData.canillas, "Canillitas")} + {renderTableData(reportData.canillasAccionistas, "Accionistas")} + {renderTableData(reportData.canillasTodos, "Resumen por Tipo de Vendedor", true)} + {reportData.canillasLiquidadasOtraFecha && reportData.canillasLiquidadasOtraFecha.length > 0 && + renderTableData(reportData.canillasLiquidadasOtraFecha, "Canillitas (Liquidados de Otras Fechas)")} + {reportData.canillasAccionistasLiquidadasOtraFecha && reportData.canillasAccionistasLiquidadasOtraFecha.length > 0 && + renderTableData(reportData.canillasAccionistasLiquidadasOtraFecha, "Accionistas (Liquidados de Otras Fechas)")} + + )} + {!loading && !error && (!reportData || ((!reportData.canillas || reportData.canillas.length === 0) && (!reportData.canillasAccionistas || reportData.canillasAccionistas.length === 0))) && + (No se encontraron datos para los criterios seleccionados.)} + + ); +}; + +export default ReporteDetalleDistribucionCanillasPage; \ No newline at end of file diff --git a/Frontend/src/pages/Reportes/ReporteListadoDistribucionCanillasImportePage.tsx b/Frontend/src/pages/Reportes/ReporteListadoDistribucionCanillasImportePage.tsx new file mode 100644 index 0000000..2334233 --- /dev/null +++ b/Frontend/src/pages/Reportes/ReporteListadoDistribucionCanillasImportePage.tsx @@ -0,0 +1,197 @@ +import React, { useState, useCallback } from 'react'; +import { + Box, Typography, Paper, CircularProgress, Alert, Button, + TableContainer, Table, TableHead, TableRow, TableCell, TableBody +} from '@mui/material'; +import reportesService from '../../services/Reportes/reportesService'; +import type { ListadoDistribucionCanillasImporteDto } from '../../models/dtos/Reportes/ListadoDistribucionCanillasImporteDto'; +import SeleccionaReporteListadoDistribucionCanillasImporte from './SeleccionaReporteListadoDistribucionCanillasImporte'; +import * as XLSX from 'xlsx'; +import axios from 'axios'; + +const ReporteListadoDistribucionCanillasImportePage: React.FC = () => { + const [reportData, setReportData] = useState([]); + 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; + fechaHasta: string; + esAccionista: boolean; + nombrePublicacion?: string; + } | null>(null); + + const handleGenerarReporte = useCallback(async (params: { + idPublicacion: number; + fechaDesde: string; + fechaHasta: string; + esAccionista: boolean; + }) => { + setLoading(true); + setError(null); + setApiErrorParams(null); + + const pubService = (await import('../../services/Distribucion/publicacionService')).default; + const pubData = await pubService.getPublicacionById(params.idPublicacion); + + setCurrentParams({...params, nombrePublicacion: pubData?.nombre}); + try { + const data = await reportesService.getListadoDistribucionCanillasImporte(params); + setReportData(data); + if (data.length === 0) { + setError("No se encontraron datos para los parámetros seleccionados."); + } + 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); + setReportData([]); + } finally { + setLoading(false); + } + }, []); + + const handleVolverAParametros = useCallback(() => { + setShowParamSelector(true); + setReportData([]); + setError(null); + setApiErrorParams(null); + setCurrentParams(null); + }, []); + + const handleExportToExcel = useCallback(() => { + if (reportData.length === 0) { + alert("No hay datos para exportar."); + return; + } + const dataToExport = reportData.map(item => ({ + "Fecha": item.fecha, // Ya viene como string dd/MM/yyyy del SP + "Llevados": item.llevados, + "Devueltos": item.devueltos, + "Vendidos": item.vendidos, + "Imp. Publicación": item.totalRendirPublicacion, + "A Rendir": item.totalRendirGeneral, + })); + + const ws = XLSX.utils.json_to_sheet(dataToExport); + const headers = Object.keys(dataToExport[0]); + ws['!cols'] = headers.map(h => { + const maxLen = dataToExport.reduce((prev, row) => { + const cell = (row as any)[h]?.toString() ?? ''; + return Math.max(prev, cell.length); + }, h.length); + return { wch: maxLen + 2 }; + }); + ws['!freeze'] = { xSplit: 0, ySplit: 1 }; + + const wb = XLSX.utils.book_new(); + XLSX.utils.book_append_sheet(wb, ws, "DistribucionCanillasImporte"); + let fileName = "ListadoDistribucionCanillasImporte"; + if (currentParams) { + fileName += `_${currentParams.nombrePublicacion?.replace(/\s+/g, '') ?? `Pub${currentParams.idPublicacion}`}`; + fileName += `_${currentParams.esAccionista ? 'Accionistas' : 'Canillitas'}`; + fileName += `_${currentParams.fechaDesde}_a_${currentParams.fechaHasta}`; + } + fileName += ".xlsx"; + XLSX.writeFile(wb, fileName); + }, [reportData, currentParams]); + + const handleGenerarYAbrirPdf = useCallback(async () => { + if (!currentParams) { + setError("Primero debe generar el reporte en pantalla o seleccionar parámetros."); + return; + } + setLoadingPdf(true); + setError(null); + try { + const blob = await reportesService.getListadoDistribucionCanillasImportePdf(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."); + } + } catch { + setError('Ocurrió un error al generar el PDF.'); + } finally { + setLoadingPdf(false); + } + }, [currentParams]); + + if (showParamSelector) { + return ( + + + + + + ); + } + + return ( + + + Reporte: Distribución Canillitas con Importe + + + + + + + + {loading && } + {error && !loading && {error}} + + {!loading && !error && reportData.length > 0 && ( + + + + + Fecha + Llevados + Devueltos + Vendidos + Importe Publicación + A Rendir + + + + {reportData.map((row, idx) => ( + + {row.fecha} + {row.llevados.toLocaleString('es-AR')} + {row.devueltos.toLocaleString('es-AR')} + {row.vendidos.toLocaleString('es-AR')} + {row.totalRendirPublicacion.toLocaleString('es-AR', { style: 'currency', currency: 'ARS' })} + {row.totalRendirGeneral.toLocaleString('es-AR', { style: 'currency', currency: 'ARS' })} + + ))} + +
+
+ )} + {!loading && !error && reportData.length === 0 && (No se encontraron datos para los criterios seleccionados.)} +
+ ); +}; + +export default ReporteListadoDistribucionCanillasImportePage; \ No newline at end of file diff --git a/Frontend/src/pages/Reportes/ReporteTiradasPublicacionesSeccionesPage.tsx b/Frontend/src/pages/Reportes/ReporteTiradasPublicacionesSeccionesPage.tsx new file mode 100644 index 0000000..58a0321 --- /dev/null +++ b/Frontend/src/pages/Reportes/ReporteTiradasPublicacionesSeccionesPage.tsx @@ -0,0 +1,209 @@ +import React, { useState, useCallback } from 'react'; +import { + Box, Typography, Paper, CircularProgress, Alert, Button, + TableContainer, Table, TableHead, TableRow, TableCell, TableBody +} from '@mui/material'; +import reportesService from '../../services/Reportes/reportesService'; +import type { TiradasPublicacionesSeccionesDto } from '../../models/dtos/Reportes/TiradasPublicacionesSeccionesDto'; +import SeleccionaReporteTiradasPublicacionesSecciones from './SeleccionaReporteTiradasPublicacionesSecciones'; +import * as XLSX from 'xlsx'; +import axios from 'axios'; + +const ReporteTiradasPublicacionesSeccionesPage: React.FC = () => { + const [reportData, setReportData] = useState([]); + 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; + fechaHasta: string; + idPlanta?: number | null; + consolidado: boolean; + nombrePublicacion?: string; + nombrePlanta?: string; + mesAnioParaNombreArchivo?: string; + } | null>(null); + + const handleGenerarReporte = useCallback(async (params: { + idPublicacion: number; + fechaDesde: string; + fechaHasta: string; + idPlanta?: number | null; + consolidado: boolean; + }) => { + setLoading(true); + setError(null); + setApiErrorParams(null); + setReportData([]); + + const pubService = (await import('../../services/Distribucion/publicacionService')).default; + const pubData = await pubService.getPublicacionById(params.idPublicacion); + let plantaNombre = "Consolidado"; + if (!params.consolidado && params.idPlanta) { + const plantaService = (await import('../../services/Impresion/plantaService')).default; + const plantaData = await plantaService.getPlantaById(params.idPlanta); + plantaNombre = plantaData?.nombre ?? "N/A"; + } + const mesAnioParts = params.fechaDesde.split('-'); + const mesAnioNombre = `${mesAnioParts[1]}/${mesAnioParts[0]}`; + + setCurrentParams({...params, nombrePublicacion: pubData?.nombre, nombrePlanta: plantaNombre, mesAnioParaNombreArchivo: mesAnioNombre}); + + try { + const data = await reportesService.getTiradasPublicacionesSecciones(params); + setReportData(data); + if (data.length === 0) { + setError("No se encontraron datos para los parámetros seleccionados."); + } + 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); + } finally { + setLoading(false); + } + }, []); + + const handleVolverAParametros = useCallback(() => { + setShowParamSelector(true); + setReportData([]); + setError(null); + setApiErrorParams(null); + setCurrentParams(null); + }, []); + + const handleExportToExcel = useCallback(() => { + if (reportData.length === 0) { + alert("No hay datos para exportar."); + return; + } + const dataToExport = reportData.map(item => ({ + "Nombre Sección": item.nombreSeccion, + "Total Páginas Impresas": item.totalPaginasImpresas, + "Cantidad Ediciones": item.cantidadTiradas, + "Total Páginas x Edición": item.totalPaginasEjemplares, + "Total Ejemplares": item.totalEjemplares, + "Prom. Pág./Ejemplar": item.promedioPaginasPorEjemplar, + })); + + const ws = XLSX.utils.json_to_sheet(dataToExport); + const headers = Object.keys(dataToExport[0] || {}); + ws['!cols'] = headers.map(h => { + const maxLen = Math.max(...dataToExport.map(row => (row as any)[h]?.toString().length ?? 0), h.length); + return { wch: maxLen + 2 }; + }); + ws['!freeze'] = { xSplit: 0, ySplit: 1 }; + + const wb = XLSX.utils.book_new(); + XLSX.utils.book_append_sheet(wb, ws, "TiradasSecciones"); + + let fileName = "ReporteTiradasSecciones"; + if (currentParams) { + fileName += `_${currentParams.nombrePublicacion?.replace(/\s+/g, '') ?? `Pub${currentParams.idPublicacion}`}`; + if (!currentParams.consolidado) fileName += `_Planta${currentParams.idPlanta}`; + else fileName += "_Consolidado"; + fileName += `_${currentParams.mesAnioParaNombreArchivo?.replace('/', '-')}`; + } + fileName += ".xlsx"; + XLSX.writeFile(wb, fileName); + }, [reportData, currentParams]); + + const handleGenerarYAbrirPdf = useCallback(async () => { + if (!currentParams) { + setError("Primero debe generar el reporte en pantalla o seleccionar parámetros."); + return; + } + setLoadingPdf(true); + setError(null); + try { + const blob = await reportesService.getTiradasPublicacionesSeccionesPdf(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."); + } + } catch { + setError('Ocurrió un error al generar el PDF.'); + } finally { + setLoadingPdf(false); + } + }, [currentParams]); + + if (showParamSelector) { + return ( + + + + + + ); + } + + return ( + + + Reporte: Tiradas por Publicación y Secciones + + + + + + + + {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.)} +
+ ); +}; + +export default ReporteTiradasPublicacionesSeccionesPage; \ No newline at end of file diff --git a/Frontend/src/pages/Reportes/ReporteVentaMensualSecretariaPage.tsx b/Frontend/src/pages/Reportes/ReporteVentaMensualSecretariaPage.tsx new file mode 100644 index 0000000..9f689b2 --- /dev/null +++ b/Frontend/src/pages/Reportes/ReporteVentaMensualSecretariaPage.tsx @@ -0,0 +1,326 @@ +import React, { useState, useCallback } from 'react'; +import { + Box, Typography, Paper, CircularProgress, Alert, Button, + TableContainer, Table, TableHead, TableRow, TableCell, TableBody +} from '@mui/material'; +import reportesService from '../../services/Reportes/reportesService'; +import type { VentaMensualSecretariaElDiaDto } from '../../models/dtos/Reportes/VentaMensualSecretariaElDiaDto'; +import type { VentaMensualSecretariaElPlataDto } from '../../models/dtos/Reportes/VentaMensualSecretariaElPlataDto'; +import type { VentaMensualSecretariaTirDevoDto } from '../../models/dtos/Reportes/VentaMensualSecretariaTirDevoDto'; +import SeleccionaReporteVentaMensual, { type TipoReporteVentaMensual } from './SeleccionaReporteVentaMensual'; +import * as XLSX from 'xlsx'; +import axios from 'axios'; + +type ReportData = VentaMensualSecretariaElDiaDto[] | VentaMensualSecretariaElPlataDto[] | VentaMensualSecretariaTirDevoDto[]; + +const ReporteVentaMensualSecretariaPage: React.FC = () => { + const [reportData, setReportData] = useState([]); + const [currentReportType, setCurrentReportType] = 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<{ + fechaDesde: string; + fechaHasta: string; + tipoReporte: TipoReporteVentaMensual; + } | null>(null); + + const handleGenerarReporte = useCallback(async (params: { + fechaDesde: string; + fechaHasta: string; + tipoReporte: TipoReporteVentaMensual; + }) => { + setLoading(true); + setError(null); + setApiErrorParams(null); + setCurrentParams(params); + setCurrentReportType(params.tipoReporte); + setReportData([]); + + try { + let data: ReportData = []; + if (params.tipoReporte === 'ElDia') { + data = await reportesService.getVentaMensualSecretariaElDia(params); + } else if (params.tipoReporte === 'ElPlata') { + data = await reportesService.getVentaMensualSecretariaElPlata(params); + } else if (params.tipoReporte === 'TirDevo') { + data = await reportesService.getVentaMensualSecretariaTirDevo(params); + } + setReportData(data); + if (data.length === 0) { + setError("No se encontraron datos para los parámetros seleccionados."); + } + 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); + } finally { + setLoading(false); + } + }, []); + + const handleVolverAParametros = useCallback(() => { + setShowParamSelector(true); + setReportData([]); + setError(null); + setApiErrorParams(null); + setCurrentParams(null); + setCurrentReportType(null); + }, []); + + const getReportTitle = () => { + if (!currentReportType) return "Venta Mensual Secretaría"; + if (currentReportType === 'ElDia') return "Venta Mensual: El Día"; + if (currentReportType === 'ElPlata') return "Venta Mensual: El Plata"; + if (currentReportType === 'TirDevo') return "Venta Mensual: Tirada/Devolución"; + return "Venta Mensual Secretaría"; + }; + + const handleExportToExcel = useCallback(() => { + if (reportData.length === 0 || !currentReportType) { + alert("No hay datos para exportar."); + return; + } + + let dataToExport: Record[] = []; + let sheetName = "Reporte"; + let fileNamePrefix = "ReporteVentaMensual"; + + if (currentReportType === 'ElDia') { + dataToExport = (reportData as VentaMensualSecretariaElDiaDto[]).map(item => ({ + "Día": item.dia, "Canillitas": item.cantidadCanillas, "Tirajes": item.tirajes, + "Ventas": item.ventas, "Accionistas": item.accionistas, "Total Coop.": item.totalCooperativa, + "Total General": item.totalGeneral + })); + sheetName = "ElDia"; + fileNamePrefix += "_ElDia"; + } else if (currentReportType === 'ElPlata') { + dataToExport = (reportData as VentaMensualSecretariaElPlataDto[]).map(item => ({ + "Día": item.dia, "Tirada Coop.": item.tiradaCoop, "Devol. Coop.": item.devolucionCoop, + "Venta Coop.": item.ventaCoop, "Tirada Can.": item.tiradaCan, "Venta Can.": item.ventaCan, + "Total": item.total + })); + sheetName = "ElPlata"; + fileNamePrefix += "_ElPlata"; + } else if (currentReportType === 'TirDevo') { + dataToExport = (reportData as VentaMensualSecretariaTirDevoDto[]).map(item => ({ + "Día": item.dia, "Tir. Coop. (ED)": item.tiradaCoop, "Dev. Coop. (ED)": item.devolucionCoop, + "Vta. Coop. (ED)": item.ventaCoop, "Vta. Can. (ED)": item.ventaCan, + "Tir. Popular": item.tiradaPopular, "Dev. Popular": item.devolucionPopular, "Vta. Popular": item.ventaPopular, + "Tir. Clarín": item.tiradaClarin, "Dev. Clarín": item.devolucionClarin, "Vta. Clarín": item.ventaClarin, + "Tir. Nación": item.tiradaNacion, "Dev. Nación": item.devolucionNacion, "Vta. Nación": item.ventaNacion, + })); + sheetName = "TiradaDevolucion"; + fileNamePrefix += "_TirDevo"; + } + + const ws = XLSX.utils.json_to_sheet(dataToExport); + const headers = Object.keys(dataToExport[0] || {}); + ws['!cols'] = headers.map(h => { + const maxLen = Math.max(...dataToExport.map(row => (row[h]?.toString() ?? '').length), h.length); + return { wch: maxLen + 2 }; + }); + ws['!freeze'] = { xSplit: 0, ySplit: 1 }; + const wb = XLSX.utils.book_new(); + XLSX.utils.book_append_sheet(wb, ws, sheetName); + let fileName = fileNamePrefix; + if (currentParams) { + fileName += `_${currentParams.fechaDesde}_a_${currentParams.fechaHasta}`; + } + fileName += ".xlsx"; + XLSX.writeFile(wb, fileName); + }, [reportData, currentReportType, currentParams]); + + const handleGenerarYAbrirPdf = useCallback(async () => { + if (!currentParams) { + setError("Primero debe generar el reporte en pantalla o seleccionar parámetros."); + return; + } + setLoadingPdf(true); + setError(null); + try { + let blob: Blob; + if (currentParams.tipoReporte === 'ElDia') { + blob = await reportesService.getVentaMensualSecretariaElDiaPdf(currentParams); + } else if (currentParams.tipoReporte === 'ElPlata') { + blob = await reportesService.getVentaMensualSecretariaElPlataPdf(currentParams); + } else if (currentParams.tipoReporte === 'TirDevo') { + blob = await reportesService.getVentaMensualSecretariaTirDevoPdf(currentParams); + } else { + setError("Tipo de reporte no válido para PDF."); + setLoadingPdf(false); + return; + } + + 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."); + } + } catch { + setError('Ocurrió un error al generar el PDF.'); + } finally { + setLoadingPdf(false); + } + }, [currentParams]); + + const renderTable = () => { + if (!reportData || reportData.length === 0 || !currentReportType) return null; + + if (currentReportType === 'ElDia') { + const data = reportData as VentaMensualSecretariaElDiaDto[]; + return ( + + + + + DíaCanillitas + TirajesVentas + AccionistasTotal Coop. + Total General + + + + {data.map((row, idx) => ( + + {row.dia} + {row.cantidadCanillas.toLocaleString('es-AR')} + {row.tirajes.toLocaleString('es-AR')} + {row.ventas.toLocaleString('es-AR')} + {row.accionistas.toLocaleString('es-AR')} + {row.totalCooperativa.toLocaleString('es-AR')} + {row.totalGeneral.toLocaleString('es-AR')} + + ))} + +
+
+ ); + } else if (currentReportType === 'ElPlata') { + const data = reportData as VentaMensualSecretariaElPlataDto[]; + return ( + + + + + DíaTirada Coop. + Devol. Coop.Venta Coop. + Tirada Can.Venta Can. + Total + + + + {data.map((row, idx) => ( + + {row.dia} + {row.tiradaCoop.toLocaleString('es-AR')} + {row.devolucionCoop.toLocaleString('es-AR')} + {row.ventaCoop.toLocaleString('es-AR')} + {row.tiradaCan.toLocaleString('es-AR')} + {row.ventaCan.toLocaleString('es-AR')} + {row.total.toLocaleString('es-AR')} + + ))} + +
+
+ ); + } else if (currentReportType === 'TirDevo') { + const data = reportData as VentaMensualSecretariaTirDevoDto[]; + return ( + + + + + Día + El Día + Popular + Clarín + Nación + + + Tir. Coop.Dev. Coop. + Vta. Coop.Vta. Can. + TiradaDevol.Venta + TiradaDevol.Venta + TiradaDevol.Venta + + + + {data.map((row, idx) => ( + + {row.dia} + {row.tiradaCoop.toLocaleString('es-AR')} + {row.devolucionCoop.toLocaleString('es-AR')} + {row.ventaCoop.toLocaleString('es-AR')} + {row.ventaCan.toLocaleString('es-AR')} + {row.tiradaPopular.toLocaleString('es-AR')} + {row.devolucionPopular.toLocaleString('es-AR')} + {row.ventaPopular.toLocaleString('es-AR')} + {row.tiradaClarin.toLocaleString('es-AR')} + {row.devolucionClarin.toLocaleString('es-AR')} + {row.ventaClarin.toLocaleString('es-AR')} + {row.tiradaNacion.toLocaleString('es-AR')} + {row.devolucionNacion.toLocaleString('es-AR')} + {row.ventaNacion.toLocaleString('es-AR')} + + ))} + +
+
+ ); + } + return null; + }; + + + if (showParamSelector) { + return ( + + + + + + ); + } + + return ( + + + {getReportTitle()} + + + + + + + + {loading && } + {error && !loading && {error}} + + {!loading && !error && renderTable()} + {!loading && !error && reportData.length === 0 && (No se encontraron datos para los criterios seleccionados.)} + + ); +}; + +export default ReporteVentaMensualSecretariaPage; \ No newline at end of file diff --git a/Frontend/src/pages/Reportes/ReportesIndexPage.tsx b/Frontend/src/pages/Reportes/ReportesIndexPage.tsx index c10afa6..245df27 100644 --- a/Frontend/src/pages/Reportes/ReportesIndexPage.tsx +++ b/Frontend/src/pages/Reportes/ReportesIndexPage.tsx @@ -8,6 +8,14 @@ const reportesSubModules = [ { 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' }, ]; const ReportesIndexPage: React.FC = () => { diff --git a/Frontend/src/pages/Reportes/SeleccionaReporteComparativaConsumoBobinas.tsx b/Frontend/src/pages/Reportes/SeleccionaReporteComparativaConsumoBobinas.tsx new file mode 100644 index 0000000..025755b --- /dev/null +++ b/Frontend/src/pages/Reportes/SeleccionaReporteComparativaConsumoBobinas.tsx @@ -0,0 +1,164 @@ +import React, { useState, useEffect } from 'react'; +import { + Box, Typography, TextField, Button, CircularProgress, Alert, + FormControl, InputLabel, Select, MenuItem, FormControlLabel, Checkbox +} from '@mui/material'; +import type { PlantaDto } from '../../models/dtos/Impresion/PlantaDto'; +import plantaService from '../../services/Impresion/plantaService'; + +interface SeleccionaReporteComparativaConsumoBobinasProps { + onGenerarReporte: (params: { + fechaInicioMesA: string; + fechaFinMesA: string; + fechaInicioMesB: string; + fechaFinMesB: string; + idPlanta?: number | null; + consolidado: boolean; + }) => Promise; + onCancel: () => void; + isLoading?: boolean; + apiErrorMessage?: string | null; +} + +const SeleccionaReporteComparativaConsumoBobinas: React.FC = ({ + onGenerarReporte, + isLoading, + apiErrorMessage +}) => { + const today = new Date(); + const currentMonthYear = today.toISOString().substring(0, 7); // YYYY-MM para el mes actual + const lastMonthDate = new Date(today.getFullYear(), today.getMonth() - 1, 1); + const lastMonthYear = lastMonthDate.toISOString().substring(0, 7); // YYYY-MM para el mes pasado + + const [mesAnioA, setMesAnioA] = useState(lastMonthYear); + const [mesAnioB, setMesAnioB] = useState(currentMonthYear); + const [idPlanta, setIdPlanta] = useState(''); + const [consolidado, setConsolidado] = useState(false); + + const [plantas, setPlantas] = useState([]); + const [loadingDropdowns, setLoadingDropdowns] = useState(false); + const [localErrors, setLocalErrors] = useState<{ [key: string]: string | null }>({}); + + useEffect(() => { + const fetchPlantas = async () => { + setLoadingDropdowns(true); + try { + const plantasData = await plantaService.getAllPlantas(); + setPlantas(plantasData); + } catch (error) { + console.error("Error al cargar plantas:", error); + setLocalErrors(prev => ({ ...prev, dropdowns: 'Error al cargar plantas.' })); + } finally { + setLoadingDropdowns(false); + } + }; + fetchPlantas(); + }, []); + + useEffect(() => { + if (consolidado) { + setIdPlanta(''); + } + }, [consolidado]); + + const getFirstAndLastDayOfMonth = (yearMonth: string): { firstDay: string, lastDay: string } => { + const [year, month] = yearMonth.split('-').map(Number); + const firstDay = new Date(year, month - 1, 1); + const lastDay = new Date(year, month, 0); // El día 0 del siguiente mes es el último del mes actual + return { + firstDay: firstDay.toISOString().split('T')[0], + lastDay: lastDay.toISOString().split('T')[0], + }; + }; + + const validate = (): boolean => { + const errors: { [key: string]: string | null } = {}; + if (!mesAnioA) errors.mesAnioA = 'Mes/Año A es obligatorio.'; + if (!mesAnioB) errors.mesAnioB = 'Mes/Año B es obligatorio.'; + if (mesAnioA && mesAnioB && mesAnioA === mesAnioB) { + errors.mesAnioB = 'Mes A y Mes B no pueden ser iguales para una comparativa.'; + } + if (!consolidado && !idPlanta) { + errors.idPlanta = 'Seleccione una planta si no es consolidado.'; + } + setLocalErrors(errors); + return Object.keys(errors).length === 0; + }; + + const handleGenerar = () => { + if (!validate()) return; + + const periodoA = getFirstAndLastDayOfMonth(mesAnioA); + const periodoB = getFirstAndLastDayOfMonth(mesAnioB); + + onGenerarReporte({ + fechaInicioMesA: periodoA.firstDay, + fechaFinMesA: periodoA.lastDay, + fechaInicioMesB: periodoB.firstDay, + fechaFinMesB: periodoB.lastDay, + idPlanta: consolidado ? null : Number(idPlanta), + consolidado + }); + }; + + return ( + + + Parámetros: Comparativa Consumo Bobinas + + + + { setMesAnioA(e.target.value); setLocalErrors(p => ({ ...p, mesAnioA: null, mesAnioB: null })); }} + margin="normal" fullWidth required error={!!localErrors.mesAnioA} helperText={localErrors.mesAnioA} + disabled={isLoading} InputLabelProps={{ shrink: true }} sx={{flex: 1}} + /> + { setMesAnioB(e.target.value); setLocalErrors(p => ({ ...p, mesAnioB: null })); }} + margin="normal" fullWidth required error={!!localErrors.mesAnioB} helperText={localErrors.mesAnioB} + disabled={isLoading} InputLabelProps={{ shrink: true }} sx={{flex: 1}} + /> + + + setConsolidado(e.target.checked)} + disabled={isLoading} + /> + } + label="Consolidado (Todas las Plantas)" + sx={{ mt: 1, mb: 1, display: 'block' }} + /> + + Planta + + {localErrors.idPlanta && {localErrors.idPlanta}} + + + {apiErrorMessage && {apiErrorMessage}} + {localErrors.dropdowns && {localErrors.dropdowns}} + + + + + + ); +}; + +export default SeleccionaReporteComparativaConsumoBobinas; \ No newline at end of file diff --git a/Frontend/src/pages/Reportes/SeleccionaReporteConsumoBobinasPublicacion.tsx b/Frontend/src/pages/Reportes/SeleccionaReporteConsumoBobinasPublicacion.tsx new file mode 100644 index 0000000..bbeeba7 --- /dev/null +++ b/Frontend/src/pages/Reportes/SeleccionaReporteConsumoBobinasPublicacion.tsx @@ -0,0 +1,87 @@ +import React, { useState } from 'react'; +import { + Box, Typography, TextField, Button, CircularProgress, Alert +} from '@mui/material'; + +interface SeleccionaReporteConsumoBobinasPublicacionProps { + onGenerarReporte: (params: { + fechaDesde: string; + fechaHasta: string; + }) => Promise; + onCancel: () => void; + isLoading?: boolean; + apiErrorMessage?: string | null; +} + +const SeleccionaReporteConsumoBobinasPublicacion: React.FC = ({ + onGenerarReporte, + isLoading, + apiErrorMessage +}) => { + const [fechaDesde, setFechaDesde] = useState(new Date().toISOString().split('T')[0]); + const [fechaHasta, setFechaHasta] = useState(new Date().toISOString().split('T')[0]); + const [localErrors, setLocalErrors] = useState<{ [key: string]: string | null }>({}); + + const validate = (): boolean => { + const errors: { [key: string]: string | null } = {}; + 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({ + fechaDesde, + fechaHasta + }); + }; + + return ( + + + Parámetros: Consumo de Bobinas por Publicación + + { 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}} + + + + + + ); +}; + +export default SeleccionaReporteConsumoBobinasPublicacion; \ No newline at end of file diff --git a/Frontend/src/pages/Reportes/SeleccionaReporteConsumoBobinasSeccion.tsx b/Frontend/src/pages/Reportes/SeleccionaReporteConsumoBobinasSeccion.tsx new file mode 100644 index 0000000..66681e3 --- /dev/null +++ b/Frontend/src/pages/Reportes/SeleccionaReporteConsumoBobinasSeccion.tsx @@ -0,0 +1,155 @@ +import React, { useState, useEffect } from 'react'; +import { + Box, Typography, TextField, Button, CircularProgress, Alert, + FormControl, InputLabel, Select, MenuItem, FormControlLabel, Checkbox +} from '@mui/material'; +import type { PlantaDto } from '../../models/dtos/Impresion/PlantaDto'; +import plantaService from '../../services/Impresion/plantaService'; + +interface SeleccionaReporteConsumoBobinasSeccionProps { + onGenerarReporte: (params: { + fechaDesde: string; + fechaHasta: string; + idPlanta?: number | null; + consolidado: boolean; + }) => Promise; + onCancel: () => void; + isLoading?: boolean; + apiErrorMessage?: string | null; +} + +const SeleccionaReporteConsumoBobinasSeccion: React.FC = ({ + onGenerarReporte, + onCancel, + isLoading, + apiErrorMessage +}) => { + const [fechaDesde, setFechaDesde] = useState(new Date().toISOString().split('T')[0]); + const [fechaHasta, setFechaHasta] = useState(new Date().toISOString().split('T')[0]); + const [idPlanta, setIdPlanta] = useState(''); + const [consolidado, setConsolidado] = useState(false); + + const [plantas, setPlantas] = useState([]); + const [loadingDropdowns, setLoadingDropdowns] = useState(false); + const [localErrors, setLocalErrors] = useState<{ [key: string]: string | null }>({}); + + useEffect(() => { + const fetchPlantas = async () => { + setLoadingDropdowns(true); + try { + const plantasData = await plantaService.getAllPlantas(); + setPlantas(plantasData); + } catch (error) { + console.error("Error al cargar plantas:", error); + setLocalErrors(prev => ({ ...prev, dropdowns: 'Error al cargar plantas.' })); + } finally { + setLoadingDropdowns(false); + } + }; + fetchPlantas(); + }, []); + + useEffect(() => { + if (consolidado) { + setIdPlanta(''); + } + }, [consolidado]); + + const validate = (): boolean => { + const errors: { [key: string]: string | null } = {}; + 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.'; + } + if (!consolidado && !idPlanta) { + errors.idPlanta = 'Seleccione una planta si no es consolidado.'; + } + setLocalErrors(errors); + return Object.keys(errors).length === 0; + }; + + const handleGenerar = () => { + if (!validate()) return; + onGenerarReporte({ + fechaDesde, + fechaHasta, + idPlanta: consolidado ? null : Number(idPlanta), + consolidado + }); + }; + + return ( + + + Parámetros: Consumo de Bobinas por Sección + + { 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 }} + /> + setConsolidado(e.target.checked)} + disabled={isLoading} + /> + } + label="Consolidado (Todas las Plantas)" + sx={{ mt: 1, mb: 1 }} + /> + + Planta + + {localErrors.idPlanta && {localErrors.idPlanta}} + + + {apiErrorMessage && {apiErrorMessage}} + {localErrors.dropdowns && {localErrors.dropdowns}} + + + + + + + ); +}; + +export default SeleccionaReporteConsumoBobinasSeccion; \ No newline at end of file diff --git a/Frontend/src/pages/Reportes/SeleccionaReporteCuentasDistribuidores.tsx b/Frontend/src/pages/Reportes/SeleccionaReporteCuentasDistribuidores.tsx new file mode 100644 index 0000000..a188345 --- /dev/null +++ b/Frontend/src/pages/Reportes/SeleccionaReporteCuentasDistribuidores.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 { DistribuidorDto } from '../../models/dtos/Distribucion/DistribuidorDto'; +import distribuidorService from '../../services/Distribucion/distribuidorService'; +import type { EmpresaDto } from '../../models/dtos/Distribucion/EmpresaDto'; +import empresaService from '../../services/Distribucion/empresaService'; + +interface SeleccionaReporteCuentasDistribuidoresProps { + onGenerarReporte: (params: { + idDistribuidor: number; + idEmpresa: number; + fechaDesde: string; + fechaHasta: string; + }) => Promise; + onCancel: () => void; + isLoading?: boolean; + apiErrorMessage?: string | null; +} + +const SeleccionaReporteCuentasDistribuidores: React.FC = ({ + onGenerarReporte, + isLoading, + apiErrorMessage +}) => { + const [idDistribuidor, setIdDistribuidor] = useState(''); + const [idEmpresa, setIdEmpresa] = 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 [empresas, setEmpresas] = useState([]); + const [loadingDropdowns, setLoadingDropdowns] = useState(false); + const [localErrors, setLocalErrors] = useState<{ [key: string]: string | null }>({}); + + useEffect(() => { + const fetchData = async () => { + setLoadingDropdowns(true); + try { + const [distData, empData] = await Promise.all([ + distribuidorService.getAllDistribuidores(), // Asume que este servicio existe + empresaService.getAllEmpresas() // Asume que este servicio existe + ]); + setDistribuidores(distData.map(d => d)); // El servicio devuelve tupla + setEmpresas(empData); + } catch (error) { + console.error("Error al cargar datos:", error); + setLocalErrors(prev => ({ ...prev, dropdowns: 'Error al cargar distribuidores o empresas.' })); + } finally { + setLoadingDropdowns(false); + } + }; + fetchData(); + }, []); + + const validate = (): boolean => { + const errors: { [key: string]: string | null } = {}; + if (!idDistribuidor) errors.idDistribuidor = 'Debe seleccionar un distribuidor.'; + if (!idEmpresa) errors.idEmpresa = 'Debe seleccionar una empresa.'; + 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), + idEmpresa: Number(idEmpresa), + fechaDesde, + fechaHasta + }); + }; + + return ( + + + Parámetros: Cuenta Corriente Distribuidor + + + Distribuidor + + {localErrors.idDistribuidor && {localErrors.idDistribuidor}} + + + + Empresa + + {localErrors.idEmpresa && {localErrors.idEmpresa}} + + + { 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 SeleccionaReporteCuentasDistribuidores; \ No newline at end of file diff --git a/Frontend/src/pages/Reportes/SeleccionaReporteDetalleDistribucionCanillas.tsx b/Frontend/src/pages/Reportes/SeleccionaReporteDetalleDistribucionCanillas.tsx new file mode 100644 index 0000000..0059c17 --- /dev/null +++ b/Frontend/src/pages/Reportes/SeleccionaReporteDetalleDistribucionCanillas.tsx @@ -0,0 +1,125 @@ +import React, { useState, useEffect } from 'react'; +import { + Box, Typography, TextField, Button, CircularProgress, Alert, + FormControl, InputLabel, Select, MenuItem +} from '@mui/material'; +import type { EmpresaDto } from '../../models/dtos/Distribucion/EmpresaDto'; +import empresaService from '../../services/Distribucion/empresaService'; + +interface SeleccionaReporteDetalleDistribucionCanillasProps { + onGenerarReporte: (params: { + fecha: string; + idEmpresa: number; + // soloTotales: boolean; // Podríamos añadirlo si el usuario elige la versión del PDF + }) => Promise; + onCancel: () => void; + isLoading?: boolean; + apiErrorMessage?: string | null; +} + +const SeleccionaReporteDetalleDistribucionCanillas: React.FC = ({ + onGenerarReporte, + isLoading, + apiErrorMessage +}) => { + const [fecha, setFecha] = useState(new Date().toISOString().split('T')[0]); + const [idEmpresa, setIdEmpresa] = useState(''); + // const [soloTotales, setSoloTotales] = useState(false); // Si se añade la opción + + const [empresas, setEmpresas] = useState([]); + const [loadingDropdowns, setLoadingDropdowns] = useState(false); + const [localErrors, setLocalErrors] = useState<{ [key: string]: string | null }>({}); + + useEffect(() => { + const fetchEmpresas = async () => { + setLoadingDropdowns(true); + try { + const data = await empresaService.getAllEmpresas(); // Asume que este servicio existe + setEmpresas(data); + } catch (error) { + console.error("Error al cargar empresas:", error); + setLocalErrors(prev => ({ ...prev, dropdowns: 'Error al cargar empresas.' })); + } finally { + setLoadingDropdowns(false); + } + }; + fetchEmpresas(); + }, []); + + const validate = (): boolean => { + const errors: { [key: string]: string | null } = {}; + if (!fecha) errors.fecha = 'La fecha es obligatoria.'; + if (!idEmpresa) errors.idEmpresa = 'Debe seleccionar una empresa.'; + setLocalErrors(errors); + return Object.keys(errors).length === 0; + }; + + const handleGenerar = () => { + if (!validate()) return; + onGenerarReporte({ + fecha, + idEmpresa: Number(idEmpresa), + // soloTotales // Si se añade la opción + }); + }; + + return ( + + + Parámetros: Detalle Distribución Canillitas + + { setFecha(e.target.value); setLocalErrors(p => ({ ...p, fecha: null })); }} + margin="normal" + fullWidth + required + error={!!localErrors.fecha} + helperText={localErrors.fecha} + disabled={isLoading} + InputLabelProps={{ shrink: true }} + /> + + Empresa + + {localErrors.idEmpresa && {localErrors.idEmpresa}} + + {/* + setSoloTotales(e.target.checked)} + disabled={isLoading} + /> + } + label="Generar solo resumen de totales (PDF)" + sx={{ mt: 1, mb: 1 }} + /> + */} + + {apiErrorMessage && {apiErrorMessage}} + {localErrors.dropdowns && {localErrors.dropdowns}} + + + + + + ); +}; + +export default SeleccionaReporteDetalleDistribucionCanillas; \ No newline at end of file diff --git a/Frontend/src/pages/Reportes/SeleccionaReporteListadoDistribucionCanillasImporte.tsx b/Frontend/src/pages/Reportes/SeleccionaReporteListadoDistribucionCanillasImporte.tsx new file mode 100644 index 0000000..d4de130 --- /dev/null +++ b/Frontend/src/pages/Reportes/SeleccionaReporteListadoDistribucionCanillasImporte.tsx @@ -0,0 +1,143 @@ +import React, { useState, useEffect } from 'react'; +import { + Box, Typography, TextField, Button, CircularProgress, Alert, + FormControl, InputLabel, Select, MenuItem, FormControlLabel, Checkbox +} from '@mui/material'; +import type { PublicacionDto } from '../../models/dtos/Distribucion/PublicacionDto'; +import publicacionService from '../../services/Distribucion/publicacionService'; + +interface SeleccionaReporteListadoDistribucionCanillasImporteProps { + onGenerarReporte: (params: { + idPublicacion: number; + fechaDesde: string; + fechaHasta: string; + esAccionista: boolean; + }) => Promise; + onCancel: () => void; + isLoading?: boolean; + apiErrorMessage?: string | null; +} + +const SeleccionaReporteListadoDistribucionCanillasImporte: React.FC = ({ + onGenerarReporte, + isLoading, + apiErrorMessage +}) => { + 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 [esAccionista, setEsAccionista] = useState(false); + + const [publicaciones, setPublicaciones] = useState([]); + const [loadingDropdowns, setLoadingDropdowns] = useState(false); + const [localErrors, setLocalErrors] = useState<{ [key: string]: string | null }>({}); + + useEffect(() => { + const fetchPublicaciones = async () => { + setLoadingDropdowns(true); + try { + const data = await publicacionService.getAllPublicaciones(undefined, undefined, true); + setPublicaciones(data.map(p => p)); + } catch (error) { + console.error("Error al cargar publicaciones:", error); + setLocalErrors(prev => ({ ...prev, dropdowns: 'Error al cargar publicaciones.' })); + } finally { + setLoadingDropdowns(false); + } + }; + fetchPublicaciones(); + }, []); + + const validate = (): boolean => { + const errors: { [key: string]: string | null } = {}; + 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({ + idPublicacion: Number(idPublicacion), + fechaDesde, + fechaHasta, + esAccionista + }); + }; + + return ( + + + Parámetros: Distribución Canillitas con Importe + + + 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 }} + /> + setEsAccionista(e.target.checked)} + disabled={isLoading} + /> + } + label="Ver Accionistas" + sx={{ mt: 1, mb: 1 }} + /> + + {apiErrorMessage && {apiErrorMessage}} + {localErrors.dropdowns && {localErrors.dropdowns}} + + + + + + ); +}; + +export default SeleccionaReporteListadoDistribucionCanillasImporte; \ No newline at end of file diff --git a/Frontend/src/pages/Reportes/SeleccionaReporteTiradasPublicacionesSecciones.tsx b/Frontend/src/pages/Reportes/SeleccionaReporteTiradasPublicacionesSecciones.tsx new file mode 100644 index 0000000..624e630 --- /dev/null +++ b/Frontend/src/pages/Reportes/SeleccionaReporteTiradasPublicacionesSecciones.tsx @@ -0,0 +1,163 @@ +import React, { useState, useEffect } from 'react'; +import { + Box, Typography, TextField, Button, CircularProgress, Alert, + FormControl, InputLabel, Select, MenuItem, FormControlLabel, Checkbox +} from '@mui/material'; +import type { PublicacionDto } from '../../models/dtos/Distribucion/PublicacionDto'; +import publicacionService from '../../services/Distribucion/publicacionService'; +import type { PlantaDto } from '../../models/dtos/Impresion/PlantaDto'; +import plantaService from '../../services/Impresion/plantaService'; + +interface SeleccionaReporteTiradasPublicacionesSeccionesProps { + onGenerarReporte: (params: { + idPublicacion: number; + fechaDesde: string; // Primer día del mes + fechaHasta: string; // Último día del mes + idPlanta?: number | null; + consolidado: boolean; + }) => Promise; + onCancel: () => void; + isLoading?: boolean; + apiErrorMessage?: string | null; +} + +const SeleccionaReporteTiradasPublicacionesSecciones: React.FC = ({ + onGenerarReporte, + isLoading, + apiErrorMessage +}) => { + const [idPublicacion, setIdPublicacion] = useState(''); + const [mesAnio, setMesAnio] = useState(new Date().toISOString().substring(0, 7)); // Formato "YYYY-MM" + const [idPlanta, setIdPlanta] = useState(''); + const [consolidado, setConsolidado] = useState(false); + + const [publicaciones, setPublicaciones] = useState([]); + const [plantas, setPlantas] = useState([]); + const [loadingDropdowns, setLoadingDropdowns] = useState(false); + const [localErrors, setLocalErrors] = useState<{ [key: string]: string | null }>({}); + + useEffect(() => { + const fetchData = async () => { + setLoadingDropdowns(true); + try { + const [pubData, plantaData] = await Promise.all([ + publicacionService.getAllPublicaciones(undefined, undefined, true), + plantaService.getAllPlantas() + ]); + setPublicaciones(pubData.map(p => p)); + setPlantas(plantaData); + } catch (error) { + console.error("Error al cargar datos:", error); + setLocalErrors(prev => ({ ...prev, dropdowns: 'Error al cargar publicaciones o plantas.' })); + } finally { + setLoadingDropdowns(false); + } + }; + fetchData(); + }, []); + + useEffect(() => { + if (consolidado) { + setIdPlanta(''); // Limpiar planta si es consolidado + } + }, [consolidado]); + + const validate = (): boolean => { + const errors: { [key: string]: string | null } = {}; + if (!idPublicacion) errors.idPublicacion = 'Debe seleccionar una publicación.'; + if (!mesAnio) errors.mesAnio = 'Debe seleccionar un Mes/Año.'; + if (!consolidado && !idPlanta) { + errors.idPlanta = 'Seleccione una planta si no es consolidado.'; + } + setLocalErrors(errors); + return Object.keys(errors).length === 0; + }; + + const handleGenerar = () => { + if (!validate()) return; + const [year, month] = mesAnio.split('-').map(Number); + const fechaDesde = new Date(year, month - 1, 1).toISOString().split('T')[0]; + const fechaHasta = new Date(year, month, 0).toISOString().split('T')[0]; + + onGenerarReporte({ + idPublicacion: Number(idPublicacion), + fechaDesde, + fechaHasta, + idPlanta: consolidado ? null : Number(idPlanta), + consolidado + }); + }; + + return ( + + + Parámetros: Tiradas por Publicación y Secciones + + + Publicación + + {localErrors.idPublicacion && {localErrors.idPublicacion}} + + { setMesAnio(e.target.value); setLocalErrors(p => ({ ...p, mesAnio: null })); }} + margin="normal" + fullWidth + required + error={!!localErrors.mesAnio} + helperText={localErrors.mesAnio} + disabled={isLoading} + InputLabelProps={{ shrink: true }} + /> + setConsolidado(e.target.checked)} + disabled={isLoading} + /> + } + label="Consolidado (Todas las Plantas)" + sx={{ mt: 1, mb: 1 }} + /> + + Planta + + {localErrors.idPlanta && {localErrors.idPlanta}} + + + {apiErrorMessage && {apiErrorMessage}} + {localErrors.dropdowns && {localErrors.dropdowns}} + + + + + + ); +}; + +export default SeleccionaReporteTiradasPublicacionesSecciones; \ No newline at end of file diff --git a/Frontend/src/pages/Reportes/SeleccionaReporteVentaMensual.tsx b/Frontend/src/pages/Reportes/SeleccionaReporteVentaMensual.tsx new file mode 100644 index 0000000..f9163d6 --- /dev/null +++ b/Frontend/src/pages/Reportes/SeleccionaReporteVentaMensual.tsx @@ -0,0 +1,114 @@ +import React, { useState } from 'react'; +import { + Box, Typography, TextField, Button, CircularProgress, Alert, + FormControl, InputLabel, Select, MenuItem, type SelectChangeEvent +} from '@mui/material'; + +export type TipoReporteVentaMensual = 'ElDia' | 'ElPlata' | 'TirDevo'; + +interface SeleccionaReporteVentaMensualProps { + onGenerarReporte: (params: { + fechaDesde: string; + fechaHasta: string; + tipoReporte: TipoReporteVentaMensual; + }) => Promise; + onCancel: () => void; // Para volver a la pantalla de selección de reportes + isLoading?: boolean; + apiErrorMessage?: string | null; +} + +const SeleccionaReporteVentaMensual: React.FC = ({ + onGenerarReporte, + isLoading, + apiErrorMessage +}) => { + const [fechaDesde, setFechaDesde] = useState(new Date().toISOString().split('T')[0]); + const [fechaHasta, setFechaHasta] = useState(new Date().toISOString().split('T')[0]); + const [tipoReporte, setTipoReporte] = useState('ElDia'); + + const [localErrors, setLocalErrors] = useState<{ [key: string]: string | null }>({}); + + const validate = (): boolean => { + const errors: { [key: string]: string | null } = {}; + 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.'; + } + if (!tipoReporte) errors.tipoReporte = 'Debe seleccionar un tipo de reporte.'; + setLocalErrors(errors); + return Object.keys(errors).length === 0; + }; + + const handleGenerar = () => { + if (!validate()) return; + onGenerarReporte({ + fechaDesde, + fechaHasta, + tipoReporte + }); + }; + + const handleTipoReporteChange = (event: SelectChangeEvent) => { + setTipoReporte(event.target.value as TipoReporteVentaMensual); + setLocalErrors(p => ({ ...p, tipoReporte: null })); + }; + + return ( + + + Parámetros: Venta Mensual Secretaría + + + Tipo de Reporte + + {localErrors.tipoReporte && {localErrors.tipoReporte}} + + { 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}} + + + + + + ); +}; + +export default SeleccionaReporteVentaMensual; \ No newline at end of file diff --git a/Frontend/src/routes/AppRoutes.tsx b/Frontend/src/routes/AppRoutes.tsx index 85f063d..cda055d 100644 --- a/Frontend/src/routes/AppRoutes.tsx +++ b/Frontend/src/routes/AppRoutes.tsx @@ -59,6 +59,14 @@ import ReporteMovimientoBobinasPage from '../pages/Reportes/ReporteMovimientoBob import ReporteMovimientoBobinasEstadoPage from '../pages/Reportes/ReporteMovimientoBobinasEstadoPage'; import ReporteListadoDistribucionGeneralPage from '../pages/Reportes/ReporteListadoDistribucionGeneralPage'; import ReporteListadoDistribucionCanillasPage from '../pages/Reportes/ReporteListadoDistribucionCanillasPage'; +import ReporteListadoDistribucionCanillasImportePage from '../pages/Reportes/ReporteListadoDistribucionCanillasImportePage'; +import ReporteVentaMensualSecretariaPage from '../pages/Reportes/ReporteVentaMensualSecretariaPage'; +import ReporteDetalleDistribucionCanillasPage from '../pages/Reportes/ReporteDetalleDistribucionCanillasPage'; +import ReporteTiradasPublicacionesSeccionesPage from '../pages/Reportes/ReporteTiradasPublicacionesSeccionesPage'; +import ReporteConsumoBobinasSeccionPage from '../pages/Reportes/ReporteConsumoBobinasSeccionPage'; +import ReporteConsumoBobinasPublicacionPage from '../pages/Reportes/ReporteConsumoBobinasPublicacionPage'; +import ReporteComparativaConsumoBobinasPage from '../pages/Reportes/ReporteComparativaConsumoBobinasPage'; +import ReporteCuentasDistribuidoresPage from '../pages/Reportes/ReporteCuentasDistribuidoresPage'; // Auditorias import GestionarAuditoriaUsuariosPage from '../pages/Usuarios/Auditoria/GestionarAuditoriaUsuariosPage'; @@ -161,6 +169,14 @@ const AppRoutes = () => { } /> } /> } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> {/* Módulo de Radios (anidado) */} diff --git a/Frontend/src/services/Reportes/reportesService.ts b/Frontend/src/services/Reportes/reportesService.ts index bd86514..322e6d0 100644 --- a/Frontend/src/services/Reportes/reportesService.ts +++ b/Frontend/src/services/Reportes/reportesService.ts @@ -4,6 +4,16 @@ import type { MovimientoBobinasDto } from '../../models/dtos/Reportes/Movimiento import type { MovimientoBobinasPorEstadoResponseDto } from '../../models/dtos/Reportes/MovimientoBobinasPorEstadoResponseDto'; import type { ListadoDistribucionGeneralResponseDto } from '../../models/dtos/Reportes/ListadoDistribucionGeneralResponseDto'; import type { ListadoDistribucionCanillasResponseDto } from '../../models/dtos/Reportes/ListadoDistribucionCanillasResponseDto'; +import type { ListadoDistribucionCanillasImporteDto } from '../../models/dtos/Reportes/ListadoDistribucionCanillasImporteDto'; +import type { VentaMensualSecretariaElDiaDto } from '../../models/dtos/Reportes/VentaMensualSecretariaElDiaDto'; +import type { VentaMensualSecretariaElPlataDto } from '../../models/dtos/Reportes/VentaMensualSecretariaElPlataDto'; +import type { VentaMensualSecretariaTirDevoDto } from '../../models/dtos/Reportes/VentaMensualSecretariaTirDevoDto'; +import type { ReporteDistribucionCanillasResponseDto } from '../../models/dtos/Reportes/ReporteDistribucionCanillasResponseDto'; +import type { TiradasPublicacionesSeccionesDto } from '../../models/dtos/Reportes/TiradasPublicacionesSeccionesDto'; +import type { ConsumoBobinasSeccionDto } from '../../models/dtos/Reportes/ConsumoBobinasSeccionDto'; +import type { ConsumoBobinasPublicacionDto } from '../../models/dtos/Reportes/ConsumoBobinasPublicacionDto'; +import type { ComparativaConsumoBobinasDto } from '../../models/dtos/Reportes/ComparativaConsumoBobinasDto'; +import type { ReporteCuentasDistribuidorResponseDto } from '../../models/dtos/Reportes/ReporteCuentasDistribuidorResponseDto'; interface GetExistenciaPapelParams { fechaDesde: string; // yyyy-MM-dd @@ -129,6 +139,189 @@ const getListadoDistribucionCanillasPdf = async (params: { 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; + }; + + 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 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 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 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 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 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 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 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 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 reportesService = { getExistenciaPapel, getExistenciaPapelPdf, @@ -139,7 +332,27 @@ const reportesService = { getListadoDistribucionGeneral, getListadoDistribucionGeneralPdf, getListadoDistribucionCanillas, - getListadoDistribucionCanillasPdf + getListadoDistribucionCanillasPdf, + getListadoDistribucionCanillasImporte, + getListadoDistribucionCanillasImportePdf, + getVentaMensualSecretariaElDia, + getVentaMensualSecretariaElDiaPdf, + getVentaMensualSecretariaElPlata, + getVentaMensualSecretariaElPlataPdf, + getVentaMensualSecretariaTirDevo, + getVentaMensualSecretariaTirDevoPdf, + getReporteDistribucionCanillas, + getReporteDistribucionCanillasPdf, + getTiradasPublicacionesSecciones, + getTiradasPublicacionesSeccionesPdf, + getConsumoBobinasSeccion, + getConsumoBobinasSeccionPdf, + getConsumoBobinasPorPublicacion, + getConsumoBobinasPorPublicacionPdf, + getComparativaConsumoBobinas, + getComparativaConsumoBobinasPdf, + getReporteCuentasDistribuidor, + getReporteCuentasDistribuidorPdf, }; export default reportesService; \ No newline at end of file