no se que modificaciones se hizo posible error
This commit is contained in:
@@ -52,5 +52,23 @@ export function initDb() {
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_registros_sheet ON registros(sheet_type);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS settings (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT,
|
||||
updated_by INTEGER,
|
||||
updated_at TEXT DEFAULT (datetime('now')),
|
||||
FOREIGN KEY (updated_by) REFERENCES users(id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS gestiones (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
etiqueta TEXT NOT NULL,
|
||||
fecha_inicio TEXT NOT NULL,
|
||||
fecha_fin TEXT,
|
||||
cerrada_at TEXT,
|
||||
cerrada_por INTEGER,
|
||||
FOREIGN KEY (cerrada_por) REFERENCES users(id)
|
||||
);
|
||||
`);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ import authRoutes from './routes/auth.js';
|
||||
import userRoutes from './routes/users.js';
|
||||
import sheetRoutes from './routes/sheets.js';
|
||||
import reportRoutes from './routes/reports.js';
|
||||
import settingsRoutes from './routes/settings.js';
|
||||
import gestionesRoutes from './routes/gestiones.js';
|
||||
import bcrypt from 'bcryptjs';
|
||||
import { db, SHEETS } from './db.js';
|
||||
|
||||
@@ -31,11 +33,20 @@ const PORT = process.env.PORT || 4000;
|
||||
app.use(cors({ origin: true, credentials: true }));
|
||||
app.use(express.json({ limit: '2mb' }));
|
||||
|
||||
app.get('/api/health', (_req, res) => res.json({ ok: true, service: 'DonMarco API' }));
|
||||
app.get('/api/health', (_req, res) =>
|
||||
res.json({
|
||||
ok: true,
|
||||
service: 'DonMarco API',
|
||||
version: '1.2.0',
|
||||
features: ['historial-gestiones', 'cierre-gestion', 'auto-guardado', 'reportes'],
|
||||
})
|
||||
);
|
||||
app.use('/api/auth', authRoutes);
|
||||
app.use('/api/users', userRoutes);
|
||||
app.use('/api/sheets', sheetRoutes);
|
||||
app.use('/api/reports', reportRoutes);
|
||||
app.use('/api/settings', settingsRoutes);
|
||||
app.use('/api/gestiones', gestionesRoutes);
|
||||
|
||||
app.listen(PORT, '0.0.0.0', () => {
|
||||
console.log(`Don Marco API escuchando en http://0.0.0.0:${PORT}`);
|
||||
|
||||
16
backend/src/routes/gestiones.js
Normal file
16
backend/src/routes/gestiones.js
Normal file
@@ -0,0 +1,16 @@
|
||||
import { Router } from 'express';
|
||||
import { authRequired } from '../middleware/auth.js';
|
||||
import { listGestiones, getGestionActual } from '../utils/gestion.js';
|
||||
|
||||
const router = Router();
|
||||
router.use(authRequired);
|
||||
|
||||
router.get('/', (_req, res) => {
|
||||
res.json({
|
||||
actual: getGestionActual(),
|
||||
historial: listGestiones().filter((g) => !g.esActual),
|
||||
todas: listGestiones(),
|
||||
});
|
||||
});
|
||||
|
||||
export default router;
|
||||
36
backend/src/routes/settings.js
Normal file
36
backend/src/routes/settings.js
Normal file
@@ -0,0 +1,36 @@
|
||||
import { Router } from 'express';
|
||||
import { authRequired, adminRequired } from '../middleware/auth.js';
|
||||
import {
|
||||
getGestionStatus,
|
||||
cerrarGestionActual,
|
||||
sugerirCierreMensual,
|
||||
} from '../utils/gestion.js';
|
||||
|
||||
const router = Router();
|
||||
|
||||
router.get('/gestion', authRequired, (_req, res) => {
|
||||
res.json({ ...getGestionStatus(), sugerirCierre: sugerirCierreMensual() });
|
||||
});
|
||||
|
||||
router.put('/gestion', authRequired, adminRequired, (req, res) => {
|
||||
const { fechaCierreGestion } = req.body;
|
||||
|
||||
if (!fechaCierreGestion) {
|
||||
return res.status(400).json({
|
||||
error: 'Indique la fecha de cierre. Para consultar historial use la barra lateral de cada planilla.',
|
||||
});
|
||||
}
|
||||
|
||||
if (!/^\d{4}-\d{2}-\d{2}$/.test(fechaCierreGestion)) {
|
||||
return res.status(400).json({ error: 'Fecha inválida. Use formato AAAA-MM-DD' });
|
||||
}
|
||||
|
||||
try {
|
||||
const result = cerrarGestionActual(fechaCierreGestion, req.user.id);
|
||||
res.json({ ...getGestionStatus(), sugerirCierre: sugerirCierreMensual(), cierre: result });
|
||||
} catch (e) {
|
||||
res.status(400).json({ error: e.message });
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
||||
@@ -2,6 +2,17 @@ import { Router } from 'express';
|
||||
import { db, SHEETS } from '../db.js';
|
||||
import { authRequired } from '../middleware/auth.js';
|
||||
import { sumIngresos, sumGastos, sumPersonal } from '../utils/calculations.js';
|
||||
import {
|
||||
getGestionStatus,
|
||||
getGestionActual,
|
||||
getGestionById,
|
||||
mergeRowsForSave,
|
||||
mergePersonalForSave,
|
||||
isFechaEnPeriodoCerrado,
|
||||
filterRowsByGestion,
|
||||
filterPersonalByGestion,
|
||||
mensualBelongsToGestion,
|
||||
} from '../utils/gestion.js';
|
||||
|
||||
const router = Router();
|
||||
router.use(authRequired);
|
||||
@@ -19,45 +30,112 @@ function checkPermission(req, sheet, needEdit = false) {
|
||||
return !!row.can_view;
|
||||
}
|
||||
|
||||
function getSheetPayload(sheetType) {
|
||||
const rows = db
|
||||
function resolveGestionView(gestionId) {
|
||||
if (!gestionId || gestionId === 'actual') {
|
||||
return { gestion: getGestionActual(), esHistorial: false };
|
||||
}
|
||||
const gestion = getGestionById(Number(gestionId));
|
||||
if (!gestion) return null;
|
||||
return { gestion, esHistorial: !gestion.esActual };
|
||||
}
|
||||
|
||||
function loadParsedRows(sheetType) {
|
||||
return db
|
||||
.prepare('SELECT id, data, updated_at FROM registros WHERE sheet_type = ? ORDER BY id')
|
||||
.all(sheetType);
|
||||
const parsed = rows.map((r) => ({ id: r.id, ...JSON.parse(r.data), updated_at: r.updated_at }));
|
||||
.all(sheetType)
|
||||
.map((r) => ({ id: r.id, ...JSON.parse(r.data), updated_at: r.updated_at }));
|
||||
}
|
||||
|
||||
function annotateRows(rows, isAdmin, esHistorial) {
|
||||
return rows.map((row) => ({
|
||||
...row,
|
||||
bloqueada: esHistorial || (!isAdmin && isFechaEnPeriodoCerrado(row.fecha)),
|
||||
}));
|
||||
}
|
||||
|
||||
function getSheetPayload(sheetType, isAdmin, gestionView) {
|
||||
const parsed = loadParsedRows(sheetType);
|
||||
const gestionState = getGestionStatus();
|
||||
const { gestion, esHistorial } = gestionView;
|
||||
|
||||
if (INGRESO_SHEETS.includes(sheetType)) {
|
||||
const totals = sumIngresos(parsed);
|
||||
return { rows: parsed, totals };
|
||||
}
|
||||
if (GASTO_SHEETS.includes(sheetType)) {
|
||||
const total = sumGastos(parsed);
|
||||
return { rows: parsed, totals: { totalGastos: total } };
|
||||
}
|
||||
if (sheetType === 'personal') {
|
||||
const mensualizados = parsed.filter((r) => r.tipo === 'mensualizado');
|
||||
const eventuales = parsed.filter((r) => r.tipo === 'eventual');
|
||||
const filtered = filterRowsByGestion(parsed, gestion);
|
||||
const rows = annotateRows(filtered, isAdmin, esHistorial);
|
||||
const totals = sumIngresos(filtered);
|
||||
return {
|
||||
mensualizados,
|
||||
eventuales,
|
||||
totals: { totalPersonal: sumPersonal(mensualizados, eventuales) },
|
||||
rows: rows.length ? rows : [],
|
||||
totals,
|
||||
gestion: gestionState,
|
||||
gestionVista: gestion,
|
||||
esHistorial,
|
||||
soloLectura: esHistorial,
|
||||
};
|
||||
}
|
||||
return { rows: parsed };
|
||||
|
||||
if (GASTO_SHEETS.includes(sheetType)) {
|
||||
const filtered = filterRowsByGestion(parsed, gestion);
|
||||
const rows = annotateRows(filtered, isAdmin, esHistorial);
|
||||
const total = sumGastos(filtered);
|
||||
return {
|
||||
rows: rows.length ? rows : [],
|
||||
totals: { totalGastos: total },
|
||||
gestion: gestionState,
|
||||
gestionVista: gestion,
|
||||
esHistorial,
|
||||
soloLectura: esHistorial,
|
||||
};
|
||||
}
|
||||
|
||||
if (sheetType === 'personal') {
|
||||
const mensualizadosAll = parsed.filter((r) => r.tipo === 'mensualizado');
|
||||
const eventualesAll = parsed.filter((r) => r.tipo === 'eventual');
|
||||
const { mensualizados, eventuales } = filterPersonalByGestion(
|
||||
mensualizadosAll,
|
||||
eventualesAll,
|
||||
gestion
|
||||
);
|
||||
const mensualizadosAnn = mensualizados.map((r) => ({
|
||||
...r,
|
||||
bloqueada: esHistorial || (!isAdmin && !mensualBelongsToGestion(r, getGestionActual()) && r.gestionId),
|
||||
}));
|
||||
const eventualesAnn = eventuales.map((r) => ({
|
||||
...r,
|
||||
bloqueada: esHistorial || (!isAdmin && isFechaEnPeriodoCerrado(r.fecha)),
|
||||
}));
|
||||
return {
|
||||
mensualizados: mensualizadosAnn,
|
||||
eventuales: eventualesAnn,
|
||||
totals: { totalPersonal: sumPersonal(mensualizados, eventuales) },
|
||||
gestion: gestionState,
|
||||
gestionVista: gestion,
|
||||
esHistorial,
|
||||
soloLectura: esHistorial,
|
||||
};
|
||||
}
|
||||
|
||||
return { rows: parsed, gestion: gestionState, gestionVista: gestion, esHistorial };
|
||||
}
|
||||
|
||||
router.get('/:sheetType', (req, res) => {
|
||||
const { sheetType } = req.params;
|
||||
const { gestionId } = req.query;
|
||||
|
||||
if (!SHEETS.includes(sheetType) || sheetType === 'reportes') {
|
||||
return res.status(400).json({ error: 'Planilla inválida' });
|
||||
}
|
||||
if (!checkPermission(req, sheetType)) {
|
||||
return res.status(403).json({ error: 'Sin permiso para ver esta planilla' });
|
||||
}
|
||||
res.json(getSheetPayload(sheetType));
|
||||
|
||||
const gestionView = resolveGestionView(gestionId);
|
||||
if (!gestionView) return res.status(404).json({ error: 'Gestión no encontrada' });
|
||||
|
||||
res.json(getSheetPayload(sheetType, req.user.isAdmin, gestionView));
|
||||
});
|
||||
|
||||
router.put('/:sheetType', (req, res) => {
|
||||
const { sheetType } = req.params;
|
||||
|
||||
if (!SHEETS.includes(sheetType) || sheetType === 'reportes') {
|
||||
return res.status(400).json({ error: 'Planilla inválida' });
|
||||
}
|
||||
@@ -65,7 +143,30 @@ router.put('/:sheetType', (req, res) => {
|
||||
return res.status(403).json({ error: 'Sin permiso para editar' });
|
||||
}
|
||||
|
||||
const { rows, mensualizados, eventuales } = req.body;
|
||||
const isAdmin = !!req.user.isAdmin;
|
||||
const gestionActual = getGestionActual();
|
||||
const existing = loadParsedRows(sheetType);
|
||||
let rowsToSave;
|
||||
let personalMensual;
|
||||
let personalEventual;
|
||||
|
||||
try {
|
||||
if (sheetType === 'personal') {
|
||||
const merged = mergePersonalForSave(
|
||||
existing,
|
||||
req.body.mensualizados,
|
||||
req.body.eventuales,
|
||||
isAdmin,
|
||||
gestionActual
|
||||
);
|
||||
personalMensual = merged.mensualizados;
|
||||
personalEventual = merged.eventuales;
|
||||
} else {
|
||||
rowsToSave = mergeRowsForSave(existing, req.body.rows, isAdmin, gestionActual, 'fecha');
|
||||
}
|
||||
} catch (e) {
|
||||
return res.status(403).json({ error: e.message });
|
||||
}
|
||||
|
||||
db.prepare('DELETE FROM registros WHERE sheet_type = ?').run(sheetType);
|
||||
|
||||
@@ -75,21 +176,21 @@ router.put('/:sheetType', (req, res) => {
|
||||
|
||||
if (sheetType === 'personal') {
|
||||
const all = [
|
||||
...(mensualizados || []).map((r) => ({ ...r, tipo: 'mensualizado' })),
|
||||
...(eventuales || []).map((r) => ({ ...r, tipo: 'eventual' })),
|
||||
...(personalMensual || []).map((r) => ({ ...r, tipo: 'mensualizado' })),
|
||||
...(personalEventual || []).map((r) => ({ ...r, tipo: 'eventual' })),
|
||||
];
|
||||
for (const row of all) {
|
||||
const { id: _id, updated_at: _u, ...data } = row;
|
||||
const { id: _id, updated_at: _u, bloqueada: _b, ...data } = row;
|
||||
insert.run(sheetType, JSON.stringify(data), req.user.id);
|
||||
}
|
||||
} else {
|
||||
for (const row of rows || []) {
|
||||
const { id: _id, updated_at: _u, ...data } = row;
|
||||
for (const row of rowsToSave || []) {
|
||||
const { id: _id, updated_at: _u, bloqueada: _b, ...data } = row;
|
||||
insert.run(sheetType, JSON.stringify(data), req.user.id);
|
||||
}
|
||||
}
|
||||
|
||||
res.json(getSheetPayload(sheetType));
|
||||
res.json(getSheetPayload(sheetType, isAdmin, { gestion: gestionActual, esHistorial: false }));
|
||||
});
|
||||
|
||||
export default router;
|
||||
|
||||
272
backend/src/utils/gestion.js
Normal file
272
backend/src/utils/gestion.js
Normal file
@@ -0,0 +1,272 @@
|
||||
import { db } from '../db.js';
|
||||
|
||||
const LEGACY_KEY = 'fecha_cierre_gestion';
|
||||
|
||||
export function todayStr() {
|
||||
return new Date().toISOString().slice(0, 10);
|
||||
}
|
||||
|
||||
function addDays(isoDate, days) {
|
||||
const d = new Date(isoDate + 'T12:00:00');
|
||||
d.setDate(d.getDate() + days);
|
||||
return d.toISOString().slice(0, 10);
|
||||
}
|
||||
|
||||
function etiquetaFromRange(inicio, fin) {
|
||||
const ref = fin || inicio;
|
||||
const [y, m] = ref.split('-');
|
||||
const meses = [
|
||||
'Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio',
|
||||
'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre',
|
||||
];
|
||||
return `${meses[Number(m) - 1]} ${y}`;
|
||||
}
|
||||
|
||||
function mapGestion(row) {
|
||||
if (!row) return null;
|
||||
return {
|
||||
id: row.id,
|
||||
etiqueta: row.etiqueta,
|
||||
fechaInicio: row.fecha_inicio,
|
||||
fechaFin: row.fecha_fin,
|
||||
esActual: row.fecha_fin === null,
|
||||
cerradaAt: row.cerrada_at,
|
||||
};
|
||||
}
|
||||
|
||||
export function ensureGestionesMigrated() {
|
||||
const count = db.prepare('SELECT COUNT(*) AS n FROM gestiones').get().n;
|
||||
if (count > 0) return;
|
||||
|
||||
const legacy = db.prepare('SELECT value FROM settings WHERE key = ?').get(LEGACY_KEY);
|
||||
if (legacy?.value) {
|
||||
const fin = legacy.value;
|
||||
const inicio = `${fin.slice(0, 7)}-01`;
|
||||
db.prepare(
|
||||
`INSERT INTO gestiones (etiqueta, fecha_inicio, fecha_fin, cerrada_at)
|
||||
VALUES (?, ?, ?, datetime('now'))`
|
||||
).run(etiquetaFromRange(inicio, fin), inicio, fin);
|
||||
const nuevoInicio = addDays(fin, 1);
|
||||
db.prepare(
|
||||
`INSERT INTO gestiones (etiqueta, fecha_inicio, fecha_fin) VALUES (?, ?, NULL)`
|
||||
).run('Gestión actual', nuevoInicio);
|
||||
db.prepare(`DELETE FROM settings WHERE key = ?`).run(LEGACY_KEY);
|
||||
return;
|
||||
}
|
||||
|
||||
db.prepare(
|
||||
`INSERT INTO gestiones (etiqueta, fecha_inicio, fecha_fin) VALUES ('Gestión actual', '2020-01-01', NULL)`
|
||||
).run();
|
||||
}
|
||||
|
||||
export function listGestiones() {
|
||||
ensureGestionesMigrated();
|
||||
return db
|
||||
.prepare('SELECT * FROM gestiones ORDER BY fecha_inicio DESC')
|
||||
.all()
|
||||
.map(mapGestion);
|
||||
}
|
||||
|
||||
export function getGestionActual() {
|
||||
ensureGestionesMigrated();
|
||||
const row = db
|
||||
.prepare('SELECT * FROM gestiones WHERE fecha_fin IS NULL ORDER BY id DESC LIMIT 1')
|
||||
.get();
|
||||
return mapGestion(row);
|
||||
}
|
||||
|
||||
export function getGestionById(id) {
|
||||
ensureGestionesMigrated();
|
||||
const row = db.prepare('SELECT * FROM gestiones WHERE id = ?').get(id);
|
||||
return mapGestion(row);
|
||||
}
|
||||
|
||||
export function findGestionForFecha(fecha) {
|
||||
if (!fecha || String(fecha).trim() === '') return getGestionActual();
|
||||
const f = String(fecha).slice(0, 10);
|
||||
const row = db
|
||||
.prepare(
|
||||
`SELECT * FROM gestiones
|
||||
WHERE fecha_inicio <= ? AND (fecha_fin IS NULL OR fecha_fin >= ?)
|
||||
ORDER BY id DESC LIMIT 1`
|
||||
)
|
||||
.get(f, f);
|
||||
return mapGestion(row);
|
||||
}
|
||||
|
||||
export function rowBelongsToGestion(row, gestion, fechaField = 'fecha') {
|
||||
if (!gestion) return true;
|
||||
const fecha = row[fechaField];
|
||||
if (!fecha || String(fecha).trim() === '') {
|
||||
return gestion.esActual;
|
||||
}
|
||||
const f = String(fecha).slice(0, 10);
|
||||
if (f < gestion.fechaInicio) return false;
|
||||
if (gestion.fechaFin && f > gestion.fechaFin) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Personal mensualizado: por gestion_id en el registro */
|
||||
export function mensualBelongsToGestion(row, gestion) {
|
||||
if (!gestion) return true;
|
||||
if (gestion.esActual) {
|
||||
return !row.gestionId || row.gestionId === gestion.id;
|
||||
}
|
||||
return row.gestionId === gestion.id;
|
||||
}
|
||||
|
||||
export function filterRowsByGestion(rows, gestion, fechaField = 'fecha') {
|
||||
if (!gestion) return rows;
|
||||
return rows.filter((r) => rowBelongsToGestion(r, gestion, fechaField));
|
||||
}
|
||||
|
||||
export function filterPersonalByGestion(mensualizados, eventuales, gestion) {
|
||||
if (!gestion) return { mensualizados, eventuales };
|
||||
return {
|
||||
mensualizados: mensualizados.filter((r) => mensualBelongsToGestion(r, gestion)),
|
||||
eventuales: eventuales.filter((r) => rowBelongsToGestion(r, gestion, 'fecha')),
|
||||
};
|
||||
}
|
||||
|
||||
export function isFechaEnPeriodoCerrado(fecha) {
|
||||
const g = findGestionForFecha(fecha);
|
||||
return g ? !g.esActual : false;
|
||||
}
|
||||
|
||||
export function isCierreActivo() {
|
||||
return db.prepare('SELECT COUNT(*) AS n FROM gestiones WHERE fecha_fin IS NOT NULL').get().n > 0
|
||||
|| !!getGestionActual()?.fechaInicio;
|
||||
}
|
||||
|
||||
export function getGestionStatus() {
|
||||
ensureGestionesMigrated();
|
||||
const actual = getGestionActual();
|
||||
const historial = listGestiones().filter((g) => !g.esActual);
|
||||
const ultimaCerrada = historial[0];
|
||||
|
||||
return {
|
||||
gestionActual: actual,
|
||||
historial,
|
||||
fechaCierreGestion: ultimaCerrada?.fechaFin || null,
|
||||
gestionCerrada: historial.length > 0,
|
||||
hoy: todayStr(),
|
||||
};
|
||||
}
|
||||
|
||||
export function cerrarGestionActual(fechaFin, userId) {
|
||||
ensureGestionesMigrated();
|
||||
const actual = db
|
||||
.prepare('SELECT * FROM gestiones WHERE fecha_fin IS NULL ORDER BY id DESC LIMIT 1')
|
||||
.get();
|
||||
if (!actual) throw new Error('No hay gestión actual abierta');
|
||||
|
||||
if (fechaFin < actual.fecha_inicio) {
|
||||
throw new Error('La fecha de cierre no puede ser anterior al inicio de la gestión actual');
|
||||
}
|
||||
|
||||
const etiqueta = etiquetaFromRange(actual.fecha_inicio, fechaFin);
|
||||
db.prepare(
|
||||
`UPDATE gestiones SET etiqueta = ?, fecha_fin = ?, cerrada_at = datetime('now'), cerrada_por = ?
|
||||
WHERE id = ?`
|
||||
).run(etiqueta, fechaFin, userId, actual.id);
|
||||
|
||||
const personalRows = db.prepare("SELECT id, data FROM registros WHERE sheet_type = 'personal'").all();
|
||||
for (const r of personalRows) {
|
||||
const data = JSON.parse(r.data);
|
||||
if (data.tipo === 'mensualizado' && !data.gestionId) {
|
||||
data.gestionId = actual.id;
|
||||
db.prepare('UPDATE registros SET data = ? WHERE id = ?').run(JSON.stringify(data), r.id);
|
||||
}
|
||||
}
|
||||
|
||||
const nuevoInicio = addDays(fechaFin, 1);
|
||||
const result = db
|
||||
.prepare(
|
||||
`INSERT INTO gestiones (etiqueta, fecha_inicio, fecha_fin) VALUES ('Gestión actual', ?, NULL)`
|
||||
)
|
||||
.run(nuevoInicio);
|
||||
|
||||
db.prepare(`DELETE FROM settings WHERE key = ?`).run(LEGACY_KEY);
|
||||
|
||||
return {
|
||||
cerrada: mapGestion({ ...actual, etiqueta, fecha_fin: fechaFin }),
|
||||
nueva: mapGestion({
|
||||
id: result.lastInsertRowid,
|
||||
etiqueta: 'Gestión actual',
|
||||
fecha_inicio: nuevoInicio,
|
||||
fecha_fin: null,
|
||||
cerrada_at: null,
|
||||
cerrada_por: null,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
function stripMeta(row) {
|
||||
const { id, updated_at, bloqueada, gestionId: _g, ...rest } = row;
|
||||
return rest;
|
||||
}
|
||||
|
||||
export function mergeRowsForSave(existing, incoming, isAdmin, gestionActual, fechaField = 'fecha') {
|
||||
const gid = gestionActual?.id;
|
||||
const historial = existing.filter((r) => {
|
||||
const f = r[fechaField];
|
||||
if (!f || String(f).trim() === '') return false;
|
||||
return !rowBelongsToGestion(r, gestionActual, fechaField);
|
||||
});
|
||||
|
||||
let actuales;
|
||||
if (isAdmin) {
|
||||
actuales = (incoming || []).map((r) => ({ ...stripMeta(r), gestionId: gid }));
|
||||
} else {
|
||||
const locked = existing.filter(
|
||||
(r) => rowBelongsToGestion(r, gestionActual, fechaField) && isFechaEnPeriodoCerrado(r[fechaField])
|
||||
);
|
||||
const unlockedIncoming = (incoming || []).filter((r) => !isFechaEnPeriodoCerrado(r[fechaField]));
|
||||
actuales = [
|
||||
...locked.map(stripMeta),
|
||||
...unlockedIncoming.map((r) => ({ ...stripMeta(r), gestionId: gid })),
|
||||
];
|
||||
}
|
||||
|
||||
return [...historial.map(stripMeta), ...actuales];
|
||||
}
|
||||
|
||||
export function mergePersonalForSave(existing, incomingMensual, incomingEventual, isAdmin, gestionActual) {
|
||||
const gid = gestionActual?.id;
|
||||
const existingM = existing.filter((r) => r.tipo === 'mensualizado');
|
||||
const existingE = existing.filter((r) => r.tipo === 'eventual');
|
||||
|
||||
const historialM = existingM.filter((r) => r.gestionId && r.gestionId !== gid);
|
||||
const historialE = existingE.filter((r) => !rowBelongsToGestion(r, gestionActual, 'fecha'));
|
||||
|
||||
let actualesM;
|
||||
let actualesE;
|
||||
|
||||
if (isAdmin) {
|
||||
actualesM = (incomingMensual || []).map((r) => ({ ...stripMeta(r), gestionId: r.gestionId || gid }));
|
||||
actualesE = (incomingEventual || []).map((r) => ({ ...stripMeta(r), gestionId: gid }));
|
||||
} else {
|
||||
const unlockedM = (incomingMensual || []).filter((r) => !r.gestionId || r.gestionId === gid);
|
||||
const lockedE = existingE.filter(
|
||||
(r) => rowBelongsToGestion(r, gestionActual, 'fecha') && isFechaEnPeriodoCerrado(r.fecha)
|
||||
);
|
||||
const unlockedE = (incomingEventual || []).filter((r) => !isFechaEnPeriodoCerrado(r.fecha));
|
||||
actualesM = unlockedM.map((r) => ({ ...stripMeta(r), gestionId: gid }));
|
||||
actualesE = [
|
||||
...lockedE.map(stripMeta),
|
||||
...unlockedE.map((r) => ({ ...stripMeta(r), gestionId: gid })),
|
||||
];
|
||||
}
|
||||
|
||||
return {
|
||||
mensualizados: [...historialM.map(stripMeta), ...actualesM],
|
||||
eventuales: [...historialE.map(stripMeta), ...actualesE],
|
||||
};
|
||||
}
|
||||
|
||||
/** Etiqueta sugerida: último día del mes actual */
|
||||
export function sugerirCierreMensual() {
|
||||
const today = new Date();
|
||||
const last = new Date(today.getFullYear(), today.getMonth() + 1, 0);
|
||||
return last.toISOString().slice(0, 10);
|
||||
}
|
||||
Reference in New Issue
Block a user