Actualizar control de glucemias con eje Y secundario para correcciones e integracion de tienda
This commit is contained in:
@@ -0,0 +1,9 @@
|
|||||||
|
# GEMINI_API_KEY: Required for Gemini AI API calls.
|
||||||
|
# AI Studio automatically injects this at runtime from user secrets.
|
||||||
|
# Users configure this via the Secrets panel in the AI Studio UI.
|
||||||
|
GEMINI_API_KEY="MY_GEMINI_API_KEY"
|
||||||
|
|
||||||
|
# APP_URL: The URL where this applet is hosted.
|
||||||
|
# AI Studio automatically injects this at runtime with the Cloud Run service URL.
|
||||||
|
# Used for self-referential links, OAuth callbacks, and API endpoints.
|
||||||
|
APP_URL="MY_APP_URL"
|
||||||
+11
-10
@@ -1,11 +1,12 @@
|
|||||||
node_modules
|
node_modules/
|
||||||
dist
|
build/
|
||||||
server/node_modules
|
dist/
|
||||||
server/*.log
|
coverage/
|
||||||
*.db
|
|
||||||
*.db-shm
|
|
||||||
*.db-wal
|
|
||||||
*.db-journal
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.env
|
*.log
|
||||||
.env.local
|
.env*
|
||||||
|
!.env.example
|
||||||
|
*.db
|
||||||
|
*.db-wal
|
||||||
|
*.db-shm
|
||||||
|
server/data/
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"name": "Administración Hospital",
|
||||||
|
"description": "Sistema de gestión y administración hospitalaria.",
|
||||||
|
"requestFramePermissions": [],
|
||||||
|
"majorCapabilities": ["MAJOR_CAPABILITY_SERVER_SIDE_GEMINI_API"]
|
||||||
|
}
|
||||||
+4
-2
@@ -4,8 +4,9 @@
|
|||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "tsx server.ts",
|
||||||
"build": "BUILD_PATH='./dist' tsc -b && vite build",
|
"build": "vite build && esbuild server.ts --bundle --platform=node --format=cjs --packages=external --sourcemap --outfile=dist/server.cjs",
|
||||||
|
"start": "node dist/server.cjs",
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
@@ -80,6 +81,7 @@
|
|||||||
"postcss": "^8.5.6",
|
"postcss": "^8.5.6",
|
||||||
"tailwindcss": "^3.4.19",
|
"tailwindcss": "^3.4.19",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
|
"tsx": "^4.23.9",
|
||||||
"tw-animate-css": "^1.4.0",
|
"tw-animate-css": "^1.4.0",
|
||||||
"typescript": "~5.9.3",
|
"typescript": "~5.9.3",
|
||||||
"typescript-eslint": "^8.46.4",
|
"typescript-eslint": "^8.46.4",
|
||||||
|
|||||||
Binary file not shown.
@@ -0,0 +1,34 @@
|
|||||||
|
import express from 'express';
|
||||||
|
import path from 'path';
|
||||||
|
import { createServer as createViteServer } from 'vite';
|
||||||
|
import apiApp from './server/index.js';
|
||||||
|
|
||||||
|
async function startServer() {
|
||||||
|
const app = express();
|
||||||
|
const PORT = 3000;
|
||||||
|
const HOST = '0.0.0.0';
|
||||||
|
|
||||||
|
// Mount API routes
|
||||||
|
app.use(apiApp);
|
||||||
|
|
||||||
|
// Vite middleware for development or static serving for production
|
||||||
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
|
const vite = await createViteServer({
|
||||||
|
server: { middlewareMode: true },
|
||||||
|
appType: 'spa',
|
||||||
|
});
|
||||||
|
app.use(vite.middlewares);
|
||||||
|
} else {
|
||||||
|
const distPath = path.join(process.cwd(), 'dist');
|
||||||
|
app.use(express.static(distPath));
|
||||||
|
app.get('*', (_req, res) => {
|
||||||
|
res.sendFile(path.join(distPath, 'index.html'));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
app.listen(PORT, HOST, () => {
|
||||||
|
console.log(`Hospital Server running on http://${HOST}:${PORT}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
startServer();
|
||||||
+563
@@ -27,6 +27,231 @@ db.pragma('cache_size = 1000');
|
|||||||
db.pragma('temp_store = MEMORY');
|
db.pragma('temp_store = MEMORY');
|
||||||
db.pragma('mmap_size = 268435456'); // 256MB
|
db.pragma('mmap_size = 268435456'); // 256MB
|
||||||
|
|
||||||
|
export function initDb() {
|
||||||
|
db.exec(`
|
||||||
|
CREATE TABLE IF NOT EXISTS usuarios (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
apellido TEXT NOT NULL,
|
||||||
|
nombre TEXT NOT NULL,
|
||||||
|
dni TEXT NOT NULL UNIQUE,
|
||||||
|
fechaNacimiento TEXT,
|
||||||
|
email TEXT,
|
||||||
|
rol TEXT,
|
||||||
|
matriculaProfesional TEXT,
|
||||||
|
passwordHash TEXT,
|
||||||
|
areaId TEXT,
|
||||||
|
fechaCreacion TEXT
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS pacientes (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
apellido TEXT NOT NULL,
|
||||||
|
nombre TEXT NOT NULL,
|
||||||
|
dni TEXT NOT NULL UNIQUE,
|
||||||
|
fechaNacimiento TEXT,
|
||||||
|
sexo TEXT,
|
||||||
|
telefono TEXT,
|
||||||
|
email TEXT,
|
||||||
|
direccion TEXT,
|
||||||
|
obraSocial TEXT,
|
||||||
|
nacionalidad TEXT,
|
||||||
|
medicacionHabitual TEXT,
|
||||||
|
antecedentes TEXT,
|
||||||
|
alergias TEXT,
|
||||||
|
grupoSanguineo TEXT,
|
||||||
|
historiaClinica TEXT
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS areas (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
nombre TEXT NOT NULL UNIQUE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS camas (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
numero TEXT NOT NULL,
|
||||||
|
areaId TEXT,
|
||||||
|
tipo TEXT DEFAULT 'Estándar',
|
||||||
|
estado TEXT DEFAULT 'Disponible',
|
||||||
|
pacienteId TEXT,
|
||||||
|
internacionId TEXT
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS internaciones (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
pacienteId TEXT NOT NULL,
|
||||||
|
camaId TEXT,
|
||||||
|
areaId TEXT,
|
||||||
|
fechaIngresoHospital TEXT,
|
||||||
|
fechaIngresoClinica TEXT,
|
||||||
|
fechaEgreso TEXT,
|
||||||
|
diagnosticoIngreso TEXT,
|
||||||
|
motivoConsulta TEXT,
|
||||||
|
enfermedadActual TEXT,
|
||||||
|
antecedentesEnfermedadActual TEXT,
|
||||||
|
diagnosticoEgreso TEXT,
|
||||||
|
medicoIngresante TEXT,
|
||||||
|
motivoEgreso TEXT,
|
||||||
|
activa INTEGER DEFAULT 1,
|
||||||
|
apache TEXT,
|
||||||
|
derivacion TEXT
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS evoluciones (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
internacionId TEXT NOT NULL,
|
||||||
|
fecha TEXT NOT NULL,
|
||||||
|
hora TEXT,
|
||||||
|
medico TEXT,
|
||||||
|
signosVitales TEXT,
|
||||||
|
examenFisico TEXT,
|
||||||
|
novedades TEXT,
|
||||||
|
comentarios TEXT,
|
||||||
|
pendientes TEXT
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS laboratorios (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
pacienteId TEXT NOT NULL,
|
||||||
|
internacionId TEXT,
|
||||||
|
fecha TEXT NOT NULL,
|
||||||
|
hora TEXT,
|
||||||
|
tipo TEXT,
|
||||||
|
resultados TEXT,
|
||||||
|
observaciones TEXT,
|
||||||
|
medicoSolicitante TEXT
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS glucemias (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
pacienteId TEXT NOT NULL,
|
||||||
|
internacionId TEXT,
|
||||||
|
fecha TEXT NOT NULL,
|
||||||
|
hora TEXT,
|
||||||
|
valor REAL NOT NULL,
|
||||||
|
correccion REAL NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS acidosbase (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
pacienteId TEXT NOT NULL,
|
||||||
|
internacionId TEXT,
|
||||||
|
fecha TEXT NOT NULL,
|
||||||
|
hora TEXT,
|
||||||
|
ph REAL,
|
||||||
|
pco2 REAL,
|
||||||
|
po2 REAL,
|
||||||
|
hco3 REAL,
|
||||||
|
be REAL,
|
||||||
|
sato2 REAL,
|
||||||
|
lactato REAL,
|
||||||
|
fio2 REAL,
|
||||||
|
interpretacion TEXT
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS cultivos (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
pacienteId TEXT NOT NULL,
|
||||||
|
internacionId TEXT,
|
||||||
|
fechaToma TEXT,
|
||||||
|
protocolo TEXT,
|
||||||
|
fechaResultado TEXT,
|
||||||
|
tipoMuestra TEXT,
|
||||||
|
germen TEXT,
|
||||||
|
sensible TEXT,
|
||||||
|
resistente TEXT,
|
||||||
|
estado TEXT DEFAULT 'NAF/Pendiente',
|
||||||
|
observaciones TEXT
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS estudiosComplementarios (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
pacienteId TEXT NOT NULL,
|
||||||
|
internacionId TEXT,
|
||||||
|
fecha TEXT,
|
||||||
|
tipo TEXT,
|
||||||
|
resultado TEXT
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS interconsultas (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
pacienteId TEXT NOT NULL,
|
||||||
|
internacionId TEXT NOT NULL,
|
||||||
|
fecha TEXT,
|
||||||
|
servicioInterconsultado TEXT,
|
||||||
|
motivo TEXT,
|
||||||
|
respuestaInterconsulta TEXT,
|
||||||
|
respuestaFecha TEXT
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS atb (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
pacienteId TEXT NOT NULL,
|
||||||
|
internacionId TEXT NOT NULL,
|
||||||
|
antibiotico TEXT,
|
||||||
|
fechaInicio TEXT,
|
||||||
|
fechaFinalizacion TEXT
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS indicaciones (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
internacionId TEXT NOT NULL,
|
||||||
|
tipo TEXT NOT NULL,
|
||||||
|
droga TEXT,
|
||||||
|
dosis TEXT,
|
||||||
|
frecuenciaHoras INTEGER,
|
||||||
|
via TEXT,
|
||||||
|
indicacionNoFco TEXT,
|
||||||
|
tipoPlan TEXT,
|
||||||
|
tipoPlan2 TEXT,
|
||||||
|
cantidadMl INTEGER,
|
||||||
|
cantidadMl2 INTEGER,
|
||||||
|
tiempoHoras INTEGER,
|
||||||
|
tipoInsulina TEXT,
|
||||||
|
unidadesDesayuno INTEGER,
|
||||||
|
unidadesAlmuerzo INTEGER,
|
||||||
|
unidadesNoche INTEGER,
|
||||||
|
estado TEXT DEFAULT 'Activa',
|
||||||
|
medicoCrea TEXT,
|
||||||
|
fechaCrea TEXT
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS movimientos_indicaciones (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
indicacionId TEXT NOT NULL,
|
||||||
|
internacionId TEXT NOT NULL,
|
||||||
|
tipo TEXT NOT NULL,
|
||||||
|
fecha TEXT NOT NULL,
|
||||||
|
profesional TEXT NOT NULL,
|
||||||
|
indicacionPrevia TEXT,
|
||||||
|
indicacionNueva TEXT
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS kv (
|
||||||
|
key TEXT PRIMARY KEY,
|
||||||
|
value TEXT
|
||||||
|
);
|
||||||
|
`);
|
||||||
|
|
||||||
|
// Create default admin if no users exist
|
||||||
|
try {
|
||||||
|
const userCount = db.prepare('SELECT COUNT(*) as count FROM usuarios').get();
|
||||||
|
if (!userCount || userCount.count === 0) {
|
||||||
|
const adminHash = bcrypt.hashSync('admin123', 10);
|
||||||
|
db.prepare(`
|
||||||
|
INSERT INTO usuarios (id, apellido, nombre, dni, fechaNacimiento, email, rol, passwordHash, fechaCreacion)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
|
`).run('admin-default', 'Administrador', 'Sistema', '12345678', '1990-01-01', 'admin@hospital.local', 'admin', adminHash, new Date().toISOString().split('T')[0]);
|
||||||
|
console.log('Usuario admin por defecto creado: DNI 12345678 / Clave admin123');
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error al verificar usuario admin por defecto:', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto-initialize tables
|
||||||
|
initDb();
|
||||||
|
|
||||||
export function getDb() {
|
export function getDb() {
|
||||||
return db;
|
return db;
|
||||||
}
|
}
|
||||||
@@ -166,6 +391,7 @@ export function getAllEvoluciones() {
|
|||||||
const rows = db.prepare('SELECT * FROM evoluciones').all();
|
const rows = db.prepare('SELECT * FROM evoluciones').all();
|
||||||
return rows.map(e => ({
|
return rows.map(e => ({
|
||||||
...e,
|
...e,
|
||||||
|
comentario: e.comentarios || e.comentario || '',
|
||||||
signosVitales: typeof e.signosVitales === 'string' ? JSON.parse(e.signosVitales) : e.signosVitales,
|
signosVitales: typeof e.signosVitales === 'string' ? JSON.parse(e.signosVitales) : e.signosVitales,
|
||||||
examenFisico: typeof e.examenFisico === 'string' ? JSON.parse(e.examenFisico) : e.examenFisico
|
examenFisico: typeof e.examenFisico === 'string' ? JSON.parse(e.examenFisico) : e.examenFisico
|
||||||
}));
|
}));
|
||||||
@@ -176,6 +402,11 @@ export function getAllLaboratorios() {
|
|||||||
return rows.map(l => ({ ...l, resultados: typeof l.resultados === 'string' ? JSON.parse(l.resultados) : l.resultados }));
|
return rows.map(l => ({ ...l, resultados: typeof l.resultados === 'string' ? JSON.parse(l.resultados) : l.resultados }));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getAllGlucemias() {
|
||||||
|
const rows = db.prepare('SELECT * FROM glucemias').all();
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
|
||||||
export function getAllAcidosBase() {
|
export function getAllAcidosBase() {
|
||||||
const rows = db.prepare('SELECT * FROM acidosbase').all();
|
const rows = db.prepare('SELECT * FROM acidosbase').all();
|
||||||
return rows;
|
return rows;
|
||||||
@@ -210,3 +441,335 @@ export function getAllMovimientosIndicaciones() {
|
|||||||
const rows = db.prepare('SELECT * FROM movimientos_indicaciones').all();
|
const rows = db.prepare('SELECT * FROM movimientos_indicaciones').all();
|
||||||
return rows;
|
return rows;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pacientes
|
||||||
|
export function createPaciente(p) {
|
||||||
|
const stmt = db.prepare('INSERT INTO pacientes (id, apellido, nombre, dni, fechaNacimiento, sexo, telefono, email, direccion, obraSocial, nacionalidad, medicacionHabitual, antecedentes, alergias, grupoSanguineo, historiaClinica) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)');
|
||||||
|
stmt.run(p.id, p.apellido, p.nombre, p.dni, p.fechaNacimiento || null, p.sexo || null, p.telefono || null, p.email || null, p.direccion || null, p.obraSocial || null, p.nacionalidad || null, p.medicacionHabitual || null, p.antecedentes || null, p.alergias || null, p.grupoSanguineo || null, p.historiaClinica || null);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updatePaciente(id, datos) {
|
||||||
|
const fields = [];
|
||||||
|
const values = [];
|
||||||
|
for (const [key, val] of Object.entries(datos)) {
|
||||||
|
if (key !== 'id' && val !== undefined) {
|
||||||
|
fields.push(`${key} = ?`);
|
||||||
|
values.push(val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (fields.length > 0) {
|
||||||
|
values.push(id);
|
||||||
|
db.prepare(`UPDATE pacientes SET ${fields.join(', ')} WHERE id = ?`).run(...values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deletePaciente(id) {
|
||||||
|
db.prepare('DELETE FROM pacientes WHERE id = ?').run(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Areas
|
||||||
|
export function createArea(area) {
|
||||||
|
db.prepare('INSERT INTO areas (id, nombre) VALUES (?, ?)').run(area.id, area.nombre);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateArea(id, datos) {
|
||||||
|
if (datos.nombre) {
|
||||||
|
db.prepare('UPDATE areas SET nombre = ? WHERE id = ?').run(datos.nombre, id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deleteArea(id) {
|
||||||
|
db.prepare('DELETE FROM areas WHERE id = ?').run(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Camas
|
||||||
|
export function createCama(cama) {
|
||||||
|
db.prepare('INSERT INTO camas (id, numero, areaId, tipo, estado, pacienteId, internacionId) VALUES (?, ?, ?, ?, ?, ?, ?)').run(cama.id, cama.numero, cama.areaId || null, cama.tipo || 'Estándar', cama.estado || 'Disponible', cama.pacienteId || null, cama.internacionId || null);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deleteCama(id) {
|
||||||
|
db.prepare('DELETE FROM camas WHERE id = ?').run(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internaciones
|
||||||
|
export function createInternacion(i) {
|
||||||
|
const stmt = db.prepare('INSERT INTO internaciones (id, pacienteId, camaId, areaId, fechaIngresoHospital, fechaIngresoClinica, fechaEgreso, diagnosticoIngreso, motivoConsulta, enfermedadActual, antecedentesEnfermedadActual, diagnosticoEgreso, medicoIngresante, motivoEgreso, activa, apache, derivacion) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)');
|
||||||
|
stmt.run(i.id, i.pacienteId, i.camaId || null, i.areaId || null, i.fechaIngresoHospital || null, i.fechaIngresoClinica || null, i.fechaEgreso || null, i.diagnosticoIngreso || null, i.motivoConsulta || null, i.enfermedadActual || null, i.antecedentesEnfermedadActual || null, i.diagnosticoEgreso || null, i.medicoIngresante || null, i.motivoEgreso || null, i.activa ? 1 : 0, i.apache || null, i.derivacion || null);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateInternacion(id, datos) {
|
||||||
|
const fields = [];
|
||||||
|
const values = [];
|
||||||
|
for (const [key, val] of Object.entries(datos)) {
|
||||||
|
if (key !== 'id' && val !== undefined) {
|
||||||
|
if (key === 'activa') {
|
||||||
|
fields.push('activa = ?');
|
||||||
|
values.push(val ? 1 : 0);
|
||||||
|
} else {
|
||||||
|
fields.push(`${key} = ?`);
|
||||||
|
values.push(val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (fields.length > 0) {
|
||||||
|
values.push(id);
|
||||||
|
db.prepare(`UPDATE internaciones SET ${fields.join(', ')} WHERE id = ?`).run(...values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deleteInternacion(id) {
|
||||||
|
db.prepare('DELETE FROM internaciones WHERE id = ?').run(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Evoluciones
|
||||||
|
export function createEvolucion(e) {
|
||||||
|
const stmt = db.prepare('INSERT INTO evoluciones (id, internacionId, fecha, hora, medico, signosVitales, examenFisico, novedades, comentarios, pendientes) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)');
|
||||||
|
stmt.run(e.id, e.internacionId, e.fecha, e.hora || '', e.medico || '', JSON.stringify(e.signosVitales || {}), JSON.stringify(e.examenFisico || {}), e.novedades || '', e.comentarios || e.comentario || '', e.pendientes || '');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateEvolucion(id, datos) {
|
||||||
|
const fields = [];
|
||||||
|
const values = [];
|
||||||
|
for (const [key, val] of Object.entries(datos)) {
|
||||||
|
if (key !== 'id' && val !== undefined) {
|
||||||
|
if (key === 'comentario') {
|
||||||
|
fields.push('comentarios = ?');
|
||||||
|
values.push(val);
|
||||||
|
} else if (key === 'signosVitales' || key === 'examenFisico') {
|
||||||
|
fields.push(`${key} = ?`);
|
||||||
|
values.push(JSON.stringify(val));
|
||||||
|
} else {
|
||||||
|
fields.push(`${key} = ?`);
|
||||||
|
values.push(val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (fields.length > 0) {
|
||||||
|
values.push(id);
|
||||||
|
db.prepare(`UPDATE evoluciones SET ${fields.join(', ')} WHERE id = ?`).run(...values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deleteEvolucion(id) {
|
||||||
|
db.prepare('DELETE FROM evoluciones WHERE id = ?').run(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Laboratorios
|
||||||
|
export function createLaboratorio(l) {
|
||||||
|
const stmt = db.prepare('INSERT INTO laboratorios (id, pacienteId, internacionId, fecha, hora, tipo, resultados, observaciones, medicoSolicitante) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)');
|
||||||
|
stmt.run(l.id, l.pacienteId, l.internacionId || null, l.fecha, l.hora || null, l.tipo || null, JSON.stringify(l.resultados || {}), l.observaciones || null, l.medicoSolicitante || null);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateLaboratorio(id, datos) {
|
||||||
|
const fields = [];
|
||||||
|
const values = [];
|
||||||
|
for (const [key, val] of Object.entries(datos)) {
|
||||||
|
if (key !== 'id' && val !== undefined) {
|
||||||
|
if (key === 'resultados') {
|
||||||
|
fields.push('resultados = ?');
|
||||||
|
values.push(JSON.stringify(val));
|
||||||
|
} else {
|
||||||
|
fields.push(`${key} = ?`);
|
||||||
|
values.push(val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (fields.length > 0) {
|
||||||
|
values.push(id);
|
||||||
|
db.prepare(`UPDATE laboratorios SET ${fields.join(', ')} WHERE id = ?`).run(...values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deleteLaboratorio(id) {
|
||||||
|
db.prepare('DELETE FROM laboratorios WHERE id = ?').run(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Glucemias
|
||||||
|
export function createGlucemia(g) {
|
||||||
|
const stmt = db.prepare('INSERT INTO glucemias (id, pacienteId, internacionId, fecha, hora, valor, correccion) VALUES (?, ?, ?, ?, ?, ?, ?)');
|
||||||
|
stmt.run(g.id, g.pacienteId, g.internacionId || null, g.fecha, g.hora || null, g.valor, g.correccion);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateGlucemia(id, datos) {
|
||||||
|
const fields = [];
|
||||||
|
const values = [];
|
||||||
|
for (const [key, val] of Object.entries(datos)) {
|
||||||
|
if (key !== 'id' && val !== undefined) {
|
||||||
|
fields.push(`${key} = ?`);
|
||||||
|
values.push(val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (fields.length > 0) {
|
||||||
|
values.push(id);
|
||||||
|
db.prepare(`UPDATE glucemias SET ${fields.join(', ')} WHERE id = ?`).run(...values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deleteGlucemia(id) {
|
||||||
|
db.prepare('DELETE FROM glucemias WHERE id = ?').run(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// AcidosBase
|
||||||
|
export function createAcidoBase(a) {
|
||||||
|
const stmt = db.prepare('INSERT INTO acidosbase (id, pacienteId, internacionId, fecha, hora, ph, pco2, po2, hco3, be, sato2, lactato, fio2, interpretacion) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)');
|
||||||
|
stmt.run(a.id, a.pacienteId, a.internacionId || null, a.fecha, a.hora || null, a.ph, a.pco2, a.po2, a.hco3, a.be, a.sato2, a.lactato || null, a.fio2 || null, a.interpretacion || null);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateAcidoBase(id, datos) {
|
||||||
|
const fields = [];
|
||||||
|
const values = [];
|
||||||
|
for (const [key, val] of Object.entries(datos)) {
|
||||||
|
if (key !== 'id' && val !== undefined) {
|
||||||
|
fields.push(`${key} = ?`);
|
||||||
|
values.push(val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (fields.length > 0) {
|
||||||
|
values.push(id);
|
||||||
|
db.prepare(`UPDATE acidosbase SET ${fields.join(', ')} WHERE id = ?`).run(...values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deleteAcidoBase(id) {
|
||||||
|
db.prepare('DELETE FROM acidosbase WHERE id = ?').run(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cultivos
|
||||||
|
export function createCultivo(c) {
|
||||||
|
const stmt = db.prepare('INSERT INTO cultivos (id, pacienteId, internacionId, fechaToma, protocolo, fechaResultado, tipoMuestra, germen, sensible, resistente, estado, observaciones) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)');
|
||||||
|
stmt.run(c.id, c.pacienteId, c.internacionId || null, c.fechaToma || null, c.protocolo || null, c.fechaResultado || null, c.tipoMuestra || null, c.germen || null, c.sensible || null, c.resistente || null, c.estado || 'NAF/Pendiente', c.observaciones || null);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateCultivo(id, datos) {
|
||||||
|
const fields = [];
|
||||||
|
const values = [];
|
||||||
|
for (const [key, val] of Object.entries(datos)) {
|
||||||
|
if (key !== 'id' && val !== undefined) {
|
||||||
|
fields.push(`${key} = ?`);
|
||||||
|
values.push(val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (fields.length > 0) {
|
||||||
|
values.push(id);
|
||||||
|
db.prepare(`UPDATE cultivos SET ${fields.join(', ')} WHERE id = ?`).run(...values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deleteCultivo(id) {
|
||||||
|
db.prepare('DELETE FROM cultivos WHERE id = ?').run(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Estudios Complementarios
|
||||||
|
export function createEstudioComplementario(e) {
|
||||||
|
const stmt = db.prepare('INSERT INTO estudiosComplementarios (id, pacienteId, internacionId, fecha, tipo, resultado) VALUES (?, ?, ?, ?, ?, ?)');
|
||||||
|
stmt.run(e.id, e.pacienteId, e.internacionId || null, e.fecha || null, e.tipo || null, e.resultado || null);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateEstudioComplementario(id, datos) {
|
||||||
|
const fields = [];
|
||||||
|
const values = [];
|
||||||
|
for (const [key, val] of Object.entries(datos)) {
|
||||||
|
if (key !== 'id' && val !== undefined) {
|
||||||
|
fields.push(`${key} = ?`);
|
||||||
|
values.push(val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (fields.length > 0) {
|
||||||
|
values.push(id);
|
||||||
|
db.prepare(`UPDATE estudiosComplementarios SET ${fields.join(', ')} WHERE id = ?`).run(...values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deleteEstudioComplementario(id) {
|
||||||
|
db.prepare('DELETE FROM estudiosComplementarios WHERE id = ?').run(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interconsultas
|
||||||
|
export function createInterconsulta(ic) {
|
||||||
|
const stmt = db.prepare('INSERT INTO interconsultas (id, pacienteId, internacionId, fecha, servicioInterconsultado, motivo, respuestaInterconsulta, respuestaFecha) VALUES (?, ?, ?, ?, ?, ?, ?, ?)');
|
||||||
|
stmt.run(ic.id, ic.pacienteId, ic.internacionId, ic.fecha || null, ic.servicioInterconsultado, ic.motivo || null, ic.respuestaInterconsulta || null, ic.respuestaFecha || null);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateInterconsulta(id, datos) {
|
||||||
|
const fields = [];
|
||||||
|
const values = [];
|
||||||
|
for (const [key, val] of Object.entries(datos)) {
|
||||||
|
if (key !== 'id' && val !== undefined) {
|
||||||
|
fields.push(`${key} = ?`);
|
||||||
|
values.push(val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (fields.length > 0) {
|
||||||
|
values.push(id);
|
||||||
|
db.prepare(`UPDATE interconsultas SET ${fields.join(', ')} WHERE id = ?`).run(...values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deleteInterconsulta(id) {
|
||||||
|
db.prepare('DELETE FROM interconsultas WHERE id = ?').run(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ATB
|
||||||
|
export function createAtb(a) {
|
||||||
|
const stmt = db.prepare('INSERT INTO atb (id, pacienteId, internacionId, antibiotico, fechaInicio, fechaFinalizacion) VALUES (?, ?, ?, ?, ?, ?)');
|
||||||
|
stmt.run(a.id, a.pacienteId, a.internacionId, a.antibiotico || null, a.fechaInicio || null, a.fechaFinalizacion || null);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateAtb(id, datos) {
|
||||||
|
const fields = [];
|
||||||
|
const values = [];
|
||||||
|
for (const [key, val] of Object.entries(datos)) {
|
||||||
|
if (key !== 'id' && val !== undefined) {
|
||||||
|
fields.push(`${key} = ?`);
|
||||||
|
values.push(val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (fields.length > 0) {
|
||||||
|
values.push(id);
|
||||||
|
db.prepare(`UPDATE atb SET ${fields.join(', ')} WHERE id = ?`).run(...values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deleteAtb(id) {
|
||||||
|
db.prepare('DELETE FROM atb WHERE id = ?').run(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Indicaciones
|
||||||
|
export function createIndicacion(ind) {
|
||||||
|
const stmt = db.prepare('INSERT INTO indicaciones (id, internacionId, tipo, droga, dosis, frecuenciaHoras, via, indicacionNoFco, tipoPlan, tipoPlan2, cantidadMl, cantidadMl2, tiempoHoras, tipoInsulina, unidadesDesayuno, unidadesAlmuerzo, unidadesNoche, estado, medicoCrea, fechaCrea) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)');
|
||||||
|
stmt.run(ind.id, ind.internacionId, ind.tipo, ind.droga || null, ind.dosis || null, ind.frecuenciaHoras || null, ind.via || null, ind.indicacionNoFco || null, ind.tipoPlan || null, ind.tipoPlan2 || null, ind.cantidadMl || null, ind.cantidadMl2 || null, ind.tiempoHoras || null, ind.tipoInsulina || null, ind.unidadesDesayuno || null, ind.unidadesAlmuerzo || null, ind.unidadesNoche || null, ind.estado || 'Activa', ind.medicoCrea || null, ind.fechaCrea || null);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateIndicacion(id, datos) {
|
||||||
|
const fields = [];
|
||||||
|
const values = [];
|
||||||
|
for (const [key, val] of Object.entries(datos)) {
|
||||||
|
if (key !== 'id' && val !== undefined) {
|
||||||
|
fields.push(`${key} = ?`);
|
||||||
|
values.push(val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (fields.length > 0) {
|
||||||
|
values.push(id);
|
||||||
|
db.prepare(`UPDATE indicaciones SET ${fields.join(', ')} WHERE id = ?`).run(...values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deleteIndicacion(id) {
|
||||||
|
db.prepare('DELETE FROM indicaciones WHERE id = ?').run(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Movimientos Indicaciones
|
||||||
|
export function createMovimientoIndicacion(m) {
|
||||||
|
const stmt = db.prepare('INSERT INTO movimientos_indicaciones (id, indicacionId, internacionId, tipo, fecha, profesional, indicacionPrevia, indicacionNueva) VALUES (?, ?, ?, ?, ?, ?, ?, ?)');
|
||||||
|
stmt.run(
|
||||||
|
m.id,
|
||||||
|
m.indicacionId || '',
|
||||||
|
m.internacionId || '',
|
||||||
|
m.tipo || '',
|
||||||
|
m.fecha || new Date().toISOString(),
|
||||||
|
m.profesional || '',
|
||||||
|
m.indicacionPrevia || null,
|
||||||
|
m.indicacionNueva || null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
+298
-28
@@ -1,6 +1,22 @@
|
|||||||
import express from 'express';
|
import express from 'express';
|
||||||
import cors from 'cors';
|
import cors from 'cors';
|
||||||
import { getDb, getValue, setValue, getUsuarioByDni, getUsuarioById, getAllUsuarios, createUsuario, updateUsuario, deleteUsuario, verifyPassword, hashPassword, getAllPacientes, getAllAreas, getAllCamas, getAllInternaciones, getAllEvoluciones, getAllLaboratorios, getAllAcidosBase, getAllCultivos, getAllEstudiosComplementarios, getAllInterconsultas, getAllAtb, getAllIndicaciones, getAllMovimientosIndicaciones } from './db.js';
|
import {
|
||||||
|
getDb, getValue, setValue, getUsuarioByDni, getUsuarioById, getAllUsuarios, createUsuario, updateUsuario, deleteUsuario, verifyPassword, hashPassword,
|
||||||
|
getAllPacientes, createPaciente, updatePaciente, deletePaciente,
|
||||||
|
getAllAreas, createArea, updateArea, deleteArea,
|
||||||
|
getAllCamas, createCama, updateCama, deleteCama,
|
||||||
|
getAllInternaciones, createInternacion, updateInternacion, deleteInternacion,
|
||||||
|
getAllEvoluciones, createEvolucion, updateEvolucion, deleteEvolucion,
|
||||||
|
getAllLaboratorios, createLaboratorio, updateLaboratorio, deleteLaboratorio,
|
||||||
|
getAllGlucemias, createGlucemia, updateGlucemia, deleteGlucemia,
|
||||||
|
getAllAcidosBase, createAcidoBase, updateAcidoBase, deleteAcidoBase,
|
||||||
|
getAllCultivos, createCultivo, updateCultivo, deleteCultivo,
|
||||||
|
getAllEstudiosComplementarios, createEstudioComplementario, updateEstudioComplementario, deleteEstudioComplementario,
|
||||||
|
getAllInterconsultas, createInterconsulta, updateInterconsulta, deleteInterconsulta,
|
||||||
|
getAllAtb, createAtb, updateAtb, deleteAtb,
|
||||||
|
getAllIndicaciones, createIndicacion, updateIndicacion, deleteIndicacion,
|
||||||
|
getAllMovimientosIndicaciones, createMovimientoIndicacion
|
||||||
|
} from './db.js';
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
app.use(cors());
|
app.use(cors());
|
||||||
@@ -18,6 +34,7 @@ app.get('/api/state', (req, res) => {
|
|||||||
internaciones: getAllInternaciones(),
|
internaciones: getAllInternaciones(),
|
||||||
evoluciones: getAllEvoluciones(),
|
evoluciones: getAllEvoluciones(),
|
||||||
laboratorios: getAllLaboratorios(),
|
laboratorios: getAllLaboratorios(),
|
||||||
|
glucemias: getAllGlucemias(),
|
||||||
acidosBase: getAllAcidosBase(),
|
acidosBase: getAllAcidosBase(),
|
||||||
cultivos: getAllCultivos(),
|
cultivos: getAllCultivos(),
|
||||||
estudiosComplementarios: getAllEstudiosComplementarios(),
|
estudiosComplementarios: getAllEstudiosComplementarios(),
|
||||||
@@ -46,6 +63,7 @@ app.put('/api/state', (req, res) => {
|
|||||||
db.prepare('DELETE FROM acidosbase').run();
|
db.prepare('DELETE FROM acidosbase').run();
|
||||||
db.prepare('DELETE FROM cultivos').run();
|
db.prepare('DELETE FROM cultivos').run();
|
||||||
db.prepare('DELETE FROM laboratorios').run();
|
db.prepare('DELETE FROM laboratorios').run();
|
||||||
|
db.prepare('DELETE FROM glucemias').run();
|
||||||
db.prepare('DELETE FROM internaciones').run();
|
db.prepare('DELETE FROM internaciones').run();
|
||||||
db.prepare('DELETE FROM camas').run();
|
db.prepare('DELETE FROM camas').run();
|
||||||
db.prepare('DELETE FROM areas').run();
|
db.prepare('DELETE FROM areas').run();
|
||||||
@@ -98,6 +116,13 @@ app.put('/api/state', (req, res) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (state.glucemias?.length) {
|
||||||
|
const stmt = db.prepare('INSERT INTO glucemias (id, pacienteId, internacionId, fecha, hora, valor, correccion) VALUES (?, ?, ?, ?, ?, ?, ?)');
|
||||||
|
for (const g of state.glucemias) {
|
||||||
|
stmt.run(g.id, g.pacienteId, g.internacionId || null, g.fecha, g.hora || null, g.valor, g.correccion);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (state.acidosBase?.length) {
|
if (state.acidosBase?.length) {
|
||||||
const stmt = db.prepare('INSERT INTO acidosbase (id, pacienteId, fecha, hora, ph, pco2, po2, hco3, be, sato2, lactato, interpretacion, fio2) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)');
|
const stmt = db.prepare('INSERT INTO acidosbase (id, pacienteId, fecha, hora, ph, pco2, po2, hco3, be, sato2, lactato, interpretacion, fio2) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)');
|
||||||
for (const a of state.acidosBase) {
|
for (const a of state.acidosBase) {
|
||||||
@@ -226,9 +251,7 @@ app.put('/api/state/partial', (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
app.listen(PORT, () => {
|
export default app;
|
||||||
console.log(`Server running on port ${PORT}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
// === AUTENTICACIÓN ===
|
// === AUTENTICACIÓN ===
|
||||||
|
|
||||||
@@ -328,25 +351,26 @@ app.post('/api/usuarios', (req, res) => {
|
|||||||
try {
|
try {
|
||||||
const { apellido, nombre, dni, fechaNacimiento, email, rol, matriculaProfesional, password, areaId } = req.body;
|
const { apellido, nombre, dni, fechaNacimiento, email, rol, matriculaProfesional, password, areaId } = req.body;
|
||||||
|
|
||||||
if (!apellido || !nombre || !dni || !fechaNacimiento || !email || !rol || !password) {
|
if (!apellido?.trim() || !nombre?.trim() || !dni?.trim() || !rol) {
|
||||||
return res.status(400).json({ error: 'Todos los campos obligatorios deben estar presentes' });
|
return res.status(400).json({ error: 'Apellido, nombre, DNI y rol son obligatorios' });
|
||||||
}
|
}
|
||||||
|
|
||||||
const existente = getUsuarioByDni(dni);
|
const existente = getUsuarioByDni(dni.trim());
|
||||||
if (existente) {
|
if (existente) {
|
||||||
return res.status(400).json({ error: 'Ya existe un usuario con ese DNI' });
|
return res.status(400).json({ error: 'Ya existe un usuario con ese DNI' });
|
||||||
}
|
}
|
||||||
|
|
||||||
const passwordHash = hashPassword(password);
|
const passwordFinal = (password && password.trim()) ? password.trim() : (dni.trim() || '123456');
|
||||||
|
const passwordHash = hashPassword(passwordFinal);
|
||||||
const usuario = {
|
const usuario = {
|
||||||
id: generateUUID(),
|
id: generateUUID(),
|
||||||
apellido,
|
apellido: apellido.trim(),
|
||||||
nombre,
|
nombre: nombre.trim(),
|
||||||
dni,
|
dni: dni.trim(),
|
||||||
fechaNacimiento,
|
fechaNacimiento: fechaNacimiento?.trim() || null,
|
||||||
email,
|
email: email?.trim() || null,
|
||||||
rol,
|
rol,
|
||||||
matriculaProfesional: matriculaProfesional || null,
|
matriculaProfesional: matriculaProfesional?.trim() || null,
|
||||||
passwordHash,
|
passwordHash,
|
||||||
areaId: areaId || null,
|
areaId: areaId || null,
|
||||||
fechaCreacion: new Date().toISOString().split('T')[0]
|
fechaCreacion: new Date().toISOString().split('T')[0]
|
||||||
@@ -355,7 +379,7 @@ app.post('/api/usuarios', (req, res) => {
|
|||||||
createUsuario(usuario);
|
createUsuario(usuario);
|
||||||
res.json({ ...usuario, passwordHash: undefined });
|
res.json({ ...usuario, passwordHash: undefined });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error('Error al crear usuario:', err);
|
||||||
res.status(500).json({ error: 'Error al crear usuario' });
|
res.status(500).json({ error: 'Error al crear usuario' });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -363,29 +387,37 @@ app.post('/api/usuarios', (req, res) => {
|
|||||||
app.put('/api/usuarios/:id', (req, res) => {
|
app.put('/api/usuarios/:id', (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
const { apellido, nombre, fechaNacimiento, email, rol, matriculaProfesional, areaId, password } = req.body;
|
const { apellido, nombre, dni, fechaNacimiento, email, rol, matriculaProfesional, areaId, password } = req.body;
|
||||||
|
|
||||||
const usuario = getUsuarioById(id);
|
const usuario = getUsuarioById(id);
|
||||||
if (!usuario) {
|
if (!usuario) {
|
||||||
return res.status(404).json({ error: 'Usuario no encontrado' });
|
return res.status(404).json({ error: 'Usuario no encontrado' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (dni && dni.trim() !== usuario.dni) {
|
||||||
|
const existente = getUsuarioByDni(dni.trim());
|
||||||
|
if (existente && existente.id !== id) {
|
||||||
|
return res.status(400).json({ error: 'Ya existe otro usuario con ese DNI' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const datos = {};
|
const datos = {};
|
||||||
if (apellido !== undefined) datos.apellido = apellido;
|
if (apellido !== undefined) datos.apellido = apellido.trim();
|
||||||
if (nombre !== undefined) datos.nombre = nombre;
|
if (nombre !== undefined) datos.nombre = nombre.trim();
|
||||||
if (fechaNacimiento !== undefined) datos.fechaNacimiento = fechaNacimiento;
|
if (dni !== undefined) datos.dni = dni.trim();
|
||||||
if (email !== undefined) datos.email = email;
|
if (fechaNacimiento !== undefined) datos.fechaNacimiento = fechaNacimiento?.trim() || null;
|
||||||
|
if (email !== undefined) datos.email = email?.trim() || null;
|
||||||
if (rol !== undefined) datos.rol = rol;
|
if (rol !== undefined) datos.rol = rol;
|
||||||
if (matriculaProfesional !== undefined) datos.matriculaProfesional = matriculaProfesional;
|
if (matriculaProfesional !== undefined) datos.matriculaProfesional = matriculaProfesional?.trim() || null;
|
||||||
if (areaId !== undefined) datos.areaId = areaId;
|
if (areaId !== undefined) datos.areaId = areaId || null;
|
||||||
if (password) {
|
if (password && password.trim()) {
|
||||||
datos.passwordHash = hashPassword(password);
|
datos.passwordHash = hashPassword(password.trim());
|
||||||
}
|
}
|
||||||
|
|
||||||
updateUsuario(id, datos);
|
updateUsuario(id, datos);
|
||||||
res.json({ ok: true });
|
res.json({ ok: true });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error('Error al actualizar usuario:', err);
|
||||||
res.status(500).json({ error: 'Error al actualizar usuario' });
|
res.status(500).json({ error: 'Error al actualizar usuario' });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -404,9 +436,7 @@ app.delete('/api/usuarios/:id', (req, res) => {
|
|||||||
app.put('/api/camas/:id', (req, res) => {
|
app.put('/api/camas/:id', (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
const { estado } = req.body;
|
updateCama(id, req.body);
|
||||||
const db = getDb();
|
|
||||||
db.prepare('UPDATE camas SET estado = ? WHERE id = ?').run(estado, id);
|
|
||||||
res.json({ ok: true });
|
res.json({ ok: true });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error updating cama:', err.message);
|
console.error('Error updating cama:', err.message);
|
||||||
@@ -414,6 +444,246 @@ app.put('/api/camas/:id', (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ========== PACIENTES ==========
|
||||||
|
app.get('/api/pacientes', (req, res) => {
|
||||||
|
try { res.json(getAllPacientes()); } catch (err) { res.status(500).json({ error: err.message }); }
|
||||||
|
});
|
||||||
|
app.post('/api/pacientes', (req, res) => {
|
||||||
|
try {
|
||||||
|
const paciente = { ...req.body, id: req.body.id || generateUUID() };
|
||||||
|
createPaciente(paciente);
|
||||||
|
res.json(paciente);
|
||||||
|
} catch (err) { res.status(500).json({ error: err.message }); }
|
||||||
|
});
|
||||||
|
app.put('/api/pacientes/:id', (req, res) => {
|
||||||
|
try { updatePaciente(req.params.id, req.body); res.json({ ok: true }); } catch (err) { res.status(500).json({ error: err.message }); }
|
||||||
|
});
|
||||||
|
app.delete('/api/pacientes/:id', (req, res) => {
|
||||||
|
try { deletePaciente(req.params.id); res.json({ ok: true }); } catch (err) { res.status(500).json({ error: err.message }); }
|
||||||
|
});
|
||||||
|
|
||||||
|
// ========== AREAS ==========
|
||||||
|
app.post('/api/areas', (req, res) => {
|
||||||
|
try {
|
||||||
|
const area = { ...req.body, id: req.body.id || generateUUID() };
|
||||||
|
createArea(area);
|
||||||
|
res.json(area);
|
||||||
|
} catch (err) { res.status(500).json({ error: err.message }); }
|
||||||
|
});
|
||||||
|
app.put('/api/areas/:id', (req, res) => {
|
||||||
|
try { updateArea(req.params.id, req.body); res.json({ ok: true }); } catch (err) { res.status(500).json({ error: err.message }); }
|
||||||
|
});
|
||||||
|
app.delete('/api/areas/:id', (req, res) => {
|
||||||
|
try { deleteArea(req.params.id); res.json({ ok: true }); } catch (err) { res.status(500).json({ error: err.message }); }
|
||||||
|
});
|
||||||
|
|
||||||
|
// ========== CAMAS ==========
|
||||||
|
app.get('/api/camas', (req, res) => {
|
||||||
|
try { res.json(getAllCamas()); } catch (err) { res.status(500).json({ error: err.message }); }
|
||||||
|
});
|
||||||
|
app.post('/api/camas', (req, res) => {
|
||||||
|
try {
|
||||||
|
const cama = { ...req.body, id: req.body.id || generateUUID() };
|
||||||
|
createCama(cama);
|
||||||
|
res.json(cama);
|
||||||
|
} catch (err) { res.status(500).json({ error: err.message }); }
|
||||||
|
});
|
||||||
|
app.delete('/api/camas/:id', (req, res) => {
|
||||||
|
try { deleteCama(req.params.id); res.json({ ok: true }); } catch (err) { res.status(500).json({ error: err.message }); }
|
||||||
|
});
|
||||||
|
|
||||||
|
// ========== INTERNACIONES ==========
|
||||||
|
app.get('/api/internaciones', (req, res) => {
|
||||||
|
try { res.json(getAllInternaciones()); } catch (err) { res.status(500).json({ error: err.message }); }
|
||||||
|
});
|
||||||
|
app.post('/api/internaciones', (req, res) => {
|
||||||
|
try {
|
||||||
|
const internacion = { ...req.body, id: req.body.id || generateUUID(), activa: true };
|
||||||
|
createInternacion(internacion);
|
||||||
|
res.json(internacion);
|
||||||
|
} catch (err) { res.status(500).json({ error: err.message }); }
|
||||||
|
});
|
||||||
|
app.put('/api/internaciones/:id', (req, res) => {
|
||||||
|
try { updateInternacion(req.params.id, req.body); res.json({ ok: true }); } catch (err) { res.status(500).json({ error: err.message }); }
|
||||||
|
});
|
||||||
|
app.delete('/api/internaciones/:id', (req, res) => {
|
||||||
|
try { deleteInternacion(req.params.id); res.json({ ok: true }); } catch (err) { res.status(500).json({ error: err.message }); }
|
||||||
|
});
|
||||||
|
|
||||||
|
// ========== EVOLUCIONES ==========
|
||||||
|
app.get('/api/evoluciones', (req, res) => {
|
||||||
|
try { res.json(getAllEvoluciones()); } catch (err) { res.status(500).json({ error: err.message }); }
|
||||||
|
});
|
||||||
|
app.post('/api/evoluciones', (req, res) => {
|
||||||
|
try {
|
||||||
|
const evolucion = { ...req.body, id: req.body.id || generateUUID() };
|
||||||
|
createEvolucion(evolucion);
|
||||||
|
res.json(evolucion);
|
||||||
|
} catch (err) { res.status(500).json({ error: err.message }); }
|
||||||
|
});
|
||||||
|
app.put('/api/evoluciones/:id', (req, res) => {
|
||||||
|
try { updateEvolucion(req.params.id, req.body); res.json({ ok: true }); } catch (err) { res.status(500).json({ error: err.message }); }
|
||||||
|
});
|
||||||
|
app.delete('/api/evoluciones/:id', (req, res) => {
|
||||||
|
try { deleteEvolucion(req.params.id); res.json({ ok: true }); } catch (err) { res.status(500).json({ error: err.message }); }
|
||||||
|
});
|
||||||
|
|
||||||
|
// ========== LABORATORIOS ==========
|
||||||
|
app.get('/api/laboratorios', (req, res) => {
|
||||||
|
try { res.json(getAllLaboratorios()); } catch (err) { res.status(500).json({ error: err.message }); }
|
||||||
|
});
|
||||||
|
app.post('/api/laboratorios', (req, res) => {
|
||||||
|
try {
|
||||||
|
const laboratorio = { ...req.body, id: req.body.id || generateUUID() };
|
||||||
|
createLaboratorio(laboratorio);
|
||||||
|
res.json(laboratorio);
|
||||||
|
} catch (err) { res.status(500).json({ error: err.message }); }
|
||||||
|
});
|
||||||
|
app.put('/api/laboratorios/:id', (req, res) => {
|
||||||
|
try { updateLaboratorio(req.params.id, req.body); res.json({ ok: true }); } catch (err) { res.status(500).json({ error: err.message }); }
|
||||||
|
});
|
||||||
|
app.delete('/api/laboratorios/:id', (req, res) => {
|
||||||
|
try { deleteLaboratorio(req.params.id); res.json({ ok: true }); } catch (err) { res.status(500).json({ error: err.message }); }
|
||||||
|
});
|
||||||
|
|
||||||
|
// ========== GLUCEMIAS ==========
|
||||||
|
app.get('/api/glucemias', (req, res) => {
|
||||||
|
try { res.json(getAllGlucemias()); } catch (err) { res.status(500).json({ error: err.message }); }
|
||||||
|
});
|
||||||
|
app.post('/api/glucemias', (req, res) => {
|
||||||
|
try {
|
||||||
|
const glucemia = { ...req.body, id: req.body.id || generateUUID() };
|
||||||
|
createGlucemia(glucemia);
|
||||||
|
res.json(glucemia);
|
||||||
|
} catch (err) { res.status(500).json({ error: err.message }); }
|
||||||
|
});
|
||||||
|
app.put('/api/glucemias/:id', (req, res) => {
|
||||||
|
try { updateGlucemia(req.params.id, req.body); res.json({ ok: true }); } catch (err) { res.status(500).json({ error: err.message }); }
|
||||||
|
});
|
||||||
|
app.delete('/api/glucemias/:id', (req, res) => {
|
||||||
|
try { deleteGlucemia(req.params.id); res.json({ ok: true }); } catch (err) { res.status(500).json({ error: err.message }); }
|
||||||
|
});
|
||||||
|
|
||||||
|
// ========== ACIDOS BASE ==========
|
||||||
|
app.get('/api/acid-os-base', (req, res) => {
|
||||||
|
try { res.json(getAllAcidosBase()); } catch (err) { res.status(500).json({ error: err.message }); }
|
||||||
|
});
|
||||||
|
app.post('/api/acid-os-base', (req, res) => {
|
||||||
|
try {
|
||||||
|
const acido = { ...req.body, id: req.body.id || generateUUID() };
|
||||||
|
createAcidoBase(acido);
|
||||||
|
res.json(acido);
|
||||||
|
} catch (err) { res.status(500).json({ error: err.message }); }
|
||||||
|
});
|
||||||
|
app.put('/api/acid-os-base/:id', (req, res) => {
|
||||||
|
try { updateAcidoBase(req.params.id, req.body); res.json({ ok: true }); } catch (err) { res.status(500).json({ error: err.message }); }
|
||||||
|
});
|
||||||
|
app.delete('/api/acid-os-base/:id', (req, res) => {
|
||||||
|
try { deleteAcidoBase(req.params.id); res.json({ ok: true }); } catch (err) { res.status(500).json({ error: err.message }); }
|
||||||
|
});
|
||||||
|
|
||||||
|
// ========== CULTIVOS ==========
|
||||||
|
app.get('/api/cultivos', (req, res) => {
|
||||||
|
try { res.json(getAllCultivos()); } catch (err) { res.status(500).json({ error: err.message }); }
|
||||||
|
});
|
||||||
|
app.post('/api/cultivos', (req, res) => {
|
||||||
|
try {
|
||||||
|
const cultivo = { ...req.body, id: req.body.id || generateUUID() };
|
||||||
|
createCultivo(cultivo);
|
||||||
|
res.json(cultivo);
|
||||||
|
} catch (err) { res.status(500).json({ error: err.message }); }
|
||||||
|
});
|
||||||
|
app.put('/api/cultivos/:id', (req, res) => {
|
||||||
|
try { updateCultivo(req.params.id, req.body); res.json({ ok: true }); } catch (err) { res.status(500).json({ error: err.message }); }
|
||||||
|
});
|
||||||
|
app.delete('/api/cultivos/:id', (req, res) => {
|
||||||
|
try { deleteCultivo(req.params.id); res.json({ ok: true }); } catch (err) { res.status(500).json({ error: err.message }); }
|
||||||
|
});
|
||||||
|
|
||||||
|
// ========== ESTUDIOS COMPLEMENTARIOS ==========
|
||||||
|
app.get('/api/estudios-complementarios', (req, res) => {
|
||||||
|
try { res.json(getAllEstudiosComplementarios()); } catch (err) { res.status(500).json({ error: err.message }); }
|
||||||
|
});
|
||||||
|
app.post('/api/estudios-complementarios', (req, res) => {
|
||||||
|
try {
|
||||||
|
const estudio = { ...req.body, id: req.body.id || generateUUID() };
|
||||||
|
createEstudioComplementario(estudio);
|
||||||
|
res.json(estudio);
|
||||||
|
} catch (err) { res.status(500).json({ error: err.message }); }
|
||||||
|
});
|
||||||
|
app.put('/api/estudios-complementarios/:id', (req, res) => {
|
||||||
|
try { updateEstudioComplementario(req.params.id, req.body); res.json({ ok: true }); } catch (err) { res.status(500).json({ error: err.message }); }
|
||||||
|
});
|
||||||
|
app.delete('/api/estudios-complementarios/:id', (req, res) => {
|
||||||
|
try { deleteEstudioComplementario(req.params.id); res.json({ ok: true }); } catch (err) { res.status(500).json({ error: err.message }); }
|
||||||
|
});
|
||||||
|
|
||||||
|
// ========== INTERCONSULTAS ==========
|
||||||
|
app.get('/api/interconsultas', (req, res) => {
|
||||||
|
try { res.json(getAllInterconsultas()); } catch (err) { res.status(500).json({ error: err.message }); }
|
||||||
|
});
|
||||||
|
app.post('/api/interconsultas', (req, res) => {
|
||||||
|
try {
|
||||||
|
const interconsulta = { ...req.body, id: req.body.id || generateUUID() };
|
||||||
|
createInterconsulta(interconsulta);
|
||||||
|
res.json(interconsulta);
|
||||||
|
} catch (err) { res.status(500).json({ error: err.message }); }
|
||||||
|
});
|
||||||
|
app.put('/api/interconsultas/:id', (req, res) => {
|
||||||
|
try { updateInterconsulta(req.params.id, req.body); res.json({ ok: true }); } catch (err) { res.status(500).json({ error: err.message }); }
|
||||||
|
});
|
||||||
|
app.delete('/api/interconsultas/:id', (req, res) => {
|
||||||
|
try { deleteInterconsulta(req.params.id); res.json({ ok: true }); } catch (err) { res.status(500).json({ error: err.message }); }
|
||||||
|
});
|
||||||
|
|
||||||
|
// ========== ATB ==========
|
||||||
|
app.get('/api/atb', (req, res) => {
|
||||||
|
try { res.json(getAllAtb()); } catch (err) { res.status(500).json({ error: err.message }); }
|
||||||
|
});
|
||||||
|
app.post('/api/atb', (req, res) => {
|
||||||
|
try {
|
||||||
|
const atb = { ...req.body, id: req.body.id || generateUUID() };
|
||||||
|
createAtb(atb);
|
||||||
|
res.json(atb);
|
||||||
|
} catch (err) { res.status(500).json({ error: err.message }); }
|
||||||
|
});
|
||||||
|
app.put('/api/atb/:id', (req, res) => {
|
||||||
|
try { updateAtb(req.params.id, req.body); res.json({ ok: true }); } catch (err) { res.status(500).json({ error: err.message }); }
|
||||||
|
});
|
||||||
|
app.delete('/api/atb/:id', (req, res) => {
|
||||||
|
try { deleteAtb(req.params.id); res.json({ ok: true }); } catch (err) { res.status(500).json({ error: err.message }); }
|
||||||
|
});
|
||||||
|
|
||||||
|
// ========== INDICACIONES ==========
|
||||||
|
app.get('/api/indicaciones', (req, res) => {
|
||||||
|
try { res.json(getAllIndicaciones()); } catch (err) { res.status(500).json({ error: err.message }); }
|
||||||
|
});
|
||||||
|
app.post('/api/indicaciones', (req, res) => {
|
||||||
|
try {
|
||||||
|
const indicacion = { ...req.body, id: req.body.id || generateUUID() };
|
||||||
|
createIndicacion(indicacion);
|
||||||
|
res.json(indicacion);
|
||||||
|
} catch (err) { res.status(500).json({ error: err.message }); }
|
||||||
|
});
|
||||||
|
app.put('/api/indicaciones/:id', (req, res) => {
|
||||||
|
try { updateIndicacion(req.params.id, req.body); res.json({ ok: true }); } catch (err) { res.status(500).json({ error: err.message }); }
|
||||||
|
});
|
||||||
|
app.delete('/api/indicaciones/:id', (req, res) => {
|
||||||
|
try { deleteIndicacion(req.params.id); res.json({ ok: true }); } catch (err) { res.status(500).json({ error: err.message }); }
|
||||||
|
});
|
||||||
|
|
||||||
|
// ========== MOVIMIENTOS INDICACIONES ==========
|
||||||
|
app.get('/api/movimientos-indicaciones', (req, res) => {
|
||||||
|
try { res.json(getAllMovimientosIndicaciones()); } catch (err) { res.status(500).json({ error: err.message }); }
|
||||||
|
});
|
||||||
|
app.post('/api/movimientos-indicaciones', (req, res) => {
|
||||||
|
try {
|
||||||
|
const movimiento = { ...req.body, id: req.body.id || generateUUID() };
|
||||||
|
createMovimientoIndicacion(movimiento);
|
||||||
|
res.json(movimiento);
|
||||||
|
} catch (err) { res.status(500).json({ error: err.message }); }
|
||||||
|
});
|
||||||
|
|
||||||
// Helper para actualizar por DNI
|
// Helper para actualizar por DNI
|
||||||
function updateUsuarioByDni(dni, datos) {
|
function updateUsuarioByDni(dni, datos) {
|
||||||
const usuario = getUsuarioByDni(dni);
|
const usuario = getUsuarioByDni(dni);
|
||||||
|
|||||||
+12
-28
@@ -113,41 +113,21 @@ function AppContent() {
|
|||||||
getPacienteById={store.getPacienteById}
|
getPacienteById={store.getPacienteById}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
case 'cultivos': {
|
case 'cultivos':
|
||||||
const internacionId = store.currentInternacionId || '';
|
|
||||||
console.log('Cultivos - currentInternacionId:', internacionId);
|
|
||||||
const internacion = store.getInternacionById(internacionId);
|
|
||||||
console.log('Cultivos - internacion:', internacion);
|
|
||||||
const paciente = internacion ? store.getPacienteById(internacion.pacienteId) : undefined;
|
|
||||||
console.log('Cultivos - paciente:', paciente);
|
|
||||||
if (!internacion) {
|
|
||||||
return (
|
|
||||||
<div className="p-4 text-center">
|
|
||||||
<p className="text-gray-500 dark:text-gray-400">Internación no encontrada (id: {internacionId})</p>
|
|
||||||
<Button onClick={() => store.setVista('internaciones')}>Volver a Internaciones</Button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (!paciente) {
|
|
||||||
return (
|
|
||||||
<div className="p-4 text-center">
|
|
||||||
<p className="text-gray-500 dark:text-gray-400">Paciente no encontrado para internación</p>
|
|
||||||
<Button onClick={() => store.setVista('internaciones')}>Volver a Internaciones</Button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<Cultivos
|
<Cultivos
|
||||||
cultivos={store.cultivos.filter(c => c.pacienteId === paciente.id)}
|
cultivos={store.cultivos}
|
||||||
patient={paciente}
|
pacientes={store.pacientes}
|
||||||
internacionId={internacion.id}
|
internaciones={store.internaciones}
|
||||||
|
camas={store.camas}
|
||||||
onAgregarCultivo={store.agregarCultivo}
|
onAgregarCultivo={store.agregarCultivo}
|
||||||
onActualizarCultivo={store.actualizarCultivo}
|
onActualizarCultivo={store.actualizarCultivo}
|
||||||
onEliminarCultivo={store.eliminarCultivo}
|
onEliminarCultivo={store.eliminarCultivo}
|
||||||
canEdit={store.canEditInternacion(internacion.id)}
|
getPacienteById={store.getPacienteById}
|
||||||
|
getInternacionActivaByPaciente={store.getInternacionActivaByPaciente}
|
||||||
|
canEdit={true}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
|
||||||
case 'historiaclinica': {
|
case 'historiaclinica': {
|
||||||
const internacionId = store.currentInternacionId || '';
|
const internacionId = store.currentInternacionId || '';
|
||||||
const internacion = store.getInternacionById(internacionId);
|
const internacion = store.getInternacionById(internacionId);
|
||||||
@@ -169,6 +149,7 @@ function AppContent() {
|
|||||||
allCamas={store.camas}
|
allCamas={store.camas}
|
||||||
evoluciones={store.getEvolucionesByInternacion(internacion.id)}
|
evoluciones={store.getEvolucionesByInternacion(internacion.id)}
|
||||||
laboratorios={store.getLaboratoriosByPaciente(paciente.id)}
|
laboratorios={store.getLaboratoriosByPaciente(paciente.id)}
|
||||||
|
glucemias={store.getGlucemiasByPaciente(paciente.id)}
|
||||||
acidosBase={store.getAcidosBaseByPaciente(paciente.id)}
|
acidosBase={store.getAcidosBaseByPaciente(paciente.id)}
|
||||||
cultivos={store.getCultivosByPaciente(paciente.id)}
|
cultivos={store.getCultivosByPaciente(paciente.id)}
|
||||||
estudiosComplementarios={store.estudiosComplementarios.filter(e => e.pacienteId === paciente.id)}
|
estudiosComplementarios={store.estudiosComplementarios.filter(e => e.pacienteId === paciente.id)}
|
||||||
@@ -180,6 +161,9 @@ function AppContent() {
|
|||||||
onAgregarLaboratorio={(l) => store.agregarLaboratorio({ ...l, internacionId: internacion.id })}
|
onAgregarLaboratorio={(l) => store.agregarLaboratorio({ ...l, internacionId: internacion.id })}
|
||||||
onActualizarLaboratorio={store.actualizarLaboratorio}
|
onActualizarLaboratorio={store.actualizarLaboratorio}
|
||||||
onEliminarLaboratorio={store.eliminarLaboratorio}
|
onEliminarLaboratorio={store.eliminarLaboratorio}
|
||||||
|
onAgregarGlucemia={(g) => store.agregarGlucemia({ ...g, internacionId: internacion.id })}
|
||||||
|
onActualizarGlucemia={store.actualizarGlucemia}
|
||||||
|
onEliminarGlucemia={store.eliminarGlucemia}
|
||||||
onAgregarAcidoBase={(a) => store.agregarAcidoBase({ ...a, internacionId: internacion.id })}
|
onAgregarAcidoBase={(a) => store.agregarAcidoBase({ ...a, internacionId: internacion.id })}
|
||||||
onActualizarAcidoBase={store.actualizarAcidoBase}
|
onActualizarAcidoBase={store.actualizarAcidoBase}
|
||||||
onEliminarAcidoBase={store.eliminarAcidoBase}
|
onEliminarAcidoBase={store.eliminarAcidoBase}
|
||||||
|
|||||||
@@ -42,14 +42,14 @@ function CommandDialog({
|
|||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<Dialog {...props}>
|
<Dialog {...props}>
|
||||||
<DialogHeader className="sr-only">
|
|
||||||
<DialogTitle>{title}</DialogTitle>
|
|
||||||
<DialogDescription>{description}</DialogDescription>
|
|
||||||
</DialogHeader>
|
|
||||||
<DialogContent
|
<DialogContent
|
||||||
className={cn("overflow-hidden p-0", className)}
|
className={cn("overflow-hidden p-0", className)}
|
||||||
showCloseButton={showCloseButton}
|
showCloseButton={showCloseButton}
|
||||||
>
|
>
|
||||||
|
<DialogHeader className="sr-only">
|
||||||
|
<DialogTitle>{title}</DialogTitle>
|
||||||
|
<DialogDescription>{description}</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
<Command className="[&_[cmdk-group-heading]]:text-muted-foreground **:data-[slot=command-input-wrapper]:h-12 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group]]:px-2 [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
|
<Command className="[&_[cmdk-group-heading]]:text-muted-foreground **:data-[slot=command-input-wrapper]:h-12 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group]]:px-2 [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
|
||||||
{children}
|
{children}
|
||||||
</Command>
|
</Command>
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { useEffect, useState } from "react"
|
||||||
import {
|
import {
|
||||||
CircleCheckIcon,
|
CircleCheckIcon,
|
||||||
InfoIcon,
|
InfoIcon,
|
||||||
@@ -10,17 +11,41 @@ import { Toaster as Sonner, type ToasterProps } from "sonner"
|
|||||||
|
|
||||||
const Toaster = ({ ...props }: ToasterProps) => {
|
const Toaster = ({ ...props }: ToasterProps) => {
|
||||||
const { theme = "system" } = useTheme()
|
const { theme = "system" } = useTheme()
|
||||||
|
const [isMobile, setIsMobile] = useState(false)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const checkMobile = () => {
|
||||||
|
setIsMobile(window.innerWidth < 640)
|
||||||
|
}
|
||||||
|
checkMobile()
|
||||||
|
window.addEventListener("resize", checkMobile)
|
||||||
|
return () => window.removeEventListener("resize", checkMobile)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const effectivePosition = isMobile ? "bottom-center" : (props.position || "top-right")
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Sonner
|
<Sonner
|
||||||
theme={theme as ToasterProps["theme"]}
|
theme={theme as ToasterProps["theme"]}
|
||||||
className="toaster group"
|
className="toaster group"
|
||||||
|
position={effectivePosition}
|
||||||
|
toastOptions={{
|
||||||
|
classNames: {
|
||||||
|
toast:
|
||||||
|
"group toast group-[.toaster]:bg-white dark:group-[.toaster]:bg-slate-900 group-[.toaster]:text-slate-900 dark:group-[.toaster]:text-slate-100 group-[.toaster]:border-slate-300 dark:group-[.toaster]:border-slate-700 group-[.toaster]:shadow-2xl opacity-100 bg-white dark:bg-slate-900 text-slate-900 dark:text-slate-100 border border-slate-300 dark:border-slate-700 shadow-2xl font-medium rounded-lg p-4",
|
||||||
|
description: "group-[.toast]:text-slate-600 dark:group-[.toast]:text-slate-400",
|
||||||
|
actionButton:
|
||||||
|
"group-[.toast]:bg-blue-600 group-[.toast]:text-white font-semibold",
|
||||||
|
cancelButton:
|
||||||
|
"group-[.toast]:bg-slate-200 group-[.toast]:text-slate-800 dark:group-[.toast]:bg-slate-800 dark:group-[.toast]:text-slate-200",
|
||||||
|
},
|
||||||
|
}}
|
||||||
icons={{
|
icons={{
|
||||||
success: <CircleCheckIcon className="size-4" />,
|
success: <CircleCheckIcon className="size-5 text-emerald-600 dark:text-emerald-400" />,
|
||||||
info: <InfoIcon className="size-4" />,
|
info: <InfoIcon className="size-5 text-blue-600 dark:text-blue-400" />,
|
||||||
warning: <TriangleAlertIcon className="size-4" />,
|
warning: <TriangleAlertIcon className="size-5 text-amber-600 dark:text-amber-400" />,
|
||||||
error: <OctagonXIcon className="size-4" />,
|
error: <OctagonXIcon className="size-5 text-red-600 dark:text-red-400" />,
|
||||||
loading: <Loader2Icon className="size-4 animate-spin" />,
|
loading: <Loader2Icon className="size-5 animate-spin text-blue-600 dark:text-blue-400" />,
|
||||||
}}
|
}}
|
||||||
style={
|
style={
|
||||||
{
|
{
|
||||||
|
|||||||
+116
-33
@@ -6,6 +6,7 @@ import type {
|
|||||||
Internacion,
|
Internacion,
|
||||||
Evolucion,
|
Evolucion,
|
||||||
Laboratorio,
|
Laboratorio,
|
||||||
|
Glucemia,
|
||||||
AcidoBase,
|
AcidoBase,
|
||||||
Cultivo,
|
Cultivo,
|
||||||
EstudioComplementario,
|
EstudioComplementario,
|
||||||
@@ -37,6 +38,7 @@ interface HospitalState {
|
|||||||
internaciones: Internacion[];
|
internaciones: Internacion[];
|
||||||
evoluciones: Evolucion[];
|
evoluciones: Evolucion[];
|
||||||
laboratorios: Laboratorio[];
|
laboratorios: Laboratorio[];
|
||||||
|
glucemias: Glucemia[];
|
||||||
acidosBase: AcidoBase[];
|
acidosBase: AcidoBase[];
|
||||||
cultivos: Cultivo[];
|
cultivos: Cultivo[];
|
||||||
estudiosComplementarios: EstudioComplementario[];
|
estudiosComplementarios: EstudioComplementario[];
|
||||||
@@ -52,7 +54,7 @@ interface HospitalState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const API_BASE = import.meta.env.VITE_API_URL || 'http://localhost:4000/api';
|
const API_BASE = import.meta.env.VITE_API_URL || '/api';
|
||||||
|
|
||||||
const defaultState = (): HospitalState => ({
|
const defaultState = (): HospitalState => ({
|
||||||
pacientes: [],
|
pacientes: [],
|
||||||
@@ -60,6 +62,7 @@ const defaultState = (): HospitalState => ({
|
|||||||
internaciones: [],
|
internaciones: [],
|
||||||
evoluciones: [],
|
evoluciones: [],
|
||||||
laboratorios: [],
|
laboratorios: [],
|
||||||
|
glucemias: [],
|
||||||
acidosBase: [],
|
acidosBase: [],
|
||||||
cultivos: [],
|
cultivos: [],
|
||||||
estudiosComplementarios: [],
|
estudiosComplementarios: [],
|
||||||
@@ -96,6 +99,7 @@ export function useHospitalStore() {
|
|||||||
estudiosComplementarios: body.estudiosComplementarios || [],
|
estudiosComplementarios: body.estudiosComplementarios || [],
|
||||||
interconsultas: body.interconsultas || [],
|
interconsultas: body.interconsultas || [],
|
||||||
atb: body.atb || [],
|
atb: body.atb || [],
|
||||||
|
glucemias: body.glucemias || [],
|
||||||
movimientosIndicaciones: body.movimientosIndicaciones || [],
|
movimientosIndicaciones: body.movimientosIndicaciones || [],
|
||||||
currentUser,
|
currentUser,
|
||||||
isAuthenticated: !!currentUser,
|
isAuthenticated: !!currentUser,
|
||||||
@@ -140,8 +144,9 @@ export function useHospitalStore() {
|
|||||||
|
|
||||||
const getInternacionAreaId = useCallback((internacionId: string): string | null => {
|
const getInternacionAreaId = useCallback((internacionId: string): string | null => {
|
||||||
const internacion = state.internaciones.find(i => i.id === internacionId);
|
const internacion = state.internaciones.find(i => i.id === internacionId);
|
||||||
return internacion?.areaId || null;
|
if (!internacion) return null;
|
||||||
}, [state.internaciones]);
|
return internacion.areaId || getCamaAreaId(internacion.camaId) || null;
|
||||||
|
}, [state.internaciones, getCamaAreaId]);
|
||||||
|
|
||||||
const getPacienteAreaId = useCallback((pacienteId: string): string | null => {
|
const getPacienteAreaId = useCallback((pacienteId: string): string | null => {
|
||||||
const internacion = state.internaciones.find(i => i.pacienteId === pacienteId && i.activa);
|
const internacion = state.internaciones.find(i => i.pacienteId === pacienteId && i.activa);
|
||||||
@@ -150,7 +155,7 @@ export function useHospitalStore() {
|
|||||||
if (anyInternacion) return getCamaAreaId(anyInternacion.camaId);
|
if (anyInternacion) return getCamaAreaId(anyInternacion.camaId);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return getCamaAreaId(internacion.camaId);
|
return internacion.areaId || getCamaAreaId(internacion.camaId) || null;
|
||||||
}, [state.internaciones, getCamaAreaId]);
|
}, [state.internaciones, getCamaAreaId]);
|
||||||
|
|
||||||
// Permission functions
|
// Permission functions
|
||||||
@@ -158,7 +163,8 @@ export function useHospitalStore() {
|
|||||||
const user = state.currentUser;
|
const user = state.currentUser;
|
||||||
if (!user) return false;
|
if (!user) return false;
|
||||||
if (user.rol === 'admin') return true;
|
if (user.rol === 'admin') return true;
|
||||||
if (!areaId) return false;
|
if (!areaId) return true;
|
||||||
|
if (!user.areaId) return true;
|
||||||
return user.areaId === areaId;
|
return user.areaId === areaId;
|
||||||
}, [state.currentUser]);
|
}, [state.currentUser]);
|
||||||
|
|
||||||
@@ -269,7 +275,7 @@ export function useHospitalStore() {
|
|||||||
console.error('Error al actualizar cama:', err);
|
console.error('Error al actualizar cama:', err);
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
}, [state.camas, state.currentUser, apiCall]);
|
}, [state, canAccessArea, apiCall]);
|
||||||
|
|
||||||
const agregarCama = useCallback(async (cama: Omit<Cama, 'id'>) => {
|
const agregarCama = useCallback(async (cama: Omit<Cama, 'id'>) => {
|
||||||
const nuevaCama: Cama = {
|
const nuevaCama: Cama = {
|
||||||
@@ -390,7 +396,7 @@ const finalizarInternacion = useCallback(async (internacionId: string, datos: {
|
|||||||
console.error('Error al finalizar internación:', err);
|
console.error('Error al finalizar internación:', err);
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
}, [state.areas, apiCall]);
|
}, [state, apiCall]);
|
||||||
|
|
||||||
const actualizarInternacion = useCallback(async (internacionId: string, datos: Partial<Internacion>) => {
|
const actualizarInternacion = useCallback(async (internacionId: string, datos: Partial<Internacion>) => {
|
||||||
const internacion = state.internaciones.find(i => i.id === internacionId);
|
const internacion = state.internaciones.find(i => i.id === internacionId);
|
||||||
@@ -449,7 +455,7 @@ const actualizarInternacion = useCallback(async (internacionId: string, datos: P
|
|||||||
console.error('Error al actualizar internación:', err);
|
console.error('Error al actualizar internación:', err);
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
}, [state.internaciones, state.currentUser, apiCall]);
|
}, [state, canAccessArea, apiCall]);
|
||||||
|
|
||||||
// Acciones de evoluciones
|
// Acciones de evoluciones
|
||||||
const agregarEvolucion = useCallback(async (evolucion: Omit<Evolucion, 'id'>) => {
|
const agregarEvolucion = useCallback(async (evolucion: Omit<Evolucion, 'id'>) => {
|
||||||
@@ -475,7 +481,7 @@ const actualizarInternacion = useCallback(async (internacionId: string, datos: P
|
|||||||
console.error('Error al agregar evolución:', err);
|
console.error('Error al agregar evolución:', err);
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
}, [state.internaciones, state.currentUser, apiCall]);
|
}, [state, canAccessArea, apiCall]);
|
||||||
|
|
||||||
const eliminarEvolucion = useCallback(async (id: string) => {
|
const eliminarEvolucion = useCallback(async (id: string) => {
|
||||||
const evolucion = state.evoluciones.find(e => e.id === id);
|
const evolucion = state.evoluciones.find(e => e.id === id);
|
||||||
@@ -497,7 +503,7 @@ const actualizarInternacion = useCallback(async (internacionId: string, datos: P
|
|||||||
console.error('Error al eliminar evolución:', err);
|
console.error('Error al eliminar evolución:', err);
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
}, [state.internaciones, state.currentUser, apiCall]);
|
}, [state, canAccessArea, apiCall]);
|
||||||
|
|
||||||
const actualizarEvolucion = useCallback(async (id: string, datos: Partial<Evolucion>) => {
|
const actualizarEvolucion = useCallback(async (id: string, datos: Partial<Evolucion>) => {
|
||||||
const evolucion = state.evoluciones.find(e => e.id === id);
|
const evolucion = state.evoluciones.find(e => e.id === id);
|
||||||
@@ -519,7 +525,7 @@ const actualizarInternacion = useCallback(async (internacionId: string, datos: P
|
|||||||
console.error('Error al actualizar evolución:', err);
|
console.error('Error al actualizar evolución:', err);
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
}, [state.internaciones, state.currentUser, apiCall]);
|
}, [state, canAccessArea, apiCall]);
|
||||||
|
|
||||||
// Acciones de laboratorios
|
// Acciones de laboratorios
|
||||||
const agregarLaboratorio = useCallback(async (laboratorio: Omit<Laboratorio, 'id'>) => {
|
const agregarLaboratorio = useCallback(async (laboratorio: Omit<Laboratorio, 'id'>) => {
|
||||||
@@ -544,7 +550,7 @@ const actualizarInternacion = useCallback(async (internacionId: string, datos: P
|
|||||||
console.error('Error al agregar laboratorio:', err);
|
console.error('Error al agregar laboratorio:', err);
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
}, [state.internaciones, state.currentUser, apiCall]);
|
}, [state, canAccessArea, apiCall]);
|
||||||
|
|
||||||
const eliminarLaboratorio = useCallback(async (id: string) => {
|
const eliminarLaboratorio = useCallback(async (id: string) => {
|
||||||
const laboratorio = state.laboratorios.find(l => l.id === id);
|
const laboratorio = state.laboratorios.find(l => l.id === id);
|
||||||
@@ -565,7 +571,7 @@ const actualizarInternacion = useCallback(async (internacionId: string, datos: P
|
|||||||
console.error('Error al eliminar laboratorio:', err);
|
console.error('Error al eliminar laboratorio:', err);
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
}, [state.internaciones, state.currentUser, apiCall]);
|
}, [state, canAccessArea, apiCall]);
|
||||||
|
|
||||||
const actualizarLaboratorio = useCallback(async (id: string, datos: Partial<Laboratorio>) => {
|
const actualizarLaboratorio = useCallback(async (id: string, datos: Partial<Laboratorio>) => {
|
||||||
const laboratorio = state.laboratorios.find(l => l.id === id);
|
const laboratorio = state.laboratorios.find(l => l.id === id);
|
||||||
@@ -586,7 +592,74 @@ const actualizarInternacion = useCallback(async (internacionId: string, datos: P
|
|||||||
console.error('Error al actualizar laboratorio:', err);
|
console.error('Error al actualizar laboratorio:', err);
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
}, [state.internaciones, state.currentUser, apiCall]);
|
}, [state, canAccessArea, apiCall]);
|
||||||
|
|
||||||
|
// Acciones de glucemias
|
||||||
|
const agregarGlucemia = useCallback(async (glucemia: Omit<Glucemia, 'id'>) => {
|
||||||
|
const internacion = state.internaciones.find(i => i.id === glucemia.internacionId);
|
||||||
|
const areaId = internacion?.areaId || null;
|
||||||
|
if (!canAccessArea(areaId)) {
|
||||||
|
console.warn('No tiene permisos para agregar glucemia en esta área');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const nuevaGlucemia: Glucemia = {
|
||||||
|
...glucemia,
|
||||||
|
id: generateUUID(),
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
await apiCall('POST', '/glucemias', nuevaGlucemia);
|
||||||
|
setState(prev => ({
|
||||||
|
...prev,
|
||||||
|
glucemias: [...prev.glucemias, nuevaGlucemia],
|
||||||
|
}));
|
||||||
|
return nuevaGlucemia.id;
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error al agregar glucemia:', err);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}, [state, canAccessArea, apiCall]);
|
||||||
|
|
||||||
|
const eliminarGlucemia = useCallback(async (id: string) => {
|
||||||
|
const glucemia = state.glucemias.find(g => g.id === id);
|
||||||
|
if (!glucemia) return;
|
||||||
|
const internacion = state.internaciones.find(i => i.id === glucemia.internacionId);
|
||||||
|
const areaId = internacion?.areaId || null;
|
||||||
|
if (!canAccessArea(areaId)) {
|
||||||
|
console.warn('No tiene permisos para eliminar glucemia en esta área');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await apiCall('DELETE', `/glucemias/${id}`);
|
||||||
|
setState(prev => ({
|
||||||
|
...prev,
|
||||||
|
glucemias: prev.glucemias.filter(g => g.id !== id),
|
||||||
|
}));
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error al eliminar glucemia:', err);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}, [state, canAccessArea, apiCall]);
|
||||||
|
|
||||||
|
const actualizarGlucemia = useCallback(async (id: string, datos: Partial<Glucemia>) => {
|
||||||
|
const glucemia = state.glucemias.find(g => g.id === id);
|
||||||
|
if (!glucemia) return;
|
||||||
|
const internacion = state.internaciones.find(i => i.id === glucemia.internacionId);
|
||||||
|
const areaId = internacion?.areaId || null;
|
||||||
|
if (!canAccessArea(areaId)) {
|
||||||
|
console.warn('No tiene permisos para actualizar glucemia en esta área');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await apiCall('PUT', `/glucemias/${id}`, datos);
|
||||||
|
setState(prev => ({
|
||||||
|
...prev,
|
||||||
|
glucemias: prev.glucemias.map(g => g.id === id ? { ...g, ...datos } : g),
|
||||||
|
}));
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error al actualizar glucemia:', err);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}, [state, canAccessArea, apiCall]);
|
||||||
|
|
||||||
// Acciones de ácido-base
|
// Acciones de ácido-base
|
||||||
const agregarAcidoBase = useCallback(async (acidoBase: Omit<AcidoBase, 'id'>) => {
|
const agregarAcidoBase = useCallback(async (acidoBase: Omit<AcidoBase, 'id'>) => {
|
||||||
@@ -611,7 +684,7 @@ const actualizarInternacion = useCallback(async (internacionId: string, datos: P
|
|||||||
console.error('Error al agregar ácido-base:', err);
|
console.error('Error al agregar ácido-base:', err);
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
}, [state.internaciones, state.currentUser, apiCall]);
|
}, [state, canAccessArea, apiCall]);
|
||||||
|
|
||||||
const actualizarAcidoBase = useCallback(async (id: string, datos: Partial<AcidoBase>) => {
|
const actualizarAcidoBase = useCallback(async (id: string, datos: Partial<AcidoBase>) => {
|
||||||
const acidoBase = state.acidosBase.find(a => a.id === id);
|
const acidoBase = state.acidosBase.find(a => a.id === id);
|
||||||
@@ -632,7 +705,7 @@ const actualizarInternacion = useCallback(async (internacionId: string, datos: P
|
|||||||
console.error('Error al actualizar ácido-base:', err);
|
console.error('Error al actualizar ácido-base:', err);
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
}, [state.internaciones, state.currentUser, apiCall]);
|
}, [state, canAccessArea, apiCall]);
|
||||||
|
|
||||||
const eliminarAcidoBase = useCallback(async (id: string) => {
|
const eliminarAcidoBase = useCallback(async (id: string) => {
|
||||||
const acidoBase = state.acidosBase.find(a => a.id === id);
|
const acidoBase = state.acidosBase.find(a => a.id === id);
|
||||||
@@ -653,7 +726,7 @@ const actualizarInternacion = useCallback(async (internacionId: string, datos: P
|
|||||||
console.error('Error al eliminar ácido-base:', err);
|
console.error('Error al eliminar ácido-base:', err);
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
}, [state.internaciones, state.currentUser, apiCall]);
|
}, [state, canAccessArea, apiCall]);
|
||||||
|
|
||||||
// Acciones de cultivos
|
// Acciones de cultivos
|
||||||
const agregarCultivo = useCallback(async (cultivo: Omit<Cultivo, 'id'>) => {
|
const agregarCultivo = useCallback(async (cultivo: Omit<Cultivo, 'id'>) => {
|
||||||
@@ -678,7 +751,7 @@ const actualizarInternacion = useCallback(async (internacionId: string, datos: P
|
|||||||
console.error('Error al agregar cultivo:', err);
|
console.error('Error al agregar cultivo:', err);
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
}, [state.internaciones, state.currentUser, apiCall]);
|
}, [state, canAccessArea, apiCall]);
|
||||||
|
|
||||||
const actualizarCultivo = useCallback(async (id: string, datos: Partial<Cultivo>) => {
|
const actualizarCultivo = useCallback(async (id: string, datos: Partial<Cultivo>) => {
|
||||||
const cultivo = state.cultivos.find(c => c.id === id);
|
const cultivo = state.cultivos.find(c => c.id === id);
|
||||||
@@ -699,7 +772,7 @@ const actualizarInternacion = useCallback(async (internacionId: string, datos: P
|
|||||||
console.error('Error al actualizar cultivo:', err);
|
console.error('Error al actualizar cultivo:', err);
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
}, [state.internaciones, state.currentUser, apiCall]);
|
}, [state, canAccessArea, apiCall]);
|
||||||
|
|
||||||
const eliminarCultivo = useCallback(async (id: string) => {
|
const eliminarCultivo = useCallback(async (id: string) => {
|
||||||
const cultivo = state.cultivos.find(c => c.id === id);
|
const cultivo = state.cultivos.find(c => c.id === id);
|
||||||
@@ -720,7 +793,7 @@ const actualizarInternacion = useCallback(async (internacionId: string, datos: P
|
|||||||
console.error('Error al eliminar cultivo:', err);
|
console.error('Error al eliminar cultivo:', err);
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
}, [state.internaciones, state.currentUser, apiCall]);
|
}, [state, canAccessArea, apiCall]);
|
||||||
|
|
||||||
// Acciones de estudios complementarios
|
// Acciones de estudios complementarios
|
||||||
const agregarEstudioComplementario = useCallback(async (estudio: Omit<EstudioComplementario, 'id'>) => {
|
const agregarEstudioComplementario = useCallback(async (estudio: Omit<EstudioComplementario, 'id'>) => {
|
||||||
@@ -745,7 +818,7 @@ const actualizarInternacion = useCallback(async (internacionId: string, datos: P
|
|||||||
console.error('Error al agregar estudio:', err);
|
console.error('Error al agregar estudio:', err);
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
}, [state.internaciones, state.currentUser, apiCall]);
|
}, [state, canAccessArea, apiCall]);
|
||||||
|
|
||||||
const actualizarEstudioComplementario = useCallback(async (id: string, datos: Partial<EstudioComplementario>) => {
|
const actualizarEstudioComplementario = useCallback(async (id: string, datos: Partial<EstudioComplementario>) => {
|
||||||
const estudio = state.estudiosComplementarios.find(e => e.id === id);
|
const estudio = state.estudiosComplementarios.find(e => e.id === id);
|
||||||
@@ -766,7 +839,7 @@ const actualizarInternacion = useCallback(async (internacionId: string, datos: P
|
|||||||
console.error('Error al actualizar estudio:', err);
|
console.error('Error al actualizar estudio:', err);
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
}, [state.internaciones, state.currentUser, apiCall]);
|
}, [state, canAccessArea, apiCall]);
|
||||||
|
|
||||||
const eliminarEstudioComplementario = useCallback(async (id: string) => {
|
const eliminarEstudioComplementario = useCallback(async (id: string) => {
|
||||||
const estudio = state.estudiosComplementarios.find(e => e.id === id);
|
const estudio = state.estudiosComplementarios.find(e => e.id === id);
|
||||||
@@ -787,7 +860,7 @@ const actualizarInternacion = useCallback(async (internacionId: string, datos: P
|
|||||||
console.error('Error al eliminar estudio:', err);
|
console.error('Error al eliminar estudio:', err);
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
}, [state.internaciones, state.currentUser, apiCall]);
|
}, [state, canAccessArea, apiCall]);
|
||||||
|
|
||||||
// Acciones de interconsultas
|
// Acciones de interconsultas
|
||||||
const agregarInterconsulta = useCallback(async (interconsulta: Omit<Interconsulta, 'id'>) => {
|
const agregarInterconsulta = useCallback(async (interconsulta: Omit<Interconsulta, 'id'>) => {
|
||||||
@@ -809,7 +882,7 @@ const actualizarInternacion = useCallback(async (internacionId: string, datos: P
|
|||||||
console.error('Error al agregar interconsulta:', err);
|
console.error('Error al agregar interconsulta:', err);
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
}, [state.internaciones, state.currentUser, apiCall]);
|
}, [state, canAccessArea, apiCall]);
|
||||||
|
|
||||||
const actualizarInterconsulta = useCallback(async (id: string, datos: Partial<Interconsulta>) => {
|
const actualizarInterconsulta = useCallback(async (id: string, datos: Partial<Interconsulta>) => {
|
||||||
const interconsulta = (state.interconsultas || []).find(ic => ic.id === id);
|
const interconsulta = (state.interconsultas || []).find(ic => ic.id === id);
|
||||||
@@ -830,7 +903,7 @@ const actualizarInternacion = useCallback(async (internacionId: string, datos: P
|
|||||||
console.error('Error al actualizar interconsulta:', err);
|
console.error('Error al actualizar interconsulta:', err);
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
}, [state.internaciones, state.currentUser, apiCall]);
|
}, [state, canAccessArea, apiCall]);
|
||||||
|
|
||||||
const eliminarInterconsulta = useCallback(async (id: string) => {
|
const eliminarInterconsulta = useCallback(async (id: string) => {
|
||||||
const interconsulta = (state.interconsultas || []).find(ic => ic.id === id);
|
const interconsulta = (state.interconsultas || []).find(ic => ic.id === id);
|
||||||
@@ -851,7 +924,7 @@ const actualizarInternacion = useCallback(async (internacionId: string, datos: P
|
|||||||
console.error('Error al eliminar interconsulta:', err);
|
console.error('Error al eliminar interconsulta:', err);
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
}, [state.internaciones, state.currentUser, apiCall]);
|
}, [state, canAccessArea, apiCall]);
|
||||||
|
|
||||||
const agregarATB = useCallback(async (atb: Omit<ATB, 'id'>) => {
|
const agregarATB = useCallback(async (atb: Omit<ATB, 'id'>) => {
|
||||||
const internacion = state.internaciones.find(i => i.id === atb.internacionId);
|
const internacion = state.internaciones.find(i => i.id === atb.internacionId);
|
||||||
@@ -872,7 +945,7 @@ const actualizarInternacion = useCallback(async (internacionId: string, datos: P
|
|||||||
console.error('Error al agregar ATB:', err);
|
console.error('Error al agregar ATB:', err);
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
}, [state.internaciones, state.currentUser, apiCall]);
|
}, [state, canAccessArea, apiCall]);
|
||||||
|
|
||||||
const actualizarATB = useCallback(async (id: string, datos: Partial<ATB>) => {
|
const actualizarATB = useCallback(async (id: string, datos: Partial<ATB>) => {
|
||||||
const atb = state.atb.find(a => a.id === id);
|
const atb = state.atb.find(a => a.id === id);
|
||||||
@@ -893,7 +966,7 @@ const actualizarInternacion = useCallback(async (internacionId: string, datos: P
|
|||||||
console.error('Error al actualizar ATB:', err);
|
console.error('Error al actualizar ATB:', err);
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
}, [state.internaciones, state.currentUser, apiCall]);
|
}, [state, canAccessArea, apiCall]);
|
||||||
|
|
||||||
const eliminarATB = useCallback(async (id: string) => {
|
const eliminarATB = useCallback(async (id: string) => {
|
||||||
const atb = state.atb.find(a => a.id === id);
|
const atb = state.atb.find(a => a.id === id);
|
||||||
@@ -914,7 +987,7 @@ const actualizarInternacion = useCallback(async (internacionId: string, datos: P
|
|||||||
console.error('Error al eliminar ATB:', err);
|
console.error('Error al eliminar ATB:', err);
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
}, [state.internaciones, state.currentUser, apiCall]);
|
}, [state, canAccessArea, apiCall]);
|
||||||
|
|
||||||
const agregarIndicacion = useCallback(async (indicacion: Omit<Indicacion, 'id'>) => {
|
const agregarIndicacion = useCallback(async (indicacion: Omit<Indicacion, 'id'>) => {
|
||||||
const internacion = state.internaciones.find(i => i.id === indicacion.internacionId);
|
const internacion = state.internaciones.find(i => i.id === indicacion.internacionId);
|
||||||
@@ -935,7 +1008,7 @@ const actualizarInternacion = useCallback(async (internacionId: string, datos: P
|
|||||||
console.error('Error al agregar indicación:', err);
|
console.error('Error al agregar indicación:', err);
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
}, [state.internaciones, state.currentUser, apiCall]);
|
}, [state.internaciones, canAccessArea, apiCall]);
|
||||||
|
|
||||||
const actualizarIndicacion = useCallback(async (id: string, datos: Partial<Indicacion>) => {
|
const actualizarIndicacion = useCallback(async (id: string, datos: Partial<Indicacion>) => {
|
||||||
const indicacion = state.indicaciones.find(i => i.id === id);
|
const indicacion = state.indicaciones.find(i => i.id === id);
|
||||||
@@ -956,7 +1029,7 @@ const actualizarInternacion = useCallback(async (internacionId: string, datos: P
|
|||||||
console.error('Error al actualizar indicación:', err);
|
console.error('Error al actualizar indicación:', err);
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
}, [state.internaciones, state.currentUser, apiCall]);
|
}, [state.indicaciones, state.internaciones, canAccessArea, apiCall]);
|
||||||
|
|
||||||
const eliminarIndicacion = useCallback(async (id: string) => {
|
const eliminarIndicacion = useCallback(async (id: string) => {
|
||||||
const indicacion = state.indicaciones.find(i => i.id === id);
|
const indicacion = state.indicaciones.find(i => i.id === id);
|
||||||
@@ -977,7 +1050,7 @@ const actualizarInternacion = useCallback(async (internacionId: string, datos: P
|
|||||||
console.error('Error al eliminar indicación:', err);
|
console.error('Error al eliminar indicación:', err);
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
}, [state.internaciones, state.currentUser, apiCall]);
|
}, [state.indicaciones, state.internaciones, canAccessArea, apiCall]);
|
||||||
|
|
||||||
const agregarMovimientoIndicacion = useCallback(async (movimiento: any) => {
|
const agregarMovimientoIndicacion = useCallback(async (movimiento: any) => {
|
||||||
const internacion = state.internaciones.find(i => i.id === movimiento.internacionId);
|
const internacion = state.internaciones.find(i => i.id === movimiento.internacionId);
|
||||||
@@ -997,7 +1070,7 @@ const actualizarInternacion = useCallback(async (internacionId: string, datos: P
|
|||||||
console.error('Error al agregar movimiento:', err);
|
console.error('Error al agregar movimiento:', err);
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
}, [state.internaciones, state.currentUser, apiCall]);
|
}, [state, canAccessArea, apiCall]);
|
||||||
|
|
||||||
const getMovimientosByInternacion = useCallback((internacionId: string) => {
|
const getMovimientosByInternacion = useCallback((internacionId: string) => {
|
||||||
return (state.movimientosIndicaciones || [])
|
return (state.movimientosIndicaciones || [])
|
||||||
@@ -1077,6 +1150,12 @@ const actualizarInternacion = useCallback(async (internacionId: string, datos: P
|
|||||||
.sort((a, b) => new Date(b.fecha).getTime() - new Date(a.fecha).getTime());
|
.sort((a, b) => new Date(b.fecha).getTime() - new Date(a.fecha).getTime());
|
||||||
}, [state.laboratorios]);
|
}, [state.laboratorios]);
|
||||||
|
|
||||||
|
const getGlucemiasByPaciente = useCallback((pacienteId: string) => {
|
||||||
|
return state.glucemias
|
||||||
|
.filter(g => g.pacienteId === pacienteId)
|
||||||
|
.sort((a, b) => new Date(b.fecha + 'T' + (b.hora || '00:00')).getTime() - new Date(a.fecha + 'T' + (a.hora || '00:00')).getTime());
|
||||||
|
}, [state.glucemias]);
|
||||||
|
|
||||||
const getAcidosBaseByPaciente = useCallback((pacienteId: string) => {
|
const getAcidosBaseByPaciente = useCallback((pacienteId: string) => {
|
||||||
return state.acidosBase
|
return state.acidosBase
|
||||||
.filter(a => a.pacienteId === pacienteId)
|
.filter(a => a.pacienteId === pacienteId)
|
||||||
@@ -1194,6 +1273,9 @@ const actualizarInternacion = useCallback(async (internacionId: string, datos: P
|
|||||||
agregarLaboratorio,
|
agregarLaboratorio,
|
||||||
actualizarLaboratorio,
|
actualizarLaboratorio,
|
||||||
eliminarLaboratorio,
|
eliminarLaboratorio,
|
||||||
|
agregarGlucemia,
|
||||||
|
actualizarGlucemia,
|
||||||
|
eliminarGlucemia,
|
||||||
agregarAcidoBase,
|
agregarAcidoBase,
|
||||||
actualizarAcidoBase,
|
actualizarAcidoBase,
|
||||||
eliminarAcidoBase,
|
eliminarAcidoBase,
|
||||||
@@ -1223,6 +1305,7 @@ const actualizarInternacion = useCallback(async (internacionId: string, datos: P
|
|||||||
getInternacionActivaByPaciente,
|
getInternacionActivaByPaciente,
|
||||||
getEvolucionesByInternacion,
|
getEvolucionesByInternacion,
|
||||||
getLaboratoriosByPaciente,
|
getLaboratoriosByPaciente,
|
||||||
|
getGlucemiasByPaciente,
|
||||||
getAcidosBaseByPaciente,
|
getAcidosBaseByPaciente,
|
||||||
getCultivosByPaciente,
|
getCultivosByPaciente,
|
||||||
setCurrentInternacion,
|
setCurrentInternacion,
|
||||||
|
|||||||
+44
-11
@@ -35,13 +35,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.dark {
|
.dark {
|
||||||
--background: 222.2 84% 4.9%;
|
--background: 224 71% 4%;
|
||||||
--foreground: 210 40% 98%;
|
--foreground: 210 40% 98%;
|
||||||
--card: 222.2 84% 4.9%;
|
--card: 222 47% 10%;
|
||||||
--card-foreground: 210 40% 98%;
|
--card-foreground: 210 40% 98%;
|
||||||
--popover: 222.2 84% 4.9%;
|
--popover: 222 47% 10%;
|
||||||
--popover-foreground: 210 40% 98%;
|
--popover-foreground: 210 40% 98%;
|
||||||
--primary: 210 40% 98%;
|
--primary: 217.2 91.2% 59.8%;
|
||||||
--primary-foreground: 222.2 47.4% 11.2%;
|
--primary-foreground: 222.2 47.4% 11.2%;
|
||||||
--secondary: 217.2 32.6% 17.5%;
|
--secondary: 217.2 32.6% 17.5%;
|
||||||
--secondary-foreground: 210 40% 98%;
|
--secondary-foreground: 210 40% 98%;
|
||||||
@@ -51,16 +51,16 @@
|
|||||||
--accent-foreground: 210 40% 98%;
|
--accent-foreground: 210 40% 98%;
|
||||||
--destructive: 0 62.8% 30.6%;
|
--destructive: 0 62.8% 30.6%;
|
||||||
--destructive-foreground: 210 40% 98%;
|
--destructive-foreground: 210 40% 98%;
|
||||||
--border: 217.2 32.6% 17.5%;
|
--border: 217.2 32.6% 20%;
|
||||||
--input: 217.2 32.6% 17.5%;
|
--input: 217.2 32.6% 20%;
|
||||||
--ring: 212.7 26.8% 83.9%;
|
--ring: 217.2 91.2% 59.8%;
|
||||||
--sidebar-background: 222.2 84% 4.9%;
|
--sidebar-background: 224 71% 4%;
|
||||||
--sidebar-foreground: 210 40% 98%;
|
--sidebar-foreground: 210 40% 98%;
|
||||||
--sidebar-primary: 210 40% 98%;
|
--sidebar-primary: 217.2 91.2% 59.8%;
|
||||||
--sidebar-primary-foreground: 222.2 47.4% 11.2%;
|
--sidebar-primary-foreground: 222.2 47.4% 11.2%;
|
||||||
--sidebar-accent: 217.2 32.6% 17.5%;
|
--sidebar-accent: 217.2 32.6% 17.5%;
|
||||||
--sidebar-accent-foreground: 210 40% 98%;
|
--sidebar-accent-foreground: 210 40% 98%;
|
||||||
--sidebar-border: 217.2 32.6% 17.5%;
|
--sidebar-border: 217.2 32.6% 20%;
|
||||||
--sidebar-ring: 217.2 91.2% 59.8%;
|
--sidebar-ring: 217.2 91.2% 59.8%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -70,6 +70,39 @@
|
|||||||
@apply border-border;
|
@apply border-border;
|
||||||
}
|
}
|
||||||
body {
|
body {
|
||||||
@apply bg-background text-foreground;
|
@apply bg-background text-foreground transition-colors duration-200;
|
||||||
|
}
|
||||||
|
textarea, input, select {
|
||||||
|
@apply bg-background text-foreground border-input placeholder:text-muted-foreground;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Toast styling for mobile margin and 100% opacity */
|
||||||
|
[data-sonner-toaster] {
|
||||||
|
z-index: 99999 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-sonner-toast] {
|
||||||
|
background-color: #ffffff !important;
|
||||||
|
color: #0f172a !important;
|
||||||
|
opacity: 1 !important;
|
||||||
|
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.25), 0 8px 10px -6px rgba(0, 0, 0, 0.25) !important;
|
||||||
|
border: 1px solid #cbd5e1 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark [data-sonner-toast] {
|
||||||
|
background-color: #0f172a !important;
|
||||||
|
color: #f8fafc !important;
|
||||||
|
border-color: #334155 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 639px) {
|
||||||
|
[data-sonner-toaster] {
|
||||||
|
bottom: 20px !important;
|
||||||
|
top: auto !important;
|
||||||
|
left: 50% !important;
|
||||||
|
transform: translateX(-50%) !important;
|
||||||
|
width: calc(100% - 32px) !important;
|
||||||
|
max-width: 420px !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+119
-48
@@ -1,31 +1,41 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { Microscope, Plus, Search, Calendar, AlertCircle, Trash2, CheckCircle2, Save, X, Pencil } from 'lucide-react';
|
import { Microscope, Plus, Search, Calendar, AlertCircle, Trash2, CheckCircle2, Pencil } from 'lucide-react';
|
||||||
import { Card, CardContent } from '@/components/ui/card';
|
import { Card, CardContent } from '@/components/ui/card';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { Label } from '@/components/ui/label';
|
import { Label } from '@/components/ui/label';
|
||||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog';
|
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||||
import type { Cultivo, Paciente, Cama, Internacion } from '@/types';
|
import type { Cultivo, Paciente, Cama, Internacion } from '@/types';
|
||||||
|
|
||||||
interface CultivosProps {
|
interface CultivosProps {
|
||||||
cultivos: Cultivo[];
|
cultivos: Cultivo[];
|
||||||
|
pacientes?: Paciente[];
|
||||||
|
internaciones?: Internacion[];
|
||||||
|
camas?: Cama[];
|
||||||
patient?: Paciente;
|
patient?: Paciente;
|
||||||
internacionId: string;
|
internacionId?: string;
|
||||||
onAgregarCultivo: (cultivo: Omit<Cultivo, 'id'>) => void;
|
onAgregarCultivo: (cultivo: Omit<Cultivo, 'id'>) => void;
|
||||||
onActualizarCultivo?: (id: string, datos: Partial<Cultivo>) => void;
|
onActualizarCultivo?: (id: string, datos: Partial<Cultivo>) => void;
|
||||||
onEliminarCultivo: (id: string) => void;
|
onEliminarCultivo: (id: string) => void;
|
||||||
|
getPacienteById?: (id: string) => Paciente | undefined;
|
||||||
|
getInternacionActivaByPaciente?: (pacienteId: string) => Internacion | undefined;
|
||||||
canEdit: boolean;
|
canEdit: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Cultivos({
|
export function Cultivos({
|
||||||
cultivos,
|
cultivos,
|
||||||
|
pacientes = [],
|
||||||
|
internaciones = [],
|
||||||
|
camas = [],
|
||||||
patient,
|
patient,
|
||||||
internacionId,
|
internacionId,
|
||||||
onAgregarCultivo,
|
onAgregarCultivo,
|
||||||
onActualizarCultivo,
|
onActualizarCultivo,
|
||||||
onEliminarCultivo,
|
onEliminarCultivo,
|
||||||
|
getPacienteById,
|
||||||
|
getInternacionActivaByPaciente,
|
||||||
canEdit,
|
canEdit,
|
||||||
}: CultivosProps) {
|
}: CultivosProps) {
|
||||||
const [busqueda, setBusqueda] = useState('');
|
const [busqueda, setBusqueda] = useState('');
|
||||||
@@ -35,6 +45,8 @@ export function Cultivos({
|
|||||||
const [dialogoDefinitivoAbierto, setDialogoDefinitivoAbierto] = useState(false);
|
const [dialogoDefinitivoAbierto, setDialogoDefinitivoAbierto] = useState(false);
|
||||||
const [cultivoSeleccionado, setCultivoSeleccionado] = useState<Cultivo | null>(null);
|
const [cultivoSeleccionado, setCultivoSeleccionado] = useState<Cultivo | null>(null);
|
||||||
|
|
||||||
|
const [pacienteSeleccionado, setPacienteSeleccionado] = useState<string>(patient?.id || '');
|
||||||
|
const [busquedaPaciente, setBusquedaPaciente] = useState('');
|
||||||
const [fechaToma, setFechaToma] = useState(new Date().toISOString().split('T')[0]);
|
const [fechaToma, setFechaToma] = useState(new Date().toISOString().split('T')[0]);
|
||||||
const [protocolo, setProtocolo] = useState('');
|
const [protocolo, setProtocolo] = useState('');
|
||||||
const [tipoMuestra, setTipoMuestra] = useState<Cultivo['tipoMuestra']>('HMCx2');
|
const [tipoMuestra, setTipoMuestra] = useState<Cultivo['tipoMuestra']>('HMCx2');
|
||||||
@@ -46,14 +58,30 @@ export function Cultivos({
|
|||||||
const [sensible, setSensible] = useState('');
|
const [sensible, setSensible] = useState('');
|
||||||
const [resistente, setResistente] = useState('');
|
const [resistente, setResistente] = useState('');
|
||||||
|
|
||||||
|
const findPaciente = (pacienteId: string): Paciente | undefined => {
|
||||||
|
if (patient && patient.id === pacienteId) return patient;
|
||||||
|
if (getPacienteById) return getPacienteById(pacienteId);
|
||||||
|
return pacientes.find(p => p.id === pacienteId);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getCamaNombreForPaciente = (pacienteId: string) => {
|
||||||
|
const inter = internaciones.find(i => i.pacienteId === pacienteId && i.activa);
|
||||||
|
if (!inter) return '';
|
||||||
|
const cama = camas.find(c => c.id === inter.camaId);
|
||||||
|
return cama ? `Cama ${cama.numero}` : '';
|
||||||
|
};
|
||||||
|
|
||||||
const cultivosFiltrados = cultivos.filter(c => {
|
const cultivosFiltrados = cultivos.filter(c => {
|
||||||
if (filtroEstado !== 'todos' && c.estado !== filtroEstado) return false;
|
if (filtroEstado !== 'todos' && c.estado !== filtroEstado) return false;
|
||||||
if (busqueda) {
|
if (busqueda) {
|
||||||
const term = busqueda.toLowerCase();
|
const term = busqueda.toLowerCase();
|
||||||
|
const pac = findPaciente(c.pacienteId);
|
||||||
|
const nombrePac = pac ? `${pac.apellido} ${pac.nombre} ${pac.dni}`.toLowerCase() : '';
|
||||||
return (
|
return (
|
||||||
c.protocolo?.toLowerCase().includes(term) ||
|
c.protocolo?.toLowerCase().includes(term) ||
|
||||||
c.germen?.toLowerCase().includes(term) ||
|
c.germen?.toLowerCase().includes(term) ||
|
||||||
c.tipoMuestra.toLowerCase().includes(term)
|
c.tipoMuestra.toLowerCase().includes(term) ||
|
||||||
|
nombrePac.includes(term)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -61,11 +89,11 @@ export function Cultivos({
|
|||||||
|
|
||||||
const getEstadoColor = (estado: string) => {
|
const getEstadoColor = (estado: string) => {
|
||||||
switch (estado) {
|
switch (estado) {
|
||||||
case 'NAF/Pendiente': return 'bg-amber-100 text-amber-800';
|
case 'NAF/Pendiente': return 'bg-amber-100 text-amber-800 dark:bg-amber-950/80 dark:text-amber-300 dark:border dark:border-amber-800';
|
||||||
case 'Parcial': return 'bg-orange-100 text-orange-800';
|
case 'Parcial': return 'bg-orange-100 text-orange-800 dark:bg-orange-950/80 dark:text-orange-300 dark:border dark:border-orange-800';
|
||||||
case 'Positivo': return 'bg-red-100 text-red-800';
|
case 'Positivo': return 'bg-red-100 text-red-800 dark:bg-red-950/80 dark:text-red-300 dark:border dark:border-red-800';
|
||||||
case 'Negativo': return 'bg-green-100 text-green-800';
|
case 'Negativo': return 'bg-green-100 text-green-800 dark:bg-green-950/80 dark:text-green-300 dark:border dark:border-green-800';
|
||||||
default: return 'bg-gray-100 text-gray-800';
|
default: return 'bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-300';
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -81,6 +109,8 @@ export function Cultivos({
|
|||||||
|
|
||||||
const abrirNuevo = () => {
|
const abrirNuevo = () => {
|
||||||
setCultivoSeleccionado(null);
|
setCultivoSeleccionado(null);
|
||||||
|
setPacienteSeleccionado(patient?.id || '');
|
||||||
|
setBusquedaPaciente('');
|
||||||
setFechaToma(new Date().toISOString().split('T')[0]);
|
setFechaToma(new Date().toISOString().split('T')[0]);
|
||||||
setProtocolo('');
|
setProtocolo('');
|
||||||
setTipoMuestra('HMCx2');
|
setTipoMuestra('HMCx2');
|
||||||
@@ -107,14 +137,18 @@ export function Cultivos({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const guardarNuevo = () => {
|
const guardarNuevo = () => {
|
||||||
if (!protocolo.trim() || !patient) return;
|
const targetPac = patient || findPaciente(pacienteSeleccionado);
|
||||||
|
if (!protocolo.trim() || !targetPac) return;
|
||||||
|
const activeInter = getInternacionActivaByPaciente ? getInternacionActivaByPaciente(targetPac.id) : undefined;
|
||||||
|
const targetInternacionId = internacionId || activeInter?.id || '';
|
||||||
|
|
||||||
onAgregarCultivo({
|
onAgregarCultivo({
|
||||||
pacienteId: patient.id,
|
pacienteId: targetPac.id,
|
||||||
internacionId,
|
internacionId: targetInternacionId,
|
||||||
fechaToma,
|
fechaToma,
|
||||||
protocolo,
|
protocolo,
|
||||||
tipoMuestra,
|
tipoMuestra,
|
||||||
observaciones: observaciones || undefined,
|
observaciones: observaciones,
|
||||||
estado: 'NAF/Pendiente',
|
estado: 'NAF/Pendiente',
|
||||||
});
|
});
|
||||||
setDialogoNuevoAbierto(false);
|
setDialogoNuevoAbierto(false);
|
||||||
@@ -126,8 +160,8 @@ export function Cultivos({
|
|||||||
onActualizarCultivo(cultivoSeleccionado.id, {
|
onActualizarCultivo(cultivoSeleccionado.id, {
|
||||||
estado: 'Parcial',
|
estado: 'Parcial',
|
||||||
germen,
|
germen,
|
||||||
sensible: sensible || undefined,
|
sensible: sensible,
|
||||||
resistente: resistente || undefined,
|
resistente: resistente,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
setDialogoParcialAbierto(false);
|
setDialogoParcialAbierto(false);
|
||||||
@@ -186,39 +220,50 @@ export function Cultivos({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{cultivosFiltrados.map((cultivo) => (
|
{cultivosFiltrados.map((cultivo) => {
|
||||||
<Card key={cultivo.id} className="hover:shadow-md transition-shadow">
|
const pac = findPaciente(cultivo.pacienteId);
|
||||||
<CardContent className="p-4">
|
const camaNombre = getCamaNombreForPaciente(cultivo.pacienteId);
|
||||||
<div className="flex flex-col gap-3">
|
|
||||||
<div className="flex flex-col sm:flex-row sm:items-start sm:justify-between gap-2">
|
return (
|
||||||
<div className="flex items-center gap-3">
|
<Card key={cultivo.id} className="hover:shadow-md transition-shadow">
|
||||||
<div className={`h-10 w-10 rounded-full flex items-center justify-center shrink-0 ${
|
<CardContent className="p-4">
|
||||||
cultivo.estado === 'NAF/Pendiente' ? 'bg-amber-100' :
|
<div className="flex flex-col gap-3">
|
||||||
cultivo.estado === 'Parcial' ? 'bg-orange-100' :
|
<div className="flex flex-col sm:flex-row sm:items-start sm:justify-between gap-2">
|
||||||
cultivo.estado === 'Positivo' ? 'bg-red-100' : 'bg-green-100'
|
<div className="flex items-center gap-3">
|
||||||
}`}>
|
<div className={`h-10 w-10 rounded-full flex items-center justify-center shrink-0 ${
|
||||||
<Microscope className={`h-5 w-5 ${
|
cultivo.estado === 'NAF/Pendiente' ? 'bg-amber-100 dark:bg-amber-950/80' :
|
||||||
cultivo.estado === 'NAF/Pendiente' ? 'text-amber-600' :
|
cultivo.estado === 'Parcial' ? 'bg-orange-100 dark:bg-orange-950/80' :
|
||||||
cultivo.estado === 'Parcial' ? 'text-orange-600' :
|
cultivo.estado === 'Positivo' ? 'bg-red-100 dark:bg-red-950/80' : 'bg-green-100 dark:bg-green-950/80'
|
||||||
cultivo.estado === 'Positivo' ? 'text-red-600' : 'text-green-600'
|
}`}>
|
||||||
}`} />
|
<Microscope className={`h-5 w-5 ${
|
||||||
</div>
|
cultivo.estado === 'NAF/Pendiente' ? 'text-amber-600 dark:text-amber-400' :
|
||||||
<div className="min-w-0">
|
cultivo.estado === 'Parcial' ? 'text-orange-600 dark:text-orange-400' :
|
||||||
<h3 className="font-bold text-sm sm:text-base truncate">
|
cultivo.estado === 'Positivo' ? 'text-red-600 dark:text-red-400' : 'text-green-600 dark:text-green-400'
|
||||||
{patient ? `${patient.apellido}, ${patient.nombre}` : 'Paciente no encontrado'}
|
}`} />
|
||||||
</h3>
|
</div>
|
||||||
<div className="flex flex-wrap items-center gap-2 text-xs sm:text-sm text-gray-500">
|
<div className="min-w-0">
|
||||||
<span className="flex items-center gap-1">
|
<div className="flex items-center gap-2">
|
||||||
<Calendar className="h-3 w-3" />
|
<h3 className="font-bold text-sm sm:text-base truncate">
|
||||||
{cultivo.fechaToma}
|
{pac ? `${pac.apellido}, ${pac.nombre}` : 'Paciente no encontrado'}
|
||||||
</span>
|
</h3>
|
||||||
<Badge variant="outline" className="text-xs">{cultivo.tipoMuestra}</Badge>
|
{camaNombre && (
|
||||||
<Badge className={`text-xs ${getEstadoColor(cultivo.estado)}`}>
|
<Badge variant="outline" className="text-xs shrink-0">
|
||||||
{getEstadoLabel(cultivo.estado)}
|
{camaNombre}
|
||||||
</Badge>
|
</Badge>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-wrap items-center gap-2 text-xs sm:text-sm text-gray-500">
|
||||||
|
<span className="flex items-center gap-1">
|
||||||
|
<Calendar className="h-3 w-3" />
|
||||||
|
{cultivo.fechaToma}
|
||||||
|
</span>
|
||||||
|
<Badge variant="outline" className="text-xs">{cultivo.tipoMuestra}</Badge>
|
||||||
|
<Badge className={`text-xs ${getEstadoColor(cultivo.estado)}`}>
|
||||||
|
{getEstadoLabel(cultivo.estado)}
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div className="flex flex-wrap items-center gap-1 sm:gap-2 ml-auto sm:ml-0">
|
<div className="flex flex-wrap items-center gap-1 sm:gap-2 ml-auto sm:ml-0">
|
||||||
{canEdit && onActualizarCultivo && cultivo.estado === 'NAF/Pendiente' && (
|
{canEdit && onActualizarCultivo && cultivo.estado === 'NAF/Pendiente' && (
|
||||||
<Button size="sm" variant="outline" className="text-xs px-2 py-1" onClick={() => abrirDefinitivo(cultivo)}>
|
<Button size="sm" variant="outline" className="text-xs px-2 py-1" onClick={() => abrirDefinitivo(cultivo)}>
|
||||||
@@ -251,7 +296,7 @@ export function Cultivos({
|
|||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
{cultivo.observaciones && (
|
{cultivo.observaciones && (
|
||||||
<p className="text-sm text-gray-600 bg-gray-50 p-2 rounded">
|
<p className="text-sm text-gray-600 dark:text-gray-300 bg-gray-50 dark:bg-gray-800 p-2 rounded">
|
||||||
{cultivo.observaciones}
|
{cultivo.observaciones}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
@@ -316,7 +361,8 @@ export function Cultivos({
|
|||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
))}
|
);
|
||||||
|
})}
|
||||||
|
|
||||||
{cultivosFiltrados.length === 0 && (
|
{cultivosFiltrados.length === 0 && (
|
||||||
<p className="text-center text-gray-500 py-8">
|
<p className="text-center text-gray-500 py-8">
|
||||||
@@ -332,6 +378,31 @@ export function Cultivos({
|
|||||||
<DialogTitle>Nuevo Cultivo</DialogTitle>
|
<DialogTitle>Nuevo Cultivo</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
|
{!patient && (
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label>Paciente</Label>
|
||||||
|
<Input
|
||||||
|
placeholder="Buscar paciente por nombre o DNI..."
|
||||||
|
value={busquedaPaciente}
|
||||||
|
onChange={(e) => setBusquedaPaciente(e.target.value)}
|
||||||
|
className="mb-2 text-xs"
|
||||||
|
/>
|
||||||
|
<Select value={pacienteSeleccionado} onValueChange={setPacienteSeleccionado}>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Seleccionar paciente" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent className="max-h-60 overflow-auto">
|
||||||
|
{pacientes
|
||||||
|
.filter(p => !busquedaPaciente || `${p.apellido} ${p.nombre} ${p.dni}`.toLowerCase().includes(busquedaPaciente.toLowerCase()))
|
||||||
|
.map(p => (
|
||||||
|
<SelectItem key={p.id} value={p.id}>
|
||||||
|
{p.apellido}, {p.nombre} (DNI: {p.dni})
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-2 gap-4">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label>Fecha de Toma</Label>
|
<Label>Fecha de Toma</Label>
|
||||||
|
|||||||
+34
-34
@@ -67,7 +67,7 @@ export function Dashboard({
|
|||||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||||
<Card className="border-l-4 border-l-blue-500">
|
<Card className="border-l-4 border-l-blue-500">
|
||||||
<CardHeader className="pb-2">
|
<CardHeader className="pb-2">
|
||||||
<CardTitle className="text-sm font-medium text-gray-500 flex items-center gap-2">
|
<CardTitle className="text-sm font-medium text-gray-500 dark:text-gray-400 flex items-center gap-2">
|
||||||
<Bed className="h-4 w-4" />
|
<Bed className="h-4 w-4" />
|
||||||
Ocupación de Camas
|
Ocupación de Camas
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
@@ -75,9 +75,9 @@ export function Dashboard({
|
|||||||
<CardContent>
|
<CardContent>
|
||||||
<div className="flex items-baseline gap-2">
|
<div className="flex items-baseline gap-2">
|
||||||
<span className="text-3xl font-bold">{estadisticas.porcentajeOcupacion}%</span>
|
<span className="text-3xl font-bold">{estadisticas.porcentajeOcupacion}%</span>
|
||||||
<span className="text-sm text-gray-500">{estadisticas.camasOcupadas}/{estadisticas.totalCamas}</span>
|
<span className="text-sm text-gray-500 dark:text-gray-400">{estadisticas.camasOcupadas}/{estadisticas.totalCamas}</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-gray-400 mt-1">
|
<p className="text-xs text-gray-400 dark:text-gray-400 mt-1">
|
||||||
{estadisticas.camasDisponibles} camas disponibles
|
{estadisticas.camasDisponibles} camas disponibles
|
||||||
</p>
|
</p>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
@@ -85,7 +85,7 @@ export function Dashboard({
|
|||||||
|
|
||||||
<Card className="border-l-4 border-l-green-500">
|
<Card className="border-l-4 border-l-green-500">
|
||||||
<CardHeader className="pb-2">
|
<CardHeader className="pb-2">
|
||||||
<CardTitle className="text-sm font-medium text-gray-500 flex items-center gap-2">
|
<CardTitle className="text-sm font-medium text-gray-500 dark:text-gray-400 flex items-center gap-2">
|
||||||
<Users className="h-4 w-4" />
|
<Users className="h-4 w-4" />
|
||||||
Camas Fuera de Área
|
Camas Fuera de Área
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
@@ -94,7 +94,7 @@ export function Dashboard({
|
|||||||
<div className="flex items-baseline gap-2">
|
<div className="flex items-baseline gap-2">
|
||||||
<span className="text-3xl font-bold">{estadisticas.camasFueraDeArea}</span>
|
<span className="text-3xl font-bold">{estadisticas.camasFueraDeArea}</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-gray-400 mt-1">
|
<p className="text-xs text-gray-400 dark:text-gray-400 mt-1">
|
||||||
Camas Fuera de Area
|
Camas Fuera de Area
|
||||||
</p>
|
</p>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
@@ -102,7 +102,7 @@ export function Dashboard({
|
|||||||
|
|
||||||
<Card className="border-l-4 border-l-purple-500">
|
<Card className="border-l-4 border-l-purple-500">
|
||||||
<CardHeader className="pb-2">
|
<CardHeader className="pb-2">
|
||||||
<CardTitle className="text-sm font-medium text-gray-500 flex items-center gap-2">
|
<CardTitle className="text-sm font-medium text-gray-500 dark:text-gray-400 flex items-center gap-2">
|
||||||
<ClipboardList className="h-4 w-4" />
|
<ClipboardList className="h-4 w-4" />
|
||||||
Internaciones Activas
|
Internaciones Activas
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
@@ -111,7 +111,7 @@ export function Dashboard({
|
|||||||
<div className="flex items-baseline gap-2">
|
<div className="flex items-baseline gap-2">
|
||||||
<span className="text-3xl font-bold">{estadisticas.internacionesActivas}</span>
|
<span className="text-3xl font-bold">{estadisticas.internacionesActivas}</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-gray-400 mt-1">
|
<p className="text-xs text-gray-400 dark:text-gray-400 mt-1">
|
||||||
En curso actualmente
|
En curso actualmente
|
||||||
</p>
|
</p>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
@@ -119,7 +119,7 @@ export function Dashboard({
|
|||||||
|
|
||||||
<Card className="border-l-4 border-l-amber-500">
|
<Card className="border-l-4 border-l-amber-500">
|
||||||
<CardHeader className="pb-2">
|
<CardHeader className="pb-2">
|
||||||
<CardTitle className="text-sm font-medium text-gray-500 flex items-center gap-2">
|
<CardTitle className="text-sm font-medium text-gray-500 dark:text-gray-400 flex items-center gap-2">
|
||||||
<Microscope className="h-4 w-4" />
|
<Microscope className="h-4 w-4" />
|
||||||
Cultivos Pendientes
|
Cultivos Pendientes
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
@@ -128,7 +128,7 @@ export function Dashboard({
|
|||||||
<div className="flex items-baseline gap-2">
|
<div className="flex items-baseline gap-2">
|
||||||
<span className="text-3xl font-bold">{estadisticas.cultivosPendientes}</span>
|
<span className="text-3xl font-bold">{estadisticas.cultivosPendientes}</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-gray-400 mt-1">
|
<p className="text-xs text-gray-400 dark:text-gray-400 mt-1">
|
||||||
Esperando resultados
|
Esperando resultados
|
||||||
</p>
|
</p>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
@@ -141,7 +141,7 @@ export function Dashboard({
|
|||||||
<Card>
|
<Card>
|
||||||
<CardHeader className="flex flex-row items-center justify-between pb-2">
|
<CardHeader className="flex flex-row items-center justify-between pb-2">
|
||||||
<CardTitle className="text-lg flex items-center gap-2">
|
<CardTitle className="text-lg flex items-center gap-2">
|
||||||
<Bed className="h-5 w-5 text-blue-600" />
|
<Bed className="h-5 w-5 text-blue-600 dark:text-blue-400" />
|
||||||
Estado de Camas
|
Estado de Camas
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<Button variant="ghost" size="sm" onClick={() => onCambiarVista('camas')}>
|
<Button variant="ghost" size="sm" onClick={() => onCambiarVista('camas')}>
|
||||||
@@ -150,33 +150,33 @@ export function Dashboard({
|
|||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className="grid grid-cols-2 gap-3">
|
<div className="grid grid-cols-2 gap-3">
|
||||||
<div className="bg-green-50 p-3 rounded-lg">
|
<div className="bg-green-50 dark:bg-green-950/60 border border-transparent dark:border-green-800/50 p-3 rounded-lg">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<CheckCircle2 className="h-4 w-4 text-green-600" />
|
<CheckCircle2 className="h-4 w-4 text-green-600 dark:text-green-400" />
|
||||||
<span className="text-sm font-medium text-green-800">Disponibles</span>
|
<span className="text-sm font-medium text-green-800 dark:text-green-300">Disponibles</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-2xl font-bold text-green-700 mt-1">{estadisticas.camasDisponibles}</p>
|
<p className="text-2xl font-bold text-green-700 dark:text-green-200 mt-1">{estadisticas.camasDisponibles}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-red-50 p-3 rounded-lg">
|
<div className="bg-red-50 dark:bg-red-950/60 border border-transparent dark:border-red-800/50 p-3 rounded-lg">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<AlertCircle className="h-4 w-4 text-red-600" />
|
<AlertCircle className="h-4 w-4 text-red-600 dark:text-red-400" />
|
||||||
<span className="text-sm font-medium text-red-800">Ocupadas</span>
|
<span className="text-sm font-medium text-red-800 dark:text-red-300">Ocupadas</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-2xl font-bold text-red-700 mt-1">{estadisticas.camasOcupadas}</p>
|
<p className="text-2xl font-bold text-red-700 dark:text-red-200 mt-1">{estadisticas.camasOcupadas}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-amber-50 p-3 rounded-lg">
|
<div className="bg-amber-50 dark:bg-amber-950/60 border border-transparent dark:border-amber-800/50 p-3 rounded-lg">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Clock className="h-4 w-4 text-amber-600" />
|
<Clock className="h-4 w-4 text-amber-600 dark:text-amber-400" />
|
||||||
<span className="text-sm font-medium text-amber-800">Mantenimiento</span>
|
<span className="text-sm font-medium text-amber-800 dark:text-amber-300">Mantenimiento</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-2xl font-bold text-amber-700 mt-1">{estadisticas.camasMantenimiento}</p>
|
<p className="text-2xl font-bold text-amber-700 dark:text-amber-200 mt-1">{estadisticas.camasMantenimiento}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-blue-50 p-3 rounded-lg">
|
<div className="bg-blue-50 dark:bg-blue-950/60 border border-transparent dark:border-blue-800/50 p-3 rounded-lg">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<TrendingUp className="h-4 w-4 text-blue-600" />
|
<TrendingUp className="h-4 w-4 text-blue-600 dark:text-blue-400" />
|
||||||
<span className="text-sm font-medium text-blue-800">Ocupación</span>
|
<span className="text-sm font-medium text-blue-800 dark:text-blue-300">Ocupación</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-2xl font-bold text-blue-700 mt-1">{estadisticas.porcentajeOcupacion}%</p>
|
<p className="text-2xl font-bold text-blue-700 dark:text-blue-200 mt-1">{estadisticas.porcentajeOcupacion}%</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
@@ -204,16 +204,16 @@ export function Dashboard({
|
|||||||
{internacionesActivas.map((internacion) => {
|
{internacionesActivas.map((internacion) => {
|
||||||
const paciente = getPacienteById(internacion.pacienteId);
|
const paciente = getPacienteById(internacion.pacienteId);
|
||||||
return (
|
return (
|
||||||
<div key={internacion.id} className="flex items-center justify-between p-3 bg-gray-50 rounded-lg">
|
<div key={internacion.id} className="flex items-center justify-between p-3 bg-gray-50 dark:bg-gray-800/80 border border-transparent dark:border-gray-700/50 rounded-lg">
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium text-gray-900">
|
<p className="font-medium text-gray-900 dark:text-gray-100">
|
||||||
{paciente ? `${paciente.apellido}, ${paciente.nombre}` : 'Paciente no encontrado'}
|
{paciente ? `${paciente.apellido}, ${paciente.nombre}` : 'Paciente no encontrado'}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-sm text-gray-500">
|
<p className="text-sm text-gray-500 dark:text-gray-400">
|
||||||
Ingreso: {internacion.fechaIngresoClinica ? formatDateDDMMYYYY(internacion.fechaIngresoClinica) : 'N/A'}
|
Ingreso: {internacion.fechaIngresoClinica ? formatDateDDMMYYYY(internacion.fechaIngresoClinica) : 'N/A'}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Badge variant="outline" className="bg-purple-50 text-purple-700 dark:bg-purple-900 dark:text-purple-300 dark:border-purple-700">
|
<Badge variant="outline" className="bg-purple-50 text-purple-700 dark:bg-purple-950/60 dark:text-purple-300 dark:border-purple-800">
|
||||||
Activa
|
Activa
|
||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
@@ -292,16 +292,16 @@ export function Dashboard({
|
|||||||
{cultivosRecientes.map((cultivo) => {
|
{cultivosRecientes.map((cultivo) => {
|
||||||
const paciente = getPacienteById(cultivo.pacienteId);
|
const paciente = getPacienteById(cultivo.pacienteId);
|
||||||
return (
|
return (
|
||||||
<div key={cultivo.id} className="flex items-center justify-between p-3 bg-gray-50 rounded-lg">
|
<div key={cultivo.id} className="flex items-center justify-between p-3 bg-gray-50 dark:bg-gray-800/80 border border-transparent dark:border-gray-700/50 rounded-lg">
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium text-gray-900">
|
<p className="font-medium text-gray-900 dark:text-gray-100">
|
||||||
{paciente ? `${paciente.apellido}, ${paciente.nombre}` : 'Paciente no encontrado'}
|
{paciente ? `${paciente.apellido}, ${paciente.nombre}` : 'Paciente no encontrado'}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-sm text-gray-500">
|
<p className="text-sm text-gray-500 dark:text-gray-400">
|
||||||
{cultivo.tipoMuestra} - {cultivo.fechaToma}
|
{cultivo.tipoMuestra} - {cultivo.fechaToma}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Badge className="bg-amber-100 text-amber-800 dark:bg-amber-900 dark:text-amber-300">
|
<Badge className="bg-amber-100 text-amber-800 dark:bg-amber-950/60 dark:text-amber-300 dark:border dark:border-amber-800">
|
||||||
<Clock className="h-3 w-3 mr-1" />
|
<Clock className="h-3 w-3 mr-1" />
|
||||||
Pendiente
|
Pendiente
|
||||||
</Badge>
|
</Badge>
|
||||||
|
|||||||
+107
-104
@@ -1,72 +1,56 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { ArrowLeft, Save, X, Bed, Calendar, Stethoscope, User, ClipboardList } from 'lucide-react';
|
import { Save, X, Bed, Stethoscope, User, Loader2 } from 'lucide-react';
|
||||||
import { Card, CardContent } from '@/components/ui/card';
|
import { Card, CardContent } from '@/components/ui/card';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { Label } from '@/components/ui/label';
|
import { Label } from '@/components/ui/label';
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
|
import { toast } from 'sonner';
|
||||||
import type { Paciente, Cama, Area, Internacion } from '@/types';
|
import type { Paciente, Cama, Area, Internacion } from '@/types';
|
||||||
|
|
||||||
interface EditIngresoProps {
|
interface EditIngresoProps {
|
||||||
internacion: Internacion;
|
internacion: Internacion;
|
||||||
paciente: Paciente;
|
paciente?: Paciente;
|
||||||
cama: Cama | undefined;
|
cama?: Cama;
|
||||||
pacientes: Paciente[];
|
pacientes: Paciente[];
|
||||||
camas: Cama[];
|
camas: Cama[];
|
||||||
areas: Area[];
|
areas: Area[];
|
||||||
onActualizarInternacion: (id: string, datos: Partial<Internacion>) => void;
|
onActualizarInternacion: (id: string, datos: Partial<Internacion>) => Promise<void> | void;
|
||||||
onAgregarCama?: (cama: Omit<Cama, 'id'>) => any;
|
onAgregarCama?: (cama: Omit<Cama, 'id'>) => Promise<string>;
|
||||||
onVolver: () => void;
|
onVolver: () => void;
|
||||||
getAreaName: (areaId: string | undefined) => string;
|
getAreaName: (areaId: string | undefined) => string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function EditIngreso({
|
export function EditIngreso({
|
||||||
internacion,
|
internacion,
|
||||||
paciente: pacienteOriginal,
|
|
||||||
cama: camaOriginal,
|
|
||||||
pacientes,
|
pacientes,
|
||||||
camas,
|
camas,
|
||||||
areas,
|
areas,
|
||||||
onActualizarInternacion,
|
onActualizarInternacion,
|
||||||
onAgregarCama,
|
onAgregarCama,
|
||||||
onVolver,
|
onVolver,
|
||||||
getAreaName
|
|
||||||
}: EditIngresoProps) {
|
}: EditIngresoProps) {
|
||||||
const [pacienteSeleccionado, setPacienteSeleccionado] = useState(internacion.pacienteId);
|
const [pacienteSeleccionado] = useState(internacion.pacienteId);
|
||||||
const [busqueda, setBusqueda] = useState('');
|
const [camaSeleccionada] = useState(internacion.camaId);
|
||||||
const [camaSeleccionada, setCamaSeleccionada] = useState(internacion.camaId);
|
const [areaSeleccionada] = useState(internacion.areaId);
|
||||||
const [areaSeleccionada, setAreaSeleccionada] = useState(internacion.areaId);
|
|
||||||
const [camaInput, setCamaInput] = useState('');
|
const [camaInput, setCamaInput] = useState('');
|
||||||
const [modoCama, setModoCama] = useState<'seleccionar' | 'escribir'>('seleccionar');
|
const [modoCama, setModoCama] = useState<'seleccionar' | 'escribir'>('seleccionar');
|
||||||
const [medico, setMedico] = useState(internacion.medicoIngresante);
|
const [medico, setMedico] = useState(internacion.medicoIngresante || '');
|
||||||
const [fechaIngresoHospital, setFechaIngresoHospital] = useState(internacion.fechaIngresoHospital || '');
|
const [fechaIngresoHospital, setFechaIngresoHospital] = useState(internacion.fechaIngresoHospital || '');
|
||||||
const [fechaIngresoClinica, setFechaIngresoClinica] = useState(internacion.fechaIngresoClinica || '');
|
const [fechaIngresoClinica, setFechaIngresoClinica] = useState(internacion.fechaIngresoClinica || '');
|
||||||
const [motivoConsulta, setMotivoConsulta] = useState(internacion.motivoConsulta || '');
|
const [motivoConsulta, setMotivoConsulta] = useState(internacion.motivoConsulta || '');
|
||||||
const [diagnosticoIngreso, setDiagnosticoIngreso] = useState(internacion.diagnosticoIngreso);
|
const [diagnosticoIngreso, setDiagnosticoIngreso] = useState(internacion.diagnosticoIngreso || '');
|
||||||
const [enfermedadActual, setEnfermedadActual] = useState(internacion.enfermedadActual);
|
const [enfermedadActual, setEnfermedadActual] = useState(internacion.enfermedadActual || '');
|
||||||
const [antecedentesEnfermedadActual, setAntecedentesEnfermedadActual] = useState(internacion.antecedentesEnfermedadActual || '');
|
const [antecedentesEnfermedadActual, setAntecedentesEnfermedadActual] = useState(internacion.antecedentesEnfermedadActual || '');
|
||||||
const [apache, setApache] = useState(internacion.apache || '');
|
const [apache, setApache] = useState(internacion.apache || '');
|
||||||
const [derivacion, setDerivacion] = useState(internacion.derivacion || '');
|
const [derivacion, setDerivacion] = useState(internacion.derivacion || '');
|
||||||
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||||
const calcularEdad = (fechaNacimiento: string) => {
|
|
||||||
const hoy = new Date();
|
|
||||||
const nacimiento = new Date(fechaNacimiento);
|
|
||||||
let edad = hoy.getFullYear() - nacimiento.getFullYear();
|
|
||||||
const mes = hoy.getMonth() - nacimiento.getMonth();
|
|
||||||
if (mes < 0 || (mes === 0 && hoy.getDate() < nacimiento.getDate())) {
|
|
||||||
edad--;
|
|
||||||
}
|
|
||||||
return edad;
|
|
||||||
};
|
|
||||||
|
|
||||||
const selectedPaciente = pacientes.find(p => p.id === pacienteSeleccionado);
|
const selectedPaciente = pacientes.find(p => p.id === pacienteSeleccionado);
|
||||||
const areasDisponibles = areas.filter(a => {
|
const normalizeStr = (str?: string) => (str || '').normalize('NFD').replace(/[\u0300-\u036f]/g, '').trim().toLowerCase();
|
||||||
const nombre = (a.nombre || '').trim().toLowerCase();
|
const areaFueraDeArea = areas.find(a => normalizeStr(a.nombre) === 'fuera de area');
|
||||||
return nombre !== 'fuera de area';
|
const areaFueraDeAreaId = areaFueraDeArea?.id || areas[0]?.id || 'fuera-de-area';
|
||||||
});
|
|
||||||
const areaFueraDeArea = areas.find(a => (a.nombre || '').trim().toLowerCase() === 'fuera de area');
|
|
||||||
const areaFueraDeAreaId = areaFueraDeArea?.id || '';
|
|
||||||
|
|
||||||
const parseBedNumber = (numero: string) => {
|
const parseBedNumber = (numero: string) => {
|
||||||
const match = numero.match(/^(\d{3})-(\d+)$/);
|
const match = numero.match(/^(\d{3})-(\d+)$/);
|
||||||
@@ -112,76 +96,95 @@ export function EditIngreso({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
if (!pacienteSeleccionado || !medico || !diagnosticoIngreso || !enfermedadActual) {
|
if (!pacienteSeleccionado) {
|
||||||
alert('Por favor complete los campos obligatorios');
|
toast.error('Debe seleccionar un paciente');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let camaId = '';
|
if (!diagnosticoIngreso.trim()) {
|
||||||
let newAreaId = '';
|
toast.error('Debe ingresar el diagnóstico de ingreso');
|
||||||
|
return;
|
||||||
if (modoCama === 'escribir') {
|
|
||||||
if (!camaInput.trim()) {
|
|
||||||
alert('Ingrese el número de cama');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const numeroCama = camaInput.trim();
|
|
||||||
const camaExistente = camas.find(c => c.numero === numeroCama);
|
|
||||||
if (camaExistente) {
|
|
||||||
camaId = camaExistente.id;
|
|
||||||
newAreaId = camaExistente.areaId || '';
|
|
||||||
} else if (onAgregarCama) {
|
|
||||||
const nuevaCamaId = onAgregarCama({
|
|
||||||
numero: numeroCama,
|
|
||||||
areaId: areaFueraDeAreaId,
|
|
||||||
tipo: 'General',
|
|
||||||
estado: 'Ocupada'
|
|
||||||
});
|
|
||||||
camaId = nuevaCamaId;
|
|
||||||
newAreaId = areaFueraDeAreaId;
|
|
||||||
} else {
|
|
||||||
alert('No se puede agregar una nueva cama');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (camaSeleccionada && camaSeleccionada !== internacion.camaId) {
|
|
||||||
const camaElegida = sortedCamas.find(c => c.id === camaSeleccionada);
|
|
||||||
if (!camaElegida) {
|
|
||||||
alert('Cama no encontrada');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
newAreaId = camaElegida.areaId || areaFueraDeAreaId;
|
|
||||||
if (!newAreaId) {
|
|
||||||
alert('La cama no tiene un área asignada');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
camaId = camaSeleccionada;
|
|
||||||
} else {
|
|
||||||
newAreaId = areaSeleccionada || internacion.areaId;
|
|
||||||
camaId = internacion.camaId;
|
|
||||||
}
|
|
||||||
if (!newAreaId) {
|
|
||||||
alert('Seleccione un área de trabajo');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onActualizarInternacion(internacion.id, {
|
if (!enfermedadActual.trim()) {
|
||||||
pacienteId: pacienteSeleccionado,
|
toast.error('Debe ingresar la enfermedad actual');
|
||||||
camaId: camaId,
|
return;
|
||||||
areaId: newAreaId,
|
}
|
||||||
medicoIngresante: medico,
|
|
||||||
diagnosticoIngreso,
|
|
||||||
motivoConsulta: motivoConsulta || undefined,
|
|
||||||
enfermedadActual,
|
|
||||||
antecedentesEnfermedadActual: antecedentesEnfermedadActual || undefined,
|
|
||||||
fechaIngresoHospital: fechaIngresoHospital || undefined,
|
|
||||||
fechaIngresoClinica: fechaIngresoClinica || undefined,
|
|
||||||
apache: apache || undefined,
|
|
||||||
derivacion: derivacion || undefined,
|
|
||||||
});
|
|
||||||
|
|
||||||
onVolver();
|
if (!medico.trim()) {
|
||||||
|
toast.error('Debe ingresar el médico ingresante');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsSubmitting(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
let camaId = '';
|
||||||
|
let newAreaId = '';
|
||||||
|
|
||||||
|
if (modoCama === 'escribir') {
|
||||||
|
if (!camaInput.trim()) {
|
||||||
|
toast.error('Ingrese el número de cama');
|
||||||
|
setIsSubmitting(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const numeroCama = camaInput.trim();
|
||||||
|
const camaExistente = camas.find(c => c.numero === numeroCama);
|
||||||
|
if (camaExistente) {
|
||||||
|
camaId = camaExistente.id;
|
||||||
|
newAreaId = camaExistente.areaId || areaFueraDeAreaId;
|
||||||
|
} else if (onAgregarCama) {
|
||||||
|
camaId = await onAgregarCama({
|
||||||
|
numero: numeroCama,
|
||||||
|
areaId: areaFueraDeAreaId,
|
||||||
|
tipo: 'General',
|
||||||
|
estado: 'Ocupada'
|
||||||
|
});
|
||||||
|
newAreaId = areaFueraDeAreaId;
|
||||||
|
} else {
|
||||||
|
toast.error('No se puede crear la cama');
|
||||||
|
setIsSubmitting(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (camaSeleccionada && camaSeleccionada !== internacion.camaId) {
|
||||||
|
const camaElegida = sortedCamas.find(c => c.id === camaSeleccionada);
|
||||||
|
if (!camaElegida) {
|
||||||
|
toast.error('Cama no encontrada');
|
||||||
|
setIsSubmitting(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
newAreaId = camaElegida.areaId || areaFueraDeAreaId;
|
||||||
|
camaId = camaSeleccionada;
|
||||||
|
} else {
|
||||||
|
newAreaId = areaSeleccionada || internacion.areaId || areaFueraDeAreaId;
|
||||||
|
camaId = internacion.camaId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await onActualizarInternacion(internacion.id, {
|
||||||
|
pacienteId: pacienteSeleccionado,
|
||||||
|
camaId: camaId,
|
||||||
|
areaId: newAreaId,
|
||||||
|
medicoIngresante: medico.trim(),
|
||||||
|
diagnosticoIngreso: diagnosticoIngreso.trim(),
|
||||||
|
motivoConsulta: motivoConsulta.trim() || undefined,
|
||||||
|
enfermedadActual: enfermedadActual.trim(),
|
||||||
|
antecedentesEnfermedadActual: antecedentesEnfermedadActual.trim() || undefined,
|
||||||
|
fechaIngresoHospital: fechaIngresoHospital || undefined,
|
||||||
|
fechaIngresoClinica: fechaIngresoClinica || undefined,
|
||||||
|
apache: apache.trim() || undefined,
|
||||||
|
derivacion: derivacion.trim() || undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
toast.success('Ingreso actualizado correctamente');
|
||||||
|
onVolver();
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error al actualizar ingreso:', err);
|
||||||
|
toast.error('Error al actualizar el ingreso');
|
||||||
|
} finally {
|
||||||
|
setIsSubmitting(false);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -198,12 +201,12 @@ export function EditIngreso({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2 ml-auto sm:ml-0">
|
<div className="flex gap-2 ml-auto sm:ml-0">
|
||||||
<Button variant="secondary" onClick={() => onVolver()}>
|
<Button variant="secondary" onClick={() => onVolver()} disabled={isSubmitting}>
|
||||||
<X className="h-4 w-4 mr-2" />
|
<X className="h-4 w-4 mr-2" />
|
||||||
Cancelar
|
Cancelar
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={handleSubmit}>
|
<Button onClick={handleSubmit} disabled={isSubmitting}>
|
||||||
<Save className="h-4 w-4 mr-2" />
|
{isSubmitting ? <Loader2 className="h-4 w-4 mr-2 animate-spin" /> : <Save className="h-4 w-4 mr-2" />}
|
||||||
Guardar Cambios
|
Guardar Cambios
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@@ -214,7 +217,7 @@ export function EditIngreso({
|
|||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<Card className="min-h-[200px]">
|
<Card className="min-h-[200px]">
|
||||||
<CardContent className="pt-6 space-y-4">
|
<CardContent className="pt-6 space-y-4">
|
||||||
<h3 className="font-semibold text-gray-900 flex items-center gap-2">
|
<h3 className="font-semibold text-gray-900 dark:text-white flex items-center gap-2">
|
||||||
<User className="h-4 w-4" />
|
<User className="h-4 w-4" />
|
||||||
Datos del Paciente
|
Datos del Paciente
|
||||||
</h3>
|
</h3>
|
||||||
@@ -288,7 +291,7 @@ export function EditIngreso({
|
|||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<Card className="min-h-[200px]">
|
<Card className="min-h-[200px]">
|
||||||
<CardContent className="pt-6 space-y-4">
|
<CardContent className="pt-6 space-y-4">
|
||||||
<h3 className="font-semibold text-gray-900 flex items-center gap-2">
|
<h3 className="font-semibold text-gray-900 dark:text-white flex items-center gap-2">
|
||||||
<Bed className="h-4 w-4" />
|
<Bed className="h-4 w-4" />
|
||||||
Asignación de Cama y Área
|
Asignación de Cama y Área
|
||||||
</h3>
|
</h3>
|
||||||
@@ -348,7 +351,7 @@ export function EditIngreso({
|
|||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<Card>
|
<Card>
|
||||||
<CardContent className="pt-6 space-y-4">
|
<CardContent className="pt-6 space-y-4">
|
||||||
<h3 className="font-semibold text-gray-900 flex items-center gap-2">
|
<h3 className="font-semibold text-gray-900 dark:text-white flex items-center gap-2">
|
||||||
<ClipboardList className="h-4 w-4" />
|
<ClipboardList className="h-4 w-4" />
|
||||||
Datos de Ingreso
|
Datos de Ingreso
|
||||||
</h3>
|
</h3>
|
||||||
@@ -399,7 +402,7 @@ export function EditIngreso({
|
|||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<Card>
|
<Card>
|
||||||
<CardContent className="pt-6 space-y-4">
|
<CardContent className="pt-6 space-y-4">
|
||||||
<h3 className="font-semibold text-gray-900">
|
<h3 className="font-semibold text-gray-900 dark:text-white">
|
||||||
Datos Clínicos
|
Datos Clínicos
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
@@ -452,7 +455,7 @@ export function EditIngreso({
|
|||||||
|
|
||||||
<Card>
|
<Card>
|
||||||
<CardContent className="pt-6 space-y-4">
|
<CardContent className="pt-6 space-y-4">
|
||||||
<h3 className="font-semibold text-gray-900 flex items-center gap-2">
|
<h3 className="font-semibold text-gray-900 dark:text-white flex items-center gap-2">
|
||||||
<Stethoscope className="h-4 w-4" />
|
<Stethoscope className="h-4 w-4" />
|
||||||
Médico Ingresante
|
Médico Ingresante
|
||||||
</h3>
|
</h3>
|
||||||
|
|||||||
@@ -7,15 +7,16 @@ import { Input } from '@/components/ui/input';
|
|||||||
import { Label } from '@/components/ui/label';
|
import { Label } from '@/components/ui/label';
|
||||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog';
|
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog';
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||||
|
import { toast } from 'sonner';
|
||||||
import type { Evolucion, Internacion, Paciente, SignosVitales, ExamenFisico } from '@/types';
|
import type { Evolucion, Internacion, Paciente, SignosVitales, ExamenFisico } from '@/types';
|
||||||
|
|
||||||
interface EvolucionesProps {
|
interface EvolucionesProps {
|
||||||
evoluciones: Evolucion[];
|
evoluciones: Evolucion[];
|
||||||
internaciones: Internacion[];
|
internaciones: Internacion[];
|
||||||
pacientes: Paciente[];
|
pacientes: Paciente[];
|
||||||
onAgregarEvolucion: (evolucion: Omit<Evolucion, 'id'>) => void;
|
onAgregarEvolucion: (evolucion: Omit<Evolucion, 'id'>) => Promise<unknown> | void;
|
||||||
onActualizarEvolucion?: (id: string, datos: Partial<Evolucion>) => void;
|
onActualizarEvolucion?: (id: string, datos: Partial<Evolucion>) => Promise<unknown> | void;
|
||||||
onEliminarEvolucion: (id: string) => void;
|
onEliminarEvolucion: (id: string) => Promise<unknown> | void;
|
||||||
getPacienteById: (id: string) => Paciente | undefined;
|
getPacienteById: (id: string) => Paciente | undefined;
|
||||||
getInternacionActivaByPaciente: (pacienteId: string) => Internacion | undefined;
|
getInternacionActivaByPaciente: (pacienteId: string) => Internacion | undefined;
|
||||||
}
|
}
|
||||||
@@ -81,9 +82,13 @@ export function Evoluciones({
|
|||||||
|
|
||||||
const abrirEditar = (evo: Evolucion) => {
|
const abrirEditar = (evo: Evolucion) => {
|
||||||
setEvolucionEditando(evo);
|
setEvolucionEditando(evo);
|
||||||
|
const internacion = internaciones.find(i => i.id === evo.internacionId);
|
||||||
|
if (internacion) {
|
||||||
|
setPacienteSeleccionado(internacion.pacienteId);
|
||||||
|
}
|
||||||
setFecha(evo.fecha);
|
setFecha(evo.fecha);
|
||||||
setHora(evo.hora);
|
setHora(evo.hora);
|
||||||
setMedico(evo.medico);
|
setMedico(evo.medico || '');
|
||||||
setTemperatura(evo.signosVitales?.temperatura?.toString() || '');
|
setTemperatura(evo.signosVitales?.temperatura?.toString() || '');
|
||||||
setPresionSistolica(evo.signosVitales?.presionSistolica?.toString() || '');
|
setPresionSistolica(evo.signosVitales?.presionSistolica?.toString() || '');
|
||||||
setPresionDiastolica(evo.signosVitales?.presionDiastolica?.toString() || '');
|
setPresionDiastolica(evo.signosVitales?.presionDiastolica?.toString() || '');
|
||||||
@@ -103,9 +108,20 @@ export function Evoluciones({
|
|||||||
setDialogoAbierto(true);
|
setDialogoAbierto(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleGuardar = () => {
|
const handleGuardar = async () => {
|
||||||
const internacion = getInternacionActivaByPaciente(pacienteSeleccionado);
|
const internacion = evolucionEditando
|
||||||
if (!internacion || !medico) return;
|
? internaciones.find(i => i.id === evolucionEditando.internacionId)
|
||||||
|
: getInternacionActivaByPaciente(pacienteSeleccionado);
|
||||||
|
|
||||||
|
if (!medico.trim()) {
|
||||||
|
toast.error('Debe completar el nombre del médico');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!internacion) {
|
||||||
|
toast.error('Seleccione un paciente internado');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const signosVitales: SignosVitales | undefined =
|
const signosVitales: SignosVitales | undefined =
|
||||||
temperatura || presionSistolica || frecuenciaCardiaca
|
temperatura || presionSistolica || frecuenciaCardiaca
|
||||||
@@ -135,7 +151,7 @@ export function Evoluciones({
|
|||||||
const evolucionData = {
|
const evolucionData = {
|
||||||
fecha,
|
fecha,
|
||||||
hora,
|
hora,
|
||||||
medico,
|
medico: medico.trim(),
|
||||||
signosVitales,
|
signosVitales,
|
||||||
examenFisico,
|
examenFisico,
|
||||||
novedades: novedades || undefined,
|
novedades: novedades || undefined,
|
||||||
@@ -143,14 +159,21 @@ export function Evoluciones({
|
|||||||
pendientes: pendientes || undefined,
|
pendientes: pendientes || undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (evolucionEditando && onActualizarEvolucion) {
|
try {
|
||||||
onActualizarEvolucion(evolucionEditando.id, evolucionData);
|
if (evolucionEditando && onActualizarEvolucion) {
|
||||||
} else {
|
await onActualizarEvolucion(evolucionEditando.id, evolucionData);
|
||||||
onAgregarEvolucion({ ...evolucionData, internacionId: internacion.id });
|
toast.success('Evolución actualizada correctamente');
|
||||||
}
|
} else {
|
||||||
|
await onAgregarEvolucion({ ...evolucionData, internacionId: internacion.id });
|
||||||
|
toast.success('Evolución agregada correctamente');
|
||||||
|
}
|
||||||
|
|
||||||
resetFormulario();
|
resetFormulario();
|
||||||
setDialogoAbierto(false);
|
setDialogoAbierto(false);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error al guardar evolución:', err);
|
||||||
|
toast.error('Error al guardar los cambios de la evolución');
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const pacientesInternados = pacientes.filter(p => {
|
const pacientesInternados = pacientes.filter(p => {
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { useHospitalStore } from '@/hooks/useHospitalStore';
|
import { useHospitalStore } from '@/hooks/useHospitalStore';
|
||||||
import type { Usuario, RolUsuario, Area } from '@/types';
|
import type { Usuario, RolUsuario, Area } from '@/types';
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent } from '@/components/ui/card';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { Label } from '@/components/ui/label';
|
import { Label } from '@/components/ui/label';
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog';
|
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { Plus, Pencil, Trash2, UserCog, Mail, Shield } from 'lucide-react';
|
import { Plus, Pencil, Trash2 } from 'lucide-react';
|
||||||
|
|
||||||
const API_BASE = 'http://localhost:4001/api';
|
const API_BASE = import.meta.env.VITE_API_URL || '/api';
|
||||||
|
|
||||||
export function GestionUsuarios() {
|
export function GestionUsuarios() {
|
||||||
const { logout } = useHospitalStore();
|
const { logout } = useHospitalStore();
|
||||||
@@ -92,23 +92,36 @@ export function GestionUsuarios() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
|
if (!form.apellido.trim() || !form.nombre.trim() || !form.dni.trim()) {
|
||||||
|
alert('Por favor ingrese Apellido, Nombre y DNI.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
let res: Response;
|
||||||
if (editUsuario) {
|
if (editUsuario) {
|
||||||
await fetch(`${API_BASE}/usuarios/${editUsuario.id}`, {
|
res = await fetch(`${API_BASE}/usuarios/${editUsuario.id}`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify(form),
|
body: JSON.stringify(form),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
await fetch(`${API_BASE}/usuarios`, {
|
res = await fetch(`${API_BASE}/usuarios`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify(form),
|
body: JSON.stringify(form),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
const errData = await res.json().catch(() => ({ error: 'Error al guardar usuario' }));
|
||||||
|
alert(errData.error || 'Error al guardar usuario');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
setDialogOpen(false);
|
setDialogOpen(false);
|
||||||
resetForm();
|
resetForm();
|
||||||
fetchUsuarios();
|
await fetchUsuarios();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
alert('Error al guardar usuario');
|
alert('Error al guardar usuario');
|
||||||
@@ -118,10 +131,16 @@ export function GestionUsuarios() {
|
|||||||
const handleDelete = async (id: string) => {
|
const handleDelete = async (id: string) => {
|
||||||
if (!confirm('¿Está seguro de eliminar este usuario?')) return;
|
if (!confirm('¿Está seguro de eliminar este usuario?')) return;
|
||||||
try {
|
try {
|
||||||
await fetch(`${API_BASE}/usuarios/${id}`, { method: 'DELETE' });
|
const res = await fetch(`${API_BASE}/usuarios/${id}`, { method: 'DELETE' });
|
||||||
fetchUsuarios();
|
if (!res.ok) {
|
||||||
|
const errData = await res.json().catch(() => ({ error: 'Error al eliminar usuario' }));
|
||||||
|
alert(errData.error || 'Error al eliminar usuario');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await fetchUsuarios();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
|
alert('Error al eliminar usuario');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -144,19 +163,20 @@ export function GestionUsuarios() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-3">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-2xl font-bold dark:text-white">Gestión de Usuarios</h1>
|
<h1 className="text-2xl font-bold dark:text-white">Gestión de Usuarios</h1>
|
||||||
<p className="text-gray-500 dark:text-gray-400">Administrar usuarios del sistema</p>
|
<p className="text-gray-500 dark:text-gray-400">Administrar usuarios del sistema</p>
|
||||||
</div>
|
</div>
|
||||||
<Button onClick={() => { resetForm(); setDialogOpen(true); }}>
|
<Button className="w-full sm:w-auto" onClick={() => { resetForm(); setDialogOpen(true); }}>
|
||||||
<Plus className="h-4 w-4 mr-2" />
|
<Plus className="h-4 w-4 mr-2" />
|
||||||
Nuevo Usuario
|
Nuevo Usuario
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Card>
|
{/* Desktop Table View */}
|
||||||
<CardContent className="p-0">
|
<Card className="hidden md:block">
|
||||||
|
<CardContent className="p-0 overflow-x-auto">
|
||||||
<Table>
|
<Table>
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
@@ -180,7 +200,7 @@ export function GestionUsuarios() {
|
|||||||
</Badge>
|
</Badge>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>{getAreaName(usu.areaId)}</TableCell>
|
<TableCell>{getAreaName(usu.areaId)}</TableCell>
|
||||||
<TableCell>{usu.email}</TableCell>
|
<TableCell>{usu.email || '-'}</TableCell>
|
||||||
<TableCell>{usu.matriculaProfesional || '-'}</TableCell>
|
<TableCell>{usu.matriculaProfesional || '-'}</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
@@ -199,13 +219,60 @@ export function GestionUsuarios() {
|
|||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
{/* Mobile Cards View */}
|
||||||
|
<div className="grid grid-cols-1 gap-3 md:hidden">
|
||||||
|
{usuarios.map((usu) => (
|
||||||
|
<Card key={usu.id} className="p-4 space-y-3">
|
||||||
|
<div className="flex items-start justify-between gap-2">
|
||||||
|
<div>
|
||||||
|
<h3 className="font-semibold text-lg text-gray-900 dark:text-gray-100">
|
||||||
|
{usu.apellido}, {usu.nombre}
|
||||||
|
</h3>
|
||||||
|
<p className="text-sm text-gray-500 dark:text-gray-400">DNI: {usu.dni}</p>
|
||||||
|
</div>
|
||||||
|
<Badge variant={usu.rol === 'admin' ? 'default' : 'secondary'}>
|
||||||
|
{getRolLabel(usu.rol)}
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-2 gap-2 text-sm pt-2 border-t border-gray-100 dark:border-gray-800">
|
||||||
|
<div>
|
||||||
|
<span className="text-xs text-gray-400 block">Área</span>
|
||||||
|
<span className="font-medium text-gray-700 dark:text-gray-300">{getAreaName(usu.areaId)}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span className="text-xs text-gray-400 block">Matrícula</span>
|
||||||
|
<span className="font-medium text-gray-700 dark:text-gray-300">{usu.matriculaProfesional || '-'}</span>
|
||||||
|
</div>
|
||||||
|
{usu.email && (
|
||||||
|
<div className="col-span-2">
|
||||||
|
<span className="text-xs text-gray-400 block">Email</span>
|
||||||
|
<span className="font-medium text-gray-700 dark:text-gray-300 truncate block">{usu.email}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex justify-end gap-2 pt-2 border-t border-gray-100 dark:border-gray-800">
|
||||||
|
<Button variant="outline" size="sm" onClick={() => openEdit(usu)}>
|
||||||
|
<Pencil className="h-4 w-4 mr-1" />
|
||||||
|
Editar
|
||||||
|
</Button>
|
||||||
|
<Button variant="outline" size="sm" className="text-red-600 hover:text-red-700 dark:text-red-400" onClick={() => handleDelete(usu.id)}>
|
||||||
|
<Trash2 className="h-4 w-4 mr-1" />
|
||||||
|
Eliminar
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
<Dialog open={dialogOpen} onOpenChange={setDialogOpen}>
|
<Dialog open={dialogOpen} onOpenChange={setDialogOpen}>
|
||||||
<DialogContent className="max-w-lg max-h-[90vh] overflow-y-auto">
|
<DialogContent className="max-w-lg w-[95vw] sm:w-full max-h-[90vh] overflow-y-auto p-4 sm:p-6">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>{editUsuario ? 'Editar Usuario' : 'Nuevo Usuario'}</DialogTitle>
|
<DialogTitle>{editUsuario ? 'Editar Usuario' : 'Nuevo Usuario'}</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4 pt-2">
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label>Apellido</Label>
|
<Label>Apellido</Label>
|
||||||
<Input value={form.apellido} onChange={(e) => setForm({ ...form, apellido: e.target.value })} />
|
<Input value={form.apellido} onChange={(e) => setForm({ ...form, apellido: e.target.value })} />
|
||||||
@@ -216,7 +283,7 @@ export function GestionUsuarios() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label>DNI</Label>
|
<Label>DNI</Label>
|
||||||
<Input value={form.dni} onChange={(e) => setForm({ ...form, dni: e.target.value })} disabled={!!editUsuario} />
|
<Input value={form.dni} onChange={(e) => setForm({ ...form, dni: e.target.value })} disabled={!!editUsuario} />
|
||||||
@@ -232,7 +299,7 @@ export function GestionUsuarios() {
|
|||||||
<Input type="email" value={form.email} onChange={(e) => setForm({ ...form, email: e.target.value })} />
|
<Input type="email" value={form.email} onChange={(e) => setForm({ ...form, email: e.target.value })} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label>Rol</Label>
|
<Label>Rol</Label>
|
||||||
<Select value={form.rol} onValueChange={(v) => setForm({ ...form, rol: v as RolUsuario })}>
|
<Select value={form.rol} onValueChange={(v) => setForm({ ...form, rol: v as RolUsuario })}>
|
||||||
@@ -269,14 +336,19 @@ export function GestionUsuarios() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label>{editUsuario ? 'Nueva Contraseña (opcional)' : 'Contraseña'}</Label>
|
<Label>{editUsuario ? 'Nueva Contraseña (opcional)' : 'Contraseña (opcional)'}</Label>
|
||||||
<Input type="password" value={form.password} onChange={(e) => setForm({ ...form, password: e.target.value })} />
|
<Input
|
||||||
|
type="password"
|
||||||
|
placeholder={editUsuario ? 'Dejar en blanco para no modificar' : 'Por defecto se usará el DNI'}
|
||||||
|
value={form.password}
|
||||||
|
onChange={(e) => setForm({ ...form, password: e.target.value })}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex justify-end gap-2 mt-4">
|
<div className="flex flex-col-reverse sm:flex-row justify-end gap-2 mt-4">
|
||||||
<Button variant="outline" onClick={() => setDialogOpen(false)}>Cancelar</Button>
|
<Button variant="outline" className="w-full sm:w-auto" onClick={() => setDialogOpen(false)}>Cancelar</Button>
|
||||||
<Button onClick={handleSubmit}>
|
<Button className="w-full sm:w-auto" onClick={handleSubmit}>
|
||||||
{editUsuario ? 'Actualizar' : 'Crear'}
|
{editUsuario ? 'Actualizar' : 'Crear'}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
import { toast } from 'sonner';
|
||||||
import { PDFDocument } from 'pdf-lib';
|
import { PDFDocument } from 'pdf-lib';
|
||||||
import { User } from 'lucide-react';
|
import { User } from 'lucide-react';
|
||||||
import {
|
import {
|
||||||
@@ -52,7 +53,7 @@ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@
|
|||||||
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area"
|
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area"
|
||||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/ui/dropdown-menu';
|
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/ui/dropdown-menu';
|
||||||
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts';
|
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts';
|
||||||
import type { Laboratorio, AcidoBase, Cultivo, Paciente, ResultadoLaboratorio, Internacion, Evolucion, Cama, EstudioComplementario, Interconsulta, ATB, Indicacion } from '@/types';
|
import type { Laboratorio, Glucemia, AcidoBase, Cultivo, Paciente, ResultadoLaboratorio, Internacion, Evolucion, Cama, EstudioComplementario, Interconsulta, ATB, Indicacion } from '@/types';
|
||||||
import { formatDateDDMMYYYY } from '@/lib/utils';
|
import { formatDateDDMMYYYY } from '@/lib/utils';
|
||||||
import { SeccionIndicaciones } from './SeccionIndicaciones';
|
import { SeccionIndicaciones } from './SeccionIndicaciones';
|
||||||
|
|
||||||
@@ -63,6 +64,7 @@ interface HistoriaClinicaProps {
|
|||||||
allCamas?: Cama[];
|
allCamas?: Cama[];
|
||||||
evoluciones: Evolucion[];
|
evoluciones: Evolucion[];
|
||||||
laboratorios: Laboratorio[];
|
laboratorios: Laboratorio[];
|
||||||
|
glucemias: Glucemia[];
|
||||||
acidosBase: AcidoBase[];
|
acidosBase: AcidoBase[];
|
||||||
cultivos: Cultivo[];
|
cultivos: Cultivo[];
|
||||||
estudiosComplementarios: EstudioComplementario[];
|
estudiosComplementarios: EstudioComplementario[];
|
||||||
@@ -77,6 +79,9 @@ interface HistoriaClinicaProps {
|
|||||||
onAgregarLaboratorio: (laboratorios: Omit<Laboratorio, 'id'>) => void;
|
onAgregarLaboratorio: (laboratorios: Omit<Laboratorio, 'id'>) => void;
|
||||||
onActualizarLaboratorio: (id: string, datos: Partial<Laboratorio>) => void;
|
onActualizarLaboratorio: (id: string, datos: Partial<Laboratorio>) => void;
|
||||||
onEliminarLaboratorio: (id: string) => void;
|
onEliminarLaboratorio: (id: string) => void;
|
||||||
|
onAgregarGlucemia: (glucemia: Omit<Glucemia, 'id'>) => void;
|
||||||
|
onActualizarGlucemia: (id: string, datos: Partial<Glucemia>) => void;
|
||||||
|
onEliminarGlucemia: (id: string) => void;
|
||||||
onAgregarLaboratorioConAcidoBase?: (l: Omit<Laboratorio, 'id'>, a?: Omit<AcidoBase, 'id'>) => void;
|
onAgregarLaboratorioConAcidoBase?: (l: Omit<Laboratorio, 'id'>, a?: Omit<AcidoBase, 'id'>) => void;
|
||||||
onAgregarAcidoBase: (acidoBase: Omit<AcidoBase, 'id'>) => void;
|
onAgregarAcidoBase: (acidoBase: Omit<AcidoBase, 'id'>) => void;
|
||||||
onActualizarAcidoBase: (id: string, datos: Partial<AcidoBase>) => void;
|
onActualizarAcidoBase: (id: string, datos: Partial<AcidoBase>) => void;
|
||||||
@@ -141,6 +146,26 @@ const RANGOS_LABORATORIO: Record<string, { min: number; max: number }> = {
|
|||||||
'Tiempo de Protrombina': { min: 12, max: 14 },
|
'Tiempo de Protrombina': { min: 12, max: 14 },
|
||||||
'KPTT': { min: 30, max: 45 },
|
'KPTT': { min: 30, max: 45 },
|
||||||
'INR': { min: 0.8, max: 1.5 },
|
'INR': { min: 0.8, max: 1.5 },
|
||||||
|
'Colesterol Total': { min: 0, max: 200 },
|
||||||
|
'Colesterol LDL': { min: 0, max: 130 },
|
||||||
|
'Colesterol No HDL': { min: 0, max: 160 },
|
||||||
|
'Colesterol HDL': { min: 40, max: 100 },
|
||||||
|
'Triglicéridos': { min: 0, max: 150 },
|
||||||
|
'Albúmina': { min: 3.5, max: 5.0 },
|
||||||
|
'Calcio Total': { min: 8.5, max: 10.5 },
|
||||||
|
'Fosfatasa Alcalina': { min: 44, max: 147 },
|
||||||
|
'LDH': { min: 140, max: 280 },
|
||||||
|
'Hierro': { min: 50, max: 170 },
|
||||||
|
'Transferrina': { min: 200, max: 360 },
|
||||||
|
'Porcentaje de Saturación de Transferrina': { min: 20, max: 50 },
|
||||||
|
'Ferritina': { min: 10, max: 300 },
|
||||||
|
'Ácido Fólico': { min: 3, max: 17 },
|
||||||
|
'Vitamina B12': { min: 200, max: 900 },
|
||||||
|
'NT-proBNP': { min: 0, max: 125 },
|
||||||
|
'Procalcitonina': { min: 0, max: 0.5 },
|
||||||
|
'Fósforo': { min: 2.5, max: 4.5 },
|
||||||
|
'Magnesio': { min: 1.6, max: 2.6 },
|
||||||
|
'Calcio Iónico': { min: 1.12, max: 1.32 },
|
||||||
};
|
};
|
||||||
|
|
||||||
function calcularEstadoLaboratorio(parametro: string, valor: string): 'Normal' | 'Alto' | 'Bajo' {
|
function calcularEstadoLaboratorio(parametro: string, valor: string): 'Normal' | 'Alto' | 'Bajo' {
|
||||||
@@ -150,6 +175,24 @@ function calcularEstadoLaboratorio(parametro: string, valor: string): 'Normal' |
|
|||||||
const num = parseFloat(valor);
|
const num = parseFloat(valor);
|
||||||
if (isNaN(num)) return 'Normal';
|
if (isNaN(num)) return 'Normal';
|
||||||
|
|
||||||
|
if (parametro === 'Tiempo de Protrombina' && num > 50) {
|
||||||
|
if (num < 70) return 'Bajo';
|
||||||
|
if (num > 100) return 'Alto';
|
||||||
|
return 'Normal';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parametro === 'Calcio Iónico') {
|
||||||
|
if (num < 2) {
|
||||||
|
if (num < 1.12) return 'Bajo';
|
||||||
|
if (num > 1.32) return 'Alto';
|
||||||
|
return 'Normal';
|
||||||
|
} else {
|
||||||
|
if (num < 4.5) return 'Bajo';
|
||||||
|
if (num > 5.6) return 'Alto';
|
||||||
|
return 'Normal';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (num < rango.min) return 'Bajo';
|
if (num < rango.min) return 'Bajo';
|
||||||
if (num > rango.max) return 'Alto';
|
if (num > rango.max) return 'Alto';
|
||||||
return 'Normal';
|
return 'Normal';
|
||||||
@@ -183,6 +226,7 @@ export function HistoriaClinica({
|
|||||||
allCamas,
|
allCamas,
|
||||||
evoluciones,
|
evoluciones,
|
||||||
laboratorios,
|
laboratorios,
|
||||||
|
glucemias,
|
||||||
acidosBase,
|
acidosBase,
|
||||||
cultivos,
|
cultivos,
|
||||||
estudiosComplementarios,
|
estudiosComplementarios,
|
||||||
@@ -193,6 +237,9 @@ export function HistoriaClinica({
|
|||||||
onAgregarLaboratorio,
|
onAgregarLaboratorio,
|
||||||
onActualizarLaboratorio,
|
onActualizarLaboratorio,
|
||||||
onEliminarLaboratorio,
|
onEliminarLaboratorio,
|
||||||
|
onAgregarGlucemia,
|
||||||
|
onActualizarGlucemia,
|
||||||
|
onEliminarGlucemia,
|
||||||
onAgregarAcidoBase,
|
onAgregarAcidoBase,
|
||||||
onActualizarAcidoBase,
|
onActualizarAcidoBase,
|
||||||
onEliminarAcidoBase,
|
onEliminarAcidoBase,
|
||||||
@@ -356,7 +403,7 @@ export function HistoriaClinica({
|
|||||||
<p className="text-gray-500 text-sm">Antecedentes de Enfermedad Actual</p>
|
<p className="text-gray-500 text-sm">Antecedentes de Enfermedad Actual</p>
|
||||||
<p className="font-medium">{internacion.antecedentesEnfermedadActual || 'Sin antecedentes registrados'}</p>
|
<p className="font-medium">{internacion.antecedentesEnfermedadActual || 'Sin antecedentes registrados'}</p>
|
||||||
</div>
|
</div>
|
||||||
<Button variant="outline" className="bg-blue-50 border-blue-300 text-blue-700 hover:bg-blue-100" onClick={async (e) => {
|
<Button variant="outline" className="bg-blue-50 border-blue-300 text-blue-700 hover:bg-blue-100 dark:bg-blue-950/60 dark:border-blue-800 dark:text-blue-300 dark:hover:bg-blue-900" onClick={async (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
try {
|
try {
|
||||||
const { PDFDocument, StandardFonts, rgb } = await import('pdf-lib');
|
const { PDFDocument, StandardFonts, rgb } = await import('pdf-lib');
|
||||||
@@ -502,7 +549,7 @@ export function HistoriaClinica({
|
|||||||
<TabsTrigger value="glucemias">
|
<TabsTrigger value="glucemias">
|
||||||
<Droplet className="h-3 w-3 sm:h-4 sm:w-4" />
|
<Droplet className="h-3 w-3 sm:h-4 sm:w-4" />
|
||||||
<span>Glucemias</span>
|
<span>Glucemias</span>
|
||||||
|
({glucemias.length})
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
<TabsTrigger value="laboratorios">
|
<TabsTrigger value="laboratorios">
|
||||||
<FlaskConical className="h-3 w-3 sm:h-4 sm:w-4" />
|
<FlaskConical className="h-3 w-3 sm:h-4 sm:w-4" />
|
||||||
@@ -538,6 +585,10 @@ export function HistoriaClinica({
|
|||||||
<ScrollBar orientation="horizontal" />
|
<ScrollBar orientation="horizontal" />
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
|
|
||||||
|
<TabsContent value="glucemias" className="mt-4 min-w-0 w-full overflow-hidden">
|
||||||
|
<SeccionGlucemias glucemias={glucemias} patientId={paciente.id} add={onAgregarGlucemia} update={onActualizarGlucemia} del={onEliminarGlucemia} canEdit={canEdit} />
|
||||||
|
</TabsContent>
|
||||||
|
|
||||||
<TabsContent value="laboratorios" className="mt-4 min-w-0 w-full overflow-hidden">
|
<TabsContent value="laboratorios" className="mt-4 min-w-0 w-full overflow-hidden">
|
||||||
<SeccionLaboratorios lab={laboratorios} patientId={paciente.id} add={onAgregarLaboratorio} update={onActualizarLaboratorio} del={onEliminarLaboratorio} addAcidoBase={onAgregarAcidoBase} canEdit={canEdit} />
|
<SeccionLaboratorios lab={laboratorios} patientId={paciente.id} add={onAgregarLaboratorio} update={onActualizarLaboratorio} del={onEliminarLaboratorio} addAcidoBase={onAgregarAcidoBase} canEdit={canEdit} />
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
@@ -570,12 +621,16 @@ export function HistoriaClinica({
|
|||||||
<SeccionIndicaciones
|
<SeccionIndicaciones
|
||||||
recomendaciones={indicadores}
|
recomendaciones={indicadores}
|
||||||
internacionId={internacion.id}
|
internacionId={internacion.id}
|
||||||
|
pacienteId={paciente.id}
|
||||||
add={onAgregarIndicacion}
|
add={onAgregarIndicacion}
|
||||||
update={onActualizarIndicacion}
|
update={onActualizarIndicacion}
|
||||||
del={onEliminarIndicacion}
|
del={onEliminarIndicacion}
|
||||||
movimientos={movimientos}
|
movimientos={movimientos}
|
||||||
onAgregarMovimiento={onAgregarMovimiento}
|
onAgregarMovimiento={onAgregarMovimiento}
|
||||||
canEdit={canEdit}
|
canEdit={canEdit}
|
||||||
|
atbList={atb}
|
||||||
|
addATB={onAgregarATB}
|
||||||
|
updateATB={onActualizarATB}
|
||||||
/>
|
/>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
@@ -609,6 +664,264 @@ export function HistoriaClinica({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function SeccionGlucemias({ glucemias, patientId, add, update, del, canEdit }: {
|
||||||
|
glucemias: Glucemia[];
|
||||||
|
patientId: string;
|
||||||
|
add: (g: Omit<Glucemia, 'id'>) => void;
|
||||||
|
update: (id: string, data: Partial<Glucemia>) => void;
|
||||||
|
del: (id: string) => void;
|
||||||
|
canEdit?: boolean;
|
||||||
|
}) {
|
||||||
|
const [dialog, setDialog] = useState(false);
|
||||||
|
const [evolDialog, setEvolDialog] = useState(false);
|
||||||
|
const [edit, setEdit] = useState<Glucemia | null>(null);
|
||||||
|
const [fecha, setFecha] = useState(new Date().toISOString().split('T')[0]);
|
||||||
|
const [hora, setHora] = useState(new Date().toTimeString().slice(0, 5));
|
||||||
|
const [valor, setValor] = useState('');
|
||||||
|
const [correccion, setCorreccion] = useState('');
|
||||||
|
|
||||||
|
const reset = () => {
|
||||||
|
setEdit(null);
|
||||||
|
setFecha(new Date().toISOString().split('T')[0]);
|
||||||
|
setHora(new Date().toTimeString().slice(0, 5));
|
||||||
|
setValor('');
|
||||||
|
setCorreccion('');
|
||||||
|
};
|
||||||
|
|
||||||
|
const loadEdit = (g: Glucemia) => {
|
||||||
|
setEdit(g);
|
||||||
|
setFecha(g.fecha);
|
||||||
|
setHora(g.hora || '');
|
||||||
|
setValor(g.valor.toString());
|
||||||
|
setCorreccion(g.correccion.toString());
|
||||||
|
setDialog(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleGuardar = () => {
|
||||||
|
const valNum = parseFloat(valor);
|
||||||
|
const corrNum = parseFloat(correccion);
|
||||||
|
|
||||||
|
if (isNaN(valNum)) {
|
||||||
|
toast.error('El valor de glucemia debe ser un número válido');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (isNaN(corrNum)) {
|
||||||
|
toast.error('El valor de corrección debe ser un número válido');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (edit) {
|
||||||
|
update(edit.id, {
|
||||||
|
fecha,
|
||||||
|
hora,
|
||||||
|
valor: valNum,
|
||||||
|
correccion: corrNum
|
||||||
|
});
|
||||||
|
toast.success('Glucemia actualizada correctamente');
|
||||||
|
} else {
|
||||||
|
add({
|
||||||
|
pacienteId: patientId,
|
||||||
|
fecha,
|
||||||
|
hora,
|
||||||
|
valor: valNum,
|
||||||
|
correccion: corrNum
|
||||||
|
});
|
||||||
|
toast.success('Glucemia registrada correctamente');
|
||||||
|
}
|
||||||
|
setDialog(false);
|
||||||
|
reset();
|
||||||
|
};
|
||||||
|
|
||||||
|
const listGlucemias = (glucemias || [])
|
||||||
|
.filter(g => g.pacienteId === patientId)
|
||||||
|
.sort((a, b) => {
|
||||||
|
const dateA = new Date(a.fecha + 'T' + (a.hora || '00:00')).getTime();
|
||||||
|
const dateB = new Date(b.fecha + 'T' + (b.hora || '00:00')).getTime();
|
||||||
|
return dateB - dateA;
|
||||||
|
});
|
||||||
|
|
||||||
|
const datosEvolucion = [...listGlucemias]
|
||||||
|
.reverse()
|
||||||
|
.map(g => ({
|
||||||
|
fechaHora: `${formatDateDDMMYYYY(g.fecha)} ${g.hora || ''}`,
|
||||||
|
glucemia: g.valor,
|
||||||
|
correccion: g.correccion
|
||||||
|
}));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-4 w-full max-w-full min-w-0">
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<div className="flex gap-2">
|
||||||
|
{canEdit && (
|
||||||
|
<Button variant="outline" onClick={() => { reset(); setDialog(true); }}>
|
||||||
|
<Plus className="h-4 w-4 mr-2" />Nueva
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
<Button variant="outline" onClick={() => setEvolDialog(true)} disabled={listGlucemias.length === 0}>
|
||||||
|
<TrendingUp className="h-4 w-4 mr-2" />Evolución
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Dialog open={dialog} onOpenChange={setDialog}>
|
||||||
|
<DialogContent className="sm:max-w-md">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>{edit ? 'Editar' : 'Nueva'} Glucemia</DialogTitle>
|
||||||
|
</DialogHeader>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="flex gap-4">
|
||||||
|
<div className="flex-1">
|
||||||
|
<Label>Fecha</Label>
|
||||||
|
<Input type="date" value={fecha} onChange={e => setFecha(e.target.value)} />
|
||||||
|
</div>
|
||||||
|
<div className="flex-1">
|
||||||
|
<Label>Hora</Label>
|
||||||
|
<Input type="time" value={hora} onChange={e => setHora(e.target.value)} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Label>Glucemia (Mg%)</Label>
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
placeholder="Ej: 110"
|
||||||
|
value={valor}
|
||||||
|
onChange={e => setValor(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Label>Corrección (UI Insulina)</Label>
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
placeholder="Ej: 2"
|
||||||
|
value={correccion}
|
||||||
|
onChange={e => setCorreccion(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-end gap-2 mt-4">
|
||||||
|
<Button variant="outline" onClick={() => setDialog(false)}>
|
||||||
|
<X className="h-4 w-4 mr-2" />Cancelar
|
||||||
|
</Button>
|
||||||
|
<Button onClick={handleGuardar}>
|
||||||
|
<Save className="h-4 w-4 mr-2" />Guardar
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
|
<Dialog open={evolDialog} onOpenChange={setEvolDialog}>
|
||||||
|
<DialogContent className="sm:max-w-2xl max-h-[90vh] overflow-y-auto">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle className="flex items-center gap-2">
|
||||||
|
<TrendingUp className="h-5 w-5" />
|
||||||
|
Evolución de Glucemias
|
||||||
|
</DialogTitle>
|
||||||
|
</DialogHeader>
|
||||||
|
<div className="space-y-4">
|
||||||
|
{datosEvolucion.length > 0 ? (
|
||||||
|
<div className="h-64 bg-muted/30 rounded-lg p-4">
|
||||||
|
<ResponsiveContainer width="100%" height="100%">
|
||||||
|
<LineChart data={datosEvolucion}>
|
||||||
|
<CartesianGrid strokeDasharray="3 3" className="opacity-30" />
|
||||||
|
<XAxis dataKey="fechaHora" tick={{ fontSize: 12 }} />
|
||||||
|
<YAxis yAxisId="left" tick={{ fontSize: 12 }} domain={['auto', 'auto']} />
|
||||||
|
<YAxis yAxisId="right" orientation="right" tick={{ fontSize: 12 }} domain={[0, 'auto']} />
|
||||||
|
<Tooltip
|
||||||
|
contentStyle={{
|
||||||
|
backgroundColor: 'hsl(var(--card))',
|
||||||
|
border: '1px solid hsl(var(--border))',
|
||||||
|
borderRadius: '8px'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Line
|
||||||
|
yAxisId="left"
|
||||||
|
name="Glucemia (mg%)"
|
||||||
|
type="monotone"
|
||||||
|
dataKey="glucemia"
|
||||||
|
stroke="hsl(var(--primary))"
|
||||||
|
strokeWidth={2}
|
||||||
|
dot={{ fill: 'hsl(var(--primary))', r: 4 }}
|
||||||
|
/>
|
||||||
|
<Line
|
||||||
|
yAxisId="right"
|
||||||
|
name="Corrección (UI)"
|
||||||
|
type="monotone"
|
||||||
|
dataKey="correccion"
|
||||||
|
stroke="#f43f5e"
|
||||||
|
strokeWidth={2}
|
||||||
|
dot={{ fill: '#f43f5e', r: 4 }}
|
||||||
|
/>
|
||||||
|
</LineChart>
|
||||||
|
</ResponsiveContainer>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="text-center py-8 text-gray-500">No hay registros de glucemia para graficar</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-end gap-2 mt-4">
|
||||||
|
<Button onClick={() => setEvolDialog(false)}>Cerrar</Button>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
|
{listGlucemias.length === 0 ? (
|
||||||
|
<div className="text-center py-8 text-gray-500">No hay registros de glucemias.</div>
|
||||||
|
) : (
|
||||||
|
<div className="rounded-md border overflow-hidden">
|
||||||
|
<div className="max-h-[400px] overflow-y-auto">
|
||||||
|
<Table>
|
||||||
|
<TableHeader>
|
||||||
|
<TableRow>
|
||||||
|
<TableHead>Fecha y Hora</TableHead>
|
||||||
|
<TableHead>Glucemia (Mg%)</TableHead>
|
||||||
|
<TableHead>Corrección (UI)</TableHead>
|
||||||
|
<TableHead className="w-[100px]">+</TableHead>
|
||||||
|
</TableRow>
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody>
|
||||||
|
{listGlucemias.map(g => (
|
||||||
|
<TableRow key={g.id}>
|
||||||
|
<TableCell>{formatDateDDMMYYYY(g.fecha)} {g.hora || ''}</TableCell>
|
||||||
|
<TableCell className="font-semibold">{g.valor} Mg%</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
{g.correccion > 0 ? (
|
||||||
|
<span className="text-rose-600 font-medium">{g.correccion} UI</span>
|
||||||
|
) : (
|
||||||
|
<span className="text-gray-400">Sin corrección</span>
|
||||||
|
)}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<Button size="sm" variant="ghost" className="h-8 w-8 p-0">
|
||||||
|
<MoreHorizontal className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="end">
|
||||||
|
<DropdownMenuItem onClick={() => loadEdit(g)}>
|
||||||
|
<Edit className="mr-2 h-4 w-4" />
|
||||||
|
Editar
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem onClick={() => del(g.id)} className="text-red-600">
|
||||||
|
<Trash2 className="mr-2 h-4 w-4" />
|
||||||
|
Eliminar
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function SeccionLaboratorios({ lab, patientId, add, update, del, addAcidoBase, canEdit }: {
|
function SeccionLaboratorios({ lab, patientId, add, update, del, addAcidoBase, canEdit }: {
|
||||||
lab: Laboratorio[];
|
lab: Laboratorio[];
|
||||||
patientId: string;
|
patientId: string;
|
||||||
@@ -732,76 +1045,220 @@ function SeccionLaboratorios({ lab, patientId, add, update, del, addAcidoBase, c
|
|||||||
const parseLaboratorioTexto = (texto: string): { resultados: ResultadoLaboratorio[]; observaciones: string } => {
|
const parseLaboratorioTexto = (texto: string): { resultados: ResultadoLaboratorio[]; observaciones: string } => {
|
||||||
const resultados: ResultadoLaboratorio[] = [];
|
const resultados: ResultadoLaboratorio[] = [];
|
||||||
const observacionesExtra: string[] = [];
|
const observacionesExtra: string[] = [];
|
||||||
|
const lipidosEncontrados: { parametro: string; valor: number; unidad: string }[] = [];
|
||||||
|
const ferricosEncontrados: { parametro: string; valor: number; unidad: string }[] = [];
|
||||||
|
const indEncontrados: { parametro: string; valor: number; unidad: string }[] = [];
|
||||||
|
|
||||||
const mapeoParametros: Record<string, { nombre: string; unidad: string; esPrincipal: boolean }> = {
|
const mapeoParametros: { claves: string[]; nombre: string; unidad: string; esPrincipal: boolean; esAdicional?: boolean }[] = [
|
||||||
|
{ claves: ['hematocrito', 'hto'], nombre: 'Hematocrito', unidad: '%', esPrincipal: true },
|
||||||
|
{ claves: ['hemoglobina corpuscular media', 'hcm'], nombre: 'HCM', unidad: 'pg', esPrincipal: false },
|
||||||
|
{ claves: ['hemoglobina', 'hb'], nombre: 'Hemoglobina', unidad: 'g/dL', esPrincipal: true },
|
||||||
|
{ claves: ['volumen corpuscular medio', 'vcm'], nombre: 'VCM', unidad: 'fL', esPrincipal: false },
|
||||||
|
{ claves: ['rdw'], nombre: 'RDW', unidad: '%', esPrincipal: false },
|
||||||
|
{ claves: ['eritroblastos'], nombre: 'Eritroblastos', unidad: '%', esPrincipal: false },
|
||||||
|
{ claves: ['neutrófilos', 'neutrofilos'], nombre: 'Neutrófilos', unidad: '%', esPrincipal: false },
|
||||||
|
{ claves: ['linfocitos'], nombre: 'Linfocitos', unidad: '%', esPrincipal: false },
|
||||||
|
{ claves: ['monocitos'], nombre: 'Monocitos', unidad: '%', esPrincipal: false },
|
||||||
|
{ claves: ['eosinófilos', 'eosinofilos'], nombre: 'Eosinófilos', unidad: '%', esPrincipal: false },
|
||||||
|
{ claves: ['basófilos', 'basofilos'], nombre: 'Basófilos', unidad: '%', esPrincipal: false },
|
||||||
|
{ claves: ['recuento de leucocitos', 'glóbulos blancos', 'globulos blancos', 'leucocitos', 'gb'], nombre: 'Leucocitos', unidad: '10³/µl', esPrincipal: true },
|
||||||
|
{ claves: ['recuento de plaquetas', 'plaquetas', 'plaq'], nombre: 'Plaquetas', unidad: '10³/µl', esPrincipal: true },
|
||||||
|
{ claves: ['volumen plaquetario medio', 'vpm'], nombre: 'VPM', unidad: 'fL', esPrincipal: false },
|
||||||
|
{ claves: ['glucosa', 'glucemia'], nombre: 'Glucemia', unidad: 'mg/dL', esPrincipal: true },
|
||||||
|
{ claves: ['urea'], nombre: 'Urea', unidad: 'mg/dL', esPrincipal: true },
|
||||||
|
{ claves: ['creatinina', 'creat'], nombre: 'Creatinina', unidad: 'mg/dL', esPrincipal: true },
|
||||||
|
{ claves: ['mdrd'], nombre: 'Filtrado Glomerular (MDRD)', unidad: 'ml/min', esPrincipal: false },
|
||||||
|
{ claves: ['sodio', 'na'], nombre: 'Sodio', unidad: 'mEq/L', esPrincipal: true },
|
||||||
|
{ claves: ['potasio', 'k+', 'k'], nombre: 'Potasio', unidad: 'mEq/L', esPrincipal: true },
|
||||||
|
{ claves: ['cloro', 'cl-', 'cl'], nombre: 'Cloro', unidad: 'mEq/L', esPrincipal: true },
|
||||||
|
{ claves: ['bilirrubina total', 'bt'], nombre: 'Bilirrubina Total', unidad: 'mg/dL', esPrincipal: true },
|
||||||
|
{ claves: ['bilirrubina directa', 'bd'], nombre: 'Bilirrubina Directa', unidad: 'mg/dL', esPrincipal: true },
|
||||||
|
{ claves: ['got', 'ast'], nombre: 'GOT', unidad: 'U/L', esPrincipal: true },
|
||||||
|
{ claves: ['gpt', 'alt'], nombre: 'GPT', unidad: 'U/L', esPrincipal: true },
|
||||||
|
{ claves: ['proteínas totales', 'proteinas totales'], nombre: 'Proteínas Totales', unidad: 'g/dL', esPrincipal: false },
|
||||||
|
{ claves: ['tiempo de protrombina', 't.p.', 't.p', 'tp', 'protrombina'], nombre: 'Tiempo de Protrombina', unidad: '%', esPrincipal: true },
|
||||||
|
{ claves: ['rin', 'inr'], nombre: 'INR', unidad: '', esPrincipal: true },
|
||||||
|
{ claves: ['aptt', 'kptt'], nombre: 'KPTT', unidad: 'seg', esPrincipal: true },
|
||||||
|
|
||||||
'hematocrito': { nombre: 'Hematocrito', unidad: '%', esPrincipal: true },
|
// Additional requested determinations
|
||||||
'hemoglobina corpuscular media': { nombre: 'HCM', unidad: 'pg', esPrincipal: false },
|
{ claves: ['colesterol ldl', 'ldl-c', 'ldl', 'colest. ldl', 'colest ldl'], nombre: 'Colesterol LDL', unidad: 'mg/dL', esPrincipal: false, esAdicional: true },
|
||||||
'hemoglobina': { nombre: 'Hemoglobina', unidad: 'g/dL', esPrincipal: true },
|
{ claves: ['colesterol no-hdl', 'colesterol no hdl', 'no-hdl', 'no hdl', 'no_hdl'], nombre: 'Colesterol No HDL', unidad: 'mg/dL', esPrincipal: false, esAdicional: true },
|
||||||
'volumen corpuscular medio': { nombre: 'VCM', unidad: 'fL', esPrincipal: false },
|
{ claves: ['colesterol hdl', 'hdl-c', 'hdl', 'colest. hdl', 'colest hdl'], nombre: 'Colesterol HDL', unidad: 'mg/dL', esPrincipal: false, esAdicional: true },
|
||||||
'rdw': { nombre: 'RDW', unidad: '%', esPrincipal: false },
|
{ claves: ['colesterol total', 'colesterol', 'colest. total', 'col. total', 'colest total'], nombre: 'Colesterol Total', unidad: 'mg/dL', esPrincipal: false, esAdicional: true },
|
||||||
'eritroblastos': { nombre: 'Eritroblastos', unidad: '%', esPrincipal: false },
|
{ claves: ['triglicéridos', 'trigliceridos', 'tg', 'triglicérido', 'triglicerido'], nombre: 'Triglicéridos', unidad: 'mg/dL', esPrincipal: false, esAdicional: true },
|
||||||
'neutrófilos': { nombre: 'Neutrófilos', unidad: '%', esPrincipal: false },
|
{ claves: ['albúmina', 'albumina'], nombre: 'Albúmina', unidad: 'g/dL', esPrincipal: false, esAdicional: true },
|
||||||
'linfocitos': { nombre: 'Linfocitos', unidad: '%', esPrincipal: false },
|
{ claves: ['calcio total', 'calcio', 'ca total', 'ca+', 'ca++'], nombre: 'Calcio Total', unidad: 'mg/dL', esPrincipal: false, esAdicional: true },
|
||||||
'monocitos': { nombre: 'Monocitos', unidad: '%', esPrincipal: false },
|
{ claves: ['fosfatasa alcalina', 'fal'], nombre: 'Fosfatasa Alcalina', unidad: 'U/L', esPrincipal: false, esAdicional: true },
|
||||||
'eosinófilos': { nombre: 'Eosinófilos', unidad: '%', esPrincipal: false },
|
{ claves: ['ldh', 'lactato deshidrogenasa', 'lactatodeshidrogenasa'], nombre: 'LDH', unidad: 'U/L', esPrincipal: false, esAdicional: true },
|
||||||
'basófilos': { nombre: 'Basófilos', unidad: '%', esPrincipal: false },
|
{ claves: ['nt-probnp', 'nt probnp', 'nt_probnp', 'probnp', 'pro-bnp', 'pro bnp'], nombre: 'NT-proBNP', unidad: 'pg/mL', esPrincipal: false, esAdicional: true },
|
||||||
'leucocitos': { nombre: 'Leucocitos', unidad: '10³/µl', esPrincipal: true },
|
{ claves: ['procalcitonina', 'pct'], nombre: 'Procalcitonina', unidad: 'ng/mL', esPrincipal: false, esAdicional: true },
|
||||||
'recuento de leucocitos': { nombre: 'Leucocitos', unidad: '10³/µl', esPrincipal: true },
|
{ claves: ['fósforo', 'fosforo', 'fosfemia'], nombre: 'Fósforo', unidad: 'mg/dL', esPrincipal: false, esAdicional: true },
|
||||||
'recuento de plaquetas': { nombre: 'Plaquetas', unidad: '10³/µl', esPrincipal: true },
|
{ claves: ['magnesio', 'mg', 'magnesemia'], nombre: 'Magnesio', unidad: 'mg/dL', esPrincipal: false, esAdicional: true },
|
||||||
'plaquetas': { nombre: 'Plaquetas', unidad: '10³/µl', esPrincipal: true },
|
{ claves: ['calcio ionico', 'calcio iónico', 'ca ionico', 'ca iónico', 'ca++ ionico', 'ca++ iónico', 'calcio_ionico'], nombre: 'Calcio Iónico', unidad: 'mmol/L', esPrincipal: false, esAdicional: true },
|
||||||
'volumen plaquetario medio': { nombre: 'VPM', unidad: 'fL', esPrincipal: false },
|
|
||||||
'procalcitonina': { nombre: 'Procalcitonina', unidad: 'ng/mL', esPrincipal: false },
|
// Perfil Férrico requested determinations (must put Porcentaje Saturación before Transferrina)
|
||||||
'glucosa': { nombre: 'Glucemia', unidad: 'mg/dL', esPrincipal: true },
|
{ 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'], nombre: 'Porcentaje de Saturación de Transferrina', unidad: '%', esPrincipal: false, esAdicional: true },
|
||||||
'urea': { nombre: 'Urea', unidad: 'mg/dL', esPrincipal: true },
|
{ claves: ['transferrina', 'transferrin'], nombre: 'Transferrina', unidad: 'mg/dL', esPrincipal: false, esAdicional: true },
|
||||||
'creatinina': { nombre: 'Creatinina', unidad: 'mg/dL', esPrincipal: true },
|
{ claves: ['hierro', 'sideremia', 'fe'], nombre: 'Hierro', unidad: 'µg/dL', esPrincipal: false, esAdicional: true },
|
||||||
'mdrd': { nombre: 'Filtrado Glomerular (MDRD)', unidad: 'ml/min', esPrincipal: false },
|
{ claves: ['ferritina', 'ferritin'], nombre: 'Ferritina', unidad: 'ng/mL', esPrincipal: false, esAdicional: true },
|
||||||
'sodio': { nombre: 'Sodio', unidad: 'mEq/L', esPrincipal: true },
|
{ claves: ['acido folico', 'ácido fólico', 'folato', 'folatos', 'folico', 'fólico'], nombre: 'Ácido Fólico', unidad: 'ng/mL', esPrincipal: false, esAdicional: true },
|
||||||
'potasio': { nombre: 'Potasio', unidad: 'mEq/L', esPrincipal: true },
|
{ claves: ['vitamina b12', 'b12', 'vit. b12'], nombre: 'Vitamina B12', unidad: 'pg/mL', esPrincipal: false, esAdicional: true }
|
||||||
'cloro': { nombre: 'Cloro', unidad: 'mEq/L', esPrincipal: true },
|
];
|
||||||
'bilirrubina total': { nombre: 'Bilirrubina Total', unidad: 'mg/dL', esPrincipal: true },
|
|
||||||
'bilirrubina directa': { nombre: 'Bilirrubina Directa', unidad: 'mg/dL', esPrincipal: true },
|
const matchClave = (lineaLower: string, clave: string): boolean => {
|
||||||
'got': { nombre: 'GOT', unidad: 'U/L', esPrincipal: true },
|
if (clave.length > 4) {
|
||||||
'gpt': { nombre: 'GPT', unidad: 'U/L', esPrincipal: true },
|
return lineaLower.includes(clave);
|
||||||
'fosfatasa alcalina': { nombre: 'Fosfatasa Alcalina', unidad: 'U/L', esPrincipal: false },
|
}
|
||||||
'proteínas totales': { nombre: 'Proteínas Totales', unidad: 'g/dL', esPrincipal: false },
|
const escaped = clave.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||||
'albúmina': { nombre: 'Albúmina', unidad: 'g/dL', esPrincipal: false },
|
const regex = new RegExp(`(?:^|[^a-z0-9_])${escaped}(?:$|[^a-z0-9_])`, 'i');
|
||||||
'tiempo de protrombina': { nombre: 'Tiempo de Protrombina', unidad: '%', esPrincipal: true },
|
return regex.test(lineaLower);
|
||||||
'rin': { nombre: 'INR', unidad: '', esPrincipal: true },
|
|
||||||
'aptt': { nombre: 'KPTT', unidad: 'seg', esPrincipal: true },
|
|
||||||
'kptt': { nombre: 'KPTT', unidad: 'seg', esPrincipal: true },
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const lineas = texto.split('\n');
|
const lineas = texto.split('\n');
|
||||||
|
|
||||||
for (const linea of lineas) {
|
for (const linea of lineas) {
|
||||||
const lineaLower = linea.toLowerCase().trim();
|
const lineaLower = linea.toLowerCase().trim();
|
||||||
|
if (!lineaLower) continue;
|
||||||
|
|
||||||
for (const [clave, info] of Object.entries(mapeoParametros)) {
|
for (const group of mapeoParametros) {
|
||||||
if (lineaLower.includes(clave)) {
|
// Skip if this parameter was already found
|
||||||
// buscar primer valor numérico después de cualquier texto (letra o palabra)
|
if (resultados.some(r => r.parametro === group.nombre)) continue;
|
||||||
const match = linea.match(/[a-zA-ZáéíóúñÁÉÍÓÚÑ]+.*?(\d+\.?\d*)/);
|
|
||||||
|
let matchedClave = false;
|
||||||
|
for (const clave of group.claves) {
|
||||||
|
if (group.nombre === 'Colesterol Total' && clave === 'colesterol') {
|
||||||
|
if (lineaLower.includes('ldl') || lineaLower.includes('hdl') || lineaLower.includes('no')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (group.nombre === 'Transferrina' && clave === 'transferrina') {
|
||||||
|
if (lineaLower.includes('saturac') || lineaLower.includes('sat') || lineaLower.includes('%')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (matchClave(lineaLower, clave)) {
|
||||||
|
matchedClave = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matchedClave) {
|
||||||
|
// Extraer primer valor numérico (soporta enteros y decimales con punto o coma)
|
||||||
|
const match = linea.match(/[a-zA-ZáéíóúñÁÉÍÓÚÑ]+.*?(\d+(?:[.,]\d+)?)/);
|
||||||
if (match && match[1]) {
|
if (match && match[1]) {
|
||||||
const valor = parseFloat(match[1].replace(',', '.'));
|
const valor = parseFloat(match[1].replace(',', '.'));
|
||||||
if (!isNaN(valor) && valor > 0 && valor < 1000) {
|
if (!isNaN(valor) && valor > 0 && valor < 1000000) {
|
||||||
const nombreNormalizado = info.nombre;
|
const nombreNormalizado = group.nombre;
|
||||||
let valorFinal = valor;
|
let valorFinal = valor;
|
||||||
if (nombreNormalizado === 'Leucocitos' || nombreNormalizado === 'Plaquetas') {
|
if (nombreNormalizado === 'Leucocitos' || nombreNormalizado === 'Plaquetas') {
|
||||||
valorFinal = valor * 1000;
|
if (valor < 200) valorFinal = valor * 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (info.esPrincipal) {
|
let unidadFinal = group.unidad;
|
||||||
|
if (nombreNormalizado === 'Tiempo de Protrombina') {
|
||||||
|
if (lineaLower.includes('seg') || lineaLower.includes('segundo')) {
|
||||||
|
unidadFinal = 'seg';
|
||||||
|
} else if (lineaLower.includes('%')) {
|
||||||
|
unidadFinal = '%';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (group.esPrincipal || group.esAdicional) {
|
||||||
resultados.push({
|
resultados.push({
|
||||||
parametro: nombreNormalizado,
|
parametro: nombreNormalizado,
|
||||||
valor: valorFinal,
|
valor: valorFinal,
|
||||||
unidad: info.unidad,
|
unidad: unidadFinal,
|
||||||
estado: calcularEstadoLaboratorio(nombreNormalizado, String(valorFinal))
|
estado: calcularEstadoLaboratorio(nombreNormalizado, String(valorFinal))
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const isLipido = [
|
||||||
|
'Colesterol Total',
|
||||||
|
'Colesterol LDL',
|
||||||
|
'Colesterol No HDL',
|
||||||
|
'Colesterol HDL',
|
||||||
|
'Triglicéridos'
|
||||||
|
].includes(nombreNormalizado);
|
||||||
|
|
||||||
|
const isFerrico = [
|
||||||
|
'Hierro',
|
||||||
|
'Transferrina',
|
||||||
|
'Porcentaje de Saturación de Transferrina',
|
||||||
|
'Ferritina',
|
||||||
|
'Ácido Fólico',
|
||||||
|
'Vitamina B12'
|
||||||
|
].includes(nombreNormalizado);
|
||||||
|
|
||||||
|
const isIndependiente = [
|
||||||
|
'Albúmina',
|
||||||
|
'Calcio Total',
|
||||||
|
'Fosfatasa Alcalina',
|
||||||
|
'LDH',
|
||||||
|
'NT-proBNP',
|
||||||
|
'Procalcitonina',
|
||||||
|
'Fósforo',
|
||||||
|
'Magnesio',
|
||||||
|
'Calcio Iónico'
|
||||||
|
].includes(nombreNormalizado);
|
||||||
|
|
||||||
|
if (isLipido) {
|
||||||
|
lipidosEncontrados.push({ parametro: nombreNormalizado, valor: valorFinal, unidad: unidadFinal });
|
||||||
|
} else if (isFerrico) {
|
||||||
|
ferricosEncontrados.push({ parametro: nombreNormalizado, valor: valorFinal, unidad: unidadFinal });
|
||||||
|
} else if (isIndependiente) {
|
||||||
|
indEncontrados.push({ parametro: nombreNormalizado, valor: valorFinal, unidad: unidadFinal });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lipidosEncontrados.length > 0) {
|
||||||
|
observacionesExtra.push('PERFIL LIPIDICO:');
|
||||||
|
const ordenLipidos = ['Colesterol Total', 'Colesterol LDL', 'Colesterol No HDL', 'Colesterol HDL', 'Triglicéridos'];
|
||||||
|
for (const nombre of ordenLipidos) {
|
||||||
|
const item = lipidosEncontrados.find(l => l.parametro === nombre);
|
||||||
|
if (item) {
|
||||||
|
observacionesExtra.push(`- ${item.parametro}: ${item.valor} ${item.unidad}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ferricosEncontrados.length > 0) {
|
||||||
|
if (observacionesExtra.length > 0) {
|
||||||
|
observacionesExtra.push('');
|
||||||
|
}
|
||||||
|
observacionesExtra.push('PERFIL FERRICO:');
|
||||||
|
const ordenFerricos = ['Hierro', 'Transferrina', 'Porcentaje de Saturación de Transferrina', 'Ferritina', 'Ácido Fólico', 'Vitamina B12'];
|
||||||
|
for (const nombre of ordenFerricos) {
|
||||||
|
const item = ferricosEncontrados.find(f => f.parametro === nombre);
|
||||||
|
if (item) {
|
||||||
|
observacionesExtra.push(`- ${item.parametro}: ${item.valor} ${item.unidad}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (indEncontrados.length > 0) {
|
||||||
|
if (observacionesExtra.length > 0) {
|
||||||
|
observacionesExtra.push('');
|
||||||
|
}
|
||||||
|
const ordenInd = [
|
||||||
|
'Albúmina',
|
||||||
|
'Calcio Total',
|
||||||
|
'Fosfatasa Alcalina',
|
||||||
|
'LDH',
|
||||||
|
'NT-proBNP',
|
||||||
|
'Procalcitonina',
|
||||||
|
'Fósforo',
|
||||||
|
'Magnesio',
|
||||||
|
'Calcio Iónico'
|
||||||
|
];
|
||||||
|
for (const nombre of ordenInd) {
|
||||||
|
const item = indEncontrados.find(i => i.parametro === nombre);
|
||||||
|
if (item) {
|
||||||
|
observacionesExtra.push(`${item.parametro}: ${item.valor} ${item.unidad}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -820,7 +1277,7 @@ function SeccionLaboratorios({ lab, patientId, add, update, del, addAcidoBase, c
|
|||||||
let sato2: number | undefined;
|
let sato2: number | undefined;
|
||||||
let lactato: number | undefined;
|
let lactato: number | undefined;
|
||||||
let fio2: number | undefined;
|
let fio2: number | undefined;
|
||||||
let fecha = importFecha;
|
const fecha = importFecha;
|
||||||
|
|
||||||
for (const linea of lineas) {
|
for (const linea of lineas) {
|
||||||
const cleanLinea = linea.replace(/show_chart|list_alt|\t+/g, ' ').replace(/,/g, '.').replace(/\s+/g, ' ').trim().toLowerCase();
|
const cleanLinea = linea.replace(/show_chart|list_alt|\t+/g, ' ').replace(/,/g, '.').replace(/\s+/g, ' ').trim().toLowerCase();
|
||||||
@@ -1346,7 +1803,7 @@ function SeccionEvoluciones({ evos, internacionId, add, update, del, canEdit }:
|
|||||||
setEdit(e);
|
setEdit(e);
|
||||||
setFecha(e.fecha);
|
setFecha(e.fecha);
|
||||||
setHora(e.hora);
|
setHora(e.hora);
|
||||||
setMedico(e.medico);
|
setMedico(e.medico || '');
|
||||||
setTemperatura(e.signosVitales?.temperatura?.toString() || '');
|
setTemperatura(e.signosVitales?.temperatura?.toString() || '');
|
||||||
setPresionSistolica(e.signosVitales?.presionSistolica?.toString() || '');
|
setPresionSistolica(e.signosVitales?.presionSistolica?.toString() || '');
|
||||||
setPresionDiastolica(e.signosVitales?.presionDiastolica?.toString() || '');
|
setPresionDiastolica(e.signosVitales?.presionDiastolica?.toString() || '');
|
||||||
@@ -1368,19 +1825,33 @@ function SeccionEvoluciones({ evos, internacionId, add, update, del, canEdit }:
|
|||||||
|
|
||||||
const evosFiltered = evos.filter(e => e.internacionId === internacionId).sort((a, b) => new Date(b.fecha + 'T' + b.hora).getTime() - new Date(a.fecha + 'T' + a.hora).getTime());
|
const evosFiltered = evos.filter(e => e.internacionId === internacionId).sort((a, b) => new Date(b.fecha + 'T' + b.hora).getTime() - new Date(a.fecha + 'T' + a.hora).getTime());
|
||||||
|
|
||||||
const handle = () => {
|
const handle = async () => {
|
||||||
if (!medico) return;
|
if (!medico.trim()) {
|
||||||
|
toast.error('Ingrese el nombre del médico');
|
||||||
|
return;
|
||||||
|
}
|
||||||
const signosVitales = temperatura || presionSistolica || frecuenciaCardiaca
|
const signosVitales = temperatura || presionSistolica || frecuenciaCardiaca
|
||||||
? { temperatura: temperatura ? parseFloat(temperatura) : undefined, presionSistolica: presionSistolica ? parseInt(presionSistolica) : undefined, presionDiastolica: presionDiastolica ? parseInt(presionDiastolica) : undefined, frecuenciaCardiaca: frecuenciaCardiaca ? parseInt(frecuenciaCardiaca) : undefined, frecuenciaRespiratoria: frecuenciaRespiratoria ? parseInt(frecuenciaRespiratoria) : undefined, saturacionO2: saturacionO2 ? parseInt(saturacionO2) : undefined }
|
? { temperatura: temperatura ? parseFloat(temperatura) : undefined, presionSistolica: presionSistolica ? parseInt(presionSistolica) : undefined, presionDiastolica: presionDiastolica ? parseInt(presionDiastolica) : undefined, frecuenciaCardiaca: frecuenciaCardiaca ? parseInt(frecuenciaCardiaca) : undefined, frecuenciaRespiratoria: frecuenciaRespiratoria ? parseInt(frecuenciaRespiratoria) : undefined, saturacionO2: saturacionO2 ? parseInt(saturacionO2) : undefined }
|
||||||
: undefined;
|
: undefined;
|
||||||
const examenFisico = snc || cardiovascular || respiratorio || abdominal || genitourinario || pielAnexos || soma
|
const examenFisico = snc || cardiovascular || respiratorio || abdominal || genitourinario || pielAnexos || soma
|
||||||
? { SNC: snc || undefined, Cardiovascular: cardiovascular || undefined, Respiratorio: respiratorio || undefined, Abdominal: abdominal || undefined, Genitourinario: genitourinario || undefined, PielAnexos: pielAnexos || undefined, SOMA: soma || undefined }
|
? { SNC: snc || undefined, Cardiovascular: cardiovascular || undefined, Respiratorio: respiratorio || undefined, Abdominal: abdominal || undefined, Genitourinario: genitourinario || undefined, PielAnexos: pielAnexos || undefined, SOMA: soma || undefined }
|
||||||
: undefined;
|
: undefined;
|
||||||
const data = { fecha, hora, medico, signosVitales, examenFisico, novedades: novedades || undefined, comentario: comentario || undefined, pendientes: pendientes || undefined };
|
const data = { fecha, hora, medico: medico.trim(), signosVitales, examenFisico, novedades: novedades || undefined, comentario: comentario || undefined, pendientes: pendientes || undefined };
|
||||||
if (edit) update(edit.id, data);
|
|
||||||
else add({ internacionId, ...data, signosVitales, examenFisico });
|
try {
|
||||||
setDialog(false);
|
if (edit) {
|
||||||
reset();
|
await update(edit.id, data);
|
||||||
|
toast.success('Evolución actualizada correctamente');
|
||||||
|
} else {
|
||||||
|
await add({ internacionId, ...data, signosVitales, examenFisico });
|
||||||
|
toast.success('Evolución agregada correctamente');
|
||||||
|
}
|
||||||
|
setDialog(false);
|
||||||
|
reset();
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error al guardar evolución:', err);
|
||||||
|
toast.error('Error al guardar la evolución');
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -1445,9 +1916,9 @@ function SeccionEvoluciones({ evos, internacionId, add, update, del, canEdit }:
|
|||||||
{canEdit && <Button size="sm" variant="outline" className="text-red-600" onClick={() => del(e.id)}><Trash2 /></Button>}
|
{canEdit && <Button size="sm" variant="outline" className="text-red-600" onClick={() => del(e.id)}><Trash2 /></Button>}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{svText && <div className="bg-blue-50 p-2 rounded-lg"><p className="text-sm font-medium text-blue-800 flex items-center gap-2"><Activity className="h-4 w-4" />Signos Vitales: {svText}</p></div>}
|
{svText && <div className="bg-blue-50 dark:bg-blue-950/60 p-2 rounded-lg border border-transparent dark:border-blue-800/50"><p className="text-sm font-medium text-blue-800 dark:text-blue-300 flex items-center gap-2"><Activity className="h-4 w-4" />Signos Vitales: {svText}</p></div>}
|
||||||
{isExpanded && <>
|
{isExpanded && <>
|
||||||
{e.examenFisico && <div className="bg-green-50 dark:bg-green-900 p-3 rounded-lg border border-green-200 dark:border-green-700"><p className="text-sm font-medium text-green-800 mb-2">Examen Físico:</p><div className="grid grid-cols-2 sm:grid-cols-4 gap-2 text-xs">
|
{e.examenFisico && <div className="bg-green-50 dark:bg-green-950/60 p-3 rounded-lg border border-green-200 dark:border-green-800/50"><p className="text-sm font-medium text-green-800 dark:text-green-300 mb-2">Examen Físico:</p><div className="grid grid-cols-2 sm:grid-cols-4 gap-2 text-xs">
|
||||||
{e.examenFisico.SNC && <div><span className="font-medium">SNC:</span> {e.examenFisico.SNC}</div>}
|
{e.examenFisico.SNC && <div><span className="font-medium">SNC:</span> {e.examenFisico.SNC}</div>}
|
||||||
{e.examenFisico.Cardiovascular && <div><span className="font-medium">CV:</span> {e.examenFisico.Cardiovascular}</div>}
|
{e.examenFisico.Cardiovascular && <div><span className="font-medium">CV:</span> {e.examenFisico.Cardiovascular}</div>}
|
||||||
{e.examenFisico.Respiratorio && <div><span className="font-medium">Resp:</span> {e.examenFisico.Respiratorio}</div>}
|
{e.examenFisico.Respiratorio && <div><span className="font-medium">Resp:</span> {e.examenFisico.Respiratorio}</div>}
|
||||||
@@ -1456,9 +1927,9 @@ function SeccionEvoluciones({ evos, internacionId, add, update, del, canEdit }:
|
|||||||
{e.examenFisico.PielAnexos && <div><span className="font-medium">Piel:</span> {e.examenFisico.PielAnexos}</div>}
|
{e.examenFisico.PielAnexos && <div><span className="font-medium">Piel:</span> {e.examenFisico.PielAnexos}</div>}
|
||||||
{e.examenFisico.SOMA && <div><span className="font-medium">SOMA:</span> {e.examenFisico.SOMA}</div>}
|
{e.examenFisico.SOMA && <div><span className="font-medium">SOMA:</span> {e.examenFisico.SOMA}</div>}
|
||||||
</div></div>}
|
</div></div>}
|
||||||
{e.novedades && <div className="bg-red-50 p-3 rounded-lg border border-red-200 mt-3"><p className="text-sm font-medium text-red-800 mb-1">Novedades:</p><p className="text-sm text-gray-800 whitespace-pre-wrap">{e.novedades}</p></div>}
|
{e.novedades && <div className="bg-red-50 dark:bg-red-950/60 p-3 rounded-lg border border-red-200 dark:border-red-800/50 mt-3"><p className="text-sm font-medium text-red-800 dark:text-red-300 mb-1">Novedades:</p><p className="text-sm text-gray-800 dark:text-gray-200 whitespace-pre-wrap">{e.novedades}</p></div>}
|
||||||
{e.comentario && <div><p className="text-sm font-medium text-gray-700">Comentario:</p><p className="text-sm text-gray-600 whitespace-pre-wrap">{e.comentario}</p></div>}
|
{e.comentario && <div><p className="text-sm font-medium text-gray-700 dark:text-gray-300">Comentario:</p><p className="text-sm text-gray-600 dark:text-gray-400 whitespace-pre-wrap">{e.comentario}</p></div>}
|
||||||
{e.pendientes && <div className="bg-amber-50 p-3 rounded-lg border border-amber-200"><p className="text-sm font-medium text-amber-800 mb-1">Pendientes:</p><p className="text-sm text-amber-700 whitespace-pre-wrap">{e.pendientes}</p></div>}
|
{e.pendientes && <div className="bg-amber-50 dark:bg-amber-950/60 p-3 rounded-lg border border-amber-200 dark:border-amber-800/50"><p className="text-sm font-medium text-amber-800 dark:text-amber-300 mb-1">Pendientes:</p><p className="text-sm text-amber-700 dark:text-amber-300 whitespace-pre-wrap">{e.pendientes}</p></div>}
|
||||||
</>}
|
</>}
|
||||||
</div>
|
</div>
|
||||||
</CardContent></Card>
|
</CardContent></Card>
|
||||||
@@ -1583,7 +2054,7 @@ function SeccionAcidosBase({ ab, patientId, add, update, del, canEdit }: {
|
|||||||
<div><Label>Lactato</Label><Input type="number" step="0.1" value={lactato} onChange={e => setLactato(e.target.value)} placeholder="1.0" /></div>
|
<div><Label>Lactato</Label><Input type="number" step="0.1" value={lactato} onChange={e => setLactato(e.target.value)} placeholder="1.0" /></div>
|
||||||
<div><Label>FiO2</Label><Input type="number" value={fio2} onChange={e => setFio2(e.target.value)} placeholder="21" /></div>
|
<div><Label>FiO2</Label><Input type="number" value={fio2} onChange={e => setFio2(e.target.value)} placeholder="21" /></div>
|
||||||
</div>
|
</div>
|
||||||
{ph && pco2 && hco3 && <div className="bg-blue-50 p-3 rounded-lg border border-blue-200"><p className="text-sm font-medium text-blue-800 flex items-center gap-2"><Activity className="h-4 w-4" />Interpretación automática: {interpretarGasometria()}</p></div>}
|
{ph && pco2 && hco3 && <div className="bg-blue-50 dark:bg-blue-950/60 p-3 rounded-lg border border-blue-200 dark:border-blue-800/50"><p className="text-sm font-medium text-blue-800 dark:text-blue-300 flex items-center gap-2"><Activity className="h-4 w-4" />Interpretación automática: {interpretarGasometria()}</p></div>}
|
||||||
<div><Label>Interpretación / Comentarios</Label><textarea className="w-full p-2 border rounded-md text-sm min-h-[80px]" value={interpretacion} onChange={e => setInterpretacion(e.target.value)} placeholder="Interpretación clínica..." /></div>
|
<div><Label>Interpretación / Comentarios</Label><textarea className="w-full p-2 border rounded-md text-sm min-h-[80px]" value={interpretacion} onChange={e => setInterpretacion(e.target.value)} placeholder="Interpretación clínica..." /></div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-end gap-2 mt-4"><Button variant="outline" onClick={() => setDialog(false)}><X className="h-4 w-4 mr-2" />Cancelar</Button><Button onClick={handle} disabled={!ph || !pco2 || !po2 || !hco3 || !be || !sato2}><Plus className="h-4 w-4 mr-2" />Guardar Gasometría</Button></div>
|
<div className="flex justify-end gap-2 mt-4"><Button variant="outline" onClick={() => setDialog(false)}><X className="h-4 w-4 mr-2" />Cancelar</Button><Button onClick={handle} disabled={!ph || !pco2 || !po2 || !hco3 || !be || !sato2}><Plus className="h-4 w-4 mr-2" />Guardar Gasometría</Button></div>
|
||||||
@@ -1607,7 +2078,7 @@ function SeccionAcidosBase({ ab, patientId, add, update, del, canEdit }: {
|
|||||||
<div><Label>Lactato</Label><Input type="number" step="0.1" value={lactato} onChange={e => setLactato(e.target.value)} placeholder="1.0" /></div>
|
<div><Label>Lactato</Label><Input type="number" step="0.1" value={lactato} onChange={e => setLactato(e.target.value)} placeholder="1.0" /></div>
|
||||||
<div><Label>FiO2</Label><Input type="number" value={fio2} onChange={e => setFio2(e.target.value)} placeholder="0.21" /></div>
|
<div><Label>FiO2</Label><Input type="number" value={fio2} onChange={e => setFio2(e.target.value)} placeholder="0.21" /></div>
|
||||||
</div>
|
</div>
|
||||||
{ph && pco2 && hco3 && <div className="bg-blue-50 p-3 rounded-lg border border-blue-200"><p className="text-sm font-medium text-blue-800 flex items-center gap-2"><Activity className="h-4 w-4" />Interpretación automática: {interpretarGasometria()}</p></div>}
|
{ph && pco2 && hco3 && <div className="bg-blue-50 dark:bg-blue-950/60 p-3 rounded-lg border border-blue-200 dark:border-blue-800/50"><p className="text-sm font-medium text-blue-800 dark:text-blue-300 flex items-center gap-2"><Activity className="h-4 w-4" />Interpretación automática: {interpretarGasometria()}</p></div>}
|
||||||
<div><Label>Interpretación / Comentarios</Label><textarea className="w-full p-2 border rounded-md text-sm min-h-[80px]" value={interpretacion} onChange={e => setInterpretacion(e.target.value)} placeholder="Interpretación clínica..." /></div>
|
<div><Label>Interpretación / Comentarios</Label><textarea className="w-full p-2 border rounded-md text-sm min-h-[80px]" value={interpretacion} onChange={e => setInterpretacion(e.target.value)} placeholder="Interpretación clínica..." /></div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-end gap-2 mt-4"><Button variant="outline" onClick={() => setEditDialog(false)}><X className="h-4 w-4 mr-2" />Cancelar</Button><Button onClick={handleEdit} disabled={!ph || !pco2 || !po2 || !hco3 || !be || !sato2}><Save className="h-4 w-4 mr-2" />Guardar Cambios</Button></div>
|
<div className="flex justify-end gap-2 mt-4"><Button variant="outline" onClick={() => setEditDialog(false)}><X className="h-4 w-4 mr-2" />Cancelar</Button><Button onClick={handleEdit} disabled={!ph || !pco2 || !po2 || !hco3 || !be || !sato2}><Save className="h-4 w-4 mr-2" />Guardar Cambios</Button></div>
|
||||||
@@ -1804,18 +2275,18 @@ function SeccionCultivos({ cults, patient, add, update, del, canEdit }: {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handle = () => { add({ pacienteId: patient.id, fechaToma, protocolo: protocolo || undefined, tipoMuestra, observaciones: observaciones || undefined, estado: 'NAF/Pendiente' }); setDialog(false); reset(); };
|
const handle = () => { add({ pacienteId: patient.id, fechaToma, protocolo, tipoMuestra, observaciones: observaciones || undefined, estado: 'NAF/Pendiente' }); setDialog(false); reset(); };
|
||||||
|
|
||||||
const handleParcial = () => {
|
const handleParcial = () => {
|
||||||
if (!selected) return;
|
if (!selected) return;
|
||||||
update(selected.id, { protocolo: protocolo || undefined, estado: 'Parcial', germen: germen || undefined, sensible: sensible || undefined, resistente: resistente || undefined });
|
update(selected.id, { protocolo, estado: 'Parcial', germen, sensible, resistente });
|
||||||
setResDialog(false);
|
setResDialog(false);
|
||||||
resetRes();
|
resetRes();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDefinitivo = () => {
|
const handleDefinitivo = () => {
|
||||||
if (!selected) return;
|
if (!selected) return;
|
||||||
update(selected.id, { protocolo: protocolo || undefined, fechaResultado, estado: estadoResultado, germen: germen || undefined, sensible: sensible || undefined, resistente: resistente || undefined });
|
update(selected.id, { protocolo, fechaResultado, estado: estadoResultado, germen, sensible, resistente });
|
||||||
setEditDialog(false);
|
setEditDialog(false);
|
||||||
resetRes();
|
resetRes();
|
||||||
};
|
};
|
||||||
@@ -1938,27 +2409,27 @@ function SeccionCultivos({ cults, patient, add, update, del, canEdit }: {
|
|||||||
<Button size="sm" variant="outline" className="text-red-600" onClick={() => del(c.id)}><Trash2 /></Button>
|
<Button size="sm" variant="outline" className="text-red-600" onClick={() => del(c.id)}><Trash2 /></Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{c.protocolo && <p className="text-xs text-gray-500">Protocolo: {c.protocolo}</p>}
|
{c.protocolo && <p className="text-xs text-gray-500 dark:text-gray-400">Protocolo: {c.protocolo}</p>}
|
||||||
{c.observaciones && <p className="text-sm text-gray-600 bg-gray-50 p-2 rounded">{c.observaciones}</p>}
|
{c.observaciones && <p className="text-sm text-gray-600 dark:text-gray-300 bg-gray-50 dark:bg-gray-800 p-2 rounded">{c.observaciones}</p>}
|
||||||
{c.estado === 'Parcial' && c.germen && (
|
{c.estado === 'Parcial' && c.germen && (
|
||||||
<div className="bg-orange-50 p-3 rounded-lg border border-orange-200">
|
<div className="bg-orange-50 dark:bg-orange-950/60 p-3 rounded-lg border border-orange-200 dark:border-orange-800/50">
|
||||||
<p className="text-sm font-medium text-orange-800 flex items-center gap-2"><AlertCircle className="h-4 w-4" />PARCIAL - Germen: {c.germen}</p>
|
<p className="text-sm font-medium text-orange-800 dark:text-orange-300 flex items-center gap-2"><AlertCircle className="h-4 w-4" />PARCIAL - Germen: {c.germen}</p>
|
||||||
{(c.sensible || c.resistente) && (
|
{(c.sensible || c.resistente) && (
|
||||||
<div className="mt-2 space-y-1">
|
<div className="mt-2 space-y-1">
|
||||||
{c.sensible && <p className="text-xs text-orange-700">Sensible: {c.sensible}</p>}
|
{c.sensible && <p className="text-xs text-orange-700 dark:text-orange-300">Sensible: {c.sensible}</p>}
|
||||||
{c.resistente && <p className="text-xs text-orange-700">Resistente: {c.resistente}</p>}
|
{c.resistente && <p className="text-xs text-orange-700 dark:text-orange-300">Resistente: {c.resistente}</p>}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{c.estado === 'Positivo' && c.germen && (
|
{c.estado === 'Positivo' && c.germen && (
|
||||||
<div className="bg-red-50 p-3 rounded-lg border border-red-200">
|
<div className="bg-red-50 dark:bg-red-950/60 p-3 rounded-lg border border-red-200 dark:border-red-800/50">
|
||||||
<p className="text-sm font-medium text-red-800 flex items-center gap-2"><AlertCircle className="h-4 w-4" />Germen: {c.germen}</p>
|
<p className="text-sm font-medium text-red-800 dark:text-red-300 flex items-center gap-2"><AlertCircle className="h-4 w-4" />Germen: {c.germen}</p>
|
||||||
{c.fechaResultado && <p className="text-xs text-red-600 mt-1">Resultado: {c.fechaResultado}</p>}
|
{c.fechaResultado && <p className="text-xs text-red-600 dark:text-red-400 mt-1">Resultado: {c.fechaResultado}</p>}
|
||||||
{(c.sensible || c.resistente) && (
|
{(c.sensible || c.resistente) && (
|
||||||
<div className="mt-2 space-y-2">
|
<div className="mt-2 space-y-2">
|
||||||
{c.sensible && <div><p className="text-xs font-medium text-green-700 mb-1">Sensible a:</p><p className="text-sm text-green-800">{c.sensible}</p></div>}
|
{c.sensible && <div><p className="text-xs font-medium text-green-700 dark:text-green-300 mb-1">Sensible a:</p><p className="text-sm text-green-800 dark:text-green-200">{c.sensible}</p></div>}
|
||||||
{c.resistente && <div><p className="text-xs font-medium text-red-700 mb-1">Resistente a:</p><p className="text-sm text-red-800">{c.resistente}</p></div>}
|
{c.resistente && <div><p className="text-xs font-medium text-red-700 dark:text-red-300 mb-1">Resistente a:</p><p className="text-sm text-red-800 dark:text-red-200">{c.resistente}</p></div>}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -2007,9 +2478,9 @@ function SeccionEstudiosComplementarios({ estudios, internacionId, pacienteId, a
|
|||||||
|
|
||||||
const loadEdit = (e: EstudioComplementario) => {
|
const loadEdit = (e: EstudioComplementario) => {
|
||||||
setEdit(e);
|
setEdit(e);
|
||||||
setFecha(e.fecha);
|
setFecha(e.fecha || new Date().toISOString().split('T')[0]);
|
||||||
setTipo(e.tipo);
|
setTipo(e.tipo || '');
|
||||||
setResultado(e.resultado);
|
setResultado(e.resultado || '');
|
||||||
setDialog(true);
|
setDialog(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -2170,8 +2641,8 @@ function SeccionInterconsultas({ interconsultas, pacienteId, internacionId, add,
|
|||||||
|
|
||||||
const loadEdit = (ic: Interconsulta) => {
|
const loadEdit = (ic: Interconsulta) => {
|
||||||
setEdit(ic);
|
setEdit(ic);
|
||||||
setFecha(ic.fecha);
|
setFecha(ic.fecha || new Date().toISOString().split('T')[0]);
|
||||||
setServicioInterconsultado(ic.servicioInterconsultado);
|
setServicioInterconsultado(ic.servicioInterconsultado || '');
|
||||||
setMotivo(ic.motivo || '');
|
setMotivo(ic.motivo || '');
|
||||||
setRespuestaInterconsulta(ic.respuestaInterconsulta || '');
|
setRespuestaInterconsulta(ic.respuestaInterconsulta || '');
|
||||||
setRespuestaFecha(ic.respuestaFecha || '');
|
setRespuestaFecha(ic.respuestaFecha || '');
|
||||||
@@ -2331,8 +2802,8 @@ function SeccionATB({ atb, internacionId, pacienteId, add, update, del, canEdit
|
|||||||
|
|
||||||
const loadEdit = (a: ATB) => {
|
const loadEdit = (a: ATB) => {
|
||||||
setEdit(a);
|
setEdit(a);
|
||||||
setAntibiotico(a.antibiotico);
|
setAntibiotico(a.antibiotico || '');
|
||||||
setFechaInicio(a.fechaInicio);
|
setFechaInicio(a.fechaInicio || new Date().toISOString().split('T')[0]);
|
||||||
setFechaFinalizacion(a.fechaFinalizacion || '');
|
setFechaFinalizacion(a.fechaFinalizacion || '');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { Input } from '@/components/ui/input';
|
|||||||
import { Label } from '@/components/ui/label';
|
import { Label } from '@/components/ui/label';
|
||||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog';
|
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog';
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||||
|
import { toast } from 'sonner';
|
||||||
import type { Internacion, Paciente, Cama, Area, Evolucion, Laboratorio, Cultivo } from '@/types';
|
import type { Internacion, Paciente, Cama, Area, Evolucion, Laboratorio, Cultivo } from '@/types';
|
||||||
import { formatDateDDMMYYYY } from '@/lib/utils';
|
import { formatDateDDMMYYYY } from '@/lib/utils';
|
||||||
|
|
||||||
@@ -78,20 +79,28 @@ export function Internaciones({
|
|||||||
setInternacionSeleccionada(null);
|
setInternacionSeleccionada(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleIniciarInternacion = () => {
|
const handleIniciarInternacion = async () => {
|
||||||
if (pacienteSeleccionado && camaSeleccionada && areaSeleccionada && motivoConsulta && enfermedadActual && medicoIngresante) {
|
if (pacienteSeleccionado && camaSeleccionada && areaSeleccionada && (diagnosticoIngreso || motivoConsulta) && enfermedadActual && medicoIngresante) {
|
||||||
onIniciarInternacion({
|
try {
|
||||||
pacienteId: pacienteSeleccionado,
|
await onIniciarInternacion({
|
||||||
camaId: camaSeleccionada,
|
pacienteId: pacienteSeleccionado,
|
||||||
areaId: areaSeleccionada,
|
camaId: camaSeleccionada,
|
||||||
diagnosticoIngreso: diagnosticoIngreso || motivoConsulta,
|
areaId: areaSeleccionada,
|
||||||
motivoConsulta,
|
diagnosticoIngreso: diagnosticoIngreso || motivoConsulta,
|
||||||
enfermedadActual,
|
motivoConsulta,
|
||||||
antecedentesEnfermedadActual: antecedentesEnfermedadActual || undefined,
|
enfermedadActual,
|
||||||
medicoIngresante,
|
antecedentesEnfermedadActual: antecedentesEnfermedadActual || undefined,
|
||||||
});
|
medicoIngresante,
|
||||||
resetFormularioNueva();
|
});
|
||||||
setDialogoNuevaAbierto(false);
|
toast.success('Internación iniciada correctamente');
|
||||||
|
resetFormularioNueva();
|
||||||
|
setDialogoNuevaAbierto(false);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error al iniciar internación:', err);
|
||||||
|
toast.error('Error al iniciar la internación');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
toast.error('Por favor complete todos los campos obligatorios');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -286,8 +295,7 @@ export function Internaciones({
|
|||||||
<div>
|
<div>
|
||||||
<Label>Antecedentes de Enfermedad Actual</Label>
|
<Label>Antecedentes de Enfermedad Actual</Label>
|
||||||
<textarea
|
<textarea
|
||||||
className="w-full p-2 border rounded-md tex</p>
|
className="w-full p-2 border rounded-md text-sm min-h-[80px]"
|
||||||
</div>t-sm min-h-[80px]"
|
|
||||||
value={antecedentesEnfermedadActual}
|
value={antecedentesEnfermedadActual}
|
||||||
onChange={(e) => setAntecedentesEnfermedadActual(e.target.value)}
|
onChange={(e) => setAntecedentesEnfermedadActual(e.target.value)}
|
||||||
placeholder="Antecedentes relevantes de la enfermedad actual..."
|
placeholder="Antecedentes relevantes de la enfermedad actual..."
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import {
|
|||||||
UserCog
|
UserCog
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Sheet, SheetContent, SheetTrigger } from '@/components/ui/sheet';
|
import { Sheet, SheetContent, SheetTrigger, SheetTitle, SheetDescription, SheetHeader } from '@/components/ui/sheet';
|
||||||
|
|
||||||
import type { Vista, Usuario } from '@/types';
|
import type { Vista, Usuario } from '@/types';
|
||||||
|
|
||||||
@@ -59,7 +59,7 @@ export function Layout({ children, vistaActual, onCambiarVista, currentUser, onL
|
|||||||
filteredMenuItems.push({ vista: 'usuarios', label: 'Usuarios', icon: UserCog });
|
filteredMenuItems.push({ vista: 'usuarios', label: 'Usuarios', icon: UserCog });
|
||||||
}
|
}
|
||||||
|
|
||||||
const NavContent = () => (
|
const renderNavContent = () => (
|
||||||
<nav className="flex flex-col gap-2">
|
<nav className="flex flex-col gap-2">
|
||||||
{filteredMenuItems.map((item) => {
|
{filteredMenuItems.map((item) => {
|
||||||
const Icon = item.icon;
|
const Icon = item.icon;
|
||||||
@@ -95,7 +95,7 @@ export function Layout({ children, vistaActual, onCambiarVista, currentUser, onL
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 p-4 overflow-auto">
|
<div className="flex-1 p-4 overflow-auto">
|
||||||
<NavContent />
|
{renderNavContent()}
|
||||||
</div>
|
</div>
|
||||||
<div className="p-4 border-t border-gray-200 dark:border-gray-700">
|
<div className="p-4 border-t border-gray-200 dark:border-gray-700">
|
||||||
<div className="text-sm text-gray-600 dark:text-gray-300 mb-2">
|
<div className="text-sm text-gray-600 dark:text-gray-300 mb-2">
|
||||||
@@ -148,6 +148,10 @@ export function Layout({ children, vistaActual, onCambiarVista, currentUser, onL
|
|||||||
</Button>
|
</Button>
|
||||||
</SheetTrigger>
|
</SheetTrigger>
|
||||||
<SheetContent side="right" className="w-64 p-0 dark:bg-gray-800">
|
<SheetContent side="right" className="w-64 p-0 dark:bg-gray-800">
|
||||||
|
<SheetHeader className="sr-only">
|
||||||
|
<SheetTitle>Menú de navegación</SheetTitle>
|
||||||
|
<SheetDescription>Navegación principal de la aplicación</SheetDescription>
|
||||||
|
</SheetHeader>
|
||||||
<div className="p-6 border-b border-gray-200 dark:border-gray-700">
|
<div className="p-6 border-b border-gray-200 dark:border-gray-700">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<span className="font-bold text-lg dark:text-white">Menú</span>
|
<span className="font-bold text-lg dark:text-white">Menú</span>
|
||||||
@@ -158,7 +162,7 @@ export function Layout({ children, vistaActual, onCambiarVista, currentUser, onL
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="p-4">
|
<div className="p-4">
|
||||||
<NavContent />
|
{renderNavContent()}
|
||||||
</div>
|
</div>
|
||||||
<div className="p-4 border-t border-gray-200 dark:border-gray-700 space-y-2">
|
<div className="p-4 border-t border-gray-200 dark:border-gray-700 space-y-2">
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
+12
-12
@@ -79,20 +79,20 @@ export function MapaCamas({
|
|||||||
|
|
||||||
const getEstadoColor = (cama: Cama) => {
|
const getEstadoColor = (cama: Cama) => {
|
||||||
if (cama.estado === 'Disponible') {
|
if (cama.estado === 'Disponible') {
|
||||||
return 'bg-green-100 border-green-300 text-green-800 dark:bg-green-900 dark:border-green-700 dark:text-green-300';
|
return 'bg-emerald-100 border-emerald-300 text-emerald-800 dark:bg-emerald-950/80 dark:border-emerald-700/60 dark:text-emerald-200';
|
||||||
}
|
}
|
||||||
if (cama.estado === 'Reservada') {
|
if (cama.estado === 'Reservada') {
|
||||||
return 'bg-orange-100 border-orange-300 text-orange-800 dark:bg-orange-900 dark:border-orange-700 dark:text-orange-300';
|
return 'bg-orange-100 border-orange-300 text-orange-800 dark:bg-orange-950/80 dark:border-orange-700/60 dark:text-orange-200';
|
||||||
}
|
}
|
||||||
if (cama.estado === 'Ocupada') {
|
if (cama.estado === 'Ocupada') {
|
||||||
// Tipos de aislamiento
|
// Tipos de aislamiento
|
||||||
const aislamientos = ['Aislamiento KPC', 'Aislamiento COVID', 'Aislamiento Clostridium', 'Aislamiento Neutropenico'];
|
const aislamientos = ['Aislamiento KPC', 'Aislamiento COVID', 'Aislamiento Clostridium', 'Aislamiento Neutropenico'];
|
||||||
if (aislamientos.includes(cama.tipo)) {
|
if (aislamientos.includes(cama.tipo)) {
|
||||||
return 'bg-red-100 border-red-300 text-red-800 dark:bg-red-900 dark:border-red-700 dark:text-red-300';
|
return 'bg-red-100 border-red-300 text-red-800 dark:bg-red-950/80 dark:border-red-700/60 dark:text-red-200';
|
||||||
}
|
}
|
||||||
return 'bg-blue-100 border-blue-300 text-blue-800 dark:bg-blue-900 dark:border-blue-700 dark:text-blue-300';
|
return 'bg-blue-100 border-blue-300 text-blue-800 dark:bg-blue-950/80 dark:border-blue-700/60 dark:text-blue-200';
|
||||||
}
|
}
|
||||||
return 'bg-amber-100 border-amber-300 text-amber-800 dark:bg-amber-900 dark:border-amber-700 dark:text-amber-300';
|
return 'bg-amber-100 border-amber-300 text-amber-800 dark:bg-amber-950/80 dark:border-amber-700/60 dark:text-amber-200';
|
||||||
};
|
};
|
||||||
|
|
||||||
const getEstadoIcono = (cama: Cama) => {
|
const getEstadoIcono = (cama: Cama) => {
|
||||||
@@ -317,13 +317,13 @@ export function MapaCamas({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{cama.estado === 'Ocupada' && paciente && internacion && (
|
{cama.estado === 'Ocupada' && paciente && internacion && (
|
||||||
<div className="bg-gray-50 p-4 rounded-lg space-y-2">
|
<div className="bg-gray-50 dark:bg-gray-800/80 border border-transparent dark:border-gray-700/60 p-4 rounded-lg space-y-2">
|
||||||
<p className="font-medium">Paciente:</p>
|
<p className="font-medium text-gray-900 dark:text-gray-100">Paciente:</p>
|
||||||
<p className="text-lg">{paciente.apellido}, {paciente.nombre}</p>
|
<p className="text-lg font-semibold text-gray-900 dark:text-gray-100">{paciente.apellido}, {paciente.nombre}</p>
|
||||||
<p className="text-sm text-gray-500">DNI: {paciente.dni}</p>
|
<p className="text-sm text-gray-500 dark:text-gray-400">DNI: {paciente.dni}</p>
|
||||||
<p className="text-sm text-gray-500">Ingreso: {internacion.fechaIngresoClinica ? formatDateDDMMYYYY(internacion.fechaIngresoClinica) : 'N/A'}</p>
|
<p className="text-sm text-gray-500 dark:text-gray-400">Ingreso: {internacion.fechaIngresoClinica ? formatDateDDMMYYYY(internacion.fechaIngresoClinica) : 'N/A'}</p>
|
||||||
<p className="text-sm text-gray-500">Diagnóstico: {internacion.diagnosticoIngreso}</p>
|
<p className="text-sm text-gray-500 dark:text-gray-400">Diagnóstico: {internacion.diagnosticoIngreso}</p>
|
||||||
<p className="text-sm text-gray-500">Médico: {internacion.medicoIngresante}</p>
|
<p className="text-sm text-gray-500 dark:text-gray-400">Médico: {internacion.medicoIngresante}</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
+114
-89
@@ -1,11 +1,12 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { ArrowLeft, Save, X, Bed, Calendar, Stethoscope, User, ClipboardList } from 'lucide-react';
|
import { ArrowLeft, Save, X, Bed, Calendar, Stethoscope, User, Loader2 } from 'lucide-react';
|
||||||
import { Card, CardContent } from '@/components/ui/card';
|
import { Card, CardContent } from '@/components/ui/card';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { Label } from '@/components/ui/label';
|
import { Label } from '@/components/ui/label';
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
|
import { toast } from 'sonner';
|
||||||
import type { Paciente, Cama, Area, Internacion } from '@/types';
|
import type { Paciente, Cama, Area, Internacion } from '@/types';
|
||||||
|
|
||||||
interface NuevoIngresoProps {
|
interface NuevoIngresoProps {
|
||||||
@@ -24,7 +25,6 @@ export function NuevoIngreso({ pacientes, camas, areas, onIniciarInternacion, on
|
|||||||
const [pacienteSeleccionado, setPacienteSeleccionado] = useState<string>('');
|
const [pacienteSeleccionado, setPacienteSeleccionado] = useState<string>('');
|
||||||
const [busqueda, setBusqueda] = useState('');
|
const [busqueda, setBusqueda] = useState('');
|
||||||
const [camaSeleccionada, setCamaSeleccionada] = useState('');
|
const [camaSeleccionada, setCamaSeleccionada] = useState('');
|
||||||
const [areaSeleccionada, setAreaSeleccionada] = useState('');
|
|
||||||
const [camaInput, setCamaInput] = useState('');
|
const [camaInput, setCamaInput] = useState('');
|
||||||
const [modoCama, setModoCama] = useState<'seleccionar' | 'escribir'>('seleccionar');
|
const [modoCama, setModoCama] = useState<'seleccionar' | 'escribir'>('seleccionar');
|
||||||
const [medico, setMedico] = useState('');
|
const [medico, setMedico] = useState('');
|
||||||
@@ -36,12 +36,13 @@ export function NuevoIngreso({ pacientes, camas, areas, onIniciarInternacion, on
|
|||||||
const [antecedentesEnfermedadActual, setAntecedentesEnfermedadActual] = useState('');
|
const [antecedentesEnfermedadActual, setAntecedentesEnfermedadActual] = useState('');
|
||||||
const [apache, setApache] = useState('');
|
const [apache, setApache] = useState('');
|
||||||
const [derivacion, setDerivacion] = useState('');
|
const [derivacion, setDerivacion] = useState('');
|
||||||
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||||
|
|
||||||
const resetFormulario = () => {
|
const resetFormulario = () => {
|
||||||
setPacienteSeleccionado('');
|
setPacienteSeleccionado('');
|
||||||
setBusqueda('');
|
setBusqueda('');
|
||||||
setCamaSeleccionada('');
|
setCamaSeleccionada('');
|
||||||
setAreaSeleccionada('');
|
setCamaInput('');
|
||||||
setMedico('');
|
setMedico('');
|
||||||
setFechaIngresoHospital('');
|
setFechaIngresoHospital('');
|
||||||
setFechaIngresoClinica('');
|
setFechaIngresoClinica('');
|
||||||
@@ -53,12 +54,10 @@ export function NuevoIngreso({ pacientes, camas, areas, onIniciarInternacion, on
|
|||||||
setDerivacion('');
|
setDerivacion('');
|
||||||
};
|
};
|
||||||
|
|
||||||
const areasDisponibles = areas.filter(a => {
|
const normalizeStr = (str?: string) => (str || '').normalize('NFD').replace(/[\u0300-\u036f]/g, '').trim().toLowerCase();
|
||||||
const nombre = (a.nombre || '').trim().toLowerCase();
|
|
||||||
return nombre !== 'fuera de area';
|
const areaFueraDeArea = areas.find(a => normalizeStr(a.nombre) === 'fuera de area');
|
||||||
});
|
const areaFueraDeAreaId = areaFueraDeArea?.id || areas[0]?.id || 'fuera-de-area';
|
||||||
const areaFueraDeArea = areas.find(a => (a.nombre || '').trim().toLowerCase() === 'fuera de area');
|
|
||||||
const areaFueraDeAreaId = areaFueraDeArea?.id || '';
|
|
||||||
|
|
||||||
const pacientesSinInternar = pacientes;
|
const pacientesSinInternar = pacientes;
|
||||||
|
|
||||||
@@ -106,26 +105,50 @@ export function NuevoIngreso({ pacientes, camas, areas, onIniciarInternacion, on
|
|||||||
});
|
});
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
if (!pacienteSeleccionado || !medico || !diagnosticoIngreso || !enfermedadActual) {
|
// Validaciones explícitas con feedback claro
|
||||||
alert('Por favor complete los campos obligatorios');
|
if (!pacienteSeleccionado) {
|
||||||
|
toast.error('Debe seleccionar un paciente de la lista');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let camaId = '';
|
if (modoCama === 'seleccionar' && !camaSeleccionada) {
|
||||||
let newAreaId = '';
|
toast.error('Debe seleccionar una cama para el paciente');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (modoCama === 'escribir') {
|
if (modoCama === 'escribir' && !camaInput.trim()) {
|
||||||
if (!camaInput.trim()) {
|
toast.error('Debe ingresar el número de cama');
|
||||||
alert('Ingrese el número de cama');
|
return;
|
||||||
return;
|
}
|
||||||
}
|
|
||||||
const numeroCama = camaInput.trim();
|
if (!diagnosticoIngreso.trim()) {
|
||||||
const camaExistente = camas.find(c => c.numero === numeroCama);
|
toast.error('Debe completar el Diagnóstico de Ingreso');
|
||||||
if (camaExistente) {
|
return;
|
||||||
camaId = camaExistente.id;
|
}
|
||||||
newAreaId = camaExistente.areaId || '';
|
|
||||||
} else if (onAgregarCama) {
|
if (!enfermedadActual.trim()) {
|
||||||
try {
|
toast.error('Debe completar la Enfermedad Actual');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!medico.trim()) {
|
||||||
|
toast.error('Debe ingresar el nombre del Médico Ingresante');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsSubmitting(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
let camaId = '';
|
||||||
|
let newAreaId = '';
|
||||||
|
|
||||||
|
if (modoCama === 'escribir') {
|
||||||
|
const numeroCama = camaInput.trim();
|
||||||
|
const camaExistente = camas.find(c => c.numero === numeroCama);
|
||||||
|
if (camaExistente) {
|
||||||
|
camaId = camaExistente.id;
|
||||||
|
newAreaId = camaExistente.areaId || areaFueraDeAreaId;
|
||||||
|
} else if (onAgregarCama) {
|
||||||
camaId = await onAgregarCama({
|
camaId = await onAgregarCama({
|
||||||
numero: numeroCama,
|
numero: numeroCama,
|
||||||
areaId: areaFueraDeAreaId,
|
areaId: areaFueraDeAreaId,
|
||||||
@@ -133,87 +156,87 @@ export function NuevoIngreso({ pacientes, camas, areas, onIniciarInternacion, on
|
|||||||
estado: 'Ocupada'
|
estado: 'Ocupada'
|
||||||
});
|
});
|
||||||
newAreaId = areaFueraDeAreaId;
|
newAreaId = areaFueraDeAreaId;
|
||||||
} catch (err) {
|
} else {
|
||||||
console.error('Error al crear cama:', err);
|
toast.error('No se puede crear la cama');
|
||||||
alert('Error al crear la cama');
|
setIsSubmitting(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
alert('No se puede agregar una nueva cama');
|
const camaElegida = sortedCamas.find(c => c.id === camaSeleccionada);
|
||||||
return;
|
if (!camaElegida) {
|
||||||
|
toast.error('Cama no encontrada');
|
||||||
|
setIsSubmitting(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
camaId = camaSeleccionada;
|
||||||
|
newAreaId = camaElegida.areaId || areaFueraDeAreaId;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
if (!camaSeleccionada) {
|
await onIniciarInternacion({
|
||||||
alert('Seleccione una cama');
|
pacienteId: pacienteSeleccionado,
|
||||||
return;
|
camaId: camaId,
|
||||||
}
|
areaId: newAreaId,
|
||||||
const camaElegida = sortedCamas.find(c => c.id === camaSeleccionada);
|
medicoIngresante: medico.trim(),
|
||||||
if (!camaElegida) {
|
diagnosticoIngreso: diagnosticoIngreso.trim(),
|
||||||
alert('Cama no encontrada');
|
motivoConsulta: motivoConsulta.trim() || undefined,
|
||||||
return;
|
enfermedadActual: enfermedadActual.trim(),
|
||||||
}
|
antecedentesEnfermedadActual: antecedentesEnfermedadActual.trim() || undefined,
|
||||||
newAreaId = camaElegida.areaId || areaFueraDeAreaId;
|
fechaIngresoHospital: fechaIngresoHospital || undefined,
|
||||||
if (!newAreaId) {
|
fechaIngresoClinica: fechaIngresoClinica || undefined,
|
||||||
alert('La cama no tiene un área asignada');
|
apache: apache.trim() || undefined,
|
||||||
return;
|
derivacion: derivacion.trim() || undefined,
|
||||||
}
|
});
|
||||||
camaId = camaSeleccionada;
|
|
||||||
|
toast.success('Ingreso registrado con éxito');
|
||||||
|
resetFormulario();
|
||||||
|
onVolver();
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error al guardar ingreso:', err);
|
||||||
|
toast.error('Ocurrió un error al guardar el ingreso');
|
||||||
|
} finally {
|
||||||
|
setIsSubmitting(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
onIniciarInternacion({
|
|
||||||
pacienteId: pacienteSeleccionado,
|
|
||||||
camaId: camaId,
|
|
||||||
areaId: newAreaId,
|
|
||||||
medicoIngresante: medico,
|
|
||||||
diagnosticoIngreso,
|
|
||||||
motivoConsulta: motivoConsulta || undefined,
|
|
||||||
enfermedadActual,
|
|
||||||
antecedentesEnfermedadActual: antecedentesEnfermedadActual || undefined,
|
|
||||||
fechaIngresoHospital: fechaIngresoHospital || undefined,
|
|
||||||
fechaIngresoClinica: fechaIngresoClinica || undefined,
|
|
||||||
apache: apache || undefined,
|
|
||||||
derivacion: derivacion || undefined,
|
|
||||||
});
|
|
||||||
|
|
||||||
resetFormulario();
|
|
||||||
onVolver();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const selectedPaciente = pacientes.find(p => p.id === pacienteSeleccionado);
|
const selectedPaciente = pacientes.find(p => p.id === pacienteSeleccionado);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6 dark:text-white">
|
<div className="space-y-6 dark:text-white">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-4">
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-3">
|
||||||
<Button variant="ghost" size="icon" onClick={onVolver}>
|
<Button variant="ghost" size="icon" onClick={onVolver} disabled={isSubmitting} className="shrink-0">
|
||||||
<ArrowLeft className="h-5 w-5" />
|
<ArrowLeft className="h-5 w-5" />
|
||||||
</Button>
|
</Button>
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-2xl font-bold text-gray-900 dark:text-white flex items-center gap-2">
|
<h1 className="text-xl sm:text-2xl font-bold text-gray-900 dark:text-white flex items-center gap-2">
|
||||||
<User className="h-6 w-6 text-blue-600" />
|
<User className="h-6 w-6 text-blue-600" />
|
||||||
Nuevo Ingreso
|
Nuevo Ingreso
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-gray-500 dark:text-gray-400">Formulario de internación</p>
|
<p className="text-sm text-gray-500 dark:text-gray-400">Formulario de internación</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2">
|
<div className="flex items-center gap-2 w-full sm:w-auto justify-end">
|
||||||
<Button variant="outline" onClick={() => { resetFormulario(); onVolver(); }}>
|
<Button variant="outline" className="flex-1 sm:flex-initial" onClick={() => { resetFormulario(); onVolver(); }} disabled={isSubmitting}>
|
||||||
<X className="h-4 w-4 mr-2" />
|
<X className="h-4 w-4 mr-2" />
|
||||||
Cancelar
|
Cancelar
|
||||||
</Button>
|
</Button>
|
||||||
<Button className="bg-blue-600 hover:bg-blue-700" onClick={handleSubmit}>
|
<Button className="bg-blue-600 hover:bg-blue-700 flex-1 sm:flex-initial" onClick={handleSubmit} disabled={isSubmitting}>
|
||||||
<Save className="h-4 w-4 mr-2" />
|
{isSubmitting ? (
|
||||||
|
<Loader2 className="h-4 w-4 mr-2 animate-spin" />
|
||||||
|
) : (
|
||||||
|
<Save className="h-4 w-4 mr-2" />
|
||||||
|
)}
|
||||||
Guardar Ingreso
|
Guardar Ingreso
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
{/* Columna 1: Datos del Paciente */}
|
{/* Columna 1: Datos del Paciente */}
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<Card className="min-h-[200px]">
|
<Card className="min-h-[200px]">
|
||||||
<CardContent className="pt-6 space-y-4">
|
<CardContent className="pt-6 space-y-4">
|
||||||
<h3 className="font-semibold text-gray-900 flex items-center gap-2">
|
<h3 className="font-semibold text-gray-900 dark:text-white flex items-center gap-2">
|
||||||
<User className="h-4 w-4" />
|
<User className="h-4 w-4" />
|
||||||
Datos del Paciente
|
Datos del Paciente
|
||||||
</h3>
|
</h3>
|
||||||
@@ -226,11 +249,11 @@ export function NuevoIngreso({ pacientes, camas, areas, onIniciarInternacion, on
|
|||||||
value={busqueda}
|
value={busqueda}
|
||||||
onChange={(e) => setBusqueda(e.target.value)}
|
onChange={(e) => setBusqueda(e.target.value)}
|
||||||
/>
|
/>
|
||||||
<div className="max-h-48 overflow-auto border rounded-md">
|
<div className="max-h-48 overflow-auto border rounded-md divide-y dark:divide-gray-800">
|
||||||
{(() => {
|
{(() => {
|
||||||
const q = busqueda.trim().toLowerCase();
|
const q = busqueda.trim().toLowerCase();
|
||||||
if (!q) {
|
if (!q) {
|
||||||
return <div className="p-2 text-sm text-gray-500">Escriba para buscar</div>;
|
return <div className="p-3 text-sm text-gray-500">Escriba para buscar</div>;
|
||||||
}
|
}
|
||||||
const matches = pacientesSinInternar.filter(p =>
|
const matches = pacientesSinInternar.filter(p =>
|
||||||
p.apellido.toLowerCase().includes(q) ||
|
p.apellido.toLowerCase().includes(q) ||
|
||||||
@@ -238,27 +261,27 @@ export function NuevoIngreso({ pacientes, camas, areas, onIniciarInternacion, on
|
|||||||
p.dni.includes(q)
|
p.dni.includes(q)
|
||||||
);
|
);
|
||||||
if (matches.length === 0) {
|
if (matches.length === 0) {
|
||||||
return <div className="p-2 text-sm text-gray-500">Sin resultados</div>;
|
return <div className="p-3 text-sm text-gray-500">Sin resultados</div>;
|
||||||
}
|
}
|
||||||
return matches.map(p => (
|
return matches.map(p => (
|
||||||
<button
|
<button
|
||||||
key={p.id}
|
key={p.id}
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setPacienteSeleccionado(p.id)}
|
onClick={() => setPacienteSeleccionado(p.id)}
|
||||||
className="w-full text-left p-2 hover:bg-gray-50 text-sm"
|
className="w-full text-left p-3 hover:bg-gray-50 dark:hover:bg-gray-800 text-sm transition-colors"
|
||||||
>
|
>
|
||||||
{p.apellido}, {p.nombre} — DNI: {p.dni}
|
<span className="font-medium">{p.apellido}, {p.nombre}</span> — DNI: {p.dni}
|
||||||
</button>
|
</button>
|
||||||
));
|
));
|
||||||
})()}
|
})()}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex items-center gap-2 flex-wrap">
|
<div className="flex items-center gap-2 flex-wrap pt-1">
|
||||||
<Badge className="bg-blue-100 text-blue-800">
|
<Badge className="bg-blue-100 text-blue-800 dark:bg-blue-950/80 dark:text-blue-300 dark:border dark:border-blue-800 text-sm py-1 px-3">
|
||||||
{selectedPaciente?.apellido}, {selectedPaciente?.nombre}
|
{selectedPaciente?.apellido}, {selectedPaciente?.nombre}
|
||||||
</Badge>
|
</Badge>
|
||||||
<Badge variant="outline">DNI: {selectedPaciente?.dni}</Badge>
|
<Badge variant="outline" className="text-sm py-1 px-3">DNI: {selectedPaciente?.dni}</Badge>
|
||||||
<Button size="sm" variant="ghost" onClick={() => { setPacienteSeleccionado(''); setBusqueda(''); }}>
|
<Button size="sm" variant="ghost" onClick={() => { setPacienteSeleccionado(''); setBusqueda(''); }}>
|
||||||
Cambiar
|
Cambiar
|
||||||
</Button>
|
</Button>
|
||||||
@@ -272,19 +295,21 @@ export function NuevoIngreso({ pacientes, camas, areas, onIniciarInternacion, on
|
|||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<Card className="min-h-[200px]">
|
<Card className="min-h-[200px]">
|
||||||
<CardContent className="pt-6 space-y-4">
|
<CardContent className="pt-6 space-y-4">
|
||||||
<h3 className="font-semibold text-gray-900 flex items-center gap-2">
|
<h3 className="font-semibold text-gray-900 dark:text-white flex items-center gap-2">
|
||||||
<Bed className="h-4 w-4" />
|
<Bed className="h-4 w-4" />
|
||||||
Asignación de Cama y Área
|
Asignación de Cama y Área
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<div className="flex gap-2 mb-4">
|
<div className="flex flex-col sm:flex-row gap-2 mb-4">
|
||||||
<Button
|
<Button
|
||||||
|
className="flex-1 text-xs sm:text-sm"
|
||||||
variant={modoCama === 'seleccionar' ? 'default' : 'outline'}
|
variant={modoCama === 'seleccionar' ? 'default' : 'outline'}
|
||||||
onClick={() => setModoCama('seleccionar')}
|
onClick={() => setModoCama('seleccionar')}
|
||||||
>
|
>
|
||||||
Seleccionar Cama
|
Seleccionar Cama
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
|
className="flex-1 text-xs sm:text-sm"
|
||||||
variant={modoCama === 'escribir' ? 'default' : 'outline'}
|
variant={modoCama === 'escribir' ? 'default' : 'outline'}
|
||||||
onClick={() => setModoCama('escribir')}
|
onClick={() => setModoCama('escribir')}
|
||||||
>
|
>
|
||||||
@@ -318,7 +343,7 @@ export function NuevoIngreso({ pacientes, camas, areas, onIniciarInternacion, on
|
|||||||
value={camaInput}
|
value={camaInput}
|
||||||
onChange={(e) => setCamaInput(e.target.value)}
|
onChange={(e) => setCamaInput(e.target.value)}
|
||||||
/>
|
/>
|
||||||
<p className="text-xs text-gray-500 mt-1">
|
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
||||||
Esta cama será creada como "Fuera de área"
|
Esta cama será creada como "Fuera de área"
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -331,7 +356,7 @@ export function NuevoIngreso({ pacientes, camas, areas, onIniciarInternacion, on
|
|||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<Card>
|
<Card>
|
||||||
<CardContent className="pt-6 space-y-4">
|
<CardContent className="pt-6 space-y-4">
|
||||||
<h3 className="font-semibold text-gray-900 flex items-center gap-2">
|
<h3 className="font-semibold text-gray-900 dark:text-white flex items-center gap-2">
|
||||||
<Calendar className="h-4 w-4" />
|
<Calendar className="h-4 w-4" />
|
||||||
Datos de Ingreso
|
Datos de Ingreso
|
||||||
</h3>
|
</h3>
|
||||||
@@ -379,7 +404,7 @@ export function NuevoIngreso({ pacientes, camas, areas, onIniciarInternacion, on
|
|||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<Card>
|
<Card>
|
||||||
<CardContent className="pt-6 space-y-4">
|
<CardContent className="pt-6 space-y-4">
|
||||||
<h3 className="font-semibold text-gray-900">
|
<h3 className="font-semibold text-gray-900 dark:text-white">
|
||||||
Datos Clínicos
|
Datos Clínicos
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
@@ -429,7 +454,7 @@ export function NuevoIngreso({ pacientes, camas, areas, onIniciarInternacion, on
|
|||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<Card>
|
<Card>
|
||||||
<CardContent className="pt-6 space-y-4">
|
<CardContent className="pt-6 space-y-4">
|
||||||
<h3 className="font-semibold text-gray-900 flex items-center gap-2">
|
<h3 className="font-semibold text-gray-900 dark:text-white flex items-center gap-2">
|
||||||
<Stethoscope className="h-4 w-4" />
|
<Stethoscope className="h-4 w-4" />
|
||||||
Médico Ingresante
|
Médico Ingresante
|
||||||
</h3>
|
</h3>
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ export function Pacientes({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleGuardar = () => {
|
const handleGuardar = () => {
|
||||||
if (!nombre || !apellido || !dni || !fechaNacimiento || !telefono) return;
|
if (!nombre || !apellido || !dni || !fechaNacimiento) return;
|
||||||
|
|
||||||
const datos = {
|
const datos = {
|
||||||
nombre,
|
nombre,
|
||||||
@@ -94,7 +94,7 @@ export function Pacientes({
|
|||||||
dni,
|
dni,
|
||||||
fechaNacimiento,
|
fechaNacimiento,
|
||||||
sexo,
|
sexo,
|
||||||
telefono,
|
telefono: telefono || undefined,
|
||||||
email: email || undefined,
|
email: email || undefined,
|
||||||
direccion: direccion || undefined,
|
direccion: direccion || undefined,
|
||||||
grupoSanguineo: grupoSanguineo || undefined,
|
grupoSanguineo: grupoSanguineo || undefined,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
import { toast } from 'sonner';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Card, CardContent } from '@/components/ui/card';
|
import { Card, CardContent } from '@/components/ui/card';
|
||||||
@@ -8,26 +9,32 @@ import { Label } from '@/components/ui/label';
|
|||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
|
||||||
import { Plus, Pencil, Trash2, X } from 'lucide-react';
|
import { Plus, Pencil, Trash2, X } from 'lucide-react';
|
||||||
import type { Indicacion, IndicacionTipo, ViaAdministracion, TipoPlanHidratacion, TipoInsulina } from '@/types';
|
import type { Indicacion, IndicacionTipo, ViaAdministracion, TipoPlanHidratacion, TipoInsulina, ATB } from '@/types';
|
||||||
|
|
||||||
export function SeccionIndicaciones({ recomendaciones, internacionId, add, update, del, movimientos, onAgregarMovimiento, canEdit }: {
|
export function SeccionIndicaciones({ recomendaciones, internacionId, pacienteId, add, update, del, movimientos, onAgregarMovimiento, canEdit, atbList = [], addATB, updateATB }: {
|
||||||
recomendaciones: Indicacion[];
|
recomendaciones: Indicacion[];
|
||||||
internacionId: string;
|
internacionId: string;
|
||||||
add: (i: Omit<Indicacion, 'id'>) => void;
|
pacienteId?: string;
|
||||||
|
add: (i: Omit<Indicacion, 'id'>) => Promise<unknown>;
|
||||||
update: (id: string, datos: Partial<Indicacion>) => void;
|
update: (id: string, datos: Partial<Indicacion>) => void;
|
||||||
del: (id: string) => void;
|
del: (id: string) => void;
|
||||||
movimientos?: any[];
|
movimientos?: unknown[];
|
||||||
onAgregarMovimiento?: (m: any) => void;
|
onAgregarMovimiento?: (m: unknown) => void;
|
||||||
canEdit?: boolean;
|
canEdit?: boolean;
|
||||||
|
atbList?: ATB[];
|
||||||
|
addATB?: (a: Omit<ATB, 'id'>) => Promise<unknown>;
|
||||||
|
updateATB?: (id: string, datos: Partial<ATB>) => void;
|
||||||
}) {
|
}) {
|
||||||
const list: Indicacion[] = Array.isArray(recomendaciones) ? recomendaciones : [];
|
const list: Indicacion[] = Array.isArray(recomendaciones) ? recomendaciones : [];
|
||||||
const movs: any[] = Array.isArray(movimientos) ? movimientos : [];
|
const movs = Array.isArray(movimientos) ? movimientos : [];
|
||||||
const [dialog, setDialog] = useState(false);
|
const [dialog, setDialog] = useState(false);
|
||||||
const [edit, setEdit] = useState<Indicacion | null>(null);
|
const [edit, setEdit] = useState<Indicacion | null>(null);
|
||||||
|
const [deleteConfirmInd, setDeleteConfirmInd] = useState<Indicacion | null>(null);
|
||||||
|
const [deleteMedico, setDeleteMedico] = useState<string>('');
|
||||||
|
|
||||||
const formatIndicacion = (i: Indicacion): string => {
|
const formatIndicacion = (i: Indicacion): string => {
|
||||||
if (i.tipo === 'Farmacologica' || i.tipo === 'Farmacologica Profilactica') return `${i.droga || ''} ${i.dosis || ''} ${i.frecuenciaHoras ? `c/${i.frecuenciaHoras}hs` : ''} ${i.via || ''}`.trim();
|
if (i.tipo === 'Farmacologica' || i.tipo === 'Farmacologica Profilactica' || i.tipo === 'Farmacologica Antibiótico') return `${i.droga || ''} ${i.dosis || ''} ${i.frecuenciaHoras ? `c/${i.frecuenciaHoras}hs` : ''} ${i.via || ''}`.trim();
|
||||||
if (i.tipo === 'Farmacologica Insulina') return `${i.tipoInsulina || ''}${i.unidadesDesayuno ? ` - Desayuno: ${i.unidadesDesayuno}U` : ''}${i.unidadesAlmuerzo ? ` - Almuerzo: ${i.unidadesAlmuerzo}U` : ''}${i.unidadesNoche ? ` - 23hs: ${i.unidadesNoche}U` : ''}`.trim();
|
if (i.tipo === 'Farmacologica Insulina') return `${i.tipoInsulina || ''}${i.unidadesDesayuno ? ` - PreDesayuno: ${i.unidadesDesayuno}U` : ''}${i.unidadesAlmuerzo ? ` - PreAlmuerzo: ${i.unidadesAlmuerzo}U` : ''}${i.unidadesNoche ? ` - 23hs: ${i.unidadesNoche}U` : ''}`.trim();
|
||||||
if (i.tipo === 'No Farmacologica') return i.indicacionNoFco || '';
|
if (i.tipo === 'No Farmacologica') return i.indicacionNoFco || '';
|
||||||
if (i.tipo === 'PHP' || i.tipo === 'PHP Paralelo') return `${i.tipoPlan || ''} ${i.cantidadMl || ''}ml en ${i.tiempoHoras || ''}hs`;
|
if (i.tipo === 'PHP' || i.tipo === 'PHP Paralelo') return `${i.tipoPlan || ''} ${i.cantidadMl || ''}ml en ${i.tiempoHoras || ''}hs`;
|
||||||
if (i.tipo === 'PHP Alterno') return `${i.tipoPlan || ''} ${i.cantidadMl || ''}ml + ${i.tipoPlan2 || ''} ${i.cantidadMl2 || ''}ml en ${i.tiempoHoras || ''}hs`;
|
if (i.tipo === 'PHP Alterno') return `${i.tipoPlan || ''} ${i.cantidadMl || ''}ml + ${i.tipoPlan2 || ''} ${i.cantidadMl2 || ''}ml en ${i.tiempoHoras || ''}hs`;
|
||||||
@@ -47,13 +54,16 @@ export function SeccionIndicaciones({ recomendaciones, internacionId, add, updat
|
|||||||
};
|
};
|
||||||
|
|
||||||
const sortedMovs = [...movs].sort((a, b) => {
|
const sortedMovs = [...movs].sort((a, b) => {
|
||||||
let aVal: any = a[sortField] || '';
|
const recordA = a as Record<string, unknown>;
|
||||||
let bVal: any = b[sortField] || '';
|
const recordB = b as Record<string, unknown>;
|
||||||
|
const aVal = String(recordA[sortField] || '');
|
||||||
|
const bVal = String(recordB[sortField] || '');
|
||||||
if (sortField === 'fecha') {
|
if (sortField === 'fecha') {
|
||||||
aVal = new Date(aVal.replace(' ', 'T')).getTime();
|
const timeA = new Date(aVal.replace(' ', 'T')).getTime();
|
||||||
bVal = new Date(bVal.replace(' ', 'T')).getTime();
|
const timeB = new Date(bVal.replace(' ', 'T')).getTime();
|
||||||
|
return sortDir === 'asc' ? timeA - timeB : timeB - timeA;
|
||||||
}
|
}
|
||||||
return sortDir === 'asc' ? (aVal > bVal ? 1 : -1) : (aVal < bVal ? 1 : -1);
|
return sortDir === 'asc' ? aVal.localeCompare(bVal) : bVal.localeCompare(aVal);
|
||||||
});
|
});
|
||||||
const [droga, setDroga] = useState('');
|
const [droga, setDroga] = useState('');
|
||||||
const [dosis, setDosis] = useState('');
|
const [dosis, setDosis] = useState('');
|
||||||
@@ -75,7 +85,7 @@ export function SeccionIndicaciones({ recomendaciones, internacionId, add, updat
|
|||||||
const vias: ViaAdministracion[] = ['Via Oral', 'EV', 'IM', 'SC', 'Por GGT', 'Por SNG'];
|
const vias: ViaAdministracion[] = ['Via Oral', 'EV', 'IM', 'SC', 'Por GGT', 'Por SNG'];
|
||||||
const planes: TipoPlanHidratacion[] = ['SF 0.9%', 'Dextrosa 5%', 'Dextrosa 10%', 'Dextrosa 25%', 'Ringer Lactato'];
|
const planes: TipoPlanHidratacion[] = ['SF 0.9%', 'Dextrosa 5%', 'Dextrosa 10%', 'Dextrosa 25%', 'Ringer Lactato'];
|
||||||
const tiposInsulina: TipoInsulina[] = ['NPH', 'Glargina'];
|
const tiposInsulina: TipoInsulina[] = ['NPH', 'Glargina'];
|
||||||
const tipos: IndicacionTipo[] = ['Farmacologica', 'Farmacologica Profilactica', 'Farmacologica Insulina', 'No Farmacologica', 'PHP', 'PHP Paralelo', 'PHP Alterno'];
|
const tipos: IndicacionTipo[] = ['Farmacologica', 'Farmacologica Antibiótico', 'Farmacologica Profilactica', 'Farmacologica Insulina', 'No Farmacologica', 'PHP', 'PHP Paralelo', 'PHP Alterno'];
|
||||||
|
|
||||||
const reset = () => {
|
const reset = () => {
|
||||||
setEdit(null);
|
setEdit(null);
|
||||||
@@ -114,19 +124,19 @@ export function SeccionIndicaciones({ recomendaciones, internacionId, add, updat
|
|||||||
setUnidadesDesayuno(i.unidadesDesayuno || '');
|
setUnidadesDesayuno(i.unidadesDesayuno || '');
|
||||||
setUnidadesAlmuerzo(i.unidadesAlmuerzo || '');
|
setUnidadesAlmuerzo(i.unidadesAlmuerzo || '');
|
||||||
setUnidadesNoche(i.unidadesNoche || '');
|
setUnidadesNoche(i.unidadesNoche || '');
|
||||||
setMedico(i.medicoCrea);
|
setMedico(i.medicoCrea || '');
|
||||||
setDialog(true);
|
setDialog(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleGuardar = () => {
|
const handleGuardar = async () => {
|
||||||
if (!medico.trim()) return;
|
if (!medico.trim()) return;
|
||||||
if ((tipo === 'Farmacologica' || tipo === 'Farmacologica Profilactica') && (!droga.trim() || !dosis.trim())) return;
|
if ((tipo === 'Farmacologica' || tipo === 'Farmacologica Profilactica' || tipo === 'Farmacologica Antibiótico') && (!droga.trim() || !dosis.trim())) return;
|
||||||
if (tipo === 'Farmacologica Insulina' && (!unidadesDesayuno && !unidadesAlmuerzo && !unidadesNoche)) return;
|
if (tipo === 'Farmacologica Insulina' && (!unidadesDesayuno && !unidadesAlmuerzo && !unidadesNoche)) return;
|
||||||
if (tipo === 'No Farmacologica' && !indicacionNoFco.trim()) return;
|
if (tipo === 'No Farmacologica' && !indicacionNoFco.trim()) return;
|
||||||
if ((tipo === 'PHP' || tipo === 'PHP Paralelo') && !cantidadMl) return;
|
if ((tipo === 'PHP' || tipo === 'PHP Paralelo') && !cantidadMl) return;
|
||||||
if (tipo === 'PHP Alterno' && (!cantidadMl || !cantidadMl2)) return;
|
if (tipo === 'PHP Alterno' && (!cantidadMl || !cantidadMl2)) return;
|
||||||
|
|
||||||
const data: any = {
|
const data: Partial<Indicacion> = {
|
||||||
internacionId,
|
internacionId,
|
||||||
tipo,
|
tipo,
|
||||||
estado: 'Activa',
|
estado: 'Activa',
|
||||||
@@ -134,7 +144,7 @@ export function SeccionIndicaciones({ recomendaciones, internacionId, add, updat
|
|||||||
fechaCrea: new Date().toISOString().split('T')[0],
|
fechaCrea: new Date().toISOString().split('T')[0],
|
||||||
};
|
};
|
||||||
|
|
||||||
if (tipo === 'Farmacologica' || tipo === 'Farmacologica Profilactica') {
|
if (tipo === 'Farmacologica' || tipo === 'Farmacologica Profilactica' || tipo === 'Farmacologica Antibiótico') {
|
||||||
data.droga = droga;
|
data.droga = droga;
|
||||||
data.dosis = dosis;
|
data.dosis = dosis;
|
||||||
data.frecuenciaHoras = frecuenciaHoras || undefined;
|
data.frecuenciaHoras = frecuenciaHoras || undefined;
|
||||||
@@ -162,7 +172,7 @@ export function SeccionIndicaciones({ recomendaciones, internacionId, add, updat
|
|||||||
const fecha = now.toISOString().split('T')[0] + ' ' + now.toTimeString().split(' ')[0].substring(0, 5);
|
const fecha = now.toISOString().split('T')[0] + ' ' + now.toTimeString().split(' ')[0].substring(0, 5);
|
||||||
if (edit) {
|
if (edit) {
|
||||||
if (onAgregarMovimiento) {
|
if (onAgregarMovimiento) {
|
||||||
onAgregarMovimiento({
|
await onAgregarMovimiento({
|
||||||
indicacionId: edit.id,
|
indicacionId: edit.id,
|
||||||
internacionId,
|
internacionId,
|
||||||
tipo: 'Modificacion',
|
tipo: 'Modificacion',
|
||||||
@@ -172,14 +182,26 @@ export function SeccionIndicaciones({ recomendaciones, internacionId, add, updat
|
|||||||
indicacionNueva: formatIndicacion({ ...edit, ...data } as Indicacion),
|
indicacionNueva: formatIndicacion({ ...edit, ...data } as Indicacion),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
update(edit.id, data);
|
await update(edit.id, data);
|
||||||
} else {
|
} else {
|
||||||
const newId = add(data);
|
const newId = await add(data);
|
||||||
if (onAgregarMovimiento && newId) {
|
if (tipo === 'Farmacologica Antibiótico' && addATB && pacienteId) {
|
||||||
onAgregarMovimiento({
|
try {
|
||||||
indicacionId: newId,
|
await addATB({
|
||||||
|
pacienteId,
|
||||||
|
internacionId,
|
||||||
|
antibiotico: droga,
|
||||||
|
fechaInicio: data.fechaCrea,
|
||||||
|
});
|
||||||
|
} catch (atbErr) {
|
||||||
|
console.error('Error al agregar ATB automáticamente:', atbErr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (onAgregarMovimiento) {
|
||||||
|
await onAgregarMovimiento({
|
||||||
|
indicacionId: typeof newId === 'string' ? newId : '',
|
||||||
internacionId,
|
internacionId,
|
||||||
tipo: 'Indicacion',
|
tipo: 'Nueva',
|
||||||
fecha,
|
fecha,
|
||||||
profesional: medico,
|
profesional: medico,
|
||||||
indicacionNueva: formatIndicacion({ ...data, id: newId } as Indicacion),
|
indicacionNueva: formatIndicacion({ ...data, id: newId } as Indicacion),
|
||||||
@@ -190,31 +212,47 @@ export function SeccionIndicaciones({ recomendaciones, internacionId, add, updat
|
|||||||
reset();
|
reset();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSuspender = (i: Indicacion, suspendioMedico: string) => {
|
const handleSuspender = async (i: Indicacion, suspendioMedico: string) => {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const fecha = now.toISOString().split('T')[0] + ' ' + now.toTimeString().split(' ')[0].substring(0, 5);
|
const fecha = now.toISOString().split('T')[0] + ' ' + now.toTimeString().split(' ')[0].substring(0, 5);
|
||||||
if (onAgregarMovimiento) {
|
try {
|
||||||
onAgregarMovimiento({
|
if (onAgregarMovimiento) {
|
||||||
indicacionId: i.id,
|
await onAgregarMovimiento({
|
||||||
internacionId,
|
indicacionId: i.id,
|
||||||
tipo: 'Suspencion',
|
internacionId,
|
||||||
fecha,
|
tipo: 'Suspencion',
|
||||||
profesional: suspendioMedico,
|
fecha,
|
||||||
indicacionPrevia: formatIndicacion(i),
|
profesional: suspendioMedico,
|
||||||
});
|
indicacionPrevia: formatIndicacion(i),
|
||||||
}
|
});
|
||||||
del(i.id);
|
}
|
||||||
};
|
|
||||||
|
|
||||||
const handleModificar = (i: Indicacion, modificoMedico: string) => {
|
if (i.tipo === 'Farmacologica Antibiótico' && updateATB && atbList) {
|
||||||
update(i.id, {
|
const fechaFin = now.toISOString().split('T')[0];
|
||||||
medicoModifica: modificoMedico,
|
const activeATB = atbList.find(a =>
|
||||||
});
|
a.internacionId === internacionId &&
|
||||||
|
a.antibiotico.toLowerCase() === (i.droga || '').toLowerCase() &&
|
||||||
|
!a.fechaFinalizacion
|
||||||
|
);
|
||||||
|
if (activeATB) {
|
||||||
|
try {
|
||||||
|
await updateATB(activeATB.id, { fechaFinalizacion: fechaFin });
|
||||||
|
} catch (atbErr) {
|
||||||
|
console.error('Error al finalizar ATB automáticamente:', atbErr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await del(i.id);
|
||||||
|
toast.success('Indicación eliminada correctamente');
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error al eliminar indicación:', err);
|
||||||
|
toast.error('Error al eliminar la indicación');
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const indsFilter = list.filter(ind => ind.internacionId === internacionId);
|
const indsFilter = list.filter(ind => ind.internacionId === internacionId);
|
||||||
const activas = indsFilter.filter(ind => ind.estado === 'Activa');
|
const activas = indsFilter.filter(ind => ind.estado === 'Activa');
|
||||||
const historial = indsFilter.filter(ind => ind.estado === 'Suspendida' || ind.fechaModificacion);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
@@ -248,7 +286,7 @@ export function SeccionIndicaciones({ recomendaciones, internacionId, add, updat
|
|||||||
<Input value={medico} onChange={e => setMedico(e.target.value)} placeholder="Nombre del médico" />
|
<Input value={medico} onChange={e => setMedico(e.target.value)} placeholder="Nombre del médico" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{tipo === 'Farmacologica' && (
|
{(tipo === 'Farmacologica' || tipo === 'Farmacologica Antibiótico') && (
|
||||||
<>
|
<>
|
||||||
<div><Label>Droga *</Label><Input value={droga} onChange={e => setDroga(e.target.value)} placeholder="Nombre del medicamento" /></div>
|
<div><Label>Droga *</Label><Input value={droga} onChange={e => setDroga(e.target.value)} placeholder="Nombre del medicamento" /></div>
|
||||||
<div><Label>Dosis *</Label><Input value={dosis} onChange={e => setDosis(e.target.value)} placeholder="Ej: 500mg, 1g, 10ml" /></div>
|
<div><Label>Dosis *</Label><Input value={dosis} onChange={e => setDosis(e.target.value)} placeholder="Ej: 500mg, 1g, 10ml" /></div>
|
||||||
@@ -272,9 +310,18 @@ export function SeccionIndicaciones({ recomendaciones, internacionId, add, updat
|
|||||||
<>
|
<>
|
||||||
<div><Label>Tipo de Insulina</Label><Select value={tipoInsulina} onValueChange={(v: TipoInsulina) => setTipoInsulina(v)}><SelectTrigger><SelectValue /></SelectTrigger><SelectContent>{tiposInsulina.map(t => <SelectItem key={t} value={t}>{t}</SelectItem>)}</SelectContent></Select></div>
|
<div><Label>Tipo de Insulina</Label><Select value={tipoInsulina} onValueChange={(v: TipoInsulina) => setTipoInsulina(v)}><SelectTrigger><SelectValue /></SelectTrigger><SelectContent>{tiposInsulina.map(t => <SelectItem key={t} value={t}>{t}</SelectItem>)}</SelectContent></Select></div>
|
||||||
<div className="grid grid-cols-3 gap-2">
|
<div className="grid grid-cols-3 gap-2">
|
||||||
<div><Label>Pre Desayuno (U)</Label><Input type="number" value={unidadesDesayuno} onChange={e => setUnidadesDesayuno(e.target.value ? parseInt(e.target.value) : '')} placeholder="U" /></div>
|
<div className="flex flex-col justify-end space-y-1.5">
|
||||||
<div><Label>Pre Almuerzo (U)</Label><Input type="number" value={unidadesAlmuerzo} onChange={e => setUnidadesAlmuerzo(e.target.value ? parseInt(e.target.value) : '')} placeholder="U" /></div>
|
<Label className="text-xs sm:text-sm">Pre Desayuno (U)</Label>
|
||||||
<div><Label>23hs (U)</Label><Input type="number" value={unidadesNoche} onChange={e => setUnidadesNoche(e.target.value ? parseInt(e.target.value) : '')} placeholder="U" /></div>
|
<Input type="number" value={unidadesDesayuno} onChange={e => setUnidadesDesayuno(e.target.value ? parseInt(e.target.value) : '')} placeholder="U" />
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col justify-end space-y-1.5">
|
||||||
|
<Label className="text-xs sm:text-sm">Pre Almuerzo (U)</Label>
|
||||||
|
<Input type="number" value={unidadesAlmuerzo} onChange={e => setUnidadesAlmuerzo(e.target.value ? parseInt(e.target.value) : '')} placeholder="U" />
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col justify-end space-y-1.5">
|
||||||
|
<Label className="text-xs sm:text-sm">23hs (U)</Label>
|
||||||
|
<Input type="number" value={unidadesNoche} onChange={e => setUnidadesNoche(e.target.value ? parseInt(e.target.value) : '')} placeholder="U" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@@ -329,13 +376,16 @@ export function SeccionIndicaciones({ recomendaciones, internacionId, add, updat
|
|||||||
{ind.tipo === 'Farmacologica' && (
|
{ind.tipo === 'Farmacologica' && (
|
||||||
<div className="font-medium">{ind.droga} {ind.dosis} {ind.frecuenciaHoras ? `c/${ind.frecuenciaHoras}hs` : ''} {ind.via}</div>
|
<div className="font-medium">{ind.droga} {ind.dosis} {ind.frecuenciaHoras ? `c/${ind.frecuenciaHoras}hs` : ''} {ind.via}</div>
|
||||||
)}
|
)}
|
||||||
|
{ind.tipo === 'Farmacologica Antibiótico' && (
|
||||||
|
<div className="font-medium">{ind.droga} {ind.dosis} {ind.frecuenciaHoras ? `c/${ind.frecuenciaHoras}hs` : ''} {ind.via} <Badge className="ml-2 bg-purple-100 text-purple-800 dark:bg-purple-950 dark:text-purple-300 hover:bg-purple-100">Antibiótico</Badge></div>
|
||||||
|
)}
|
||||||
{ind.tipo === 'Farmacologica Profilactica' && (
|
{ind.tipo === 'Farmacologica Profilactica' && (
|
||||||
<div className="font-medium">{ind.droga} {ind.dosis} {ind.frecuenciaHoras ? `c/${ind.frecuenciaHoras}hs` : ''} {ind.via}</div>
|
<div className="font-medium">{ind.droga} {ind.dosis} {ind.frecuenciaHoras ? `c/${ind.frecuenciaHoras}hs` : ''} {ind.via}</div>
|
||||||
)}
|
)}
|
||||||
{ind.tipo === 'Farmacologica Insulina' && (
|
{ind.tipo === 'Farmacologica Insulina' && (
|
||||||
<div className="font-medium">{ind.tipoInsulina}
|
<div className="font-medium">{ind.tipoInsulina}
|
||||||
{ind.unidadesDesayuno && ` - Desayuno: ${ind.unidadesDesayuno}U`}
|
{ind.unidadesDesayuno && ` - PreDesayuno: ${ind.unidadesDesayuno}U`}
|
||||||
{ind.unidadesAlmuerzo && ` - Almuerzo: ${ind.unidadesAlmuerzo}U`}
|
{ind.unidadesAlmuerzo && ` - PreAlmuerzo: ${ind.unidadesAlmuerzo}U`}
|
||||||
{ind.unidadesNoche && ` - 23hs: ${ind.unidadesNoche}U`}
|
{ind.unidadesNoche && ` - 23hs: ${ind.unidadesNoche}U`}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -350,16 +400,13 @@ export function SeccionIndicaciones({ recomendaciones, internacionId, add, updat
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-1">
|
<div className="flex gap-1">
|
||||||
<Button size="sm" variant="outline" className="text-orange-600" onClick={() => {
|
{canEdit && <Button size="sm" variant="outline" title="Editar" onClick={() => loadEdit(ind)}>
|
||||||
const med = prompt('Médico que suspende:');
|
|
||||||
if (med) handleSuspender(ind, med);
|
|
||||||
}}>
|
|
||||||
<X className="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
{canEdit && <Button size="sm" variant="outline" onClick={() => loadEdit(ind)}>
|
|
||||||
<Pencil className="h-4 w-4" />
|
<Pencil className="h-4 w-4" />
|
||||||
</Button>}
|
</Button>}
|
||||||
{canEdit && <Button size="sm" variant="outline" className="text-red-600" onClick={() => del(ind.id)}>
|
{canEdit && <Button size="sm" variant="outline" className="text-red-600 hover:text-red-700 hover:bg-red-50 dark:hover:bg-red-950/30" title="Eliminar" onClick={() => {
|
||||||
|
setDeleteConfirmInd(ind);
|
||||||
|
setDeleteMedico(ind.medicoCrea || '');
|
||||||
|
}}>
|
||||||
<Trash2 className="h-4 w-4" />
|
<Trash2 className="h-4 w-4" />
|
||||||
</Button>}
|
</Button>}
|
||||||
</div>
|
</div>
|
||||||
@@ -369,6 +416,53 @@ export function SeccionIndicaciones({ recomendaciones, internacionId, add, updat
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{deleteConfirmInd && (
|
||||||
|
<Dialog open={!!deleteConfirmInd} onOpenChange={(open) => { if (!open) setDeleteConfirmInd(null); }}>
|
||||||
|
<DialogContent className="max-w-md">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle className="text-red-600 flex items-center gap-2">
|
||||||
|
<Trash2 className="h-5 w-5" />
|
||||||
|
Confirmar eliminación de indicación
|
||||||
|
</DialogTitle>
|
||||||
|
</DialogHeader>
|
||||||
|
<div className="space-y-4 py-2">
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
¿Está seguro de que desea suspender/eliminar esta indicación? Se registrará la baja en el historial.
|
||||||
|
</p>
|
||||||
|
<div className="p-3 bg-muted rounded-md text-sm font-medium">
|
||||||
|
<span className="text-xs text-muted-foreground block mb-1">Indicación:</span>
|
||||||
|
{formatIndicacion(deleteConfirmInd)}
|
||||||
|
</div>
|
||||||
|
<div className="space-y-1.5">
|
||||||
|
<Label htmlFor="delete-medico">Médico que suspende / elimina *</Label>
|
||||||
|
<Input
|
||||||
|
id="delete-medico"
|
||||||
|
value={deleteMedico}
|
||||||
|
onChange={(e) => setDeleteMedico(e.target.value)}
|
||||||
|
placeholder="Nombre del profesional..."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-end gap-2 mt-2">
|
||||||
|
<Button variant="outline" onClick={() => setDeleteConfirmInd(null)}>
|
||||||
|
Cancelar
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="destructive"
|
||||||
|
onClick={async () => {
|
||||||
|
const med = deleteMedico.trim() || deleteConfirmInd.medicoCrea || 'Médico';
|
||||||
|
const indToDel = deleteConfirmInd;
|
||||||
|
setDeleteConfirmInd(null);
|
||||||
|
await handleSuspender(indToDel, med);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Eliminar
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
)}
|
||||||
|
|
||||||
{showHistorial && (
|
{showHistorial && (
|
||||||
<Dialog open={showHistorial} onOpenChange={setShowHistorial}>
|
<Dialog open={showHistorial} onOpenChange={setShowHistorial}>
|
||||||
<DialogContent className="max-w-4xl max-h-[80vh] overflow-auto">
|
<DialogContent className="max-w-4xl max-h-[80vh] overflow-auto">
|
||||||
@@ -381,13 +475,13 @@ export function SeccionIndicaciones({ recomendaciones, internacionId, add, updat
|
|||||||
<Table>
|
<Table>
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableHead className="cursor-pointer hover:bg-gray-100" onClick={() => handleSort('fecha')}>
|
<TableHead className="cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-800" onClick={() => handleSort('fecha')}>
|
||||||
Fecha {sortField === 'fecha' && (sortDir === 'asc' ? '↑' : '↓')}
|
Fecha {sortField === 'fecha' && (sortDir === 'asc' ? '↑' : '↓')}
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableHead className="cursor-pointer hover:bg-gray-100" onClick={() => handleSort('profesional')}>
|
<TableHead className="cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-800" onClick={() => handleSort('profesional')}>
|
||||||
Profesional {sortField === 'profesional' && (sortDir === 'asc' ? '↑' : '↓')}
|
Profesional {sortField === 'profesional' && (sortDir === 'asc' ? '↑' : '↓')}
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableHead className="cursor-pointer hover:bg-gray-100" onClick={() => handleSort('tipo')}>
|
<TableHead className="cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-800" onClick={() => handleSort('tipo')}>
|
||||||
Tipo {sortField === 'tipo' && (sortDir === 'asc' ? '↑' : '↓')}
|
Tipo {sortField === 'tipo' && (sortDir === 'asc' ? '↑' : '↓')}
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableHead>Indicación</TableHead>
|
<TableHead>Indicación</TableHead>
|
||||||
@@ -399,8 +493,14 @@ export function SeccionIndicaciones({ recomendaciones, internacionId, add, updat
|
|||||||
<TableCell>{mov.fecha}</TableCell>
|
<TableCell>{mov.fecha}</TableCell>
|
||||||
<TableCell>{mov.profesional}</TableCell>
|
<TableCell>{mov.profesional}</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<Badge className={mov.tipo === 'Indicacion' ? 'bg-green-500' : mov.tipo === 'Suspencion' ? 'bg-red-500' : 'bg-orange-500'}>
|
<Badge className={
|
||||||
{mov.tipo === 'Indicacion' ? 'Indicación' : mov.tipo === 'Suspencion' ? 'Suspensión' : 'Modificación'}
|
mov.tipo === 'Nueva' || mov.tipo === 'Indicacion'
|
||||||
|
? 'bg-green-600 hover:bg-green-700 text-white'
|
||||||
|
: mov.tipo === 'Suspencion' || mov.tipo === 'Suspensión' || mov.tipo === 'Eliminacion'
|
||||||
|
? 'bg-red-600 hover:bg-red-700 text-white'
|
||||||
|
: 'bg-orange-500 hover:bg-orange-600 text-white'
|
||||||
|
}>
|
||||||
|
{mov.tipo === 'Nueva' || mov.tipo === 'Indicacion' ? 'Nueva' : mov.tipo === 'Suspencion' || mov.tipo === 'Suspensión' || mov.tipo === 'Eliminacion' ? 'Suspensión' : 'Modificación'}
|
||||||
</Badge>
|
</Badge>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
@@ -409,7 +509,7 @@ export function SeccionIndicaciones({ recomendaciones, internacionId, add, updat
|
|||||||
<div className="text-red-500 line-through">{mov.indicacionPrevia}</div>
|
<div className="text-red-500 line-through">{mov.indicacionPrevia}</div>
|
||||||
<div className="text-green-500">{mov.indicacionNueva}</div>
|
<div className="text-green-500">{mov.indicacionNueva}</div>
|
||||||
</div>
|
</div>
|
||||||
) : mov.tipo === 'Suspencion' ? mov.indicacionPrevia : mov.indicacionNueva}
|
) : (mov.tipo === 'Suspencion' || mov.tipo === 'Suspensión' || mov.tipo === 'Eliminacion') ? mov.indicacionPrevia : mov.indicacionNueva || mov.indicacionPrevia}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
))}
|
))}
|
||||||
|
|||||||
+11
-1
@@ -112,6 +112,16 @@ export interface ResultadoLaboratorio {
|
|||||||
estado: 'Normal' | 'Alto' | 'Bajo' | 'Crítico';
|
estado: 'Normal' | 'Alto' | 'Bajo' | 'Crítico';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Glucemia {
|
||||||
|
id: string;
|
||||||
|
pacienteId: string;
|
||||||
|
internacionId?: string;
|
||||||
|
fecha: string;
|
||||||
|
hora: string;
|
||||||
|
valor: number; // in mg%
|
||||||
|
correccion: number; // in UI insulin
|
||||||
|
}
|
||||||
|
|
||||||
export interface AcidoBase {
|
export interface AcidoBase {
|
||||||
id: string;
|
id: string;
|
||||||
pacienteId: string;
|
pacienteId: string;
|
||||||
@@ -173,7 +183,7 @@ export interface ATB {
|
|||||||
fechaFinalizacion?: string;
|
fechaFinalizacion?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type IndicacionTipo = 'Farmacologica' | 'Farmacologica Profilactica' | 'Farmacologica Insulina' | 'No Farmacologica' | 'PHP' | 'PHP Paralelo' | 'PHP Alterno';
|
export type IndicacionTipo = 'Farmacologica' | 'Farmacologica Profilactica' | 'Farmacologica Antibiótico' | 'Farmacologica Insulina' | 'No Farmacologica' | 'PHP' | 'PHP Paralelo' | 'PHP Alterno';
|
||||||
export type TipoInsulina = 'NPH' | 'Glargina';
|
export type TipoInsulina = 'NPH' | 'Glargina';
|
||||||
export type ViaAdministracion = 'Via Oral' | 'EV' | 'IM' | 'SC' | 'Por GGT' | 'Por SNG';
|
export type ViaAdministracion = 'Via Oral' | 'EV' | 'IM' | 'SC' | 'Por GGT' | 'Por SNG';
|
||||||
export type TipoPlanHidratacion = 'SF 0.9%' | 'Dextrosa 5%' | 'Dextrosa 10%' | 'Dextrosa 25%' | 'Ringer Lactato';
|
export type TipoPlanHidratacion = 'SF 0.9%' | 'Dextrosa 5%' | 'Dextrosa 10%' | 'Dextrosa 25%' | 'Ringer Lactato';
|
||||||
|
|||||||
@@ -14,11 +14,5 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
server: {
|
server: {
|
||||||
host: '0.0.0.0',
|
host: '0.0.0.0',
|
||||||
proxy: {
|
|
||||||
'/api': {
|
|
||||||
target: 'http://localhost:4001',
|
|
||||||
changeOrigin: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user