1530 lines
53 KiB
JavaScript
1530 lines
53 KiB
JavaScript
import { MongoClient, ObjectId } 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: [],
|
|
otrosLaboratorios: [],
|
|
glucemias: [],
|
|
acidosbase: [],
|
|
cultivos: [],
|
|
estudiosComplementarios: [],
|
|
interconsultas: [],
|
|
atb: [],
|
|
indicaciones: [],
|
|
movimientos_indicaciones: [],
|
|
pendientes: [],
|
|
tipos_cultivo: [],
|
|
grupos_laboratorio: [],
|
|
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;
|
|
if (!rest.id && _id) {
|
|
rest.id = _id.toString();
|
|
}
|
|
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 (!id) return null;
|
|
const idStr = String(id).trim();
|
|
if (db) {
|
|
let internacion = await db.collection('internaciones').findOne({ id: idStr });
|
|
if (!internacion && ObjectId.isValid(idStr)) {
|
|
try {
|
|
internacion = await db.collection('internaciones').findOne({ _id: new ObjectId(idStr) });
|
|
} catch {
|
|
// ignore invalid objectid
|
|
}
|
|
}
|
|
if (internacion) internacion.activa = !!internacion.activa;
|
|
return cleanDoc(internacion);
|
|
}
|
|
const internacion = memStore.internaciones.find(i => i.id === idStr || String(i.id).trim() === idStr);
|
|
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,
|
|
servicioAlQuePasa: internacion.servicioAlQuePasa || 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);
|
|
}
|
|
}
|
|
|
|
// ========== TIPOS DE CULTIVO ==========
|
|
export const DEFAULT_TIPOS_CULTIVO = [
|
|
{ id: '1', nombre: 'HMCx2', categoria: 'Hemocultivos', descripcion: 'Hemocultivos seriados x2' },
|
|
{ id: '2', nombre: 'RC', categoria: 'Catéter', descripcion: 'Retro cultivo / punta de catéter' },
|
|
{ id: '3', nombre: 'PC', categoria: 'Punción', descripcion: 'Punción cultivo' },
|
|
{ id: '4', nombre: 'UC', categoria: 'Urocultivo', descripcion: 'Urocultivo / muestra de orina' },
|
|
{ id: '5', nombre: 'LCR', categoria: 'Líquidos', descripcion: 'Líquido cefalorraquídeo' },
|
|
{ id: '6', nombre: 'LP', categoria: 'Líquidos', descripcion: 'Líquido pleural' },
|
|
{ id: '7', nombre: 'LAsc', categoria: 'Líquidos', descripcion: 'Líquido ascítico' },
|
|
{ id: '8', nombre: 'LAbd', categoria: 'Líquidos', descripcion: 'Líquido abdominal' },
|
|
{ id: '9', nombre: 'Coleccion', categoria: 'Líquidos y Colecciones', descripcion: 'Muestra de colección / absceso' },
|
|
{ id: '10', nombre: 'Partes Blandas', categoria: 'Tejidos', descripcion: 'Cultivo de partes blandas / tejido' },
|
|
{ id: '11', nombre: 'Esputo GC', categoria: 'Respiratorio', descripcion: 'Esputo Germen Común' },
|
|
{ id: '12', nombre: 'Esputo TBC', categoria: 'Respiratorio', descripcion: 'Esputo Tuberculosis' },
|
|
{ id: '13', nombre: 'Baciloscopia', categoria: 'Respiratorio', descripcion: 'Baciloscopia directa' },
|
|
{ id: '14', nombre: 'HNF Test Rápido', categoria: 'Hisopado Nasofaríngeo', descripcion: 'Hisopado nasofaríngeo test rápido' },
|
|
{ id: '15', nombre: 'HNF Panel PCR', categoria: 'Hisopado Nasofaríngeo', descripcion: 'Hisopado nasofaríngeo panel PCR virológico' },
|
|
{ id: '16', nombre: 'Hisopado Rectal KPC', categoria: 'Vigilancia Epidemiológica', descripcion: 'Hisopado rectal para screening de KPC/BLEE' }
|
|
];
|
|
|
|
export async function getAllTiposCultivo() {
|
|
let list = [];
|
|
if (db) {
|
|
const raw = await db.collection('tipos_cultivo').find().toArray();
|
|
list = cleanDocs(raw);
|
|
if (list.length === 0) {
|
|
// Seed default
|
|
for (const item of DEFAULT_TIPOS_CULTIVO) {
|
|
await db.collection('tipos_cultivo').insertOne({ ...item });
|
|
}
|
|
list = [...DEFAULT_TIPOS_CULTIVO];
|
|
}
|
|
// Also read any existing custom tipoMuestra from registered cultivos that might not be in the list
|
|
const allCultivos = await db.collection('cultivos').find().toArray();
|
|
const existingNombres = new Set(list.map(t => (t.nombre || '').trim().toLowerCase()));
|
|
for (const c of allCultivos) {
|
|
if (c.tipoMuestra && c.tipoMuestra.trim() && !existingNombres.has(c.tipoMuestra.trim().toLowerCase())) {
|
|
const nuevoTipo = {
|
|
id: generateUUID(),
|
|
nombre: c.tipoMuestra.trim(),
|
|
categoria: 'Personalizado',
|
|
descripcion: 'Importado automáticamente desde registro existente de cultivo'
|
|
};
|
|
await db.collection('tipos_cultivo').insertOne(nuevoTipo);
|
|
list.push(nuevoTipo);
|
|
existingNombres.add(c.tipoMuestra.trim().toLowerCase());
|
|
}
|
|
}
|
|
return list;
|
|
} else {
|
|
if (!memStore.tipos_cultivo || memStore.tipos_cultivo.length === 0) {
|
|
memStore.tipos_cultivo = DEFAULT_TIPOS_CULTIVO.map(item => ({ ...item }));
|
|
}
|
|
list = [...memStore.tipos_cultivo];
|
|
const existingNombres = new Set(list.map(t => (t.nombre || '').trim().toLowerCase()));
|
|
for (const c of (memStore.cultivos || [])) {
|
|
if (c.tipoMuestra && c.tipoMuestra.trim() && !existingNombres.has(c.tipoMuestra.trim().toLowerCase())) {
|
|
const nuevoTipo = {
|
|
id: generateUUID(),
|
|
nombre: c.tipoMuestra.trim(),
|
|
categoria: 'Personalizado',
|
|
descripcion: 'Importado automáticamente desde registro existente de cultivo'
|
|
};
|
|
memStore.tipos_cultivo.push(nuevoTipo);
|
|
list.push(nuevoTipo);
|
|
existingNombres.add(c.tipoMuestra.trim().toLowerCase());
|
|
}
|
|
}
|
|
return list;
|
|
}
|
|
}
|
|
|
|
export async function createTipoCultivo(tipo) {
|
|
const doc = {
|
|
id: tipo.id || generateUUID(),
|
|
nombre: (tipo.nombre || '').trim(),
|
|
categoria: (tipo.categoria || 'General').trim(),
|
|
descripcion: (tipo.descripcion || '').trim()
|
|
};
|
|
if (!doc.nombre) {
|
|
throw new Error('El nombre del tipo de cultivo es requerido');
|
|
}
|
|
|
|
if (db) {
|
|
const existing = await db.collection('tipos_cultivo').findOne({ nombre: doc.nombre });
|
|
if (existing) {
|
|
throw new Error(`Ya existe un tipo de cultivo con el nombre "${doc.nombre}"`);
|
|
}
|
|
await db.collection('tipos_cultivo').insertOne(doc);
|
|
} else {
|
|
if (!memStore.tipos_cultivo) memStore.tipos_cultivo = [];
|
|
const exists = memStore.tipos_cultivo.some(t => t.nombre.toLowerCase() === doc.nombre.toLowerCase());
|
|
if (exists) {
|
|
throw new Error(`Ya existe un tipo de cultivo con el nombre "${doc.nombre}"`);
|
|
}
|
|
memStore.tipos_cultivo.push(doc);
|
|
}
|
|
return doc;
|
|
}
|
|
|
|
export async function updateTipoCultivo(id, datos) {
|
|
const updateDoc = {};
|
|
if (datos.nombre !== undefined) updateDoc.nombre = datos.nombre.trim();
|
|
if (datos.categoria !== undefined) updateDoc.categoria = datos.categoria.trim();
|
|
if (datos.descripcion !== undefined) updateDoc.descripcion = datos.descripcion.trim();
|
|
|
|
if (db) {
|
|
if (updateDoc.nombre) {
|
|
const existing = await db.collection('tipos_cultivo').findOne({ nombre: updateDoc.nombre, id: { $ne: id } });
|
|
if (existing) {
|
|
throw new Error(`Ya existe otro tipo de cultivo con el nombre "${updateDoc.nombre}"`);
|
|
}
|
|
}
|
|
await db.collection('tipos_cultivo').updateOne({ id }, { $set: updateDoc });
|
|
} else {
|
|
if (!memStore.tipos_cultivo) memStore.tipos_cultivo = [];
|
|
if (updateDoc.nombre) {
|
|
const exists = memStore.tipos_cultivo.some(t => t.id !== id && t.nombre.toLowerCase() === updateDoc.nombre.toLowerCase());
|
|
if (exists) {
|
|
throw new Error(`Ya existe otro tipo de cultivo con el nombre "${updateDoc.nombre}"`);
|
|
}
|
|
}
|
|
const idx = memStore.tipos_cultivo.findIndex(t => t.id === id);
|
|
if (idx !== -1) {
|
|
memStore.tipos_cultivo[idx] = { ...memStore.tipos_cultivo[idx], ...updateDoc };
|
|
}
|
|
}
|
|
return { id, ...datos };
|
|
}
|
|
|
|
export async function deleteTipoCultivo(id) {
|
|
if (db) {
|
|
await db.collection('tipos_cultivo').deleteOne({ id });
|
|
} else {
|
|
if (memStore.tipos_cultivo) {
|
|
memStore.tipos_cultivo = memStore.tipos_cultivo.filter(t => t.id !== id);
|
|
}
|
|
}
|
|
return { success: true };
|
|
}
|
|
|
|
export async function restablecerTiposCultivo() {
|
|
if (db) {
|
|
await db.collection('tipos_cultivo').deleteMany({});
|
|
for (const item of DEFAULT_TIPOS_CULTIVO) {
|
|
await db.collection('tipos_cultivo').insertOne({ ...item });
|
|
}
|
|
return await getAllTiposCultivo();
|
|
} else {
|
|
memStore.tipos_cultivo = DEFAULT_TIPOS_CULTIVO.map(item => ({ ...item }));
|
|
return [...memStore.tipos_cultivo];
|
|
}
|
|
}
|
|
|
|
// ========== GRUPOS DE DETERMINACIONES DE LABORATORIO ==========
|
|
export const DEFAULT_GRUPOS_LABORATORIO = [
|
|
{
|
|
id: 'grp-lipidos',
|
|
nombreGrupo: 'Perfil Lipídico',
|
|
descripcion: 'Determinaciones del metabolismo lipídico y riesgo aterogénico',
|
|
activo: true,
|
|
orden: 1,
|
|
determinaciones: [
|
|
{ id: 'lip-1', nombre: 'Colesterol Total', claves: ['colesterol total', 'colesterol', 'colest. total', 'col. total', 'colest total'], unidad: 'mg/dL', rangoReferencia: '< 200 mg/dL', esAdicional: true },
|
|
{ id: 'lip-2', nombre: 'Colesterol LDL', claves: ['colesterol ldl', 'ldl-c', 'ldl', 'colest. ldl', 'colest ldl'], unidad: 'mg/dL', rangoReferencia: '< 100 mg/dL', esAdicional: true },
|
|
{ id: 'lip-3', nombre: 'Colesterol No HDL', claves: ['colesterol no-hdl', 'colesterol no hdl', 'no-hdl', 'no hdl', 'no_hdl'], unidad: 'mg/dL', rangoReferencia: '< 130 mg/dL', esAdicional: true },
|
|
{ id: 'lip-4', nombre: 'Colesterol HDL', claves: ['colesterol hdl', 'hdl-c', 'hdl', 'colest. hdl', 'colest hdl'], unidad: 'mg/dL', rangoReferencia: '> 40 mg/dL', esAdicional: true },
|
|
{ id: 'lip-5', nombre: 'Triglicéridos', claves: ['triglicéridos', 'trigliceridos', 'tg', 'triglicérido', 'triglicerido'], unidad: 'mg/dL', rangoReferencia: '< 150 mg/dL', esAdicional: true }
|
|
]
|
|
},
|
|
{
|
|
id: 'grp-ferrico',
|
|
nombreGrupo: 'Perfil Férrico',
|
|
descripcion: 'Metabolismo del hierro, transferrina, ferritina y vitaminas hematopoyéticas',
|
|
activo: true,
|
|
orden: 2,
|
|
determinaciones: [
|
|
{ id: 'fer-1', nombre: 'Hierro', claves: ['hierro', 'sideremia', 'fe'], unidad: 'µg/dL', rangoReferencia: '60 - 170 µg/dL', esAdicional: true },
|
|
{ id: 'fer-2', nombre: 'Transferrina', claves: ['transferrina', 'transferrin'], unidad: 'mg/dL', rangoReferencia: '200 - 360 mg/dL', esAdicional: true },
|
|
{ id: 'fer-3', nombre: 'Porcentaje de Saturación de Transferrina', claves: ['porcentaje de saturacion de transferrina', 'porcentaje de saturación de transferrina', 'saturacion de transferrina', 'saturación de transferrina', 'sat. transferrina', 'sat transferrina', '% sat', '% de sat', '% saturacion', '% saturación'], unidad: '%', rangoReferencia: '20 - 50 %', esAdicional: true },
|
|
{ id: 'fer-4', nombre: 'Ferritina', claves: ['ferritina', 'ferritin'], unidad: 'ng/mL', rangoReferencia: '30 - 400 ng/mL', esAdicional: true },
|
|
{ id: 'fer-5', nombre: 'Ácido Fólico', claves: ['acido folico', 'ácido fólico', 'folato', 'folatos', 'folico', 'fólico'], unidad: 'ng/mL', rangoReferencia: '3.0 - 17.0 ng/mL', esAdicional: true },
|
|
{ id: 'fer-6', nombre: 'Vitamina B12', claves: ['vitamina b12', 'b12', 'vit. b12'], unidad: 'pg/mL', rangoReferencia: '200 - 900 pg/mL', esAdicional: true }
|
|
]
|
|
},
|
|
{
|
|
id: 'grp-fosfocalcico',
|
|
nombreGrupo: 'Metabolismo Fosfocálcico y Medio Interno Extra',
|
|
descripcion: 'Calcio total, calcio iónico, fósforo y magnesio sérico',
|
|
activo: true,
|
|
orden: 3,
|
|
determinaciones: [
|
|
{ id: 'fcal-1', nombre: 'Calcio Total', claves: ['calcio total', 'calcio', 'ca total', 'ca+', 'ca++'], unidad: 'mg/dL', rangoReferencia: '8.5 - 10.5 mg/dL', esAdicional: true },
|
|
{ id: 'fcal-2', nombre: 'Calcio Iónico', claves: ['calcio ionico', 'calcio iónico', 'ca ionico', 'ca iónico', 'ca++ ionico', 'ca++ iónico', 'calcio_ionico'], unidad: 'mmol/L', rangoReferencia: '1.15 - 1.33 mmol/L', esAdicional: true },
|
|
{ id: 'fcal-3', nombre: 'Fósforo', claves: ['fósforo', 'fosforo', 'fosfemia'], unidad: 'mg/dL', rangoReferencia: '2.5 - 4.5 mg/dL', esAdicional: true },
|
|
{ id: 'fcal-4', nombre: 'Magnesio', claves: ['magnesio', 'magnesemia', 'mg++', 'mg+', 'mg2+', 'mg 2+', 'mg.', 'magnesio plasmatico', 'magnesio plasmático', 'magnesio serico', 'magnesio sérico', 'magnesio en sangre', 'mg serico', 'mg sérico', 'mg plasmatico', 'mg plasmático', 'mg'], unidad: 'mg/dL', rangoReferencia: '1.7 - 2.4 mg/dL', esAdicional: true }
|
|
]
|
|
},
|
|
{
|
|
id: 'grp-enzimas-inflamacion',
|
|
nombreGrupo: 'Enzimas, Proteínas e Inflamación',
|
|
descripcion: 'Albúmina, FAL, LDH, Procalcitonina, PCR, eritrosedimentación y enzimas',
|
|
activo: true,
|
|
orden: 4,
|
|
determinaciones: [
|
|
{ id: 'enz-1', nombre: 'Albúmina', claves: ['albúmina', 'albumina'], unidad: 'g/dL', rangoReferencia: '3.5 - 5.0 g/dL', esAdicional: true },
|
|
{ id: 'enz-2', nombre: 'Fosfatasa Alcalina', claves: ['fosfatasa alcalina', 'fal'], unidad: 'U/L', rangoReferencia: '40 - 130 U/L', esAdicional: true },
|
|
{ id: 'enz-3', nombre: 'LDH', claves: ['ldh', 'lactato deshidrogenasa', 'lactatodeshidrogenasa'], unidad: 'U/L', rangoReferencia: '135 - 225 U/L', esAdicional: true },
|
|
{ id: 'enz-4', nombre: 'Procalcitonina', claves: ['procalcitonina', 'pct'], unidad: 'ng/mL', rangoReferencia: '< 0.5 ng/mL', esAdicional: true },
|
|
{ id: 'enz-5', nombre: 'Proteína C Reactiva', claves: ['proteina c reactiva', 'proteína c reactiva', 'pcr cuantitativa', 'pcr ultrasensible', 'pcr'], unidad: 'mg/L', rangoReferencia: '< 5 mg/L', esAdicional: true },
|
|
{ id: 'enz-6', nombre: 'Eritrosedimentación', claves: ['eritrosedimentacion', 'eritrosedimentación', 'vsg', 'esr', 'eritro'], unidad: 'mm/h', rangoReferencia: '< 20 mm/h', esAdicional: true },
|
|
{ id: 'enz-7', nombre: 'CPK', claves: ['cpk', 'creatinfosfoquinasa', 'creatin fosfoquinasa', 'ck total', 'ck'], unidad: 'U/L', rangoReferencia: '20 - 200 U/L', esAdicional: true },
|
|
{ id: 'enz-8', nombre: 'Amilasa', claves: ['amilasa', 'amilasemia'], unidad: 'U/L', rangoReferencia: '28 - 100 U/L', esAdicional: true },
|
|
{ id: 'enz-9', nombre: 'Lipasa', claves: ['lipasa', 'lipasemia'], unidad: 'U/L', rangoReferencia: '13 - 60 U/L', esAdicional: true }
|
|
]
|
|
},
|
|
{
|
|
id: 'grp-tiroideo',
|
|
nombreGrupo: 'Perfil Tiroideo',
|
|
descripcion: 'Hormonas tiroideas e hipofisarias (TSH, T4L, T4, T3)',
|
|
activo: true,
|
|
orden: 5,
|
|
determinaciones: [
|
|
{ id: 'tir-1', nombre: 'TSH', claves: ['tsh', 'tirotrofina', 'tirotropina', 'tsh ultrasensible'], unidad: 'uUI/mL', rangoReferencia: '0.4 - 4.0 uUI/mL', esAdicional: true },
|
|
{ id: 'tir-2', nombre: 'T4 Libre', claves: ['t4 libre', 't4l', 't4-l', 'tiroxina libre'], unidad: 'ng/dL', rangoReferencia: '0.8 - 1.8 ng/dL', esAdicional: true },
|
|
{ id: 'tir-3', nombre: 'T4 Total', claves: ['t4 total', 't4', 'tiroxina'], unidad: 'µg/dL', rangoReferencia: '4.5 - 12.0 µg/dL', esAdicional: true },
|
|
{ id: 'tir-4', nombre: 'T3 Total', claves: ['t3 total', 't3', 'triyodotironina'], unidad: 'ng/dL', rangoReferencia: '80 - 200 ng/dL', esAdicional: true }
|
|
]
|
|
},
|
|
{
|
|
id: 'grp-cardiacos',
|
|
nombreGrupo: 'Biomarcadores Cardíacos',
|
|
descripcion: 'Péptidos natriuréticos, troponinas y marcadores de isquemia/falla',
|
|
activo: true,
|
|
orden: 6,
|
|
determinaciones: [
|
|
{ id: 'car-1', nombre: 'NT-proBNP', claves: ['nt-probnp', 'nt probnp', 'nt_probnp', 'probnp', 'pro-bnp', 'pro bnp'], unidad: 'pg/mL', rangoReferencia: '< 125 pg/mL', esAdicional: true },
|
|
{ id: 'car-2', nombre: 'Troponina T / I', claves: ['troponina t', 'troponina i', 'troponina ultrasensible', 'troponina', 'tn-t', 'tn-i', 'tnt', 'tni'], unidad: 'ng/mL', rangoReferencia: '< 0.014 ng/mL', esAdicional: true },
|
|
{ id: 'car-3', nombre: 'CK-MB', claves: ['ck-mb', 'ckmb', 'ck mb'], unidad: 'U/L', rangoReferencia: '< 25 U/L', esAdicional: true }
|
|
]
|
|
},
|
|
{
|
|
id: 'grp-hemograma-indices',
|
|
nombreGrupo: 'Hemograma - Índices y Fórmula',
|
|
descripcion: 'Constantes corpusculares e índices hematimétricos adicionales',
|
|
activo: true,
|
|
orden: 7,
|
|
determinaciones: [
|
|
{ id: 'hem-1', nombre: 'VCM', claves: ['volumen corpuscular medio', 'vcm'], unidad: 'fL', rangoReferencia: '80 - 100 fL', esAdicional: true },
|
|
{ id: 'hem-2', nombre: 'HCM', claves: ['hemoglobina corpuscular media', 'hcm'], unidad: 'pg', rangoReferencia: '27 - 33 pg', esAdicional: true },
|
|
{ id: 'hem-3', nombre: 'CHCM', claves: ['concentracion de hemoglobina corpuscular media', 'chcm'], unidad: 'g/dL', rangoReferencia: '32 - 36 g/dL', esAdicional: true },
|
|
{ id: 'hem-4', nombre: 'RDW', claves: ['rdw', 'ide', 'ancho de distribucion eritrocitaria'], unidad: '%', rangoReferencia: '11.5 - 14.5 %', esAdicional: true },
|
|
{ id: 'hem-5', nombre: 'Neutrófilos', claves: ['neutrófilos', 'neutrofilos', 'neutrofilos segmentados', 'segmentados'], unidad: '%', rangoReferencia: '45 - 70 %', esAdicional: true },
|
|
{ id: 'hem-6', nombre: 'Linfocitos', claves: ['linfocitos', 'linfo'], unidad: '%', rangoReferencia: '20 - 45 %', esAdicional: true },
|
|
{ id: 'hem-7', nombre: 'Monocitos', claves: ['monocitos', 'mono'], unidad: '%', rangoReferencia: '2 - 10 %', esAdicional: true },
|
|
{ id: 'hem-8', nombre: 'Eosinófilos', claves: ['eosinófilos', 'eosinofilos', 'eosino'], unidad: '%', rangoReferencia: '1 - 4 %', esAdicional: true },
|
|
{ id: 'hem-9', nombre: 'Basófilos', claves: ['basófilos', 'basofilos'], unidad: '%', rangoReferencia: '0 - 1 %', esAdicional: true },
|
|
{ id: 'hem-10', nombre: 'Eritroblastos', claves: ['eritroblastos'], unidad: '%', rangoReferencia: '0 %', esAdicional: true },
|
|
{ id: 'hem-11', nombre: 'VPM', claves: ['volumen plaquetario medio', 'vpm'], unidad: 'fL', rangoReferencia: '7.5 - 11.5 fL', esAdicional: true }
|
|
]
|
|
}
|
|
];
|
|
|
|
export async function getAllGruposLaboratorio() {
|
|
let list = [];
|
|
if (db) {
|
|
const raw = await db.collection('grupos_laboratorio').find().sort({ orden: 1, nombreGrupo: 1 }).toArray();
|
|
list = cleanDocs(raw);
|
|
if (list.length === 0) {
|
|
for (const item of DEFAULT_GRUPOS_LABORATORIO) {
|
|
await db.collection('grupos_laboratorio').insertOne({ ...item });
|
|
}
|
|
list = [...DEFAULT_GRUPOS_LABORATORIO];
|
|
}
|
|
return list;
|
|
} else {
|
|
if (!memStore.grupos_laboratorio || memStore.grupos_laboratorio.length === 0) {
|
|
memStore.grupos_laboratorio = DEFAULT_GRUPOS_LABORATORIO.map(item => JSON.parse(JSON.stringify(item)));
|
|
}
|
|
return [...memStore.grupos_laboratorio];
|
|
}
|
|
}
|
|
|
|
export async function createGrupoLaboratorio(grupo) {
|
|
const doc = {
|
|
id: grupo.id || generateUUID(),
|
|
nombreGrupo: (grupo.nombreGrupo || '').trim(),
|
|
descripcion: (grupo.descripcion || '').trim(),
|
|
activo: grupo.activo !== false,
|
|
orden: typeof grupo.orden === 'number' ? grupo.orden : 99,
|
|
determinaciones: Array.isArray(grupo.determinaciones) ? grupo.determinaciones.map(d => ({
|
|
id: d.id || generateUUID(),
|
|
nombre: (d.nombre || '').trim(),
|
|
claves: Array.isArray(d.claves) ? d.claves.map(c => (c || '').trim().toLowerCase()).filter(Boolean) : [],
|
|
unidad: (d.unidad || '').trim(),
|
|
esPrincipal: Boolean(d.esPrincipal),
|
|
esAdicional: d.esAdicional !== false,
|
|
rangoReferencia: (d.rangoReferencia || '').trim(),
|
|
descripcion: (d.descripcion || '').trim()
|
|
})) : [],
|
|
createdAt: new Date().toISOString(),
|
|
updatedAt: new Date().toISOString()
|
|
};
|
|
|
|
if (!doc.nombreGrupo) {
|
|
throw new Error('El nombre del grupo de laboratorio es requerido');
|
|
}
|
|
|
|
if (db) {
|
|
const existing = await db.collection('grupos_laboratorio').findOne({ nombreGrupo: doc.nombreGrupo });
|
|
if (existing) {
|
|
throw new Error(`Ya existe un grupo de determinaciones con el nombre "${doc.nombreGrupo}"`);
|
|
}
|
|
await db.collection('grupos_laboratorio').insertOne(doc);
|
|
} else {
|
|
if (!memStore.grupos_laboratorio) memStore.grupos_laboratorio = [];
|
|
const exists = memStore.grupos_laboratorio.some(g => g.nombreGrupo.toLowerCase() === doc.nombreGrupo.toLowerCase());
|
|
if (exists) {
|
|
throw new Error(`Ya existe un grupo de determinaciones con el nombre "${doc.nombreGrupo}"`);
|
|
}
|
|
memStore.grupos_laboratorio.push(doc);
|
|
}
|
|
return doc;
|
|
}
|
|
|
|
export async function updateGrupoLaboratorio(id, datos) {
|
|
const updateDoc = { ...datos, updatedAt: new Date().toISOString() };
|
|
delete updateDoc._id;
|
|
delete updateDoc.id;
|
|
|
|
if (datos.nombreGrupo) {
|
|
updateDoc.nombreGrupo = datos.nombreGrupo.trim();
|
|
}
|
|
if (datos.descripcion !== undefined) {
|
|
updateDoc.descripcion = datos.descripcion.trim();
|
|
}
|
|
if (datos.activo !== undefined) {
|
|
updateDoc.activo = Boolean(datos.activo);
|
|
}
|
|
if (datos.orden !== undefined) {
|
|
updateDoc.orden = Number(datos.orden);
|
|
}
|
|
if (Array.isArray(datos.determinaciones)) {
|
|
updateDoc.determinaciones = datos.determinaciones.map(d => ({
|
|
id: d.id || generateUUID(),
|
|
nombre: (d.nombre || '').trim(),
|
|
claves: Array.isArray(d.claves) ? d.claves.map(c => (c || '').trim().toLowerCase()).filter(Boolean) : [],
|
|
unidad: (d.unidad || '').trim(),
|
|
esPrincipal: Boolean(d.esPrincipal),
|
|
esAdicional: d.esAdicional !== false,
|
|
rangoReferencia: (d.rangoReferencia || '').trim(),
|
|
descripcion: (d.descripcion || '').trim()
|
|
}));
|
|
}
|
|
|
|
if (db) {
|
|
if (updateDoc.nombreGrupo) {
|
|
const existing = await db.collection('grupos_laboratorio').findOne({
|
|
id: { $ne: id },
|
|
nombreGrupo: updateDoc.nombreGrupo
|
|
});
|
|
if (existing) {
|
|
throw new Error(`Ya existe otro grupo de determinaciones con el nombre "${updateDoc.nombreGrupo}"`);
|
|
}
|
|
}
|
|
await db.collection('grupos_laboratorio').updateOne({ id }, { $set: updateDoc });
|
|
} else {
|
|
if (!memStore.grupos_laboratorio) memStore.grupos_laboratorio = [];
|
|
if (updateDoc.nombreGrupo) {
|
|
const exists = memStore.grupos_laboratorio.some(g => g.id !== id && g.nombreGrupo.toLowerCase() === updateDoc.nombreGrupo.toLowerCase());
|
|
if (exists) {
|
|
throw new Error(`Ya existe otro grupo de determinaciones con el nombre "${updateDoc.nombreGrupo}"`);
|
|
}
|
|
}
|
|
const idx = memStore.grupos_laboratorio.findIndex(g => g.id === id);
|
|
if (idx !== -1) {
|
|
memStore.grupos_laboratorio[idx] = { ...memStore.grupos_laboratorio[idx], ...updateDoc };
|
|
}
|
|
}
|
|
return { id, ...updateDoc };
|
|
}
|
|
|
|
export async function deleteGrupoLaboratorio(id) {
|
|
if (db) {
|
|
await db.collection('grupos_laboratorio').deleteOne({ id });
|
|
} else {
|
|
if (memStore.grupos_laboratorio) {
|
|
memStore.grupos_laboratorio = memStore.grupos_laboratorio.filter(g => g.id !== id);
|
|
}
|
|
}
|
|
return { success: true };
|
|
}
|
|
|
|
export async function restablecerGruposLaboratorio() {
|
|
if (db) {
|
|
await db.collection('grupos_laboratorio').deleteMany({});
|
|
for (const item of DEFAULT_GRUPOS_LABORATORIO) {
|
|
await db.collection('grupos_laboratorio').insertOne({ ...item });
|
|
}
|
|
return await getAllGruposLaboratorio();
|
|
} else {
|
|
memStore.grupos_laboratorio = DEFAULT_GRUPOS_LABORATORIO.map(item => JSON.parse(JSON.stringify(item)));
|
|
return [...memStore.grupos_laboratorio];
|
|
}
|
|
}
|
|
|
|
// ========== 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);
|
|
}
|
|
}
|
|
|
|
// ========== PENDIENTES ==========
|
|
export async function getAllPendientes() {
|
|
if (db) {
|
|
const pendientes = await db.collection('pendientes').find().toArray();
|
|
return cleanDocs(pendientes);
|
|
}
|
|
return [...memStore.pendientes];
|
|
}
|
|
|
|
export async function createPendiente(pendiente) {
|
|
if (db) {
|
|
await db.collection('pendientes').insertOne(pendiente);
|
|
} else {
|
|
memStore.pendientes.push(pendiente);
|
|
}
|
|
}
|
|
|
|
export async function updatePendiente(id, datos) {
|
|
if (db) {
|
|
await db.collection('pendientes').updateOne({ id }, { $set: datos });
|
|
} else {
|
|
const idx = memStore.pendientes.findIndex(p => p.id === id);
|
|
if (idx !== -1) {
|
|
memStore.pendientes[idx] = { ...memStore.pendientes[idx], ...datos };
|
|
}
|
|
}
|
|
}
|
|
|
|
export async function deletePendiente(id) {
|
|
if (db) {
|
|
await db.collection('pendientes').deleteOne({ id });
|
|
} else {
|
|
memStore.pendientes = memStore.pendientes.filter(p => p.id !== id);
|
|
}
|
|
}
|
|
|
|
// ========== 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;
|
|
}
|
|
}
|
|
|
|
export function getDb() {
|
|
return db;
|
|
}
|
|
|
|
export async function exportAllData() {
|
|
const collections = [
|
|
'usuarios', 'pacientes', 'areas', 'camas', 'internaciones',
|
|
'evoluciones', 'laboratorios', 'otrosLaboratorios', 'glucemias', 'acidosbase',
|
|
'cultivos', 'tipos_cultivo', 'grupos_laboratorio', 'estudiosComplementarios', 'interconsultas', 'atb',
|
|
'indicaciones', 'movimientos_indicaciones', 'pendientes', 'kv'
|
|
];
|
|
const dump = {};
|
|
if (db) {
|
|
for (const name of collections) {
|
|
const docs = await db.collection(name).find().toArray();
|
|
dump[name] = docs;
|
|
}
|
|
} else {
|
|
for (const name of collections) {
|
|
dump[name] = [...(memStore[name] || [])];
|
|
}
|
|
}
|
|
return dump;
|
|
}
|
|
|
|
export async function importAllData(dump) {
|
|
const collections = [
|
|
'usuarios', 'pacientes', 'areas', 'camas', 'internaciones',
|
|
'evoluciones', 'laboratorios', 'otrosLaboratorios', 'glucemias', 'acidosbase',
|
|
'cultivos', 'tipos_cultivo', 'grupos_laboratorio', 'estudiosComplementarios', 'interconsultas', 'atb',
|
|
'indicaciones', 'movimientos_indicaciones', 'pendientes', 'kv'
|
|
];
|
|
if (db) {
|
|
for (const name of collections) {
|
|
if (Array.isArray(dump[name])) {
|
|
try {
|
|
await db.collection(name).deleteMany({});
|
|
} catch (e) {
|
|
console.warn(`Could not clear collection ${name}:`, e);
|
|
}
|
|
if (dump[name].length > 0) {
|
|
const cleaned = dump[name].map(doc => {
|
|
const copy = { ...doc };
|
|
if (copy._id) {
|
|
if (ObjectId.isValid(copy._id)) {
|
|
copy._id = new ObjectId(copy._id);
|
|
} else {
|
|
delete copy._id;
|
|
}
|
|
}
|
|
return copy;
|
|
});
|
|
await db.collection(name).insertMany(cleaned);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
for (const name of collections) {
|
|
if (Array.isArray(dump[name])) {
|
|
memStore[name] = [...dump[name]];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
export async function getAllOtrosLaboratorios() {
|
|
if (db) {
|
|
const records = await db.collection('otros-laboratorios').find().toArray();
|
|
return cleanDocs(records);
|
|
}
|
|
return [...memStore.otrosLaboratorios];
|
|
}
|
|
|
|
export async function createOtroLaboratorio(record) {
|
|
const doc = {
|
|
id: record.id,
|
|
pacienteId: record.pacienteId,
|
|
internacionId: record.internacionId,
|
|
fecha: record.fecha,
|
|
hora: record.hora,
|
|
observaciones: record.observaciones,
|
|
createdAt: new Date().toISOString()
|
|
};
|
|
|
|
if (db) {
|
|
await db.collection('otros-laboratorios').insertOne(doc);
|
|
return cleanDoc(doc);
|
|
}
|
|
|
|
memStore.otrosLaboratorios.push(doc);
|
|
return doc;
|
|
}
|
|
|
|
export async function updateOtroLaboratorio(id, updates) {
|
|
if (db) {
|
|
await db.collection('otros-laboratorios').updateOne(
|
|
{ id },
|
|
{ $set: { ...updates, updatedAt: new Date().toISOString() } }
|
|
);
|
|
return { success: true };
|
|
}
|
|
|
|
const idx = memStore.otrosLaboratorios.findIndex(x => x.id === id);
|
|
if (idx !== -1) {
|
|
memStore.otrosLaboratorios[idx] = { ...memStore.otrosLaboratorios[idx], ...updates, updatedAt: new Date().toISOString() };
|
|
return { success: true };
|
|
}
|
|
return { success: false };
|
|
}
|
|
|
|
export async function deleteOtroLaboratorio(id) {
|
|
if (db) {
|
|
await db.collection('otros-laboratorios').deleteOne({ id });
|
|
return { success: true };
|
|
}
|
|
|
|
const idx = memStore.otrosLaboratorios.findIndex(x => x.id === id);
|
|
if (idx !== -1) {
|
|
memStore.otrosLaboratorios.splice(idx, 1);
|
|
return { success: true };
|
|
}
|
|
return { success: false };
|
|
}
|