feat: implementar permisos por área de trabajo

- Agregar funciones de verificación de área en useHospitalStore
- canEditInArea, canEditCama, canEditInternacion, canEditPaciente
- Leer datos de tablas individuales en lugar de tabla kv
- Usar sessionStorage para persistir sesión de usuario
- Agregar prop canEdit a componentes de secciones en HC
- Ocultar botones de edición para usuarios sin permisos en el área
- MapaCamas: ocultar botones de edición para camas fuera del área
This commit is contained in:
2026-04-23 02:18:42 -03:00
parent 76e01bfd79
commit e6e1582b25
8 changed files with 411 additions and 118 deletions
+152 -35
View File
@@ -1,6 +1,6 @@
import express from 'express';
import cors from 'cors';
import { getValue, setValue, getUsuarioByDni, getUsuarioById, getAllUsuarios, createUsuario, updateUsuario, deleteUsuario, verifyPassword, hashPassword } from './db.js';
import { openDb, getValue, setValue, getUsuarioByDni, getUsuarioById, getAllUsuarios, createUsuario, updateUsuario, deleteUsuario, verifyPassword, hashPassword, getAllPacientes, getAllAreas, getAllCamas, getAllInternaciones, getAllEvoluciones, getAllLaboratorios, getAllAcidosBase, getAllCultivos, getAllEstudiosComplementarios, getAllInterconsultas, getAllAtb, getAllIndicaciones, getAllMovimientosIndicaciones } from './db.js';
const app = express();
app.use(cors());
@@ -11,9 +11,28 @@ const STORAGE_KEY = 'hospital-data-v1';
app.get('/api/state', async (req, res) => {
try {
const v = await getValue(STORAGE_KEY);
if (!v) return res.json(null);
res.json(JSON.parse(v));
const state = {
pacientes: await getAllPacientes(),
areas: await getAllAreas(),
camas: await getAllCamas(),
internaciones: (await getAllInternaciones()).map(i => ({ ...i, activa: !!i.activa })),
evoluciones: (await getAllEvoluciones()).map(e => ({
...e,
signosVitales: typeof e.signosVitales === 'string' ? JSON.parse(e.signosVitales) : e.signosVitales,
examenFisico: typeof e.examenFisico === 'string' ? JSON.parse(e.examenFisico) : e.examenFisico
})),
laboratorios: (await getAllLaboratorios()).map(l => ({ ...l, resultados: typeof l.resultados === 'string' ? JSON.parse(l.resultados) : l.resultados })),
acidosBase: await getAllAcidosBase(),
cultivos: await getAllCultivos(),
estudiosComplementarios: await getAllEstudiosComplementarios(),
interconsultas: (await getAllInterconsultas()).map(ic => ({ ...ic, realizada: !!ic.realizada })),
atb: await getAllAtb(),
indicaciones: await getAllIndicaciones(),
movimientosIndicaciones: await getAllMovimientosIndicaciones(),
vistaActual: 'dashboard',
currentInternacionId: null
};
res.json(state);
} catch (err) {
console.error(err);
res.status(500).json({ error: 'failed to read state' });
@@ -22,8 +41,135 @@ app.get('/api/state', async (req, res) => {
app.put('/api/state', async (req, res) => {
try {
const body = req.body;
await setValue(STORAGE_KEY, JSON.stringify(body));
const state = req.body;
const db = await openDb();
await db.run('DELETE FROM evoluciones');
await db.run('DELETE FROM acidosbase');
await db.run('DELETE FROM cultivos');
await db.run('DELETE FROM laboratorios');
await db.run('DELETE FROM internaciones');
await db.run('DELETE FROM camas');
await db.run('DELETE FROM areas');
await db.run('DELETE FROM pacientes');
await db.run('DELETE FROM estudiosComplementarios');
await db.run('DELETE FROM interconsultas');
await db.run('DELETE FROM atb');
await db.run('DELETE FROM indicaciones');
await db.run('DELETE FROM movimientos_indicaciones');
if (state.pacientes?.length) {
for (const p of state.pacientes) {
await db.run(
'INSERT INTO pacientes (id, apellido, nombre, dni, fechaNacimiento, sexo, telefono, email, direccion, obraSocial, nacionalidad, medicacionHabitual, antecedentes, alergias, grupoSanguineo, historiaClinica) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
[p.id, p.apellido, p.nombre, p.dni, p.fechaNacimiento || null, p.sexo || null, p.telefono || null, p.email || null, p.direccion || null, p.obraSocial || null, p.nacionalidad || null, p.medicacionHabitual || null, p.antecedentes || null, p.alergias || null, p.grupoSanguineo || null, p.historiaClinica || null]
);
}
}
if (state.areas?.length) {
for (const a of state.areas) {
await db.run('INSERT INTO areas (id, nombre) VALUES (?, ?)', [a.id, a.nombre]);
}
}
if (state.camas?.length) {
for (const c of state.camas) {
await db.run('INSERT INTO camas (id, numero, areaId, tipo, estado) VALUES (?, ?, ?, ?, ?)', [c.id, c.numero, c.areaId, c.tipo, c.estado]);
}
}
if (state.internaciones?.length) {
for (const i of state.internaciones) {
await db.run(
'INSERT INTO internaciones (id, pacienteId, camaId, fechaIngresoHospital, fechaIngresoClinica, fechaEgreso, diagnosticoIngreso, motivoConsulta, enfermedadActual, antecedentesEnfermedadActual, diagnosticoEgreso, medicoIngresante, motivoEgreso, activa, apache, derivacion) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
[i.id, i.pacienteId, i.camaId, i.fechaIngresoHospital || null, i.fechaIngresoClinica || null, i.fechaEgreso || null, i.diagnosticoIngreso || null, i.motivoConsulta || null, i.enfermedadActual || null, i.antecedentesEnfermedadActual || null, i.diagnosticoEgreso || null, i.medicoIngresante || null, i.motivoEgreso || null, i.activa ? 1 : 0, i.apache || null, i.derivacion || null]
);
}
}
if (state.evoluciones?.length) {
for (const e of state.evoluciones) {
await db.run(
'INSERT INTO evoluciones (id, internacionId, fecha, hora, medico, signosVitales, examenFisico, novedades, comentarios, pendientes) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
[e.id, e.internacionId, e.fecha, e.hora || '', e.medico || '', JSON.stringify(e.signosVitales || {}), JSON.stringify(e.examenFisico || {}), e.novedades || '', e.comentarios || '', e.pendientes || '']
);
}
}
if (state.laboratorios?.length) {
for (const l of state.laboratorios) {
await db.run(
'INSERT INTO laboratorios (id, pacienteId, fecha, hora, tipo, resultados, observaciones) VALUES (?, ?, ?, ?, ?, ?, ?)',
[l.id, l.pacienteId, l.fecha, l.hora || null, l.tipo, JSON.stringify(l.resultados), l.observaciones || null]
);
}
}
if (state.acidosBase?.length) {
for (const a of state.acidosBase) {
await db.run(
'INSERT INTO acidosbase (id, pacienteId, fecha, hora, ph, pco2, po2, hco3, be, sato2, lactato, interpretacion, fio2) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
[a.id, a.pacienteId, a.fecha, a.hora, a.ph, a.pco2, a.po2, a.hco3, a.be, a.sato2, a.lactato || null, a.interpretacion || null, a.fio2 || null]
);
}
}
if (state.cultivos?.length) {
for (const c of state.cultivos) {
await db.run(
'INSERT INTO cultivos (id, pacienteId, internacionId, fechaToma, protocolo, tipoMuestra, observaciones, estado, fechaResultado, germen, sensible, resistente) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
[c.id, c.pacienteId, c.internacionId, c.fechaToma, c.protocolo, c.tipoMuestra, c.observaciones, c.estado, c.fechaResultado, c.germen, c.sensible, c.resistente]
);
}
}
if (state.estudiosComplementarios?.length) {
for (const e of state.estudiosComplementarios) {
await db.run(
'INSERT INTO estudiosComplementarios (id, pacienteId, internacionId, fecha, tipo, resultado) VALUES (?, ?, ?, ?, ?, ?)',
[e.id, e.pacienteId, e.internacionId || null, e.fecha, e.tipo, e.resultado]
);
}
}
if (state.interconsultas?.length) {
for (const ic of state.interconsultas) {
await db.run(
'INSERT INTO interconsultas (id, pacienteId, internacionId, fecha, servicioInterconsultado, motivo, respuestaInterconsulta, respuestaFecha) VALUES (?, ?, ?, ?, ?, ?, ?, ?)',
[ic.id, ic.pacienteId, ic.internacionId, ic.fecha, ic.servicioInterconsultado, ic.motivo || null, ic.respuestaInterconsulta || null, ic.respuestaFecha || null]
);
}
}
if (state.atb?.length) {
for (const atb of state.atb) {
await db.run(
'INSERT INTO atb (id, pacienteId, internacionId, antibiotico, fechaInicio, fechaFinalizacion) VALUES (?, ?, ?, ?, ?, ?)',
[atb.id, atb.pacienteId, atb.internacionId, atb.antibiotico, atb.fechaInicio, atb.fechaFinalizacion || null]
);
}
}
if (state.indicaciones?.length) {
for (const ind of state.indicaciones) {
await db.run(
'INSERT INTO indicaciones (id, internacionId, tipo, droga, dosis, frecuenciaHoras, via, tipoPlan, tipoPlan2, cantidadMl, cantidadMl2, tiempoHoras, estado, medicoCrea, fechaCrea, tipoInsulina, unidadesDesayuno, unidadesAlmuerzo, unidadesNoche, indicacionNoFco) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
[ind.id, ind.internacionId, ind.tipo, ind.droga || null, ind.dosis || null, ind.frecuenciaHoras || null, ind.via || null, ind.tipoPlan || null, ind.tipoPlan2 || null, ind.cantidadMl || null, ind.cantidadMl2 || null, ind.tiempoHoras || null, ind.estado, ind.medicoCrea, ind.fechaCrea, ind.tipoInsulina || null, ind.unidadesDesayuno || null, ind.unidadesAlmuerzo || null, ind.unidadesNoche || null, ind.indicacionNoFco || null]
);
}
}
if (state.movimientosIndicaciones?.length) {
for (const mov of state.movimientosIndicaciones) {
await db.run(
'INSERT INTO movimientos_indicaciones (id, indicacionId, internacionId, tipo, fecha, profesional, indicacionPrevia, indicacionNueva) VALUES (?, ?, ?, ?, ?, ?, ?, ?)',
[mov.id, mov.indicacionId, mov.internacionId, mov.tipo, mov.fecha, mov.profesional, mov.indicacionPrevia || null, mov.indicacionNueva || null]
);
}
}
await db.close();
res.json({ ok: true });
} catch (err) {
console.error(err);
@@ -35,35 +181,6 @@ app.listen(PORT, () => {
console.log(`API server listening on http://localhost:${PORT}`);
});
// Crear usuario admin inicial si no existe
async function initAdminUser() {
try {
const adminExists = await getUsuarioByDni('12345678');
if (!adminExists) {
const passwordHash = await hashPassword('admin123');
const adminUser = {
id: generateUUID(),
apellido: 'Admin',
nombre: 'Sistema',
dni: '12345678',
fechaNacimiento: '1970-01-01',
email: 'admin@hospital.gob.ar',
rol: 'admin',
matriculaProfesional: null,
passwordHash,
areaId: null,
fechaCreacion: new Date().toISOString().split('T')[0]
};
await createUsuario(adminUser);
console.log('Usuario admin creado: DNI 12345678, Password admin123');
}
} catch (err) {
console.error('Error creating admin user:', err);
}
}
initAdminUser();
// === AUTENTICACIÓN ===
function generateUUID() {