no se que modificaciones se hizo posible error

This commit is contained in:
unknown
2026-06-13 12:04:04 -04:00
parent ff90340fdb
commit fa05ae3754
24 changed files with 2466 additions and 402 deletions

View File

@@ -1,177 +1,468 @@
import { useState, useEffect, useMemo, useRef } from 'react';
import { sheetsApi } from '../api/client';
import { useAuth } from '../context/AuthContext';
import { useSheetAutoSave } from '../hooks/useSheetAutoSave';
import { useGestionView } from '../hooks/useGestionView';
import SaveStatus from './SaveStatus';
import GestionBanner from './GestionBanner';
import GestionesSidebar from './GestionesSidebar';
import { emptyGastoRow, formatMoney } from '../utils/calc';
import { isRowEditable } from '../utils/gestion';
export default function GastoSheet({ sheetType, title, defaultClasificacion }) {
const { canEdit } = useAuth();
const { canEdit, user } = useAuth();
const editable = canEdit(sheetType);
const isAdmin = !!user?.isAdmin;
const { selectedGestionId, selectGestion, sidebarGestiones } = useGestionView();
const [rows, setRows] = useState([emptyGastoRow()]);
const [gestion, setGestion] = useState(null);
const [gestionVista, setGestionVista] = useState(null);
const [esHistorial, setEsHistorial] = useState(false);
const [loading, setLoading] = useState(true);
const [loadError, setLoadError] = useState(null);
const skipNotifyRef = useRef(false);
const emptyRow = () => ({ ...emptyGastoRow(), clasificacion: defaultClasificacion });
const canEditSheet = editable && !esHistorial;
const { status, error: saveError, setReady, notifyChange, saveNow } = useSheetAutoSave({
enabled: editable,
enabled: canEditSheet,
sheetType,
});
useEffect(() => {
let active = true;
setLoading(true);
setLoadError(null);
setReady(false);
(async () => {
try {
const data = await sheetsApi.get(sheetType);
const data = await sheetsApi.get(sheetType, { gestionId: selectedGestionId });
if (!active) return;
const loaded = data.rows?.length ? data.rows : [emptyGastoRow()];
const loaded = data.rows || [];
skipNotifyRef.current = true;
setRows(loaded.map((r) => ({ ...r, clasificacion: r.clasificacion || defaultClasificacion })));
setReady(true);
if (loaded.length) {
setRows(loaded.map((r) => ({ ...r, clasificacion: r.clasificacion || defaultClasificacion })));
} else if (!data.esHistorial && canEditSheet) {
setRows([emptyRow()]);
} else {
setRows([]);
}
setGestion(data.gestion || null);
setGestionVista(data.gestionVista || null);
setEsHistorial(!!data.esHistorial);
setReady(!data.esHistorial);
} catch (e) {
if (active) setLoadError(e.message);
} finally {
if (active) setLoading(false);
}
})();
return () => {
active = false;
};
}, [sheetType, defaultClasificacion, setReady]);
}, [sheetType, defaultClasificacion, selectedGestionId, setReady, canEditSheet]);
useEffect(() => {
if (loading || !editable) return;
if (loading || !canEditSheet) return;
if (skipNotifyRef.current) {
skipNotifyRef.current = false;
return;
}
notifyChange({ rows });
}, [rows, loading, editable, notifyChange]);
}, [rows, loading, canEditSheet, notifyChange]);
const total = useMemo(
() => rows.reduce((acc, r) => acc + (Number(r.monto) || 0), 0),
[rows]
);
const updateRow = (index, field, value) => {
if (!isRowEditable(rows[index], canEditSheet, isAdmin)) return;
setRows((prev) => prev.map((r, i) => (i === index ? { ...r, [field]: value } : r)));
};
const addRow = () =>
setRows((prev) => [...prev, { ...emptyGastoRow(), clasificacion: defaultClasificacion }]);
const addRow = () => setRows((prev) => [...prev, emptyRow()]);
const removeRow = (index) => {
if (!isRowEditable(rows[index], canEditSheet, isAdmin)) return;
skipNotifyRef.current = true;
const next = rows.filter((_, i) => i !== index);
const result =
next.length > 0 ? next : [{ ...emptyGastoRow(), clasificacion: defaultClasificacion }];
const result = next.length > 0 ? next : [emptyRow()];
setRows(result);
saveNow({ rows: result });
};
const clearSheet = () => {
if (!window.confirm('¿Vaciar toda la planilla? Se eliminarán todas las filas.')) return;
const blank = [{ ...emptyGastoRow(), clasificacion: defaultClasificacion }];
const msg = gestion?.fechaCierreGestion && !isAdmin
? '¿Eliminar solo las filas editables? Las del período cerrado se conservarán.'
: '¿Vaciar toda la planilla? Se eliminarán todas las filas de la gestión actual.';
if (!window.confirm(msg)) return;
skipNotifyRef.current = true;
setRows(blank);
saveNow({ rows: blank });
if (isAdmin || !gestion?.fechaCierreGestion) {
const blank = [emptyRow()];
setRows(blank);
saveNow({ rows: blank });
return;
}
const kept = rows.filter((r) => r.bloqueada);
const result = kept.length ? kept : [emptyRow()];
setRows(result);
saveNow({ rows: result });
};
if (loading) return <p className="loading">Cargando planilla</p>;
return (
<div>
<h1 className="page-title">{title}</h1>
{loadError && <div className="alert alert-error">{loadError}</div>}
{editable && <SaveStatus status={status} error={saveError} />}
{!editable && <div className="alert alert-info">Solo lectura</div>}
<div className="toolbar">
{editable && (
<>
<button type="button" className="btn btn-primary btn-icon" onClick={addRow}>
+
</button>
<button type="button" className="btn btn-secondary" onClick={() => saveNow({ rows })} disabled={status === 'saving'}>
Guardar ahora
</button>
<button type="button" className="btn btn-danger" onClick={clearSheet}>
Vaciar planilla
</button>
</>
<div className="sheet-layout">
<GestionesSidebar
gestiones={sidebarGestiones}
selectedId={selectedGestionId}
onSelect={selectGestion}
/>
<div className="sheet-layout__main">
<h1 className="page-title">{title}</h1>
{loadError && <div className="alert alert-error">{loadError}</div>}
{esHistorial && gestionVista && (
<div className="historial-badge">
Historial: <strong>{gestionVista.etiqueta}</strong> ({gestionVista.fechaInicio} {' '}
{gestionVista.fechaFin}) solo lectura
</div>
)}
{!esHistorial && <GestionBanner gestion={gestion} isAdmin={isAdmin} />}
{canEditSheet && <SaveStatus status={status} error={saveError} />}
{esHistorial && <div className="alert alert-info">Gestión cerrada solo consulta</div>}
{!editable && !esHistorial && <div className="alert alert-info">Solo lectura</div>}
<div className="toolbar">
{canEditSheet && (
<>
<button type="button" className="btn btn-primary btn-icon" onClick={addRow}>
+
</button>
<button type="button" className="btn btn-secondary" onClick={() => saveNow({ rows })} disabled={status === 'saving'}>
Guardar ahora
</button>
<button type="button" className="btn btn-danger" onClick={clearSheet}>
Vaciar planilla
</button>
</>
)}
</div>
{rows.length === 0 ? (
<p className="loading">Sin registros en esta gestión.</p>
) : (
<div className="card sheet-table-wrap">
<table className="sheet-table">
<thead>
<tr>
<th>Fecha</th>
<th>Detalle</th>
<th>Impuestos</th>
<th> Factura</th>
<th>Monto</th>
<th>Clasificación</th>
{canEditSheet && <th></th>}
</tr>
</thead>
<tbody>
{rows.map((row, i) => {
const rowEditable = isRowEditable(row, canEditSheet, isAdmin);
return (
<tr key={i} className={row.bloqueada ? 'row-locked' : ''}>
<td>
<input type="date" value={row.fecha || ''} disabled={!rowEditable} onChange={(e) => updateRow(i, 'fecha', e.target.value)} />
</td>
<td>
<input value={row.detalle || ''} disabled={!rowEditable} onChange={(e) => updateRow(i, 'detalle', e.target.value)} />
</td>
<td>
<input type="number" min="0" step="any" value={row.impuestos ?? ''} disabled={!rowEditable} onChange={(e) => updateRow(i, 'impuestos', e.target.value)} />
</td>
<td>
<input value={row.numeroFactura || ''} disabled={!rowEditable} onChange={(e) => updateRow(i, 'numeroFactura', e.target.value)} />
</td>
<td>
<input type="number" min="0" step="any" value={row.monto ?? ''} disabled={!rowEditable} onChange={(e) => updateRow(i, 'monto', e.target.value)} />
</td>
<td>
<select value={row.clasificacion || 'diario'} disabled={!rowEditable} onChange={(e) => updateRow(i, 'clasificacion', e.target.value)}>
<option value="diario">Gasto Diario</option>
<option value="fijo">Gasto Fijo</option>
<option value="variable">Gasto Variable</option>
</select>
</td>
{canEditSheet && (
<td>
<button
type="button"
className="btn btn-danger btn-icon"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
removeRow(i);
}}
disabled={!rowEditable}
title={row.bloqueada ? 'Bloqueado — período cerrado' : 'Eliminar fila'}
>
×
</button>
</td>
)}
</tr>
);
})}
</tbody>
</table>
</div>
)}
<div className="totals-bar">
<span>
Total gastos: <strong>{formatMoney(total)}</strong>
</span>
</div>
</div>
<div className="card sheet-table-wrap">
<table className="sheet-table">
<thead>
<tr>
<th>Fecha</th>
<th>Detalle</th>
<th>Impuestos</th>
<th> Factura</th>
<th>Monto</th>
<th>Clasificación</th>
{editable && <th></th>}
</tr>
</thead>
<tbody>
{rows.map((row, i) => (
<tr key={i}>
<td>
<input type="date" value={row.fecha || ''} disabled={!editable} onChange={(e) => updateRow(i, 'fecha', e.target.value)} />
</td>
<td>
<input value={row.detalle || ''} disabled={!editable} onChange={(e) => updateRow(i, 'detalle', e.target.value)} />
</td>
<td>
<input type="number" min="0" step="any" value={row.impuestos ?? ''} disabled={!editable} onChange={(e) => updateRow(i, 'impuestos', e.target.value)} />
</td>
<td>
<input value={row.numeroFactura || ''} disabled={!editable} onChange={(e) => updateRow(i, 'numeroFactura', e.target.value)} />
</td>
<td>
<input type="number" min="0" step="any" value={row.monto ?? ''} disabled={!editable} onChange={(e) => updateRow(i, 'monto', e.target.value)} />
</td>
<td>
<select value={row.clasificacion || 'diario'} disabled={!editable} onChange={(e) => updateRow(i, 'clasificacion', e.target.value)}>
<option value="diario">Gasto Diario</option>
<option value="fijo">Gasto Fijo</option>
<option value="variable">Gasto Variable</option>
</select>
</td>
{editable && (
<td>
<button
type="button"
className="btn btn-danger btn-icon"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
removeRow(i);
}}
title="Eliminar fila"
>
×
</button>
</td>
)}
</tr>
))}
</tbody>
</table>
</div>
<div className="totals-bar">
<span>
Total gastos: <strong>{formatMoney(total)}</strong>
</span>
</div>
</div>
);
}