Actualizacion de sistema: mejoras en seleccion de categorias de cultivos, optimizacion responsive de listado de cultivos e historia clinica, y correccion de componentes

This commit is contained in:
2026-08-16 20:40:26 +00:00
parent 1665c76bba
commit 5b51e4e8eb
14 changed files with 1349 additions and 327 deletions
+47
View File
@@ -12,6 +12,7 @@ import { initDb,
getAllGlucemias, createGlucemia, updateGlucemia, deleteGlucemia,
getAllAcidosBase, createAcidoBase, updateAcidoBase, deleteAcidoBase,
getAllCultivos, createCultivo, updateCultivo, deleteCultivo,
getAllTiposCultivo, createTipoCultivo, updateTipoCultivo, deleteTipoCultivo, restablecerTiposCultivo,
getAllEstudiosComplementarios, createEstudioComplementario, updateEstudioComplementario, deleteEstudioComplementario,
getAllInterconsultas, createInterconsulta, updateInterconsulta, deleteInterconsulta,
getAllAtb, createAtb, updateAtb, deleteAtb,
@@ -95,6 +96,7 @@ app.get('/api/state', async (req, res) => {
glucemias: await getAllGlucemias(),
acidosBase: await getAllAcidosBase(),
cultivos: await getAllCultivos(),
tiposCultivo: await getAllTiposCultivo(),
estudiosComplementarios: await getAllEstudiosComplementarios(),
interconsultas: (await getAllInterconsultas()).map(ic => ({ ...ic, realizada: !!ic.realizada })),
atb: await getAllAtb(),
@@ -580,6 +582,51 @@ app.delete('/api/cultivos/:id', async (req, res) => {
}
});
// ========== TIPOS DE CULTIVO ==========
app.get('/api/tipos-cultivo', async (req, res) => {
try {
res.json(await getAllTiposCultivo());
} catch (err) {
res.status(500).json({ error: 'Error al obtener tipos de cultivo' });
}
});
app.post('/api/tipos-cultivo', async (req, res) => {
try {
const nuevo = await createTipoCultivo(req.body);
res.json(nuevo);
} catch (err) {
res.status(400).json({ error: err.message || 'Error al crear tipo de cultivo' });
}
});
app.put('/api/tipos-cultivo/:id', async (req, res) => {
try {
const updated = await updateTipoCultivo(req.params.id, req.body);
res.json(updated);
} catch (err) {
res.status(400).json({ error: err.message || 'Error al actualizar tipo de cultivo' });
}
});
app.delete('/api/tipos-cultivo/:id', async (req, res) => {
try {
await deleteTipoCultivo(req.params.id);
res.json({ ok: true });
} catch (err) {
res.status(500).json({ error: 'Error al eliminar tipo de cultivo' });
}
});
app.post('/api/tipos-cultivo/reset', async (req, res) => {
try {
const list = await restablecerTiposCultivo();
res.json(list);
} catch (err) {
res.status(500).json({ error: 'Error al restablecer tipos de cultivo' });
}
});
// ========== ESTUDIOS COMPLEMENTARIOS ==========
app.get('/api/estudios-complementarios', async (req, res) => {
try {
+158 -2
View File
@@ -66,6 +66,8 @@ const memStore = {
indicaciones: [],
movimientos_indicaciones: [],
pendientes: [],
tipos_cultivo: [],
otrosLaboratorios: [],
kv: {}
};
@@ -703,6 +705,160 @@ export async function deleteCultivo(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) {
@@ -1003,7 +1159,7 @@ export async function exportAllData() {
const collections = [
'usuarios', 'pacientes', 'areas', 'camas', 'internaciones',
'evoluciones', 'laboratorios', 'glucemias', 'acidosbase',
'cultivos', 'estudiosComplementarios', 'interconsultas', 'atb',
'cultivos', 'tipos_cultivo', 'estudiosComplementarios', 'interconsultas', 'atb',
'indicaciones', 'movimientos_indicaciones', 'pendientes', 'kv'
];
const dump = {};
@@ -1024,7 +1180,7 @@ export async function importAllData(dump) {
const collections = [
'usuarios', 'pacientes', 'areas', 'camas', 'internaciones',
'evoluciones', 'laboratorios', 'glucemias', 'acidosbase',
'cultivos', 'estudiosComplementarios', 'interconsultas', 'atb',
'cultivos', 'tipos_cultivo', 'estudiosComplementarios', 'interconsultas', 'atb',
'indicaciones', 'movimientos_indicaciones', 'pendientes', 'kv'
];
if (db) {