Fix: solucionar error SQLITE_BUSY y mejorar backend
- Migrar de sqlite3 a better-sqlite3 con conexión persistente - Configurar WAL mode, busy_timeout y PRAGMAs optimizados - Implementar transacciones para operaciones bulk - Corregir error initialLoadComplete en useHospitalStore.ts - Agregar tablas usuarios y kv faltantes - Actualizar .gitignore para archivos WAL de SQLite
This commit is contained in:
@@ -3,6 +3,9 @@ dist
|
|||||||
server/node_modules
|
server/node_modules
|
||||||
server/*.log
|
server/*.log
|
||||||
*.db
|
*.db
|
||||||
|
*.db-shm
|
||||||
|
*.db-wal
|
||||||
|
*.db-journal
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.env
|
.env
|
||||||
.env.local
|
.env.local
|
||||||
Generated
+15
@@ -36,6 +36,7 @@
|
|||||||
"@radix-ui/react-toggle-group": "^1.1.11",
|
"@radix-ui/react-toggle-group": "^1.1.11",
|
||||||
"@radix-ui/react-tooltip": "^1.2.8",
|
"@radix-ui/react-tooltip": "^1.2.8",
|
||||||
"bcryptjs": "^3.0.3",
|
"bcryptjs": "^3.0.3",
|
||||||
|
"better-sqlite3": "^12.9.0",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"cmdk": "^1.1.1",
|
"cmdk": "^1.1.1",
|
||||||
@@ -5509,6 +5510,20 @@
|
|||||||
"bcrypt": "bin/bcrypt"
|
"bcrypt": "bin/bcrypt"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/better-sqlite3": {
|
||||||
|
"version": "12.9.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-12.9.0.tgz",
|
||||||
|
"integrity": "sha512-wqUv4Gm3toFpHDQmaKD4QhZm3g1DjUBI0yzS4UBl6lElUmXFYdTQmmEDpAFa5o8FiFiymURypEnfVHzILKaxqQ==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"bindings": "^1.5.0",
|
||||||
|
"prebuild-install": "^7.1.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "20.x || 22.x || 23.x || 24.x || 25.x"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/binary-extensions": {
|
"node_modules/binary-extensions": {
|
||||||
"version": "2.3.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
|
||||||
|
|||||||
@@ -38,6 +38,7 @@
|
|||||||
"@radix-ui/react-toggle-group": "^1.1.11",
|
"@radix-ui/react-toggle-group": "^1.1.11",
|
||||||
"@radix-ui/react-tooltip": "^1.2.8",
|
"@radix-ui/react-tooltip": "^1.2.8",
|
||||||
"bcryptjs": "^3.0.3",
|
"bcryptjs": "^3.0.3",
|
||||||
|
"better-sqlite3": "^12.9.0",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"cmdk": "^1.1.1",
|
"cmdk": "^1.1.1",
|
||||||
|
|||||||
Binary file not shown.
+100
-136
@@ -1,83 +1,79 @@
|
|||||||
import sqlite3 from 'sqlite3';
|
import Database from 'better-sqlite3';
|
||||||
import { open } from 'sqlite';
|
|
||||||
import { dirname } from 'path';
|
import { dirname } from 'path';
|
||||||
import { fileURLToPath } from 'url';
|
import { fileURLToPath } from 'url';
|
||||||
import bcrypt from 'bcryptjs';
|
import bcrypt from 'bcryptjs';
|
||||||
|
import { existsSync, mkdirSync } from 'fs';
|
||||||
|
|
||||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||||
const DB_PATH = __dirname + '/data/hospital.db';
|
const DB_PATH = __dirname + '/data/hospital.db';
|
||||||
|
|
||||||
export async function openDb() {
|
// Ensure data directory exists
|
||||||
const db = await open({
|
if (!existsSync(__dirname + '/data')) {
|
||||||
filename: DB_PATH,
|
mkdirSync(__dirname + '/data', { recursive: true });
|
||||||
driver: sqlite3.Database,
|
}
|
||||||
});
|
|
||||||
|
|
||||||
await db.exec(`
|
// Create persistent database connection with WAL mode and proper settings
|
||||||
CREATE TABLE IF NOT EXISTS kv (
|
const db = new Database(DB_PATH);
|
||||||
key TEXT PRIMARY KEY,
|
|
||||||
value TEXT
|
|
||||||
);
|
|
||||||
`);
|
|
||||||
|
|
||||||
await db.exec(`
|
// Enable WAL mode for better concurrency
|
||||||
CREATE TABLE IF NOT EXISTS usuarios (
|
db.pragma('journal_mode = WAL');
|
||||||
id TEXT PRIMARY KEY,
|
|
||||||
apellido TEXT NOT NULL,
|
|
||||||
nombre TEXT NOT NULL,
|
|
||||||
dni TEXT UNIQUE NOT NULL,
|
|
||||||
fechaNacimiento TEXT NOT NULL,
|
|
||||||
email TEXT NOT NULL,
|
|
||||||
rol TEXT NOT NULL CHECK(rol IN ('admin', 'medico', 'enfermero')),
|
|
||||||
matriculaProfesional TEXT,
|
|
||||||
passwordHash TEXT NOT NULL,
|
|
||||||
areaId TEXT,
|
|
||||||
fechaCreacion TEXT NOT NULL
|
|
||||||
);
|
|
||||||
`);
|
|
||||||
|
|
||||||
|
// Set busy timeout to 5000ms (5 seconds)
|
||||||
|
db.pragma('busy_timeout = 5000');
|
||||||
|
|
||||||
|
// Additional performance and concurrency settings
|
||||||
|
db.pragma('synchronous = NORMAL');
|
||||||
|
db.pragma('cache_size = 1000');
|
||||||
|
db.pragma('temp_store = MEMORY');
|
||||||
|
db.pragma('mmap_size = 268435456'); // 256MB
|
||||||
|
|
||||||
|
export function getDb() {
|
||||||
return db;
|
return db;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getValue(key) {
|
function rowToObj(row) {
|
||||||
const db = await openDb();
|
if (!row) return row;
|
||||||
const row = await db.get('SELECT value FROM kv WHERE key = ?', key);
|
const obj = {};
|
||||||
await db.close();
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getValue(key) {
|
||||||
|
const row = db.prepare('SELECT value FROM kv WHERE key = ?').get(key);
|
||||||
return row?.value ?? null;
|
return row?.value ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function setValue(key, value) {
|
export function setValue(key, value) {
|
||||||
const db = await openDb();
|
const stmt = db.prepare('INSERT INTO kv (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value=excluded.value');
|
||||||
await db.run('INSERT INTO kv (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value=excluded.value', key, value);
|
stmt.run(key, value);
|
||||||
await db.close();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getUsuarioByDni(dni) {
|
export function getUsuarioByDni(dni) {
|
||||||
const db = await openDb();
|
const row = db.prepare('SELECT * FROM usuarios WHERE dni = ?').get(dni);
|
||||||
const row = await db.get('SELECT * FROM usuarios WHERE dni = ?', dni);
|
|
||||||
await db.close();
|
|
||||||
return row || null;
|
return row || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getUsuarioById(id) {
|
export function getUsuarioById(id) {
|
||||||
const db = await openDb();
|
const row = db.prepare('SELECT * FROM usuarios WHERE id = ?').get(id);
|
||||||
const row = await db.get('SELECT * FROM usuarios WHERE id = ?', id);
|
|
||||||
await db.close();
|
|
||||||
return row || null;
|
return row || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getAllUsuarios() {
|
export function getAllUsuarios() {
|
||||||
const db = await openDb();
|
const rows = db.prepare('SELECT * FROM usuarios ORDER BY apellido, nombre').all();
|
||||||
const rows = await db.all('SELECT * FROM usuarios ORDER BY apellido, nombre');
|
|
||||||
await db.close();
|
|
||||||
return rows;
|
return rows;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createUsuario(usuario) {
|
export function createUsuario(usuario) {
|
||||||
const db = await openDb();
|
const stmt = db.prepare(`INSERT INTO usuarios (id, apellido, nombre, dni, fechaNacimiento, email, rol, matriculaProfesional, passwordHash, areaId, fechaCreacion)
|
||||||
await db.run(
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
|
||||||
`INSERT INTO usuarios (id, apellido, nombre, dni, fechaNacimiento, email, rol, matriculaProfesional, passwordHash, areaId, fechaCreacion)
|
stmt.run(
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
||||||
usuario.id,
|
usuario.id,
|
||||||
usuario.apellido,
|
usuario.apellido,
|
||||||
usuario.nombre,
|
usuario.nombre,
|
||||||
@@ -90,11 +86,9 @@ export async function createUsuario(usuario) {
|
|||||||
usuario.areaId || null,
|
usuario.areaId || null,
|
||||||
usuario.fechaCreacion
|
usuario.fechaCreacion
|
||||||
);
|
);
|
||||||
await db.close();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateUsuario(id, datos) {
|
export function updateUsuario(id, datos) {
|
||||||
const db = await openDb();
|
|
||||||
const fields = [];
|
const fields = [];
|
||||||
const values = [];
|
const values = [];
|
||||||
|
|
||||||
@@ -109,53 +103,40 @@ export async function updateUsuario(id, datos) {
|
|||||||
|
|
||||||
if (fields.length > 0) {
|
if (fields.length > 0) {
|
||||||
values.push(id);
|
values.push(id);
|
||||||
await db.run(`UPDATE usuarios SET ${fields.join(', ')} WHERE id = ?`, values);
|
db.prepare(`UPDATE usuarios SET ${fields.join(', ')} WHERE id = ?`).run(values);
|
||||||
}
|
}
|
||||||
await db.close();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteUsuario(id) {
|
export function deleteUsuario(id) {
|
||||||
const db = await openDb();
|
db.prepare('DELETE FROM usuarios WHERE id = ?').run(id);
|
||||||
await db.run('DELETE FROM usuarios WHERE id = ?', id);
|
|
||||||
await db.close();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function verifyPassword(dni, password) {
|
export function verifyPassword(dni, password) {
|
||||||
const db = await openDb();
|
const row = db.prepare('SELECT passwordHash FROM usuarios WHERE dni = ?').get(dni);
|
||||||
const row = await db.get('SELECT passwordHash FROM usuarios WHERE dni = ?', dni);
|
|
||||||
await db.close();
|
|
||||||
|
|
||||||
if (!row) return false;
|
if (!row) return false;
|
||||||
return bcrypt.compareSync(password, row.passwordHash);
|
return bcrypt.compareSync(password, row.passwordHash);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function hashPassword(password) {
|
export function hashPassword(password) {
|
||||||
return bcrypt.hashSync(password, 10);
|
return bcrypt.hashSync(password, 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getAllPacientes() {
|
export function getAllPacientes() {
|
||||||
const db = await openDb();
|
const rows = db.prepare('SELECT * FROM pacientes').all();
|
||||||
const rows = await db.all('SELECT * FROM pacientes');
|
|
||||||
await db.close();
|
|
||||||
return rows;
|
return rows;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getAllAreas() {
|
export function getAllAreas() {
|
||||||
const db = await openDb();
|
const rows = db.prepare('SELECT * FROM areas').all();
|
||||||
const rows = await db.all('SELECT * FROM areas');
|
|
||||||
await db.close();
|
|
||||||
return rows;
|
return rows;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getAllCamas() {
|
export function getAllCamas() {
|
||||||
const db = await openDb();
|
const rows = db.prepare('SELECT * FROM camas').all();
|
||||||
const rows = await db.all('SELECT * FROM camas');
|
|
||||||
await db.close();
|
|
||||||
return rows;
|
return rows;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateCama(id, updates) {
|
export function updateCama(id, updates) {
|
||||||
const db = await openDb();
|
|
||||||
const fields = [];
|
const fields = [];
|
||||||
const values = [];
|
const values = [];
|
||||||
if (updates.estado !== undefined) {
|
if (updates.estado !== undefined) {
|
||||||
@@ -172,77 +153,60 @@ export async function updateCama(id, updates) {
|
|||||||
}
|
}
|
||||||
if (fields.length > 0) {
|
if (fields.length > 0) {
|
||||||
values.push(id);
|
values.push(id);
|
||||||
await db.run(`UPDATE camas SET ${fields.join(', ')} WHERE id = ?`, values);
|
db.prepare(`UPDATE camas SET ${fields.join(', ')} WHERE id = ?`).run(values);
|
||||||
}
|
}
|
||||||
await db.close();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getAllInternaciones() {
|
export function getAllInternaciones() {
|
||||||
const db = await openDb();
|
const rows = db.prepare('SELECT * FROM internaciones').all();
|
||||||
const rows = await db.all('SELECT * FROM internaciones');
|
return rows.map(i => ({ ...i, activa: !!i.activa }));
|
||||||
await db.close();
|
}
|
||||||
|
|
||||||
|
export function getAllEvoluciones() {
|
||||||
|
const rows = db.prepare('SELECT * FROM evoluciones').all();
|
||||||
|
return rows.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
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getAllLaboratorios() {
|
||||||
|
const rows = db.prepare('SELECT * FROM laboratorios').all();
|
||||||
|
return rows.map(l => ({ ...l, resultados: typeof l.resultados === 'string' ? JSON.parse(l.resultados) : l.resultados }));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getAllAcidosBase() {
|
||||||
|
const rows = db.prepare('SELECT * FROM acidosbase').all();
|
||||||
return rows;
|
return rows;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getAllEvoluciones() {
|
export function getAllCultivos() {
|
||||||
const db = await openDb();
|
const rows = db.prepare('SELECT * FROM cultivos').all();
|
||||||
const rows = await db.all('SELECT * FROM evoluciones');
|
|
||||||
await db.close();
|
|
||||||
return rows;
|
return rows;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getAllLaboratorios() {
|
export function getAllEstudiosComplementarios() {
|
||||||
const db = await openDb();
|
const rows = db.prepare('SELECT * FROM estudiosComplementarios').all();
|
||||||
const rows = await db.all('SELECT * FROM laboratorios');
|
|
||||||
await db.close();
|
|
||||||
return rows;
|
return rows;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getAllAcidosBase() {
|
export function getAllInterconsultas() {
|
||||||
const db = await openDb();
|
const rows = db.prepare('SELECT * FROM interconsultas').all();
|
||||||
const rows = await db.all('SELECT * FROM acidosbase');
|
return rows.map(ic => ({ ...ic, realizada: !!ic.realizada }));
|
||||||
await db.close();
|
}
|
||||||
|
|
||||||
|
export function getAllAtb() {
|
||||||
|
const rows = db.prepare('SELECT * FROM atb').all();
|
||||||
return rows;
|
return rows;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getAllCultivos() {
|
export function getAllIndicaciones() {
|
||||||
const db = await openDb();
|
const rows = db.prepare('SELECT * FROM indicaciones').all();
|
||||||
const rows = await db.all('SELECT * FROM cultivos');
|
|
||||||
await db.close();
|
|
||||||
return rows;
|
return rows;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getAllEstudiosComplementarios() {
|
export function getAllMovimientosIndicaciones() {
|
||||||
const db = await openDb();
|
const rows = db.prepare('SELECT * FROM movimientos_indicaciones').all();
|
||||||
const rows = await db.all('SELECT * FROM estudiosComplementarios');
|
|
||||||
await db.close();
|
|
||||||
return rows;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getAllInterconsultas() {
|
|
||||||
const db = await openDb();
|
|
||||||
const rows = await db.all('SELECT * FROM interconsultas');
|
|
||||||
await db.close();
|
|
||||||
return rows;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getAllAtb() {
|
|
||||||
const db = await openDb();
|
|
||||||
const rows = await db.all('SELECT * FROM atb');
|
|
||||||
await db.close();
|
|
||||||
return rows;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getAllIndicaciones() {
|
|
||||||
const db = await openDb();
|
|
||||||
const rows = await db.all('SELECT * FROM indicaciones');
|
|
||||||
await db.close();
|
|
||||||
return rows;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getAllMovimientosIndicaciones() {
|
|
||||||
const db = await openDb();
|
|
||||||
const rows = await db.all('SELECT * FROM movimientos_indicaciones');
|
|
||||||
await db.close();
|
|
||||||
return rows;
|
return rows;
|
||||||
}
|
}
|
||||||
|
|||||||
+163
-123
@@ -1,6 +1,6 @@
|
|||||||
import express from 'express';
|
import express from 'express';
|
||||||
import cors from 'cors';
|
import cors from 'cors';
|
||||||
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';
|
import { getDb, 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();
|
const app = express();
|
||||||
app.use(cors());
|
app.use(cors());
|
||||||
@@ -9,26 +9,22 @@ app.use(express.json({ limit: '5mb' }));
|
|||||||
const PORT = process.env.PORT || 4001;
|
const PORT = process.env.PORT || 4001;
|
||||||
const STORAGE_KEY = 'hospital-data-v1';
|
const STORAGE_KEY = 'hospital-data-v1';
|
||||||
|
|
||||||
app.get('/api/state', async (req, res) => {
|
app.get('/api/state', (req, res) => {
|
||||||
try {
|
try {
|
||||||
const state = {
|
const state = {
|
||||||
pacientes: await getAllPacientes(),
|
pacientes: getAllPacientes(),
|
||||||
areas: await getAllAreas(),
|
areas: getAllAreas(),
|
||||||
camas: await getAllCamas(),
|
camas: getAllCamas(),
|
||||||
internaciones: (await getAllInternaciones()).map(i => ({ ...i, activa: !!i.activa })),
|
internaciones: getAllInternaciones(),
|
||||||
evoluciones: (await getAllEvoluciones()).map(e => ({
|
evoluciones: getAllEvoluciones(),
|
||||||
...e,
|
laboratorios: getAllLaboratorios(),
|
||||||
signosVitales: typeof e.signosVitales === 'string' ? JSON.parse(e.signosVitales) : e.signosVitales,
|
acidosBase: getAllAcidosBase(),
|
||||||
examenFisico: typeof e.examenFisico === 'string' ? JSON.parse(e.examenFisico) : e.examenFisico
|
cultivos: getAllCultivos(),
|
||||||
})),
|
estudiosComplementarios: getAllEstudiosComplementarios(),
|
||||||
laboratorios: (await getAllLaboratorios()).map(l => ({ ...l, resultados: typeof l.resultados === 'string' ? JSON.parse(l.resultados) : l.resultados })),
|
interconsultas: getAllInterconsultas(),
|
||||||
acidosBase: await getAllAcidosBase(),
|
atb: getAllAtb(),
|
||||||
cultivos: await getAllCultivos(),
|
indicaciones: getAllIndicaciones(),
|
||||||
estudiosComplementarios: await getAllEstudiosComplementarios(),
|
movimientosIndicaciones: getAllMovimientosIndicaciones(),
|
||||||
interconsultas: (await getAllInterconsultas()).map(ic => ({ ...ic, realizada: !!ic.realizada })),
|
|
||||||
atb: await getAllAtb(),
|
|
||||||
indicaciones: await getAllIndicaciones(),
|
|
||||||
movimientosIndicaciones: await getAllMovimientosIndicaciones(),
|
|
||||||
vistaActual: 'dashboard',
|
vistaActual: 'dashboard',
|
||||||
currentInternacionId: null
|
currentInternacionId: null
|
||||||
};
|
};
|
||||||
@@ -39,137 +35,119 @@ app.get('/api/state', async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
app.put('/api/state', async (req, res) => {
|
app.put('/api/state', (req, res) => {
|
||||||
try {
|
try {
|
||||||
const state = req.body;
|
const state = req.body;
|
||||||
const db = await openDb();
|
const db = getDb();
|
||||||
|
|
||||||
await db.run('DELETE FROM evoluciones');
|
// Use transaction for atomicity and better performance
|
||||||
await db.run('DELETE FROM acidosbase');
|
const result = db.transaction(() => {
|
||||||
await db.run('DELETE FROM cultivos');
|
db.prepare('DELETE FROM evoluciones').run();
|
||||||
await db.run('DELETE FROM laboratorios');
|
db.prepare('DELETE FROM acidosbase').run();
|
||||||
await db.run('DELETE FROM internaciones');
|
db.prepare('DELETE FROM cultivos').run();
|
||||||
await db.run('DELETE FROM camas');
|
db.prepare('DELETE FROM laboratorios').run();
|
||||||
await db.run('DELETE FROM areas');
|
db.prepare('DELETE FROM internaciones').run();
|
||||||
await db.run('DELETE FROM pacientes');
|
db.prepare('DELETE FROM camas').run();
|
||||||
await db.run('DELETE FROM estudiosComplementarios');
|
db.prepare('DELETE FROM areas').run();
|
||||||
await db.run('DELETE FROM interconsultas');
|
db.prepare('DELETE FROM pacientes').run();
|
||||||
await db.run('DELETE FROM atb');
|
db.prepare('DELETE FROM estudiosComplementarios').run();
|
||||||
await db.run('DELETE FROM indicaciones');
|
db.prepare('DELETE FROM interconsultas').run();
|
||||||
await db.run('DELETE FROM movimientos_indicaciones');
|
db.prepare('DELETE FROM atb').run();
|
||||||
|
db.prepare('DELETE FROM indicaciones').run();
|
||||||
|
db.prepare('DELETE FROM movimientos_indicaciones').run();
|
||||||
|
|
||||||
if (state.pacientes?.length) {
|
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) {
|
for (const p of state.pacientes) {
|
||||||
await db.run(
|
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);
|
||||||
'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) {
|
if (state.areas?.length) {
|
||||||
|
const stmt = db.prepare('INSERT INTO areas (id, nombre) VALUES (?, ?)');
|
||||||
for (const a of state.areas) {
|
for (const a of state.areas) {
|
||||||
await db.run('INSERT INTO areas (id, nombre) VALUES (?, ?)', [a.id, a.nombre]);
|
stmt.run(a.id, a.nombre);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state.camas?.length) {
|
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) {
|
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]);
|
stmt.run(c.id, c.numero, c.areaId, c.tipo, c.estado);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state.internaciones?.length) {
|
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) {
|
for (const i of state.internaciones) {
|
||||||
await db.run(
|
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);
|
||||||
'INSERT OR IGNORE INTO internaciones (id, pacienteId, camaId, areaId, fechaIngresoHospital, fechaIngresoClinica, fechaEgreso, diagnosticoIngreso, motivoConsulta, enfermedadActual, antecedentesEnfermedadActual, diagnosticoEgreso, medicoIngresante, motivoEgreso, activa, apache, derivacion) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
|
|
||||||
[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) {
|
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) {
|
for (const e of state.evoluciones) {
|
||||||
await db.run(
|
stmt.run(e.id, e.internacionId, e.fecha, e.hora || '', e.medico || '', JSON.stringify(e.signosVitales || {}), JSON.stringify(e.examenFisico || {}), e.novedades || '', e.comentarios || '', e.pendientes || '');
|
||||||
'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) {
|
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) {
|
for (const l of state.laboratorios) {
|
||||||
await db.run(
|
stmt.run(l.id, l.pacienteId, l.fecha, l.hora || null, l.tipo, JSON.stringify(l.resultados), l.observaciones || null);
|
||||||
'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) {
|
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) {
|
for (const a of state.acidosBase) {
|
||||||
await db.run(
|
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);
|
||||||
'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) {
|
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) {
|
for (const c of state.cultivos) {
|
||||||
await db.run(
|
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);
|
||||||
'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) {
|
if (state.estudiosComplementarios?.length) {
|
||||||
|
const stmt = db.prepare('INSERT INTO estudiosComplementarios (id, pacienteId, internacionId, fecha, tipo, resultado) VALUES (?, ?, ?, ?, ?, ?)');
|
||||||
for (const e of state.estudiosComplementarios) {
|
for (const e of state.estudiosComplementarios) {
|
||||||
await db.run(
|
stmt.run(e.id, e.pacienteId, e.internacionId || null, e.fecha, e.tipo, e.resultado);
|
||||||
'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) {
|
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) {
|
for (const ic of state.interconsultas) {
|
||||||
await db.run(
|
stmt.run(ic.id, ic.pacienteId, ic.internacionId, ic.fecha, ic.servicioInterconsultado, ic.motivo || null, ic.respuestaInterconsulta || null, ic.respuestaFecha || null);
|
||||||
'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) {
|
if (state.atb?.length) {
|
||||||
|
const stmt = db.prepare('INSERT INTO atb (id, pacienteId, internacionId, antibiotico, fechaInicio, fechaFinalizacion) VALUES (?, ?, ?, ?, ?, ?)');
|
||||||
for (const atb of state.atb) {
|
for (const atb of state.atb) {
|
||||||
await db.run(
|
stmt.run(atb.id, atb.pacienteId, atb.internacionId, atb.antibiotico, atb.fechaInicio, atb.fechaFinalizacion || null);
|
||||||
'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) {
|
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) {
|
for (const ind of state.indicaciones) {
|
||||||
await db.run(
|
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);
|
||||||
'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) {
|
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) {
|
for (const mov of state.movimientosIndicaciones) {
|
||||||
await db.run(
|
stmt.run(mov.id, mov.indicacionId, mov.internacionId, mov.tipo, mov.fecha, mov.profesional, mov.indicacionPrevia || null, mov.indicacionNueva || null);
|
||||||
'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 });
|
res.json({ ok: true });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
@@ -177,8 +155,79 @@ app.put('/api/state', async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Partial state update (only changed parts)
|
||||||
|
app.put('/api/state/partial', (req, res) => {
|
||||||
|
try {
|
||||||
|
const updates = req.body;
|
||||||
|
if (!updates || typeof updates !== 'object') {
|
||||||
|
return res.status(400).json({ error: 'Invalid update data' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const db = getDb();
|
||||||
|
|
||||||
|
// Use transaction for atomicity
|
||||||
|
const result = db.transaction(() => {
|
||||||
|
// Only update tables that were actually changed
|
||||||
|
if (updates.pacientes) {
|
||||||
|
db.prepare('DELETE FROM pacientes').run();
|
||||||
|
if (updates.pacientes.length > 0) {
|
||||||
|
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 updates.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 (updates.areas) {
|
||||||
|
db.prepare('DELETE FROM areas').run();
|
||||||
|
if (updates.areas.length > 0) {
|
||||||
|
const stmt = db.prepare('INSERT INTO areas (id, nombre) VALUES (?, ?)');
|
||||||
|
for (const a of updates.areas) {
|
||||||
|
stmt.run(a.id, a.nombre);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updates.camas) {
|
||||||
|
db.prepare('DELETE FROM camas').run();
|
||||||
|
if (updates.camas.length > 0) {
|
||||||
|
const stmt = db.prepare('INSERT INTO camas (id, numero, areaId, tipo, estado) VALUES (?, ?, ?, ?, ?)');
|
||||||
|
for (const c of updates.camas) {
|
||||||
|
stmt.run(c.id, c.numero, c.areaId, c.tipo, c.estado);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updates.internaciones) {
|
||||||
|
db.prepare('DELETE FROM internaciones').run();
|
||||||
|
if (updates.internaciones.length > 0) {
|
||||||
|
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 updates.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 (updates.evoluciones) {
|
||||||
|
db.prepare('DELETE FROM evoluciones').run();
|
||||||
|
if (updates.evoluciones.length > 0) {
|
||||||
|
const stmt = db.prepare('INSERT OR IGNORE INTO evoluciones (id, internacionId, fecha, hora, medico, signosVitales, examenFisico, novedades, comentarios, pendientes) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)');
|
||||||
|
for (const e of updates.evoluciones) {
|
||||||
|
stmt.run(e.id, e.internacionId, e.fecha, e.hora || '', e.medico || '', JSON.stringify(e.signosVitales || {}), JSON.stringify(e.examenFisico || {}), e.novedades || '', e.comentarios || '', e.pendientes || '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
res.json({ ok: true });
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
res.status(500).json({ error: err.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
app.listen(PORT, () => {
|
app.listen(PORT, () => {
|
||||||
console.log(`API server listening on http://localhost:${PORT}`);
|
console.log(`Server running on port ${PORT}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
// === AUTENTICACIÓN ===
|
// === AUTENTICACIÓN ===
|
||||||
@@ -191,19 +240,19 @@ function generateUUID() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
app.post('/api/auth/login', async (req, res) => {
|
app.post('/api/auth/login', (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { dni, password } = req.body;
|
const { dni, password } = req.body;
|
||||||
if (!dni || !password) {
|
if (!dni || !password) {
|
||||||
return res.status(400).json({ error: 'DNI y contraseña requeridos' });
|
return res.status(400).json({ error: 'DNI y contraseña requeridos' });
|
||||||
}
|
}
|
||||||
|
|
||||||
const usuario = await getUsuarioByDni(dni);
|
const usuario = getUsuarioByDni(dni);
|
||||||
if (!usuario) {
|
if (!usuario) {
|
||||||
return res.status(401).json({ error: 'Usuario no encontrado' });
|
return res.status(401).json({ error: 'Usuario no encontrado' });
|
||||||
}
|
}
|
||||||
|
|
||||||
const validPassword = await verifyPassword(dni, password);
|
const validPassword = verifyPassword(dni, password);
|
||||||
if (!validPassword) {
|
if (!validPassword) {
|
||||||
return res.status(401).json({ error: 'Contraseña incorrecta' });
|
return res.status(401).json({ error: 'Contraseña incorrecta' });
|
||||||
}
|
}
|
||||||
@@ -216,20 +265,20 @@ app.post('/api/auth/login', async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post('/api/auth/change-password', async (req, res) => {
|
app.post('/api/auth/change-password', (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { dni, oldPassword, newPassword } = req.body;
|
const { dni, oldPassword, newPassword } = req.body;
|
||||||
if (!dni || !oldPassword || !newPassword) {
|
if (!dni || !oldPassword || !newPassword) {
|
||||||
return res.status(400).json({ error: 'Todos los campos son requeridos' });
|
return res.status(400).json({ error: 'Todos los campos son requeridos' });
|
||||||
}
|
}
|
||||||
|
|
||||||
const validPassword = await verifyPassword(dni, oldPassword);
|
const validPassword = verifyPassword(dni, oldPassword);
|
||||||
if (!validPassword) {
|
if (!validPassword) {
|
||||||
return res.status(401).json({ error: 'Contraseña actual incorrecta' });
|
return res.status(401).json({ error: 'Contraseña actual incorrecta' });
|
||||||
}
|
}
|
||||||
|
|
||||||
const newHash = await hashPassword(newPassword);
|
const newHash = hashPassword(newPassword);
|
||||||
await updateUsuarioByDni(dni, { passwordHash: newHash });
|
updateUsuarioByDni(dni, { passwordHash: newHash });
|
||||||
res.json({ ok: true });
|
res.json({ ok: true });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
@@ -237,14 +286,14 @@ app.post('/api/auth/change-password', async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
app.put('/api/auth/update-email', async (req, res) => {
|
app.put('/api/auth/update-email', (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { dni, newEmail } = req.body;
|
const { dni, newEmail } = req.body;
|
||||||
if (!dni || !newEmail) {
|
if (!dni || !newEmail) {
|
||||||
return res.status(400).json({ error: 'DNI y email son requeridos' });
|
return res.status(400).json({ error: 'DNI y email son requeridos' });
|
||||||
}
|
}
|
||||||
|
|
||||||
await updateUsuarioByDni(dni, { email: newEmail });
|
updateUsuarioByDni(dni, { email: newEmail });
|
||||||
res.json({ ok: true });
|
res.json({ ok: true });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
@@ -254,9 +303,9 @@ app.put('/api/auth/update-email', async (req, res) => {
|
|||||||
|
|
||||||
// === GESTIÓN DE USUARIOS (ADMIN) ===
|
// === GESTIÓN DE USUARIOS (ADMIN) ===
|
||||||
|
|
||||||
app.get('/api/areas', async (req, res) => {
|
app.get('/api/areas', (req, res) => {
|
||||||
try {
|
try {
|
||||||
const areas = await getAllAreas();
|
const areas = getAllAreas();
|
||||||
res.json(areas);
|
res.json(areas);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
@@ -264,9 +313,9 @@ app.get('/api/areas', async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get('/api/usuarios', async (req, res) => {
|
app.get('/api/usuarios', (req, res) => {
|
||||||
try {
|
try {
|
||||||
const usuarios = await getAllUsuarios();
|
const usuarios = getAllUsuarios();
|
||||||
const usuariosSinPassword = usuarios.map(({ passwordHash, ...u }) => u);
|
const usuariosSinPassword = usuarios.map(({ passwordHash, ...u }) => u);
|
||||||
res.json(usuariosSinPassword);
|
res.json(usuariosSinPassword);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -275,7 +324,7 @@ app.get('/api/usuarios', async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post('/api/usuarios', async (req, res) => {
|
app.post('/api/usuarios', (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { apellido, nombre, dni, fechaNacimiento, email, rol, matriculaProfesional, password, areaId } = req.body;
|
const { apellido, nombre, dni, fechaNacimiento, email, rol, matriculaProfesional, password, areaId } = req.body;
|
||||||
|
|
||||||
@@ -283,12 +332,12 @@ app.post('/api/usuarios', async (req, res) => {
|
|||||||
return res.status(400).json({ error: 'Todos los campos obligatorios deben estar presentes' });
|
return res.status(400).json({ error: 'Todos los campos obligatorios deben estar presentes' });
|
||||||
}
|
}
|
||||||
|
|
||||||
const existente = await getUsuarioByDni(dni);
|
const existente = getUsuarioByDni(dni);
|
||||||
if (existente) {
|
if (existente) {
|
||||||
return res.status(400).json({ error: 'Ya existe un usuario con ese DNI' });
|
return res.status(400).json({ error: 'Ya existe un usuario con ese DNI' });
|
||||||
}
|
}
|
||||||
|
|
||||||
const passwordHash = await hashPassword(password);
|
const passwordHash = hashPassword(password);
|
||||||
const usuario = {
|
const usuario = {
|
||||||
id: generateUUID(),
|
id: generateUUID(),
|
||||||
apellido,
|
apellido,
|
||||||
@@ -303,7 +352,7 @@ app.post('/api/usuarios', async (req, res) => {
|
|||||||
fechaCreacion: new Date().toISOString().split('T')[0]
|
fechaCreacion: new Date().toISOString().split('T')[0]
|
||||||
};
|
};
|
||||||
|
|
||||||
await createUsuario(usuario);
|
createUsuario(usuario);
|
||||||
res.json({ ...usuario, passwordHash: undefined });
|
res.json({ ...usuario, passwordHash: undefined });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
@@ -311,12 +360,12 @@ app.post('/api/usuarios', async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
app.put('/api/usuarios/:id', async (req, res) => {
|
app.put('/api/usuarios/:id', (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
const { apellido, nombre, fechaNacimiento, email, rol, matriculaProfesional, areaId, password } = req.body;
|
const { apellido, nombre, fechaNacimiento, email, rol, matriculaProfesional, areaId, password } = req.body;
|
||||||
|
|
||||||
const usuario = await getUsuarioById(id);
|
const usuario = getUsuarioById(id);
|
||||||
if (!usuario) {
|
if (!usuario) {
|
||||||
return res.status(404).json({ error: 'Usuario no encontrado' });
|
return res.status(404).json({ error: 'Usuario no encontrado' });
|
||||||
}
|
}
|
||||||
@@ -330,10 +379,10 @@ app.put('/api/usuarios/:id', async (req, res) => {
|
|||||||
if (matriculaProfesional !== undefined) datos.matriculaProfesional = matriculaProfesional;
|
if (matriculaProfesional !== undefined) datos.matriculaProfesional = matriculaProfesional;
|
||||||
if (areaId !== undefined) datos.areaId = areaId;
|
if (areaId !== undefined) datos.areaId = areaId;
|
||||||
if (password) {
|
if (password) {
|
||||||
datos.passwordHash = await hashPassword(password);
|
datos.passwordHash = hashPassword(password);
|
||||||
}
|
}
|
||||||
|
|
||||||
await updateUsuario(id, datos);
|
updateUsuario(id, datos);
|
||||||
res.json({ ok: true });
|
res.json({ ok: true });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
@@ -341,10 +390,10 @@ app.put('/api/usuarios/:id', async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
app.delete('/api/usuarios/:id', async (req, res) => {
|
app.delete('/api/usuarios/:id', (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
await deleteUsuario(id);
|
deleteUsuario(id);
|
||||||
res.json({ ok: true });
|
res.json({ ok: true });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
@@ -352,32 +401,23 @@ app.delete('/api/usuarios/:id', async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
app.put('/api/camas/:id', async (req, res) => {
|
app.put('/api/camas/:id', (req, res) => {
|
||||||
let retries = 3;
|
|
||||||
while (retries > 0) {
|
|
||||||
try {
|
try {
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
const { estado } = req.body;
|
const { estado } = req.body;
|
||||||
const db = await openDb();
|
const db = getDb();
|
||||||
await db.run('UPDATE camaS SET estado = ? WHERE id = ?', [estado, id]);
|
db.prepare('UPDATE camas SET estado = ? WHERE id = ?').run(estado, id);
|
||||||
await db.close();
|
res.json({ ok: true });
|
||||||
return res.json({ ok: true });
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error updating cama (retry', retries, '):', err.message);
|
console.error('Error updating cama:', err.message);
|
||||||
retries--;
|
res.status(500).json({ error: err.message });
|
||||||
if (retries === 0) {
|
|
||||||
return res.status(500).json({ error: err.message });
|
|
||||||
}
|
|
||||||
await new Promise(r => setTimeout(r, 200));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Helper para actualizar por DNI
|
// Helper para actualizar por DNI
|
||||||
async function updateUsuarioByDni(dni, datos) {
|
function updateUsuarioByDni(dni, datos) {
|
||||||
const { getUsuarioByDni, updateUsuario } = await import('./db.js');
|
const usuario = getUsuarioByDni(dni);
|
||||||
const usuario = await getUsuarioByDni(dni);
|
|
||||||
if (usuario) {
|
if (usuario) {
|
||||||
await updateUsuario(usuario.id, datos);
|
updateUsuario(usuario.id, datos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,14 @@ if (!existsSync(join(__dirname, 'data'))) {
|
|||||||
|
|
||||||
const db = new Database(DB_PATH);
|
const db = new Database(DB_PATH);
|
||||||
|
|
||||||
|
// Configure WAL mode and performance settings
|
||||||
|
db.pragma('journal_mode = WAL');
|
||||||
|
db.pragma('busy_timeout = 5000');
|
||||||
|
db.pragma('synchronous = NORMAL');
|
||||||
|
db.pragma('cache_size = 1000');
|
||||||
|
db.pragma('temp_store = MEMORY');
|
||||||
|
db.pragma('mmap_size = 268435456');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
db.exec("ALTER TABLE internaciones ADD COLUMN areaId TEXT;");
|
db.exec("ALTER TABLE internaciones ADD COLUMN areaId TEXT;");
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -23,6 +31,20 @@ try {
|
|||||||
}
|
}
|
||||||
|
|
||||||
db.exec(`
|
db.exec(`
|
||||||
|
CREATE TABLE IF NOT EXISTS usuarios (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
apellido TEXT NOT NULL,
|
||||||
|
nombre TEXT NOT NULL,
|
||||||
|
dni TEXT NOT NULL UNIQUE,
|
||||||
|
fechaNacimiento TEXT,
|
||||||
|
email TEXT,
|
||||||
|
rol TEXT,
|
||||||
|
matriculaProfesional TEXT,
|
||||||
|
passwordHash TEXT,
|
||||||
|
areaId TEXT,
|
||||||
|
fechaCreacion TEXT
|
||||||
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS pacientes (
|
CREATE TABLE IF NOT EXISTS pacientes (
|
||||||
id TEXT PRIMARY KEY,
|
id TEXT PRIMARY KEY,
|
||||||
apellido TEXT NOT NULL,
|
apellido TEXT NOT NULL,
|
||||||
@@ -139,6 +161,11 @@ CREATE TABLE IF NOT EXISTS areas (
|
|||||||
tipo TEXT,
|
tipo TEXT,
|
||||||
resultado TEXT
|
resultado TEXT
|
||||||
);
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS kv (
|
||||||
|
key TEXT PRIMARY KEY,
|
||||||
|
value TEXT
|
||||||
|
);
|
||||||
`);
|
`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -111,33 +111,61 @@ export function useHospitalStore() {
|
|||||||
return () => { mounted = false; };
|
return () => { mounted = false; };
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Persist state to backend when it changes (debounced)
|
// Persistence: event-driven + periodic save
|
||||||
const [initialLoadComplete, setInitialLoadComplete] = useState(false);
|
const [saveQueue, setSaveQueue] = useState<string[]>([]);
|
||||||
|
const [isSaving, setIsSaving] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
// Track which parts changed
|
||||||
if (isLoaded && !initialLoadComplete) {
|
const [changedKeys, setChangedKeys] = useState<Set<string>>(new Set());
|
||||||
setInitialLoadComplete(true);
|
|
||||||
}
|
|
||||||
}, [isLoaded, initialLoadComplete]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
// Mark parts as changed
|
||||||
if (!initialLoadComplete) return;
|
const markChanged = (key: string) => {
|
||||||
const t = setTimeout(() => {
|
setChangedKeys(prev => {
|
||||||
(async () => {
|
const next = new Set(prev);
|
||||||
|
next.add(key);
|
||||||
|
return next;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Save only changed parts
|
||||||
|
const saveChanges = useCallback(async () => {
|
||||||
|
if (isSaving || changedKeys.size === 0) return;
|
||||||
|
setIsSaving(true);
|
||||||
try {
|
try {
|
||||||
const { currentUser, isAuthenticated, ...stateToSave } = state;
|
const keysToSave = Array.from(changedKeys);
|
||||||
await fetch(`${API_BASE}/state`, {
|
const partialState: any = {};
|
||||||
|
keysToSave.forEach(key => {
|
||||||
|
if (key in state) {
|
||||||
|
partialState[key] = state[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await fetch(`${API_BASE}/state/partial`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify(stateToSave),
|
body: JSON.stringify(partialState),
|
||||||
});
|
});
|
||||||
|
setChangedKeys(new Set());
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// ignore save errors for now
|
console.error('Save error:', err);
|
||||||
|
} finally {
|
||||||
|
setIsSaving(false);
|
||||||
}
|
}
|
||||||
})();
|
}, [state, changedKeys, isSaving]);
|
||||||
}, 300);
|
|
||||||
return () => clearTimeout(t);
|
// Periodic save every 5 seconds if there are changes
|
||||||
}, [state, initialLoadComplete]);
|
useEffect(() => {
|
||||||
|
if (!isLoaded) return;
|
||||||
|
const t = setInterval(() => {
|
||||||
|
saveChanges();
|
||||||
|
}, 5000);
|
||||||
|
return () => clearInterval(t);
|
||||||
|
}, [isLoaded, saveChanges]);
|
||||||
|
|
||||||
|
// Also save on特定 events (optional)
|
||||||
|
const queueSave = (keys: string[]) => {
|
||||||
|
keys.forEach(markChanged);
|
||||||
|
};
|
||||||
|
|
||||||
// Utility functions
|
// Utility functions
|
||||||
const getCamaAreaId = useCallback((camaId: string): string | null => {
|
const getCamaAreaId = useCallback((camaId: string): string | null => {
|
||||||
|
|||||||
Reference in New Issue
Block a user