Agregar tabla interconsultas: DB, store, UI con CRUD completo

This commit is contained in:
2026-04-17 10:00:10 -03:00
parent 59471d65bc
commit 4510938991
6 changed files with 983 additions and 5 deletions
+4
View File
@@ -144,6 +144,7 @@ function AppContent() {
acidosBase={store.getAcidosBaseByPaciente(paciente.id)}
cultivos={store.getCultivosByPaciente(paciente.id)}
estudiosComplementarios={store.estudiosComplementarios.filter(e => e.pacienteId === paciente.id)}
interconsultas={store.interconsultas.filter(ic => ic.internacionId === internacion.id)}
onAgregarEvolucion={store.agregarEvolucion}
onActualizarEvolucion={store.actualizarEvolucion}
onEliminarEvolucion={store.eliminarEvolucion}
@@ -159,6 +160,9 @@ function AppContent() {
onAgregarEstudioComplementario={(e) => store.agregarEstudioComplementario({ ...e, internacionId: internacion.id })}
onActualizarEstudioComplementario={store.actualizarEstudioComplementario}
onEliminarEstudioComplementario={store.eliminarEstudioComplementario}
onAgregarInterconsulta={(ic) => store.agregarInterconsulta({ ...ic, internacionId: internacion.id })}
onActualizarInterconsulta={store.actualizarInterconsulta}
onEliminarInterconsulta={store.eliminarInterconsulta}
onActualizarInternacion={store.actualizarInternacion}
onActualizarCama={store.actualizarCama}
onVolver={() => store.setVista('internaciones')}
+30
View File
@@ -9,6 +9,7 @@ import type {
AcidoBase,
Cultivo,
EstudioComplementario,
Interconsulta,
Vista
} from '@/types';
@@ -35,6 +36,7 @@ interface HospitalState {
acidosBase: AcidoBase[];
cultivos: Cultivo[];
estudiosComplementarios: EstudioComplementario[];
interconsultas: Interconsulta[];
vistaActual: Vista;
currentInternacionId?: string | null;
}
@@ -51,6 +53,7 @@ const defaultState = (): HospitalState => ({
acidosBase: [],
cultivos: [],
estudiosComplementarios: [],
interconsultas: [],
vistaActual: 'dashboard',
camas: []
});
@@ -349,6 +352,30 @@ const finalizarInternacion = useCallback((internacionId: string, datos: {
}));
}, []);
// Acciones de interconsultas
const agregarInterconsulta = useCallback((interconsulta: Omit<Interconsulta, 'id'>) => {
const nuevaInterconsulta: Interconsulta = { ...interconsulta, id: generateUUID() };
setState(prev => ({
...prev,
interconsultas: [...prev.interconsultas, nuevaInterconsulta],
}));
return nuevaInterconsulta.id;
}, []);
const actualizarInterconsulta = useCallback((id: string, datos: Partial<Interconsulta>) => {
setState(prev => ({
...prev,
interconsultas: prev.interconsultas.map(ic => ic.id === id ? { ...ic, ...datos } : ic),
}));
}, []);
const eliminarInterconsulta = useCallback((id: string) => {
setState(prev => ({
...prev,
interconsultas: prev.interconsultas.filter(ic => ic.id !== id),
}));
}, []);
// Acciones de areas
const agregarArea = useCallback((area: Omit<Area, 'id'>) => {
const nuevaArea: Area = { ...area, id: generateUUID() };
@@ -472,6 +499,9 @@ const getEstadisticas = useCallback(() => {
agregarEstudioComplementario,
actualizarEstudioComplementario,
eliminarEstudioComplementario,
agregarInterconsulta,
actualizarInterconsulta,
eliminarInterconsulta,
agregarArea,
actualizarArea,
eliminarArea,
+161 -5
View File
@@ -50,7 +50,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, EstudioComplementario } from '@/types';
import type { Laboratorio, AcidoBase, Cultivo, Paciente, ResultadoLaboratorio, Internacion, Evolucion, Cama, EstudioComplementario, Interconsulta } from '@/types';
import { formatDateDDMMYYYY } from '@/lib/utils';
interface HistoriaClinicaProps {
@@ -63,6 +63,7 @@ interface HistoriaClinicaProps {
acidosBase: AcidoBase[];
cultivos: Cultivo[];
estudiosComplementarios: EstudioComplementario[];
interconsultas: Interconsulta[];
onAgregarEvolucion: (evolucion: Omit<Evolucion, 'id'>) => void;
onActualizarEvolucion: (id: string, datos: Partial<Evolucion>) => void;
onEliminarEvolucion: (id: string) => void;
@@ -79,6 +80,9 @@ interface HistoriaClinicaProps {
onAgregarEstudioComplementario: (estudio: Omit<EstudioComplementario, 'id'>) => void;
onActualizarEstudioComplementario: (id: string, datos: Partial<EstudioComplementario>) => void;
onEliminarEstudioComplementario: (id: string) => void;
onAgregarInterconsulta: (interconsulta: Omit<Interconsulta, 'id'>) => void;
onActualizarInterconsulta: (id: string, datos: Partial<Interconsulta>) => void;
onEliminarInterconsulta: (id: string) => void;
onActualizarInternacion: (id: string, datos: Partial<Internacion>) => void;
onActualizarCama: (id: string, datos: Partial<Cama>) => void;
onVolver: () => void;
@@ -168,6 +172,7 @@ export function HistoriaClinica({
acidosBase,
cultivos,
estudiosComplementarios,
interconsultas,
onAgregarEvolucion,
onActualizarEvolucion,
onEliminarEvolucion,
@@ -183,6 +188,9 @@ export function HistoriaClinica({
onAgregarEstudioComplementario,
onActualizarEstudioComplementario,
onEliminarEstudioComplementario,
onAgregarInterconsulta,
onActualizarInterconsulta,
onEliminarInterconsulta,
onActualizarInternacion,
onActualizarCama,
onVolver,
@@ -538,10 +546,14 @@ export function HistoriaClinica({
</TabsContent>
<TabsContent value="interconsultas" className="mt-4">
<div className="text-center py-8 text-gray-400">
<Users className="h-12 w-12 mx-auto mb-2 opacity-50" />
<p>Sección Interconsultas en desarrollo</p>
</div>
<SeccionInterconsultas
interconsultas={interconsultas as Interconsulta[]}
pacienteId={paciente.id}
internacionId={internacion.id}
add={onAgregarInterconsulta}
update={onActualizarInterconsulta}
del={onEliminarInterconsulta}
/>
</TabsContent>
</Tabs>
</div>
@@ -2006,4 +2018,148 @@ function SeccionEstudiosComplementarios({ estudios, internacionId, pacienteId, a
)}
</div>
);
}
function SeccionInterconsultas({ interconsultas, pacienteId, internacionId, add, update, del }: {
interconsultas: Interconsulta[];
pacienteId: string;
internacionId: string;
add: (ic: Omit<Interconsulta, 'id'>) => void;
update: (id: string, datos: Partial<Interconsulta>) => void;
del: (id: string) => void;
}) {
const [dialog, setDialog] = useState(false);
const [fecha, setFecha] = useState(new Date().toISOString().split('T')[0]);
const [servicioInterconsultado, setServicioInterconsultado] = useState('');
const [realizada, setRealizada] = useState(false);
const [respuestaInterconsulta, setRespuestaInterconsulta] = useState('');
const [edit, setEdit] = useState<Interconsulta | null>(null);
const reset = () => {
setFecha(new Date().toISOString().split('T')[0]);
setServicioInterconsultado('');
setRealizada(false);
setRespuestaInterconsulta('');
setEdit(null);
};
const handleGuardar = () => {
if (!servicioInterconsultado) return;
if (edit) {
update(edit.id, { fecha, servicioInterconsultado, realizada, respuestaInterconsulta });
} else {
add({ pacienteId, internacionId, fecha, servicioInterconsultado, realizada, respuestaInterconsulta });
}
setDialog(false);
reset();
};
const loadEdit = (ic: Interconsulta) => {
setEdit(ic);
setFecha(ic.fecha);
setServicioInterconsultado(ic.servicioInterconsultado);
setRealizada(ic.realizada);
setRespuestaInterconsulta(ic.respuestaInterconsulta || '');
setDialog(true);
};
const icsFiltrados = interconsultas.filter(ic => ic.internacionId === internacionId);
return (
<div className="space-y-4">
<div className="flex justify-between items-center">
<Button variant="outline" onClick={() => { reset(); setDialog(true); }}>
<Plus className="h-4 w-4 mr-2" />
Nueva IC
</Button>
</div>
<Dialog open={dialog} onOpenChange={setDialog}>
<DialogContent className="sm:max-w-lg">
<DialogHeader>
<DialogTitle>{edit ? 'Editar' : 'Nueva'} Interconsulta</DialogTitle>
</DialogHeader>
<div className="space-y-4">
<div>
<Label>Fecha</Label>
<Input type="date" value={fecha} onChange={e => setFecha(e.target.value)} />
</div>
<div>
<Label>Servicio Interconsultado</Label>
<Input
value={servicioInterconsultado}
onChange={e => setServicioInterconsultado(e.target.value)}
placeholder="Ej: Cardiología, Neurología, Infectología, etc."
/>
</div>
<div className="flex items-center gap-2">
<input
type="checkbox"
id="realizada"
checked={realizada}
onChange={e => setRealizada(e.target.checked)}
className="w-4 h-4"
/>
<Label htmlFor="realizada">Realizada</Label>
</div>
<div>
<Label>Respuesta</Label>
<textarea
className="w-full p-2 border rounded-md text-sm min-h-[100px]"
value={respuestaInterconsulta}
onChange={e => setRespuestaInterconsulta(e.target.value)}
placeholder="Ingrese la respuesta de la interconsulta..."
/>
</div>
</div>
<div className="flex justify-end gap-2 mt-4">
<Button variant="outline" onClick={() => setDialog(false)}>Cancelar</Button>
<Button onClick={handleGuardar} disabled={!servicioInterconsultado}>
<Save className="h-4 w-4 mr-2" />
Guardar
</Button>
</div>
</DialogContent>
</Dialog>
{icsFiltrados.length === 0 ? (
<div className="text-center py-8 text-gray-400">
<Users className="h-12 w-12 mx-auto mb-2 opacity-50" />
<p>No hay interconsultas</p>
</div>
) : (
<div className="space-y-3">
{icsFiltrados.sort((a, b) => new Date(b.fecha).getTime() - new Date(a.fecha).getTime()).map(ic => (
<Card key={ic.id} className="hover:shadow-md transition-shadow">
<CardContent className="p-4">
<div className="flex flex-col sm:flex-row sm:items-start sm:justify-between gap-3">
<div className="flex-1">
<div className="flex items-center gap-2 mb-2">
<Calendar className="h-4 w-4 text-gray-500" />
<span className="text-sm font-medium">{ic.fecha}</span>
{ic.realizada && (
<Badge variant="secondary" className="ml-2">Realizada</Badge>
)}
</div>
<h4 className="font-semibold text-gray-900 dark:text-white">{ic.servicioInterconsultado}</h4>
{ic.respuestaInterconsulta && (
<p className="text-sm text-gray-600 dark:text-gray-300 mt-1 whitespace-pre-wrap">{ic.respuestaInterconsulta}</p>
)}
</div>
<div className="flex gap-1 self-start">
<Button size="sm" variant="outline" onClick={() => loadEdit(ic)}>
<Pencil className="h-4 w-4" />
</Button>
<Button size="sm" variant="outline" className="text-red-600" onClick={() => del(ic.id)}>
<Trash2 className="h-4 w-4" />
</Button>
</div>
</div>
</CardContent>
</Card>
))}
</div>
)}
</div>
);
}
+10
View File
@@ -152,4 +152,14 @@ export interface EstudioComplementario {
resultado: string;
}
export interface Interconsulta {
id: string;
pacienteId: string;
internacionId: string;
fecha: string;
servicioInterconsultado: string;
realizada: boolean;
respuestaInterconsulta?: string;
}
export type Vista = 'dashboard' | 'camas' | 'pacientes' | 'internaciones' | 'evoluciones' | 'laboratorios' | 'acidobase' | 'cultivos' | 'historiaclinica' | 'nuevoingreso' | 'editaringreso';