1992 lines
108 KiB
TypeScript
1992 lines
108 KiB
TypeScript
import { useState } from 'react';
|
||
import { PDFDocument } from 'pdf-lib';
|
||
import {
|
||
FlaskConical,
|
||
Microscope,
|
||
Plus,
|
||
Calendar,
|
||
Clock,
|
||
AlertCircle,
|
||
CheckCircle2,
|
||
ArrowLeft,
|
||
Pencil,
|
||
Trash2,
|
||
FileText,
|
||
ChevronDown,
|
||
ChevronUp,
|
||
ChevronRight,
|
||
FileDown,
|
||
Thermometer,
|
||
Heart,
|
||
Wind,
|
||
Droplets,
|
||
Save,
|
||
X,
|
||
MoreHorizontal,
|
||
Edit,
|
||
ClipboardList,
|
||
TrendingUp,
|
||
TypeOutline,
|
||
Activity
|
||
} 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 { 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 { formatDateDDMMYYYY } from '@/lib/utils';
|
||
|
||
interface HistoriaClinicaProps {
|
||
internacion: Internacion;
|
||
paciente: Paciente;
|
||
cama?: Cama;
|
||
allCamas?: Cama[];
|
||
evoluciones: Evolucion[];
|
||
laboratorios: Laboratorio[];
|
||
acidosBase: AcidoBase[];
|
||
cultivos: Cultivo[];
|
||
estudiosComplementarios: EstudioComplementario[];
|
||
onAgregarEvolucion: (evolucion: Omit<Evolucion, 'id'>) => void;
|
||
onActualizarEvolucion: (id: string, datos: Partial<Evolucion>) => void;
|
||
onEliminarEvolucion: (id: string) => void;
|
||
onAgregarLaboratorio: (laboratorios: Omit<Laboratorio, 'id'>) => void;
|
||
onActualizarLaboratorio: (id: string, datos: Partial<Laboratorio>) => void;
|
||
onEliminarLaboratorio: (id: string) => void;
|
||
onAgregarLaboratorioConAcidoBase?: (l: Omit<Laboratorio, 'id'>, a?: Omit<AcidoBase, 'id'>) => void;
|
||
onAgregarAcidoBase: (acidoBase: Omit<AcidoBase, 'id'>) => void;
|
||
onActualizarAcidoBase: (id: string, datos: Partial<AcidoBase>) => void;
|
||
onEliminarAcidoBase: (id: string) => void;
|
||
onAgregarCultivo: (cultivo: Omit<Cultivo, 'id'>) => void;
|
||
onActualizarCultivo: (id: string, datos: Partial<Cultivo>) => void;
|
||
onEliminarCultivo: (id: string) => void;
|
||
onAgregarEstudioComplementario: (estudio: Omit<EstudioComplementario, 'id'>) => void;
|
||
onEliminarEstudioComplementario: (id: string) => void;
|
||
onActualizarInternacion: (id: string, datos: Partial<Internacion>) => void;
|
||
onActualizarCama: (id: string, datos: Partial<Cama>) => void;
|
||
onVolver: () => void;
|
||
onEditarIngreso?: () => void;
|
||
}
|
||
|
||
const PARAMETROS_COMUNES: Record<string, { unidad: string; referencia: string }> = {
|
||
'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<string, { min: number; max: number }> = {
|
||
'Hematocrito': { min: 36, max: 42 },
|
||
'Hemoglobina': { min: 12, max: 14 },
|
||
'Leucocitos': { min: 4000, max: 11000 },
|
||
'Plaquetas': { min: 150000, max: 400000 },
|
||
'Glucemia': { min: 70, max: 100 },
|
||
'Urea': { min: 17, max: 48.5 },
|
||
'Creatinina': { min: 0.5, max: 1.2 },
|
||
'Sodio': { min: 135, max: 145 },
|
||
'Potasio': { min: 3.5, max: 5.1 },
|
||
'Cloro': { min: 101, max: 109 },
|
||
'Bilirrubina Total': { min: 0.1, max: 1.2 },
|
||
'Bilirrubina Directa': { min: 0, max: 0.3 },
|
||
'GOT': { min: 0, max: 35 },
|
||
'GPT': { min: 0, max: 35 },
|
||
'Tiempo de Protrombina': { min: 12, max: 14 },
|
||
'KPTT': { min: 30, max: 45 },
|
||
'INR': { min: 0.8, max: 1.5 },
|
||
};
|
||
|
||
function calcularEstadoLaboratorio(parametro: string, valor: string): 'Normal' | 'Alto' | 'Bajo' {
|
||
const rango = RANGOS_LABORATORIO[parametro];
|
||
if (!rango) return 'Normal';
|
||
|
||
const num = parseFloat(valor);
|
||
if (isNaN(num)) return 'Normal';
|
||
|
||
if (num < rango.min) return 'Bajo';
|
||
if (num > rango.max) return 'Alto';
|
||
return 'Normal';
|
||
}
|
||
|
||
function wrapText(text: string, font: any, fontSize: number, maxWidth: number): string[] {
|
||
const words = text.split(' ');
|
||
const lines: string[] = [];
|
||
let currentLine = '';
|
||
|
||
for (const word of words) {
|
||
const testLine = currentLine ? `${currentLine} ${word}` : word;
|
||
const testWidth = font.widthOfTextAtSize(testLine, fontSize);
|
||
if (testWidth > maxWidth && currentLine) {
|
||
lines.push(currentLine);
|
||
currentLine = word;
|
||
} else {
|
||
currentLine = testLine;
|
||
}
|
||
}
|
||
if (currentLine) lines.push(currentLine);
|
||
return lines;
|
||
}
|
||
|
||
export function HistoriaClinica({
|
||
internacion,
|
||
paciente,
|
||
cama,
|
||
allCamas,
|
||
evoluciones,
|
||
laboratorios,
|
||
acidosBase,
|
||
cultivos,
|
||
estudiosComplementarios,
|
||
onAgregarEvolucion,
|
||
onActualizarEvolucion,
|
||
onEliminarEvolucion,
|
||
onAgregarLaboratorio,
|
||
onActualizarLaboratorio,
|
||
onEliminarLaboratorio,
|
||
onAgregarAcidoBase,
|
||
onActualizarAcidoBase,
|
||
onEliminarAcidoBase,
|
||
onAgregarCultivo,
|
||
onActualizarCultivo,
|
||
onEliminarCultivo,
|
||
onAgregarEstudioComplementario,
|
||
onEliminarEstudioComplementario,
|
||
onActualizarInternacion,
|
||
onActualizarCama,
|
||
onVolver,
|
||
onEditarIngreso,
|
||
}: HistoriaClinicaProps) {
|
||
const [tabActivo, setTabActivo] = useState('evoluciones');
|
||
const [detalleExpandido, setDetalleExpandido] = useState(false);
|
||
const [editIngresoDialog, setEditIngresoDialog] = useState(false);
|
||
const [editFechaIngreso, setEditFechaIngreso] = useState(internacion.fechaIngresoClinica);
|
||
const [editMotivoConsulta, setEditMotivoConsulta] = useState(internacion.motivoConsulta || '');
|
||
const [editDiagnostico, setEditDiagnostico] = useState(internacion.diagnosticoIngreso || '');
|
||
const [editEnfermedadActual, setEditEnfermedadActual] = useState(internacion.enfermedadActual || '');
|
||
const [editAntecedentes, setEditAntecedentes] = useState(internacion.antecedentesEnfermedadActual || '');
|
||
const [editMedico, setEditMedico] = useState(internacion.medicoIngresante || '');
|
||
const [editCamaId, setEditCamaId] = useState(internacion.camaId || '');
|
||
|
||
const calcularEdad = (fechaNacimiento: string) => {
|
||
const hoy = new Date();
|
||
const nac = new Date(fechaNacimiento);
|
||
let edad = hoy.getFullYear() - nac.getFullYear();
|
||
const mes = hoy.getMonth() - nac.getMonth();
|
||
if (mes < 0 || (mes === 0 && hoy.getDate() < nac.getDate())) {
|
||
edad--;
|
||
}
|
||
return edad;
|
||
};
|
||
|
||
const calcularDiasInternado = (fechaIngreso: string) => {
|
||
const hoy = new Date();
|
||
const ingreso = new Date(fechaIngreso);
|
||
const diff = hoy.getTime() - ingreso.getTime();
|
||
return Math.floor(diff / (1000 * 60 * 60 * 24));
|
||
};
|
||
|
||
const handleGuardarEdicion = () => {
|
||
if (editCamaId && editCamaId !== internacion.camaId && cama) {
|
||
onActualizarCama(cama.id, { estado: 'Disponible', pacienteId: undefined, internacionId: undefined });
|
||
}
|
||
if (editCamaId) {
|
||
onActualizarCama(editCamaId, { estado: 'Ocupada', pacienteId: paciente.id, internacionId: internacion.id });
|
||
}
|
||
onActualizarInternacion(internacion.id, {
|
||
fechaIngresoClinica: editFechaIngreso,
|
||
motivoConsulta: editMotivoConsulta,
|
||
diagnosticoIngreso: editDiagnostico,
|
||
enfermedadActual: editEnfermedadActual,
|
||
antecedentesEnfermedadActual: editAntecedentes,
|
||
medicoIngresante: editMedico,
|
||
camaId: editCamaId || undefined,
|
||
});
|
||
setEditIngresoDialog(false);
|
||
};
|
||
|
||
return (
|
||
<div className="space-y-6 dark:text-white">
|
||
<div>
|
||
<div className="flex items-center gap-4 mb-4">
|
||
|
||
<div>
|
||
<h1 className="text-2xl font-bold text-gray-900 dark:text-white flex items-center gap-2"><ClipboardList className="h-6 w-6" />Historia Clínica</h1>
|
||
<p className="text-gray-500">
|
||
Cama: {cama?.numero || 'N/A'} | {paciente.apellido}, {paciente.nombre} | {paciente.fechaNacimiento ? calcularEdad(paciente.fechaNacimiento) + ' años' : ''} | DNI: {paciente.dni}
|
||
</p>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<div className="flex items-center gap-2 flex-wrap">
|
||
<Button variant="outline" onClick={onVolver}>
|
||
<ArrowLeft className="h-4 w-4 mr-2" />
|
||
Volver
|
||
</Button>
|
||
<Button variant="outline" onClick={onEditarIngreso}>
|
||
<Pencil className="h-4 w-4 mr-2" />
|
||
Editar Ingreso
|
||
</Button>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
|
||
|
||
<Dialog open={editIngresoDialog} onOpenChange={setEditIngresoDialog}>
|
||
<DialogContent className="sm:max-w-2xl max-h-[90vh] overflow-y-auto">
|
||
<DialogHeader>
|
||
<DialogTitle>Editar Ingreso</DialogTitle>
|
||
</DialogHeader>
|
||
<Card className="bg-muted/50">
|
||
<CardContent className="p-4">
|
||
<div className="flex flex-wrap gap-2 mb-2">
|
||
<Badge variant="outline" className="bg-primary/10 text-primary border-primary/30">
|
||
{paciente.apellido}, {paciente.nombre}
|
||
</Badge>
|
||
<Badge variant="outline" className="bg-primary/10 text-primary border-primary/30">
|
||
{paciente.fechaNacimiento ? calcularEdad(paciente.fechaNacimiento) + ' años' : ''}
|
||
</Badge>
|
||
<Badge variant="outline" className="bg-primary/10 text-primary border-primary/30">
|
||
DNI: {paciente.dni}
|
||
</Badge>
|
||
</div>
|
||
<div className="flex flex-wrap gap-2">
|
||
<Badge variant="secondary">
|
||
HC: {paciente.historiaClinica || 'N/A'}
|
||
</Badge>
|
||
<Badge variant="secondary">
|
||
{paciente.obraSocial || 'Sin obra social'}
|
||
</Badge>
|
||
<Badge variant="secondary">
|
||
{paciente.nacionalidad || 'N/A'}
|
||
</Badge>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
<div className="space-y-4">
|
||
<div className="grid grid-cols-3 gap-4">
|
||
<div>
|
||
<Label>Fecha de Ingreso *</Label>
|
||
<Input type="date" value={editFechaIngreso} onChange={e => setEditFechaIngreso(e.target.value)} />
|
||
</div>
|
||
<div>
|
||
<Label>Médico Ingresante *</Label>
|
||
<Input value={editMedico} onChange={e => setEditMedico(e.target.value)} placeholder="Nombre del médico" />
|
||
</div>
|
||
<div>
|
||
<Label>Cama</Label>
|
||
<Select value={editCamaId} onValueChange={setEditCamaId}>
|
||
<SelectTrigger><SelectValue placeholder="Seleccionar cama" /></SelectTrigger>
|
||
<SelectContent>
|
||
{(allCamas || []).filter((c: Cama) => c.estado === 'Disponible' || c.id === cama?.id).map((c: Cama) => (
|
||
<SelectItem key={c.id} value={c.id}>{c.numero}</SelectItem>
|
||
))}
|
||
</SelectContent>
|
||
</Select>
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<Label>Motivo de Consulta</Label>
|
||
<textarea className="w-full p-2 border rounded-md text-sm min-h-[60px]" value={editMotivoConsulta} onChange={e => setEditMotivoConsulta(e.target.value)} />
|
||
</div>
|
||
<div>
|
||
<Label>Diagnóstico de Ingreso *</Label>
|
||
<textarea className="w-full p-2 border rounded-md text-sm min-h-[60px]" value={editDiagnostico} onChange={e => setEditDiagnostico(e.target.value)} />
|
||
</div>
|
||
<div>
|
||
<Label>Enfermedad Actual</Label>
|
||
<textarea className="w-full p-2 border rounded-md text-sm min-h-[80px]" value={editEnfermedadActual} onChange={e => setEditEnfermedadActual(e.target.value)} />
|
||
</div>
|
||
<div>
|
||
<Label>Antecedentes de la Enfermedad Actual</Label>
|
||
<textarea className="w-full p-2 border rounded-md text-sm min-h-[80px]" value={editAntecedentes} onChange={e => setEditAntecedentes(e.target.value)} />
|
||
</div>
|
||
</div>
|
||
<div className="flex justify-end gap-2 mt-4">
|
||
<Button variant="outline" onClick={() => setEditIngresoDialog(false)}><X className="h-4 w-4 mr-2" />Cancelar</Button>
|
||
<Button onClick={handleGuardarEdicion} disabled={!editFechaIngreso || !editDiagnostico || !editMedico}><Save className="h-4 w-4 mr-2" />Guardar</Button>
|
||
</div>
|
||
</DialogContent>
|
||
</Dialog>
|
||
|
||
<Card>
|
||
<CardContent className="p-0">
|
||
<button type="button" className="w-full p-4 flex items-center justify-between hover:bg-gray-50" onClick={() => setDetalleExpandido(!detalleExpandido)}>
|
||
<div className="grid grid-cols-2 sm:grid-cols-4 gap-4 text-sm w-full">
|
||
|
||
<div>
|
||
<p className="text-gray-500">Antecedentes</p>
|
||
<p className="font-medium truncate">{paciente.antecedentes || 'No constan'}</p>
|
||
</div>
|
||
|
||
<div>
|
||
<p className="text-gray-500">Fecha de Ingreso</p>
|
||
<p className="font-medium">{internacion.fechaIngresoClinica ? formatDateDDMMYYYY(internacion.fechaIngresoClinica) : 'N/A'}</p>
|
||
</div>
|
||
<div>
|
||
<p className="text-gray-500">Diagnóstico de Ingreso</p>
|
||
<p className="font-medium truncate">{internacion.diagnosticoIngreso}</p>
|
||
</div>
|
||
<div>
|
||
<p className="text-gray-500">Días de Internación</p>
|
||
<Badge className={internacion.fechaIngresoClinica && calcularDiasInternado(internacion.fechaIngresoClinica) > 7 ? 'bg-red-100 text-red-800' : 'bg-blue-100 text-blue-800'}>
|
||
{internacion.fechaIngresoClinica ? calcularDiasInternado(internacion.fechaIngresoClinica) : 0} días
|
||
</Badge>
|
||
</div>
|
||
|
||
</div>
|
||
{detalleExpandido ? <ChevronUp className="h-5 w-5 text-gray-400" /> : <ChevronDown className="h-5 w-5 text-gray-400" />}
|
||
</button>
|
||
{detalleExpandido && (
|
||
<div className="border-t p-4 space-y-4">
|
||
<div>
|
||
<p className="text-gray-500 text-sm">Motivo de Consulta</p>
|
||
<p className="font-medium">{internacion.motivoConsulta || 'Sin motivo registrado'}</p>
|
||
</div>
|
||
<div>
|
||
<p className="text-gray-500 text-sm">Enfermedad Actual</p>
|
||
<p className="font-medium">{internacion.enfermedadActual || 'Sin información'}</p>
|
||
</div>
|
||
<div>
|
||
<p className="text-gray-500 text-sm">Antecedentes de Enfermedad Actual</p>
|
||
<p className="font-medium">{internacion.antecedentesEnfermedadActual || 'Sin antecedentes registrados'}</p>
|
||
</div>
|
||
<Button variant="outline" className="bg-blue-50 border-blue-300 text-blue-700 hover:bg-blue-100" onClick={async (e) => {
|
||
e.stopPropagation();
|
||
try {
|
||
const { PDFDocument, StandardFonts, rgb } = await import('pdf-lib');
|
||
const pdfDoc = await PDFDocument.create();
|
||
const page = pdfDoc.addPage([595, 842]);
|
||
const { width, height } = page.getSize();
|
||
|
||
const font = await pdfDoc.embedFont(StandardFonts.Helvetica);
|
||
const fontBold = await pdfDoc.embedFont(StandardFonts.HelveticaBold);
|
||
|
||
const colorPrimary = rgb(0.18, 0.34, 0.55);
|
||
const colorText = rgb(0.13, 0.13, 0.13);
|
||
const colorGray = rgb(0.45, 0.45, 0.45);
|
||
const colorLightGray = rgb(0.92, 0.92, 0.92);
|
||
|
||
const edad = paciente.fechaNacimiento ? calcularEdad(paciente.fechaNacimiento) : '';
|
||
const fechaFormateada = internacion.fechaIngresoClinica ? formatDateDDMMYYYY(internacion.fechaIngresoClinica) : 'N/A';
|
||
const fechaHospital = internacion.fechaIngresoHospital ? formatDateDDMMYYYY(internacion.fechaIngresoHospital) : '';
|
||
|
||
// Header
|
||
page.drawRectangle({ x: 0, y: height - 100, width, height: 100, color: colorPrimary });
|
||
page.drawText('HOSPITAL DONACIÓN F. SANTOJANNI – DIVISIÓN CLÍNICA MÉDICA', { x: 60, y: height - 35, size: 14, font: fontBold, color: rgb(1, 1, 1) });
|
||
page.drawText('HISTORIA CLÍNICA DE INGRESO', { x: 170, y: height - 55, size: 12, font: fontBold, color: rgb(1, 1, 1) });
|
||
|
||
let y = height - 140;
|
||
|
||
// Datos Filiatorios
|
||
page.drawRectangle({ x: 45, y: y - 8, width: 505, height: 24, color: colorLightGray });
|
||
page.drawText('DATOS FILIATORIOS', { x: 50, y: y, size: 12, font: fontBold, color: colorPrimary });
|
||
y -= 35;
|
||
|
||
// Primera línea: Fechas y cama
|
||
const fechaIngresoHospText = fechaHospital ? `Fecha ingreso al hospital: ${fechaHospital}` : '';
|
||
const fechaIngresoClinText = fechaFormateada ? `Fecha ingreso a clínica: ${fechaFormateada}` : '';
|
||
const camaText = `Cama: ${cama?.numero || 'N/A'}`;
|
||
|
||
page.drawText(fechaIngresoHospText, { x: 50, y, size: 10, font: fontBold, color: colorGray });
|
||
page.drawText(fechaIngresoClinText, { x: 260, y, size: 10, font: fontBold, color: colorGray });
|
||
y -= 18;
|
||
page.drawText(camaText, { x: 50, y, size: 10, font: fontBold, color: colorGray });
|
||
y -= 25;
|
||
|
||
// Segunda línea: Apellido, Nombre y DNI
|
||
page.drawText(`Apellido y Nombre: ${paciente.apellido || ''}, ${paciente.nombre || ''}`, { x: 50, y, size: 10, font: fontBold, color: colorGray });
|
||
y -= 18;
|
||
page.drawText(`DNI: ${paciente.dni || ''}`, { x: 50, y, size: 10, font: fontBold, color: colorGray });
|
||
page.drawText(`Edad: ${edad ? `${edad} años` : ''}`, { x: 280, y, size: 10, font: fontBold, color: colorGray });
|
||
y -= 40;
|
||
|
||
// Datos Clínicos
|
||
page.drawRectangle({ x: 45, y: y - 8, width: 505, height: 24, color: colorLightGray });
|
||
page.drawText('DATOS CLÍNICOS', { x: 50, y: y, size: 12, font: fontBold, color: colorPrimary });
|
||
y -= 35;
|
||
|
||
page.drawText('Motivo de Consulta:', { x: 50, y, size: 10, font: fontBold, color: colorGray });
|
||
y -= 18;
|
||
const motivoLines = wrapText(internacion.motivoConsulta || 'Sin información', font, 10, 480);
|
||
for (const line of motivoLines.slice(0, 4)) {
|
||
page.drawText(line, { x: 50, y, size: 10, font, color: colorText });
|
||
y -= 14;
|
||
}
|
||
y -= 10;
|
||
if (motivoLines.length > 4) y -= (motivoLines.length - 4) * 14;
|
||
|
||
page.drawText('Enfermedad Actual:', { x: 50, y, size: 10, font: fontBold, color: colorGray });
|
||
y -= 18;
|
||
const enfermedadLines = wrapText(internacion.enfermedadActual || 'Sin información', font, 10, 480);
|
||
for (const line of enfermedadLines.slice(0, 4)) {
|
||
page.drawText(line, { x: 50, y, size: 10, font, color: colorText });
|
||
y -= 14;
|
||
}
|
||
y -= 10;
|
||
if (enfermedadLines.length > 4) y -= (enfermedadLines.length - 4) * 14;
|
||
|
||
page.drawText('Antecedentes de la Enfermedad Actual:', { x: 50, y, size: 10, font: fontBold, color: colorGray });
|
||
y -= 18;
|
||
const antecedentesLines = wrapText(internacion.antecedentesEnfermedadActual || 'Sin antecedentes registrados', font, 10, 480);
|
||
for (const line of antecedentesLines.slice(0, 4)) {
|
||
page.drawText(line, { x: 50, y, size: 10, font, color: colorText });
|
||
y -= 14;
|
||
}
|
||
y -= 10;
|
||
if (antecedentesLines.length > 4) y -= (antecedentesLines.length - 4) * 14;
|
||
|
||
page.drawText('Diagnóstico de Ingreso:', { x: 50, y, size: 10, font: fontBold, color: colorGray });
|
||
y -= 18;
|
||
const diagnosticoLines = wrapText(internacion.diagnosticoIngreso || 'Sin diagnóstico', font, 10, 480);
|
||
for (const line of diagnosticoLines.slice(0, 3)) {
|
||
page.drawText(line, { x: 50, y, size: 10, font, color: colorText });
|
||
y -= 14;
|
||
}
|
||
|
||
// Footer
|
||
y -= 20;
|
||
page.drawLine({ start: { x: 50, y }, end: { x: 250, y }, thickness: 0.5, color: colorGray });
|
||
page.drawText('Firma del Médico', { x: 50, y: y - 15, size: 9, font, color: colorGray });
|
||
page.drawLine({ start: { x: 320, y }, end: { x: 520, y }, thickness: 0.5, color: colorGray });
|
||
page.drawText('Aclaración / Sello', { x: 320, y: y - 15, size: 9, font, color: colorGray });
|
||
page.drawText('v2.0', { x: width - 60, y: 30, size: 8, font, color: colorGray });
|
||
|
||
const pdfBytes = await pdfDoc.save();
|
||
const blob = new Blob([new Uint8Array(pdfBytes)], { type: 'application/pdf' });
|
||
const url = URL.createObjectURL(blob);
|
||
const link = document.createElement('a');
|
||
link.href = url;
|
||
link.download = `FormularioIngreso_${paciente.apellido}_${paciente.dni}.pdf`;
|
||
link.click();
|
||
URL.revokeObjectURL(url);
|
||
} catch (err) {
|
||
console.error('Error generating PDF:', err);
|
||
}
|
||
}}>
|
||
<FileDown className="h-4 w-4 mr-2" />
|
||
Descargar Formulario de Ingreso
|
||
</Button>
|
||
</div>
|
||
)}
|
||
</CardContent>
|
||
</Card>
|
||
|
||
<Tabs value={tabActivo} onValueChange={setTabActivo}>
|
||
<TabsList className="flex w-full overflow-x-auto gap-1 pb-2">
|
||
<TabsTrigger value="evoluciones" className="flex-shrink-0 flex items-center gap-1 text-xs sm:text-sm">
|
||
<FileText className="h-3 w-3 sm:h-4 sm:w-4" />
|
||
<span className="hidden sm:inline">Evoluciones</span>
|
||
<span className="sm:hidden">Evol</span>
|
||
({evoluciones.length})
|
||
</TabsTrigger>
|
||
<TabsTrigger value="laboratorios" className="flex-shrink-0 flex items-center gap-1 text-xs sm:text-sm">
|
||
<FlaskConical className="h-3 w-3 sm:h-4 sm:w-4" />
|
||
<span className="hidden sm:inline">Laboratorio</span>
|
||
<span className="sm:hidden">Lab</span>
|
||
({laboratorios.length})
|
||
</TabsTrigger>
|
||
<TabsTrigger value="acidobase" className="flex-shrink-0 flex items-center gap-1 text-xs sm:text-sm">
|
||
<Activity className="h-3 w-3 sm:h-4 sm:w-4" />
|
||
<span className="hidden sm:inline">Ácido-Base</span>
|
||
<span className="sm:hidden">AB</span>
|
||
({acidosBase.length})
|
||
</TabsTrigger>
|
||
<TabsTrigger value="cultivos" className="flex-shrink-0 flex items-center gap-1 text-xs sm:text-sm">
|
||
<Microscope className="h-3 w-3 sm:h-4 sm:w-4" />
|
||
<span className="hidden sm:inline">Cultivos</span>
|
||
<span className="sm:hidden">Cult</span>
|
||
({cultivos.length})
|
||
</TabsTrigger>
|
||
<TabsTrigger value="estudios" className="flex-shrink-0 flex items-center gap-1 text-xs sm:text-sm">
|
||
<ClipboardList className="h-3 w-3 sm:h-4 sm:w-4" />
|
||
<span className="hidden sm:inline">Estudios</span>
|
||
<span className="sm:hidden">Est</span>
|
||
({estudiosComplementarios.length})
|
||
</TabsTrigger>
|
||
</TabsList>
|
||
|
||
<TabsContent value="laboratorios" className="mt-4">
|
||
<SeccionLaboratorios lab={laboratorios} patientId={paciente.id} add={onAgregarLaboratorio} update={onActualizarLaboratorio} del={onEliminarLaboratorio} addAcidoBase={onAgregarAcidoBase} />
|
||
</TabsContent>
|
||
|
||
<TabsContent value="evoluciones" className="mt-4">
|
||
<SeccionEvoluciones evos={evoluciones} internacionId={internacion.id} add={onAgregarEvolucion} update={onActualizarEvolucion} del={onEliminarEvolucion} />
|
||
</TabsContent>
|
||
|
||
<TabsContent value="acidobase" className="mt-4">
|
||
<SeccionAcidosBase ab={acidosBase} patientId={paciente.id} add={onAgregarAcidoBase} update={onActualizarAcidoBase} del={onEliminarAcidoBase} />
|
||
</TabsContent>
|
||
|
||
<TabsContent value="cultivos" className="mt-4">
|
||
<SeccionCultivos cults={cultivos} patient={paciente} add={onAgregarCultivo} update={onActualizarCultivo} del={onEliminarCultivo} />
|
||
</TabsContent>
|
||
|
||
<TabsContent value="estudios" className="mt-4">
|
||
<SeccionEstudiosComplementarios
|
||
estudios={estudiosComplementarios}
|
||
internacionId={internacion.id}
|
||
pacienteId={paciente.id}
|
||
add={onAgregarEstudioComplementario}
|
||
del={onEliminarEstudioComplementario}
|
||
/>
|
||
</TabsContent>
|
||
</Tabs>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
function SeccionLaboratorios({ lab, patientId, add, update, del, addAcidoBase }: {
|
||
lab: Laboratorio[];
|
||
patientId: string;
|
||
add: (l: Omit<Laboratorio, 'id'>) => void;
|
||
update: (id: string, data: Partial<Laboratorio>) => void;
|
||
del: (id: string) => void;
|
||
addAcidoBase?: (a: Omit<AcidoBase, 'id'>) => void;
|
||
}) {
|
||
const [dialog, setDialog] = useState(false);
|
||
const [obsDialog, setObsDialog] = useState(false);
|
||
const [evolDialog, setEvolDialog] = useState(false);
|
||
const [selectedLab, setSelectedLab] = useState<Laboratorio | null>(null);
|
||
const [edit, setEdit] = useState<Laboratorio | null>(null);
|
||
const [obsLab, setObsLab] = useState<Laboratorio | null>(null);
|
||
const [fecha, setFecha] = useState(new Date().toISOString().split('T')[0]);
|
||
const [observaciones, setObservaciones] = useState('');
|
||
const [resultados, setResultados] = useState<ResultadoLaboratorio[]>([]);
|
||
const [hto, setHto] = useState('');
|
||
const [hb, setHb] = useState('');
|
||
const [gb, setGb] = useState('');
|
||
const [plaq, setPlaq] = useState('');
|
||
const [gluc, setGluc] = useState('');
|
||
const [urea, setUrea] = useState('');
|
||
const [creat, setCreat] = useState('');
|
||
const [na, setNa] = useState('');
|
||
const [k, setK] = useState('');
|
||
const [cl, setCl] = useState('');
|
||
const [bt, setBt] = useState('');
|
||
const [bd, setBd] = useState('');
|
||
const [got, setGot] = useState('');
|
||
const [gpt, setGpt] = useState('');
|
||
const [tp, setTp] = useState('');
|
||
const [kptt, setKptt] = useState('');
|
||
const [rin, setRin] = useState('');
|
||
const [parametroEvolucion, setParametroEvolucion] = useState('');
|
||
const [importDialog, setImportDialog] = useState(false);
|
||
const [importFecha, setImportFecha] = useState(new Date().toISOString().split('T')[0]);
|
||
const [importTexto, setImportTexto] = useState('');
|
||
const [importResultados, setImportResultados] = useState<ResultadoLaboratorio[]>([]);
|
||
const [importObservaciones, setImportObservaciones] = useState('');
|
||
const [importAcidoBase, setImportAcidoBase] = useState<Omit<AcidoBase, 'id'> | null>(null);
|
||
|
||
const parametrosLaboratorio = [
|
||
'Hematocrito', 'Hemoglobina', 'Leucocitos', 'Plaquetas',
|
||
'Glucemia', 'Urea', 'Creatinina',
|
||
'Sodio', 'Potasio', 'Cloro',
|
||
'Bilirrubina Total', 'Bilirrubina Directa', 'GOT', 'GPT',
|
||
'Tiempo de Protrombina', 'KPTT', 'INR'
|
||
];
|
||
|
||
const datosEvolucion = lab
|
||
.filter(l => l.resultados.some(r => r.parametro === parametroEvolucion))
|
||
.sort((a, b) => new Date(a.fecha).getTime() - new Date(b.fecha).getTime())
|
||
.map(l => {
|
||
const r = l.resultados.find(r => r.parametro === parametroEvolucion);
|
||
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': return 'bg-amber-100 text-amber-800';
|
||
case 'Bajo': return 'bg-blue-100 text-blue-800';
|
||
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}` : '-';
|
||
};
|
||
|
||
const getValorRaw = (l: Laboratorio, param: string) => {
|
||
const r = l.resultados.find(r => r.parametro === param);
|
||
return r ? `${r.valor}` : '';
|
||
};
|
||
|
||
const getValorStyle = (l: Laboratorio, param: string) => {
|
||
const r = l.resultados.find(r => r.parametro === param);
|
||
if (!r) return '';
|
||
if (r.estado === 'Crítico') return 'text-red-600 font-bold';
|
||
if (r.estado === 'Alto') return 'text-amber-600';
|
||
if (r.estado === 'Bajo') return 'text-blue-600';
|
||
return 'text-green-600';
|
||
};
|
||
|
||
const buildResultados = (): ResultadoLaboratorio[] => {
|
||
const res: ResultadoLaboratorio[] = [];
|
||
if (hto) res.push({ parametro: 'Hematocrito', valor: hto, unidad: '%', estado: calcularEstadoLaboratorio('Hematocrito', hto) });
|
||
if (hb) res.push({ parametro: 'Hemoglobina', valor: hb, unidad: 'g/dL', estado: calcularEstadoLaboratorio('Hemoglobina', hb) });
|
||
if (gb) res.push({ parametro: 'Leucocitos', valor: gb, unidad: '/mm3', estado: calcularEstadoLaboratorio('Leucocitos', gb) });
|
||
if (plaq) res.push({ parametro: 'Plaquetas', valor: plaq, unidad: '/mm3', estado: calcularEstadoLaboratorio('Plaquetas', plaq) });
|
||
if (gluc) res.push({ parametro: 'Glucemia', valor: gluc, unidad: 'mg/dL', estado: calcularEstadoLaboratorio('Glucemia', gluc) });
|
||
if (urea) res.push({ parametro: 'Urea', valor: urea, unidad: 'mg/dL', estado: calcularEstadoLaboratorio('Urea', urea) });
|
||
if (creat) res.push({ parametro: 'Creatinina', valor: creat, unidad: 'mg/dL', estado: calcularEstadoLaboratorio('Creatinina', creat) });
|
||
if (na) res.push({ parametro: 'Sodio', valor: na, unidad: 'mEq/L', estado: calcularEstadoLaboratorio('Sodio', na) });
|
||
if (k) res.push({ parametro: 'Potasio', valor: k, unidad: 'mEq/L', estado: calcularEstadoLaboratorio('Potasio', k) });
|
||
if (cl) res.push({ parametro: 'Cloro', valor: cl, unidad: 'mEq/L', estado: calcularEstadoLaboratorio('Cloro', cl) });
|
||
if (bt) res.push({ parametro: 'Bilirrubina Total', valor: bt, unidad: 'mg/dL', estado: calcularEstadoLaboratorio('Bilirrubina Total', bt) });
|
||
if (bd) res.push({ parametro: 'Bilirrubina Directa', valor: bd, unidad: 'mg/dL', estado: calcularEstadoLaboratorio('Bilirrubina Directa', bd) });
|
||
if (got) res.push({ parametro: 'GOT', valor: got, unidad: 'U/L', estado: calcularEstadoLaboratorio('GOT', got) });
|
||
if (gpt) res.push({ parametro: 'GPT', valor: gpt, unidad: 'U/L', estado: calcularEstadoLaboratorio('GPT', gpt) });
|
||
if (tp) res.push({ parametro: 'Tiempo de Protrombina', valor: tp, unidad: 'seg', estado: calcularEstadoLaboratorio('Tiempo de Protrombina', tp) });
|
||
if (kptt) res.push({ parametro: 'KPTT', valor: kptt, unidad: 'seg', estado: calcularEstadoLaboratorio('KPTT', kptt) });
|
||
if (rin) res.push({ parametro: 'INR', valor: rin, unidad: '', estado: calcularEstadoLaboratorio('INR', rin) });
|
||
return res;
|
||
};
|
||
|
||
const reset = () => {
|
||
setFecha(new Date().toISOString().split('T')[0]);
|
||
setObservaciones('');
|
||
setHto(''); setHb(''); setGb(''); setPlaq('');
|
||
setGluc(''); setUrea(''); setCreat('');
|
||
setNa(''); setK(''); setCl('');
|
||
setBt(''); setBd(''); setGot(''); setGpt('');
|
||
setTp(''); setKptt(''); setRin('');
|
||
setEdit(null);
|
||
};
|
||
|
||
const parseLaboratorioTexto = (texto: string): { resultados: ResultadoLaboratorio[]; observaciones: string } => {
|
||
const resultados: ResultadoLaboratorio[] = [];
|
||
const observacionesExtra: string[] = [];
|
||
|
||
const mapeoParametros: Record<string, { nombre: string; unidad: string; esPrincipal: boolean }> = {
|
||
'hematíes': { nombre: 'Hematíes', unidad: '10⁶/µl', esPrincipal: false },
|
||
'hematocrito': { nombre: 'Hematocrito', unidad: '%', esPrincipal: true },
|
||
'hemoglobina corpuscular media': { nombre: 'HCM', unidad: 'pg', esPrincipal: false },
|
||
'concentración de hemoglobina media': { nombre: 'CHCM', unidad: 'g/dL', esPrincipal: false },
|
||
'hemoglobina': { nombre: 'Hemoglobina', unidad: 'g/dL', esPrincipal: true },
|
||
'volumen corpuscular medio': { nombre: 'VCM', unidad: 'fL', esPrincipal: false },
|
||
'rdw': { nombre: 'RDW', unidad: '%', esPrincipal: false },
|
||
'eritroblastos': { nombre: 'Eritroblastos', unidad: '%', esPrincipal: false },
|
||
'neutrófilos': { nombre: 'Neutrófilos', unidad: '%', esPrincipal: false },
|
||
'linfocitos': { nombre: 'Linfocitos', unidad: '%', esPrincipal: false },
|
||
'monocitos': { nombre: 'Monocitos', unidad: '%', esPrincipal: false },
|
||
'eosinófilos': { nombre: 'Eosinófilos', unidad: '%', esPrincipal: false },
|
||
'basófilos': { nombre: 'Basófilos', unidad: '%', esPrincipal: false },
|
||
'leucocitos': { nombre: 'Leucocitos', unidad: '10³/µl', esPrincipal: true },
|
||
'recuento de leucocitos': { nombre: 'Leucocitos', unidad: '10³/µl', esPrincipal: true },
|
||
'recuento de plaquetas': { nombre: 'Plaquetas', unidad: '10³/µl', esPrincipal: true },
|
||
'plaquetas': { nombre: 'Plaquetas', unidad: '10³/µl', esPrincipal: true },
|
||
'volumen plaquetario medio': { nombre: 'VPM', unidad: 'fL', esPrincipal: false },
|
||
'procalcitonina': { nombre: 'Procalcitonina', unidad: 'ng/mL', esPrincipal: false },
|
||
'glucosa': { nombre: 'Glucemia', unidad: 'mg/dL', esPrincipal: true },
|
||
'urea': { nombre: 'Urea', unidad: 'mg/dL', esPrincipal: true },
|
||
'creatinina': { nombre: 'Creatinina', unidad: 'mg/dL', esPrincipal: true },
|
||
'mdrd': { nombre: 'Filtrado Glomerular (MDRD)', unidad: 'ml/min', esPrincipal: false },
|
||
'sodio': { nombre: 'Sodio', unidad: 'mEq/L', esPrincipal: true },
|
||
'potasio': { nombre: 'Potasio', unidad: 'mEq/L', esPrincipal: true },
|
||
'cloro': { nombre: 'Cloro', unidad: 'mEq/L', esPrincipal: true },
|
||
'bilirrubina total': { nombre: 'Bilirrubina Total', unidad: 'mg/dL', esPrincipal: true },
|
||
'bilirrubina directa': { nombre: 'Bilirrubina Directa', unidad: 'mg/dL', esPrincipal: true },
|
||
'got': { nombre: 'GOT', unidad: 'U/L', esPrincipal: true },
|
||
'gpt': { nombre: 'GPT', unidad: 'U/L', esPrincipal: true },
|
||
'fosfatasa alcalina': { nombre: 'Fosfatasa Alcalina', unidad: 'U/L', esPrincipal: false },
|
||
'proteínas totales': { nombre: 'Proteínas Totales', unidad: 'g/dL', esPrincipal: false },
|
||
'albúmina': { nombre: 'Albúmina', unidad: 'g/dL', esPrincipal: false },
|
||
'tiempo de protrombina': { nombre: 'Tiempo de Protrombina', unidad: '%', esPrincipal: true },
|
||
'rin': { nombre: 'INR', unidad: '', esPrincipal: true },
|
||
'aptt': { nombre: 'KPTT', unidad: 'seg', esPrincipal: true },
|
||
'kptt': { nombre: 'KPTT', unidad: 'seg', esPrincipal: true },
|
||
};
|
||
|
||
const lineas = texto.split('\n');
|
||
|
||
for (const linea of lineas) {
|
||
const lineaLower = linea.toLowerCase().trim();
|
||
|
||
for (const [clave, info] of Object.entries(mapeoParametros)) {
|
||
console.log('Clave evaluando:', clave, 'incluye?', lineaLower.includes(clave));
|
||
if (lineaLower.includes(clave)) {
|
||
const cleanLinea = linea.replace(/show_chart|list_alt/g, '').replace(/\t+/g, ' ').replace(/\s+/g, ' ').trim();
|
||
const partes = cleanLinea.split(' ');
|
||
|
||
const searchTerms = [clave.split(' ')[0], clave.split(' ').slice(-1)[0]];
|
||
console.log('Buscando:', searchTerms, 'en partes:', partes);
|
||
let idxParam = partes.findIndex(p => searchTerms.some(term => p.toLowerCase().includes(term)));
|
||
console.log('idxParam encontrado:', idxParam);
|
||
if (idxParam === -1) idxParam = 0;
|
||
|
||
for (let i = idxParam + 1; i < partes.length; i++) {
|
||
const parte = partes[i].replace(',', '.').replace('³', '').replace('²', '');
|
||
const valor = parseFloat(parte);
|
||
if (!isNaN(valor) && valor > 0 && valor < 1000) {
|
||
const sig = partes[i + 1] || '';
|
||
const esRango = sig === '-' || /\d/.test(sig);
|
||
|
||
if (!esRango) {
|
||
const nombreNormalizado = info.nombre;
|
||
let valorFinal = valor;
|
||
if (nombreNormalizado === 'Leucocitos' || nombreNormalizado === 'Plaquetas') {
|
||
valorFinal = valor * 1000;
|
||
}
|
||
|
||
if (info.esPrincipal) {
|
||
resultados.push({
|
||
parametro: nombreNormalizado,
|
||
valor: valorFinal,
|
||
unidad: info.unidad,
|
||
estado: calcularEstadoLaboratorio(nombreNormalizado, parte)
|
||
});
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
return { resultados, observaciones: observacionesExtra.join('\n') };
|
||
};
|
||
|
||
const parseAcidoBaseTexto = (texto: string): Omit<AcidoBase, 'id'> | null => {
|
||
const lineas = texto.split('\n');
|
||
|
||
let ph: number | undefined;
|
||
let pco2: number | undefined;
|
||
let po2: number | undefined;
|
||
let hco3: number | undefined;
|
||
let be: number | undefined;
|
||
let sato2: number | undefined;
|
||
let lactato: number | undefined;
|
||
let fio2: number | undefined;
|
||
let fecha = importFecha;
|
||
|
||
for (const linea of lineas) {
|
||
const cleanLinea = linea.replace(/show_chart|list_alt|\t+/g, ' ').replace(/,/g, '.').replace(/\s+/g, ' ').trim().toLowerCase();
|
||
|
||
const getValue = (param: string): number | undefined => {
|
||
const idx = cleanLinea.indexOf(param);
|
||
if (idx === -1) return undefined;
|
||
const after = cleanLinea.slice(idx + param.length).trim();
|
||
const parts = after.split(' ');
|
||
for (const p of parts) {
|
||
const v = parseFloat(p);
|
||
if (!isNaN(v) && v > 0 && v < 1000) return v;
|
||
}
|
||
return undefined;
|
||
};
|
||
|
||
if (cleanLinea.includes('estado') && cleanLinea.includes('ácido') || cleanLinea.includes('base')) {
|
||
ph = getValue('ph') || (cleanLinea.match(/ph\s+(\d+\.?\d*)/)?.[1] ? parseFloat(cleanLinea.match(/ph\s+(\d+\.?\d*)/)![1]) : undefined);
|
||
pco2 = getValue('pco2') || getValue('pco₂');
|
||
po2 = getValue('po2') || getValue('po₂');
|
||
hco3 = getValue('hco3');
|
||
be = getValue('exceso de base') || getValue('base excess') || getValue('exceso');
|
||
sato2 = getValue('saturación') || getValue('sato2') || getValue('sat');
|
||
lactato = getValue('lactato');
|
||
fio2 = getValue('fio2');
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (!ph) {
|
||
for (const linea of lineas) {
|
||
const cleanLinea = linea.replace(/show_chart|list_alt|\t+/g, ' ').replace(/,/g, '.').replace(/\s+/g, ' ').trim().toLowerCase();
|
||
const getValue = (param: string): number | undefined => {
|
||
const idx = cleanLinea.indexOf(param);
|
||
if (idx === -1) return undefined;
|
||
const after = cleanLinea.slice(idx + param.length).trim();
|
||
const parts = after.split(' ');
|
||
for (const p of parts) {
|
||
const v = parseFloat(p);
|
||
if (!isNaN(v) && v > 0 && v < 1000) return v;
|
||
}
|
||
return undefined;
|
||
};
|
||
|
||
if (cleanLinea.includes('ph') && !cleanLinea.includes('pco2') && !cleanLinea.includes('pco')) { ph = getValue('ph'); }
|
||
else if (cleanLinea.includes('pco2') || cleanLinea.includes('pco₂')) { pco2 = getValue('pco2'); }
|
||
else if (cleanLinea.includes('po2') || cleanLinea.includes('po₂')) { po2 = getValue('po2'); }
|
||
else if (cleanLinea.includes('hco3')) { hco3 = getValue('hco3'); }
|
||
else if (cleanLinea.includes('exceso') || cleanLinea.includes('base excess')) { be = getValue('exceso') || getValue('base'); }
|
||
else if (cleanLinea.includes('saturación') || cleanLinea.includes('sato2')) { sato2 = getValue('saturación') || getValue('sat'); }
|
||
else if (cleanLinea.includes('lactato')) { lactato = getValue('lactato'); }
|
||
else if (cleanLinea.includes('fio2')) { fio2 = getValue('fio2'); }
|
||
}
|
||
}
|
||
|
||
console.log('AB:', { ph, pco2, po2, hco3, be, sato2 });
|
||
|
||
if (ph) {
|
||
const hora = new Date().toTimeString().slice(0, 5);
|
||
return { pacienteId: patientId, fecha, hora, ph, pco2: pco2 || 40, po2: po2 || 85, hco3: hco3 || 24, be: be || 0, sato2: sato2 || 97, lactato, fio2, interpretacion: '' };
|
||
}
|
||
return null;
|
||
};
|
||
|
||
const handleProcesarTexto = () => {
|
||
const { resultados, observaciones } = parseLaboratorioTexto(importTexto);
|
||
const acidoBase = parseAcidoBaseTexto(importTexto);
|
||
|
||
setImportResultados(resultados);
|
||
setImportObservaciones(observaciones);
|
||
setImportAcidoBase(acidoBase);
|
||
};
|
||
|
||
const handleImportarLaboratorio = () => {
|
||
if (!addAcidoBase) return;
|
||
if (importResultados.length === 0 && !importObservaciones && !importAcidoBase) return;
|
||
|
||
if (importAcidoBase) {
|
||
addAcidoBase(importAcidoBase);
|
||
}
|
||
|
||
add({
|
||
pacienteId: patientId,
|
||
fecha: importFecha,
|
||
resultados: importResultados,
|
||
observaciones: importObservaciones
|
||
});
|
||
|
||
setImportDialog(false);
|
||
setImportTexto('');
|
||
setImportResultados([]);
|
||
setImportObservaciones('');
|
||
setImportAcidoBase(null);
|
||
setImportFecha(new Date().toISOString().split('T')[0]);
|
||
};
|
||
|
||
const loadEdit = (l: Laboratorio) => {
|
||
setEdit(l);
|
||
setFecha(l.fecha);
|
||
setObservaciones(l.observaciones || '');
|
||
setHto(getValorRaw(l, 'Hematocrito'));
|
||
setHb(getValorRaw(l, 'Hemoglobina'));
|
||
setGb(getValorRaw(l, 'Leucocitos'));
|
||
setPlaq(getValorRaw(l, 'Plaquetas'));
|
||
setGluc(getValorRaw(l, 'Glucemia'));
|
||
setUrea(getValorRaw(l, 'Urea'));
|
||
setCreat(getValorRaw(l, 'Creatinina'));
|
||
setNa(getValorRaw(l, 'Sodio'));
|
||
setK(getValorRaw(l, 'Potasio'));
|
||
setCl(getValorRaw(l, 'Cloro'));
|
||
setBt(getValorRaw(l, 'Bilirrubina Total'));
|
||
setBd(getValorRaw(l, 'Bilirrubina Directa'));
|
||
setGot(getValorRaw(l, 'GOT'));
|
||
setGpt(getValorRaw(l, 'GPT'));
|
||
setTp(getValorRaw(l, 'Tiempo de Protrombina'));
|
||
setKptt(getValorRaw(l, 'KPTT'));
|
||
setRin(getValorRaw(l, 'INR'));
|
||
setDialog(true);
|
||
};
|
||
|
||
const handleGuardar = () => {
|
||
const resultadosLaboratorio = buildResultados();
|
||
if (edit) {
|
||
update(edit.id, { fecha, resultados: resultadosLaboratorio, observaciones });
|
||
} else {
|
||
add({ pacienteId: patientId || '', fecha, resultados: resultadosLaboratorio, observaciones });
|
||
}
|
||
setDialog(false);
|
||
reset();
|
||
};
|
||
|
||
const labs = lab.filter(l => l.pacienteId === patientId).sort((a, b) => new Date(b.fecha).getTime() - new Date(a.fecha).getTime());
|
||
|
||
return (
|
||
<div className="space-y-4">
|
||
<div className="flex justify-between">
|
||
<div className="flex gap-2">
|
||
<Button variant="outline" onClick={() => { reset(); setDialog(true); }}><Plus className="h-4 w-4 mr-2" />Nuevo Laboratorio</Button>
|
||
<Button variant="outline" onClick={() => { setSelectedLab(lab[0] || null); setParametroEvolucion('Hematocrito'); setEvolDialog(true); }}><TrendingUp className="h-4 w-4 mr-2" />Evolución</Button>
|
||
<Button variant="outline" onClick={() => { setImportFecha(new Date().toISOString().split('T')[0]); setImportTexto(''); setImportResultados([]); setImportObservaciones(''); setImportDialog(true); }}><FileText className="h-4 w-4 mr-2" />Importar</Button>
|
||
</div>
|
||
</div>
|
||
<Dialog open={dialog} onOpenChange={setDialog}>
|
||
<DialogContent className="sm:max-w-2xl max-h-[90vh] overflow-y-auto">
|
||
<DialogHeader><DialogTitle>{edit ? 'Editar' : 'Nuevo'} Laboratorio</DialogTitle></DialogHeader>
|
||
<div className="space-y-4">
|
||
<div><Label>Fecha</Label><Input type="date" value={fecha} onChange={e => setFecha(e.target.value)} /></div>
|
||
|
||
<div className="bg-purple-50 dark:bg-purple-900/30 p-3 rounded-lg border border-purple-200 dark:border-purple-700">
|
||
<p className="text-sm font-bold text-purple-800 mb-3">Hemograma</p>
|
||
<div className="grid grid-cols-4 gap-2">
|
||
<div><Label className="text-xs">Hto</Label><Input placeholder="%" value={hto} onChange={e => setHto(e.target.value)} /></div>
|
||
<div><Label className="text-xs">Hb</Label><Input placeholder="g/dL" value={hb} onChange={e => setHb(e.target.value)} /></div>
|
||
<div><Label className="text-xs">GB</Label><Input placeholder="/mm3" value={gb} onChange={e => setGb(e.target.value)} /></div>
|
||
<div><Label className="text-xs">Plaq</Label><Input placeholder="/mm3" value={plaq} onChange={e => setPlaq(e.target.value)} /></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="bg-green-50 dark:bg-green-900/30 p-3 rounded-lg border border-green-200 dark:border-green-700">
|
||
<p className="text-sm font-bold text-green-800 mb-3">Química</p>
|
||
<div className="grid grid-cols-3 gap-2">
|
||
<div><Label className="text-xs">Gluc</Label><Input placeholder="mg/dL" value={gluc} onChange={e => setGluc(e.target.value)} /></div>
|
||
<div><Label className="text-xs">Urea</Label><Input placeholder="mg/dL" value={urea} onChange={e => setUrea(e.target.value)} /></div>
|
||
<div><Label className="text-xs">Creat.</Label><Input placeholder="mg/dL" value={creat} onChange={e => setCreat(e.target.value)} /></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="bg-green-50 dark:bg-green-900/30 p-3 rounded-lg border border-green-200 dark:border-green-700">
|
||
<p className="text-sm font-bold text-green-800 mb-3">Ionograma</p>
|
||
<div className="grid grid-cols-3 gap-2">
|
||
<div><Label className="text-xs">Na+</Label><Input placeholder="mEq/L" value={na} onChange={e => setNa(e.target.value)} /></div>
|
||
<div><Label className="text-xs">K+</Label><Input placeholder="mEq/L" value={k} onChange={e => setK(e.target.value)} /></div>
|
||
<div><Label className="text-xs">Cl-</Label><Input placeholder="mEq/L" value={cl} onChange={e => setCl(e.target.value)} /></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="bg-green-50 dark:bg-green-900/30 p-3 rounded-lg border border-green-200 dark:border-green-700">
|
||
<p className="text-sm font-bold text-green-800 mb-3">Hepatograma</p>
|
||
<div className="grid grid-cols-4 gap-2">
|
||
<div><Label className="text-xs">Bt</Label><Input placeholder="mg/dL" value={bt} onChange={e => setBt(e.target.value)} /></div>
|
||
<div><Label className="text-xs">BD</Label><Input placeholder="mg/dL" value={bd} onChange={e => setBd(e.target.value)} /></div>
|
||
<div><Label className="text-xs">GOT</Label><Input placeholder="U/L" value={got} onChange={e => setGot(e.target.value)} /></div>
|
||
<div><Label className="text-xs">GPT</Label><Input placeholder="U/L" value={gpt} onChange={e => setGpt(e.target.value)} /></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="bg-cyan-50 dark:bg-cyan-900/30 p-3 rounded-lg border border-cyan-200 dark:border-cyan-700">
|
||
<p className="text-sm font-bold text-cyan-800 mb-3">Coagulograma</p>
|
||
<div className="grid grid-cols-3 gap-2">
|
||
<div><Label className="text-xs">TP</Label><Input placeholder="seg" value={tp} onChange={e => setTp(e.target.value)} /></div>
|
||
<div><Label className="text-xs">KPTT</Label><Input placeholder="seg" value={kptt} onChange={e => setKptt(e.target.value)} /></div>
|
||
<div><Label className="text-xs">RIN</Label><Input placeholder="" value={rin} onChange={e => setRin(e.target.value)} /></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div><Label>Observaciones</Label><textarea className="w-full p-2 border rounded-md" value={observaciones} onChange={e => setObservaciones(e.target.value)} /></div>
|
||
</div>
|
||
<div className="flex justify-end gap-2 mt-4"><Button variant="outline" onClick={() => setDialog(false)}><X className="h-4 w-4 mr-2" />Cancelar</Button><Button onClick={handleGuardar}><Save className="h-4 w-4 mr-2" />Guardar</Button></div>
|
||
</DialogContent>
|
||
</Dialog>
|
||
|
||
<Dialog open={obsDialog} onOpenChange={setObsDialog}>
|
||
<DialogContent className="sm:max-w-lg">
|
||
<DialogHeader><DialogTitle>Observaciones</DialogTitle></DialogHeader>
|
||
<div className="space-y-2">
|
||
<p className="text-sm text-gray-500">{obsLab?.fecha}</p>
|
||
<p className="text-sm text-gray-500">{obsLab?.tipo}</p>
|
||
<div className="bg-gray-50 p-3 rounded-lg border border-gray-200 dark:bg-gray-800 dark:border-gray-700">
|
||
<p className="whitespace-pre-wrap">{obsLab?.observaciones}</p>
|
||
</div>
|
||
</div>
|
||
<div className="flex justify-end gap-2 mt-4"><Button onClick={() => setObsDialog(false)}>Cerrar</Button></div>
|
||
</DialogContent>
|
||
</Dialog>
|
||
|
||
<Dialog open={evolDialog} onOpenChange={setEvolDialog}>
|
||
<DialogContent className="sm:max-w-2xl max-h-[90vh] overflow-y-auto">
|
||
<DialogHeader>
|
||
<DialogTitle className="flex items-center gap-2">
|
||
<TrendingUp className="h-5 w-5" />
|
||
Evolución de Laboratorio
|
||
</DialogTitle>
|
||
</DialogHeader>
|
||
<div className="space-y-4">
|
||
<div>
|
||
<Label>Parámetro a visualizar</Label>
|
||
<Select value={parametroEvolucion} onValueChange={setParametroEvolucion}>
|
||
<SelectTrigger><SelectValue placeholder="Seleccionar parámetro" /></SelectTrigger>
|
||
<SelectContent>
|
||
{parametrosLaboratorio.map(p => (
|
||
<SelectItem key={p} value={p}>{p}</SelectItem>
|
||
))}
|
||
</SelectContent>
|
||
</Select>
|
||
</div>
|
||
{parametroEvolucion && datosEvolucion.length > 0 && (
|
||
<div className="h-64 bg-muted/30 rounded-lg p-4">
|
||
<ResponsiveContainer width="100%" height="100%">
|
||
<LineChart data={datosEvolucion}>
|
||
<CartesianGrid strokeDasharray="3 3" className="opacity-30" />
|
||
<XAxis dataKey="fecha" tick={{ fontSize: 12 }} />
|
||
<YAxis tick={{ fontSize: 12 }} domain={['auto', 'auto']} />
|
||
<Tooltip
|
||
contentStyle={{
|
||
backgroundColor: 'hsl(var(--card))',
|
||
border: '1px solid hsl(var(--border))',
|
||
borderRadius: '8px'
|
||
}}
|
||
/>
|
||
<Line
|
||
type="monotone"
|
||
dataKey="valor"
|
||
stroke="hsl(var(--primary))"
|
||
strokeWidth={2}
|
||
dot={{ fill: 'hsl(var(--primary))', r: 4 }}
|
||
/>
|
||
</LineChart>
|
||
</ResponsiveContainer>
|
||
</div>
|
||
)}
|
||
{parametroEvolucion && datosEvolucion.length === 0 && (
|
||
<div className="text-center py-8 text-gray-400">
|
||
<TrendingUp className="h-12 w-12 mx-auto mb-2 opacity-50" />
|
||
<p>No hay datos para este parámetro</p>
|
||
</div>
|
||
)}
|
||
</div>
|
||
<div className="flex justify-end gap-2 mt-4">
|
||
<Button onClick={() => setEvolDialog(false)}>Cerrar</Button>
|
||
</div>
|
||
</DialogContent>
|
||
</Dialog>
|
||
|
||
<Dialog open={importDialog} onOpenChange={setImportDialog}>
|
||
<DialogContent className="sm:max-w-2xl max-h-[90vh] overflow-y-auto">
|
||
<DialogHeader>
|
||
<DialogTitle className="flex items-center gap-2">
|
||
<FileText className="h-5 w-5" />
|
||
Importar Laboratorio
|
||
</DialogTitle>
|
||
</DialogHeader>
|
||
<div className="space-y-4">
|
||
<div>
|
||
<Label>Fecha del Laboratorio</Label>
|
||
<Input type="date" value={importFecha} onChange={e => setImportFecha(e.target.value)} />
|
||
</div>
|
||
<div>
|
||
<Label>Pegar texto del resultado de laboratorio</Label>
|
||
<textarea
|
||
className="w-full p-2 border rounded-md text-sm min-h-[200px] font-mono"
|
||
placeholder="Pegue aquí el texto del resultado de laboratorio..."
|
||
value={importTexto}
|
||
onChange={e => setImportTexto(e.target.value)}
|
||
/>
|
||
</div>
|
||
<Button variant="outline" onClick={handleProcesarTexto} className="w-full">
|
||
<FileText className="h-4 w-4 mr-2" />
|
||
Procesar Texto
|
||
</Button>
|
||
|
||
{importResultados.length > 0 && (
|
||
<div className="border rounded-md max-h-48 overflow-y-auto">
|
||
<Table>
|
||
<TableHeader>
|
||
<TableRow>
|
||
<TableHead>Parámetro</TableHead>
|
||
<TableHead>Valor</TableHead>
|
||
<TableHead>Unidad</TableHead>
|
||
</TableRow>
|
||
</TableHeader>
|
||
<TableBody>
|
||
{importResultados.map((r, idx) => (
|
||
<TableRow key={idx}>
|
||
<TableCell>{r.parametro}</TableCell>
|
||
<TableCell className={r.estado === 'Alto' ? 'text-amber-600' : r.estado === 'Bajo' ? 'text-blue-600' : r.estado === 'Crítico' ? 'text-red-600 font-bold' : 'text-green-600'}>{r.valor}</TableCell>
|
||
<TableCell>{r.unidad}</TableCell>
|
||
</TableRow>
|
||
))}
|
||
</TableBody>
|
||
</Table>
|
||
</div>
|
||
)}
|
||
|
||
{importAcidoBase && (
|
||
<div className="border rounded-md p-3 bg-green-50">
|
||
<p className="text-sm font-medium mb-2 flex items-center gap-2">
|
||
<Activity className="h-4 w-4" />
|
||
Estado Ácido Base detectado:
|
||
</p>
|
||
<div className="grid grid-cols-4 gap-2 text-sm">
|
||
<div><span className="text-gray-500">pH:</span> <span className="font-medium">{importAcidoBase.ph}</span></div>
|
||
<div><span className="text-gray-500">pCO2:</span> <span className="font-medium">{importAcidoBase.pco2}</span></div>
|
||
<div><span className="text-gray-500">pO2:</span> <span className="font-medium">{importAcidoBase.po2}</span></div>
|
||
<div><span className="text-gray-500">HCO3:</span> <span className="font-medium">{importAcidoBase.hco3}</span></div>
|
||
<div><span className="text-gray-500">BE:</span> <span className="font-medium">{importAcidoBase.be}</span></div>
|
||
<div><span className="text-gray-500">SatO2:</span> <span className="font-medium">{importAcidoBase.sato2}%</span></div>
|
||
{importAcidoBase.lactato && <div><span className="text-gray-500">Lactato:</span> <span className="font-medium">{importAcidoBase.lactato}</span></div>}
|
||
{importAcidoBase.fio2 && <div><span className="text-gray-500">FiO2:</span> <span className="font-medium">{importAcidoBase.fio2}</span></div>}
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{importObservaciones && (
|
||
<div className="border rounded-md p-3 bg-muted/50">
|
||
<p className="text-sm font-medium mb-1">Valores adicionales en observaciones:</p>
|
||
<p className="text-sm whitespace-pre-wrap">{importObservaciones}</p>
|
||
</div>
|
||
)}
|
||
|
||
{importResultados.length === 0 && !importObservaciones && !importAcidoBase && importTexto && (
|
||
<div className="text-center py-4 text-gray-500">
|
||
<p>No se detectaron parámetros. Verifique que el texto contenga los nombres correctos.</p>
|
||
</div>
|
||
)}
|
||
</div>
|
||
<div className="flex justify-end gap-2 mt-4">
|
||
<Button variant="outline" onClick={() => setImportDialog(false)}>Cancelar</Button>
|
||
<Button onClick={handleImportarLaboratorio} disabled={importResultados.length === 0 && !importObservaciones && !importAcidoBase}>
|
||
<Save className="h-4 w-4 mr-2" />
|
||
{(importAcidoBase ? 'Guardar Gasometría' : 'Guardar Laboratorio')}
|
||
</Button>
|
||
</div>
|
||
</DialogContent>
|
||
</Dialog>
|
||
|
||
{labs.length === 0 ? (
|
||
<div className="text-center py-8 text-gray-400"><FlaskConical className="h-12 w-12 mx-auto mb-2 opacity-50" /><p>No hay laboratorios</p></div>
|
||
) : (
|
||
<div className="relative w-full overflow-auto grid grid-cols-1">
|
||
|
||
<Table>
|
||
<TableHeader>
|
||
<TableRow>
|
||
<TableHead>Fecha</TableHead>
|
||
<TableHead>Hto</TableHead>
|
||
<TableHead>Hb</TableHead>
|
||
<TableHead>GB</TableHead>
|
||
<TableHead>Plaq</TableHead>
|
||
<TableHead>Gluc</TableHead>
|
||
<TableHead>Urea</TableHead>
|
||
<TableHead>Creat.</TableHead>
|
||
<TableHead>Na+</TableHead>
|
||
<TableHead>K+</TableHead>
|
||
<TableHead>Cl-</TableHead>
|
||
<TableHead>BT</TableHead>
|
||
<TableHead>BDir</TableHead>
|
||
<TableHead>GOT</TableHead>
|
||
<TableHead>GPT</TableHead>
|
||
<TableHead>TP</TableHead>
|
||
<TableHead>KPTT</TableHead>
|
||
<TableHead>RIN</TableHead>
|
||
<TableHead>Acciones</TableHead>
|
||
</TableRow>
|
||
</TableHeader>
|
||
<TableBody>
|
||
{labs.map(l => (
|
||
<TableRow key={l.id}>
|
||
<TableCell>{l.fecha}</TableCell>
|
||
<TableCell className={getValorStyle(l, 'Hematocrito')}>{getValor(l, 'Hematocrito')}</TableCell>
|
||
<TableCell className={getValorStyle(l, 'Hemoglobina')}>{getValor(l, 'Hemoglobina')}</TableCell>
|
||
<TableCell className={getValorStyle(l, 'Leucocitos')}>{getValor(l, 'Leucocitos')}</TableCell>
|
||
<TableCell className={getValorStyle(l, 'Plaquetas')}>{getValor(l, 'Plaquetas')}</TableCell>
|
||
<TableCell className={getValorStyle(l, 'Glucemia')}>{getValor(l, 'Glucemia')}</TableCell>
|
||
<TableCell className={getValorStyle(l, 'Urea')}>{getValor(l, 'Urea')}</TableCell>
|
||
<TableCell className={getValorStyle(l, 'Creatinina')}>{getValor(l, 'Creatinina')}</TableCell>
|
||
<TableCell className={getValorStyle(l, 'Sodio')}>{getValor(l, 'Sodio')}</TableCell>
|
||
<TableCell className={getValorStyle(l, 'Potasio')}>{getValor(l, 'Potasio')}</TableCell>
|
||
<TableCell className={getValorStyle(l, 'Cloro')}>{getValor(l, 'Cloro')}</TableCell>
|
||
<TableCell className={getValorStyle(l, 'Bilirrubina Total')}>{getValor(l, 'Bilirrubina Total')}</TableCell>
|
||
<TableCell className={getValorStyle(l, 'Bilirrubina Directa')}>{getValor(l, 'Bilirrubina Directa')}</TableCell>
|
||
<TableCell className={getValorStyle(l, 'GOT')}>{getValor(l, 'GOT')}</TableCell>
|
||
<TableCell className={getValorStyle(l, 'GPT')}>{getValor(l, 'GPT')}</TableCell>
|
||
<TableCell className={getValorStyle(l, 'Tiempo de Protrombina')}>{getValor(l, 'Tiempo de Protrombina')}</TableCell>
|
||
<TableCell className={getValorStyle(l, 'KPTT')}>{getValor(l, 'KPTT')}</TableCell>
|
||
<TableCell className={getValorStyle(l, 'INR')}>{getValor(l, 'INR')}</TableCell>
|
||
<TableCell>
|
||
<DropdownMenu>
|
||
<DropdownMenuTrigger asChild>
|
||
<Button size="sm" variant="ghost" className="h-8 w-8 p-0">
|
||
<MoreHorizontal className="h-4 w-4" />
|
||
</Button>
|
||
</DropdownMenuTrigger>
|
||
<DropdownMenuContent align="end">
|
||
<DropdownMenuItem onClick={() => loadEdit(l)}>
|
||
<Edit className="mr-2 h-4 w-4" />
|
||
Editar
|
||
</DropdownMenuItem>
|
||
<DropdownMenuItem onClick={() => del(l.id)} className="text-red-600">
|
||
<Trash2 className="mr-2 h-4 w-4" />
|
||
Eliminar
|
||
</DropdownMenuItem>
|
||
<DropdownMenuItem onClick={() => { setObsLab(l); setObsDialog(true); }}>
|
||
<ClipboardList className="mr-2 h-4 w-4" />
|
||
Ver Observaciones
|
||
</DropdownMenuItem>
|
||
</DropdownMenuContent>
|
||
</DropdownMenu>
|
||
</TableCell>
|
||
</TableRow>
|
||
))}
|
||
</TableBody>
|
||
</Table>
|
||
</div>
|
||
|
||
)}
|
||
</div>
|
||
);
|
||
}
|
||
|
||
function SeccionEvoluciones({ evos, internacionId, add, update, del }: {
|
||
evos: Evolucion[];
|
||
internacionId: string;
|
||
add: (e: Omit<Evolucion, 'id'>) => void;
|
||
update: (id: string, data: Partial<Evolucion>) => void;
|
||
del: (id: string) => void;
|
||
}) {
|
||
const [dialog, setDialog] = useState(false);
|
||
const [edit, setEdit] = useState<Evolucion | null>(null);
|
||
const [expanded, setExpanded] = useState<string | null>(null);
|
||
const [fecha, setFecha] = useState(new Date().toISOString().split('T')[0]);
|
||
const [hora, setHora] = useState(new Date().toTimeString().slice(0, 5));
|
||
const [medico, setMedico] = useState('');
|
||
const [temperatura, setTemperatura] = useState('');
|
||
const [presionSistolica, setPresionSistolica] = useState('');
|
||
const [presionDiastolica, setPresionDiastolica] = useState('');
|
||
const [frecuenciaCardiaca, setFrecuenciaCardiaca] = useState('');
|
||
const [frecuenciaRespiratoria, setFrecuenciaRespiratoria] = useState('');
|
||
const [saturacionO2, setSaturacionO2] = useState('');
|
||
const [snc, setSnc] = useState('');
|
||
const [cardiovascular, setCardiovascular] = useState('');
|
||
const [respiratorio, setRespiratorio] = useState('');
|
||
const [abdominal, setAbdominal] = useState('');
|
||
const [genitourinario, setGenitourinario] = useState('');
|
||
const [pielAnexos, setPielAnexos] = useState('');
|
||
const [soma, setSoma] = useState('');
|
||
const [novedades, setNovedades] = useState('');
|
||
const [comentario, setComentario] = useState('');
|
||
const [pendientes, setPendientes] = useState('');
|
||
|
||
const reset = () => {
|
||
setFecha(new Date().toISOString().split('T')[0]);
|
||
setHora(new Date().toTimeString().slice(0, 5));
|
||
setMedico('');
|
||
setTemperatura('');
|
||
setPresionSistolica('');
|
||
setPresionDiastolica('');
|
||
setFrecuenciaCardiaca('');
|
||
setFrecuenciaRespiratoria('');
|
||
setSaturacionO2('');
|
||
setSnc('');
|
||
setCardiovascular('');
|
||
setRespiratorio('');
|
||
setAbdominal('');
|
||
setGenitourinario('');
|
||
setPielAnexos('');
|
||
setSoma('');
|
||
setNovedades('');
|
||
setComentario('');
|
||
setPendientes('');
|
||
setEdit(null);
|
||
};
|
||
|
||
const openEdit = (e: Evolucion) => {
|
||
setEdit(e);
|
||
setFecha(e.fecha);
|
||
setHora(e.hora);
|
||
setMedico(e.medico);
|
||
setTemperatura(e.signosVitales?.temperatura?.toString() || '');
|
||
setPresionSistolica(e.signosVitales?.presionSistolica?.toString() || '');
|
||
setPresionDiastolica(e.signosVitales?.presionDiastolica?.toString() || '');
|
||
setFrecuenciaCardiaca(e.signosVitales?.frecuenciaCardiaca?.toString() || '');
|
||
setFrecuenciaRespiratoria(e.signosVitales?.frecuenciaRespiratoria?.toString() || '');
|
||
setSaturacionO2(e.signosVitales?.saturacionO2?.toString() || '');
|
||
setSnc(e.examenFisico?.SNC || '');
|
||
setCardiovascular(e.examenFisico?.Cardiovascular || '');
|
||
setRespiratorio(e.examenFisico?.Respiratorio || '');
|
||
setAbdominal(e.examenFisico?.Abdominal || '');
|
||
setGenitourinario(e.examenFisico?.Genitourinario || '');
|
||
setPielAnexos(e.examenFisico?.PielAnexos || '');
|
||
setSoma(e.examenFisico?.SOMA || '');
|
||
setNovedades(e.novedades || '');
|
||
setComentario(e.comentario || '');
|
||
setPendientes(e.pendientes || '');
|
||
setDialog(true);
|
||
};
|
||
|
||
const evosFiltered = evos.filter(e => e.internacionId === internacionId).sort((a, b) => new Date(b.fecha + 'T' + b.hora).getTime() - new Date(a.fecha + 'T' + a.hora).getTime());
|
||
|
||
const handle = () => {
|
||
if (!medico) return;
|
||
const signosVitales = temperatura || presionSistolica || frecuenciaCardiaca
|
||
? { temperatura: temperatura ? parseFloat(temperatura) : undefined, presionSistolica: presionSistolica ? parseInt(presionSistolica) : undefined, presionDiastolica: presionDiastolica ? parseInt(presionDiastolica) : undefined, frecuenciaCardiaca: frecuenciaCardiaca ? parseInt(frecuenciaCardiaca) : undefined, frecuenciaRespiratoria: frecuenciaRespiratoria ? parseInt(frecuenciaRespiratoria) : undefined, saturacionO2: saturacionO2 ? parseInt(saturacionO2) : undefined }
|
||
: undefined;
|
||
const examenFisico = snc || cardiovascular || respiratorio || abdominal || genitourinario || pielAnexos || soma
|
||
? { SNC: snc || undefined, Cardiovascular: cardiovascular || undefined, Respiratorio: respiratorio || undefined, Abdominal: abdominal || undefined, Genitourinario: genitourinario || undefined, PielAnexos: pielAnexos || undefined, SOMA: soma || undefined }
|
||
: undefined;
|
||
const data = { fecha, hora, medico, signosVitales, examenFisico, novedades: novedades || undefined, comentario: comentario || undefined, pendientes: pendientes || undefined };
|
||
if (edit) update(edit.id, data);
|
||
else add({ internacionId, ...data, signosVitales, examenFisico });
|
||
setDialog(false);
|
||
reset();
|
||
};
|
||
|
||
return (
|
||
<div className="space-y-4">
|
||
<div className="flex">
|
||
<Button variant="outline" onClick={() => { reset(); setDialog(true); }}><Plus className="h-4 w-4 mr-2" />Nueva Evolución</Button>
|
||
</div>
|
||
<Dialog open={dialog} onOpenChange={setDialog}>
|
||
<DialogContent className="sm:max-w-3xl max-h-[90vh] overflow-y-auto">
|
||
<DialogHeader><DialogTitle>{edit ? 'Editar' : 'Nueva'} Evolución</DialogTitle></DialogHeader>
|
||
<div className="space-y-4">
|
||
<div className="grid grid-cols-2 gap-4">
|
||
<div><Label>Fecha *</Label><Input type="date" value={fecha} onChange={e => setFecha(e.target.value)} /></div>
|
||
<div><Label>Hora *</Label><Input type="time" value={hora} onChange={e => setHora(e.target.value)} /></div>
|
||
</div>
|
||
<div className="bg-blue-50 p-3 rounded-lg border border-blue-200">
|
||
<p className="text-sm font-bold text-blue-800 mb-3 flex items-center gap-2"><Activity className="h-4 w-4" />Signos Vitales</p>
|
||
<div className="grid grid-cols-2 sm:grid-cols-3 gap-4">
|
||
<div><Label className="flex items-center gap-1"><Heart className="h-4 w-4" />TAS</Label><Input type="number" value={presionSistolica} onChange={e => setPresionSistolica(e.target.value)} placeholder="120" /></div>
|
||
<div><Label className="flex items-center gap-1"><Heart className="h-4 w-4" />TAD</Label><Input type="number" value={presionDiastolica} onChange={e => setPresionDiastolica(e.target.value)} placeholder="80" /></div>
|
||
<div><Label className="flex items-center gap-1"><Activity className="h-4 w-4" />FC</Label><Input type="number" value={frecuenciaCardiaca} onChange={e => setFrecuenciaCardiaca(e.target.value)} placeholder="72" /></div>
|
||
<div><Label className="flex items-center gap-1"><Wind className="h-4 w-4" />FR</Label><Input type="number" value={frecuenciaRespiratoria} onChange={e => setFrecuenciaRespiratoria(e.target.value)} placeholder="16" /></div>
|
||
<div><Label className="flex items-center gap-1"><Thermometer className="h-4 w-4" />T°</Label><Input type="number" step="0.1" value={temperatura} onChange={e => setTemperatura(e.target.value)} placeholder="36.5" /></div>
|
||
<div><Label className="flex items-center gap-1"><Droplets className="h-4 w-4" />SatO2</Label><Input type="number" value={saturacionO2} onChange={e => setSaturacionO2(e.target.value)} placeholder="98" /></div>
|
||
</div>
|
||
</div>
|
||
<div className="bg-green-50 dark:bg-green-900/30 p-3 rounded-lg border border-green-200 dark:border-green-700">
|
||
<p className="text-sm font-bold text-green-800 mb-3 flex items-center gap-2"><FileText className="h-4 w-4" />Examen Físico</p>
|
||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||
<div><Label>SNC</Label><textarea className="w-full p-2 border rounded-md text-sm min-h-[60px]" value={snc} onChange={e => setSnc(e.target.value)} placeholder="Estado neurológico" /></div>
|
||
<div><Label>Cardiovascular</Label><textarea className="w-full p-2 border rounded-md text-sm min-h-[60px]" value={cardiovascular} onChange={e => setCardiovascular(e.target.value)} placeholder="Hallazgos cardiovasculares" /></div>
|
||
<div><Label>Respiratorio</Label><textarea className="w-full p-2 border rounded-md text-sm min-h-[60px]" value={respiratorio} onChange={e => setRespiratorio(e.target.value)} placeholder="Hallazgos respiratorios" /></div>
|
||
<div><Label>Abdominal</Label><textarea className="w-full p-2 border rounded-md text-sm min-h-[60px]" value={abdominal} onChange={e => setAbdominal(e.target.value)} placeholder="Hallazgos abdominales" /></div>
|
||
<div><Label>Genitourinario</Label><textarea className="w-full p-2 border rounded-md text-sm min-h-[60px]" value={genitourinario} onChange={e => setGenitourinario(e.target.value)} placeholder="Hallazgos genitourinarios" /></div>
|
||
<div><Label>Piel y anexos</Label><textarea className="w-full p-2 border rounded-md text-sm min-h-[60px]" value={pielAnexos} onChange={e => setPielAnexos(e.target.value)} placeholder="Estado de piel y anexos" /></div>
|
||
<div><Label>SOMA</Label><Input value={soma} onChange={e => setSoma(e.target.value)} placeholder="Estado SOMA" /></div>
|
||
</div>
|
||
</div>
|
||
<div><Label>Novedades</Label><textarea className="w-full p-2 border rounded-md text-sm min-h-[80px]" value={novedades} onChange={e => setNovedades(e.target.value)} placeholder="Novedades..." /></div>
|
||
<div><Label>Comentario</Label><textarea className="w-full p-2 border rounded-md text-sm min-h-[80px]" value={comentario} onChange={e => setComentario(e.target.value)} placeholder="Comentarios adicionales..." /></div>
|
||
<div><Label>Pendientes</Label><textarea className="w-full p-2 border rounded-md text-sm min-h-[80px]" value={pendientes} onChange={e => setPendientes(e.target.value)} placeholder="Pendientes..." /></div>
|
||
<div><Label>Médico *</Label><Input value={medico} onChange={e => setMedico(e.target.value)} placeholder="Nombre del médico" /></div>
|
||
</div>
|
||
<div className="flex justify-end gap-2 mt-4"><Button variant="outline" onClick={() => { reset(); setDialog(false); }}><X className="h-4 w-4 mr-2" />Cancelar</Button><Button onClick={handle} disabled={!medico}><Save className="h-4 w-4 mr-2" />Guardar Evolución</Button></div>
|
||
</DialogContent>
|
||
</Dialog>
|
||
{evosFiltered.length === 0 ? <div className="text-center py-8 text-gray-400"><FileText className="h-12 w-12 mx-auto mb-2 opacity-50" /><p>No hay evoluciones</p></div> :
|
||
evosFiltered.map(e => {
|
||
const isExpanded = expanded === e.id;
|
||
const sv = e.signosVitales;
|
||
const svText = sv ? [sv.presionSistolica && sv.presionDiastolica ? `PA: ${sv.presionSistolica}/${sv.presionDiastolica}` : null, sv.frecuenciaCardiaca ? `FC: ${sv.frecuenciaCardiaca}` : null, sv.frecuenciaRespiratoria ? `FR: ${sv.frecuenciaRespiratoria}` : null, sv.temperatura ? `T: ${sv.temperatura}°C` : null, sv.saturacionO2 ? `SatO2: ${sv.saturacionO2}%` : null].filter(Boolean).join(' | ') : null;
|
||
return (
|
||
<Card key={e.id} className="mb-2"><CardContent className="p-4">
|
||
<div className="flex flex-col gap-3">
|
||
<div className="flex items-start justify-between">
|
||
<div><p className="font-medium">{e.fecha} {e.hora}</p><p className="text-sm text-gray-600">Médico: {e.medico}</p></div>
|
||
<div className="flex gap-1">
|
||
<Button size="sm" variant="outline" onClick={() => setExpanded(isExpanded ? null : e.id)}>{isExpanded ? <ChevronUp className="h-4 w-4" /> : <ChevronDown className="h-4 w-4" />}</Button>
|
||
<Button size="sm" variant="ghost" onClick={() => openEdit(e)}><Pencil /></Button>
|
||
<Button size="sm" variant="ghost" className="text-red-600" onClick={() => del(e.id)}><Trash2 /></Button>
|
||
</div>
|
||
</div>
|
||
{svText && <div className="bg-blue-50 p-2 rounded-lg"><p className="text-sm font-medium text-blue-800 flex items-center gap-2"><Activity className="h-4 w-4" />Signos Vitales: {svText}</p></div>}
|
||
{isExpanded && <>
|
||
{e.examenFisico && <div className="bg-green-50 dark:bg-green-900/30 p-3 rounded-lg border border-green-200 dark:border-green-700"><p className="text-sm font-medium text-green-800 mb-2">Examen Físico:</p><div className="grid grid-cols-2 sm:grid-cols-4 gap-2 text-xs">
|
||
{e.examenFisico.SNC && <div><span className="font-medium">SNC:</span> {e.examenFisico.SNC}</div>}
|
||
{e.examenFisico.Cardiovascular && <div><span className="font-medium">CV:</span> {e.examenFisico.Cardiovascular}</div>}
|
||
{e.examenFisico.Respiratorio && <div><span className="font-medium">Resp:</span> {e.examenFisico.Respiratorio}</div>}
|
||
{e.examenFisico.Abdominal && <div><span className="font-medium">Abd:</span> {e.examenFisico.Abdominal}</div>}
|
||
{e.examenFisico.Genitourinario && <div><span className="font-medium">GU:</span> {e.examenFisico.Genitourinario}</div>}
|
||
{e.examenFisico.PielAnexos && <div><span className="font-medium">Piel:</span> {e.examenFisico.PielAnexos}</div>}
|
||
{e.examenFisico.SOMA && <div><span className="font-medium">SOMA:</span> {e.examenFisico.SOMA}</div>}
|
||
</div></div>}
|
||
{e.novedades && <div className="bg-red-50 p-3 rounded-lg border border-red-200 mt-3"><p className="text-sm font-medium text-red-800 mb-1">Novedades:</p><p className="text-sm text-gray-800 whitespace-pre-wrap">{e.novedades}</p></div>}
|
||
{e.comentario && <div><p className="text-sm font-medium text-gray-700">Comentario:</p><p className="text-sm text-gray-600 whitespace-pre-wrap">{e.comentario}</p></div>}
|
||
{e.pendientes && <div className="bg-amber-50 p-3 rounded-lg border border-amber-200"><p className="text-sm font-medium text-amber-800 mb-1">Pendientes:</p><p className="text-sm text-amber-700 whitespace-pre-wrap">{e.pendientes}</p></div>}
|
||
</>}
|
||
</div>
|
||
</CardContent></Card>
|
||
);
|
||
})
|
||
}
|
||
</div>
|
||
);
|
||
}
|
||
|
||
function SeccionAcidosBase({ ab, patientId, add, update, del }: {
|
||
ab: AcidoBase[];
|
||
patientId: string;
|
||
add: (a: Omit<AcidoBase, 'id'>) => void;
|
||
update: (id: string, datos: Partial<AcidoBase>) => void;
|
||
del: (id: string) => void;
|
||
}) {
|
||
const [dialog, setDialog] = useState(false);
|
||
const [editDialog, setEditDialog] = useState(false);
|
||
const [selected, setSelected] = useState<AcidoBase | null>(null);
|
||
const [fecha, setFecha] = useState(new Date().toISOString().split('T')[0]);
|
||
const [hora, setHora] = useState(new Date().toTimeString().slice(0, 5));
|
||
const [ph, setPh] = useState('');
|
||
const [pco2, setPco2] = useState('');
|
||
const [po2, setPo2] = useState('');
|
||
const [hco3, setHco3] = useState('');
|
||
const [be, setBe] = useState('');
|
||
const [sato2, setSato2] = useState('');
|
||
const [lactato, setLactato] = useState('');
|
||
const [fio2, setFio2] = useState('');
|
||
const [interpretacion, setInterpretacion] = useState('');
|
||
|
||
const reset = () => {
|
||
setFecha(new Date().toISOString().split('T')[0]);
|
||
setHora(new Date().toTimeString().slice(0, 5));
|
||
setPh(''); setPco2(''); setPo2(''); setHco3(''); setBe(''); setSato2(''); setLactato(''); setFio2(''); setInterpretacion('');
|
||
setSelected(null);
|
||
};
|
||
|
||
const loadEdit = (a: AcidoBase) => {
|
||
setSelected(a);
|
||
setFecha(a.fecha);
|
||
setHora(a.hora);
|
||
setPh(String(a.ph));
|
||
setPco2(String(a.pco2));
|
||
setPo2(String(a.po2));
|
||
setHco3(String(a.hco3));
|
||
setBe(String(a.be));
|
||
setSato2(String(a.sato2));
|
||
setLactato(a.lactato ? String(a.lactato) : '');
|
||
setFio2(a.fio2 ? String(a.fio2) : '');
|
||
setInterpretacion(a.interpretacion || '');
|
||
setEditDialog(true);
|
||
};
|
||
|
||
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<string, { min: number; max: number }> = { 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 '';
|
||
let interp = '';
|
||
if (phVal < 7.35) { interp += 'Acidemia - '; interp += pco2Val > 45 ? 'Acidosis Respiratoria' : hco3Val < 22 ? 'Acidosis Metabólica' : 'Acidosis Mixta'; }
|
||
else if (phVal > 7.45) { interp += 'Alcalemia - '; interp += pco2Val < 35 ? 'Alcalosis Respiratoria' : hco3Val > 26 ? 'Alcalosis Metabólica' : 'Alcalosis Mixta'; }
|
||
else { interp += 'pH Normal - '; interp += pco2Val > 45 || hco3Val < 22 ? 'Compensación en curso' : 'Equilibrio Ácido-Base'; }
|
||
if (beVal && Math.abs(beVal) > 2) interp += beVal > 0 ? ' (Exceso de bases elevado)' : ' (Déficit de bases)';
|
||
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 });
|
||
setDialog(false);
|
||
reset();
|
||
};
|
||
|
||
const handleEdit = () => {
|
||
if (!selected || !ph || !pco2 || !po2 || !hco3 || !be || !sato2) return;
|
||
const interpretacionAuto = interpretarGasometria();
|
||
update(selected.id, { 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 });
|
||
setEditDialog(false);
|
||
reset();
|
||
};
|
||
|
||
return (
|
||
<div className="space-y-4">
|
||
<div className="flex">
|
||
<Button variant="outline" onClick={() => { reset(); setDialog(true); }}><Plus className="h-4 w-4 mr-2" />Nueva Gasometría</Button>
|
||
</div>
|
||
<Dialog open={dialog} onOpenChange={setDialog}>
|
||
<DialogContent className="sm:max-w-2xl max-h-[90vh] overflow-y-auto">
|
||
<DialogHeader><DialogTitle>Nueva Gasometría Arterial</DialogTitle></DialogHeader>
|
||
<div className="space-y-4">
|
||
<div className="grid grid-cols-2 gap-4">
|
||
<div><Label>Fecha *</Label><Input type="date" value={fecha} onChange={e => setFecha(e.target.value)} /></div>
|
||
<div><Label>Hora *</Label><Input type="time" value={hora} onChange={e => setHora(e.target.value)} /></div>
|
||
</div>
|
||
<div className="grid grid-cols-4 sm:grid-cols-8 gap-2">
|
||
<div><Label>pH *</Label><Input type="number" step="0.01" value={ph} onChange={e => setPh(e.target.value)} placeholder="7.40" /></div>
|
||
<div><Label>pCO2 *</Label><Input type="number" value={pco2} onChange={e => setPco2(e.target.value)} placeholder="40" /></div>
|
||
<div><Label>pO2 *</Label><Input type="number" value={po2} onChange={e => setPo2(e.target.value)} placeholder="85" /></div>
|
||
<div><Label>HCO3 *</Label><Input type="number" step="0.1" value={hco3} onChange={e => setHco3(e.target.value)} placeholder="24" /></div>
|
||
<div><Label>BE *</Label><Input type="number" step="0.1" value={be} onChange={e => setBe(e.target.value)} placeholder="0" /></div>
|
||
<div><Label>SatO2 *</Label><Input type="number" value={sato2} onChange={e => setSato2(e.target.value)} placeholder="97" /></div>
|
||
<div><Label>Lactato</Label><Input type="number" step="0.1" value={lactato} onChange={e => setLactato(e.target.value)} placeholder="1.0" /></div>
|
||
<div><Label>FiO2</Label><Input type="number" value={fio2} onChange={e => setFio2(e.target.value)} placeholder="21" /></div>
|
||
</div>
|
||
{ph && pco2 && hco3 && <div className="bg-blue-50 p-3 rounded-lg border border-blue-200"><p className="text-sm font-medium text-blue-800 flex items-center gap-2"><Activity className="h-4 w-4" />Interpretación automática: {interpretarGasometria()}</p></div>}
|
||
<div><Label>Interpretación / Comentarios</Label><textarea className="w-full p-2 border rounded-md text-sm min-h-[80px]" value={interpretacion} onChange={e => setInterpretacion(e.target.value)} placeholder="Interpretación clínica..." /></div>
|
||
</div>
|
||
<div className="flex justify-end gap-2 mt-4"><Button variant="outline" onClick={() => setDialog(false)}><X className="h-4 w-4 mr-2" />Cancelar</Button><Button onClick={handle} disabled={!ph || !pco2 || !po2 || !hco3 || !be || !sato2}><Plus className="h-4 w-4 mr-2" />Guardar Gasometría</Button></div>
|
||
</DialogContent>
|
||
</Dialog>
|
||
<Dialog open={editDialog} onOpenChange={setEditDialog}>
|
||
<DialogContent className="sm:max-w-2xl max-h-[90vh] overflow-y-auto">
|
||
<DialogHeader><DialogTitle>Editar Gasometría</DialogTitle></DialogHeader>
|
||
<div className="space-y-4">
|
||
<div className="grid grid-cols-2 gap-4">
|
||
<div><Label>Fecha *</Label><Input type="date" value={fecha} onChange={e => setFecha(e.target.value)} /></div>
|
||
<div><Label>Hora *</Label><Input type="time" value={hora} onChange={e => setHora(e.target.value)} /></div>
|
||
</div>
|
||
<div className="grid grid-cols-4 sm:grid-cols-8 gap-2">
|
||
<div><Label>pH *</Label><Input type="number" step="0.01" value={ph} onChange={e => setPh(e.target.value)} placeholder="7.40" /></div>
|
||
<div><Label>pCO2 *</Label><Input type="number" value={pco2} onChange={e => setPco2(e.target.value)} placeholder="40" /></div>
|
||
<div><Label>pO2 *</Label><Input type="number" value={po2} onChange={e => setPo2(e.target.value)} placeholder="85" /></div>
|
||
<div><Label>HCO3 *</Label><Input type="number" step="0.1" value={hco3} onChange={e => setHco3(e.target.value)} placeholder="24" /></div>
|
||
<div><Label>BE *</Label><Input type="number" step="0.1" value={be} onChange={e => setBe(e.target.value)} placeholder="0" /></div>
|
||
<div><Label>SatO2 *</Label><Input type="number" value={sato2} onChange={e => setSato2(e.target.value)} placeholder="97" /></div>
|
||
<div><Label>Lactato</Label><Input type="number" step="0.1" value={lactato} onChange={e => setLactato(e.target.value)} placeholder="1.0" /></div>
|
||
<div><Label>FiO2</Label><Input type="number" value={fio2} onChange={e => setFio2(e.target.value)} placeholder="0.21" /></div>
|
||
</div>
|
||
{ph && pco2 && hco3 && <div className="bg-blue-50 p-3 rounded-lg border border-blue-200"><p className="text-sm font-medium text-blue-800 flex items-center gap-2"><Activity className="h-4 w-4" />Interpretación automática: {interpretarGasometria()}</p></div>}
|
||
<div><Label>Interpretación / Comentarios</Label><textarea className="w-full p-2 border rounded-md text-sm min-h-[80px]" value={interpretacion} onChange={e => setInterpretacion(e.target.value)} placeholder="Interpretación clínica..." /></div>
|
||
</div>
|
||
<div className="flex justify-end gap-2 mt-4"><Button variant="outline" onClick={() => setEditDialog(false)}><X className="h-4 w-4 mr-2" />Cancelar</Button><Button onClick={handleEdit} disabled={!ph || !pco2 || !po2 || !hco3 || !be || !sato2}><Save className="h-4 w-4 mr-2" />Guardar Cambios</Button></div>
|
||
</DialogContent>
|
||
</Dialog>
|
||
{abFiltered.length === 0 ? <div className="text-center py-8 text-gray-400"><Activity className="h-12 w-12 mx-auto mb-2 opacity-50" /><p>No hay gasometrías</p></div> :
|
||
abFiltered.map(a => {
|
||
const phSt = getEstadoParametro('ph', a.ph);
|
||
return (
|
||
<Card key={a.id} className="mb-2"><CardContent className="p-4">
|
||
<div className="flex flex-col gap-3">
|
||
<div className="flex items-start justify-between">
|
||
<div><p className="font-medium">{a.fecha} {a.hora}</p></div>
|
||
<div className="flex items-center gap-2">
|
||
|
||
<Badge variant="outline" className="text-xs">{a.fio2 && a.po2 &&<span className="mr-1">PaFiO2: {Math.round(a.po2 / a.fio2 * 100) / 100}</span>}</Badge>
|
||
<Badge variant="outline" className="text-xs">{a.fio2 && <span className="mr-1">FiO₂:{a.fio2}</span>}</Badge>
|
||
<Badge className={getColorPh(a.ph)}>pH: {a.ph}</Badge>
|
||
<Button size="sm" variant="ghost" className="text-blue-600" onClick={() => loadEdit(a)}><Edit /></Button>
|
||
<Button size="sm" variant="ghost" className="text-red-600" onClick={() => del(a.id)}><Trash2 /></Button>
|
||
</div>
|
||
</div>
|
||
<div className="grid grid-cols-3 sm:grid-cols-8 gap-2">
|
||
<div className="bg-gray-50 p-2 rounded text-center"><p className="text-xs text-gray-500">pH</p><p className={`font-bold ${phSt.color}`}>{a.ph}</p></div>
|
||
<div className="bg-gray-50 p-2 rounded text-center"><p className="text-xs text-gray-500">pCO2</p><p className="font-bold">{a.pco2}</p></div>
|
||
<div className="bg-gray-50 p-2 rounded text-center"><p className="text-xs text-gray-500">pO2</p><p className="font-bold">{a.po2}</p></div>
|
||
<div className="bg-gray-50 p-2 rounded text-center"><p className="text-xs text-gray-500">HCO3</p><p className="font-bold">{a.hco3}</p></div>
|
||
<div className="bg-gray-50 p-2 rounded text-center"><p className="text-xs text-gray-500">BE</p><p className="font-bold">{a.be}</p></div>
|
||
<div className="bg-gray-50 p-2 rounded text-center"><p className="text-xs text-gray-500">SatO2</p><p className="font-bold">{a.sato2}%</p></div>
|
||
{a.lactato && <div className="bg-gray-50 p-2 rounded text-center"><p className="text-xs text-gray-500">Lactato</p><p className="font-bold">{a.lactato}</p></div>}
|
||
|
||
{a.fio2 && <div className="bg-gray-50 p-2 rounded text-center"><p className="text-xs text-gray-500">FiO2</p><p className="font-bold">{a.fio2}</p></div>}
|
||
</div>
|
||
{a.interpretacion && <div className="bg-teal-50 p-3 rounded-lg border border-teal-200"><p className="text-sm font-medium text-teal-800">Interpretación: {a.interpretacion}</p></div>}
|
||
</div>
|
||
</CardContent></Card>
|
||
);
|
||
})
|
||
}
|
||
</div>
|
||
);
|
||
}
|
||
|
||
function SeccionCultivos({ cults, patient, add, update, del }: {
|
||
cults: Cultivo[];
|
||
patient: Paciente;
|
||
add: (c: Omit<Cultivo, 'id'>) => void;
|
||
update: (id: string, data: Partial<Cultivo>) => void;
|
||
del: (id: string) => void;
|
||
}) {
|
||
const [dialog, setDialog] = useState(false);
|
||
const [resDialog, setResDialog] = useState(false);
|
||
const [editDialog, setEditDialog] = useState(false);
|
||
const [selected, setSelected] = useState<Cultivo | null>(null);
|
||
const [isParcialMode, setIsParcialMode] = useState(false);
|
||
const [fechaToma, setFechaToma] = useState(new Date().toISOString().split('T')[0]);
|
||
const [protocolo, setProtocolo] = useState('');
|
||
const [tipoMuestra, setTipoMuestra] = useState<Cultivo['tipoMuestra']>('HMCx2');
|
||
const [observaciones, setObservaciones] = useState('');
|
||
const [fechaResultado, setFechaResultado] = useState(new Date().toISOString().split('T')[0]);
|
||
const [estadoResultado, setEstadoResultado] = useState<Cultivo['estado']>('Parcial');
|
||
const [germen, setGermen] = useState('');
|
||
const [sensible, setSensible] = useState('');
|
||
const [resistente, setResistente] = useState('');
|
||
|
||
const reset = () => {
|
||
setFechaToma(new Date().toISOString().split('T')[0]);
|
||
setProtocolo('');
|
||
setTipoMuestra('HMCx2');
|
||
setObservaciones('');
|
||
setSelected(null);
|
||
};
|
||
|
||
const resetRes = () => {
|
||
setFechaResultado(new Date().toISOString().split('T')[0]);
|
||
setEstadoResultado('Positivo');
|
||
setGermen('');
|
||
setSensible('');
|
||
setResistente('');
|
||
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 || '');
|
||
setResistente(c.resistente || '');
|
||
setResDialog(true);
|
||
};
|
||
|
||
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);
|
||
setGermen(c.germen || '');
|
||
setSensible(c.sensible || '');
|
||
setResistente(c.resistente || '');
|
||
setEditDialog(true);
|
||
};
|
||
|
||
const cs = cults.filter(c => c.pacienteId === patient.id).sort((a, b) => new Date(b.fechaToma).getTime() - new Date(a.fechaToma).getTime());
|
||
|
||
const getEstadoColor = (estado: Cultivo['estado']) => {
|
||
switch (estado) {
|
||
case 'NAF/Pendiente': return 'bg-amber-100 text-amber-800';
|
||
case 'Parcial': return 'bg-orange-100 text-orange-800';
|
||
case 'Positivo': return 'bg-red-100 text-red-800';
|
||
case 'Negativo': return 'bg-green-100 text-green-800';
|
||
}
|
||
};
|
||
|
||
const getEstadoLabel = (estado: Cultivo['estado']) => {
|
||
switch (estado) {
|
||
case 'NAF/Pendiente': return 'NAF/Pendiente';
|
||
case 'Parcial': return 'Parcial';
|
||
case 'Positivo': return 'Positivo';
|
||
case 'Negativo': return 'Negativo Final';
|
||
}
|
||
};
|
||
|
||
const handle = () => { add({ pacienteId: patient.id, fechaToma, protocolo: protocolo || undefined, tipoMuestra, observaciones: observaciones || undefined, estado: 'NAF/Pendiente' }); setDialog(false); reset(); };
|
||
|
||
const handleParcial = () => {
|
||
if (!selected) return;
|
||
update(selected.id, { protocolo: protocolo || undefined, estado: 'Parcial', germen: germen || undefined, sensible: sensible || undefined, resistente: resistente || undefined });
|
||
setResDialog(false);
|
||
resetRes();
|
||
};
|
||
|
||
const handleDefinitivo = () => {
|
||
if (!selected) return;
|
||
update(selected.id, { protocolo: protocolo || undefined, fechaResultado, estado: estadoResultado, germen: germen || undefined, sensible: sensible || undefined, resistente: resistente || undefined });
|
||
setEditDialog(false);
|
||
resetRes();
|
||
};
|
||
|
||
return (
|
||
<div className="space-y-4">
|
||
<div className="flex">
|
||
<Button variant="outline" onClick={() => { reset(); setDialog(true); }}><Plus className="h-4 w-4 mr-2" />Nuevo Cultivo</Button>
|
||
</div>
|
||
<Dialog open={dialog} onOpenChange={setDialog}>
|
||
<DialogContent className="sm:max-w-lg">
|
||
<DialogHeader><DialogTitle>Nuevo Cultivo</DialogTitle></DialogHeader>
|
||
<div className="space-y-4">
|
||
<div className="grid grid-cols-2 gap-4">
|
||
<div><Label>Fecha de Toma *</Label><Input type="date" value={fechaToma} onChange={e => setFechaToma(e.target.value)} /></div>
|
||
<div><Label>Protocolo</Label><Input value={protocolo} onChange={e => setProtocolo(e.target.value)} placeholder="N° Protocolo" /></div>
|
||
</div>
|
||
<div>
|
||
<Label>Tipo de Muestra *</Label>
|
||
<Select value={tipoMuestra} onValueChange={(v: Cultivo['tipoMuestra']) => setTipoMuestra(v)}>
|
||
<SelectTrigger><SelectValue /></SelectTrigger>
|
||
<SelectContent>
|
||
<SelectItem value="HMCx2">HMCx2</SelectItem>
|
||
<SelectItem value="RC">RC</SelectItem>
|
||
<SelectItem value="PC">PC</SelectItem>
|
||
<SelectItem value="UC">UC</SelectItem>
|
||
<SelectItem value="LCR">LCR</SelectItem>
|
||
<SelectItem value="LP">LP</SelectItem>
|
||
<SelectItem value="LAsc">LAsc</SelectItem>
|
||
<SelectItem value="LAbd">LAbd</SelectItem>
|
||
<SelectItem value="Coleccion">Coleccion</SelectItem>
|
||
</SelectContent>
|
||
</Select>
|
||
</div>
|
||
<div><Label>Observaciones</Label><textarea className="w-full p-2 border rounded-md text-sm min-h-[60px]" value={observaciones} onChange={e => setObservaciones(e.target.value)} /></div>
|
||
</div>
|
||
<div className="flex justify-end gap-2 mt-4"><Button variant="outline" onClick={() => setDialog(false)}><X className="h-4 w-4 mr-2" />Cancelar</Button><Button variant="outline" onClick={handle}><Plus className="h-4 w-4 mr-2" />Guardar</Button></div>
|
||
</DialogContent>
|
||
</Dialog>
|
||
|
||
<Dialog open={resDialog} onOpenChange={setResDialog}>
|
||
<DialogContent className="sm:max-w-xl max-h-[90vh] overflow-y-auto">
|
||
<DialogHeader><DialogTitle>Cargar Resultado Parcial</DialogTitle></DialogHeader>
|
||
<div className="space-y-4">
|
||
<div className="bg-orange-50 border border-orange-200 p-3 rounded-lg">
|
||
<p className="text-sm font-medium text-orange-800">Resultado Parcial - Sujeto a modificación</p>
|
||
<p className="text-xs text-orange-600 mt-1">Ingrese resultado parcial. Esto puede modificarse posteriormente.</p>
|
||
</div>
|
||
<div><Label>Protocolo</Label><Input value={protocolo} onChange={e => setProtocolo(e.target.value)} placeholder="N° Protocolo" /></div>
|
||
<div><Label>Germen *</Label><Input value={germen} onChange={e => setGermen(e.target.value)} placeholder="Ej: Staphylococcos aureus" /></div>
|
||
<div><Label>Sensible a:</Label><Input value={sensible} onChange={e => setSensible(e.target.value)} placeholder="Ej: Amikacina, Vancomicina" /></div>
|
||
<div><Label>Resistente a:</Label><Input value={resistente} onChange={e => setResistente(e.target.value)} placeholder="Ej: Oxacilina" /></div>
|
||
</div>
|
||
<div className="flex justify-end gap-2 mt-4"><Button variant="outline" onClick={() => { resetRes(); setResDialog(false); }}><X className="h-4 w-4 mr-2" />Cancelar</Button><Button variant="outline" onClick={handleParcial} disabled={!germen}><Save className="h-4 w-4 mr-2" />Guardar Parcial</Button></div>
|
||
</DialogContent>
|
||
</Dialog>
|
||
|
||
<Dialog open={editDialog} onOpenChange={setEditDialog}>
|
||
<DialogContent className="sm:max-w-xl max-h-[90vh] overflow-y-auto">
|
||
<DialogHeader><DialogTitle>Cargar Resultado Definitivo</DialogTitle></DialogHeader>
|
||
<div className="space-y-4">
|
||
<div className="grid grid-cols-2 gap-4">
|
||
<div><Label>Fecha de Resultado *</Label><Input type="date" value={fechaResultado} onChange={e => setFechaResultado(e.target.value)} /></div>
|
||
<div><Label>Resultado *</Label>
|
||
<Select value={estadoResultado} onValueChange={(v: Cultivo['estado']) => setEstadoResultado(v)}>
|
||
<SelectTrigger><SelectValue /></SelectTrigger>
|
||
<SelectContent>
|
||
<SelectItem value="Positivo">Positivo</SelectItem>
|
||
<SelectItem value="Negativo">Negativo Final</SelectItem>
|
||
</SelectContent>
|
||
</Select>
|
||
</div>
|
||
</div>
|
||
<div><Label>Protocolo</Label><Input value={protocolo} onChange={e => setProtocolo(e.target.value)} placeholder="N° Protocolo" /></div>
|
||
{estadoResultado === 'Positivo' && (
|
||
<>
|
||
<div><Label>Germen *</Label><Input value={germen} onChange={e => setGermen(e.target.value)} placeholder="Ej: Staphylococcus aureus" /></div>
|
||
<div><Label>Sensible a:</Label><Input value={sensible} onChange={e => setSensible(e.target.value)} placeholder="Ej: Amikacina, Gentamicina" /></div>
|
||
<div><Label>Resistente a:</Label><Input value={resistente} onChange={e => setResistente(e.target.value)} placeholder="Ej: Ampicilina, Cefazolina" /></div>
|
||
</>
|
||
)}
|
||
</div>
|
||
<div className="flex justify-end gap-2 mt-4"><Button variant="outline" onClick={() => { resetRes(); setEditDialog(false); }}><X className="h-4 w-4 mr-2" />Cancelar</Button><Button variant="outline" onClick={handleDefinitivo} disabled={estadoResultado === 'Positivo' && !germen}><Save className="h-4 w-4 mr-2" />Guardar Definitivo</Button></div>
|
||
</DialogContent>
|
||
</Dialog>
|
||
|
||
<div>
|
||
<div className="grid grid-cols-1 gap-4">
|
||
{cs.length === 0 ? <div className="text-center py-8 text-gray-400"><Microscope className="h-12 w-12 mx-auto mb-2 opacity-50" /><p>No hay cultivos</p></div> :
|
||
cs.map(c => (
|
||
<Card key={c.id} className="hover:shadow-md transition-shadow"><CardContent className="p-4">
|
||
<div className="flex flex-col gap-3">
|
||
<div className="flex flex-col sm:flex-row sm:items-start sm:justify-between gap-4">
|
||
<div className="flex items-center gap-3">
|
||
<div className={`h-10 w-10 rounded-full flex items-center justify-center flex-shrink-0 ${c.estado === 'NAF/Pendiente' ? 'bg-amber-100' : c.estado === 'Parcial' ? 'bg-orange-100' : c.estado === 'Positivo' ? 'bg-red-100' : 'bg-green-100'}`}>
|
||
<Microscope className={`h-5 w-5 ${c.estado === 'NAF/Pendiente' ? 'text-amber-600' : c.estado === 'Parcial' ? 'text-orange-600' : c.estado === 'Positivo' ? 'text-red-600' : 'text-green-600'}`} />
|
||
</div>
|
||
<div>
|
||
<p className="font-medium">{c.fechaToma}</p>
|
||
<div className="flex items-center gap-2 text-sm text-gray-500">
|
||
<Badge variant="outline">{c.tipoMuestra}</Badge>
|
||
<Badge className={getEstadoColor(c.estado)}>{getEstadoLabel(c.estado)}</Badge>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div className="flex flex-wrap items-center gap-2">
|
||
{c.estado === 'NAF/Pendiente' && (
|
||
<>
|
||
<Button size="sm" variant="outline" onClick={() => openParcial(c)}><AlertCircle className="h-4 w-4 mr-1" />Parcial</Button>
|
||
<Button size="sm" variant="outline" onClick={() => { openDefinitivo(c); setIsParcialMode(false); }}><CheckCircle2 className="h-4 w-4 mr-1" />Definitivo</Button>
|
||
</>
|
||
)}
|
||
{c.estado === 'Parcial' && (
|
||
<>
|
||
<Button size="sm" variant="outline" onClick={() => openParcial(c)}><Pencil className="h-4 w-4 mr-1" />Editar Parcial</Button>
|
||
<Button size="sm" variant="outline" onClick={() => openDefinitivo(c)}><CheckCircle2 className="h-4 w-4 mr-1" />Definitivo</Button>
|
||
</>
|
||
)}
|
||
{(c.estado === 'Positivo' || c.estado === 'Negativo') && <Button size="sm" variant="outline" onClick={() => openDefinitivo(c)}><Pencil className="h-4 w-4" /></Button>}
|
||
<Button size="sm" variant="outline" className="text-red-600" onClick={() => del(c.id)}><Trash2 /></Button>
|
||
</div>
|
||
</div>
|
||
{c.protocolo && <p className="text-xs text-gray-500">Protocolo: {c.protocolo}</p>}
|
||
{c.observaciones && <p className="text-sm text-gray-600 bg-gray-50 p-2 rounded">{c.observaciones}</p>}
|
||
{c.estado === 'Parcial' && c.germen && (
|
||
<div className="bg-orange-50 p-3 rounded-lg border border-orange-200">
|
||
<p className="text-sm font-medium text-orange-800 flex items-center gap-2"><AlertCircle className="h-4 w-4" />PARCIAL - Germen: {c.germen}</p>
|
||
{(c.sensible || c.resistente) && (
|
||
<div className="mt-2 space-y-1">
|
||
{c.sensible && <p className="text-xs text-orange-700">Sensible: {c.sensible}</p>}
|
||
{c.resistente && <p className="text-xs text-orange-700">Resistente: {c.resistente}</p>}
|
||
</div>
|
||
)}
|
||
</div>
|
||
)}
|
||
{c.estado === 'Positivo' && c.germen && (
|
||
<div className="bg-red-50 p-3 rounded-lg border border-red-200">
|
||
<p className="text-sm font-medium text-red-800 flex items-center gap-2"><AlertCircle className="h-4 w-4" />Germen: {c.germen}</p>
|
||
{c.fechaResultado && <p className="text-xs text-red-600 mt-1">Resultado: {c.fechaResultado}</p>}
|
||
{(c.sensible || c.resistente) && (
|
||
<div className="mt-2 space-y-2">
|
||
{c.sensible && <div><p className="text-xs font-medium text-green-700 mb-1">Sensible a:</p><p className="text-sm text-green-800">{c.sensible}</p></div>}
|
||
{c.resistente && <div><p className="text-xs font-medium text-red-700 mb-1">Resistente a:</p><p className="text-sm text-red-800">{c.resistente}</p></div>}
|
||
</div>
|
||
)}
|
||
</div>
|
||
)}
|
||
{c.estado === 'Negativo' && (
|
||
<div className="bg-green-50 dark:bg-green-900/30 p-3 rounded-lg border border-green-200 dark:border-green-700">
|
||
<p className="text-sm font-medium text-green-800 flex items-center gap-2"><CheckCircle2 className="h-4 w-4" />Sin crecimiento de microorganismos</p>
|
||
{c.fechaResultado && <p className="text-xs text-green-600 mt-1">Resultado: {c.fechaResultado}</p>}
|
||
</div>
|
||
)}
|
||
</div>
|
||
</CardContent></Card>
|
||
))}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
function SeccionEstudiosComplementarios({ estudios, internacionId, pacienteId, add, del }: {
|
||
estudios: EstudioComplementario[];
|
||
internacionId: string;
|
||
pacienteId: string;
|
||
add: (e: Omit<EstudioComplementario, 'id'>) => 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 (
|
||
<div className="space-y-4">
|
||
<div className="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4">
|
||
<div className="flex gap-2">
|
||
<Button variant="outline" onClick={() => { reset(); setDialog(true); }}>
|
||
<Plus className="h-4 w-4 mr-2" />
|
||
Nuevo Estudio
|
||
</Button>
|
||
</div>
|
||
<Select value={filtro} onValueChange={(v: 'todos' | 'internacion') => setFiltro(v)}>
|
||
<SelectTrigger className="w-[180px]">
|
||
<SelectValue placeholder="Filtrar" />
|
||
</SelectTrigger>
|
||
<SelectContent>
|
||
<SelectItem value="internacion">Solo esta internación</SelectItem>
|
||
<SelectItem value="todos">Todos los estudios</SelectItem>
|
||
</SelectContent>
|
||
</Select>
|
||
</div>
|
||
|
||
<Dialog open={dialog} onOpenChange={setDialog}>
|
||
<DialogContent className="sm:max-w-lg">
|
||
<DialogHeader>
|
||
<DialogTitle>Nuevo Estudio Complementario</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>Tipo de Estudio</Label>
|
||
<Input
|
||
value={tipo}
|
||
onChange={e => setTipo(e.target.value)}
|
||
placeholder="Ej: Radiografía de tórax, ECG, Tomografía, etc."
|
||
/>
|
||
</div>
|
||
<div>
|
||
<Label>Resultado</Label>
|
||
<textarea
|
||
className="w-full p-2 border rounded-md text-sm min-h-[100px]"
|
||
value={resultado}
|
||
onChange={e => setResultado(e.target.value)}
|
||
placeholder="Ingrese el resultado del estudio..."
|
||
/>
|
||
</div>
|
||
</div>
|
||
<div className="flex justify-end gap-2 mt-4">
|
||
<Button variant="outline" onClick={() => setDialog(false)}>Cancelar</Button>
|
||
<Button onClick={handleGuardar} disabled={!tipo || !resultado}>
|
||
<Save className="h-4 w-4 mr-2" />
|
||
Guardar
|
||
</Button>
|
||
</div>
|
||
</DialogContent>
|
||
</Dialog>
|
||
|
||
{estudiosFiltrados.length === 0 ? (
|
||
<div className="text-center py-8 text-gray-400">
|
||
<ClipboardList className="h-12 w-12 mx-auto mb-2 opacity-50" />
|
||
<p>No hay estudios complementarios</p>
|
||
</div>
|
||
) : (
|
||
<div className="space-y-3">
|
||
{estudiosFiltrados.sort((a, b) => new Date(b.fecha).getTime() - new Date(a.fecha).getTime()).map(e => (
|
||
<Card key={e.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">{e.fecha}</span>
|
||
</div>
|
||
<h4 className="font-semibold text-gray-900 dark:text-white">{e.tipo}</h4>
|
||
<p className="text-sm text-gray-600 dark:text-gray-300 mt-1 whitespace-pre-wrap">{e.resultado}</p>
|
||
</div>
|
||
<Button size="sm" variant="outline" className="text-red-600 self-start" onClick={() => del(e.id)}>
|
||
<Trash2 className="h-4 w-4" />
|
||
</Button>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
))}
|
||
</div>
|
||
)}
|
||
</div>
|
||
);
|
||
} |