335 lines
11 KiB
JavaScript
335 lines
11 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);
|
|
|
|
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,
|
|
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,
|
|
respuestaInterconsulta TEXT
|
|
)
|
|
`);
|
|
console.log('Tabla interconsultas creada');
|
|
}
|
|
} catch (e) {
|
|
console.log('Error tabla interconsultas:', 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 })),
|
|
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 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 INTO internaciones (id, pacienteId, camaId, 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.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 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, respuestaInterconsulta) VALUES (?, ?, ?, ?, ?, ?, ?)');
|
|
for (const ic of state.interconsultas) {
|
|
stmt.run(ic.id, ic.pacienteId, ic.internacionId, ic.fecha, ic.servicioInterconsultado, ic.respuestaInterconsulta || 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}`);
|
|
});
|