Files
administracionHospital/src/sections/Internaciones.tsx
T

463 lines
21 KiB
TypeScript

import { useState } from 'react';
import { ClipboardList, Plus, Search, Bed, Calendar, Stethoscope, LogOut, CheckCircle2 } from 'lucide-react';
import { Card, CardContent } from '@/components/ui/card';
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 { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import type { Internacion, Paciente, Cama, Area, Evolucion, Laboratorio, Cultivo } from '@/types';
import { formatDateDDMMYYYY } from '@/lib/utils';
interface InternacionesProps {
internaciones: Internacion[];
pacientes: Paciente[];
camas: Cama[];
evoluciones?: Evolucion[];
laboratorios?: Laboratorio[];
cultivos?: Cultivo[];
areas: Area[];
onIniciarInternacion: (internacion: Omit<Internacion, 'id' | 'activa'>) => void;
onFinalizarInternacion: (internacionId: string, datos: {
fechaEgreso: string;
diagnosticoEgreso: string;
motivoEgreso: Internacion['motivoEgreso']
}) => void;
getPacienteById: (id: string) => Paciente | undefined;
getCamaById: (id: string) => Cama | undefined;
onVerHC?: (internacionId: string) => void;
onNuevoIngreso?: () => void;
}
export function Internaciones({
internaciones,
pacientes,
camas,
onIniciarInternacion,
onFinalizarInternacion,
getPacienteById,
getCamaById,
areas,
onVerHC,
onNuevoIngreso,
}: InternacionesProps) {
const [filtroEstado, setFiltroEstado] = useState<'todas' | 'activas' | 'finalizadas'>('todas');
const [dialogoNuevaAbierto, setDialogoNuevaAbierto] = useState(false);
const [dialogoEgresoAbierto, setDialogoEgresoAbierto] = useState(false);
const [internacionSeleccionada, setInternacionSeleccionada] = useState<Internacion | null>(null);
const [busqueda, setBusqueda] = useState('');
const [pacienteSeleccionado, setPacienteSeleccionado] = useState<string>('');
const [camaSeleccionada, setCamaSeleccionada] = useState<string>('');
const [diagnosticoIngreso, setDiagnosticoIngreso] = useState('');
const [enfermedadActual, setEnfermedadActual] = useState('');
const [motivoConsulta, setMotivoConsulta] = useState('');
const [antecedentesEnfermedadActual, setAntecedentesEnfermedadActual] = useState('');
const [medicoIngresante, setMedicoIngresante] = useState('');
// Formulario nueva internación
const [fechaEgreso, setFechaEgreso] = useState('');
const [diagnosticoEgreso, setDiagnosticoEgreso] = useState('');
const [motivoEgreso, setMotivoEgreso] = useState<Internacion['motivoEgreso']>('Alta médica');
const resetFormularioNueva = () => {
setPacienteSeleccionado('');
setCamaSeleccionada('');
setDiagnosticoIngreso('');
setEnfermedadActual('');
setAntecedentesEnfermedadActual('');
setMedicoIngresante('');
};
const resetFormularioEgreso = () => {
setFechaEgreso('');
setDiagnosticoEgreso('');
setMotivoEgreso('Alta médica');
setInternacionSeleccionada(null);
};
const handleIniciarInternacion = () => {
if (pacienteSeleccionado && camaSeleccionada && motivoConsulta && enfermedadActual && medicoIngresante) {
onIniciarInternacion({
pacienteId: pacienteSeleccionado,
camaId: camaSeleccionada,
diagnosticoIngreso: diagnosticoIngreso || motivoConsulta,
motivoConsulta,
enfermedadActual,
antecedentesEnfermedadActual: antecedentesEnfermedadActual || undefined,
medicoIngresante,
});
resetFormularioNueva();
setDialogoNuevaAbierto(false);
}
};
const handleFinalizarInternacion = () => {
if (internacionSeleccionada && fechaEgreso && diagnosticoEgreso && motivoEgreso) {
onFinalizarInternacion(internacionSeleccionada.id, {
fechaEgreso,
diagnosticoEgreso,
motivoEgreso,
});
resetFormularioEgreso();
setDialogoEgresoAbierto(false);
}
};
const internacionesFiltradas = internaciones.filter(i => {
const paciente = getPacienteById(i.pacienteId);
const cumpleBusqueda = !busqueda ||
(paciente && (
paciente.nombre.toLowerCase().includes(busqueda.toLowerCase()) ||
paciente.apellido.toLowerCase().includes(busqueda.toLowerCase()) ||
paciente.dni.includes(busqueda)
));
const cumpleEstado = filtroEstado === 'todas' ||
(filtroEstado === 'activas' && i.activa) ||
(filtroEstado === 'finalizadas' && !i.activa);
return cumpleBusqueda && cumpleEstado;
}).sort((a, b) => new Date(b.fechaIngresoClinica || '').getTime() - new Date(a.fechaIngresoClinica || '').getTime());
const pacientesSinInternar = pacientes.filter(p => {
const internacionActiva = internaciones.find(i => i.pacienteId === p.id && i.activa);
return !internacionActiva;
});
const camasDisponibles = camas.filter(c => c.estado === 'Disponible');
const getAreaName = (areaId?: string) => areas.find(a => a.id === areaId)?.nombre;
const abrirDialogoEgreso = (internacion: Internacion) => {
setInternacionSeleccionada(internacion);
setFechaEgreso(new Date().toISOString().split('T')[0]);
setDialogoEgresoAbierto(true);
};
// Ver Historia Clínica (navegar a página)
return (
<div className="space-y-6 dark:text-white">
{/* Header */}
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
<div>
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">Internaciones</h1>
<p className="text-gray-500 dark:text-gray-400">Gestión de internaciones en sala</p>
</div>
<Dialog open={dialogoNuevaAbierto} onOpenChange={setDialogoNuevaAbierto}>
<DialogTrigger asChild>
<Button variant="outline" onClick={() => onNuevoIngreso ? onNuevoIngreso() : setDialogoNuevaAbierto(true)}>
<Plus className="h-4 w-4 mr-2" />
Nuevo Ingreso
</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-lg">
<DialogHeader>
<DialogTitle>Nuevo Ingreso</DialogTitle>
</DialogHeader>
<div className="space-y-4">
<div>
<Label>Paciente *</Label>
{!pacienteSeleccionado ? (
<>
<Input
placeholder="Buscar por apellido, nombre o DNI"
value={busqueda}
onChange={(e) => setBusqueda(e.target.value)}
className="mb-2"
/>
<div className="max-h-48 overflow-auto border rounded-md">
{(() => {
const q = busqueda.trim().toLowerCase();
if (!q) {
return (
<div className="p-2 text-sm text-gray-500">Escribe apellido, nombre o DNI para buscar</div>
);
}
const matches = pacientesSinInternar.filter(p => (
p.apellido.toLowerCase().includes(q) ||
p.nombre.toLowerCase().includes(q) ||
p.dni.includes(q)
));
if (matches.length === 0) {
return <div className="p-2 text-sm text-gray-500">No se encontraron pacientes coincidentes</div>;
}
return matches.map(p => (
<button
key={p.id}
type="button"
onClick={() => setPacienteSeleccionado(p.id)}
className={`w-full text-left p-2 hover:bg-gray-50 ${pacienteSeleccionado === p.id ? 'bg-gray-100' : ''}`}
>
{p.apellido}, {p.nombre} DNI: {p.dni}
</button>
));
})()}
</div>
</>
) : (
<div className="flex items-center gap-2">
<Badge className="bg-gray-100 text-gray-800 px-3 py-1">
{(() => {
const p = pacientes.find(x => x.id === pacienteSeleccionado);
return p ? `${p.apellido}, ${p.nombre} — DNI: ${p.dni}` : '';
})()}
</Badge>
<Button size="sm" variant="outline" onClick={() => setPacienteSeleccionado('')}>Cambiar</Button>
</div>
)}
</div>
<div>
<Label>Cama *</Label>
<Select value={camaSeleccionada} onValueChange={setCamaSeleccionada}>
<SelectTrigger>
<SelectValue placeholder="Seleccionar cama" />
</SelectTrigger>
<SelectContent>
{camasDisponibles.map(c => (
<SelectItem key={c.id} value={c.id}>
{c.numero} - {getAreaName(c.areaId) || 'Sin área'} ({c.tipo})
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div>
<Label>Motivo de Consulta *</Label>
<textarea
className="w-full p-2 border rounded-md text-sm min-h-[80px]"
value={motivoConsulta}
onChange={(e) => setMotivoConsulta(e.target.value)}
placeholder="Motivo de consulta..."
/>
</div>
<div>
<Label>Enfermedad Actual *</Label>
<textarea
className="w-full p-2 border rounded-md text-sm min-h-[100px]"
value={enfermedadActual}
onChange={(e) => setEnfermedadActual(e.target.value)}
placeholder="Describa la enfermedad actual..."
/>
</div>
<div>
<Label>Antecedentes de Enfermedad Actual</Label>
<textarea
className="w-full p-2 border rounded-md tex</p>
</div>t-sm min-h-[80px]"
value={antecedentesEnfermedadActual}
onChange={(e) => setAntecedentesEnfermedadActual(e.target.value)}
placeholder="Antecedentes relevantes de la enfermedad actual..."
/>
</div>
<div>
<Label>Médico Ingresante *</Label>
<Input
value={medicoIngresante}
onChange={(e) => setMedicoIngresante(e.target.value)}
placeholder="Nombre del médico ingresante"
/>
</div>
<Button variant="outline"
className="w-full"
onClick={handleIniciarInternacion}
disabled={!pacienteSeleccionado || !camaSeleccionada || !motivoConsulta || !enfermedadActual || !medicoIngresante}
>
<Plus className="h-4 w-4 mr-2" />
Iniciar Internación
</Button>
</div>
</DialogContent>
</Dialog>
</div>
{/* Filtros */}
<Card>
<CardContent className="p-4">
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
<div className="relative sm:col-span-2">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-gray-400" />
<Input
className="pl-10"
placeholder="Buscar paciente..."
value={busqueda}
onChange={(e) => setBusqueda(e.target.value)}
/>
</div>
<div>
<Select value={filtroEstado} onValueChange={(v: 'todas' | 'activas' | 'finalizadas') => setFiltroEstado(v)}>
<SelectTrigger>
<SelectValue placeholder="Filtrar por estado" />
</SelectTrigger>
<SelectContent>
<SelectItem value="todas">Todas las internaciones</SelectItem>
<SelectItem value="activas">Internaciones activas</SelectItem>
<SelectItem value="finalizadas">Internaciones finalizadas</SelectItem>
</SelectContent>
</Select>
</div>
</div>
</CardContent>
</Card>
{/* Lista de Internaciones */}
<div className="space-y-4">
{internacionesFiltradas.map((internacion) => {
const paciente = getPacienteById(internacion.pacienteId);
const cama = getCamaById(internacion.camaId);
return (
<Card key={internacion.id} className="hover:shadow-md transition-shadow">
<CardContent className="p-4 dark:bg-gray-800">
<div className="flex flex-col lg:flex-row lg:items-start lg:justify-between gap-4">
<div className="flex items-start gap-4">
<div className={`h-12 w-12 rounded-full flex items-center justify-center flex-shrink-0 ${
internacion.activa ? 'bg-purple-100' : 'bg-gray-100'
}`}>
{internacion.activa ? (
<ClipboardList className="h-6 w-6 text-purple-600" />
) : (
<CheckCircle2 className="h-6 w-6 text-gray-600" />
)}
</div>
<div>
<div className="flex items-center gap-2 flex-wrap">
<h3 className="font-bold text-lg">
{paciente ? `${paciente.apellido}, ${paciente.nombre}` : 'Paciente no encontrado'}
</h3>
<Badge className={internacion.activa ? 'bg-purple-100 text-purple-800' : 'bg-gray-100 text-gray-800'}>
{internacion.activa ? 'Activa' : 'Finalizada'}
</Badge>
</div>
<div className="grid grid-cols-2 sm:grid-cols-3 gap-x-4 gap-y-2 mt-3 text-sm">
<div className="flex items-center gap-2 text-gray-600">
<Bed className="h-4 w-4" />
<span>Cama {cama?.numero || '-'} ({getAreaName(cama?.areaId) || 'Sin área'})
</span>
</div>
<div className="flex items-center gap-2 text-gray-600">
<Calendar className="h-4 w-4" />
<span>Ingreso: {internacion.fechaIngresoClinica ? formatDateDDMMYYYY(internacion.fechaIngresoClinica) : 'N/A'}</span>
</div>
<div className="flex items-center gap-2 text-gray-600">
<Stethoscope className="h-4 w-4" />
<span className="truncate">{internacion.medicoIngresante}</span>
</div>
</div>
<div className="mt-2">
<p className="text-sm text-gray-600">
<span className="font-medium">Diagnóstico de ingreso:</span>{' '}
{internacion.diagnosticoIngreso}
</p>
</div>
{!internacion.activa && internacion.fechaEgreso && (
<div className="mt-2 space-y-1">
<div className="flex items-center gap-2 text-sm text-gray-600">
<LogOut className="h-4 w-4" />
<span>Egreso: {internacion.fechaEgreso}</span>
</div>
<p className="text-sm text-gray-600">
<span className="font-medium">Diagnóstico de egreso:</span>{' '}
{internacion.diagnosticoEgreso}
</p>
<p className="text-sm text-gray-600">
<span className="font-medium">Motivo:</span>{' '}
{internacion.motivoEgreso}
</p>
</div>
)}
</div>
</div>
<div className="flex items-center gap-2">
{internacion.activa && (
<Dialog open={dialogoEgresoAbierto && internacionSeleccionada?.id === internacion.id} onOpenChange={setDialogoEgresoAbierto}>
<DialogTrigger asChild>
<Button
variant="outline"
className="whitespace-nowrap"
onClick={() => abrirDialogoEgreso(internacion)}
>
<LogOut className="h-4 w-4 mr-2" />
Registrar Egreso
</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-lg">
<DialogHeader>
<DialogTitle>Dar Alta</DialogTitle>
</DialogHeader>
<div className="space-y-4">
<div>
<Label>Paciente</Label>
<Input
value={paciente ? `${paciente.apellido}, ${paciente.nombre}` : ''}
disabled
/>
</div>
<div>
<Label>Fecha de Egreso *</Label>
<Input
type="date"
value={fechaEgreso}
onChange={(e) => setFechaEgreso(e.target.value)}
/>
</div>
<div>
<Label>Motivo de Egreso *</Label>
<Select value={motivoEgreso} onValueChange={(v) => setMotivoEgreso(v as Internacion['motivoEgreso'])}>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="Alta médica">Alta médica</SelectItem>
<SelectItem value="Alta voluntaria">Alta voluntaria</SelectItem>
<SelectItem value="Derivación">Derivación</SelectItem>
<SelectItem value="Fallecimiento">Fallecimiento</SelectItem>
<SelectItem value="Otro">Otro</SelectItem>
</SelectContent>
</Select>
</div>
<div>
<Label>Diagnóstico de Egreso *</Label>
<textarea
className="w-full p-2 border rounded-md text-sm min-h-[80px]"
value={diagnosticoEgreso}
onChange={(e) => setDiagnosticoEgreso(e.target.value)}
placeholder="Ingrese el diagnóstico de egreso..."
/>
</div>
<Button variant="outline"
className="w-full"
onClick={handleFinalizarInternacion}
disabled={!fechaEgreso || !diagnosticoEgreso || !motivoEgreso}
>
<CheckCircle2 className="h-4 w-4 mr-2" />
Confirmar Egreso
</Button>
</div>
</DialogContent>
</Dialog>
)}
<Button variant="outline" onClick={() => { if (typeof onVerHC === 'function') onVerHC(internacion.id); }}>Ver HC</Button>
</div>
</div>
</CardContent>
</Card>
);
})}
</div>
{internacionesFiltradas.length === 0 && (
<div className="text-center py-12 text-gray-400">
<ClipboardList className="h-16 w-16 mx-auto mb-4 opacity-50" />
<p className="text-lg">
{busqueda || filtroEstado !== 'todas'
? 'No se encontraron internaciones con esos filtros'
: 'No hay internaciones registradas'}
</p>
</div>
)}
{/* Historia Clínica ahora es una página separada */}
</div>
);
}