From 97adfd08b75f288a1182cf9ee84b76b6f2c20def Mon Sep 17 00:00:00 2001 From: Sergio Nicolas Lavaise Date: Thu, 16 Apr 2026 01:22:15 -0300 Subject: [PATCH] Agregar estudios complementarios en HC - Agregar tabla estudiosComplementarios en SQLite y API - Agregar interface EstudioComplementario en tipos - Agregar funciones en store para gestionar estudios - Nuevo tab en HistoriaClinica con filtro por internacion/todos - Fix tema dark mode (colores black en lugar de blue) --- server/server.js | 25 ++++- src/App.tsx | 3 + src/hooks/useHospitalStore.ts | 33 ++++++ src/index.css | 92 ++++++++--------- src/sections/HistoriaClinica.tsx | 166 +++++++++++++++++++++++++++++-- src/types/index.ts | 9 ++ 6 files changed, 274 insertions(+), 54 deletions(-) diff --git a/server/server.js b/server/server.js index 9608dbb..ecd5eae 100644 --- a/server/server.js +++ b/server/server.js @@ -121,6 +121,15 @@ db.exec(` 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 + ); `); try { @@ -129,6 +138,12 @@ try { // La columna ya existe } +try { + db.exec("ALTER TABLE laboratorios ADD COLUMN internacionId TEXT"); +} catch (e) { + // La columna ya existe +} + const app = express(); app.use(cors()); app.use(express.json({ limit: '50mb' })); @@ -177,6 +192,7 @@ app.get('/api/state', (req, res) => { laboratorios: queryAll('SELECT * FROM laboratorios').map(l => ({ ...l, resultados: typeof l.resultados === 'string' ? JSON.parse(l.resultados) : l.resultados })), acidosBase: queryAll('SELECT * FROM acidosbase'), cultivos: queryAll('SELECT * FROM cultivos'), + estudiosComplementarios: queryAll('SELECT * FROM estudiosComplementarios'), vistaActual: 'dashboard', currentInternacionId: null }; @@ -245,12 +261,19 @@ app.put('/api/state', (req, res) => { } if (state.cultivos?.length) { - const stmt = db.prepare('INSERT INTO cultivos (id, pacienteId, internacionId, fechaToma, protocolo, tipoMuestra, observaciones, estado, fechaResultado, germen, sensible, resistente) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'); + const stmt = db.prepare('INSERT INTO cultivos (id, pacienteId, internacionId, fechaToma, protocolo, tipoMuestra, observaciones, estado, fechaResultado, germen, culpable, resistente) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'); for (const c of state.cultivos) { stmt.run(c.id, c.pacienteId, c.internacionId, c.fechaToma, c.protocolo, c.tipoMuestra, c.observaciones, c.estado, c.fechaResultado, c.germen, c.sensible, c.resistente); } } + if (state.estudiosComplementarios?.length) { + const stmt = db.prepare('INSERT INTO estudiosComplementarios (id, pacienteId, internacionId, fecha, tipo, resultado) VALUES (?, ?, ?, ?, ?, ?)'); + for (const e of state.estudiosComplementarios) { + stmt.run(e.id, e.pacienteId, e.internacionId || null, e.fecha, e.tipo, e.resultado); + } + } + res.json({ ok: true }); }); diff --git a/src/App.tsx b/src/App.tsx index 7fa7c64..a0345d4 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -143,6 +143,7 @@ function AppContent() { laboratorios={store.getLaboratoriosByPaciente(paciente.id)} acidosBase={store.getAcidosBaseByPaciente(paciente.id)} cultivos={store.getCultivosByPaciente(paciente.id)} + estudiosComplementarios={store.estudiosComplementarios.filter(e => e.pacienteId === paciente.id)} onAgregarEvolucion={store.agregarEvolucion} onActualizarEvolucion={store.actualizarEvolucion} onEliminarEvolucion={store.eliminarEvolucion} @@ -154,6 +155,8 @@ function AppContent() { onAgregarCultivo={(c) => store.agregarCultivo({ ...c, internacionId: internacion.id })} onActualizarCultivo={store.actualizarCultivo} onEliminarCultivo={store.eliminarCultivo} + onAgregarEstudioComplementario={(e) => store.agregarEstudioComplementario({ ...e, internacionId: internacion.id })} + onEliminarEstudioComplementario={store.eliminarEstudioComplementario} onActualizarInternacion={store.actualizarInternacion} onActualizarCama={store.actualizarCama} onVolver={() => store.setVista('internaciones')} diff --git a/src/hooks/useHospitalStore.ts b/src/hooks/useHospitalStore.ts index b2a994b..685f573 100644 --- a/src/hooks/useHospitalStore.ts +++ b/src/hooks/useHospitalStore.ts @@ -8,6 +8,7 @@ import type { Laboratorio, AcidoBase, Cultivo, + EstudioComplementario, Vista } from '@/types'; @@ -33,6 +34,7 @@ interface HospitalState { laboratorios: Laboratorio[]; acidosBase: AcidoBase[]; cultivos: Cultivo[]; + estudiosComplementarios: EstudioComplementario[]; vistaActual: Vista; currentInternacionId?: string | null; } @@ -48,6 +50,7 @@ const defaultState = (): HospitalState => ({ laboratorios: [], acidosBase: [], cultivos: [], + estudiosComplementarios: [], vistaActual: 'dashboard', camas: [] }); @@ -312,6 +315,33 @@ const finalizarInternacion = useCallback((internacionId: string, datos: { })); }, []); + // Acciones de estudios complementarios + const agregarEstudioComplementario = useCallback((estudio: Omit) => { + const nuevoEstudio: EstudioComplementario = { + ...estudio, + id: generateUUID(), + }; + setState(prev => ({ + ...prev, + estudiosComplementarios: [...prev.estudiosComplementarios, nuevoEstudio], + })); + return nuevoEstudio.id; + }, []); + + const actualizarEstudioComplementario = useCallback((id: string, datos: Partial) => { + setState(prev => ({ + ...prev, + estudiosComplementarios: prev.estudiosComplementarios.map(e => e.id === id ? { ...e, ...datos } : e), + })); + }, []); + + const eliminarEstudioComplementario = useCallback((id: string) => { + setState(prev => ({ + ...prev, + estudiosComplementarios: prev.estudiosComplementarios.filter(e => e.id !== id), + })); + }, []); + // Acciones de areas const agregarArea = useCallback((area: Omit) => { const nuevaArea: Area = { ...area, id: generateUUID() }; @@ -431,6 +461,9 @@ const getEstadisticas = useCallback(() => { agregarCultivo, actualizarCultivo, eliminarCultivo, + agregarEstudioComplementario, + actualizarEstudioComplementario, + eliminarEstudioComplementario, agregarArea, actualizarArea, eliminarArea, diff --git a/src/index.css b/src/index.css index 0056c43..deec11c 100644 --- a/src/index.css +++ b/src/index.css @@ -5,62 +5,62 @@ @layer base { :root { --background: 0 0% 100%; - --foreground: 240 10% 3.9%; + --foreground: 222.2 84% 4.9%; --card: 0 0% 100%; - --card-foreground: 240 10% 3.9%; + --card-foreground: 222.2 84% 4.9%; --popover: 0 0% 100%; - --popover-foreground: 240 10% 3.9%; - --primary: 240 5.9% 10%; - --primary-foreground: 0 0% 98%; - --secondary: 240 4.8% 95.9%; - --secondary-foreground: 240 5.9% 10%; - --muted: 240 4.8% 95.9%; - --muted-foreground: 240 3.8% 46.1%; - --accent: 240 4.8% 95.9%; - --accent-foreground: 240 5.9% 10%; + --popover-foreground: 222.2 84% 4.9%; + --primary: 222.2 47.4% 11.2%; + --primary-foreground: 210 40% 98%; + --secondary: 210 40% 96.1%; + --secondary-foreground: 222.2 47.4% 11.2%; + --muted: 210 40% 96.1%; + --muted-foreground: 215.4 16.3% 46.9%; + --accent: 210 40% 96.1%; + --accent-foreground: 222.2 47.4% 11.2%; --destructive: 0 84.2% 60.2%; - --destructive-foreground: 0 0% 98%; - --border: 240 5.9% 90%; - --input: 240 5.9% 90%; - --ring: 240 5.9% 10%; + --destructive-foreground: 210 40% 98%; + --border: 214.3 31.8% 91.4%; + --input: 214.3 31.8% 91.4%; + --ring: 222.2 84% 4.9%; --radius: 0.625rem; --sidebar-background: 0 0% 98%; - --sidebar-foreground: 240 5.3% 26.1%; - --sidebar-primary: 240 5.9% 10%; - --sidebar-primary-foreground: 0 0% 98%; - --sidebar-accent: 240 4.8% 95.9%; - --sidebar-accent-foreground: 240 5.9% 10%; - --sidebar-border: 220 13% 91%; + --sidebar-foreground: 222.2 84% 4.9%; + --sidebar-primary: 222.2 47.4% 11.2%; + --sidebar-primary-foreground: 210 40% 98%; + --sidebar-accent: 210 40% 96.1%; + --sidebar-accent-foreground: 222.2 47.4% 11.2%; + --sidebar-border: 214.3 31.8% 91.4%; --sidebar-ring: 217.2 91.2% 59.8%; } .dark { - --background: 240 10% 3.9%; - --foreground: 0 0% 98%; - --card: 240 10% 3.9%; - --card-foreground: 0 0% 98%; - --popover: 240 10% 3.9%; - --popover-foreground: 0 0% 98%; - --primary: 0 0% 98%; - --primary-foreground: 240 5.9% 10%; - --secondary: 240 3.7% 15.9%; - --secondary-foreground: 0 0% 98%; - --muted: 240 3.7% 15.9%; - --muted-foreground: 240 5% 64.9%; - --accent: 240 3.7% 15.9%; - --accent-foreground: 0 0% 98%; + --background: 222.2 84% 4.9%; + --foreground: 210 40% 98%; + --card: 222.2 84% 4.9%; + --card-foreground: 210 40% 98%; + --popover: 222.2 84% 4.9%; + --popover-foreground: 210 40% 98%; + --primary: 210 40% 98%; + --primary-foreground: 222.2 47.4% 11.2%; + --secondary: 217.2 32.6% 17.5%; + --secondary-foreground: 210 40% 98%; + --muted: 217.2 32.6% 17.5%; + --muted-foreground: 215 20.2% 65.1%; + --accent: 217.2 32.6% 17.5%; + --accent-foreground: 210 40% 98%; --destructive: 0 62.8% 30.6%; - --destructive-foreground: 0 0% 98%; - --border: 240 3.7% 15.9%; - --input: 240 3.7% 15.9%; - --ring: 240 4.9% 83.9%; - --sidebar-background: 240 5.9% 10%; - --sidebar-foreground: 240 4.8% 95.9%; - --sidebar-primary: 224.3 76.3% 48%; - --sidebar-primary-foreground: 0 0% 100%; - --sidebar-accent: 240 3.7% 15.9%; - --sidebar-accent-foreground: 240 4.8% 95.9%; - --sidebar-border: 240 3.7% 15.9%; + --destructive-foreground: 210 40% 98%; + --border: 217.2 32.6% 17.5%; + --input: 217.2 32.6% 17.5%; + --ring: 212.7 26.8% 83.9%; + --sidebar-background: 222.2 84% 4.9%; + --sidebar-foreground: 210 40% 98%; + --sidebar-primary: 210 40% 98%; + --sidebar-primary-foreground: 222.2 47.4% 11.2%; + --sidebar-accent: 217.2 32.6% 17.5%; + --sidebar-accent-foreground: 210 40% 98%; + --sidebar-border: 217.2 32.6% 17.5%; --sidebar-ring: 217.2 91.2% 59.8%; } } diff --git a/src/sections/HistoriaClinica.tsx b/src/sections/HistoriaClinica.tsx index b89cf85..192eead 100644 --- a/src/sections/HistoriaClinica.tsx +++ b/src/sections/HistoriaClinica.tsx @@ -39,7 +39,7 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/ui/dropdown-menu'; import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts'; -import type { Laboratorio, AcidoBase, Cultivo, Paciente, ResultadoLaboratorio, Internacion, Evolucion, Cama } from '@/types'; +import type { Laboratorio, AcidoBase, Cultivo, Paciente, ResultadoLaboratorio, Internacion, Evolucion, Cama, EstudioComplementario } from '@/types'; import { formatDateDDMMYYYY } from '@/lib/utils'; interface HistoriaClinicaProps { @@ -51,6 +51,7 @@ interface HistoriaClinicaProps { laboratorios: Laboratorio[]; acidosBase: AcidoBase[]; cultivos: Cultivo[]; + estudiosComplementarios: EstudioComplementario[]; onAgregarEvolucion: (evolucion: Omit) => void; onActualizarEvolucion: (id: string, datos: Partial) => void; onEliminarEvolucion: (id: string) => void; @@ -62,6 +63,8 @@ interface HistoriaClinicaProps { onAgregarCultivo: (cultivo: Omit) => void; onActualizarCultivo: (id: string, datos: Partial) => void; onEliminarCultivo: (id: string) => void; + onAgregarEstudioComplementario: (estudio: Omit) => void; + onEliminarEstudioComplementario: (id: string) => void; onActualizarInternacion: (id: string, datos: Partial) => void; onActualizarCama: (id: string, datos: Partial) => void; onVolver: () => void; @@ -148,6 +151,7 @@ export function HistoriaClinica({ laboratorios, acidosBase, cultivos, + estudiosComplementarios, onAgregarEvolucion, onActualizarEvolucion, onEliminarEvolucion, @@ -159,6 +163,8 @@ export function HistoriaClinica({ onAgregarCultivo, onActualizarCultivo, onEliminarCultivo, + onAgregarEstudioComplementario, + onEliminarEstudioComplementario, onActualizarInternacion, onActualizarCama, onVolver, @@ -509,6 +515,12 @@ export function HistoriaClinica({ Cult ({cultivos.length}) + + + Estudios + Est + ({estudiosComplementarios.length}) + @@ -526,6 +538,16 @@ export function HistoriaClinica({ + + + + ); @@ -1011,10 +1033,13 @@ if (!esUnidadSiguiente) { - {labs.length === 0 ?

No hay laboratorios

: -
- - + {labs.length === 0 ? ( +

No hay laboratorios

+ ) : ( +
+ +
+ Fecha Hto @@ -1085,8 +1110,9 @@ if (!esUnidadSiguiente) { ))}
-
- } + + + )} ); } @@ -1657,4 +1683,130 @@ const resetRes = () => { } ); +} + +function SeccionEstudiosComplementarios({ estudios, internacionId, pacienteId, add, del }: { + estudios: EstudioComplementario[]; + internacionId: string; + pacienteId: string; + add: (e: Omit) => void; + del: (id: string) => void; +}) { + const [dialog, setDialog] = useState(false); + const [filtro, setFiltro] = useState<'todos' | 'internacion'>('internacion'); + const [fecha, setFecha] = useState(new Date().toISOString().split('T')[0]); + const [tipo, setTipo] = useState(''); + const [resultado, setResultado] = useState(''); + + const estudiosFiltrados = filtro === 'internacion' + ? estudios.filter(e => e.internacionId === internacionId) + : estudios; + + const reset = () => { + setFecha(new Date().toISOString().split('T')[0]); + setTipo(''); + setResultado(''); + }; + + const handleGuardar = () => { + if (!tipo || !resultado) return; + add({ + pacienteId, + internacionId, + fecha, + tipo, + resultado, + }); + setDialog(false); + reset(); + }; + + return ( +
+
+
+ +
+ +
+ + + + + Nuevo Estudio Complementario + +
+
+ + setFecha(e.target.value)} /> +
+
+ + setTipo(e.target.value)} + placeholder="Ej: Radiografía de tórax, ECG, Tomografía, etc." + /> +
+
+ +