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, 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, cantidadMl INTEGER, tiempoHoras INTEGER, estado TEXT DEFAULT 'Activa', medicoCrea TEXT, fechaCrea TEXT, medicoSuspende TEXT, fechaSuspension TEXT ) `); console.log('Tabla indicaciones creada'); } } catch (e) { console.log('Error tabla 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'), 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, 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, descripcion, tipoPlan, cantidadMl, tiempoHoras, estado, medicoCrea, fechaCrea, medicoSuspende, fechaSuspension) 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.descripcion || null, ind.tipoPlan || null, ind.cantidadMl || null, ind.tiempoHoras || null, ind.estado, ind.medicoCrea, ind.fechaCrea, ind.medicoSuspende || null, ind.fechaSuspension || 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}`); });