Files
administracionHospital/server/api-mongodb.js
T

1010 lines
30 KiB
JavaScript

import express from 'express';
import cors from 'cors';
import { initDb,
getUsuarioByDni, getUsuarioById, getAllUsuarios, createUsuario, updateUsuario, deleteUsuario, verifyPassword, hashPassword,
getAllPacientes, getPacienteById, createPaciente, updatePaciente, deletePaciente,
getAllAreas, createArea, updateArea, deleteArea,
getAllCamas, getCamaById, createCama, updateCama, deleteCama,
getAllInternaciones, getInternacionById, createInternacion, updateInternacion, deleteInternacion,
getAllEvoluciones, createEvolucion, updateEvolucion, deleteEvolucion,
getAllLaboratorios, createLaboratorio, updateLaboratorio, deleteLaboratorio,
getAllOtrosLaboratorios, createOtroLaboratorio, updateOtroLaboratorio, deleteOtroLaboratorio,
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,
getAllIndicaciones, createIndicacion, updateIndicacion, deleteIndicacion,
getAllMovimientosIndicaciones, createMovimientoIndicacion,
getAllPendientes, createPendiente, updatePendiente, deletePendiente,
getValue, setValue,
getDb, exportAllData, importAllData
} from './db-mongodb.js';
// Capture live logs in-memory
const logBuffer = [];
const originalLog = console.log;
const originalError = console.error;
const originalWarn = console.warn;
function addLog(type, args) {
const message = args.map(arg => {
if (typeof arg === 'object') {
try { return JSON.stringify(arg); } catch { return String(arg); }
}
return String(arg);
}).join(' ');
const logEntry = `[${new Date().toISOString()}] [${type}] ${message}`;
logBuffer.push(logEntry);
if (logBuffer.length > 500) {
logBuffer.shift();
}
}
console.log = (...args) => {
addLog('INFO', args);
originalLog.apply(console, args);
};
console.error = (...args) => {
addLog('ERROR', args);
originalError.apply(console, args);
};
console.warn = (...args) => {
addLog('WARN', args);
originalWarn.apply(console, args);
};
const app = express();
app.use(cors({
origin: true,
credentials: true
}));
app.use(express.json({ limit: '50mb' }));
// UUID generator
function generateUUID() {
if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
return crypto.randomUUID();
}
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
const r = (Math.random() * 16) | 0;
const v = c === 'x' ? r : (r & 0x3) | 0x8;
return v.toString(16);
});
}
// ========== STATE ENDPOINT (initial load) ==========
app.get('/api/state', async (req, res) => {
try {
const areasList = await getAllAreas();
const usuariosList = await getAllUsuarios();
const state = {
pacientes: await getAllPacientes(),
areas: areasList,
grupos: areasList,
camas: await getAllCamas(),
internaciones: await getAllInternaciones(),
evoluciones: (await getAllEvoluciones()).map(e => ({
...e,
signosVitales: typeof e.signosVitales === 'string' ? JSON.parse(e.signosVitales) : e.signosVitales,
examenFisico: typeof e.examenFisico === 'string' ? JSON.parse(e.examenFisico) : e.examenFisico
})),
laboratorios: (await getAllLaboratorios()).map(l => ({ ...l, resultados: typeof l.resultados === 'string' ? JSON.parse(l.resultados) : l.resultados })),
otrosLaboratorios: await getAllOtrosLaboratorios(),
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(),
indicaciones: await getAllIndicaciones(),
movimientosIndicaciones: await getAllMovimientosIndicaciones(),
pendientes: await getAllPendientes(),
usuarios: usuariosList.map(({ passwordHash, ...u }) => u),
vistaActual: 'dashboard',
currentInternacionId: null
};
res.json(state);
} catch (err) {
console.error(err);
res.status(500).json({ error: 'failed to read state' });
}
});
// ========== USUARIOS ==========
app.get('/api/usuarios', async (req, res) => {
try {
const usuarios = await getAllUsuarios();
const usuariosSinPassword = usuarios.map(({ passwordHash, ...u }) => u);
res.json(usuariosSinPassword);
} catch (err) {
res.status(500).json({ error: 'Error al obtener usuarios' });
}
});
app.post('/api/usuarios', async (req, res) => {
try {
const { apellido, nombre, dni, fechaNacimiento, email, rol, matriculaProfesional, password, areaId } = req.body;
if (!apellido || !nombre || !dni || !fechaNacimiento || !email || !rol || !password) {
return res.status(400).json({ error: 'Todos los campos obligatorios deben estar presentes' });
}
const existente = await getUsuarioByDni(dni);
if (existente) {
return res.status(400).json({ error: 'Ya existe un usuario con ese DNI' });
}
const passwordHash = hashPassword(password);
const usuario = {
id: generateUUID(),
apellido, nombre, dni, fechaNacimiento, email, rol,
matriculaProfesional: matriculaProfesional || null,
passwordHash,
areaId: areaId || null,
fechaCreacion: new Date().toISOString().split('T')[0]
};
await createUsuario(usuario);
res.json({ ...usuario, passwordHash: undefined });
} catch (err) {
res.status(500).json({ error: 'Error al crear usuario' });
}
});
app.put('/api/usuarios/:id', async (req, res) => {
try {
const { id } = req.params;
const { apellido, nombre, fechaNacimiento, email, rol, matriculaProfesional, areaId, password } = req.body;
const usuario = await getUsuarioById(id);
if (!usuario) {
return res.status(404).json({ error: 'Usuario no encontrado' });
}
const datos = {};
if (apellido !== undefined) datos.apellido = apellido;
if (nombre !== undefined) datos.nombre = nombre;
if (fechaNacimiento !== undefined) datos.fechaNacimiento = fechaNacimiento;
if (email !== undefined) datos.email = email;
if (rol !== undefined) datos.rol = rol;
if (matriculaProfesional !== undefined) datos.matriculaProfesional = matriculaProfesional;
if (areaId !== undefined) datos.areaId = areaId;
if (password) datos.passwordHash = hashPassword(password);
await updateUsuario(id, datos);
res.json({ ok: true });
} catch (err) {
res.status(500).json({ error: 'Error al actualizar usuario' });
}
});
app.delete('/api/usuarios/:id', async (req, res) => {
try {
await deleteUsuario(req.params.id);
res.json({ ok: true });
} catch (err) {
res.status(500).json({ error: 'Error al eliminar usuario' });
}
});
// ========== PACIENTES ==========
app.get('/api/pacientes', async (req, res) => {
try {
res.json(await getAllPacientes());
} catch (err) {
res.status(500).json({ error: 'Error al obtener pacientes' });
}
});
app.post('/api/pacientes', async (req, res) => {
try {
const paciente = { ...req.body, id: req.body.id || generateUUID() };
await createPaciente(paciente);
res.json(paciente);
} catch (err) {
res.status(500).json({ error: 'Error al crear paciente' });
}
});
app.put('/api/pacientes/:id', async (req, res) => {
try {
await updatePaciente(req.params.id, req.body);
res.json({ ok: true });
} catch (err) {
res.status(500).json({ error: 'Error al actualizar paciente' });
}
});
app.delete('/api/pacientes/:id', async (req, res) => {
try {
await deletePaciente(req.params.id);
res.json({ ok: true });
} catch (err) {
res.status(500).json({ error: 'Error al eliminar paciente' });
}
});
// ========== AREAS & GRUPOS ==========
app.get(['/api/areas', '/api/grupos'], async (req, res) => {
try {
res.json(await getAllAreas());
} catch (err) {
res.status(500).json({ error: 'Error al obtener áreas/grupos' });
}
});
app.post(['/api/areas', '/api/grupos'], async (req, res) => {
try {
const area = { ...req.body, id: req.body.id || generateUUID() };
await createArea(area);
res.json(area);
} catch (err) {
res.status(500).json({ error: 'Error al crear área/grupo' });
}
});
app.put(['/api/areas/:id', '/api/grupos/:id'], async (req, res) => {
try {
await updateArea(req.params.id, req.body);
res.json({ ok: true });
} catch (err) {
res.status(500).json({ error: 'Error al actualizar área/grupo' });
}
});
app.delete(['/api/areas/:id', '/api/grupos/:id'], async (req, res) => {
try {
await deleteArea(req.params.id);
res.json({ ok: true });
} catch (err) {
res.status(500).json({ error: 'Error al eliminar área/grupo' });
}
});
// ========== CAMAS ==========
app.get('/api/camas', async (req, res) => {
try {
res.json(await getAllCamas());
} catch (err) {
res.status(500).json({ error: 'Error al obtener camas' });
}
});
app.put('/api/camas/:id', async (req, res) => {
try {
await updateCama(req.params.id, req.body);
res.json({ ok: true });
} catch (err) {
res.status(500).json({ error: 'Error al actualizar cama' });
}
});
app.post('/api/camas', async (req, res) => {
try {
const cama = req.body;
if (!cama.id) {
cama.id = generateUUID();
}
await createCama(cama);
res.json(cama);
} catch (err) {
res.status(500).json({ error: 'Error al crear cama' });
}
});
app.delete('/api/camas/:id', async (req, res) => {
try {
await deleteCama(req.params.id);
res.json({ ok: true });
} catch (err) {
res.status(500).json({ error: 'Error al eliminar cama' });
}
});
// ========== INTERNACIONES ==========
app.get('/api/internaciones', async (req, res) => {
try {
res.json(await getAllInternaciones());
} catch (err) {
res.status(500).json({ error: 'Error al obtener internaciones' });
}
});
app.post('/api/internaciones', async (req, res) => {
try {
const internacion = { ...req.body, id: req.body.id || generateUUID(), activa: req.body.activa !== undefined ? req.body.activa : true };
await createInternacion(internacion);
res.json(internacion);
} catch (err) {
res.status(500).json({ error: 'Error al crear internacion' });
}
});
app.put('/api/internaciones/:id', async (req, res) => {
try {
await updateInternacion(req.params.id, req.body);
res.json({ ok: true });
} catch (err) {
res.status(500).json({ error: 'Error al actualizar internacion' });
}
});
app.delete('/api/internaciones/:id', async (req, res) => {
try {
await deleteInternacion(req.params.id);
res.json({ ok: true });
} catch (err) {
res.status(500).json({ error: 'Error al eliminar internacion' });
}
});
// ========== EVOLUCIONES ==========
app.get('/api/evoluciones', async (req, res) => {
try {
res.json(await getAllEvoluciones());
} catch (err) {
res.status(500).json({ error: 'Error al obtener evoluciones' });
}
});
app.post('/api/evoluciones', async (req, res) => {
try {
const evolucion = { ...req.body, id: req.body.id || generateUUID() };
await createEvolucion(evolucion);
res.json(evolucion);
} catch (err) {
res.status(500).json({ error: 'Error al crear evolucion' });
}
});
app.put('/api/evoluciones/:id', async (req, res) => {
try {
await updateEvolucion(req.params.id, req.body);
res.json({ ok: true });
} catch (err) {
res.status(500).json({ error: 'Error al actualizar evolucion' });
}
});
app.delete('/api/evoluciones/:id', async (req, res) => {
try {
await deleteEvolucion(req.params.id);
res.json({ ok: true });
} catch (err) {
res.status(500).json({ error: 'Error al eliminar evolucion' });
}
});
// ========== LABORATORIOS ==========
app.get('/api/laboratorios', async (req, res) => {
try {
res.json(await getAllLaboratorios());
} catch (err) {
res.status(500).json({ error: 'Error al obtener laboratorios' });
}
});
app.post('/api/laboratorios', async (req, res) => {
try {
const laboratorio = { ...req.body, id: req.body.id || generateUUID() };
await createLaboratorio(laboratorio);
res.json(laboratorio);
} catch (err) {
res.status(500).json({ error: 'Error al crear laboratorio' });
}
});
app.put('/api/laboratorios/:id', async (req, res) => {
try {
await updateLaboratorio(req.params.id, req.body);
res.json({ ok: true });
} catch (err) {
res.status(500).json({ error: 'Error al actualizar laboratorio' });
}
});
app.delete('/api/laboratorios/:id', async (req, res) => {
try {
await deleteLaboratorio(req.params.id);
res.json({ ok: true });
} catch (err) {
res.status(500).json({ error: 'Error al eliminar laboratorio' });
}
});
// ========== OTROS LABORATORIOS ==========
app.get('/api/otros-laboratorios', async (req, res) => {
try {
const records = await getAllOtrosLaboratorios();
res.json(records);
} catch (error) {
console.error('Error fetching otros-laboratorios:', error);
res.status(500).json({ error: 'Failed to fetch otros-laboratorios' });
}
});
app.post('/api/otros-laboratorios', async (req, res) => {
try {
const { pacienteId, fecha } = req.body;
if (!pacienteId || !fecha) {
return res.status(400).json({ error: 'pacienteId and fecha are required' });
}
const record = {
id: generateUUID(),
observaciones: '',
...req.body
};
const newRecord = await createOtroLaboratorio(record);
res.status(201).json(newRecord);
} catch (error) {
console.error('Error creating otro laboratorio:', error);
res.status(500).json({ error: 'Failed to create otro laboratorio' });
}
});
app.put('/api/otros-laboratorios/:id', async (req, res) => {
try {
const result = await updateOtroLaboratorio(req.params.id, req.body);
if (!result.success) return res.status(404).json({ error: 'Record not found' });
res.json(result);
} catch (error) {
console.error('Error updating otro laboratorio:', error);
res.status(500).json({ error: 'Failed to update otro laboratorio' });
}
});
app.delete('/api/otros-laboratorios/:id', async (req, res) => {
try {
const result = await deleteOtroLaboratorio(req.params.id);
if (!result.success) return res.status(404).json({ error: 'Record not found' });
res.json({ message: 'Deleted successfully' });
} catch (error) {
console.error('Error deleting otro laboratorio:', error);
res.status(500).json({ error: 'Failed to delete otro laboratorio' });
}
});
// ========== GLUCEMIAS ==========
app.get('/api/glucemias', async (req, res) => {
try {
res.json(await getAllGlucemias());
} catch (err) {
res.status(500).json({ error: 'Error al obtener glucemias' });
}
});
app.post('/api/glucemias', async (req, res) => {
try {
const glucemia = { ...req.body, id: req.body.id || generateUUID() };
await createGlucemia(glucemia);
res.json(glucemia);
} catch (err) {
res.status(500).json({ error: 'Error al crear glucemia' });
}
});
app.put('/api/glucemias/:id', async (req, res) => {
try {
await updateGlucemia(req.params.id, req.body);
res.json({ ok: true });
} catch (err) {
res.status(500).json({ error: 'Error al actualizar glucemia' });
}
});
app.delete('/api/glucemias/:id', async (req, res) => {
try {
await deleteGlucemia(req.params.id);
res.json({ ok: true });
} catch (err) {
res.status(500).json({ error: 'Error al eliminar glucemia' });
}
});
// ========== ACIDOS BASE ==========
app.get('/api/acid-os-base', async (req, res) => {
try {
res.json(await getAllAcidosBase());
} catch (err) {
res.status(500).json({ error: 'Error al obtener acidos base' });
}
});
app.post('/api/acid-os-base', async (req, res) => {
try {
const acido = { ...req.body, id: req.body.id || generateUUID() };
await createAcidoBase(acido);
res.json(acido);
} catch (err) {
res.status(500).json({ error: 'Error al crear acido base' });
}
});
app.put('/api/acid-os-base/:id', async (req, res) => {
try {
await updateAcidoBase(req.params.id, req.body);
res.json({ ok: true });
} catch (err) {
res.status(500).json({ error: 'Error al actualizar acido base' });
}
});
app.delete('/api/acid-os-base/:id', async (req, res) => {
try {
await deleteAcidoBase(req.params.id);
res.json({ ok: true });
} catch (err) {
res.status(500).json({ error: 'Error al eliminar acido base' });
}
});
// ========== CULTIVOS ==========
app.get('/api/cultivos', async (req, res) => {
try {
res.json(await getAllCultivos());
} catch (err) {
res.status(500).json({ error: 'Error al obtener cultivos' });
}
});
app.post('/api/cultivos', async (req, res) => {
try {
const cultivo = { ...req.body, id: req.body.id || generateUUID() };
await createCultivo(cultivo);
res.json(cultivo);
} catch (err) {
res.status(500).json({ error: 'Error al crear cultivo' });
}
});
app.put('/api/cultivos/:id', async (req, res) => {
try {
await updateCultivo(req.params.id, req.body);
res.json({ ok: true });
} catch (err) {
res.status(500).json({ error: 'Error al actualizar cultivo' });
}
});
app.delete('/api/cultivos/:id', async (req, res) => {
try {
await deleteCultivo(req.params.id);
res.json({ ok: true });
} catch (err) {
res.status(500).json({ error: 'Error al eliminar cultivo' });
}
});
// ========== 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 {
res.json(await getAllEstudiosComplementarios());
} catch (err) {
res.status(500).json({ error: 'Error al obtener estudios' });
}
});
app.post('/api/estudios-complementarios', async (req, res) => {
try {
const estudio = { ...req.body, id: req.body.id || generateUUID() };
await createEstudioComplementario(estudio);
res.json(estudio);
} catch (err) {
res.status(500).json({ error: 'Error al crear estudio' });
}
});
app.put('/api/estudios-complementarios/:id', async (req, res) => {
try {
await updateEstudioComplementario(req.params.id, req.body);
res.json({ ok: true });
} catch (err) {
res.status(500).json({ error: 'Error al actualizar estudio' });
}
});
app.delete('/api/estudios-complementarios/:id', async (req, res) => {
try {
await deleteEstudioComplementario(req.params.id);
res.json({ ok: true });
} catch (err) {
res.status(500).json({ error: 'Error al eliminar estudio' });
}
});
// ========== INTERCONSULTAS ==========
app.get('/api/interconsultas', async (req, res) => {
try {
res.json(await getAllInterconsultas());
} catch (err) {
res.status(500).json({ error: 'Error al obtener interconsultas' });
}
});
app.post('/api/interconsultas', async (req, res) => {
try {
const interconsulta = { ...req.body, id: req.body.id || generateUUID() };
await createInterconsulta(interconsulta);
res.json(interconsulta);
} catch (err) {
res.status(500).json({ error: 'Error al crear interconsulta' });
}
});
app.put('/api/interconsultas/:id', async (req, res) => {
try {
await updateInterconsulta(req.params.id, req.body);
res.json({ ok: true });
} catch (err) {
res.status(500).json({ error: 'Error al actualizar interconsulta' });
}
});
app.delete('/api/interconsultas/:id', async (req, res) => {
try {
await deleteInterconsulta(req.params.id);
res.json({ ok: true });
} catch (err) {
res.status(500).json({ error: 'Error al eliminar interconsulta' });
}
});
// ========== ATB ==========
app.get('/api/atb', async (req, res) => {
try {
res.json(await getAllAtb());
} catch (err) {
res.status(500).json({ error: 'Error al obtener ATB' });
}
});
app.post('/api/atb', async (req, res) => {
try {
const atb = { ...req.body, id: req.body.id || generateUUID() };
await createAtb(atb);
res.json(atb);
} catch (err) {
res.status(500).json({ error: 'Error al crear ATB' });
}
});
app.put('/api/atb/:id', async (req, res) => {
try {
await updateAtb(req.params.id, req.body);
res.json({ ok: true });
} catch (err) {
res.status(500).json({ error: 'Error al actualizar ATB' });
}
});
app.delete('/api/atb/:id', async (req, res) => {
try {
await deleteAtb(req.params.id);
res.json({ ok: true });
} catch (err) {
res.status(500).json({ error: 'Error al eliminar ATB' });
}
});
// ========== INDICACIONES ==========
app.get('/api/indicaciones', async (req, res) => {
try {
res.json(await getAllIndicaciones());
} catch (err) {
res.status(500).json({ error: 'Error al obtener indicaciones' });
}
});
app.post('/api/indicaciones', async (req, res) => {
try {
const indicacion = { ...req.body, id: req.body.id || generateUUID() };
await createIndicacion(indicacion);
res.json(indicacion);
} catch (err) {
res.status(500).json({ error: 'Error al crear indicacion' });
}
});
app.put('/api/indicaciones/:id', async (req, res) => {
try {
await updateIndicacion(req.params.id, req.body);
res.json({ ok: true });
} catch (err) {
res.status(500).json({ error: 'Error al actualizar indicacion' });
}
});
app.delete('/api/indicaciones/:id', async (req, res) => {
try {
await deleteIndicacion(req.params.id);
res.json({ ok: true });
} catch (err) {
res.status(500).json({ error: 'Error al eliminar indicacion' });
}
});
// ========== MOVIMIENTOS INDICACIONES ==========
app.get('/api/movimientos-indicaciones', async (req, res) => {
try {
res.json(await getAllMovimientosIndicaciones());
} catch (err) {
res.status(500).json({ error: 'Error al obtener movimientos' });
}
});
app.post('/api/movimientos-indicaciones', async (req, res) => {
try {
const movimiento = { ...req.body, id: req.body.id || generateUUID() };
console.log('Creating movimiento:', movimiento);
await createMovimientoIndicacion(movimiento);
res.json(movimiento);
} catch (err) {
console.error('Error creating movimiento:', err);
res.status(500).json({ error: err.message || 'Error al crear movimiento' });
}
});
// ========== PENDIENTES ==========
app.get('/api/pendientes', async (req, res) => {
try {
res.json(await getAllPendientes());
} catch (err) {
res.status(500).json({ error: 'Error al obtener pendientes' });
}
});
app.post('/api/pendientes', async (req, res) => {
try {
const pendiente = { ...req.body, id: req.body.id || generateUUID() };
await createPendiente(pendiente);
res.json(pendiente);
} catch (err) {
res.status(500).json({ error: 'Error al crear pendiente' });
}
});
app.put('/api/pendientes/:id', async (req, res) => {
try {
await updatePendiente(req.params.id, req.body);
res.json({ ok: true });
} catch (err) {
res.status(500).json({ error: 'Error al actualizar pendiente' });
}
});
app.delete('/api/pendientes/:id', async (req, res) => {
try {
await deletePendiente(req.params.id);
res.json({ ok: true });
} catch (err) {
res.status(500).json({ error: 'Error al eliminar pendiente' });
}
});
// ========== AUTH ==========
app.post('/api/auth/login', async (req, res) => {
try {
const { dni, password } = req.body;
if (!dni || !password) {
return res.status(400).json({ error: 'DNI y contraseña requeridos' });
}
const usuario = await getUsuarioByDni(dni);
if (!usuario) {
return res.status(401).json({ error: 'Usuario no encontrado' });
}
const validPassword = await verifyPassword(dni, password);
if (!validPassword) {
return res.status(401).json({ error: 'Contraseña incorrecta' });
}
const { passwordHash, ...userWithoutPassword } = usuario;
res.json(userWithoutPassword);
} catch (err) {
res.status(500).json({ error: 'Error en autenticación' });
}
});
app.post('/api/auth/change-password', async (req, res) => {
try {
const { dni, oldPassword, newPassword } = req.body;
if (!dni || !oldPassword || !newPassword) {
return res.status(400).json({ error: 'Todos los campos son requeridos' });
}
const validPassword = await verifyPassword(dni, oldPassword);
if (!validPassword) {
return res.status(401).json({ error: 'Contraseña actual incorrecta' });
}
const newHash = hashPassword(newPassword);
const usuario = await getUsuarioByDni(dni);
if (usuario) {
await updateUsuario(usuario.id, { passwordHash: newHash });
}
res.json({ ok: true });
} catch (err) {
res.status(500).json({ error: 'Error al cambiar contraseña' });
}
});
app.put('/api/auth/update-email', async (req, res) => {
try {
const { dni, newEmail } = req.body;
if (!dni || !newEmail) {
return res.status(400).json({ error: 'DNI y email son requeridos' });
}
const usuario = await getUsuarioByDni(dni);
if (usuario) {
await updateUsuario(usuario.id, { email: newEmail });
}
res.json({ ok: true });
} catch (err) {
res.status(500).json({ error: 'Error al actualizar email' });
}
});
// ========== ADMIN SYSTEM ENDPOINTS ==========
app.post('/api/admin/mongosh', async (req, res) => {
try {
const { command } = req.body;
if (!command) {
return res.status(400).json({ error: 'Comando requerido' });
}
const dbInstance = getDb();
if (!dbInstance) {
return res.json({ result: 'Advertencia: MongoDB no está conectado. Utilizando store en memoria. Las consultas de mongosh no están disponibles.', isMemStore: true });
}
const dbProxy = new Proxy(dbInstance, {
get(target, prop) {
if (typeof target[prop] !== 'undefined') {
if (typeof target[prop] === 'function') {
return target[prop].bind(target);
}
return target[prop];
}
return target.collection(prop);
}
});
const { ObjectId } = await import('mongodb');
const evalFn = new Function('db', 'ObjectId', `
return (async () => {
${command.trim().includes('return') || command.trim().startsWith('{') || command.trim().includes(';') ? command : 'return (' + command + ')'}
})();
`);
let finalResult = await evalFn(dbProxy, ObjectId);
// Si el resultado es un cursor de MongoDB (ej. db.collection.find()), lo convertimos a array
if (finalResult && typeof finalResult === 'object' && typeof finalResult.toArray === 'function') {
finalResult = await finalResult.toArray();
}
res.json({ result: finalResult });
} catch (err) {
res.status(400).json({ error: err.message || String(err) });
}
});
app.get('/api/admin/db/export', async (req, res) => {
try {
const dump = await exportAllData();
res.setHeader('Content-Type', 'application/json');
res.setHeader('Content-Disposition', 'attachment; filename=hospital_backup.json');
res.json(dump);
} catch (err) {
res.status(500).json({ error: 'Error al exportar base de datos: ' + err.message });
}
});
app.post('/api/admin/db/import', async (req, res) => {
try {
const dump = req.body;
if (!dump || typeof dump !== 'object') {
return res.status(400).json({ error: 'Formato de importación inválido' });
}
await importAllData(dump);
res.json({ ok: true });
} catch (err) {
res.status(500).json({ error: 'Error al importar base de datos: ' + err.message });
}
});
app.get('/api/admin/logs', async (req, res) => {
try {
let fileLogs = '';
try {
const fs = await import('fs/promises');
fileLogs = await fs.readFile('server_log.txt', 'utf8');
} catch {
// ignore
}
const memoryLogs = logBuffer.join('\n');
const combined = [fileLogs, '\n--- LOGS DE LA SESION EN VIVO ---', memoryLogs].filter(Boolean).join('\n');
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
res.send(combined);
} catch (err) {
res.status(500).send('Error al leer logs: ' + err.message);
}
});
// Initialize DB and start server
const PORT = process.env.PORT || 4000;
const HOST = process.env.HOST || '0.0.0.0';
if (import.meta.url === `file://${process.argv[1]}`) {
initDb()
.then(() => {
app.listen(PORT, HOST, () => {
console.log(`Backend API running on http://${HOST}:${PORT}`);
});
})
.catch((err) => {
console.error('Failed to initialize MongoDB in standalone mode:', err);
process.exit(1);
});
}
export { initDb };
export default app;