feat: make side panel Cultivos section read-only and place bed badge on the left

This commit is contained in:
2026-08-11 13:10:55 +00:00
commit a17deb815e
117 changed files with 32924 additions and 0 deletions
+742
View File
@@ -0,0 +1,742 @@
import express from 'express';
import cors from 'cors';
import { initDb,
getUsuarioByDni, getUsuarioById, getAllUsuarios, createUsuario, updateUsuario, deleteUsuario, verifyPassword, hashPassword,
getAllPacientes, getPacienteById, createPaciente, updatePaciente, deletePaciente,
getAllAreas, createArea, updateArea, deleteArea,
getAllCamas, getCamaById, createCama, updateCama, deleteCama,
getAllInternaciones, getInternacionById, createInternacion, updateInternacion, deleteInternacion,
getAllEvoluciones, createEvolucion, updateEvolucion, deleteEvolucion,
getAllLaboratorios, createLaboratorio, updateLaboratorio, deleteLaboratorio,
getAllGlucemias, createGlucemia, updateGlucemia, deleteGlucemia,
getAllAcidosBase, createAcidoBase, updateAcidoBase, deleteAcidoBase,
getAllCultivos, createCultivo, updateCultivo, deleteCultivo,
getAllEstudiosComplementarios, createEstudioComplementario, updateEstudioComplementario, deleteEstudioComplementario,
getAllInterconsultas, createInterconsulta, updateInterconsulta, deleteInterconsulta,
getAllAtb, createAtb, updateAtb, deleteAtb,
getAllIndicaciones, createIndicacion, updateIndicacion, deleteIndicacion,
getAllMovimientosIndicaciones, createMovimientoIndicacion,
getValue, setValue
} from './db-mongodb.js';
const app = express();
app.use(cors({
origin: true,
credentials: true
}));
app.use(express.json({ limit: '50mb' }));
// UUID generator
function generateUUID() {
if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
return crypto.randomUUID();
}
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
const r = (Math.random() * 16) | 0;
const v = c === 'x' ? r : (r & 0x3) | 0x8;
return v.toString(16);
});
}
// ========== STATE ENDPOINT (initial load) ==========
app.get('/api/state', async (req, res) => {
try {
const areasList = await getAllAreas();
const usuariosList = await getAllUsuarios();
const state = {
pacientes: await getAllPacientes(),
areas: areasList,
grupos: areasList,
camas: await getAllCamas(),
internaciones: await getAllInternaciones(),
evoluciones: (await getAllEvoluciones()).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: (await getAllLaboratorios()).map(l => ({ ...l, resultados: typeof l.resultados === 'string' ? JSON.parse(l.resultados) : l.resultados })),
glucemias: await getAllGlucemias(),
acidosBase: await getAllAcidosBase(),
cultivos: await getAllCultivos(),
estudiosComplementarios: await getAllEstudiosComplementarios(),
interconsultas: (await getAllInterconsultas()).map(ic => ({ ...ic, realizada: !!ic.realizada })),
atb: await getAllAtb(),
indicaciones: await getAllIndicaciones(),
movimientosIndicaciones: await getAllMovimientosIndicaciones(),
usuarios: usuariosList.map(({ passwordHash, ...u }) => u),
vistaActual: 'dashboard',
currentInternacionId: null
};
res.json(state);
} catch (err) {
console.error(err);
res.status(500).json({ error: 'failed to read state' });
}
});
// ========== USUARIOS ==========
app.get('/api/usuarios', async (req, res) => {
try {
const usuarios = await getAllUsuarios();
const usuariosSinPassword = usuarios.map(({ passwordHash, ...u }) => u);
res.json(usuariosSinPassword);
} catch (err) {
res.status(500).json({ error: 'Error al obtener usuarios' });
}
});
app.post('/api/usuarios', async (req, res) => {
try {
const { apellido, nombre, dni, fechaNacimiento, email, rol, matriculaProfesional, password, areaId } = req.body;
if (!apellido || !nombre || !dni || !fechaNacimiento || !email || !rol || !password) {
return res.status(400).json({ error: 'Todos los campos obligatorios deben estar presentes' });
}
const existente = await getUsuarioByDni(dni);
if (existente) {
return res.status(400).json({ error: 'Ya existe un usuario con ese DNI' });
}
const passwordHash = hashPassword(password);
const usuario = {
id: generateUUID(),
apellido, nombre, dni, fechaNacimiento, email, rol,
matriculaProfesional: matriculaProfesional || null,
passwordHash,
areaId: areaId || null,
fechaCreacion: new Date().toISOString().split('T')[0]
};
await createUsuario(usuario);
res.json({ ...usuario, passwordHash: undefined });
} catch (err) {
res.status(500).json({ error: 'Error al crear usuario' });
}
});
app.put('/api/usuarios/:id', async (req, res) => {
try {
const { id } = req.params;
const { apellido, nombre, fechaNacimiento, email, rol, matriculaProfesional, areaId, password } = req.body;
const usuario = await getUsuarioById(id);
if (!usuario) {
return res.status(404).json({ error: 'Usuario no encontrado' });
}
const datos = {};
if (apellido !== undefined) datos.apellido = apellido;
if (nombre !== undefined) datos.nombre = nombre;
if (fechaNacimiento !== undefined) datos.fechaNacimiento = fechaNacimiento;
if (email !== undefined) datos.email = email;
if (rol !== undefined) datos.rol = rol;
if (matriculaProfesional !== undefined) datos.matriculaProfesional = matriculaProfesional;
if (areaId !== undefined) datos.areaId = areaId;
if (password) datos.passwordHash = hashPassword(password);
await updateUsuario(id, datos);
res.json({ ok: true });
} catch (err) {
res.status(500).json({ error: 'Error al actualizar usuario' });
}
});
app.delete('/api/usuarios/:id', async (req, res) => {
try {
await deleteUsuario(req.params.id);
res.json({ ok: true });
} catch (err) {
res.status(500).json({ error: 'Error al eliminar usuario' });
}
});
// ========== PACIENTES ==========
app.get('/api/pacientes', async (req, res) => {
try {
res.json(await getAllPacientes());
} catch (err) {
res.status(500).json({ error: 'Error al obtener pacientes' });
}
});
app.post('/api/pacientes', async (req, res) => {
try {
const paciente = { ...req.body, id: generateUUID() };
await createPaciente(paciente);
res.json(paciente);
} catch (err) {
res.status(500).json({ error: 'Error al crear paciente' });
}
});
app.put('/api/pacientes/:id', async (req, res) => {
try {
await updatePaciente(req.params.id, req.body);
res.json({ ok: true });
} catch (err) {
res.status(500).json({ error: 'Error al actualizar paciente' });
}
});
app.delete('/api/pacientes/:id', async (req, res) => {
try {
await deletePaciente(req.params.id);
res.json({ ok: true });
} catch (err) {
res.status(500).json({ error: 'Error al eliminar paciente' });
}
});
// ========== AREAS & GRUPOS ==========
app.get(['/api/areas', '/api/grupos'], async (req, res) => {
try {
res.json(await getAllAreas());
} catch (err) {
res.status(500).json({ error: 'Error al obtener áreas/grupos' });
}
});
app.post(['/api/areas', '/api/grupos'], async (req, res) => {
try {
const area = { ...req.body, id: req.body.id || generateUUID() };
await createArea(area);
res.json(area);
} catch (err) {
res.status(500).json({ error: 'Error al crear área/grupo' });
}
});
app.put(['/api/areas/:id', '/api/grupos/:id'], async (req, res) => {
try {
await updateArea(req.params.id, req.body);
res.json({ ok: true });
} catch (err) {
res.status(500).json({ error: 'Error al actualizar área/grupo' });
}
});
app.delete(['/api/areas/:id', '/api/grupos/:id'], async (req, res) => {
try {
await deleteArea(req.params.id);
res.json({ ok: true });
} catch (err) {
res.status(500).json({ error: 'Error al eliminar área/grupo' });
}
});
// ========== CAMAS ==========
app.get('/api/camas', async (req, res) => {
try {
res.json(await getAllCamas());
} catch (err) {
res.status(500).json({ error: 'Error al obtener camas' });
}
});
app.put('/api/camas/:id', async (req, res) => {
try {
await updateCama(req.params.id, req.body);
res.json({ ok: true });
} catch (err) {
res.status(500).json({ error: 'Error al actualizar cama' });
}
});
app.post('/api/camas', async (req, res) => {
try {
const cama = req.body;
if (!cama.id) {
cama.id = generateUUID();
}
await createCama(cama);
res.json(cama);
} catch (err) {
res.status(500).json({ error: 'Error al crear cama' });
}
});
app.delete('/api/camas/:id', async (req, res) => {
try {
await deleteCama(req.params.id);
res.json({ ok: true });
} catch (err) {
res.status(500).json({ error: 'Error al eliminar cama' });
}
});
// ========== INTERNACIONES ==========
app.get('/api/internaciones', async (req, res) => {
try {
res.json(await getAllInternaciones());
} catch (err) {
res.status(500).json({ error: 'Error al obtener internaciones' });
}
});
app.post('/api/internaciones', async (req, res) => {
try {
const internacion = { ...req.body, id: generateUUID(), activa: true };
await createInternacion(internacion);
res.json(internacion);
} catch (err) {
res.status(500).json({ error: 'Error al crear internacion' });
}
});
app.put('/api/internaciones/:id', async (req, res) => {
try {
await updateInternacion(req.params.id, req.body);
res.json({ ok: true });
} catch (err) {
res.status(500).json({ error: 'Error al actualizar internacion' });
}
});
app.delete('/api/internaciones/:id', async (req, res) => {
try {
await deleteInternacion(req.params.id);
res.json({ ok: true });
} catch (err) {
res.status(500).json({ error: 'Error al eliminar internacion' });
}
});
// ========== EVOLUCIONES ==========
app.get('/api/evoluciones', async (req, res) => {
try {
res.json(await getAllEvoluciones());
} catch (err) {
res.status(500).json({ error: 'Error al obtener evoluciones' });
}
});
app.post('/api/evoluciones', async (req, res) => {
try {
const evolucion = { ...req.body, id: generateUUID() };
await createEvolucion(evolucion);
res.json(evolucion);
} catch (err) {
res.status(500).json({ error: 'Error al crear evolucion' });
}
});
app.put('/api/evoluciones/:id', async (req, res) => {
try {
await updateEvolucion(req.params.id, req.body);
res.json({ ok: true });
} catch (err) {
res.status(500).json({ error: 'Error al actualizar evolucion' });
}
});
app.delete('/api/evoluciones/:id', async (req, res) => {
try {
await deleteEvolucion(req.params.id);
res.json({ ok: true });
} catch (err) {
res.status(500).json({ error: 'Error al eliminar evolucion' });
}
});
// ========== LABORATORIOS ==========
app.get('/api/laboratorios', async (req, res) => {
try {
res.json(await getAllLaboratorios());
} catch (err) {
res.status(500).json({ error: 'Error al obtener laboratorios' });
}
});
app.post('/api/laboratorios', async (req, res) => {
try {
const laboratorio = { ...req.body, id: generateUUID() };
await createLaboratorio(laboratorio);
res.json(laboratorio);
} catch (err) {
res.status(500).json({ error: 'Error al crear laboratorio' });
}
});
app.put('/api/laboratorios/:id', async (req, res) => {
try {
await updateLaboratorio(req.params.id, req.body);
res.json({ ok: true });
} catch (err) {
res.status(500).json({ error: 'Error al actualizar laboratorio' });
}
});
app.delete('/api/laboratorios/:id', async (req, res) => {
try {
await deleteLaboratorio(req.params.id);
res.json({ ok: true });
} catch (err) {
res.status(500).json({ error: 'Error al eliminar laboratorio' });
}
});
// ========== GLUCEMIAS ==========
app.get('/api/glucemias', async (req, res) => {
try {
res.json(await getAllGlucemias());
} catch (err) {
res.status(500).json({ error: 'Error al obtener glucemias' });
}
});
app.post('/api/glucemias', async (req, res) => {
try {
const glucemia = { ...req.body, id: generateUUID() };
await createGlucemia(glucemia);
res.json(glucemia);
} catch (err) {
res.status(500).json({ error: 'Error al crear glucemia' });
}
});
app.put('/api/glucemias/:id', async (req, res) => {
try {
await updateGlucemia(req.params.id, req.body);
res.json({ ok: true });
} catch (err) {
res.status(500).json({ error: 'Error al actualizar glucemia' });
}
});
app.delete('/api/glucemias/:id', async (req, res) => {
try {
await deleteGlucemia(req.params.id);
res.json({ ok: true });
} catch (err) {
res.status(500).json({ error: 'Error al eliminar glucemia' });
}
});
// ========== ACIDOS BASE ==========
app.get('/api/acid-os-base', async (req, res) => {
try {
res.json(await getAllAcidosBase());
} catch (err) {
res.status(500).json({ error: 'Error al obtener acidos base' });
}
});
app.post('/api/acid-os-base', async (req, res) => {
try {
const acido = { ...req.body, id: generateUUID() };
await createAcidoBase(acido);
res.json(acido);
} catch (err) {
res.status(500).json({ error: 'Error al crear acido base' });
}
});
app.put('/api/acid-os-base/:id', async (req, res) => {
try {
await updateAcidoBase(req.params.id, req.body);
res.json({ ok: true });
} catch (err) {
res.status(500).json({ error: 'Error al actualizar acido base' });
}
});
app.delete('/api/acid-os-base/:id', async (req, res) => {
try {
await deleteAcidoBase(req.params.id);
res.json({ ok: true });
} catch (err) {
res.status(500).json({ error: 'Error al eliminar acido base' });
}
});
// ========== CULTIVOS ==========
app.get('/api/cultivos', async (req, res) => {
try {
res.json(await getAllCultivos());
} catch (err) {
res.status(500).json({ error: 'Error al obtener cultivos' });
}
});
app.post('/api/cultivos', async (req, res) => {
try {
const cultivo = { ...req.body, id: generateUUID() };
await createCultivo(cultivo);
res.json(cultivo);
} catch (err) {
res.status(500).json({ error: 'Error al crear cultivo' });
}
});
app.put('/api/cultivos/:id', async (req, res) => {
try {
await updateCultivo(req.params.id, req.body);
res.json({ ok: true });
} catch (err) {
res.status(500).json({ error: 'Error al actualizar cultivo' });
}
});
app.delete('/api/cultivos/:id', async (req, res) => {
try {
await deleteCultivo(req.params.id);
res.json({ ok: true });
} catch (err) {
res.status(500).json({ error: 'Error al eliminar cultivo' });
}
});
// ========== ESTUDIOS COMPLEMENTARIOS ==========
app.get('/api/estudios-complementarios', async (req, res) => {
try {
res.json(await getAllEstudiosComplementarios());
} catch (err) {
res.status(500).json({ error: 'Error al obtener estudios' });
}
});
app.post('/api/estudios-complementarios', async (req, res) => {
try {
const estudio = { ...req.body, id: generateUUID() };
await createEstudioComplementario(estudio);
res.json(estudio);
} catch (err) {
res.status(500).json({ error: 'Error al crear estudio' });
}
});
app.put('/api/estudios-complementarios/:id', async (req, res) => {
try {
await updateEstudioComplementario(req.params.id, req.body);
res.json({ ok: true });
} catch (err) {
res.status(500).json({ error: 'Error al actualizar estudio' });
}
});
app.delete('/api/estudios-complementarios/:id', async (req, res) => {
try {
await deleteEstudioComplementario(req.params.id);
res.json({ ok: true });
} catch (err) {
res.status(500).json({ error: 'Error al eliminar estudio' });
}
});
// ========== INTERCONSULTAS ==========
app.get('/api/interconsultas', async (req, res) => {
try {
res.json(await getAllInterconsultas());
} catch (err) {
res.status(500).json({ error: 'Error al obtener interconsultas' });
}
});
app.post('/api/interconsultas', async (req, res) => {
try {
const interconsulta = { ...req.body, id: generateUUID() };
await createInterconsulta(interconsulta);
res.json(interconsulta);
} catch (err) {
res.status(500).json({ error: 'Error al crear interconsulta' });
}
});
app.put('/api/interconsultas/:id', async (req, res) => {
try {
await updateInterconsulta(req.params.id, req.body);
res.json({ ok: true });
} catch (err) {
res.status(500).json({ error: 'Error al actualizar interconsulta' });
}
});
app.delete('/api/interconsultas/:id', async (req, res) => {
try {
await deleteInterconsulta(req.params.id);
res.json({ ok: true });
} catch (err) {
res.status(500).json({ error: 'Error al eliminar interconsulta' });
}
});
// ========== ATB ==========
app.get('/api/atb', async (req, res) => {
try {
res.json(await getAllAtb());
} catch (err) {
res.status(500).json({ error: 'Error al obtener ATB' });
}
});
app.post('/api/atb', async (req, res) => {
try {
const atb = { ...req.body, id: generateUUID() };
await createAtb(atb);
res.json(atb);
} catch (err) {
res.status(500).json({ error: 'Error al crear ATB' });
}
});
app.put('/api/atb/:id', async (req, res) => {
try {
await updateAtb(req.params.id, req.body);
res.json({ ok: true });
} catch (err) {
res.status(500).json({ error: 'Error al actualizar ATB' });
}
});
app.delete('/api/atb/:id', async (req, res) => {
try {
await deleteAtb(req.params.id);
res.json({ ok: true });
} catch (err) {
res.status(500).json({ error: 'Error al eliminar ATB' });
}
});
// ========== INDICACIONES ==========
app.get('/api/indicaciones', async (req, res) => {
try {
res.json(await getAllIndicaciones());
} catch (err) {
res.status(500).json({ error: 'Error al obtener indicaciones' });
}
});
app.post('/api/indicaciones', async (req, res) => {
try {
const indicacion = { ...req.body, id: generateUUID() };
await createIndicacion(indicacion);
res.json(indicacion);
} catch (err) {
res.status(500).json({ error: 'Error al crear indicacion' });
}
});
app.put('/api/indicaciones/:id', async (req, res) => {
try {
await updateIndicacion(req.params.id, req.body);
res.json({ ok: true });
} catch (err) {
res.status(500).json({ error: 'Error al actualizar indicacion' });
}
});
app.delete('/api/indicaciones/:id', async (req, res) => {
try {
await deleteIndicacion(req.params.id);
res.json({ ok: true });
} catch (err) {
res.status(500).json({ error: 'Error al eliminar indicacion' });
}
});
// ========== MOVIMIENTOS INDICACIONES ==========
app.get('/api/movimientos-indicaciones', async (req, res) => {
try {
res.json(await getAllMovimientosIndicaciones());
} catch (err) {
res.status(500).json({ error: 'Error al obtener movimientos' });
}
});
app.post('/api/movimientos-indicaciones', async (req, res) => {
try {
const movimiento = { ...req.body, id: generateUUID() };
console.log('Creating movimiento:', movimiento);
await createMovimientoIndicacion(movimiento);
res.json(movimiento);
} catch (err) {
console.error('Error creating movimiento:', err);
res.status(500).json({ error: err.message || 'Error al crear movimiento' });
}
});
// ========== AUTH ==========
app.post('/api/auth/login', async (req, res) => {
try {
const { dni, password } = req.body;
if (!dni || !password) {
return res.status(400).json({ error: 'DNI y contraseña requeridos' });
}
const usuario = await getUsuarioByDni(dni);
if (!usuario) {
return res.status(401).json({ error: 'Usuario no encontrado' });
}
const validPassword = await verifyPassword(dni, password);
if (!validPassword) {
return res.status(401).json({ error: 'Contraseña incorrecta' });
}
const { passwordHash, ...userWithoutPassword } = usuario;
res.json(userWithoutPassword);
} catch (err) {
res.status(500).json({ error: 'Error en autenticación' });
}
});
app.post('/api/auth/change-password', async (req, res) => {
try {
const { dni, oldPassword, newPassword } = req.body;
if (!dni || !oldPassword || !newPassword) {
return res.status(400).json({ error: 'Todos los campos son requeridos' });
}
const validPassword = await verifyPassword(dni, oldPassword);
if (!validPassword) {
return res.status(401).json({ error: 'Contraseña actual incorrecta' });
}
const newHash = hashPassword(newPassword);
const usuario = await getUsuarioByDni(dni);
if (usuario) {
await updateUsuario(usuario.id, { passwordHash: newHash });
}
res.json({ ok: true });
} catch (err) {
res.status(500).json({ error: 'Error al cambiar contraseña' });
}
});
app.put('/api/auth/update-email', async (req, res) => {
try {
const { dni, newEmail } = req.body;
if (!dni || !newEmail) {
return res.status(400).json({ error: 'DNI y email son requeridos' });
}
const usuario = await getUsuarioByDni(dni);
if (usuario) {
await updateUsuario(usuario.id, { email: newEmail });
}
res.json({ ok: true });
} catch (err) {
res.status(500).json({ error: 'Error al actualizar email' });
}
});
// Initialize DB and start server
const PORT = process.env.PORT || 4000;
const HOST = process.env.HOST || '0.0.0.0';
if (import.meta.url === `file://${process.argv[1]}`) {
initDb()
.then(() => {
app.listen(PORT, HOST, () => {
console.log(`Backend API running on http://${HOST}:${PORT}`);
});
})
.catch((err) => {
console.error('Failed to initialize MongoDB in standalone mode:', err);
process.exit(1);
});
}
export { initDb };
export default app;
+945
View File
@@ -0,0 +1,945 @@
import { MongoClient } from 'mongodb';
import bcrypt from 'bcryptjs';
const { compareSync, hashSync } = bcrypt;
export function generateUUID() {
if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
return crypto.randomUUID();
}
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
const r = (Math.random() * 16) | 0;
const v = c === 'x' ? r : (r & 0x3) | 0x8;
return v.toString(16);
});
}
function getMongoUri() {
const envUri = (process.env.MONGODB_URI || '').trim();
if (envUri.startsWith('mongodb://') || envUri.startsWith('mongodb+srv://')) {
return envUri;
}
return 'mongodb://127.0.0.1:27017';
}
const DB_NAME = process.env.DB_NAME || 'hospital';
let client;
let db = null;
// Helper to create default admin object from env or fallback
function createDefaultAdminUser() {
const adminDni = process.env.ADMIN_DNI || '12345678';
const adminPassword = process.env.ADMIN_PASSWORD || 'admin123';
const adminNombre = process.env.ADMIN_NOMBRE || 'Sistema';
const adminApellido = process.env.ADMIN_APELLIDO || 'Administrador';
const adminEmail = process.env.ADMIN_EMAIL || 'admin@hospital.local';
return {
id: 'admin-default',
apellido: adminApellido,
nombre: adminNombre,
dni: adminDni,
fechaNacimiento: '1990-01-01',
email: adminEmail,
rol: 'admin',
passwordHash: hashSync(adminPassword, 10),
fechaCreacion: new Date().toISOString().split('T')[0]
};
}
// In-memory fallback store when DB is not connected
const memStore = {
usuarios: [createDefaultAdminUser()],
pacientes: [],
areas: [],
camas: [],
internaciones: [],
evoluciones: [],
laboratorios: [],
glucemias: [],
acidosbase: [],
cultivos: [],
estudiosComplementarios: [],
interconsultas: [],
atb: [],
indicaciones: [],
movimientos_indicaciones: [],
kv: {}
};
export async function initDb() {
try {
const uri = getMongoUri();
client = new MongoClient(uri, {
connectTimeoutMS: 5000,
serverSelectionTimeoutMS: 5000
});
await client.connect();
db = client.db(DB_NAME);
console.log(`Connected to MongoDB database: ${DB_NAME}`);
// Create unique index for users and patients DNI
await db.collection('usuarios').createIndex({ dni: 1 }, { unique: true });
await db.collection('pacientes').createIndex({ dni: 1 }, { unique: true });
await db.collection('areas').createIndex({ nombre: 1 }, { unique: true });
// Check if default admin exists in MongoDB in production
const adminDni = process.env.ADMIN_DNI || '12345678';
const adminExists = await db.collection('usuarios').findOne({ dni: adminDni });
if (!adminExists) {
const defaultAdmin = createDefaultAdminUser();
await db.collection('usuarios').insertOne(defaultAdmin);
console.log(`Default admin created/ensured in database: DNI ${adminDni}`);
}
} catch (error) {
console.error('Failed to initialize MongoDB (will use in-memory store):', error.message || error);
db = null;
}
}
// Helper to remove MongoDB's internal _id from returned objects to keep schema clean
function cleanDoc(doc) {
if (!doc) return null;
const { _id, ...rest } = doc;
return rest;
}
function cleanDocs(docs) {
if (!docs) return [];
return docs.map(cleanDoc);
}
// ========== USUARIOS ==========
export async function getUsuarioByDni(dni) {
const dniStr = String(dni).trim();
if (db) {
const user = await db.collection('usuarios').findOne({ dni: dniStr });
return cleanDoc(user);
}
return memStore.usuarios.find(u => String(u.dni) === dniStr) || null;
}
export async function getUsuarioById(id) {
if (db) {
const user = await db.collection('usuarios').findOne({ id });
return cleanDoc(user);
}
return memStore.usuarios.find(u => u.id === id) || null;
}
export async function getAllUsuarios() {
if (db) {
const users = await db.collection('usuarios').find().sort({ apellido: 1, nombre: 1 }).toArray();
return cleanDocs(users);
}
return [...memStore.usuarios];
}
export async function createUsuario(usuario) {
if (db) {
await db.collection('usuarios').insertOne(usuario);
} else {
memStore.usuarios.push(usuario);
}
}
export async function updateUsuario(id, datos) {
const updateDoc = {};
for (const [key, val] of Object.entries(datos)) {
if (val !== undefined) {
updateDoc[key] = val;
}
}
if (db) {
if (Object.keys(updateDoc).length > 0) {
await db.collection('usuarios').updateOne({ id }, { $set: updateDoc });
}
} else {
const idx = memStore.usuarios.findIndex(u => u.id === id);
if (idx !== -1) {
memStore.usuarios[idx] = { ...memStore.usuarios[idx], ...updateDoc };
}
}
}
export async function deleteUsuario(id) {
if (db) {
await db.collection('usuarios').deleteOne({ id });
} else {
memStore.usuarios = memStore.usuarios.filter(u => u.id !== id);
}
}
export async function verifyPassword(dni, password) {
const dniStr = String(dni).trim();
if (db) {
const user = await db.collection('usuarios').findOne({ dni: dniStr });
if (!user || !user.passwordHash) return false;
return compareSync(password, user.passwordHash);
}
const user = memStore.usuarios.find(u => String(u.dni) === dniStr);
if (!user || !user.passwordHash) return false;
return compareSync(password, user.passwordHash);
}
export function hashPassword(password) {
return hashSync(password, 10);
}
// ========== PACIENTES ==========
export async function getAllPacientes() {
if (db) {
const pacientes = await db.collection('pacientes').find().toArray();
return cleanDocs(pacientes);
}
return [...memStore.pacientes];
}
export async function getPacienteById(id) {
if (db) {
const paciente = await db.collection('pacientes').findOne({ id });
return cleanDoc(paciente);
}
return memStore.pacientes.find(p => p.id === id) || null;
}
export async function createPaciente(paciente) {
if (db) {
await db.collection('pacientes').insertOne(paciente);
} else {
memStore.pacientes.push(paciente);
}
}
export async function updatePaciente(id, datos) {
const updateDoc = {};
for (const [key, val] of Object.entries(datos)) {
if (key !== 'id' && val !== undefined) {
updateDoc[key] = val;
}
}
if (db) {
if (Object.keys(updateDoc).length > 0) {
await db.collection('pacientes').updateOne({ id }, { $set: updateDoc });
}
} else {
const idx = memStore.pacientes.findIndex(p => p.id === id);
if (idx !== -1) {
memStore.pacientes[idx] = { ...memStore.pacientes[idx], ...updateDoc };
}
}
}
export async function deletePaciente(id) {
if (db) {
await db.collection('pacientes').deleteOne({ id });
} else {
memStore.pacientes = memStore.pacientes.filter(p => p.id !== id);
}
}
// ========== AREAS / GRUPOS ==========
export async function getAllAreas() {
if (db) {
const areas = await db.collection('areas').find().toArray();
return cleanDocs(areas);
}
return [...memStore.areas];
}
export async function createArea(area) {
if (db) {
await db.collection('areas').insertOne(area);
} else {
memStore.areas.push(area);
}
}
export async function updateArea(id, datos) {
if (db) {
if (datos.nombre !== undefined) {
await db.collection('areas').updateOne({ id }, { $set: { nombre: datos.nombre } });
}
} else {
const idx = memStore.areas.findIndex(a => a.id === id);
if (idx !== -1) {
memStore.areas[idx] = { ...memStore.areas[idx], ...datos };
}
}
}
export async function deleteArea(id) {
if (db) {
await db.collection('areas').deleteOne({ id });
} else {
memStore.areas = memStore.areas.filter(a => a.id !== id);
}
}
// ========== CAMAS ==========
export async function getAllCamas() {
if (db) {
const camas = await db.collection('camas').find().toArray();
return cleanDocs(camas);
}
return [...memStore.camas];
}
export async function getCamaById(id) {
if (db) {
const cama = await db.collection('camas').findOne({ id });
return cleanDoc(cama);
}
return memStore.camas.find(c => c.id === id) || null;
}
export async function createCama(cama) {
const id = cama.id || generateUUID();
const doc = {
id,
numero: cama.numero,
areaId: cama.areaId || cama.grupoId,
grupoId: cama.grupoId || cama.areaId,
tipo: cama.tipo || 'Estándar',
estado: cama.estado || 'Disponible',
pacienteId: cama.pacienteId || null,
internacionId: cama.internacionId || null
};
if (db) {
await db.collection('camas').insertOne(doc);
} else {
memStore.camas.push(doc);
}
}
export async function updateCama(id, updates) {
const updateDoc = {};
for (const [key, val] of Object.entries(updates)) {
if (val !== undefined) {
updateDoc[key] = val;
}
}
if (db) {
if (Object.keys(updateDoc).length > 0) {
await db.collection('camas').updateOne({ id }, { $set: updateDoc });
}
} else {
const idx = memStore.camas.findIndex(c => c.id === id);
if (idx !== -1) {
memStore.camas[idx] = { ...memStore.camas[idx], ...updateDoc };
}
}
}
export async function deleteCama(id) {
if (db) {
await db.collection('camas').deleteOne({ id });
} else {
memStore.camas = memStore.camas.filter(c => c.id !== id);
}
}
// ========== INTERNACIONES ==========
export async function getAllInternaciones() {
if (db) {
const internaciones = await db.collection('internaciones').find().toArray();
return cleanDocs(internaciones).map(i => ({ ...i, activa: !!i.activa }));
}
return memStore.internaciones.map(i => ({ ...i, activa: !!i.activa }));
}
export async function getInternacionById(id) {
if (db) {
const internacion = await db.collection('internaciones').findOne({ id });
if (internacion) internacion.activa = !!internacion.activa;
return cleanDoc(internacion);
}
const internacion = memStore.internaciones.find(i => i.id === id);
if (!internacion) return null;
return { ...internacion, activa: !!internacion.activa };
}
export async function createInternacion(internacion) {
const doc = {
id: internacion.id,
pacienteId: internacion.pacienteId,
camaId: internacion.camaId || null,
areaId: internacion.areaId || internacion.grupoId || null,
grupoId: internacion.grupoId || internacion.areaId || null,
fechaIngresoHospital: internacion.fechaIngresoHospital || null,
fechaIngresoClinica: internacion.fechaIngresoClinica || null,
fechaEgreso: internacion.fechaEgreso || null,
diagnosticoIngreso: internacion.diagnosticoIngreso || null,
motivoConsulta: internacion.motivoConsulta || null,
enfermedadActual: internacion.enfermedadActual || null,
antecedentesEnfermedadActual: internacion.antecedentesEnfermedadActual || null,
diagnosticoEgreso: internacion.diagnosticoEgreso || null,
medicoIngresante: internacion.medicoIngresante || null,
motivoEgreso: internacion.motivoEgreso || null,
activa: internacion.activa ? 1 : 0,
apache: internacion.apache || null,
derivacion: internacion.derivacion || null
};
if (db) {
await db.collection('internaciones').insertOne(doc);
} else {
memStore.internaciones.push(doc);
}
}
export async function updateInternacion(id, datos) {
const updateDoc = {};
for (const [key, val] of Object.entries(datos)) {
if (val !== undefined) {
if (key === 'activa') {
updateDoc.activa = val ? 1 : 0;
} else {
updateDoc[key] = val;
}
}
}
if (db) {
if (Object.keys(updateDoc).length > 0) {
await db.collection('internaciones').updateOne({ id }, { $set: updateDoc });
}
} else {
const idx = memStore.internaciones.findIndex(i => i.id === id);
if (idx !== -1) {
memStore.internaciones[idx] = { ...memStore.internaciones[idx], ...updateDoc };
}
}
}
export async function deleteInternacion(id) {
if (db) {
await db.collection('internaciones').deleteOne({ id });
} else {
memStore.internaciones = memStore.internaciones.filter(i => i.id !== id);
}
}
// ========== EVOLUCIONES ==========
export async function getAllEvoluciones() {
if (db) {
const evoluciones = await db.collection('evoluciones').find().toArray();
return cleanDocs(evoluciones);
}
return [...memStore.evoluciones];
}
export async function createEvolucion(evolucion) {
const doc = {
id: evolucion.id,
internacionId: evolucion.internacionId,
fecha: evolucion.fecha,
hora: evolucion.hora || '',
medico: evolucion.medico || '',
signosVitales: typeof evolucion.signosVitales === 'string' ? evolucion.signosVitales : JSON.stringify(evolucion.signosVitales || {}),
examenFisico: typeof evolucion.examenFisico === 'string' ? evolucion.examenFisico : JSON.stringify(evolucion.examenFisico || {}),
novedades: evolucion.novedades || '',
comentarios: evolucion.comentarios || evolucion.comentario || '',
pendientes: evolucion.pendientes || ''
};
if (db) {
await db.collection('evoluciones').insertOne(doc);
} else {
memStore.evoluciones.push(doc);
}
}
export async function updateEvolucion(id, datos) {
const updateDoc = {};
for (const [key, val] of Object.entries(datos)) {
if (val !== undefined) {
if (key === 'comentario') {
updateDoc.comentarios = val;
} else if (key === 'signosVitales' || key === 'examenFisico') {
updateDoc[key] = typeof val === 'string' ? val : JSON.stringify(val);
} else {
updateDoc[key] = val;
}
}
}
if (db) {
if (Object.keys(updateDoc).length > 0) {
await db.collection('evoluciones').updateOne({ id }, { $set: updateDoc });
}
} else {
const idx = memStore.evoluciones.findIndex(e => e.id === id);
if (idx !== -1) {
memStore.evoluciones[idx] = { ...memStore.evoluciones[idx], ...updateDoc };
}
}
}
export async function deleteEvolucion(id) {
if (db) {
await db.collection('evoluciones').deleteOne({ id });
} else {
memStore.evoluciones = memStore.evoluciones.filter(e => e.id !== id);
}
}
// ========== LABORATORIOS ==========
export async function getAllLaboratorios() {
if (db) {
const laboratorios = await db.collection('laboratorios').find().toArray();
return cleanDocs(laboratorios);
}
return [...memStore.laboratorios];
}
export async function createLaboratorio(laboratorio) {
const doc = {
id: laboratorio.id,
pacienteId: laboratorio.pacienteId,
internacionId: laboratorio.internacionId || null,
fecha: laboratorio.fecha,
hora: laboratorio.hora || null,
tipo: laboratorio.tipo,
resultados: typeof laboratorio.resultados === 'string' ? laboratorio.resultados : JSON.stringify(laboratorio.resultados || {}),
observaciones: laboratorio.observaciones || null,
medicoSolicitante: laboratorio.medicoSolicitante || null
};
if (db) {
await db.collection('laboratorios').insertOne(doc);
} else {
memStore.laboratorios.push(doc);
}
}
export async function updateLaboratorio(id, datos) {
const updateDoc = {};
for (const [key, val] of Object.entries(datos)) {
if (val !== undefined) {
if (key === 'resultados') {
updateDoc.resultados = typeof val === 'string' ? val : JSON.stringify(val);
} else {
updateDoc[key] = val;
}
}
}
if (db) {
if (Object.keys(updateDoc).length > 0) {
await db.collection('laboratorios').updateOne({ id }, { $set: updateDoc });
}
} else {
const idx = memStore.laboratorios.findIndex(l => l.id === id);
if (idx !== -1) {
memStore.laboratorios[idx] = { ...memStore.laboratorios[idx], ...updateDoc };
}
}
}
export async function deleteLaboratorio(id) {
if (db) {
await db.collection('laboratorios').deleteOne({ id });
} else {
memStore.laboratorios = memStore.laboratorios.filter(l => l.id !== id);
}
}
// ========== GLUCEMIAS ==========
export async function getAllGlucemias() {
if (db) {
const glucemias = await db.collection('glucemias').find().toArray();
return cleanDocs(glucemias);
}
return [...memStore.glucemias];
}
export async function createGlucemia(glucemia) {
if (db) {
await db.collection('glucemias').insertOne(glucemia);
} else {
memStore.glucemias.push(glucemia);
}
}
export async function updateGlucemia(id, datos) {
if (db) {
await db.collection('glucemias').updateOne({ id }, { $set: datos });
} else {
const idx = memStore.glucemias.findIndex(g => g.id === id);
if (idx !== -1) {
memStore.glucemias[idx] = { ...memStore.glucemias[idx], ...datos };
}
}
}
export async function deleteGlucemia(id) {
if (db) {
await db.collection('glucemias').deleteOne({ id });
} else {
memStore.glucemias = memStore.glucemias.filter(g => g.id !== id);
}
}
// ========== ACIDOS BASE ==========
export async function getAllAcidosBase() {
if (db) {
const acidos = await db.collection('acidosbase').find().toArray();
return cleanDocs(acidos);
}
return [...memStore.acidosbase];
}
export async function createAcidoBase(acido) {
const doc = {
id: acido.id,
pacienteId: acido.pacienteId,
internacionId: acido.internacionId || null,
fecha: acido.fecha,
hora: acido.hora || null,
ph: acido.ph !== undefined ? Number(acido.ph) : null,
pco2: acido.pco2 !== undefined ? Number(acido.pco2) : null,
po2: acido.po2 !== undefined ? Number(acido.po2) : null,
hco3: acido.hco3 !== undefined ? Number(acido.hco3) : null,
be: acido.be !== undefined ? Number(acido.be) : null,
sato2: acido.sato2 !== undefined ? Number(acido.sato2) : null,
lactato: acido.lactato !== undefined ? Number(acido.lactato) : null,
interpretacion: acido.interpretacion || null,
fio2: acido.fio2 !== undefined ? Number(acido.fio2) : null
};
if (db) {
await db.collection('acidosbase').insertOne(doc);
} else {
memStore.acidosbase.push(doc);
}
}
export async function updateAcidoBase(id, datos) {
if (db) {
await db.collection('acidosbase').updateOne({ id }, { $set: datos });
} else {
const idx = memStore.acidosbase.findIndex(a => a.id === id);
if (idx !== -1) {
memStore.acidosbase[idx] = { ...memStore.acidosbase[idx], ...datos };
}
}
}
export async function deleteAcidoBase(id) {
if (db) {
await db.collection('acidosbase').deleteOne({ id });
} else {
memStore.acidosbase = memStore.acidosbase.filter(a => a.id !== id);
}
}
// ========== CULTIVOS ==========
export async function getAllCultivos() {
if (db) {
const cultivos = await db.collection('cultivos').find().toArray();
return cleanDocs(cultivos);
}
return [...memStore.cultivos];
}
export async function createCultivo(cultivo) {
const doc = {
id: cultivo.id,
pacienteId: cultivo.pacienteId,
internacionId: cultivo.internacionId,
fechaToma: cultivo.fechaToma || null,
protocolo: cultivo.protocolo || null,
fechaResultado: cultivo.fechaResultado || null,
tipoMuestra: cultivo.tipoMuestra || null,
germen: cultivo.germen || null,
sensible: cultivo.sensible || null,
resistente: cultivo.resistente || null,
estado: cultivo.estado || 'NAF/Pendiente',
observaciones: cultivo.observaciones || null
};
if (db) {
await db.collection('cultivos').insertOne(doc);
} else {
memStore.cultivos.push(doc);
}
}
export async function updateCultivo(id, datos) {
if (db) {
const updateDoc = {};
for (const [key, val] of Object.entries(datos)) {
if (val !== undefined) {
updateDoc[key] = val;
}
}
if (Object.keys(updateDoc).length > 0) {
await db.collection('cultivos').updateOne({ id }, { $set: updateDoc });
}
} else {
const idx = memStore.cultivos.findIndex(c => c.id === id);
if (idx !== -1) {
memStore.cultivos[idx] = { ...memStore.cultivos[idx], ...datos };
}
}
}
export async function deleteCultivo(id) {
if (db) {
await db.collection('cultivos').deleteOne({ id });
} else {
memStore.cultivos = memStore.cultivos.filter(c => c.id !== id);
}
}
// ========== ESTUDIOS COMPLEMENTARIOS ==========
export async function getAllEstudiosComplementarios() {
if (db) {
const estudios = await db.collection('estudiosComplementarios').find().toArray();
return cleanDocs(estudios);
}
return [...memStore.estudiosComplementarios];
}
export async function createEstudioComplementario(estudio) {
const doc = {
id: estudio.id,
pacienteId: estudio.pacienteId,
internacionId: estudio.internacionId || null,
fecha: estudio.fecha || null,
tipo: estudio.tipo || null,
resultado: estudio.resultado || null
};
if (db) {
await db.collection('estudiosComplementarios').insertOne(doc);
} else {
memStore.estudiosComplementarios.push(doc);
}
}
export async function updateEstudioComplementario(id, datos) {
if (db) {
await db.collection('estudiosComplementarios').updateOne({ id }, { $set: datos });
} else {
const idx = memStore.estudiosComplementarios.findIndex(e => e.id === id);
if (idx !== -1) {
memStore.estudiosComplementarios[idx] = { ...memStore.estudiosComplementarios[idx], ...datos };
}
}
}
export async function deleteEstudioComplementario(id) {
if (db) {
await db.collection('estudiosComplementarios').deleteOne({ id });
} else {
memStore.estudiosComplementarios = memStore.estudiosComplementarios.filter(e => e.id !== id);
}
}
// ========== INTERCONSULTAS ==========
export async function getAllInterconsultas() {
if (db) {
const interconsultas = await db.collection('interconsultas').find().toArray();
return cleanDocs(interconsultas).map(ic => ({ ...ic, realizada: !!ic.realizada }));
}
return memStore.interconsultas.map(ic => ({ ...ic, realizada: !!ic.realizada }));
}
export async function createInterconsulta(ic) {
const doc = {
id: ic.id,
pacienteId: ic.pacienteId,
internacionId: ic.internacionId,
fecha: ic.fecha || null,
servicioInterconsultado: ic.servicioInterconsultado,
motivo: ic.motivo || null,
respuestaInterconsulta: ic.respuestaInterconsulta || null,
respuestaFecha: ic.respuestaFecha || null
};
if (db) {
await db.collection('interconsultas').insertOne(doc);
} else {
memStore.interconsultas.push(doc);
}
}
export async function updateInterconsulta(id, datos) {
if (db) {
const updateDoc = {};
for (const [key, val] of Object.entries(datos)) {
if (val !== undefined) {
updateDoc[key] = val;
}
}
if (Object.keys(updateDoc).length > 0) {
await db.collection('interconsultas').updateOne({ id }, { $set: updateDoc });
}
} else {
const idx = memStore.interconsultas.findIndex(i => i.id === id);
if (idx !== -1) {
memStore.interconsultas[idx] = { ...memStore.interconsultas[idx], ...datos };
}
}
}
export async function deleteInterconsulta(id) {
if (db) {
await db.collection('interconsultas').deleteOne({ id });
} else {
memStore.interconsultas = memStore.interconsultas.filter(i => i.id !== id);
}
}
// ========== ATB ==========
export async function getAllAtb() {
if (db) {
const atb = await db.collection('atb').find().toArray();
return cleanDocs(atb);
}
return [...memStore.atb];
}
export async function createAtb(atb) {
const doc = {
id: atb.id,
pacienteId: atb.pacienteId,
internacionId: atb.internacionId,
antibiotico: atb.antibiotico,
fechaInicio: atb.fechaInicio,
fechaFinalizacion: atb.fechaFinalizacion || null
};
if (db) {
await db.collection('atb').insertOne(doc);
} else {
memStore.atb.push(doc);
}
}
export async function updateAtb(id, datos) {
if (db) {
await db.collection('atb').updateOne({ id }, { $set: datos });
} else {
const idx = memStore.atb.findIndex(a => a.id === id);
if (idx !== -1) {
memStore.atb[idx] = { ...memStore.atb[idx], ...datos };
}
}
}
export async function deleteAtb(id) {
if (db) {
await db.collection('atb').deleteOne({ id });
} else {
memStore.atb = memStore.atb.filter(a => a.id !== id);
}
}
// ========== INDICACIONES ==========
export async function getAllIndicaciones() {
if (db) {
const indicaciones = await db.collection('indicaciones').find().toArray();
return cleanDocs(indicaciones);
}
return [...memStore.indicaciones];
}
export async function createIndicacion(ind) {
const doc = {
id: ind.id,
internacionId: ind.internacionId,
tipo: ind.tipo,
droga: ind.droga || null,
dosis: ind.dosis || null,
frecuenciaHoras: ind.frecuenciaHoras || null,
via: ind.via || null,
tipoPlan: ind.tipoPlan || null,
tipoPlan2: ind.tipoPlan2 || null,
cantidadMl: ind.cantidadMl || null,
cantidadMl2: ind.cantidadMl2 || null,
tiempoHoras: ind.tiempoHoras || null,
estado: ind.estado || 'Activa',
medicoCrea: ind.medicoCrea || null,
fechaCrea: ind.fechaCrea || null,
tipoInsulina: ind.tipoInsulina || null,
unidadesDesayuno: ind.unidadesDesayuno || null,
unidadesAlmuerzo: ind.unidadesAlmuerzo || null,
unidadesNoche: ind.unidadesNoche || null,
indicacionNoFco: ind.indicacionNoFco || null
};
if (db) {
await db.collection('indicaciones').insertOne(doc);
} else {
memStore.indicaciones.push(doc);
}
}
export async function updateIndicacion(id, datos) {
const updateDoc = {};
for (const [key, val] of Object.entries(datos)) {
if (val !== undefined) {
updateDoc[key] = val;
}
}
if (db) {
if (Object.keys(updateDoc).length > 0) {
await db.collection('indicaciones').updateOne({ id }, { $set: updateDoc });
}
} else {
const idx = memStore.indicaciones.findIndex(i => i.id === id);
if (idx !== -1) {
memStore.indicaciones[idx] = { ...memStore.indicaciones[idx], ...updateDoc };
}
}
}
export async function deleteIndicacion(id) {
if (db) {
await db.collection('indicaciones').deleteOne({ id });
} else {
memStore.indicaciones = memStore.indicaciones.filter(i => i.id !== id);
}
}
// ========== MOVIMIENTOS INDICACIONES ==========
export async function getAllMovimientosIndicaciones() {
if (db) {
const movimientos = await db.collection('movimientos_indicaciones').find().toArray();
return cleanDocs(movimientos);
}
return [...memStore.movimientos_indicaciones];
}
export async function createMovimientoIndicacion(mov) {
const doc = {
id: mov.id,
indicacionId: mov.indicacionId,
internacionId: mov.internacionId,
tipo: mov.tipo,
fecha: mov.fecha,
profesional: mov.profesional,
indicacionPrevia: mov.indicacionPrevia || null,
indicacionNueva: mov.indicacionNueva || null
};
if (db) {
await db.collection('movimientos_indicaciones').insertOne(doc);
} else {
memStore.movimientos_indicaciones.push(doc);
}
}
// ========== KV STORE ==========
export async function getValue(key) {
if (db) {
const doc = await db.collection('kv').findOne({ key });
return doc?.value ?? null;
}
return memStore.kv[key] ?? null;
}
export async function setValue(key, value) {
if (db) {
await db.collection('kv').updateOne(
{ key },
{ $set: { key, value } },
{ upsert: true }
);
} else {
memStore.kv[key] = value;
}
}
+1220
View File
File diff suppressed because it is too large Load Diff
+15
View File
@@ -0,0 +1,15 @@
{
"name": "hospital-server",
"version": "1.0.0",
"type": "module",
"scripts": {
"start": "node index.js"
},
"dependencies": {
"bcryptjs": "^2.4.3",
"cors": "^2.8.5",
"express": "^4.21.2",
"http-proxy-middleware": "^3.0.5",
"mongodb": "^6.8.0"
}
}