Files
backendDonMarco/frontend/src/pages/ReportesPage.jsx
2026-06-03 16:59:27 -04:00

176 lines
6.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useState, useEffect } from 'react';
import { reportsApi } from '../api/client';
import { formatMoney } from '../utils/calc';
const INCLUDE_OPTS = [
{ key: 'ventas', label: 'Ventas' },
{ key: 'servicios', label: 'Servicios' },
{ key: 'alquileres', label: 'Alquileres' },
{ key: 'gastos_diarios', label: 'Gastos diarios' },
{ key: 'gastos_fijos', label: 'Gastos fijos' },
{ key: 'personal', label: 'Personal' },
];
export default function ReportesPage() {
const [tipo, setTipo] = useState('mensual');
const [desde, setDesde] = useState('');
const [hasta, setHasta] = useState('');
const [include, setInclude] = useState(
Object.fromEntries(INCLUDE_OPTS.map((o) => [o.key, true]))
);
const [report, setReport] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const loadReport = async () => {
setLoading(true);
setError(null);
try {
const data = await reportsApi.get({
tipo,
desde: desde || undefined,
hasta: hasta || undefined,
include: JSON.stringify(include),
});
setReport(data);
} catch (e) {
setError(e.message);
} finally {
setLoading(false);
}
};
useEffect(() => {
loadReport();
}, []);
const toggleInclude = (key) => setInclude((p) => ({ ...p, [key]: !p[key] }));
return (
<div>
<h1 className="page-title">Planilla de Reportes</h1>
<div className="card" style={{ marginBottom: '1rem' }}>
<div className="toolbar" style={{ flexWrap: 'wrap' }}>
<label>
Tipo de reporte{' '}
<select value={tipo} onChange={(e) => setTipo(e.target.value)}>
<option value="diario">Diario</option>
<option value="semanal">Semanal</option>
<option value="mensual">Mensual</option>
<option value="custom">Rango personalizado</option>
</select>
</label>
<label>
Desde <input type="date" value={desde} onChange={(e) => setDesde(e.target.value)} />
</label>
<label>
Hasta <input type="date" value={hasta} onChange={(e) => setHasta(e.target.value)} />
</label>
<button type="button" className="btn btn-primary" onClick={loadReport} disabled={loading}>
{loading ? 'Calculando…' : 'Generar reporte'}
</button>
</div>
<p style={{ fontSize: '0.85rem', color: 'var(--muted)' }}>Planillas a incluir en el cálculo:</p>
<div className="perm-grid">
{INCLUDE_OPTS.map((o) => (
<label key={o.key} className="perm-item checkbox-row">
<input type="checkbox" checked={!!include[o.key]} onChange={() => toggleInclude(o.key)} />
{o.label}
</label>
))}
</div>
</div>
{error && <div className="alert alert-error">{error}</div>}
{report && (
<>
<div className="card">
<h2 style={{ marginTop: 0 }}>Desglose por planilla</h2>
<table className="sheet-table" style={{ minWidth: 'auto' }}>
<tbody>
<tr>
<td>Ventas (ingresos)</td>
<td className="computed">{formatMoney(report.desglose.ventas.totalIngresos)}</td>
<td>Utilidad: {formatMoney(report.desglose.ventas.totalUtilidad)}</td>
</tr>
<tr>
<td>Servicios</td>
<td className="computed">{formatMoney(report.desglose.servicios.totalIngresos)}</td>
<td>Utilidad: {formatMoney(report.desglose.servicios.totalUtilidad)}</td>
</tr>
<tr>
<td>Alquileres</td>
<td className="computed">{formatMoney(report.desglose.alquileres.totalIngresos)}</td>
<td>Utilidad: {formatMoney(report.desglose.alquileres.totalUtilidad)}</td>
</tr>
<tr>
<td>Gastos diarios</td>
<td colSpan={2} className="computed">{formatMoney(report.desglose.gastosDiarios.total)}</td>
</tr>
<tr>
<td>Gastos fijos</td>
<td colSpan={2} className="computed">{formatMoney(report.desglose.gastosFijos.total)}</td>
</tr>
<tr>
<td>Gastos variables (clasificados)</td>
<td colSpan={2} className="computed">{formatMoney(report.desglose.gastosVariables.total)}</td>
</tr>
<tr>
<td>Planilla de personal</td>
<td colSpan={2} className="computed">{formatMoney(report.desglose.personal.total)}</td>
</tr>
</tbody>
</table>
</div>
<div className="totals-bar" style={{ marginTop: '1rem' }}>
<span>Total ingresos: {formatMoney(report.totales.totalIngresos)}</span>
<span>Total gastos: {formatMoney(report.totales.totalGastos)}</span>
<span>Total personal: {formatMoney(report.totales.totalPersonal)}</span>
</div>
{report.cierreSemanal && Object.keys(report.cierreSemanal).length > 0 && (
<div className="card" style={{ marginTop: '1rem' }}>
<h3>Cierre semanal gastos diarios por semana</h3>
<ul>
{Object.entries(report.cierreSemanal).map(([sem, monto]) => (
<li key={sem}>
Semana desde {sem}: <strong>{formatMoney(monto)}</strong>
</li>
))}
</ul>
<p>
Total gastos semanales en periodo:{' '}
<strong>{formatMoney(report.totalGastosSemanales)}</strong>
</p>
</div>
)}
{report.cierreMensual && (
<div className="card" style={{ marginTop: '1rem' }}>
<h3>Cierre mensual consolidado</h3>
<p>Gastos fijos en periodo: {formatMoney(report.cierreMensual.gastosFijosEnPeriodo)}</p>
<p>
Total gastos mensuales consolidado:{' '}
<strong>{formatMoney(report.cierreMensual.totalGastosMensualesConsolidado)}</strong>
</p>
</div>
)}
<div className={`report-result ${report.totales.estado}`}>
Resultado neto ({tipo}): {formatMoney(report.totales.resultadoNeto)} {' '}
{report.totales.estado === 'utilidad' ? 'UTILIDAD (Ganancia)' : 'PÉRDIDA'}
</div>
<p style={{ fontSize: '0.8rem', color: 'var(--muted)', marginTop: '0.5rem' }}>
Fórmula: (Ventas + Alquileres + Servicios) Total Gastos Total Personal
</p>
</>
)}
</div>
);
}