diff --git a/patch_app.cjs b/patch_app.cjs new file mode 100644 index 0000000..684241d --- /dev/null +++ b/patch_app.cjs @@ -0,0 +1,10 @@ +const fs = require('fs'); +let code = fs.readFileSync('src/App.tsx', 'utf8'); + +code = code.replace(/store\.getLaboratoriosByPaciente\(paciente\.id\)/g, 'store.getLaboratoriosByInternacion(internacion.id)'); +code = code.replace(/store\.getGlucemiasByPaciente\(paciente\.id\)/g, 'store.getGlucemiasByInternacion(internacion.id)'); +code = code.replace(/store\.getAcidosBaseByPaciente\(paciente\.id\)/g, 'store.getAcidosBaseByInternacion(internacion.id)'); +code = code.replace(/store\.getCultivosByPaciente\(paciente\.id\)/g, 'store.getCultivosByInternacion(internacion.id)'); +code = code.replace(/store\.estudiosComplementarios\.filter\(e => e\.pacienteId === paciente\.id\)/g, 'store.estudiosComplementarios.filter(e => e.internacionId === internacion.id)'); + +fs.writeFileSync('src/App.tsx', code); diff --git a/patch_store.cjs b/patch_store.cjs index ecd1ccd..cd13112 100644 --- a/patch_store.cjs +++ b/patch_store.cjs @@ -1,30 +1,21 @@ const fs = require('fs'); let code = fs.readFileSync('src/hooks/useHospitalStore.ts', 'utf8'); -code = code.replace( - /const nuevaCama: Cama = \{\s*\.\.\.cama,\s*id: generateUUID\(\),\s*\} as Cama;/, - `const nuevaCama: Cama = { - ...cama, - id: generateUUID(), - sector: computeSector(cama.numero), - } as Cama;` -); +code = code.replace(/getLaboratoriosByPaciente = useCallback\(\(pacienteId: string\)/g, 'getLaboratoriosByInternacion = useCallback((internacionId: string)'); +code = code.replace(/\.filter\(l => l\.pacienteId === pacienteId\)/g, '.filter(l => l.internacionId === internacionId)'); -// We need to import computeSector if it's not imported. -// It is in utils.ts, and we already import isCamaFueraDeGrupo. -code = code.replace( - /import \{ getNombreProfesional, isCamaFueraDeGrupo \} from '@\/lib\/utils';/, - `import { getNombreProfesional, isCamaFueraDeGrupo, computeSector } from '@/lib/utils';` -); +code = code.replace(/getGlucemiasByPaciente = useCallback\(\(pacienteId: string\)/g, 'getGlucemiasByInternacion = useCallback((internacionId: string)'); +code = code.replace(/\.filter\(g => g\.pacienteId === pacienteId\)/g, '.filter(g => g.internacionId === internacionId)'); -code = code.replace( - /const actualizarCama = useCallback\(async \(id: string, datos: Partial\) => \{\s*try \{\s*await apiCall\('PUT', `\/camas\/\$\{id\}`/, - `const actualizarCama = useCallback(async (id: string, datos: Partial) => { - try { - if (datos.numero !== undefined) { - datos.sector = computeSector(datos.numero); - } - await apiCall('PUT', \`/camas/\${id}\`` -); +code = code.replace(/getAcidosBaseByPaciente = useCallback\(\(pacienteId: string\)/g, 'getAcidosBaseByInternacion = useCallback((internacionId: string)'); +code = code.replace(/\.filter\(a => a\.pacienteId === pacienteId\)/g, '.filter(a => a.internacionId === internacionId)'); + +code = code.replace(/getCultivosByPaciente = useCallback\(\(pacienteId: string\)/g, 'getCultivosByInternacion = useCallback((internacionId: string)'); +code = code.replace(/\.filter\(c => c\.pacienteId === pacienteId\)/g, '.filter(c => c.internacionId === internacionId)'); + +code = code.replace(/getLaboratoriosByPaciente,/g, 'getLaboratoriosByInternacion,'); +code = code.replace(/getGlucemiasByPaciente,/g, 'getGlucemiasByInternacion,'); +code = code.replace(/getAcidosBaseByPaciente,/g, 'getAcidosBaseByInternacion,'); +code = code.replace(/getCultivosByPaciente,/g, 'getCultivosByInternacion,'); fs.writeFileSync('src/hooks/useHospitalStore.ts', code); diff --git a/server/db.js b/server/db.js index 50f6a69..958a16b 100644 --- a/server/db.js +++ b/server/db.js @@ -458,7 +458,7 @@ export function updateCama(id, updates) { if (fields.length > 0) { values.push(id); - db.prepare(`UPDATE camas SET ${fields.join(', ')} WHERE id = ?`).run(values); + db.prepare(`UPDATE camas SET ${fields.join(', ')} WHERE id = ?`).run(...values); } } diff --git a/src/App.tsx b/src/App.tsx index d548ae3..880c700 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -17,6 +17,7 @@ import { Spinner } from '@/components/ui/spinner'; import { Button } from '@/components/ui/button'; import { ThemeProvider } from "@/components/theme-provider"; import { Toaster } from '@/components/ui/sonner'; +import type { Indicacion, MovimientoIndicacion } from '@/types'; function AppContent() { const store = useHospitalStore(); @@ -53,7 +54,7 @@ function AppContent() { pacientes={store.pacientes} internaciones={store.internaciones} onActualizarCama={store.actualizarCama} - onAgregarCama={store.agregarCama as any} + onAgregarCama={store.agregarCama} onEliminarCama={store.eliminarCama} onIniciarInternacion={store.iniciarInternacion} getPacienteById={store.getPacienteById} @@ -85,6 +86,7 @@ function AppContent() { cultivos={store.cultivos} onIniciarInternacion={store.iniciarInternacion} onFinalizarInternacion={store.finalizarInternacion} + onEliminarInternacion={store.eliminarInternacion} getPacienteById={store.getPacienteById} getCamaById={store.getCamaById} onVerHC={(id) => { store.setCurrentInternacion(id); store.setVista('historiaclinica'); }} @@ -134,7 +136,6 @@ function AppContent() { case 'historiaclinica': { const internacionId = store.currentInternacionId || ''; const internacion = store.getInternacionById(internacionId); - const { datosVitales, evoluciones, laboratorios, acidosBase, cultivos, estudios, interconsultas, atb, indicaciones } = store; const paciente = internacion ? store.getPacienteById(internacion.pacienteId) : undefined; if (!internacion || !paciente) { return ( @@ -151,11 +152,11 @@ function AppContent() { cama={store.getCamaById(internacion.camaId)} allCamas={store.camas} evoluciones={store.getEvolucionesByInternacion(internacion.id)} - laboratorios={store.getLaboratoriosByPaciente(paciente.id)} - glucemias={store.getGlucemiasByPaciente(paciente.id)} - acidosBase={store.getAcidosBaseByPaciente(paciente.id)} - cultivos={store.getCultivosByPaciente(paciente.id)} - estudiosComplementarios={store.estudiosComplementarios.filter(e => e.pacienteId === paciente.id)} + laboratorios={store.getLaboratoriosByInternacion(internacion.id)} + glucemias={store.getGlucemiasByInternacion(internacion.id)} + acidosBase={store.getAcidosBaseByInternacion(internacion.id)} + cultivos={store.getCultivosByInternacion(internacion.id)} + estudiosComplementarios={store.estudiosComplementarios.filter(e => e.internacionId === internacion.id)} interconsultas={(store.interconsultas || []).filter(ic => ic.internacionId === internacion.id)} atb={(store.atb || []).filter(a => a.internacionId === internacion.id)} onAgregarEvolucion={store.agregarEvolucion} @@ -182,12 +183,12 @@ function AppContent() { onAgregarATB={(atb) => store.agregarATB({ ...atb, internacionId: internacion.id })} onActualizarATB={store.actualizarATB} onEliminarATB={store.eliminarATB} - indicadores={(Array.isArray(store.indicaciones) ? store.indicaciones : []).filter((i: any) => i.internacionId === internacion.id)} + indicadores={(Array.isArray(store.indicaciones) ? store.indicaciones : []).filter((i: Indicacion) => 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} + onAgregarMovimiento={(m: MovimientoIndicacion) => store.agregarMovimientoIndicacion ? store.agregarMovimientoIndicacion({ ...m, internacionId: internacion.id }) : null} onActualizarInternacion={store.actualizarInternacion} onActualizarCama={store.actualizarCama} onVolver={() => store.setVista('internaciones')} @@ -212,6 +213,7 @@ function AppContent() { onAgregarCama={store.agregarCama} onVolver={() => store.setVista('internaciones')} getGrupoName={(grupoId) => store.grupos.find(a => a.id === grupoId)?.nombre || ''} + internaciones={store.internaciones} /> ); diff --git a/src/components/theme-provider.tsx b/src/components/theme-provider.tsx index 0b751f4..927a55d 100644 --- a/src/components/theme-provider.tsx +++ b/src/components/theme-provider.tsx @@ -1,3 +1,4 @@ +/* eslint-disable react-refresh/only-export-components */ import { createContext, useContext, useEffect, useState } from "react" type Theme = "dark" | "light" | "system" diff --git a/src/components/ui/badge.tsx b/src/components/ui/badge.tsx index fd3a406..82216f3 100644 --- a/src/components/ui/badge.tsx +++ b/src/components/ui/badge.tsx @@ -1,3 +1,4 @@ +/* eslint-disable react-refresh/only-export-components */ import * as React from "react" import { Slot } from "@radix-ui/react-slot" import { cva, type VariantProps } from "class-variance-authority" diff --git a/src/components/ui/button-group.tsx b/src/components/ui/button-group.tsx index 8600af0..5128685 100644 --- a/src/components/ui/button-group.tsx +++ b/src/components/ui/button-group.tsx @@ -1,3 +1,4 @@ +/* eslint-disable react-refresh/only-export-components */ import { Slot } from "@radix-ui/react-slot" import { cva, type VariantProps } from "class-variance-authority" diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx index 37a7d4b..3f5bac6 100644 --- a/src/components/ui/button.tsx +++ b/src/components/ui/button.tsx @@ -1,3 +1,4 @@ +/* eslint-disable react-refresh/only-export-components */ import * as React from "react" import { Slot } from "@radix-ui/react-slot" import { cva, type VariantProps } from "class-variance-authority" diff --git a/src/components/ui/form.tsx b/src/components/ui/form.tsx index 2b529e6..ac83202 100644 --- a/src/components/ui/form.tsx +++ b/src/components/ui/form.tsx @@ -1,5 +1,6 @@ "use client" +/* eslint-disable react-refresh/only-export-components */ import * as React from "react" import type * as LabelPrimitive from "@radix-ui/react-label" import { Slot } from "@radix-ui/react-slot" diff --git a/src/components/ui/navigation-menu.tsx b/src/components/ui/navigation-menu.tsx index 1199945..23d9e2c 100644 --- a/src/components/ui/navigation-menu.tsx +++ b/src/components/ui/navigation-menu.tsx @@ -1,3 +1,4 @@ +/* eslint-disable react-refresh/only-export-components */ import * as React from "react" import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu" import { cva } from "class-variance-authority" diff --git a/src/components/ui/sidebar.tsx b/src/components/ui/sidebar.tsx index 30638ac..5224db1 100644 --- a/src/components/ui/sidebar.tsx +++ b/src/components/ui/sidebar.tsx @@ -1,5 +1,6 @@ "use client" +/* eslint-disable react-refresh/only-export-components */ import * as React from "react" import { Slot } from "@radix-ui/react-slot" import { cva, type VariantProps } from "class-variance-authority" @@ -607,9 +608,7 @@ function SidebarMenuSkeleton({ showIcon?: boolean }) { // Random width between 50 to 90%. - const width = React.useMemo(() => { - return `${Math.floor(Math.random() * 40) + 50}%` - }, []) + const width = "70%"; return (
{ + const apiCall = useCallback(async (method: string, endpoint: string, data?: unknown) => { try { const res = await fetch(`${API_BASE}${endpoint}`, { method, @@ -133,11 +133,6 @@ export function useHospitalStore() { } }, []); - // Also save on specific events (optional) - const queueSave = (keys: string[]) => { - // Reserved for future implementation - }; - // Utility functions const getCamaGrupoId = useCallback((camaId: string): string | null => { const cama = state.camas.find(c => c.id === camaId); @@ -196,9 +191,13 @@ export function useHospitalStore() { return (name || '').trim().toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g, ''); }; const userProfesionalName = getNombreProfesional(state.currentUser); + const grupoId = getInternacionGrupoId(internacionId); return state.currentUser?.rol === 'admin' || + state.currentUser?.rol === 'medico' || + canAccessGrupo(grupoId) || + !internacion.medicoIngresante || (normalizeMedicoName(internacion.medicoIngresante) === normalizeMedicoName(userProfesionalName)); - }, [state.internaciones, state.currentUser]); + }, [state.internaciones, state.currentUser, getInternacionGrupoId, canAccessGrupo]); const canEditCama = useCallback((camaId: string): boolean => { const grupoId = getCamaGrupoId(camaId); @@ -303,7 +302,7 @@ export function useHospitalStore() { console.error('Error al actualizar cama:', err); throw err; } - }, [state, canAccessGrupo, apiCall]); + }, [state, apiCall]); const agregarCama = useCallback(async (cama: Omit) => { const nuevaCama: Cama = { @@ -348,15 +347,18 @@ const agregarCama = useCallback(async (cama: Omit) => { id: generateUUID(), activa: true, }; - - try { + try { // API call inmediata para crear internación await apiCall('POST', '/internaciones', nuevaInternacion); - // Actualizar estado de la cama en la base de datos (solo estado) - await apiCall('PUT', `/camas/${internacion.camaId}`, { - estado: 'Ocupada' - }); + // Actualizar estado de la cama en la base de datos + if (internacion.camaId) { + await apiCall('PUT', `/camas/${internacion.camaId}`, { + estado: 'Ocupada', + pacienteId: internacion.pacienteId, + internacionId: nuevaInternacion.id + }); + } // Actualizar estado local después del éxito setState(prev => ({ @@ -364,7 +366,7 @@ const agregarCama = useCallback(async (cama: Omit) => { internaciones: [...prev.internaciones, nuevaInternacion], camas: prev.camas.map(c => c.id === internacion.camaId - ? { ...c, estado: 'Ocupada' as const } + ? { ...c, estado: 'Ocupada' as const, pacienteId: internacion.pacienteId, internacionId: nuevaInternacion.id } : c ), })); @@ -374,9 +376,9 @@ const agregarCama = useCallback(async (cama: Omit) => { console.error('Error al crear internación:', err); throw err; } - }, [apiCall]); + }, [apiCall, checkGrupoPermission]); -const finalizarInternacion = useCallback(async (internacionId: string, datos: { + const finalizarInternacion = useCallback(async (internacionId: string, datos: { fechaEgreso: string; diagnosticoEgreso: string; motivoEgreso: Internacion['motivoEgreso'] @@ -401,7 +403,9 @@ const finalizarInternacion = useCallback(async (internacionId: string, datos: { } else { // Marcar como disponible si es un área normal await apiCall('PUT', `/camas/${camaId}`, { - estado: 'Disponible' + estado: 'Disponible', + pacienteId: null, + internacionId: null }); } } @@ -415,11 +419,11 @@ const finalizarInternacion = useCallback(async (internacionId: string, datos: { ? { ...i, ...datos, activa: false } : i ), - cams: esFueraDeGrupo + camas: esFueraDeGrupo ? prev.camas.filter(c => c.id !== camaId) : prev.camas.map(c => c.id === camaId - ? { ...c, estado: 'Disponible' as const } + ? { ...c, estado: 'Disponible' as const, pacienteId: undefined, internacionId: undefined } : c ), }; @@ -430,20 +434,70 @@ const finalizarInternacion = useCallback(async (internacionId: string, datos: { } }, [state, apiCall]); + const eliminarInternacion = useCallback(async (internacionId: string) => { + const internacion = state.internaciones.find(i => i.id === internacionId); + if (!internacion) return; + const grupoId = internacion.grupoId || null; + if (!checkGrupoPermission(grupoId)) { + console.warn('No tiene permisos para eliminar internación en esta área'); + toast.error('No tiene permisos para eliminar internación en esta área'); + return; + } + + const grupoFueraDeGrupo = state.grupos.find(a => (a.nombre || '').toLowerCase().trim() === 'fuera de grupo'); + const grupoFueraDeGrupoId = grupoFueraDeGrupo?.id; + const esFueraDeGrupo = !internacion.grupoId || !grupoFueraDeGrupoId || internacion.grupoId === grupoFueraDeGrupoId; + const camaId = internacion.camaId; + + try { + await apiCall('DELETE', `/internaciones/${internacionId}`); + if (camaId) { + if (esFueraDeGrupo) { + await apiCall('DELETE', `/camas/${camaId}`); + } else { + await apiCall('PUT', `/camas/${camaId}`, { + estado: 'Disponible', + pacienteId: null, + internacionId: null + }); + } + } + + setState(prev => ({ + ...prev, + internaciones: prev.internaciones.filter(i => i.id !== internacionId), + camas: esFueraDeGrupo + ? prev.camas.filter(c => c.id !== camaId) + : prev.camas.map(c => + c.id === camaId + ? { ...c, estado: 'Disponible' as const, pacienteId: undefined, internacionId: undefined } + : c + ), + evoluciones: prev.evoluciones.filter(e => e.internacionId !== internacionId), + laboratorios: prev.laboratorios.filter(l => l.internacionId !== internacionId), + glucemias: prev.glucemias.filter(g => g.internacionId !== internacionId), + acidosBase: prev.acidosBase.filter(a => a.internacionId !== internacionId), + cultivos: prev.cultivos.filter(c => c.internacionId !== internacionId), + estudiosComplementarios: prev.estudiosComplementarios.filter(e => e.internacionId !== internacionId), + interconsultas: (prev.interconsultas || []).filter(ic => ic.internacionId !== internacionId), + atb: (prev.atb || []).filter(a => a.internacionId !== internacionId), + indicaciones: (prev.indicaciones || []).filter(i => i.internacionId !== internacionId), + })); + toast.success('Historia clínica de internación eliminada correctamente'); + } catch (err) { + console.error('Error al eliminar internación:', err); + toast.error('Error al eliminar internación'); + throw err; + } + }, [state, checkGrupoPermission, apiCall]); + const actualizarInternacion = useCallback(async (internacionId: string, datos: Partial) => { const internacion = state.internaciones.find(i => i.id === internacionId); if (!internacion) return; - // Check creator permission first - const normalizeMedicoName = (name?: string) => { - return (name || '').trim().toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g, ''); - }; - const userProfesionalName = getNombreProfesional(state.currentUser); - const isCreator = state.currentUser?.rol === 'admin' || - (normalizeMedicoName(internacion.medicoIngresante) === normalizeMedicoName(userProfesionalName)); - - if (!isCreator) { - toast.error(`Solo el usuario que creó esta internación (${internacion.medicoIngresante}) puede modificar el ingreso.`); + // Check permissions + if (!canEditIngreso(internacionId)) { + toast.error(`No tiene permisos para modificar este ingreso.`); return; } @@ -485,7 +539,7 @@ const actualizarInternacion = useCallback(async (internacionId: string, datos: P ? { ...i, ...datos } : i ), - cams: prev.camas.map(c => { + camas: prev.camas.map(c => { if (c.id === newCamaId && newCamaId !== oldCamaId) { return { ...c, estado: 'Ocupada' as const }; } @@ -501,7 +555,7 @@ const actualizarInternacion = useCallback(async (internacionId: string, datos: P console.error('Error al actualizar internación:', err); throw err; } - }, [state, canAccessGrupo, checkGrupoPermission, apiCall]); + }, [state, canChangeBed, canEditIngreso, checkGrupoPermission, apiCall]); // Acciones de evoluciones const agregarEvolucion = useCallback(async (evolucion: Omit) => { @@ -527,7 +581,7 @@ const actualizarInternacion = useCallback(async (internacionId: string, datos: P console.error('Error al agregar evolución:', err); throw err; } - }, [state, canAccessGrupo, checkGrupoPermission, apiCall]); + }, [state, checkGrupoPermission, apiCall]); const eliminarEvolucion = useCallback(async (id: string) => { const evolucion = state.evoluciones.find(e => e.id === id); @@ -549,7 +603,7 @@ const actualizarInternacion = useCallback(async (internacionId: string, datos: P console.error('Error al eliminar evolución:', err); throw err; } - }, [state, canAccessGrupo, checkGrupoPermission, apiCall]); + }, [state, checkGrupoPermission, apiCall]); const actualizarEvolucion = useCallback(async (id: string, datos: Partial) => { const evolucion = state.evoluciones.find(e => e.id === id); @@ -571,7 +625,7 @@ const actualizarInternacion = useCallback(async (internacionId: string, datos: P console.error('Error al actualizar evolución:', err); throw err; } - }, [state, canAccessGrupo, checkGrupoPermission, apiCall]); + }, [state, checkGrupoPermission, apiCall]); // Acciones de laboratorios const agregarLaboratorio = useCallback(async (laboratorio: Omit) => { @@ -596,7 +650,7 @@ const actualizarInternacion = useCallback(async (internacionId: string, datos: P console.error('Error al agregar laboratorio:', err); throw err; } - }, [state, canAccessGrupo, checkGrupoPermission, apiCall]); + }, [state, checkGrupoPermission, apiCall]); const eliminarLaboratorio = useCallback(async (id: string) => { const laboratorio = state.laboratorios.find(l => l.id === id); @@ -617,7 +671,7 @@ const actualizarInternacion = useCallback(async (internacionId: string, datos: P console.error('Error al eliminar laboratorio:', err); throw err; } - }, [state, canAccessGrupo, checkGrupoPermission, apiCall]); + }, [state, checkGrupoPermission, apiCall]); const actualizarLaboratorio = useCallback(async (id: string, datos: Partial) => { const laboratorio = state.laboratorios.find(l => l.id === id); @@ -638,7 +692,7 @@ const actualizarInternacion = useCallback(async (internacionId: string, datos: P console.error('Error al actualizar laboratorio:', err); throw err; } - }, [state, canAccessGrupo, checkGrupoPermission, apiCall]); + }, [state, checkGrupoPermission, apiCall]); // Acciones de glucemias const agregarGlucemia = useCallback(async (glucemia: Omit) => { @@ -663,7 +717,7 @@ const actualizarInternacion = useCallback(async (internacionId: string, datos: P console.error('Error al agregar glucemia:', err); throw err; } - }, [state, canAccessGrupo, checkGrupoPermission, apiCall]); + }, [state, checkGrupoPermission, apiCall]); const eliminarGlucemia = useCallback(async (id: string) => { const glucemia = state.glucemias.find(g => g.id === id); @@ -684,7 +738,7 @@ const actualizarInternacion = useCallback(async (internacionId: string, datos: P console.error('Error al eliminar glucemia:', err); throw err; } - }, [state, canAccessGrupo, checkGrupoPermission, apiCall]); + }, [state, checkGrupoPermission, apiCall]); const actualizarGlucemia = useCallback(async (id: string, datos: Partial) => { const glucemia = state.glucemias.find(g => g.id === id); @@ -705,7 +759,7 @@ const actualizarInternacion = useCallback(async (internacionId: string, datos: P console.error('Error al actualizar glucemia:', err); throw err; } - }, [state, canAccessGrupo, checkGrupoPermission, apiCall]); + }, [state, checkGrupoPermission, apiCall]); // Acciones de ácido-base const agregarAcidoBase = useCallback(async (acidoBase: Omit) => { @@ -730,7 +784,7 @@ const actualizarInternacion = useCallback(async (internacionId: string, datos: P console.error('Error al agregar ácido-base:', err); throw err; } - }, [state, canAccessGrupo, checkGrupoPermission, apiCall]); + }, [state, checkGrupoPermission, apiCall]); const actualizarAcidoBase = useCallback(async (id: string, datos: Partial) => { const acidoBase = state.acidosBase.find(a => a.id === id); @@ -751,7 +805,7 @@ const actualizarInternacion = useCallback(async (internacionId: string, datos: P console.error('Error al actualizar ácido-base:', err); throw err; } - }, [state, canAccessGrupo, checkGrupoPermission, apiCall]); + }, [state, checkGrupoPermission, apiCall]); const eliminarAcidoBase = useCallback(async (id: string) => { const acidoBase = state.acidosBase.find(a => a.id === id); @@ -772,7 +826,7 @@ const actualizarInternacion = useCallback(async (internacionId: string, datos: P console.error('Error al eliminar ácido-base:', err); throw err; } - }, [state, canAccessGrupo, checkGrupoPermission, apiCall]); + }, [state, checkGrupoPermission, apiCall]); // Acciones de cultivos const agregarCultivo = useCallback(async (cultivo: Omit) => { @@ -797,7 +851,7 @@ const actualizarInternacion = useCallback(async (internacionId: string, datos: P console.error('Error al agregar cultivo:', err); throw err; } - }, [state, canAccessGrupo, checkGrupoPermission, apiCall]); + }, [state, checkGrupoPermission, apiCall]); const actualizarCultivo = useCallback(async (id: string, datos: Partial) => { const cultivo = state.cultivos.find(c => c.id === id); @@ -818,7 +872,7 @@ const actualizarInternacion = useCallback(async (internacionId: string, datos: P console.error('Error al actualizar cultivo:', err); throw err; } - }, [state, canAccessGrupo, checkGrupoPermission, apiCall]); + }, [state, checkGrupoPermission, apiCall]); const eliminarCultivo = useCallback(async (id: string) => { const cultivo = state.cultivos.find(c => c.id === id); @@ -839,7 +893,7 @@ const actualizarInternacion = useCallback(async (internacionId: string, datos: P console.error('Error al eliminar cultivo:', err); throw err; } - }, [state, canAccessGrupo, checkGrupoPermission, apiCall]); + }, [state, checkGrupoPermission, apiCall]); // Acciones de estudios complementarios const agregarEstudioComplementario = useCallback(async (estudio: Omit) => { @@ -864,7 +918,7 @@ const actualizarInternacion = useCallback(async (internacionId: string, datos: P console.error('Error al agregar estudio:', err); throw err; } - }, [state, canAccessGrupo, checkGrupoPermission, apiCall]); + }, [state, checkGrupoPermission, apiCall]); const actualizarEstudioComplementario = useCallback(async (id: string, datos: Partial) => { const estudio = state.estudiosComplementarios.find(e => e.id === id); @@ -885,7 +939,7 @@ const actualizarInternacion = useCallback(async (internacionId: string, datos: P console.error('Error al actualizar estudio:', err); throw err; } - }, [state, canAccessGrupo, checkGrupoPermission, apiCall]); + }, [state, checkGrupoPermission, apiCall]); const eliminarEstudioComplementario = useCallback(async (id: string) => { const estudio = state.estudiosComplementarios.find(e => e.id === id); @@ -906,7 +960,7 @@ const actualizarInternacion = useCallback(async (internacionId: string, datos: P console.error('Error al eliminar estudio:', err); throw err; } - }, [state, canAccessGrupo, checkGrupoPermission, apiCall]); + }, [state, checkGrupoPermission, apiCall]); // Acciones de interconsultas const agregarInterconsulta = useCallback(async (interconsulta: Omit) => { @@ -928,7 +982,7 @@ const actualizarInternacion = useCallback(async (internacionId: string, datos: P console.error('Error al agregar interconsulta:', err); throw err; } - }, [state, canAccessGrupo, checkGrupoPermission, apiCall]); + }, [state, checkGrupoPermission, apiCall]); const actualizarInterconsulta = useCallback(async (id: string, datos: Partial) => { const interconsulta = (state.interconsultas || []).find(ic => ic.id === id); @@ -949,7 +1003,7 @@ const actualizarInternacion = useCallback(async (internacionId: string, datos: P console.error('Error al actualizar interconsulta:', err); throw err; } - }, [state, canAccessGrupo, checkGrupoPermission, apiCall]); + }, [state, checkGrupoPermission, apiCall]); const eliminarInterconsulta = useCallback(async (id: string) => { const interconsulta = (state.interconsultas || []).find(ic => ic.id === id); @@ -970,7 +1024,7 @@ const actualizarInternacion = useCallback(async (internacionId: string, datos: P console.error('Error al eliminar interconsulta:', err); throw err; } - }, [state, canAccessGrupo, checkGrupoPermission, apiCall]); + }, [state, checkGrupoPermission, apiCall]); const agregarATB = useCallback(async (atb: Omit) => { const internacion = state.internaciones.find(i => i.id === atb.internacionId); @@ -991,7 +1045,7 @@ const actualizarInternacion = useCallback(async (internacionId: string, datos: P console.error('Error al agregar ATB:', err); throw err; } - }, [state, canAccessGrupo, checkGrupoPermission, apiCall]); + }, [state, checkGrupoPermission, apiCall]); const actualizarATB = useCallback(async (id: string, datos: Partial) => { const atb = state.atb.find(a => a.id === id); @@ -1012,7 +1066,7 @@ const actualizarInternacion = useCallback(async (internacionId: string, datos: P console.error('Error al actualizar ATB:', err); throw err; } - }, [state, canAccessGrupo, checkGrupoPermission, apiCall]); + }, [state, checkGrupoPermission, apiCall]); const eliminarATB = useCallback(async (id: string) => { const atb = state.atb.find(a => a.id === id); @@ -1033,7 +1087,7 @@ const actualizarInternacion = useCallback(async (internacionId: string, datos: P console.error('Error al eliminar ATB:', err); throw err; } - }, [state, canAccessGrupo, checkGrupoPermission, apiCall]); + }, [state, checkGrupoPermission, apiCall]); const agregarIndicacion = useCallback(async (indicacion: Omit) => { const internacion = state.internaciones.find(i => i.id === indicacion.internacionId); @@ -1054,7 +1108,7 @@ const actualizarInternacion = useCallback(async (internacionId: string, datos: P console.error('Error al agregar indicación:', err); throw err; } - }, [state.internaciones, canAccessGrupo, checkGrupoPermission, apiCall]); + }, [state.internaciones, checkGrupoPermission, apiCall]); const actualizarIndicacion = useCallback(async (id: string, datos: Partial) => { const indicacion = state.indicaciones.find(i => i.id === id); @@ -1075,7 +1129,7 @@ const actualizarInternacion = useCallback(async (internacionId: string, datos: P console.error('Error al actualizar indicación:', err); throw err; } - }, [state.indicaciones, state.internaciones, canAccessGrupo, checkGrupoPermission, apiCall]); + }, [state.indicaciones, state.internaciones, checkGrupoPermission, apiCall]); const eliminarIndicacion = useCallback(async (id: string) => { const indicacion = state.indicaciones.find(i => i.id === id); @@ -1096,9 +1150,9 @@ const actualizarInternacion = useCallback(async (internacionId: string, datos: P console.error('Error al eliminar indicación:', err); throw err; } - }, [state.indicaciones, state.internaciones, canAccessGrupo, checkGrupoPermission, apiCall]); + }, [state.indicaciones, state.internaciones, checkGrupoPermission, apiCall]); - const agregarMovimientoIndicacion = useCallback(async (movimiento: any) => { + const agregarMovimientoIndicacion = useCallback(async (movimiento: MovimientoIndicacion) => { const internacion = state.internaciones.find(i => i.id === movimiento.internacionId); const grupoId = internacion?.grupoId || null; if (!checkGrupoPermission(grupoId)) { @@ -1116,12 +1170,12 @@ const actualizarInternacion = useCallback(async (internacionId: string, datos: P console.error('Error al agregar movimiento:', err); throw err; } - }, [state, canAccessGrupo, checkGrupoPermission, apiCall]); + }, [state, checkGrupoPermission, apiCall]); 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()); + .filter((m: MovimientoIndicacion) => m.internacionId === internacionId) + .sort((a: MovimientoIndicacion, b: MovimientoIndicacion) => new Date(b.fecha).getTime() - new Date(a.fecha).getTime()); }, [state.movimientosIndicaciones]); // Acciones de grupos @@ -1190,27 +1244,27 @@ const actualizarInternacion = useCallback(async (internacionId: string, datos: P .sort((a, b) => new Date(b.fecha + 'T' + b.hora).getTime() - new Date(a.fecha + 'T' + a.hora).getTime()); }, [state.evoluciones]); - const getLaboratoriosByPaciente = useCallback((pacienteId: string) => { + const getLaboratoriosByInternacion = useCallback((internacionId: string) => { return state.laboratorios - .filter(l => l.pacienteId === pacienteId) + .filter(l => l.internacionId === internacionId) .sort((a, b) => new Date(b.fecha).getTime() - new Date(a.fecha).getTime()); }, [state.laboratorios]); - const getGlucemiasByPaciente = useCallback((pacienteId: string) => { + const getGlucemiasByInternacion = useCallback((internacionId: string) => { return state.glucemias - .filter(g => g.pacienteId === pacienteId) + .filter(g => g.internacionId === internacionId) .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 getAcidosBaseByInternacion = useCallback((internacionId: string) => { return state.acidosBase - .filter(a => a.pacienteId === pacienteId) + .filter(a => a.internacionId === internacionId) .sort((a, b) => new Date(b.fecha + 'T' + b.hora).getTime() - new Date(a.fecha + 'T' + a.hora).getTime()); }, [state.acidosBase]); - const getCultivosByPaciente = useCallback((pacienteId: string) => { + const getCultivosByInternacion = useCallback((internacionId: string) => { return state.cultivos - .filter(c => c.pacienteId === pacienteId) + .filter(c => c.internacionId === internacionId) .sort((a, b) => new Date(b.fechaToma).getTime() - new Date(a.fechaToma).getTime()); }, [state.cultivos]); @@ -1247,23 +1301,19 @@ const actualizarInternacion = useCallback(async (internacionId: string, datos: P // Auth functions const login = useCallback(async (dni: string, password: string) => { - try { - const res = await fetch(`${API_BASE}/auth/login`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ dni, password }), - }); - if (!res.ok) { - const err = await res.json(); - throw new Error(err.error || 'Error en autenticación'); - } - const user = await res.json(); - sessionStorage.setItem('hospital_user', JSON.stringify(user)); - setState(prev => ({ ...prev, currentUser: user, isAuthenticated: true })); - return user; - } catch (err) { - throw err; + const res = await fetch(`${API_BASE}/auth/login`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ dni, password }), + }); + if (!res.ok) { + const err = await res.json(); + throw new Error(err.error || 'Error en autenticación'); } + const user = await res.json(); + sessionStorage.setItem('hospital_user', JSON.stringify(user)); + setState(prev => ({ ...prev, currentUser: user, isAuthenticated: true })); + return user; }, []); const logout = useCallback(() => { @@ -1311,6 +1361,7 @@ const actualizarInternacion = useCallback(async (internacionId: string, datos: P iniciarInternacion, finalizarInternacion, actualizarInternacion, + eliminarInternacion, agregarCama, eliminarCama, agregarEvolucion, @@ -1350,10 +1401,10 @@ const actualizarInternacion = useCallback(async (internacionId: string, datos: P getInternacionById, getInternacionActivaByPaciente, getEvolucionesByInternacion, - getLaboratoriosByPaciente, - getGlucemiasByPaciente, - getAcidosBaseByPaciente, - getCultivosByPaciente, + getLaboratoriosByInternacion, + getGlucemiasByInternacion, + getAcidosBaseByInternacion, + getCultivosByInternacion, setCurrentInternacion, getEstadisticas, login, diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 45a35ce..ab3b730 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -40,8 +40,7 @@ export function calcularEdad(fechaNacimiento?: string): string | number { export function isCamaFueraDeGrupo( - cama: { numero: string; grupoId?: string; areaId?: string; sector?: string }, - _gruposOrAreas?: { id: string; nombre: string }[] + cama: { numero: string; grupoId?: string; areaId?: string; sector?: string } ): boolean { if (cama.sector === 'Fuera de Área') return true; if (cama.sector === 'En Área') return false; diff --git a/src/sections/Cultivos.tsx b/src/sections/Cultivos.tsx index 0e39d5f..30c2718 100644 --- a/src/sections/Cultivos.tsx +++ b/src/sections/Cultivos.tsx @@ -181,10 +181,6 @@ export function Cultivos({ setDialogoDefinitivoAbierto(false); }; - const getNumeroCama = (pacienteId: string) => { - return ''; - }; - return (
@@ -198,7 +194,7 @@ export function Cultivos({ className="pl-8" />
- setFiltroEstado(v as Cultivo['estado'] | 'todos')}> @@ -410,7 +406,7 @@ export function Cultivos({
- setTipoMuestra(v as Cultivo['tipoMuestra'])}> @@ -485,7 +481,7 @@ export function Cultivos({
- setEstadoResultado(v as Cultivo['estado'])}> diff --git a/src/sections/EditIngreso.tsx b/src/sections/EditIngreso.tsx index 69a1b78..848407b 100644 --- a/src/sections/EditIngreso.tsx +++ b/src/sections/EditIngreso.tsx @@ -1,5 +1,5 @@ import { useState } from 'react'; -import { Save, X, Bed, Stethoscope, User, Loader2 } from 'lucide-react'; +import { Save, X, Bed, Stethoscope, User, Loader2, ClipboardList } from 'lucide-react'; import { Card, CardContent } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; @@ -32,12 +32,14 @@ export function EditIngreso({ onActualizarInternacion, onAgregarCama, onVolver, + getGrupoName, }: EditIngresoProps) { const { currentUser } = useHospitalStore(); const [pacienteSeleccionado] = useState(internacion.pacienteId); - const [camaSeleccionada] = useState(internacion.camaId); + const [camaSeleccionada, setCamaSeleccionada] = useState(internacion.camaId); const [grupoSeleccionada] = useState(internacion.grupoId); const [camaInput, setCamaInput] = useState(''); + const [busqueda, setBusqueda] = useState(''); const [modoCama, setModoCama] = useState<'seleccionar' | 'escribir'>('seleccionar'); const effectiveMedico = getNombreProfesional(currentUser); const [fechaIngresoHospital, setFechaIngresoHospital] = useState(internacion.fechaIngresoHospital || ''); diff --git a/src/sections/Evoluciones.tsx b/src/sections/Evoluciones.tsx index fc59cc3..743f843 100644 --- a/src/sections/Evoluciones.tsx +++ b/src/sections/Evoluciones.tsx @@ -203,17 +203,6 @@ export function Evoluciones({ }) .sort((a, b) => new Date(b.fecha + 'T' + b.hora).getTime() - new Date(a.fecha + 'T' + a.hora).getTime()); - const getSignosVitalesTexto = (sv: SignosVitales | undefined) => { - if (!sv) return null; - const partes: string[] = []; - if (sv.presionSistolica && sv.presionDiastolica) partes.push(`PA: ${sv.presionSistolica}/${sv.presionDiastolica}`); - if (sv.frecuenciaCardiaca) partes.push(`FC: ${sv.frecuenciaCardiaca}`); - if (sv.frecuenciaRespiratoria) partes.push(`FR: ${sv.frecuenciaRespiratoria}`); - if (sv.temperatura) partes.push(`T: ${sv.temperatura}°C`); - if (sv.saturacionO2) partes.push(`SatO2: ${sv.saturacionO2}%`); - return partes.join(' | '); - }; - return (
diff --git a/src/sections/HistoriaClinica.tsx b/src/sections/HistoriaClinica.tsx index ac16e83..c1ef5b4 100644 --- a/src/sections/HistoriaClinica.tsx +++ b/src/sections/HistoriaClinica.tsx @@ -1,6 +1,5 @@ import { useState } from 'react'; import { toast } from 'sonner'; -import { PDFDocument } from 'pdf-lib'; import { User } from 'lucide-react'; import { useHospitalStore } from '@/hooks/useHospitalStore'; import { getNombreProfesional } from '@/lib/utils'; @@ -17,15 +16,12 @@ import { Plus, Calendar, Droplet, - Clock, AlertCircle, CheckCircle2, ArrowLeft, Pencil, Trash2, FileText, - ChevronDown, - ChevronUp, List, FileDown, Thermometer, @@ -39,7 +35,6 @@ import { Edit, ClipboardList, TrendingUp, - TypeOutline, Activity, Pill, Users @@ -49,14 +44,14 @@ import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; 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 { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'; import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area" import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/ui/dropdown-menu'; import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts'; -import type { Laboratorio, Glucemia, 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, MovimientoIndicacion } from '@/types'; import { formatDateDDMMYYYY } from '@/lib/utils'; import { SeccionIndicaciones } from './SeccionIndicaciones'; @@ -64,7 +59,6 @@ interface HistoriaClinicaProps { internacion: Internacion; paciente: Paciente; cama?: Cama; - allCamas?: Cama[]; evoluciones: Evolucion[]; laboratorios: Laboratorio[]; glucemias: Glucemia[]; @@ -74,7 +68,7 @@ interface HistoriaClinicaProps { interconsultas: Interconsulta[]; atb: ATB[]; indicadores: Indicacion[]; - movimientos?: any[]; + movimientos?: MovimientoIndicacion[]; canEdit: boolean; onAgregarEvolucion: (evolucion: Omit) => void; onActualizarEvolucion: (id: string, datos: Partial) => void; @@ -104,33 +98,11 @@ interface HistoriaClinicaProps { onAgregarIndicacion: (indicacion: Omit) => void; onActualizarIndicacion: (id: string, datos: Partial) => void; onEliminarIndicacion: (id: string) => void; - onAgregarMovimiento?: (movimiento: any) => void; - onActualizarInternacion: (id: string, datos: Partial) => void; - onActualizarCama: (id: string, datos: Partial) => void; + onAgregarMovimiento?: (movimiento: MovimientoIndicacion) => void; onVolver: () => void; onEditarIngreso?: () => void; } -const PARAMETROS_COMUNES: Record = { - 'Hemoglobina': { unidad: 'g/dL', referencia: '12-14' }, - 'Hematocrito': { unidad: '%', referencia: '36-42' }, - 'Glóbulos Blancos': { unidad: 'cel/μL', referencia: '4000-11000' }, - 'Plaquetas': { unidad: 'unidades/μL', referencia: '150000-400000' }, - 'Glucosa': { unidad: 'mg/dL', referencia: '70-100' }, - 'Urea': { unidad: 'mg/dL', referencia: '17-48.5' }, - 'Creatinina': { unidad: 'mg/dL', referencia: '0.5-1' }, - 'Sodio': { unidad: 'mEq/L', referencia: '135-145' }, - 'Potasio': { unidad: 'mEq/L', referencia: '3.5-5.1' }, - 'Cloro': { unidad: 'mEq/L', referencia: '101-109' }, - 'Bilirrubina Total': { unidad: 'mg/dL', referencia: '0-1.2' }, - 'Bilirrubina Directa': { unidad: 'mg/dL', referencia: '0-0.3' }, - 'TGO/AST': { unidad: 'U/L', referencia: '0-35' }, - 'TGP/ALT': { unidad: 'U/L', referencia: '0-35' }, - 'TP': { unidad: '%', referencia: '70-100' }, - 'KPTT': { unidad: 'seg', referencia: '30-45' }, - 'RIN': { unidad: '', referencia: '0.8-1.5' }, -}; - const RANGOS_LABORATORIO: Record = { 'Hematocrito': { min: 36, max: 42 }, 'Hemoglobina': { min: 12, max: 14 }, @@ -201,7 +173,7 @@ function calcularEstadoLaboratorio(parametro: string, valor: string): 'Normal' | return 'Normal'; } -function wrapText(text: string, font: any, fontSize: number, maxWidth: number): string[] { +function wrapText(text: string, font: { widthOfTextAtSize: (text: string, size: number) => number }, fontSize: number, maxWidth: number): string[] { const words = text.split(' '); const lines: string[] = []; let currentLine = ''; @@ -226,7 +198,6 @@ export function HistoriaClinica({ internacion, paciente, cama, - allCamas, evoluciones, laboratorios, glucemias, @@ -265,8 +236,6 @@ export function HistoriaClinica({ onEliminarIndicacion, movimientos, onAgregarMovimiento, - onActualizarInternacion, - onActualizarCama, onVolver, onEditarIngreso, canEdit, @@ -590,11 +559,11 @@ export function HistoriaClinica({ - + - + @@ -602,11 +571,11 @@ export function HistoriaClinica({ - + - + @@ -668,9 +637,10 @@ export function HistoriaClinica({ ); } -function SeccionGlucemias({ glucemias, patientId, add, update, del, canEdit }: { +function SeccionGlucemias({ glucemias, patientId, internacionId, add, update, del, canEdit }: { glucemias: Glucemia[]; patientId: string; + internacionId: string; add: (g: Omit) => void; update: (id: string, data: Partial) => void; del: (id: string) => void; @@ -725,6 +695,7 @@ function SeccionGlucemias({ glucemias, patientId, add, update, del, canEdit }: { } else { add({ pacienteId: patientId, + internacionId, fecha, hora, valor: valNum, @@ -926,9 +897,10 @@ function SeccionGlucemias({ glucemias, patientId, add, update, del, canEdit }: { ); } -function SeccionLaboratorios({ lab, patientId, add, update, del, addAcidoBase, canEdit }: { +function SeccionLaboratorios({ lab, patientId, internacionId, add, update, del, addAcidoBase, canEdit }: { lab: Laboratorio[]; patientId: string; + internacionId: string; add: (l: Omit) => void; update: (id: string, data: Partial) => void; del: (id: string) => void; @@ -938,13 +910,11 @@ function SeccionLaboratorios({ lab, patientId, add, update, del, addAcidoBase, c const [dialog, setDialog] = useState(false); const [obsDialog, setObsDialog] = useState(false); const [evolDialog, setEvolDialog] = useState(false); - const [selectedLab, setSelectedLab] = useState(null); const [edit, setEdit] = useState(null); const [obsLab, setObsLab] = useState(null); const [fecha, setFecha] = useState(new Date().toISOString().split('T')[0]); const [hora, setHora] = useState(new Date().toTimeString().slice(0, 5)); const [observaciones, setObservaciones] = useState(''); - const [resultados, setResultados] = useState([]); const [hto, setHto] = useState(''); const [hb, setHb] = useState(''); const [gb, setGb] = useState(''); @@ -988,13 +958,6 @@ function SeccionLaboratorios({ lab, patientId, add, update, del, addAcidoBase, c return { fecha: l.fecha, valor: r ? parseFloat(String(r.valor)) : null }; }); - const getEstadoColor = (estado: ResultadoLaboratorio['estado']) => { - switch (estado) { - case 'Normal': return 'bg-green-100 text-green-800'; - case 'Alto': case 'Bajo': case 'Crítico': return 'bg-red-100 text-red-800'; - } - }; - const getValor = (l: Laboratorio, param: string) => { const r = l.resultados.find(r => r.parametro === param); return r ? `${r.valor}` : '-'; @@ -1357,7 +1320,7 @@ function SeccionLaboratorios({ lab, patientId, add, update, del, addAcidoBase, c if (importResultados.length === 0 && !importObservaciones && !importAcidoBase) return; if (importAcidoBase) { - addAcidoBase({ ...importAcidoBase, fecha: importFecha, hora: importHora }); + addAcidoBase({ ...importAcidoBase, pacienteId: patientId, internacionId, fecha: importFecha, hora: importHora }); } add({ @@ -1407,7 +1370,7 @@ function SeccionLaboratorios({ lab, patientId, add, update, del, addAcidoBase, c if (edit) { update(edit.id, { fecha, hora, resultados: resultadosLaboratorio, observaciones }); } else { - add({ pacienteId: patientId || '', fecha, hora, resultados: resultadosLaboratorio, observaciones }); + add({ pacienteId: patientId || '', internacionId, fecha, hora, resultados: resultadosLaboratorio, observaciones }); } setDialog(false); reset(); @@ -1761,7 +1724,6 @@ function SeccionEvoluciones({ evos, internacionId, add, update, del, canEdit }: const [dialog, setDialog] = useState(false); const [edit, setEdit] = useState(null); const [evoDetalle, setEvoDetalle] = useState(null); - const [expanded, setExpanded] = useState(null); const [fecha, setFecha] = useState(new Date().toISOString().split('T')[0]); const [hora, setHora] = useState(new Date().toTimeString().slice(0, 5)); const effectiveMedico = getNombreProfesional(currentUser); @@ -2067,9 +2029,10 @@ function SeccionEvoluciones({ evos, internacionId, add, update, del, canEdit }: ); } -function SeccionAcidosBase({ ab, patientId, add, update, del, canEdit }: { +function SeccionAcidosBase({ ab, patientId, internacionId, add, update, del, canEdit }: { ab: AcidoBase[]; patientId: string; + internacionId: string; add: (a: Omit) => void; update: (id: string, datos: Partial) => void; del: (id: string) => void; @@ -2116,15 +2079,6 @@ function SeccionAcidosBase({ ab, patientId, add, update, del, canEdit }: { const abFiltered = ab.filter(a => a.pacienteId === patientId).sort((a, b) => new Date(b.fecha + 'T' + b.hora).getTime() - new Date(a.fecha + 'T' + a.hora).getTime()); - const getEstadoParametro = (param: string, val: number) => { - const refs: Record = { ph: { min: 7.35, max: 7.45 }, pco2: { min: 35, max: 45 }, po2: { min: 80, max: 100 }, hco3: { min: 22, max: 26 }, be: { min: -2, max: 2 }, sato2: { min: 95, max: 100 } }; - const r = refs[param]; - if (!r) return { estado: 'Normal', color: 'text-gray-800' }; - if (val < r.min) return { estado: 'Bajo', color: 'text-blue-600' }; - if (val > r.max) return { estado: 'Alto', color: 'text-red-600' }; - return { estado: 'Normal', color: 'text-green-600' }; - }; - const interpretarGasometria = () => { const phVal = parseFloat(ph), pco2Val = parseFloat(pco2), hco3Val = parseFloat(hco3), beVal = parseFloat(be); if (!phVal || !pco2Val || !hco3Val) return ''; @@ -2136,16 +2090,10 @@ function SeccionAcidosBase({ ab, patientId, add, update, del, canEdit }: { return interp; }; - const getColorPh = (ph: number) => { - if (ph < 7.2 || ph > 7.6) return 'bg-red-100 text-red-800'; - if (ph < 7.35 || ph > 7.45) return 'bg-amber-100 text-amber-800'; - return 'bg-green-100 text-green-800'; - }; - const handle = () => { if (!ph || !pco2 || !po2 || !hco3 || !be || !sato2) return; const interpretacionAuto = interpretarGasometria(); - add({ pacienteId: patientId, fecha, hora, ph: parseFloat(ph), pco2: parseFloat(pco2), po2: parseFloat(po2), hco3: parseFloat(hco3), be: parseFloat(be), sato2: parseFloat(sato2), lactato: lactato ? parseFloat(lactato) : undefined, fio2: fio2 ? parseFloat(fio2) : undefined, interpretacion: interpretacion || interpretacionAuto }); + add({ pacienteId: patientId, internacionId, fecha, hora, ph: parseFloat(ph), pco2: parseFloat(pco2), po2: parseFloat(po2), hco3: parseFloat(hco3), be: parseFloat(be), sato2: parseFloat(sato2), lactato: lactato ? parseFloat(lactato) : undefined, fio2: fio2 ? parseFloat(fio2) : undefined, interpretacion: interpretacion || interpretacionAuto }); setDialog(false); reset(); }; @@ -2300,9 +2248,10 @@ function SeccionAcidosBase({ ab, patientId, add, update, del, canEdit }: { ); } -function SeccionCultivos({ cults, patient, add, update, del, canEdit }: { +function SeccionCultivos({ cults, patient, internacionId, add, update, del, canEdit }: { cults: Cultivo[]; patient: Paciente; + internacionId: string; add: (c: Omit) => void; update: (id: string, data: Partial) => void; del: (id: string) => void; @@ -2312,7 +2261,6 @@ function SeccionCultivos({ cults, patient, add, update, del, canEdit }: { const [resDialog, setResDialog] = useState(false); const [editDialog, setEditDialog] = useState(false); const [selected, setSelected] = useState(null); - const [isParcialMode, setIsParcialMode] = useState(false); const [fechaToma, setFechaToma] = useState(new Date().toISOString().split('T')[0]); const [protocolo, setProtocolo] = useState(''); const [tipoMuestra, setTipoMuestra] = useState('HMCx2'); @@ -2340,29 +2288,8 @@ function SeccionCultivos({ cults, patient, add, update, del, canEdit }: { setSelected(null); }; - const abrirParcial = (c: Cultivo) => { - setSelected(c); - setProtocolo(c.protocolo || ''); - setGermen(c.germen || ''); - setSensible(c.sensible || ''); - setResistente(c.resistente || ''); - setResDialog(true); - }; - - const abrirDefinitivo = (c: Cultivo) => { - setSelected(c); - setProtocolo(c.protocolo || ''); - setFechaResultado(c.fechaResultado || new Date().toISOString().split('T')[0]); - setEstadoResultado(c.estado === 'Parcial' || c.estado === 'Positivo' ? 'Positivo' : c.estado); - setGermen(c.germen || ''); - setSensible(c.sensible || ''); - setResistente(c.resistente || ''); - setEditDialog(true); - }; - const openParcial = (c: Cultivo) => { setSelected(c); - setIsParcialMode(true); setProtocolo(c.protocolo || ''); setGermen(c.germen || ''); setSensible(c.sensible || ''); @@ -2372,7 +2299,6 @@ function SeccionCultivos({ cults, patient, add, update, del, canEdit }: { const openDefinitivo = (c: Cultivo) => { setSelected(c); - setIsParcialMode(false); setProtocolo(c.protocolo || ''); setFechaResultado(c.fechaResultado || new Date().toISOString().split('T')[0]); setEstadoResultado(c.estado === 'Parcial' ? 'Positivo' : c.estado); @@ -2402,7 +2328,7 @@ function SeccionCultivos({ cults, patient, add, update, del, canEdit }: { } }; - const handle = () => { add({ pacienteId: patient.id, fechaToma, protocolo, tipoMuestra, observaciones: observaciones || undefined, estado: 'NAF/Pendiente' }); setDialog(false); reset(); }; + const handle = () => { add({ pacienteId: patient.id, internacionId, fechaToma, protocolo, tipoMuestra, observaciones: observaciones || undefined, estado: 'NAF/Pendiente' }); setDialog(false); reset(); }; const handleParcial = () => { if (!selected) return; @@ -2753,7 +2679,7 @@ function SeccionInterconsultas({ interconsultas, pacienteId, internacionId, add, const handleGuardar = () => { if (!servicioInterconsultado) return; - const datos: any = { fecha, servicioInterconsultado, motivo, respuestaInterconsulta }; + const datos: Partial = { fecha, servicioInterconsultado, motivo, respuestaInterconsulta }; if (respuestaInterconsulta) { datos.respuestaFecha = respuestaFecha || new Date().toISOString().split('T')[0]; } diff --git a/src/sections/Internaciones.tsx b/src/sections/Internaciones.tsx index dc7f1c6..32ad4d1 100644 --- a/src/sections/Internaciones.tsx +++ b/src/sections/Internaciones.tsx @@ -1,5 +1,5 @@ import { useState } from 'react'; -import { ClipboardList, Plus, Search, LogOut, CheckCircle2, MoreHorizontal, FileText } from 'lucide-react'; +import { ClipboardList, Plus, Search, LogOut, CheckCircle2, MoreHorizontal, FileText, Trash2 } from 'lucide-react'; import { Card, CardContent } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; @@ -28,6 +28,7 @@ interface InternacionesProps { diagnosticoEgreso: string; motivoEgreso: Internacion['motivoEgreso'] }) => void; + onEliminarInternacion?: (internacionId: string) => Promise | void; getPacienteById: (id: string) => Paciente | undefined; getCamaById: (id: string) => Cama | undefined; onVerHC?: (internacionId: string) => void; @@ -40,6 +41,7 @@ export function Internaciones({ camas, onIniciarInternacion, onFinalizarInternacion, + onEliminarInternacion, getPacienteById, getCamaById, grupos, @@ -50,7 +52,9 @@ export function Internaciones({ const [filtroEstado, setFiltroEstado] = useState<'todas' | 'activas' | 'finalizadas'>('todas'); const [dialogoNuevaAbierto, setDialogoNuevaAbierto] = useState(false); const [dialogoEgresoAbierto, setDialogoEgresoAbierto] = useState(false); + const [dialogoEliminarAbierto, setDialogoEliminarAbierto] = useState(false); const [internacionSeleccionada, setInternacionSeleccionada] = useState(null); + const [internacionAEliminar, setInternacionAEliminar] = useState(null); const [busqueda, setBusqueda] = useState(''); const [pacienteSeleccionado, setPacienteSeleccionado] = useState(''); const [camaSeleccionada, setCamaSeleccionada] = useState(''); @@ -488,6 +492,16 @@ export function Internaciones({ Ver HC + { + setInternacionAEliminar(internacion); + setDialogoEliminarAbierto(true); + }} + > + + Eliminar HC + {internacion.activa && ( abrirDialogoEgreso(internacion)}> @@ -567,7 +581,36 @@ export function Internaciones({
- {/* Historia Clínica ahora es una página separada */} + {/* Modal Confirmar Eliminación HC */} + { if (!open) setInternacionAEliminar(null); setDialogoEliminarAbierto(open); }}> + + + Eliminar Historia Clínica de Internación + +
+

+ ¿Está seguro de que desea eliminar esta historia clínica de internación y todos sus registros asociados (evoluciones, laboratorios, cultivos, indicaciones, etc.)? Esta acción no se puede deshacer. +

+
+ + +
+
+
+
); } diff --git a/src/sections/Layout.tsx b/src/sections/Layout.tsx index 1ca48e1..9ef5275 100644 --- a/src/sections/Layout.tsx +++ b/src/sections/Layout.tsx @@ -1,3 +1,4 @@ +import { useState } from 'react'; import { LayoutDashboard, Bed, @@ -35,6 +36,7 @@ interface LayoutProps { export function Layout({ children, vistaActual, onCambiarVista, currentUser, onLogout }: LayoutProps) { const { theme, setTheme } = useTheme() + const [mobileMenuOpen, setMobileMenuOpen] = useState(false); const toggleDarkMode = () => setTheme(theme === "dark" ? "light" : "dark") @@ -59,7 +61,7 @@ export function Layout({ children, vistaActual, onCambiarVista, currentUser, onL filteredMenuItems.push({ vista: 'usuarios', label: 'Usuarios', icon: UserCog }); } - const renderNavContent = () => ( + const renderNavContent = (onItemClick?: () => void) => (
Gestion Historia Clinica Electronica
- + - @@ -162,13 +167,16 @@ export function Layout({ children, vistaActual, onCambiarVista, currentUser, onL
- {renderNavContent()} + {renderNavContent(() => setMobileMenuOpen(false))}
@@ -589,7 +586,7 @@ export function MapaCamas({
{editingBed && ( - )} diff --git a/src/sections/NuevoIngreso.tsx b/src/sections/NuevoIngreso.tsx index d6253f7..8704ad2 100644 --- a/src/sections/NuevoIngreso.tsx +++ b/src/sections/NuevoIngreso.tsx @@ -21,9 +21,10 @@ interface NuevoIngresoProps { onActualizarPaciente?: (id: string, datos: Partial) => void; onVolver: () => void; getGrupoName: (grupoId: string | undefined) => string; + internaciones: Internacion[]; } -export function NuevoIngreso({ pacientes, camas, grupos, onIniciarInternacion, onAgregarCama, onVolver, getGrupoName }: NuevoIngresoProps) { +export function NuevoIngreso({ pacientes, camas, grupos, onIniciarInternacion, onAgregarCama, onVolver, getGrupoName, internaciones }: NuevoIngresoProps) { const { currentUser } = useHospitalStore(); const [pacienteSeleccionado, setPacienteSeleccionado] = useState(''); const [busqueda, setBusqueda] = useState(''); @@ -61,7 +62,10 @@ export function NuevoIngreso({ pacientes, camas, grupos, onIniciarInternacion, o const grupoFueraDeGrupo = grupos.find(a => normalizeStr(a.nombre) === 'fuera de grupo'); const grupoFueraDeGrupoId = grupoFueraDeGrupo?.id || grupos[0]?.id || 'fuera-de-grupo'; - const pacientesSinInternar = pacientes; + const pacientesSinInternar = pacientes.filter(p => { + const internacionActiva = internaciones.find(i => i.pacienteId === p.id && i.activa); + return !internacionActiva; + }); const parseBedNumber = (numero: string) => { const match = numero.match(/^(\d{3})-(\d+)$/); diff --git a/src/types/index.ts b/src/types/index.ts index a87f589..30cca29 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -225,6 +225,17 @@ export interface Indicacion { fechaSuspension?: string; } +export interface MovimientoIndicacion { + id: string; + internacionId: string; + indicacionId?: string; + tipo: string; + fecha: string; + hora: string; + medico: string; + observaciones?: string; +} + export type Vista = 'dashboard' | 'camas' | 'pacientes' | 'internaciones' | 'evoluciones' | 'laboratorios' | 'acidobase' | 'cultivos' | 'historiaclinica' | 'nuevoingreso' | 'editaringreso' | 'usuarios'; export type RolUsuario = 'admin' | 'medico' | 'enfermero';