Final de creación de Módulos de Reportes. Se procede a testeos y ordenamientos...

This commit is contained in:
2025-05-29 15:10:02 -03:00
parent 70fc847721
commit 1182a4cdee
40 changed files with 3510 additions and 2 deletions

View File

@@ -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")]

View File

@@ -0,0 +1,6 @@
export interface BalanceCuentaDebCredDto {
fecha: string;
referencia?: string;
debe: number;
haber: number;
}

View File

@@ -0,0 +1,10 @@
export interface BalanceCuentaDistDto {
fecha: string;
publicacion: string;
distribuidor: string;
cantidad: number;
remito: string;
observacion?: string;
debe: number;
haber: number;
}

View File

@@ -0,0 +1,8 @@
export interface BalanceCuentaPagosDto {
fecha: string;
recibo: number;
tipo: string;
debe: number;
haber: number;
detalle?: string;
}

View File

@@ -0,0 +1,9 @@
export interface ComparativaConsumoBobinasDto {
tipoBobina: string;
bobinasUtilizadasMesA: number;
bobinasUtilizadasMesB: number;
diferenciaBobinasUtilizadas: number;
kilosUtilizadosMesA: number;
kilosUtilizadosMesB: number;
diferenciaKilosUtilizados: number;
}

View File

@@ -0,0 +1,6 @@
export interface ConsumoBobinasPublicacionDto {
nombrePlanta: string;
nombrePublicacion: string;
totalKilos: number;
cantidadBobinas: number;
}

View File

@@ -0,0 +1,7 @@
export interface ConsumoBobinasSeccionDto {
nombrePublicacion: string;
nombreSeccion: string;
nombreBobina: string;
cantidadBobinas: number;
totalKilos: number;
}

View File

@@ -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;
}

View File

@@ -0,0 +1,7 @@
export interface DetalleDistribucionCanillaAllDto {
publicacion: string;
totalCantSalida: number;
totalCantEntrada: number;
totalRendir: number;
tipoVendedor: string;
}

View File

@@ -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
}

View File

@@ -0,0 +1,3 @@
export interface DevueltosOtrosDiasDto {
devueltos: number;
}

View File

@@ -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
}

View File

@@ -0,0 +1,3 @@
export interface ObtenerCtrlDevolucionesDto {
remito: number;
}

View File

@@ -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
}

View File

@@ -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;
}

View File

@@ -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[];
}

View File

@@ -0,0 +1,3 @@
export interface SaldoDto {
monto: number;
}

View File

@@ -0,0 +1,8 @@
export interface TiradasPublicacionesSeccionesDto {
nombreSeccion: string;
totalPaginasImpresas: number;
cantidadTiradas: number;
totalPaginasEjemplares: number;
totalEjemplares: number;
promedioPaginasPorEjemplar: number;
}

View File

@@ -0,0 +1,9 @@
export interface VentaMensualSecretariaElDiaDto {
dia: number;
cantidadCanillas: number;
tirajes: number;
ventas: number;
accionistas: number;
totalCooperativa: number;
totalGeneral: number;
}

View File

@@ -0,0 +1,9 @@
export interface VentaMensualSecretariaElPlataDto {
dia: number;
tiradaCoop: number;
devolucionCoop: number;
ventaCoop: number;
tiradaCan: number;
ventaCan: number;
total: number;
}

View File

@@ -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;
}

View File

@@ -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<ComparativaConsumoBobinasDto[]>([]);
const [loading, setLoading] = useState(false);
const [loadingPdf, setLoadingPdf] = useState(false);
const [error, setError] = useState<string | null>(null);
const [apiErrorParams, setApiErrorParams] = useState<string | null>(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 (
<Box sx={{ p: 2, display: 'flex', justifyContent: 'center', mt: 2 }}>
<Paper sx={{ width: '100%', maxWidth: 600 }} elevation={3}>
<SeleccionaReporteComparativaConsumoBobinas
onGenerarReporte={handleGenerarReporte}
onCancel={handleVolverAParametros}
isLoading={loading}
apiErrorMessage={apiErrorParams}
/>
</Paper>
</Box>
);
}
return (
<Box sx={{ p: 2 }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2, flexWrap: 'wrap', gap: 1 }}>
<Typography variant="h5">Reporte: Comparativa Consumo de Bobinas</Typography>
<Box sx={{ display: 'flex', gap: 1 }}>
<Button onClick={handleGenerarYAbrirPdf} variant="contained" disabled={loadingPdf || reportData.length === 0 || !!error} size="small">
{loadingPdf ? <CircularProgress size={20} color="inherit" /> : "Abrir PDF"}
</Button>
<Button onClick={handleExportToExcel} variant="outlined" disabled={reportData.length === 0 || !!error} size="small">
Exportar a Excel
</Button>
<Button onClick={handleVolverAParametros} variant="outlined" color="secondary" size="small">
Nuevos Parámetros
</Button>
</Box>
</Box>
{loading && <Box sx={{ textAlign: 'center' }}><CircularProgress /></Box>}
{error && !loading && <Alert severity="error" sx={{ my: 2 }}>{error}</Alert>}
{!loading && !error && reportData.length > 0 && (
<TableContainer component={Paper} sx={{ maxHeight: 'calc(100vh - 240px)' }}>
<Table stickyHeader size="small">
<TableHead>
<TableRow>
<TableCell>Tipo Bobina</TableCell>
<TableCell align="right">Cant. Mes A</TableCell>
<TableCell align="right">Cant. Mes B</TableCell>
<TableCell align="right">Dif. Cant.</TableCell>
<TableCell align="right" sx={{ borderLeft: '2px solid grey' }}>Kg Mes A</TableCell>
<TableCell align="right">Kg Mes B</TableCell>
<TableCell align="right">Dif. Kg</TableCell>
</TableRow>
</TableHead>
<TableBody>
{reportData.map((row, idx) => (
<TableRow key={`${row.tipoBobina}-${idx}`}>
<TableCell>{row.tipoBobina}</TableCell>
<TableCell align="right">{row.bobinasUtilizadasMesA.toLocaleString('es-AR')}</TableCell>
<TableCell align="right">{row.bobinasUtilizadasMesB.toLocaleString('es-AR')}</TableCell>
<TableCell align="right">{row.diferenciaBobinasUtilizadas.toLocaleString('es-AR')}</TableCell>
<TableCell align="right" sx={{ borderLeft: '2px solid grey' }}>{row.kilosUtilizadosMesA.toLocaleString('es-AR')}</TableCell>
<TableCell align="right">{row.kilosUtilizadosMesB.toLocaleString('es-AR')}</TableCell>
<TableCell align="right">{row.diferenciaKilosUtilizados.toLocaleString('es-AR')}</TableCell>
</TableRow>
))}
</TableBody>
<TableFooter>
<TableRow sx={{backgroundColor: 'grey.300'}}>
<TableCell align="right" sx={{fontWeight: 'bold', fontSize: '1.1rem'}}>TOTALES:</TableCell>
<TableCell align="right" sx={{fontWeight: 'bold', fontSize: '1.1rem'}}>{reportData.reduce((sum, item) => sum + item.bobinasUtilizadasMesA, 0).toLocaleString('es-AR')}</TableCell>
<TableCell align="right" sx={{fontWeight: 'bold', fontSize: '1.1rem'}}>{reportData.reduce((sum, item) => sum + item.bobinasUtilizadasMesB, 0).toLocaleString('es-AR')}</TableCell>
<TableCell align="right" sx={{fontWeight: 'bold', fontSize: '1.1rem'}}>{reportData.reduce((sum, item) => sum + item.diferenciaBobinasUtilizadas, 0).toLocaleString('es-AR')}</TableCell>
<TableCell align="right" sx={{fontWeight: 'bold', fontSize: '1.1rem', borderLeft: '2px solid grey'}}>{reportData.reduce((sum, item) => sum + item.kilosUtilizadosMesA, 0).toLocaleString('es-AR')}</TableCell>
<TableCell align="right" sx={{fontWeight: 'bold', fontSize: '1.1rem'}}>{reportData.reduce((sum, item) => sum + item.kilosUtilizadosMesB, 0).toLocaleString('es-AR')}</TableCell>
<TableCell align="right" sx={{fontWeight: 'bold', fontSize: '1.1rem'}}>{reportData.reduce((sum, item) => sum + item.diferenciaKilosUtilizados, 0).toLocaleString('es-AR')}</TableCell>
</TableRow>
</TableFooter>
</Table>
</TableContainer>
)}
{!loading && !error && reportData.length === 0 && (<Typography>No se encontraron datos para los criterios seleccionados.</Typography>)}
</Box>
);
};
export default ReporteComparativaConsumoBobinasPage;

View File

@@ -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<string, ConsumoBobinasPublicacionDto[]>);
};
const ReporteConsumoBobinasPublicacionPage: React.FC = () => {
const [reportData, setReportData] = useState<ConsumoBobinasPublicacionDto[]>([]);
const [loading, setLoading] = useState(false);
const [loadingPdf, setLoadingPdf] = useState(false);
const [error, setError] = useState<string | null>(null);
const [apiErrorParams, setApiErrorParams] = useState<string | null>(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 (
<Box sx={{ p: 2, display: 'flex', justifyContent: 'center', mt: 2 }}>
<Paper sx={{ width: '100%', maxWidth: 600 }} elevation={3}>
<SeleccionaReporteConsumoBobinasPublicacion
onGenerarReporte={handleGenerarReporte}
onCancel={handleVolverAParametros}
isLoading={loading}
apiErrorMessage={apiErrorParams}
/>
</Paper>
</Box>
);
}
return (
<Box sx={{ p: 2 }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2, flexWrap: 'wrap', gap: 1 }}>
<Typography variant="h5">Reporte: Consumo de Bobinas por Publicación</Typography>
<Box sx={{ display: 'flex', gap: 1 }}>
<Button onClick={handleGenerarYAbrirPdf} variant="contained" disabled={loadingPdf || reportData.length === 0 || !!error} size="small">
{loadingPdf ? <CircularProgress size={20} color="inherit" /> : "Abrir PDF"}
</Button>
<Button onClick={handleExportToExcel} variant="outlined" disabled={reportData.length === 0 || !!error} size="small">
Exportar a Excel
</Button>
<Button onClick={handleVolverAParametros} variant="outlined" color="secondary" size="small">
Nuevos Parámetros
</Button>
</Box>
</Box>
{loading && <Box sx={{ textAlign: 'center' }}><CircularProgress /></Box>}
{error && !loading && <Alert severity="error" sx={{ my: 2 }}>{error}</Alert>}
{!loading && !error && reportData.length > 0 && (
<TableContainer component={Paper} sx={{ maxHeight: 'calc(100vh - 240px)' }}>
<Table stickyHeader size="small">
<TableHead>
<TableRow>
<TableCell sx={{ fontWeight: 'bold' }}>Planta</TableCell>
<TableCell sx={{ fontWeight: 'bold' }}>Publicación</TableCell>
<TableCell align="right" sx={{ fontWeight: 'bold' }}>Total Kilos</TableCell>
<TableCell align="right" sx={{ fontWeight: 'bold' }}>Cant. Bobinas</TableCell>
</TableRow>
</TableHead>
<TableBody>
{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 (
<React.Fragment key={plantaKey}>
<TableRow sx={{ backgroundColor: 'rgba(0, 0, 0, 0.08)' }}>
<TableCell colSpan={4} sx={{ fontWeight: 'bold' }}>{plantaKey}</TableCell>
</TableRow>
{publicaciones.map((pub, pubIdx) => (
<TableRow key={`${plantaKey}-${pubIdx}`}>
<TableCell></TableCell> {/* Columna Planta vacía para esta fila */}
<TableCell>{pub.nombrePublicacion}</TableCell>
<TableCell align="right">{pub.totalKilos.toLocaleString('es-AR')}</TableCell>
<TableCell align="right">{pub.cantidadBobinas.toLocaleString('es-AR')}</TableCell>
</TableRow>
))}
{/* Fila de totales por planta */}
<TableRow sx={{ backgroundColor: 'rgba(0, 0, 0, 0.04)'}}>
<TableCell colSpan={2} align="right" sx={{ fontWeight: 'bold', fontStyle:'italic' }}>Total Planta ({plantaKey}):</TableCell>
<TableCell align="right" sx={{ fontWeight: 'bold', fontStyle:'italic' }}>{totalKilosPlanta.toLocaleString('es-AR')}</TableCell>
<TableCell align="right" sx={{ fontWeight: 'bold', fontStyle:'italic' }}>{totalBobinasPlanta.toLocaleString('es-AR')}</TableCell>
</TableRow>
</React.Fragment>
)})}
</TableBody>
<TableFooter>
<TableRow sx={{backgroundColor: 'grey.300'}}>
<TableCell colSpan={2} align="right" sx={{fontWeight: 'bold', fontSize: '1.1rem'}}>TOTAL GENERAL:</TableCell>
<TableCell align="right" sx={{fontWeight: 'bold', fontSize: '1.1rem'}}>{totalGeneralKilos.toLocaleString('es-AR')}</TableCell>
<TableCell align="right" sx={{fontWeight: 'bold', fontSize: '1.1rem'}}>{totalGeneralBobinas.toLocaleString('es-AR')}</TableCell>
</TableRow>
</TableFooter>
</Table>
</TableContainer>
)}
{!loading && !error && reportData.length === 0 && (<Typography>No se encontraron datos para los criterios seleccionados.</Typography>)}
</Box>
);
};
export default ReporteConsumoBobinasPublicacionPage;

View File

@@ -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<string, Record<string, ConsumoBobinasSeccionDto[]>>);
};
const ReporteConsumoBobinasSeccionPage: React.FC = () => {
const [reportData, setReportData] = useState<ConsumoBobinasSeccionDto[]>([]);
const [loading, setLoading] = useState(false);
const [loadingPdf, setLoadingPdf] = useState(false);
const [error, setError] = useState<string | null>(null);
const [apiErrorParams, setApiErrorParams] = useState<string | null>(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 (
<Box sx={{ p: 2, display: 'flex', justifyContent: 'center', mt: 2 }}>
<Paper sx={{ width: '100%', maxWidth: 600 }} elevation={3}>
<SeleccionaReporteConsumoBobinasSeccion
onGenerarReporte={handleGenerarReporte}
onCancel={handleVolverAParametros}
isLoading={loading}
apiErrorMessage={apiErrorParams}
/>
</Paper>
</Box>
);
}
return (
<Box sx={{ p: 2 }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2, flexWrap: 'wrap', gap: 1 }}>
<Typography variant="h5">Reporte: Consumo de Bobinas por Sección</Typography>
<Box sx={{ display: 'flex', gap: 1 }}>
<Button onClick={handleGenerarYAbrirPdf} variant="contained" disabled={loadingPdf || reportData.length === 0 || !!error} size="small">
{loadingPdf ? <CircularProgress size={20} color="inherit" /> : "Abrir PDF"}
</Button>
<Button onClick={handleExportToExcel} variant="outlined" disabled={reportData.length === 0 || !!error} size="small">
Exportar a Excel
</Button>
<Button onClick={handleVolverAParametros} variant="outlined" color="secondary" size="small">
Nuevos Parámetros
</Button>
</Box>
</Box>
{loading && <Box sx={{ textAlign: 'center' }}><CircularProgress /></Box>}
{error && !loading && <Alert severity="error" sx={{ my: 2 }}>{error}</Alert>}
{!loading && !error && reportData.length > 0 && (
<TableContainer component={Paper} sx={{ maxHeight: 'calc(100vh - 240px)' }}>
<Table stickyHeader size="small">
<TableHead>
<TableRow>
<TableCell sx={{ fontWeight: 'bold' }}>Publicación</TableCell>
<TableCell sx={{ fontWeight: 'bold' }}>Sección</TableCell>
<TableCell sx={{ fontWeight: 'bold' }}>Tipo Bobina</TableCell>
<TableCell align="right" sx={{ fontWeight: 'bold' }}>Cant. Bobinas</TableCell>
<TableCell align="right" sx={{ fontWeight: 'bold' }}>Total Kilos</TableCell>
</TableRow>
</TableHead>
<TableBody>
{Object.entries(groupedData).map(([pubKey, secciones]) => (
<React.Fragment key={pubKey}>
<TableRow sx={{ backgroundColor: 'rgba(0, 0, 0, 0.08)' }}>
<TableCell colSpan={5} sx={{ fontWeight: 'bold' }}>{pubKey}</TableCell>
</TableRow>
{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 (
<React.Fragment key={`${pubKey}-${secKey}`}>
<TableRow sx={{ backgroundColor: 'rgba(0, 0, 0, 0.04)' }}>
<TableCell></TableCell> {/* Columna Publicación vacía para esta fila */}
<TableCell colSpan={4} sx={{ fontWeight: 'bold' }}>{secKey}</TableCell>
</TableRow>
{bobinas.map((bob, bobIdx) => (
<TableRow key={`${pubKey}-${secKey}-${bobIdx}`}>
<TableCell></TableCell>
<TableCell></TableCell>
<TableCell>{bob.nombreBobina}</TableCell>
<TableCell align="right">{bob.cantidadBobinas.toLocaleString('es-AR')}</TableCell>
<TableCell align="right">{bob.totalKilos.toLocaleString('es-AR')}</TableCell>
</TableRow>
))}
{/* Fila de totales por sección */}
<TableRow sx={{ backgroundColor: 'rgba(0, 0, 0, 0.02)'}}>
<TableCell colSpan={3} align="right" sx={{ fontWeight: 'bold', fontStyle:'italic' }}>Total Sección ({secKey}):</TableCell>
<TableCell align="right" sx={{ fontWeight: 'bold', fontStyle:'italic' }}>{totalBobinasSeccion.toLocaleString('es-AR')}</TableCell>
<TableCell align="right" sx={{ fontWeight: 'bold', fontStyle:'italic' }}>{totalKilosSeccion.toLocaleString('es-AR')}</TableCell>
</TableRow>
</React.Fragment>
);
})}
</React.Fragment>
))}
</TableBody>
<TableFooter>
<TableRow sx={{backgroundColor: 'grey.300'}}>
<TableCell colSpan={3} align="right" sx={{fontWeight: 'bold', fontSize: '1.1rem'}}>TOTAL GENERAL:</TableCell>
<TableCell align="right" sx={{fontWeight: 'bold', fontSize: '1.1rem'}}>{totalGeneralBobinas.toLocaleString('es-AR')}</TableCell>
<TableCell align="right" sx={{fontWeight: 'bold', fontSize: '1.1rem'}}>{totalGeneralKilos.toLocaleString('es-AR')}</TableCell>
</TableRow>
</TableFooter>
</Table>
</TableContainer>
)}
{!loading && !error && reportData.length === 0 && (<Typography>No se encontraron datos para los criterios seleccionados.</Typography>)}
</Box>
);
};
export default ReporteConsumoBobinasSeccionPage;

View File

@@ -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<ReporteCuentasDistribuidorResponseDto | null>(null);
const [loading, setLoading] = useState(false);
const [loadingPdf, setLoadingPdf] = useState(false);
const [error, setError] = useState<string | null>(null);
const [apiErrorParams, setApiErrorParams] = useState<string | null>(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[]) => (
<TableContainer component={Paper} sx={{ maxHeight: '300px', mb: 2 }}>
<Table stickyHeader size="small">
<TableHead>
<TableRow>
<TableCell>Fecha</TableCell><TableCell>Publicación</TableCell>
<TableCell>Remito</TableCell><TableCell align="right">Cantidad</TableCell>
<TableCell>Observación</TableCell>
<TableCell align="right">Debe</TableCell><TableCell align="right">Haber</TableCell>
</TableRow>
</TableHead>
<TableBody>
{data.map((row, idx) => (
<TableRow key={`es-${idx}`}>
<TableCell>{new Date(row.fecha).toLocaleDateString('es-AR', { timeZone: 'UTC' })}</TableCell>
<TableCell>{row.publicacion}</TableCell><TableCell>{row.remito}</TableCell>
<TableCell align="right">{row.cantidad.toLocaleString('es-AR')}</TableCell>
<TableCell>{row.observacion}</TableCell>
<TableCell align="right">{row.debe.toLocaleString('es-AR', { style: 'currency', currency: 'ARS' })}</TableCell>
<TableCell align="right">{row.haber.toLocaleString('es-AR', { style: 'currency', currency: 'ARS' })}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
);
const renderDebitosCreditosTable = (data: BalanceCuentaDebCredDto[]) => (
<TableContainer component={Paper} sx={{ maxHeight: '300px', mb: 2 }}>
<Table stickyHeader size="small">
<TableHead>
<TableRow>
<TableCell>Fecha</TableCell><TableCell>Referencia</TableCell>
<TableCell align="right">Debe</TableCell><TableCell align="right">Haber</TableCell>
</TableRow>
</TableHead>
<TableBody>
{data.map((row, idx) => (
<TableRow key={`dc-${idx}`}>
<TableCell>{new Date(row.fecha).toLocaleDateString('es-AR', { timeZone: 'UTC' })}</TableCell>
<TableCell>{row.referencia}</TableCell>
<TableCell align="right">{row.debe.toLocaleString('es-AR', { style: 'currency', currency: 'ARS' })}</TableCell>
<TableCell align="right">{row.haber.toLocaleString('es-AR', { style: 'currency', currency: 'ARS' })}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
);
const renderPagosTable = (data: BalanceCuentaPagosDto[]) => (
<TableContainer component={Paper} sx={{ maxHeight: '300px', mb: 2 }}>
<Table stickyHeader size="small">
<TableHead>
<TableRow>
<TableCell>Fecha</TableCell><TableCell>Recibo</TableCell><TableCell>Tipo</TableCell>
<TableCell align="right">Debe</TableCell><TableCell align="right">Haber</TableCell>
<TableCell>Detalle</TableCell>
</TableRow>
</TableHead>
<TableBody>
{data.map((row, idx) => (
<TableRow key={`pa-${idx}`}>
<TableCell>{new Date(row.fecha).toLocaleDateString('es-AR', { timeZone: 'UTC' })}</TableCell>
<TableCell>{row.recibo}</TableCell><TableCell>{row.tipo}</TableCell>
<TableCell align="right">{row.debe.toLocaleString('es-AR', { style: 'currency', currency: 'ARS' })}</TableCell>
<TableCell align="right">{row.haber.toLocaleString('es-AR', { style: 'currency', currency: 'ARS' })}</TableCell>
<TableCell>{row.detalle}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
);
if (showParamSelector) {
return (
<Box sx={{ p: 2, display: 'flex', justifyContent: 'center', mt: 2 }}>
<Paper sx={{ width: '100%', maxWidth: 600 }} elevation={3}>
<SeleccionaReporteCuentasDistribuidores
onGenerarReporte={handleGenerarReporte}
onCancel={handleVolverAParametros}
isLoading={loading}
apiErrorMessage={apiErrorParams}
/>
</Paper>
</Box>
);
}
return (
<Box sx={{ p: 2 }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2, flexWrap: 'wrap', gap: 1 }}>
<Typography variant="h5">Reporte: Cuenta Corriente Distribuidor</Typography>
<Box sx={{ display: 'flex', gap: 1 }}>
<Button onClick={handleGenerarYAbrirPdf} variant="contained" disabled={loadingPdf || !reportData || !!error} size="small">
{loadingPdf ? <CircularProgress size={20} color="inherit" /> : "Abrir PDF"}
</Button>
<Button onClick={handleExportToExcel} variant="outlined" disabled={!reportData || !!error} size="small">
Exportar a Excel
</Button>
<Button onClick={handleVolverAParametros} variant="outlined" color="secondary" size="small">
Nuevos Parámetros
</Button>
</Box>
</Box>
{loading && <Box sx={{ textAlign: 'center' }}><CircularProgress /></Box>}
{error && !loading && <Alert severity="error" sx={{ my: 2 }}>{error}</Alert>}
{!loading && !error && reportData && (
<>
<Typography variant="h6" gutterBottom>Saldo Actual</Typography>
{reportData.saldos && reportData.saldos.length > 0 ? (
<Typography sx={{ mb: 2 }}>
{reportData.saldos[0].monto.toLocaleString('es-AR', { style: 'currency', currency: 'ARS' })}
</Typography>
) : <Typography sx={{ mb: 2, fontStyle: 'italic' }}>No hay saldo actual disponible.</Typography>}
<Typography variant="h6" gutterBottom>Movimientos (Entradas/Salidas)</Typography>
{reportData.entradasSalidas && reportData.entradasSalidas.length > 0 ?
renderEntradasSalidasTable(reportData.entradasSalidas) : <Typography sx={{ fontStyle: 'italic' }}>No hay movimientos de entradas/salidas.</Typography>}
<Typography variant="h6" gutterBottom sx={{ mt: 2 }}>Notas de Débito/Crédito</Typography>
{reportData.debitosCreditos && reportData.debitosCreditos.length > 0 ?
renderDebitosCreditosTable(reportData.debitosCreditos) : <Typography sx={{ fontStyle: 'italic' }}>No hay notas de débito/crédito.</Typography>}
<Typography variant="h6" gutterBottom sx={{ mt: 2 }}>Pagos</Typography>
{reportData.pagos && reportData.pagos.length > 0 ?
renderPagosTable(reportData.pagos) : <Typography sx={{ fontStyle: 'italic' }}>No hay pagos registrados.</Typography>}
</>
)}
{!loading && !error && (!reportData || (!reportData.entradasSalidas?.length && !reportData.debitosCreditos?.length && !reportData.pagos?.length && !reportData.saldos?.length)) &&
(<Typography>No se encontraron datos para los criterios seleccionados.</Typography>)}
</Box>
);
};
export default ReporteCuentasDistribuidoresPage;

View File

@@ -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<ReporteDistribucionCanillasResponseDto | null>(null);
const [loading, setLoading] = useState(false);
const [loadingPdf, setLoadingPdf] = useState(false);
const [error, setError] = useState<string | null>(null);
const [apiErrorParams, setApiErrorParams] = useState<string | null>(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<string, string>) => {
if (data && data.length > 0) {
const exportedData = data.map(item => {
const row: Record<string, any> = {};
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 <Typography sx={{mt:1, fontStyle:'italic'}}>No hay datos para {title.toLowerCase()}.</Typography>;
return (
<>
<Typography variant="subtitle1" gutterBottom sx={{ mt: 2, fontWeight:'bold' }}>{title}</Typography>
<TableContainer component={Paper} sx={{ maxHeight: '300px', mb: 2 }}>
<Table stickyHeader size="small">
<TableHead>
<TableRow>
<TableCell>Publicación</TableCell>
<TableCell>{isAllDto ? 'Tipo Vendedor' : 'Canillita'}</TableCell>
{ (data[0] as DetalleDistribucionCanillaDto).fecha && <TableCell>Fecha Mov.</TableCell> }
<TableCell align="right">Llevados</TableCell>
<TableCell align="right">Devueltos</TableCell>
<TableCell align="right">Vendidos</TableCell>
<TableCell align="right">A Rendir</TableCell>
</TableRow>
</TableHead>
<TableBody>
{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 (
<TableRow key={`${title.replace(/\s+/g, '')}-${idx}`}>
<TableCell>{item.publicacion}</TableCell>
<TableCell>{isAllDto ? item.tipoVendedor : item.canilla}</TableCell>
{ fechaMov && <TableCell>{fechaMov}</TableCell> }
<TableCell align="right">{item.totalCantSalida.toLocaleString('es-AR')}</TableCell>
<TableCell align="right">{item.totalCantEntrada.toLocaleString('es-AR')}</TableCell>
<TableCell align="right">{(item.totalCantSalida - item.totalCantEntrada).toLocaleString('es-AR')}</TableCell>
<TableCell align="right">{item.totalRendir.toLocaleString('es-AR', { style: 'currency', currency: 'ARS' })}</TableCell>
</TableRow>
)})}
</TableBody>
</Table>
</TableContainer>
</>
);
};
if (showParamSelector) {
return (
<Box sx={{ p: 2, display: 'flex', justifyContent: 'center', mt: 2 }}>
<Paper sx={{ width: '100%', maxWidth: 600 }} elevation={3}>
<SeleccionaReporteDetalleDistribucionCanillas
onGenerarReporte={handleGenerarReporte}
onCancel={handleVolverAParametros}
isLoading={loading}
apiErrorMessage={apiErrorParams}
/>
</Paper>
</Box>
);
}
return (
<Box sx={{ p: 2 }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2, flexWrap: 'wrap', gap: 1 }}>
<Typography variant="h5">Reporte: Detalle Distribución Canillitas</Typography>
<Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap' }}>
<Button onClick={() => handleGenerarYAbrirPdf(false)} variant="contained" disabled={loadingPdf || !reportData || !!error} size="small">
{loadingPdf && !pdfSoloTotales ? <CircularProgress size={20} color="inherit" /> : "PDF Detalle"}
</Button>
<Button onClick={() => handleGenerarYAbrirPdf(true)} variant="contained" color="secondary" disabled={loadingPdf || !reportData || !!error} size="small">
{loadingPdf && pdfSoloTotales ? <CircularProgress size={20} color="inherit" /> : "PDF Totales"}
</Button>
<Button onClick={handleExportToExcel} variant="outlined" disabled={!reportData || !!error} size="small">
Exportar a Excel
</Button>
<Button onClick={handleVolverAParametros} variant="outlined" color="secondary" size="small">
Nuevos Parámetros
</Button>
</Box>
</Box>
{loading && <Box sx={{ textAlign: 'center' }}><CircularProgress /></Box>}
{error && !loading && <Alert severity="error" sx={{ my: 2 }}>{error}</Alert>}
{!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))) &&
(<Typography>No se encontraron datos para los criterios seleccionados.</Typography>)}
</Box>
);
};
export default ReporteDetalleDistribucionCanillasPage;

View File

@@ -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<ListadoDistribucionCanillasImporteDto[]>([]);
const [loading, setLoading] = useState(false);
const [loadingPdf, setLoadingPdf] = useState(false);
const [error, setError] = useState<string | null>(null);
const [apiErrorParams, setApiErrorParams] = useState<string | null>(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 (
<Box sx={{ p: 2, display: 'flex', justifyContent: 'center', mt: 2 }}>
<Paper sx={{ width: '100%', maxWidth: 600 }} elevation={3}>
<SeleccionaReporteListadoDistribucionCanillasImporte
onGenerarReporte={handleGenerarReporte}
onCancel={handleVolverAParametros}
isLoading={loading}
apiErrorMessage={apiErrorParams}
/>
</Paper>
</Box>
);
}
return (
<Box sx={{ p: 2 }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2, flexWrap: 'wrap', gap: 1 }}>
<Typography variant="h5">Reporte: Distribución Canillitas con Importe</Typography>
<Box sx={{ display: 'flex', gap: 1 }}>
<Button onClick={handleGenerarYAbrirPdf} variant="contained" disabled={loadingPdf || reportData.length === 0 || !!error} size="small">
{loadingPdf ? <CircularProgress size={20} color="inherit" /> : "Abrir PDF"}
</Button>
<Button onClick={handleExportToExcel} variant="outlined" disabled={reportData.length === 0 || !!error} size="small">
Exportar a Excel
</Button>
<Button onClick={handleVolverAParametros} variant="outlined" color="secondary" size="small">
Nuevos Parámetros
</Button>
</Box>
</Box>
{loading && <Box sx={{ textAlign: 'center' }}><CircularProgress /></Box>}
{error && !loading && <Alert severity="error" sx={{ my: 2 }}>{error}</Alert>}
{!loading && !error && reportData.length > 0 && (
<TableContainer component={Paper} sx={{ maxHeight: 'calc(100vh - 240px)' }}>
<Table stickyHeader size="small">
<TableHead>
<TableRow>
<TableCell>Fecha</TableCell>
<TableCell align="right">Llevados</TableCell>
<TableCell align="right">Devueltos</TableCell>
<TableCell align="right">Vendidos</TableCell>
<TableCell align="right">Importe Publicación</TableCell>
<TableCell align="right">A Rendir</TableCell>
</TableRow>
</TableHead>
<TableBody>
{reportData.map((row, idx) => (
<TableRow key={`importe-${idx}`}>
<TableCell>{row.fecha}</TableCell>
<TableCell align="right">{row.llevados.toLocaleString('es-AR')}</TableCell>
<TableCell align="right">{row.devueltos.toLocaleString('es-AR')}</TableCell>
<TableCell align="right">{row.vendidos.toLocaleString('es-AR')}</TableCell>
<TableCell align="right">{row.totalRendirPublicacion.toLocaleString('es-AR', { style: 'currency', currency: 'ARS' })}</TableCell>
<TableCell align="right">{row.totalRendirGeneral.toLocaleString('es-AR', { style: 'currency', currency: 'ARS' })}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
)}
{!loading && !error && reportData.length === 0 && (<Typography>No se encontraron datos para los criterios seleccionados.</Typography>)}
</Box>
);
};
export default ReporteListadoDistribucionCanillasImportePage;

View File

@@ -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<TiradasPublicacionesSeccionesDto[]>([]);
const [loading, setLoading] = useState(false);
const [loadingPdf, setLoadingPdf] = useState(false);
const [error, setError] = useState<string | null>(null);
const [apiErrorParams, setApiErrorParams] = useState<string | null>(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 (
<Box sx={{ p: 2, display: 'flex', justifyContent: 'center', mt: 2 }}>
<Paper sx={{ width: '100%', maxWidth: 600 }} elevation={3}>
<SeleccionaReporteTiradasPublicacionesSecciones
onGenerarReporte={handleGenerarReporte}
onCancel={handleVolverAParametros}
isLoading={loading}
apiErrorMessage={apiErrorParams}
/>
</Paper>
</Box>
);
}
return (
<Box sx={{ p: 2 }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2, flexWrap: 'wrap', gap: 1 }}>
<Typography variant="h5">Reporte: Tiradas por Publicación y Secciones</Typography>
<Box sx={{ display: 'flex', gap: 1 }}>
<Button onClick={handleGenerarYAbrirPdf} variant="contained" disabled={loadingPdf || reportData.length === 0 || !!error} size="small">
{loadingPdf ? <CircularProgress size={20} color="inherit" /> : "Abrir PDF"}
</Button>
<Button onClick={handleExportToExcel} variant="outlined" disabled={reportData.length === 0 || !!error} size="small">
Exportar a Excel
</Button>
<Button onClick={handleVolverAParametros} variant="outlined" color="secondary" size="small">
Nuevos Parámetros
</Button>
</Box>
</Box>
{loading && <Box sx={{ textAlign: 'center' }}><CircularProgress /></Box>}
{error && !loading && <Alert severity="error" sx={{ my: 2 }}>{error}</Alert>}
{!loading && !error && reportData.length > 0 && (
<TableContainer component={Paper} sx={{ maxHeight: 'calc(100vh - 240px)' }}>
<Table stickyHeader size="small">
<TableHead>
<TableRow>
<TableCell>Nombre Sección</TableCell>
<TableCell align="right">Total Páginas Imp.</TableCell>
<TableCell align="right">Cant. Ediciones</TableCell>
<TableCell align="right">Total Pág. x Edición</TableCell>
<TableCell align="right">Total Ejemplares</TableCell>
<TableCell align="right">Prom. Pág./Ejemplar</TableCell>
</TableRow>
</TableHead>
<TableBody>
{reportData.map((row, idx) => (
<TableRow key={`${row.nombreSeccion}-${idx}`}>
<TableCell>{row.nombreSeccion}</TableCell>
<TableCell align="right">{row.totalPaginasImpresas.toLocaleString('es-AR')}</TableCell>
<TableCell align="right">{row.cantidadTiradas.toLocaleString('es-AR')}</TableCell>
<TableCell align="right">{row.totalPaginasEjemplares.toLocaleString('es-AR')}</TableCell>
<TableCell align="right">{row.totalEjemplares.toLocaleString('es-AR')}</TableCell>
<TableCell align="right">{row.promedioPaginasPorEjemplar.toLocaleString('es-AR')}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
)}
{!loading && !error && reportData.length === 0 && (<Typography>No se encontraron datos para los criterios seleccionados.</Typography>)}
</Box>
);
};
export default ReporteTiradasPublicacionesSeccionesPage;

View File

@@ -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<ReportData>([]);
const [currentReportType, setCurrentReportType] = useState<TipoReporteVentaMensual | null>(null);
const [loading, setLoading] = useState(false);
const [loadingPdf, setLoadingPdf] = useState(false);
const [error, setError] = useState<string | null>(null);
const [apiErrorParams, setApiErrorParams] = useState<string | null>(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<string, any>[] = [];
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 (
<TableContainer component={Paper} sx={{ maxHeight: 'calc(100vh - 280px)' }}>
<Table stickyHeader size="small">
<TableHead>
<TableRow>
<TableCell>Día</TableCell><TableCell align="right">Canillitas</TableCell>
<TableCell align="right">Tirajes</TableCell><TableCell align="right">Ventas</TableCell>
<TableCell align="right">Accionistas</TableCell><TableCell align="right">Total Coop.</TableCell>
<TableCell align="right">Total General</TableCell>
</TableRow>
</TableHead>
<TableBody>
{data.map((row, idx) => (
<TableRow key={`eldia-${idx}`}>
<TableCell>{row.dia}</TableCell>
<TableCell align="right">{row.cantidadCanillas.toLocaleString('es-AR')}</TableCell>
<TableCell align="right">{row.tirajes.toLocaleString('es-AR')}</TableCell>
<TableCell align="right">{row.ventas.toLocaleString('es-AR')}</TableCell>
<TableCell align="right">{row.accionistas.toLocaleString('es-AR')}</TableCell>
<TableCell align="right">{row.totalCooperativa.toLocaleString('es-AR')}</TableCell>
<TableCell align="right">{row.totalGeneral.toLocaleString('es-AR')}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
);
} else if (currentReportType === 'ElPlata') {
const data = reportData as VentaMensualSecretariaElPlataDto[];
return (
<TableContainer component={Paper} sx={{ maxHeight: 'calc(100vh - 280px)' }}>
<Table stickyHeader size="small">
<TableHead>
<TableRow>
<TableCell>Día</TableCell><TableCell align="right">Tirada Coop.</TableCell>
<TableCell align="right">Devol. Coop.</TableCell><TableCell align="right">Venta Coop.</TableCell>
<TableCell align="right">Tirada Can.</TableCell><TableCell align="right">Venta Can.</TableCell>
<TableCell align="right">Total</TableCell>
</TableRow>
</TableHead>
<TableBody>
{data.map((row, idx) => (
<TableRow key={`elplata-${idx}`}>
<TableCell>{row.dia}</TableCell>
<TableCell align="right">{row.tiradaCoop.toLocaleString('es-AR')}</TableCell>
<TableCell align="right">{row.devolucionCoop.toLocaleString('es-AR')}</TableCell>
<TableCell align="right">{row.ventaCoop.toLocaleString('es-AR')}</TableCell>
<TableCell align="right">{row.tiradaCan.toLocaleString('es-AR')}</TableCell>
<TableCell align="right">{row.ventaCan.toLocaleString('es-AR')}</TableCell>
<TableCell align="right">{row.total.toLocaleString('es-AR')}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
);
} else if (currentReportType === 'TirDevo') {
const data = reportData as VentaMensualSecretariaTirDevoDto[];
return (
<TableContainer component={Paper} sx={{ maxHeight: 'calc(100vh - 280px)' }}>
<Table stickyHeader size="small">
<TableHead>
<TableRow>
<TableCell rowSpan={2} sx={{verticalAlign: 'bottom'}}>Día</TableCell>
<TableCell colSpan={4} align="center" sx={{borderBottom: '1px solid rgba(224, 224, 224, 1)'}}>El Día</TableCell>
<TableCell colSpan={3} align="center" sx={{borderBottom: '1px solid rgba(224, 224, 224, 1)'}}>Popular</TableCell>
<TableCell colSpan={3} align="center" sx={{borderBottom: '1px solid rgba(224, 224, 224, 1)'}}>Clarín</TableCell>
<TableCell colSpan={3} align="center" sx={{borderBottom: '1px solid rgba(224, 224, 224, 1)'}}>Nación</TableCell>
</TableRow>
<TableRow>
<TableCell align="right">Tir. Coop.</TableCell><TableCell align="right">Dev. Coop.</TableCell>
<TableCell align="right">Vta. Coop.</TableCell><TableCell align="right">Vta. Can.</TableCell>
<TableCell align="right">Tirada</TableCell><TableCell align="right">Devol.</TableCell><TableCell align="right">Venta</TableCell>
<TableCell align="right">Tirada</TableCell><TableCell align="right">Devol.</TableCell><TableCell align="right">Venta</TableCell>
<TableCell align="right">Tirada</TableCell><TableCell align="right">Devol.</TableCell><TableCell align="right">Venta</TableCell>
</TableRow>
</TableHead>
<TableBody>
{data.map((row, idx) => (
<TableRow key={`tirdevo-${idx}`}>
<TableCell>{row.dia}</TableCell>
<TableCell align="right">{row.tiradaCoop.toLocaleString('es-AR')}</TableCell>
<TableCell align="right">{row.devolucionCoop.toLocaleString('es-AR')}</TableCell>
<TableCell align="right">{row.ventaCoop.toLocaleString('es-AR')}</TableCell>
<TableCell align="right">{row.ventaCan.toLocaleString('es-AR')}</TableCell>
<TableCell align="right">{row.tiradaPopular.toLocaleString('es-AR')}</TableCell>
<TableCell align="right">{row.devolucionPopular.toLocaleString('es-AR')}</TableCell>
<TableCell align="right">{row.ventaPopular.toLocaleString('es-AR')}</TableCell>
<TableCell align="right">{row.tiradaClarin.toLocaleString('es-AR')}</TableCell>
<TableCell align="right">{row.devolucionClarin.toLocaleString('es-AR')}</TableCell>
<TableCell align="right">{row.ventaClarin.toLocaleString('es-AR')}</TableCell>
<TableCell align="right">{row.tiradaNacion.toLocaleString('es-AR')}</TableCell>
<TableCell align="right">{row.devolucionNacion.toLocaleString('es-AR')}</TableCell>
<TableCell align="right">{row.ventaNacion.toLocaleString('es-AR')}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
);
}
return null;
};
if (showParamSelector) {
return (
<Box sx={{ p: 2, display: 'flex', justifyContent: 'center', mt: 2 }}>
<Paper sx={{ width: '100%', maxWidth: 600 }} elevation={3}>
<SeleccionaReporteVentaMensual
onGenerarReporte={handleGenerarReporte}
onCancel={handleVolverAParametros} // Este podría navegar a /reportes
isLoading={loading}
apiErrorMessage={apiErrorParams}
/>
</Paper>
</Box>
);
}
return (
<Box sx={{ p: 2 }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2, flexWrap: 'wrap', gap: 1 }}>
<Typography variant="h5">{getReportTitle()}</Typography>
<Box sx={{ display: 'flex', gap: 1 }}>
<Button onClick={handleGenerarYAbrirPdf} variant="contained" disabled={loadingPdf || reportData.length === 0 || !!error} size="small">
{loadingPdf ? <CircularProgress size={20} color="inherit" /> : "Abrir PDF"}
</Button>
<Button onClick={handleExportToExcel} variant="outlined" disabled={reportData.length === 0 || !!error} size="small">
Exportar a Excel
</Button>
<Button onClick={handleVolverAParametros} variant="outlined" color="secondary" size="small">
Nuevos Parámetros
</Button>
</Box>
</Box>
{loading && <Box sx={{ textAlign: 'center' }}><CircularProgress /></Box>}
{error && !loading && <Alert severity="error" sx={{ my: 2 }}>{error}</Alert>}
{!loading && !error && renderTable()}
{!loading && !error && reportData.length === 0 && (<Typography>No se encontraron datos para los criterios seleccionados.</Typography>)}
</Box>
);
};
export default ReporteVentaMensualSecretariaPage;

View File

@@ -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 = () => {

View File

@@ -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<void>;
onCancel: () => void;
isLoading?: boolean;
apiErrorMessage?: string | null;
}
const SeleccionaReporteComparativaConsumoBobinas: React.FC<SeleccionaReporteComparativaConsumoBobinasProps> = ({
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<string>(lastMonthYear);
const [mesAnioB, setMesAnioB] = useState<string>(currentMonthYear);
const [idPlanta, setIdPlanta] = useState<number | string>('');
const [consolidado, setConsolidado] = useState<boolean>(false);
const [plantas, setPlantas] = useState<PlantaDto[]>([]);
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 (
<Box sx={{ p: 2, border: '1px solid #ccc', borderRadius: '4px', minWidth: 380 }}>
<Typography variant="h6" gutterBottom>
Parámetros: Comparativa Consumo Bobinas
</Typography>
<Box sx={{ display: 'flex', gap: 2, mb: 1, alignItems: 'flex-start' }}>
<TextField
label="Mes/Año A" type="month" value={mesAnioA}
onChange={(e) => { 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}}
/>
<TextField
label="Mes/Año B" type="month" value={mesAnioB}
onChange={(e) => { 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}}
/>
</Box>
<FormControlLabel
control={
<Checkbox
checked={consolidado}
onChange={(e) => setConsolidado(e.target.checked)}
disabled={isLoading}
/>
}
label="Consolidado (Todas las Plantas)"
sx={{ mt: 1, mb: 1, display: 'block' }}
/>
<FormControl fullWidth margin="normal" error={!!localErrors.idPlanta} disabled={isLoading || loadingDropdowns || consolidado}>
<InputLabel id="planta-select-label-comp" required={!consolidado}>Planta</InputLabel>
<Select
labelId="planta-select-label-comp"
label="Planta"
value={consolidado ? '' : idPlanta}
onChange={(e) => { setIdPlanta(e.target.value as number); setLocalErrors(p => ({ ...p, idPlanta: null })); }}
>
<MenuItem value="" disabled><em>{consolidado ? 'N/A (Consolidado)' : 'Seleccione una planta'}</em></MenuItem>
{plantas.map((p) => (
<MenuItem key={p.idPlanta} value={p.idPlanta}>{p.nombre}</MenuItem>
))}
</Select>
{localErrors.idPlanta && <Typography color="error" variant="caption" sx={{ml:1.5}}>{localErrors.idPlanta}</Typography>}
</FormControl>
{apiErrorMessage && <Alert severity="error" sx={{ mt: 2 }}>{apiErrorMessage}</Alert>}
{localErrors.dropdowns && <Alert severity="warning" sx={{ mt: 1 }}>{localErrors.dropdowns}</Alert>}
<Box sx={{ mt: 3, display: 'flex', justifyContent: 'flex-end', gap: 1 }}>
<Button onClick={handleGenerar} variant="contained" disabled={isLoading || loadingDropdowns}>
{isLoading ? <CircularProgress size={24} /> : 'Generar Reporte'}
</Button>
</Box>
</Box>
);
};
export default SeleccionaReporteComparativaConsumoBobinas;

View File

@@ -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<void>;
onCancel: () => void;
isLoading?: boolean;
apiErrorMessage?: string | null;
}
const SeleccionaReporteConsumoBobinasPublicacion: React.FC<SeleccionaReporteConsumoBobinasPublicacionProps> = ({
onGenerarReporte,
isLoading,
apiErrorMessage
}) => {
const [fechaDesde, setFechaDesde] = useState<string>(new Date().toISOString().split('T')[0]);
const [fechaHasta, setFechaHasta] = useState<string>(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 (
<Box sx={{ p: 2, border: '1px solid #ccc', borderRadius: '4px', minWidth: 380 }}>
<Typography variant="h6" gutterBottom>
Parámetros: Consumo de Bobinas por Publicación
</Typography>
<TextField
label="Fecha Desde"
type="date"
value={fechaDesde}
onChange={(e) => { 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 }}
/>
<TextField
label="Fecha Hasta"
type="date"
value={fechaHasta}
onChange={(e) => { 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 && <Alert severity="error" sx={{ mt: 2 }}>{apiErrorMessage}</Alert>}
<Box sx={{ mt: 3, display: 'flex', justifyContent: 'flex-end', gap: 1 }}>
<Button onClick={handleGenerar} variant="contained" disabled={isLoading}>
{isLoading ? <CircularProgress size={24} /> : 'Generar Reporte'}
</Button>
</Box>
</Box>
);
};
export default SeleccionaReporteConsumoBobinasPublicacion;

View File

@@ -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<void>;
onCancel: () => void;
isLoading?: boolean;
apiErrorMessage?: string | null;
}
const SeleccionaReporteConsumoBobinasSeccion: React.FC<SeleccionaReporteConsumoBobinasSeccionProps> = ({
onGenerarReporte,
onCancel,
isLoading,
apiErrorMessage
}) => {
const [fechaDesde, setFechaDesde] = useState<string>(new Date().toISOString().split('T')[0]);
const [fechaHasta, setFechaHasta] = useState<string>(new Date().toISOString().split('T')[0]);
const [idPlanta, setIdPlanta] = useState<number | string>('');
const [consolidado, setConsolidado] = useState<boolean>(false);
const [plantas, setPlantas] = useState<PlantaDto[]>([]);
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 (
<Box sx={{ p: 2, border: '1px solid #ccc', borderRadius: '4px', minWidth: 380 }}>
<Typography variant="h6" gutterBottom>
Parámetros: Consumo de Bobinas por Sección
</Typography>
<TextField
label="Fecha Desde"
type="date"
value={fechaDesde}
onChange={(e) => { 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 }}
/>
<TextField
label="Fecha Hasta"
type="date"
value={fechaHasta}
onChange={(e) => { setFechaHasta(e.target.value); setLocalErrors(p => ({ ...p, fechaHasta: null })); }}
margin="normal"
fullWidth
required
error={!!localErrors.fechaHasta}
helperText={localErrors.fechaHasta}
disabled={isLoading}
InputLabelProps={{ shrink: true }}
/>
<FormControlLabel
control={
<Checkbox
checked={consolidado}
onChange={(e) => setConsolidado(e.target.checked)}
disabled={isLoading}
/>
}
label="Consolidado (Todas las Plantas)"
sx={{ mt: 1, mb: 1 }}
/>
<FormControl fullWidth margin="normal" error={!!localErrors.idPlanta} disabled={isLoading || loadingDropdowns || consolidado}>
<InputLabel id="planta-select-label-consumo" required={!consolidado}>Planta</InputLabel>
<Select
labelId="planta-select-label-consumo"
label="Planta"
value={consolidado ? '' : idPlanta}
onChange={(e) => { setIdPlanta(e.target.value as number); setLocalErrors(p => ({ ...p, idPlanta: null })); }}
>
<MenuItem value="" disabled><em>{consolidado ? 'N/A (Consolidado)' : 'Seleccione una planta'}</em></MenuItem>
{plantas.map((p) => (
<MenuItem key={p.idPlanta} value={p.idPlanta}>{p.nombre}</MenuItem>
))}
</Select>
{localErrors.idPlanta && <Typography color="error" variant="caption" sx={{ml:1.5}}>{localErrors.idPlanta}</Typography>}
</FormControl>
{apiErrorMessage && <Alert severity="error" sx={{ mt: 2 }}>{apiErrorMessage}</Alert>}
{localErrors.dropdowns && <Alert severity="warning" sx={{ mt: 1 }}>{localErrors.dropdowns}</Alert>}
<Box sx={{ mt: 3, display: 'flex', justifyContent: 'flex-end', gap: 1 }}>
<Button onClick={onCancel} color="secondary" disabled={isLoading}>
Cancelar
</Button>
<Button onClick={handleGenerar} variant="contained" disabled={isLoading || loadingDropdowns}>
{isLoading ? <CircularProgress size={24} /> : 'Generar Reporte'}
</Button>
</Box>
</Box>
);
};
export default SeleccionaReporteConsumoBobinasSeccion;

View File

@@ -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<void>;
onCancel: () => void;
isLoading?: boolean;
apiErrorMessage?: string | null;
}
const SeleccionaReporteCuentasDistribuidores: React.FC<SeleccionaReporteCuentasDistribuidoresProps> = ({
onGenerarReporte,
isLoading,
apiErrorMessage
}) => {
const [idDistribuidor, setIdDistribuidor] = useState<number | string>('');
const [idEmpresa, setIdEmpresa] = useState<number | string>('');
const [fechaDesde, setFechaDesde] = useState<string>(new Date().toISOString().split('T')[0]);
const [fechaHasta, setFechaHasta] = useState<string>(new Date().toISOString().split('T')[0]);
const [distribuidores, setDistribuidores] = useState<DistribuidorDto[]>([]);
const [empresas, setEmpresas] = useState<EmpresaDto[]>([]);
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 (
<Box sx={{ p: 2, border: '1px solid #ccc', borderRadius: '4px', minWidth: 380 }}>
<Typography variant="h6" gutterBottom>
Parámetros: Cuenta Corriente Distribuidor
</Typography>
<FormControl fullWidth margin="normal" error={!!localErrors.idDistribuidor} disabled={isLoading || loadingDropdowns}>
<InputLabel id="distribuidor-select-label" required>Distribuidor</InputLabel>
<Select
labelId="distribuidor-select-label"
label="Distribuidor"
value={idDistribuidor}
onChange={(e) => { setIdDistribuidor(e.target.value as number); setLocalErrors(p => ({ ...p, idDistribuidor: null })); }}
>
<MenuItem value="" disabled><em>Seleccione un distribuidor</em></MenuItem>
{distribuidores.map((d) => (
<MenuItem key={d.idDistribuidor} value={d.idDistribuidor}>{d.nombre}</MenuItem>
))}
</Select>
{localErrors.idDistribuidor && <Typography color="error" variant="caption" sx={{ml:1.5}}>{localErrors.idDistribuidor}</Typography>}
</FormControl>
<FormControl fullWidth margin="normal" error={!!localErrors.idEmpresa} disabled={isLoading || loadingDropdowns}>
<InputLabel id="empresa-select-label-cta" required>Empresa</InputLabel>
<Select
labelId="empresa-select-label-cta"
label="Empresa"
value={idEmpresa}
onChange={(e) => { setIdEmpresa(e.target.value as number); setLocalErrors(p => ({ ...p, idEmpresa: null })); }}
>
<MenuItem value="" disabled><em>Seleccione una empresa</em></MenuItem>
{empresas.map((e) => (
<MenuItem key={e.idEmpresa} value={e.idEmpresa}>{e.nombre}</MenuItem>
))}
</Select>
{localErrors.idEmpresa && <Typography color="error" variant="caption" sx={{ml:1.5}}>{localErrors.idEmpresa}</Typography>}
</FormControl>
<TextField
label="Fecha Desde"
type="date"
value={fechaDesde}
onChange={(e) => { 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 }}
/>
<TextField
label="Fecha Hasta"
type="date"
value={fechaHasta}
onChange={(e) => { 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 && <Alert severity="error" sx={{ mt: 2 }}>{apiErrorMessage}</Alert>}
{localErrors.dropdowns && <Alert severity="warning" sx={{ mt: 1 }}>{localErrors.dropdowns}</Alert>}
<Box sx={{ mt: 3, display: 'flex', justifyContent: 'flex-end', gap: 1 }}>
<Button onClick={handleGenerar} variant="contained" disabled={isLoading || loadingDropdowns}>
{isLoading ? <CircularProgress size={24} /> : 'Generar Reporte'}
</Button>
</Box>
</Box>
);
};
export default SeleccionaReporteCuentasDistribuidores;

View File

@@ -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<void>;
onCancel: () => void;
isLoading?: boolean;
apiErrorMessage?: string | null;
}
const SeleccionaReporteDetalleDistribucionCanillas: React.FC<SeleccionaReporteDetalleDistribucionCanillasProps> = ({
onGenerarReporte,
isLoading,
apiErrorMessage
}) => {
const [fecha, setFecha] = useState<string>(new Date().toISOString().split('T')[0]);
const [idEmpresa, setIdEmpresa] = useState<number | string>('');
// const [soloTotales, setSoloTotales] = useState<boolean>(false); // Si se añade la opción
const [empresas, setEmpresas] = useState<EmpresaDto[]>([]);
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 (
<Box sx={{ p: 2, border: '1px solid #ccc', borderRadius: '4px', minWidth: 380 }}>
<Typography variant="h6" gutterBottom>
Parámetros: Detalle Distribución Canillitas
</Typography>
<TextField
label="Fecha"
type="date"
value={fecha}
onChange={(e) => { setFecha(e.target.value); setLocalErrors(p => ({ ...p, fecha: null })); }}
margin="normal"
fullWidth
required
error={!!localErrors.fecha}
helperText={localErrors.fecha}
disabled={isLoading}
InputLabelProps={{ shrink: true }}
/>
<FormControl fullWidth margin="normal" error={!!localErrors.idEmpresa} disabled={isLoading || loadingDropdowns}>
<InputLabel id="empresa-select-label-distcan" required>Empresa</InputLabel>
<Select
labelId="empresa-select-label-distcan"
label="Empresa"
value={idEmpresa}
onChange={(e) => { setIdEmpresa(e.target.value as number); setLocalErrors(p => ({ ...p, idEmpresa: null })); }}
>
<MenuItem value="" disabled><em>Seleccione una empresa</em></MenuItem>
{empresas.map((e) => (
<MenuItem key={e.idEmpresa} value={e.idEmpresa}>{e.nombre}</MenuItem>
))}
</Select>
{localErrors.idEmpresa && <Typography color="error" variant="caption" sx={{ml:1.5}}>{localErrors.idEmpresa}</Typography>}
</FormControl>
{/*
<FormControlLabel
control={
<Checkbox
checked={soloTotales}
onChange={(e) => setSoloTotales(e.target.checked)}
disabled={isLoading}
/>
}
label="Generar solo resumen de totales (PDF)"
sx={{ mt: 1, mb: 1 }}
/>
*/}
{apiErrorMessage && <Alert severity="error" sx={{ mt: 2 }}>{apiErrorMessage}</Alert>}
{localErrors.dropdowns && <Alert severity="warning" sx={{ mt: 1 }}>{localErrors.dropdowns}</Alert>}
<Box sx={{ mt: 3, display: 'flex', justifyContent: 'flex-end', gap: 1 }}>
<Button onClick={handleGenerar} variant="contained" disabled={isLoading || loadingDropdowns}>
{isLoading ? <CircularProgress size={24} /> : 'Generar Reporte'}
</Button>
</Box>
</Box>
);
};
export default SeleccionaReporteDetalleDistribucionCanillas;

View File

@@ -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<void>;
onCancel: () => void;
isLoading?: boolean;
apiErrorMessage?: string | null;
}
const SeleccionaReporteListadoDistribucionCanillasImporte: React.FC<SeleccionaReporteListadoDistribucionCanillasImporteProps> = ({
onGenerarReporte,
isLoading,
apiErrorMessage
}) => {
const [idPublicacion, setIdPublicacion] = useState<number | string>('');
const [fechaDesde, setFechaDesde] = useState<string>(new Date().toISOString().split('T')[0]);
const [fechaHasta, setFechaHasta] = useState<string>(new Date().toISOString().split('T')[0]);
const [esAccionista, setEsAccionista] = useState<boolean>(false);
const [publicaciones, setPublicaciones] = useState<PublicacionDto[]>([]);
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 (
<Box sx={{ p: 2, border: '1px solid #ccc', borderRadius: '4px', minWidth: 380 }}>
<Typography variant="h6" gutterBottom>
Parámetros: Distribución Canillitas con Importe
</Typography>
<FormControl fullWidth margin="normal" error={!!localErrors.idPublicacion} disabled={isLoading || loadingDropdowns}>
<InputLabel id="publicacion-select-label-imp" required>Publicación</InputLabel>
<Select
labelId="publicacion-select-label-imp"
label="Publicación"
value={idPublicacion}
onChange={(e) => { setIdPublicacion(e.target.value as number); setLocalErrors(p => ({ ...p, idPublicacion: null })); }}
>
<MenuItem value="" disabled><em>Seleccione una publicación</em></MenuItem>
{publicaciones.map((p) => (
<MenuItem key={p.idPublicacion} value={p.idPublicacion}>{p.nombre}</MenuItem>
))}
</Select>
{localErrors.idPublicacion && <Typography color="error" variant="caption" sx={{ml:1.5}}>{localErrors.idPublicacion}</Typography>}
</FormControl>
<TextField
label="Fecha Desde"
type="date"
value={fechaDesde}
onChange={(e) => { 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 }}
/>
<TextField
label="Fecha Hasta"
type="date"
value={fechaHasta}
onChange={(e) => { setFechaHasta(e.target.value); setLocalErrors(p => ({ ...p, fechaHasta: null })); }}
margin="normal"
fullWidth
required
error={!!localErrors.fechaHasta}
helperText={localErrors.fechaHasta}
disabled={isLoading}
InputLabelProps={{ shrink: true }}
/>
<FormControlLabel
control={
<Checkbox
checked={esAccionista}
onChange={(e) => setEsAccionista(e.target.checked)}
disabled={isLoading}
/>
}
label="Ver Accionistas"
sx={{ mt: 1, mb: 1 }}
/>
{apiErrorMessage && <Alert severity="error" sx={{ mt: 2 }}>{apiErrorMessage}</Alert>}
{localErrors.dropdowns && <Alert severity="warning" sx={{ mt: 1 }}>{localErrors.dropdowns}</Alert>}
<Box sx={{ mt: 3, display: 'flex', justifyContent: 'flex-end', gap: 1 }}>
<Button onClick={handleGenerar} variant="contained" disabled={isLoading || loadingDropdowns}>
{isLoading ? <CircularProgress size={24} /> : 'Generar Reporte'}
</Button>
</Box>
</Box>
);
};
export default SeleccionaReporteListadoDistribucionCanillasImporte;

View File

@@ -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<void>;
onCancel: () => void;
isLoading?: boolean;
apiErrorMessage?: string | null;
}
const SeleccionaReporteTiradasPublicacionesSecciones: React.FC<SeleccionaReporteTiradasPublicacionesSeccionesProps> = ({
onGenerarReporte,
isLoading,
apiErrorMessage
}) => {
const [idPublicacion, setIdPublicacion] = useState<number | string>('');
const [mesAnio, setMesAnio] = useState<string>(new Date().toISOString().substring(0, 7)); // Formato "YYYY-MM"
const [idPlanta, setIdPlanta] = useState<number | string>('');
const [consolidado, setConsolidado] = useState<boolean>(false);
const [publicaciones, setPublicaciones] = useState<PublicacionDto[]>([]);
const [plantas, setPlantas] = useState<PlantaDto[]>([]);
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 (
<Box sx={{ p: 2, border: '1px solid #ccc', borderRadius: '4px', minWidth: 380 }}>
<Typography variant="h6" gutterBottom>
Parámetros: Tiradas por Publicación y Secciones
</Typography>
<FormControl fullWidth margin="normal" error={!!localErrors.idPublicacion} disabled={isLoading || loadingDropdowns}>
<InputLabel id="publicacion-select-label-tir" required>Publicación</InputLabel>
<Select
labelId="publicacion-select-label-tir"
label="Publicación"
value={idPublicacion}
onChange={(e) => { setIdPublicacion(e.target.value as number); setLocalErrors(p => ({ ...p, idPublicacion: null })); }}
>
<MenuItem value="" disabled><em>Seleccione una publicación</em></MenuItem>
{publicaciones.map((p) => (
<MenuItem key={p.idPublicacion} value={p.idPublicacion}>{p.nombre}</MenuItem>
))}
</Select>
{localErrors.idPublicacion && <Typography color="error" variant="caption" sx={{ml:1.5}}>{localErrors.idPublicacion}</Typography>}
</FormControl>
<TextField
label="Mes y Año"
type="month"
value={mesAnio}
onChange={(e) => { setMesAnio(e.target.value); setLocalErrors(p => ({ ...p, mesAnio: null })); }}
margin="normal"
fullWidth
required
error={!!localErrors.mesAnio}
helperText={localErrors.mesAnio}
disabled={isLoading}
InputLabelProps={{ shrink: true }}
/>
<FormControlLabel
control={
<Checkbox
checked={consolidado}
onChange={(e) => setConsolidado(e.target.checked)}
disabled={isLoading}
/>
}
label="Consolidado (Todas las Plantas)"
sx={{ mt: 1, mb: 1 }}
/>
<FormControl fullWidth margin="normal" error={!!localErrors.idPlanta} disabled={isLoading || loadingDropdowns || consolidado}>
<InputLabel id="planta-select-label-tir" required={!consolidado}>Planta</InputLabel>
<Select
labelId="planta-select-label-tir"
label="Planta"
value={consolidado ? '' : idPlanta}
onChange={(e) => { setIdPlanta(e.target.value as number); setLocalErrors(p => ({ ...p, idPlanta: null })); }}
>
<MenuItem value="" disabled><em>{consolidado ? 'N/A (Consolidado)' : 'Seleccione una planta'}</em></MenuItem>
{plantas.map((p) => (
<MenuItem key={p.idPlanta} value={p.idPlanta}>{p.nombre}</MenuItem>
))}
</Select>
{localErrors.idPlanta && <Typography color="error" variant="caption" sx={{ml:1.5}}>{localErrors.idPlanta}</Typography>}
</FormControl>
{apiErrorMessage && <Alert severity="error" sx={{ mt: 2 }}>{apiErrorMessage}</Alert>}
{localErrors.dropdowns && <Alert severity="warning" sx={{ mt: 1 }}>{localErrors.dropdowns}</Alert>}
<Box sx={{ mt: 3, display: 'flex', justifyContent: 'flex-end', gap: 1 }}>
<Button onClick={handleGenerar} variant="contained" disabled={isLoading || loadingDropdowns}>
{isLoading ? <CircularProgress size={24} /> : 'Generar Reporte'}
</Button>
</Box>
</Box>
);
};
export default SeleccionaReporteTiradasPublicacionesSecciones;

View File

@@ -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<void>;
onCancel: () => void; // Para volver a la pantalla de selección de reportes
isLoading?: boolean;
apiErrorMessage?: string | null;
}
const SeleccionaReporteVentaMensual: React.FC<SeleccionaReporteVentaMensualProps> = ({
onGenerarReporte,
isLoading,
apiErrorMessage
}) => {
const [fechaDesde, setFechaDesde] = useState<string>(new Date().toISOString().split('T')[0]);
const [fechaHasta, setFechaHasta] = useState<string>(new Date().toISOString().split('T')[0]);
const [tipoReporte, setTipoReporte] = useState<TipoReporteVentaMensual>('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<string>) => {
setTipoReporte(event.target.value as TipoReporteVentaMensual);
setLocalErrors(p => ({ ...p, tipoReporte: null }));
};
return (
<Box sx={{ p: 2, border: '1px solid #ccc', borderRadius: '4px', minWidth: 380 }}>
<Typography variant="h6" gutterBottom>
Parámetros: Venta Mensual Secretaría
</Typography>
<FormControl fullWidth margin="normal" error={!!localErrors.tipoReporte} disabled={isLoading}>
<InputLabel id="tipo-reporte-select-label" required>Tipo de Reporte</InputLabel>
<Select
labelId="tipo-reporte-select-label"
label="Tipo de Reporte"
value={tipoReporte}
onChange={handleTipoReporteChange}
>
<MenuItem value="ElDia">El Día</MenuItem>
<MenuItem value="ElPlata">El Plata</MenuItem>
<MenuItem value="TirDevo">Tirada / Devolución</MenuItem>
</Select>
{localErrors.tipoReporte && <Typography color="error" variant="caption" sx={{ml:1.5}}>{localErrors.tipoReporte}</Typography>}
</FormControl>
<TextField
label="Fecha Desde"
type="date"
value={fechaDesde}
onChange={(e) => { 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 }}
/>
<TextField
label="Fecha Hasta"
type="date"
value={fechaHasta}
onChange={(e) => { 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 && <Alert severity="error" sx={{ mt: 2 }}>{apiErrorMessage}</Alert>}
<Box sx={{ mt: 3, display: 'flex', justifyContent: 'flex-end', gap: 1 }}>
<Button onClick={handleGenerar} variant="contained" disabled={isLoading}>
{isLoading ? <CircularProgress size={24} /> : 'Generar Reporte'}
</Button>
</Box>
</Box>
);
};
export default SeleccionaReporteVentaMensual;

View File

@@ -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 = () => {
<Route path="movimiento-bobinas-estado" element={<ReporteMovimientoBobinasEstadoPage />} />
<Route path="listado-distribucion-general" element={<ReporteListadoDistribucionGeneralPage />} />
<Route path="listado-distribucion-canillas" element={<ReporteListadoDistribucionCanillasPage />} />
<Route path="listado-distribucion-canillas-importe" element={<ReporteListadoDistribucionCanillasImportePage />} />
<Route path="venta-mensual-secretaria" element={<ReporteVentaMensualSecretariaPage />} />
<Route path="detalle-distribucion-canillas" element={<ReporteDetalleDistribucionCanillasPage />} />
<Route path="tiradas-publicaciones-secciones" element={<ReporteTiradasPublicacionesSeccionesPage />} />
<Route path="consumo-bobinas-seccion" element={<ReporteConsumoBobinasSeccionPage />} />
<Route path="consumo-bobinas-publicacion" element={<ReporteConsumoBobinasPublicacionPage />} />
<Route path="comparativa-consumo-bobinas" element={<ReporteComparativaConsumoBobinasPage />} />
<Route path="cuentas-distribuidores" element={<ReporteCuentasDistribuidoresPage />} />
</Route>
{/* Módulo de Radios (anidado) */}

View File

@@ -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<ListadoDistribucionCanillasImporteDto[]> => {
const response = await apiClient.get<ListadoDistribucionCanillasImporteDto[]>('/reportes/listado-distribucion-canillas-importe', { params });
return response.data;
};
const getListadoDistribucionCanillasImportePdf = async (params: {
idPublicacion: number;
fechaDesde: string;
fechaHasta: string;
esAccionista: boolean;
}): Promise<Blob> => {
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<VentaMensualSecretariaElDiaDto[]> => {
const response = await apiClient.get<VentaMensualSecretariaElDiaDto[]>('/reportes/venta-mensual-secretaria/el-dia', { params });
return response.data;
};
const getVentaMensualSecretariaElDiaPdf = async (params: { fechaDesde: string; fechaHasta: string }): Promise<Blob> => {
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<VentaMensualSecretariaElPlataDto[]> => {
const response = await apiClient.get<VentaMensualSecretariaElPlataDto[]>('/reportes/venta-mensual-secretaria/el-plata', { params });
return response.data;
};
const getVentaMensualSecretariaElPlataPdf = async (params: { fechaDesde: string; fechaHasta: string }): Promise<Blob> => {
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<VentaMensualSecretariaTirDevoDto[]> => {
const response = await apiClient.get<VentaMensualSecretariaTirDevoDto[]>('/reportes/venta-mensual-secretaria/tirada-devolucion', { params });
return response.data;
};
const getVentaMensualSecretariaTirDevoPdf = async (params: { fechaDesde: string; fechaHasta: string }): Promise<Blob> => {
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<ReporteDistribucionCanillasResponseDto> => {
const response = await apiClient.get<ReporteDistribucionCanillasResponseDto>('/reportes/distribucion-canillas', { params });
return response.data;
};
const getReporteDistribucionCanillasPdf = async (params: {
fecha: string;
idEmpresa: number;
soloTotales: boolean; // Nuevo parámetro
}): Promise<Blob> => {
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<TiradasPublicacionesSeccionesDto[]> => {
const response = await apiClient.get<TiradasPublicacionesSeccionesDto[]>('/reportes/tiradas-publicaciones-secciones', { params });
return response.data;
};
const getTiradasPublicacionesSeccionesPdf = async (params: {
idPublicacion: number;
fechaDesde: string;
fechaHasta: string;
idPlanta?: number | null;
consolidado: boolean;
}): Promise<Blob> => {
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<ConsumoBobinasSeccionDto[]> => {
const response = await apiClient.get<ConsumoBobinasSeccionDto[]>('/reportes/consumo-bobinas-seccion', { params });
return response.data;
};
const getConsumoBobinasSeccionPdf = async (params: {
fechaDesde: string;
fechaHasta: string;
idPlanta?: number | null;
consolidado: boolean;
}): Promise<Blob> => {
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<ConsumoBobinasPublicacionDto[]> => {
const response = await apiClient.get<ConsumoBobinasPublicacionDto[]>('/reportes/consumo-bobinas-publicacion', { params });
return response.data;
};
const getConsumoBobinasPorPublicacionPdf = async (params: {
fechaDesde: string;
fechaHasta: string;
}): Promise<Blob> => {
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<ComparativaConsumoBobinasDto[]> => {
const response = await apiClient.get<ComparativaConsumoBobinasDto[]>('/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<Blob> => {
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<ReporteCuentasDistribuidorResponseDto> => {
const response = await apiClient.get<ReporteCuentasDistribuidorResponseDto>('/reportes/cuentas-distribuidores', { params });
return response.data;
};
const getReporteCuentasDistribuidorPdf = async (params: {
idDistribuidor: number;
idEmpresa: number;
fechaDesde: string;
fechaHasta: string;
}): Promise<Blob> => {
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;