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:
2026-04-24 02:06:21 -03:00
parent d30dbab8f0
commit e887d66411
8 changed files with 405 additions and 327 deletions
+100 -136
View File
@@ -1,83 +1,79 @@
import sqlite3 from 'sqlite3';
import { open } from 'sqlite';
import Database from 'better-sqlite3';
import { dirname } from 'path';
import { fileURLToPath } from 'url';
import bcrypt from 'bcryptjs';
import { existsSync, mkdirSync } from 'fs';
const __dirname = dirname(fileURLToPath(import.meta.url));
const DB_PATH = __dirname + '/data/hospital.db';
export async function openDb() {
const db = await open({
filename: DB_PATH,
driver: sqlite3.Database,
});
// Ensure data directory exists
if (!existsSync(__dirname + '/data')) {
mkdirSync(__dirname + '/data', { recursive: true });
}
await db.exec(`
CREATE TABLE IF NOT EXISTS kv (
key TEXT PRIMARY KEY,
value TEXT
);
`);
// Create persistent database connection with WAL mode and proper settings
const db = new Database(DB_PATH);
await db.exec(`
CREATE TABLE IF NOT EXISTS usuarios (
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
);
`);
// Enable WAL mode for better concurrency
db.pragma('journal_mode = WAL');
// 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;
}
export async function getValue(key) {
const db = await openDb();
const row = await db.get('SELECT value FROM kv WHERE key = ?', key);
await db.close();
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;
}
export function getValue(key) {
const row = db.prepare('SELECT value FROM kv WHERE key = ?').get(key);
return row?.value ?? null;
}
export async function setValue(key, value) {
const db = await openDb();
await db.run('INSERT INTO kv (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value=excluded.value', key, value);
await db.close();
export function setValue(key, value) {
const stmt = db.prepare('INSERT INTO kv (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value=excluded.value');
stmt.run(key, value);
}
export async function getUsuarioByDni(dni) {
const db = await openDb();
const row = await db.get('SELECT * FROM usuarios WHERE dni = ?', dni);
await db.close();
export function getUsuarioByDni(dni) {
const row = db.prepare('SELECT * FROM usuarios WHERE dni = ?').get(dni);
return row || null;
}
export async function getUsuarioById(id) {
const db = await openDb();
const row = await db.get('SELECT * FROM usuarios WHERE id = ?', id);
await db.close();
export function getUsuarioById(id) {
const row = db.prepare('SELECT * FROM usuarios WHERE id = ?').get(id);
return row || null;
}
export async function getAllUsuarios() {
const db = await openDb();
const rows = await db.all('SELECT * FROM usuarios ORDER BY apellido, nombre');
await db.close();
export function getAllUsuarios() {
const rows = db.prepare('SELECT * FROM usuarios ORDER BY apellido, nombre').all();
return rows;
}
export async function createUsuario(usuario) {
const db = await openDb();
await db.run(
`INSERT INTO usuarios (id, apellido, nombre, dni, fechaNacimiento, email, rol, matriculaProfesional, passwordHash, areaId, fechaCreacion)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
export function createUsuario(usuario) {
const stmt = db.prepare(`INSERT INTO usuarios (id, apellido, nombre, dni, fechaNacimiento, email, rol, matriculaProfesional, passwordHash, areaId, fechaCreacion)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
stmt.run(
usuario.id,
usuario.apellido,
usuario.nombre,
@@ -90,11 +86,9 @@ export async function createUsuario(usuario) {
usuario.areaId || null,
usuario.fechaCreacion
);
await db.close();
}
export async function updateUsuario(id, datos) {
const db = await openDb();
export function updateUsuario(id, datos) {
const fields = [];
const values = [];
@@ -109,53 +103,40 @@ export async function updateUsuario(id, datos) {
if (fields.length > 0) {
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) {
const db = await openDb();
await db.run('DELETE FROM usuarios WHERE id = ?', id);
await db.close();
export function deleteUsuario(id) {
db.prepare('DELETE FROM usuarios WHERE id = ?').run(id);
}
export async function verifyPassword(dni, password) {
const db = await openDb();
const row = await db.get('SELECT passwordHash FROM usuarios WHERE dni = ?', dni);
await db.close();
export function verifyPassword(dni, password) {
const row = db.prepare('SELECT passwordHash FROM usuarios WHERE dni = ?').get(dni);
if (!row) return false;
return bcrypt.compareSync(password, row.passwordHash);
}
export async function hashPassword(password) {
export function hashPassword(password) {
return bcrypt.hashSync(password, 10);
}
export async function getAllPacientes() {
const db = await openDb();
const rows = await db.all('SELECT * FROM pacientes');
await db.close();
export function getAllPacientes() {
const rows = db.prepare('SELECT * FROM pacientes').all();
return rows;
}
export async function getAllAreas() {
const db = await openDb();
const rows = await db.all('SELECT * FROM areas');
await db.close();
export function getAllAreas() {
const rows = db.prepare('SELECT * FROM areas').all();
return rows;
}
export async function getAllCamas() {
const db = await openDb();
const rows = await db.all('SELECT * FROM camas');
await db.close();
export function getAllCamas() {
const rows = db.prepare('SELECT * FROM camas').all();
return rows;
}
export async function updateCama(id, updates) {
const db = await openDb();
export function updateCama(id, updates) {
const fields = [];
const values = [];
if (updates.estado !== undefined) {
@@ -172,77 +153,60 @@ export async function updateCama(id, updates) {
}
if (fields.length > 0) {
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() {
const db = await openDb();
const rows = await db.all('SELECT * FROM internaciones');
await db.close();
export function getAllInternaciones() {
const rows = db.prepare('SELECT * FROM internaciones').all();
return rows.map(i => ({ ...i, activa: !!i.activa }));
}
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;
}
export async function getAllEvoluciones() {
const db = await openDb();
const rows = await db.all('SELECT * FROM evoluciones');
await db.close();
export function getAllCultivos() {
const rows = db.prepare('SELECT * FROM cultivos').all();
return rows;
}
export async function getAllLaboratorios() {
const db = await openDb();
const rows = await db.all('SELECT * FROM laboratorios');
await db.close();
export function getAllEstudiosComplementarios() {
const rows = db.prepare('SELECT * FROM estudiosComplementarios').all();
return rows;
}
export async function getAllAcidosBase() {
const db = await openDb();
const rows = await db.all('SELECT * FROM acidosbase');
await db.close();
export function getAllInterconsultas() {
const rows = db.prepare('SELECT * FROM interconsultas').all();
return rows.map(ic => ({ ...ic, realizada: !!ic.realizada }));
}
export function getAllAtb() {
const rows = db.prepare('SELECT * FROM atb').all();
return rows;
}
export async function getAllCultivos() {
const db = await openDb();
const rows = await db.all('SELECT * FROM cultivos');
await db.close();
export function getAllIndicaciones() {
const rows = db.prepare('SELECT * FROM indicaciones').all();
return rows;
}
export async function getAllEstudiosComplementarios() {
const db = await openDb();
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();
export function getAllMovimientosIndicaciones() {
const rows = db.prepare('SELECT * FROM movimientos_indicaciones').all();
return rows;
}