diff --git a/server/server.js b/server/server.js index 7858045..8e61d52 100644 --- a/server/server.js +++ b/server/server.js @@ -251,33 +251,54 @@ try { // column may already exist } try { - db.exec("ALTER TABLE recomendaciones ADD COLUMN fechaModificacion TEXT"); + db.exec("ALTER TABLE indicaciones ADD COLUMN fechaModificacion TEXT"); } catch (e) { // column may already exist } try { - db.exec("ALTER TABLE recomendaciones ADD COLUMN tipoInsulina TEXT"); + db.exec("ALTER TABLE indicaciones ADD COLUMN tipoInsulina TEXT"); } catch (e) { // column may already exist } try { - db.exec("ALTER TABLE recomendaciones ADD COLUMN unidadesDesayuno INTEGER"); + db.exec("ALTER TABLE indicaciones ADD COLUMN unidadesDesayuno INTEGER"); } catch (e) { // column may already exist } try { - db.exec("ALTER TABLE recomendaciones ADD COLUMN unidadesAlmuerzo INTEGER"); + db.exec("ALTER TABLE indicaciones ADD COLUMN unidadesAlmuerzo INTEGER"); } catch (e) { // column may already exist } try { - db.exec("ALTER TABLE recomendaciones ADD COLUMN unidadesNoche INTEGER"); + db.exec("ALTER TABLE indicaciones ADD COLUMN unidadesNoche INTEGER"); } catch (e) { // column may already exist } } } catch (e) { - console.log('Error tabla recomendaciones:', e.message); + console.log('Error tabla indicaciones:', e.message); +} + +try { + const exists = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='movimientos_indicaciones'").get(); + if (!exists) { + db.exec(` + CREATE TABLE 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 + ) + `); + console.log('Tabla movimientos_indicaciones creada'); + } +} catch (e) { + console.log('Tabla movimientos_indicaciones:', e.message); } const app = express(); @@ -332,6 +353,7 @@ app.get('/api/state', (req, res) => { interconsultas: queryAll('SELECT * FROM interconsultas').map(ic => ({ ...ic, realizada: !!ic.realizada })), atb: queryAll('SELECT * FROM atb'), indicaciones: queryAll('SELECT * FROM indicaciones'), + movimientosIndicaciones: queryAll('SELECT * FROM movimientos_indicaciones'), vistaActual: 'dashboard', currentInternacionId: null }; @@ -432,9 +454,17 @@ if (state.laboratorios?.length) { run('DELETE FROM indicaciones'); if (state.indicaciones?.length) { - const stmt = db.prepare('INSERT INTO indicaciones (id, internacionId, tipo, droga, dosis, frecuenciaHoras, via, descripcion, tipoPlan, tipoPlan2, cantidadMl, cantidadMl2, tiempoHoras, estado, medicoCrea, fechaCrea, medicoModifica, fechaModificacion, medicoSuspende, fechaSuspension) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'); + const stmt = db.prepare('INSERT INTO recomendaciones (id, internacionId, tipo, droga, dosis, frecuenciaHoras, via, descripcion, tipoPlan, tipoPlan2, cantidadMl, cantidadMl2, tiempoHoras, estado, medicoCrea, fechaCrea, medicoModifica, medicoSuspende, fechaSuspension, tipoInsulina, unidadesDesayuno, unidadesAlmuerzo, unidadesNoche) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'); for (const ind of state.indicaciones) { - stmt.run(ind.id, ind.internacionId, ind.tipo, ind.droga || null, ind.dosis || null, ind.frecuenciaHoras || null, ind.via || null, ind.descripcion || null, ind.tipoPlan || null, ind.tipoPlan2 || null, ind.cantidadMl || null, ind.cantidadMl2 || null, ind.tiempoHoras || null, ind.estado, ind.medicoCrea, ind.fechaCrea, ind.medicoModifica || null, ind.fechaModificacion || null, ind.medicoSuspende || null, ind.fechaSuspension || null); + stmt.run(ind.id, ind.internacionId, ind.tipo, ind.droga || null, ind.dosis || null, ind.frecuenciaHoras || null, ind.via || null, ind.descripcion || null, ind.tipoPlan || null, ind.tipoPlan2 || null, ind.cantidadMl || null, ind.cantidadMl2 || null, ind.tiempoHoras || null, ind.estado, ind.medicoCrea, ind.fechaCrea, ind.medicoModifica || null, ind.medicoSuspende || null, ind.fechaSuspension || null, ind.tipoInsulina || null, ind.unidadesDesayuno || null, ind.unidadesAlmuerzo || null, ind.unidadesNoche || null); + } + } + + run('DELETE FROM movimientos_indicaciones'); + if (state.movimientosIndicaciones?.length) { + const stmt = db.prepare('INSERT INTO movimientos_indicaciones (id, indicacionId, internacionId, tipo, fecha, profesional, indicacionPrevia, indicacionNueva) VALUES (?, ?, ?, ?, ?, ?, ?, ?)'); + for (const mov of state.movimientosIndicaciones) { + stmt.run(mov.id, mov.indicacionId, mov.internacionId, mov.tipo, mov.fecha, mov.profesional, mov.indicacionPrevia || null, mov.indicacionNueva || null); } } diff --git a/src/App.tsx b/src/App.tsx index 1ec7973..aa94969 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -168,9 +168,11 @@ function AppContent() { onActualizarATB={store.actualizarATB} onEliminarATB={store.eliminarATB} indicadores={(Array.isArray(store.indicaciones) ? store.indicaciones : []).filter((i: any) => i.internacionId === internacion.id)} + movimientos={store.getMovimientosByInternacion ? store.getMovimientosByInternacion(internacion.id) : []} onAgregarIndicacion={(i) => store.agregarIndicacion({ ...i, internacionId: internacion.id })} onActualizarIndicacion={store.actualizarIndicacion} onEliminarIndicacion={store.eliminarIndicacion} + onAgregarMovimiento={(m: any) => store.agregarMovimientoIndicacion ? store.agregarMovimientoIndicacion({ ...m, internacionId: internacion.id }) : null} onActualizarInternacion={store.actualizarInternacion} onActualizarCama={store.actualizarCama} onVolver={() => store.setVista('internaciones')} diff --git a/src/hooks/useHospitalStore.ts b/src/hooks/useHospitalStore.ts index a0380d6..781beb1 100644 --- a/src/hooks/useHospitalStore.ts +++ b/src/hooks/useHospitalStore.ts @@ -41,6 +41,7 @@ interface HospitalState { interconsultas: Interconsulta[]; atb: ATB[]; indicaciones: Indicacion[]; + movimientosIndicaciones: any[]; vistaActual: Vista; currentInternacionId?: string | null; } @@ -61,6 +62,7 @@ const defaultState = (): HospitalState => ({ atb: [], vistaActual: 'dashboard', indicaciones: [], + movimientosIndicaciones: [], camas: [] }); @@ -77,7 +79,9 @@ export function useHospitalStore() { if (!res.ok) throw new Error('no state'); const body = await res.json(); if (mounted && body) { - setState(body as HospitalState); + const normalized = body as HospitalState; + normalized.movimientosIndicaciones = normalized.movimientosIndicaciones || []; + setState(normalized); } } catch (err) { // no remote state or unreachable — keep defaults @@ -428,6 +432,19 @@ const finalizarInternacion = useCallback((internacionId: string, datos: { })); }, []); + const agregarMovimientoIndicacion = useCallback((movimiento: any) => { + setState(prev => ({ + ...prev, + movimientosIndicaciones: [...(prev.movimientosIndicaciones || []), { ...movimiento, id: generateUUID() }], + })); + }, []); + + const getMovimientosByInternacion = useCallback((internacionId: string) => { + return (state.movimientosIndicaciones || []) + .filter((m: any) => m.internacionId === internacionId) + .sort((a: any, b: any) => new Date(b.fecha).getTime() - new Date(a.fecha).getTime()); + }, [state.movimientosIndicaciones]); + // Acciones de areas const agregarArea = useCallback((area: Omit) => { const nuevaArea: Area = { ...area, id: generateUUID() }; @@ -560,6 +577,8 @@ const getEstadisticas = useCallback(() => { agregarIndicacion, actualizarIndicacion, eliminarIndicacion, + agregarMovimientoIndicacion, + getMovimientosByInternacion, agregarArea, actualizarArea, eliminarArea, diff --git a/src/sections/HistoriaClinica.tsx b/src/sections/HistoriaClinica.tsx index 3401884..1acf482 100644 --- a/src/sections/HistoriaClinica.tsx +++ b/src/sections/HistoriaClinica.tsx @@ -93,6 +93,8 @@ interface HistoriaClinicaProps { onAgregarIndicacion: (indicacion: Omit) => void; onActualizarIndicacion: (id: string, datos: Partial) => void; onEliminarIndicacion: (id: string) => void; + movimientos?: any[]; + onAgregarMovimiento?: (m: any) => void; onActualizarInternacion: (id: string, datos: Partial) => void; onActualizarCama: (id: string, datos: Partial) => void; onVolver: () => void; @@ -209,6 +211,8 @@ export function HistoriaClinica({ onAgregarIndicacion, onActualizarIndicacion, onEliminarIndicacion, + movimientos, + onAgregarMovimiento, onActualizarInternacion, onActualizarCama, onVolver, @@ -559,6 +563,8 @@ export function HistoriaClinica({ add={onAgregarIndicacion} update={onActualizarIndicacion} del={onEliminarIndicacion} + movimientos={movimientos} + onAgregarMovimiento={onAgregarMovimiento} /> diff --git a/src/sections/SeccionIndicaciones.tsx b/src/sections/SeccionIndicaciones.tsx index 7a2954d..ee1f019 100644 --- a/src/sections/SeccionIndicaciones.tsx +++ b/src/sections/SeccionIndicaciones.tsx @@ -10,17 +10,50 @@ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@ import { Plus, Pencil, Trash2, X } from 'lucide-react'; import type { Indicacion, IndicacionTipo, ViaAdministracion, TipoPlanHidratacion, TipoInsulina } from '@/types'; -export function SeccionIndicaciones({ recomendaciones, internacionId, add, update, del }: { +export function SeccionIndicaciones({ recomendaciones, internacionId, add, update, del, movimientos, onAgregarMovimiento }: { recomendaciones: Indicacion[]; internacionId: string; add: (i: Omit) => void; update: (id: string, datos: Partial) => void; del: (id: string) => void; + movimientos?: any[]; + onAgregarMovimiento?: (m: any) => void; }) { const list: Indicacion[] = Array.isArray(recomendaciones) ? recomendaciones : []; + const movs: any[] = Array.isArray(movimientos) ? movimientos : []; const [dialog, setDialog] = useState(false); const [edit, setEdit] = useState(null); + + const formatIndicacion = (i: Indicacion): string => { + if (i.tipo === 'Farmacologica') 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 === 'No Farmacologica') return i.descripcion || ''; + 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`; + return ''; + }; const [tipo, setTipo] = useState('Farmacologica'); + const [sortField, setSortField] = useState<'fecha' | 'profesional' | 'tipo'>('fecha'); + const [sortDir, setSortDir] = useState<'asc' | 'desc'>('desc'); + + const handleSort = (field: 'fecha' | 'profesional' | 'tipo') => { + if (sortField === field) { + setSortDir(sortDir === 'asc' ? 'desc' : 'asc'); + } else { + setSortField(field); + setSortDir('desc'); + } + }; + + const sortedMovs = [...movs].sort((a, b) => { + let aVal: any = a[sortField] || ''; + let bVal: any = b[sortField] || ''; + if (sortField === 'fecha') { + aVal = new Date(aVal.replace(' ', 'T')).getTime(); + bVal = new Date(bVal.replace(' ', 'T')).getTime(); + } + return sortDir === 'asc' ? (aVal > bVal ? 1 : -1) : (aVal < bVal ? 1 : -1); + }); const [droga, setDroga] = useState(''); const [dosis, setDosis] = useState(''); const [frecuenciaHoras, setFrecuenciaHoras] = useState(''); @@ -38,10 +71,10 @@ export function SeccionIndicaciones({ recomendaciones, internacionId, add, updat const [medico, setMedico] = useState(''); const [showHistorial, setShowHistorial] = useState(false); - const vias: ViaAdministracion[] = ['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 tiposInsulina: TipoInsulina[] = ['NPH', 'Glargina']; - const tipos: IndicacionTipo[] = ['Farmacologica', 'Farmacologica Insulina', 'No Farmacologica', 'PHP', 'Paralelo', 'Alterno']; + const tipos: IndicacionTipo[] = ['Farmacologica', 'Farmacologica Profilactica', 'Farmacologica Insulina', 'No Farmacologica', 'PHP', 'PHP Paralelo', 'PHP Alterno']; const reset = () => { setEdit(null); @@ -86,11 +119,11 @@ export function SeccionIndicaciones({ recomendaciones, internacionId, add, updat const handleGuardar = () => { if (!medico.trim()) return; - if (tipo === 'Farmacologica' && (!droga.trim() || !dosis.trim())) return; + if ((tipo === 'Farmacologica' || tipo === 'Farmacologica Profilactica') && (!droga.trim() || !dosis.trim())) return; if (tipo === 'Farmacologica Insulina' && (!unidadesDesayuno && !unidadesAlmuerzo && !unidadesNoche)) return; if (tipo === 'No Farmacologica' && !descripcion.trim()) return; - if ((tipo === 'PHP' || tipo === 'Paralelo') && !cantidadMl) return; - if (tipo === 'Alterno' && (!cantidadMl || !cantidadMl2)) return; + if ((tipo === 'PHP' || tipo === 'PHP Paralelo') && !cantidadMl) return; + if (tipo === 'PHP Alterno' && (!cantidadMl || !cantidadMl2)) return; const data: any = { internacionId, @@ -100,7 +133,7 @@ export function SeccionIndicaciones({ recomendaciones, internacionId, add, updat fechaCrea: new Date().toISOString().split('T')[0], }; - if (tipo === 'Farmacologica') { + if (tipo === 'Farmacologica' || tipo === 'Farmacologica Profilactica') { data.droga = droga; data.dosis = dosis; data.frecuenciaHoras = frecuenciaHoras || undefined; @@ -110,13 +143,14 @@ export function SeccionIndicaciones({ recomendaciones, internacionId, add, updat data.unidadesDesayuno = unidadesDesayuno || undefined; data.unidadesAlmuerzo = unidadesAlmuerzo || undefined; data.unidadesNoche = unidadesNoche || undefined; + console.log('Guardando insulina:', tipoInsulina, unidadesDesayuno, unidadesAlmuerzo, unidadesNoche); } else if (tipo === 'No Farmacologica') { data.descripcion = descripcion; - } else if (tipo === 'PHP' || tipo === 'Paralelo') { + } else if (tipo === 'PHP' || tipo === 'PHP Paralelo') { data.tipoPlan = tipoPlan; data.cantidadMl = cantidadMl; data.tiempoHoras = tiempoHoras; - } else if (tipo === 'Alterno') { + } else if (tipo === 'PHP Alterno') { data.tipoPlan = tipoPlan; data.cantidadMl = cantidadMl; data.tipoPlan2 = tipoPlan2; @@ -124,27 +158,61 @@ export function SeccionIndicaciones({ recomendaciones, internacionId, add, updat data.tiempoHoras = tiempoHoras; } + const now = new Date(); + const fecha = now.toISOString().split('T')[0] + ' ' + now.toTimeString().split(' ')[0].substring(0, 5); if (edit) { + if (onAgregarMovimiento) { + onAgregarMovimiento({ + indicacionId: edit.id, + internacionId, + tipo: 'Modificacion', + fecha, + profesional: medico, + indicacionPrevia: formatIndicacion(edit), + indicacionNueva: formatIndicacion({ ...edit, ...data } as Indicacion), + }); + } update(edit.id, data); } else { - add(data); + const newId = add(data); + if (onAgregarMovimiento && newId) { + onAgregarMovimiento({ + indicacionId: newId, + internacionId, + tipo: 'Indicacion', + fecha, + profesional: medico, + indicacionNueva: formatIndicacion({ ...data, id: newId } as Indicacion), + }); + } } setDialog(false); reset(); }; const handleSuspender = (i: Indicacion, suspendioMedico: string) => { + const now = new Date(); + const fecha = now.toISOString().split('T')[0] + ' ' + now.toTimeString().split(' ')[0].substring(0, 5); update(i.id, { estado: 'Suspendida', medicoSuspende: suspendioMedico, - fechaSuspension: new Date().toISOString().split('T')[0], + fechaSuspension: fecha, }); + if (onAgregarMovimiento) { + onAgregarMovimiento({ + indicacionId: i.id, + internacionId, + tipo: 'Suspencion', + fecha, + profesional: suspendioMedico, + indicacionPrevia: formatIndicacion(i), + }); + } }; const handleModificar = (i: Indicacion, modificoMedico: string) => { update(i.id, { medicoModifica: modificoMedico, - fechaModificacion: new Date().toISOString().split('T')[0], }); }; @@ -159,7 +227,7 @@ export function SeccionIndicaciones({ recomendaciones, internacionId, add, updat Nueva Indicación @@ -184,7 +252,17 @@ export function SeccionIndicaciones({ recomendaciones, internacionId, add, updat setMedico(e.target.value)} placeholder="Nombre del médico" /> - {tipo === 'Farmacologica' && ( +{tipo === 'Farmacologica' && ( + <> +
setDroga(e.target.value)} placeholder="Nombre del medicamento" />
+
setDosis(e.target.value)} placeholder="Ej: 500mg, 1g, 10ml" />
+
+
setFrecuenciaHoras(e.target.value ? parseInt(e.target.value) : '')} placeholder="Cada X horas" />
+
+
+ + )} + {tipo === 'Farmacologica Profilactica' && ( <>
setDroga(e.target.value)} placeholder="Nombre del medicamento" />
setDosis(e.target.value)} placeholder="Ej: 500mg, 1g, 10ml" />
@@ -194,7 +272,6 @@ export function SeccionIndicaciones({ recomendaciones, internacionId, add, updat )} - {tipo === 'Farmacologica Insulina' && ( <>
@@ -210,7 +287,7 @@ export function SeccionIndicaciones({ recomendaciones, internacionId, add, updat