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: [], otrosLaboratorios: [], 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]; } } // ========== 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', 'glucemias', 'acidosbase', 'cultivos', 'tipos_cultivo', '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', 'glucemias', 'acidosbase', 'cultivos', 'tipos_cultivo', '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 }; }