Files
administracionHospital/server/server.js
T
snlavaise 56402563fb Add area assignment to internaciones and permission controls
- Add areaId field to Internacion type and database schema
- Add area selection in NuevoIngreso, EditIngreso, and Internaciones forms
- Filter out 'Fuera de area' from area selection dropdowns
- Add permission controls for users to only edit internaciones in their area
- Add canEdit check to hide 'Editar Ingreso' button in HistoriaClinica
- Fix getInternacionAreaId to use new areaId field first, fallback to cama area
- Add INSERT OR IGNORE to prevent duplicate errors
2026-04-23 17:49:07 -03:00

484 lines
16 KiB
JavaScript

import express from 'express';
import cors from 'cors';
import http from 'http';
import Database from 'better-sqlite3';
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
import { dirname, join } from 'path';
import { fileURLToPath } from 'url';
const __dirname = dirname(fileURLToPath(import.meta.url));
const DB_PATH = join(__dirname, 'data', 'hospital.db');
const DIST_PATH = join(__dirname, '..', 'dist');
if (!existsSync(join(__dirname, 'data'))) {
mkdirSync(join(__dirname, 'data'));
}
const db = new Database(DB_PATH);
try {
db.exec("ALTER TABLE internaciones ADD COLUMN areaId TEXT;");
} catch (e) {
// Ignore errors - column might already exist or other issues
}
db.exec(`
CREATE TABLE IF NOT EXISTS pacientes (
id TEXT PRIMARY KEY,
apellido TEXT NOT NULL,
nombre TEXT NOT NULL,
dni TEXT NOT NULL UNIQUE,
fechaNacimiento TEXT,
sexo TEXT,
telefono TEXT,
email TEXT,
direccion TEXT,
obraSocial TEXT,
nacionalidad TEXT,
medicacionHabitual TEXT,
antecedentes TEXT,
alergias TEXT,
grupoSanguineo TEXT
);
CREATE TABLE IF NOT EXISTS areas (
id TEXT PRIMARY KEY,
nombre TEXT NOT NULL UNIQUE
);
CREATE TABLE IF NOT EXISTS camas (
id TEXT PRIMARY KEY,
numero TEXT NOT NULL,
areaId TEXT,
tipo TEXT DEFAULT 'Estándar',
estado TEXT DEFAULT 'Disponible'
);
CREATE TABLE IF NOT EXISTS internaciones (
id TEXT PRIMARY KEY,
pacienteId TEXT NOT NULL,
camaId TEXT,
areaId TEXT,
fechaIngresoHospital TEXT,
fechaIngresoClinica TEXT,
fechaEgreso TEXT,
diagnosticoIngreso TEXT,
motivoConsulta TEXT,
enfermedadActual TEXT,
antecedentesEnfermedadActual TEXT,
diagnosticoEgreso TEXT,
medicoIngresante TEXT,
motivoEgreso TEXT,
activa INTEGER DEFAULT 1,
apache TEXT,
derivacion TEXT
);
CREATE TABLE IF NOT EXISTS evoluciones (
id TEXT PRIMARY KEY,
internacionId TEXT NOT NULL,
fecha TEXT NOT NULL,
hora TEXT,
medico TEXT,
signosVitales TEXT,
examenFisico TEXT,
novedades TEXT,
comentarios TEXT,
pendientes TEXT
);
CREATE TABLE IF NOT EXISTS laboratorios (
id TEXT PRIMARY KEY,
pacienteId TEXT NOT NULL,
internacionId TEXT,
fecha TEXT NOT NULL,
hora TEXT,
tipo TEXT,
resultados TEXT,
observaciones TEXT,
medicoSolicitante TEXT
);
CREATE TABLE IF NOT EXISTS acidosbase (
id TEXT PRIMARY KEY,
pacienteId TEXT NOT NULL,
internacionId TEXT,
fecha TEXT NOT NULL,
hora TEXT,
ph REAL,
pco2 REAL,
po2 REAL,
hco3 REAL,
be REAL,
sato2 REAL,
lactato REAL,
interpretacion TEXT
);
CREATE TABLE IF NOT EXISTS cultivos (
id TEXT PRIMARY KEY,
pacienteId TEXT NOT NULL,
internacionId TEXT,
fechaToma TEXT,
protocolo TEXT,
fechaResultado TEXT,
tipoMuestra TEXT,
germen TEXT,
sensible TEXT,
resistente TEXT,
estado TEXT DEFAULT 'NAF/Pendiente',
observaciones TEXT
);
CREATE TABLE IF NOT EXISTS estudiosComplementarios (
id TEXT PRIMARY KEY,
pacienteId TEXT NOT NULL,
internacionId TEXT,
fecha TEXT,
tipo TEXT,
resultado TEXT
);
`);
try {
db.exec("ALTER TABLE pacientes ADD COLUMN historiaClinica TEXT");
} catch (e) {
// La columna ya existe
}
try {
db.exec("ALTER TABLE acidosbase ADD COLUMN fio2 REAL");
} catch (e) {
// La columna ya existe
}
try {
db.exec("ALTER TABLE laboratorios ADD COLUMN internacionId TEXT");
} catch (e) {
// La columna ya existe
}
try {
db.exec("ALTER TABLE laboratorios ADD COLUMN hora TEXT");
} catch (e) {
// La columna ya existe
}
try {
const existingTable = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='interconsultas'").get();
if (!existingTable) {
db.exec(`
CREATE TABLE interconsultas (
id TEXT PRIMARY KEY,
pacienteId TEXT NOT NULL,
internacionId TEXT NOT NULL,
fecha TEXT,
servicioInterconsultado TEXT,
motivo TEXT,
respuestaInterconsulta TEXT,
respuestaFecha TEXT
)
`);
console.log('Tabla interconsultas creada');
} else {
try {
db.exec("ALTER TABLE interconsultas ADD COLUMN respuestaFecha TEXT");
} catch (e) {
// Column already exists
}
try {
db.exec("ALTER TABLE interconsultas ADD COLUMN motivo TEXT");
} catch (e) {
// Column already exists
}
}
} catch (e) {
console.log('Error tabla interconsultas:', e.message);
}
try {
const existingATB = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='atb'").get();
if (!existingATB) {
db.exec(`
CREATE TABLE atb (
id TEXT PRIMARY KEY,
pacienteId TEXT NOT NULL,
internacionId TEXT NOT NULL,
antibiotico TEXT,
fechaInicio TEXT,
fechaFinalizacion TEXT
)
`);
console.log('Tabla atb creada');
}
} catch (e) {
console.log('Error tabla atb:', e.message);
}
try {
const existingIndicaciones = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='indicaciones'").get();
if (!existingIndicaciones) {
db.exec(`
CREATE TABLE indicaciones (
id TEXT PRIMARY KEY,
internacionId TEXT NOT NULL,
tipo TEXT NOT NULL,
droga TEXT,
dosis TEXT,
frecuenciaHoras INTEGER,
via TEXT,
descripcion TEXT,
tipoPlan TEXT,
tipoPlan2 TEXT,
cantidadMl INTEGER,
cantidadMl2 INTEGER,
tiempoHoras INTEGER,
estado TEXT DEFAULT 'Activa',
medicoCrea TEXT,
fechaCrea TEXT,
indicacionNoFco TEXT
)
`);
console.log('Tabla indicaciones creada');
} else {
try {
db.exec("ALTER TABLE indicaciones ADD COLUMN tipoPlan2 TEXT");
} catch (e) {
// column may already exist
}
try {
db.exec("ALTER TABLE indicaciones ADD COLUMN cantidadMl2 INTEGER");
} catch (e) {
// column may already exist
}
try {
db.exec("ALTER TABLE indicaciones ADD COLUMN tipoInsulina TEXT");
} catch (e) {
// column may already exist
}
try {
db.exec("ALTER TABLE indicaciones ADD COLUMN unidadesDesayuno INTEGER");
} catch (e) {
// column may already exist
}
try {
db.exec("ALTER TABLE indicaciones ADD COLUMN unidadesAlmuerzo INTEGER");
} catch (e) {
// column may already exist
}
try {
db.exec("ALTER TABLE indicaciones ADD COLUMN unidadesNoche INTEGER");
} catch (e) {
// column may already exist
}
}
} catch (e) {
console.log('Error tabla indicaciones:', e.message);
}
try {
const exists = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='movimientos_indicaciones'").get();
if (!exists) {
db.exec(`
CREATE TABLE movimientos_indicaciones (
id TEXT PRIMARY KEY,
indicacionId TEXT NOT NULL,
internacionId TEXT NOT NULL,
tipo TEXT NOT NULL,
fecha TEXT NOT NULL,
profesional TEXT NOT NULL,
indicacionPrevia TEXT,
indicacionNueva TEXT
)
`);
console.log('Tabla movimientos_indicaciones creada');
}
} catch (e) {
console.log('Tabla movimientos_indicaciones:', e.message);
}
const app = express();
app.use(cors());
app.use(express.json({ limit: '50mb' }));
function rowToObj(row) {
if (!row) return row;
const obj = {};
for (const key in row) {
const val = row[key];
try {
obj[key] = typeof val === 'string' && (val.startsWith('[') || val.startsWith('{')) ? JSON.parse(val) : val;
} catch {
obj[key] = val;
}
}
return obj;
}
function queryAll(sql, params = []) {
const stmt = db.prepare(sql);
const rows = params.length ? stmt.all(...params) : stmt.all();
return rows.map(rowToObj);
}
function queryOne(sql, params = []) {
const stmt = db.prepare(sql);
const row = params.length ? stmt.get(...params) : stmt.get();
return rowToObj(row);
}
function run(sql, params = []) {
const stmt = db.prepare(sql);
return params.length ? stmt.run(...params) : stmt.run();
}
app.get('/api/state', (req, res) => {
const state = {
pacientes: queryAll('SELECT * FROM pacientes'),
areas: queryAll('SELECT * FROM areas'),
camas: queryAll('SELECT * FROM camas'),
internaciones: queryAll('SELECT * FROM internaciones').map(i => ({ ...i, activa: !!i.activa })),
evoluciones: queryAll('SELECT * FROM evoluciones').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: queryAll('SELECT * FROM laboratorios').map(l => ({ ...l, resultados: typeof l.resultados === 'string' ? JSON.parse(l.resultados) : l.resultados })),
acidosBase: queryAll('SELECT * FROM acidosbase'),
cultivos: queryAll('SELECT * FROM cultivos'),
estudiosComplementarios: queryAll('SELECT * FROM estudiosComplementarios'),
interconsultas: queryAll('SELECT * FROM interconsultas').map(ic => ({ ...ic, realizada: !!ic.realizada })),
atb: queryAll('SELECT * FROM atb'),
indicaciones: queryAll('SELECT * FROM indicaciones'),
movimientosIndicaciones: queryAll('SELECT * FROM movimientos_indicaciones'),
vistaActual: 'dashboard',
currentInternacionId: null
};
res.json(state);
});
app.put('/api/state', (req, res) => {
const state = req.body;
run('DELETE FROM evoluciones');
run('DELETE FROM acidosbase');
run('DELETE FROM cultivos');
run('DELETE FROM laboratorios');
run('DELETE FROM internaciones');
run('DELETE FROM camas');
run('DELETE FROM areas');
run('DELETE FROM pacientes');
run('DELETE FROM estudiosComplementarios');
if (state.pacientes?.length) {
const stmt = db.prepare('INSERT INTO pacientes (id, apellido, nombre, dni, fechaNacimiento, sexo, telefono, email, direccion, obraSocial, nacionalidad, medicacionHabitual, antecedentes, alergias, grupoSanguineo, historiaClinica) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)');
for (const p of state.pacientes) {
stmt.run(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) {
const stmt = db.prepare('INSERT INTO areas (id, nombre) VALUES (?, ?)');
for (const a of state.areas) {
stmt.run(a.id, a.nombre);
}
}
if (state.camas?.length) {
const stmt = db.prepare('INSERT OR IGNORE INTO camas (id, numero, areaId, tipo, estado) VALUES (?, ?, ?, ?, ?)');
for (const c of state.camas) {
stmt.run(c.id, c.numero, c.areaId, c.tipo, c.estado);
}
}
if (state.internaciones?.length) {
const stmt = db.prepare('INSERT OR IGNORE INTO internaciones (id, pacienteId, camaId, areaId, fechaIngresoHospital, fechaIngresoClinica, fechaEgreso, diagnosticoIngreso, motivoConsulta, enfermedadActual, antecedentesEnfermedadActual, diagnosticoEgreso, medicoIngresante, motivoEgreso, activa, apache, derivacion) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)');
for (const i of state.internaciones) {
stmt.run(i.id, i.pacienteId, i.camaId, i.areaId || null, 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) {
const stmt = db.prepare('INSERT OR IGNORE INTO evoluciones (id, internacionId, fecha, hora, medico, signosVitales, examenFisico, novedades, comentarios, pendientes) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)');
for (const e of state.evoluciones) {
stmt.run(e.id, e.internacionId, e.fecha, e.hora || '', e.medico || '', JSON.stringify(e.signosVitales || {}), JSON.stringify(e.examenFisico || {}), e.novedades || '', e.comentario || '', e.pendientes || '');
}
}
if (state.laboratorios?.length) {
const stmt = db.prepare('INSERT INTO laboratorios (id, pacienteId, fecha, hora, tipo, resultados, observaciones) VALUES (?, ?, ?, ?, ?, ?, ?)');
for (const l of state.laboratorios) {
stmt.run(l.id, l.pacienteId, l.fecha, l.hora || null, l.tipo, JSON.stringify(l.resultados), l.observaciones || null);
}
}
if (state.acidosBase?.length) {
const stmt = db.prepare('INSERT INTO acidosbase (id, pacienteId, fecha, hora, ph, pco2, po2, hco3, be, sato2, lactato, interpretacion, fio2) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)');
for (const a of state.acidosBase) {
stmt.run(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) {
const stmt = db.prepare('INSERT INTO cultivos (id, pacienteId, internacionId, fechaToma, protocolo, tipoMuestra, observaciones, estado, fechaResultado, germen, sensible, resistente) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)');
for (const c of state.cultivos) {
stmt.run(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) {
const stmt = db.prepare('INSERT INTO estudiosComplementarios (id, pacienteId, internacionId, fecha, tipo, resultado) VALUES (?, ?, ?, ?, ?, ?)');
for (const e of state.estudiosComplementarios) {
stmt.run(e.id, e.pacienteId, e.internacionId || null, e.fecha, e.tipo, e.resultado);
}
}
run('DELETE FROM interconsultas');
if (state.interconsultas?.length) {
const stmt = db.prepare('INSERT INTO interconsultas (id, pacienteId, internacionId, fecha, servicioInterconsultado, motivo, respuestaInterconsulta, respuestaFecha) VALUES (?, ?, ?, ?, ?, ?, ?, ?)');
for (const ic of state.interconsultas) {
stmt.run(ic.id, ic.pacienteId, ic.internacionId, ic.fecha, ic.servicioInterconsultado, ic.motivo || null, ic.respuestaInterconsulta || null, ic.respuestaFecha || null);
}
}
run('DELETE FROM atb');
if (state.atb?.length) {
const stmt = db.prepare('INSERT INTO atb (id, pacienteId, internacionId, antibiotico, fechaInicio, fechaFinalizacion) VALUES (?, ?, ?, ?, ?, ?)');
for (const atb of state.atb) {
stmt.run(atb.id, atb.pacienteId, atb.internacionId, atb.antibiotico, atb.fechaInicio, atb.fechaFinalizacion || null);
}
}
run('DELETE FROM indicaciones');
if (state.indicaciones?.length) {
const stmt = db.prepare('INSERT INTO indicaciones (id, internacionId, tipo, droga, dosis, frecuenciaHoras, via, tipoPlan, tipoPlan2, cantidadMl, cantidadMl2, tiempoHoras, estado, medicoCrea, fechaCrea, tipoInsulina, unidadesDesayuno, unidadesAlmuerzo, unidadesNoche, indicacionNoFco) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)');
for (const ind of state.indicaciones) {
stmt.run(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);
}
}
run('DELETE FROM movimientos_indicaciones');
if (state.movimientosIndicaciones?.length) {
const stmt = db.prepare('INSERT INTO movimientos_indicaciones (id, indicacionId, internacionId, tipo, fecha, profesional, indicacionPrevia, indicacionNueva) VALUES (?, ?, ?, ?, ?, ?, ?, ?)');
for (const mov of state.movimientosIndicaciones) {
stmt.run(mov.id, mov.indicacionId, mov.internacionId, mov.tipo, mov.fecha, mov.profesional, mov.indicacionPrevia || null, mov.indicacionNueva || null);
}
}
res.json({ ok: true });
});
if (existsSync(DIST_PATH)) {
app.use(express.static(DIST_PATH));
}
app.get('*', (req, res) => {
res.sendFile(join(DIST_PATH, 'index.html'));
});
const PORT = process.env.PORT || 4000;
const HOST = process.env.HOST || '0.0.0.0';
app.listen(Number(PORT), HOST, () => {
console.log(`Server running on http://${HOST}:${PORT}`);
});