diff --git a/src/App.tsx b/src/App.tsx index fffd990..d90359d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -13,6 +13,7 @@ import { NuevoIngreso } from '@/sections/NuevoIngreso'; import { EditIngreso } from '@/sections/EditIngreso'; import { Login } from '@/sections/Login'; import { GestionUsuarios } from '@/sections/GestionUsuarios'; +import { PendientesSala } from '@/sections/PendientesSala'; import { Spinner } from '@/components/ui/spinner'; import { Button } from '@/components/ui/button'; import { ThemeProvider } from "@/components/theme-provider"; @@ -91,6 +92,22 @@ function AppContent() { getCamaById={store.getCamaById} onVerHC={(id) => { store.setCurrentInternacion(id); store.setVista('historiaclinica'); }} onNuevoIngreso={() => store.setVista('nuevoingreso')} + onVerPendientesDeSala={() => store.setVista('pendientessala')} + /> + ); + case 'pendientessala': + return ( + { store.setCurrentInternacion(id); store.setVista('historiaclinica'); }} + onVolver={() => store.setVista('internaciones')} + getPacienteById={store.getPacienteById} + getCamaById={store.getCamaById} /> ); case 'evoluciones': diff --git a/src/sections/Internaciones.tsx b/src/sections/Internaciones.tsx index bba192d..2aa78d1 100644 --- a/src/sections/Internaciones.tsx +++ b/src/sections/Internaciones.tsx @@ -1,5 +1,5 @@ import { useState } from 'react'; -import { ClipboardList, Plus, Search, LogOut, CheckCircle2, MoreHorizontal, FileText, Trash2, Bed, User, Calendar, IdCard, Activity, CalendarDays, Clock, Settings } from 'lucide-react'; +import { ClipboardList, Plus, Search, LogOut, CheckCircle2, MoreHorizontal, FileText, Trash2, Bed, User, Calendar, IdCard, Activity, CalendarDays, Clock, Settings, ListTodo } from 'lucide-react'; import { Card, CardContent } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; @@ -33,6 +33,7 @@ interface InternacionesProps { getCamaById: (id: string) => Cama | undefined; onVerHC?: (internacionId: string) => void; onNuevoIngreso?: () => void; + onVerPendientesDeSala?: () => void; } export function Internaciones({ @@ -47,6 +48,7 @@ export function Internaciones({ grupos, onVerHC, onNuevoIngreso, + onVerPendientesDeSala, }: InternacionesProps) { const { currentUser } = useHospitalStore(); const [filtroEstado, setFiltroEstado] = useState<'todas' | 'activas' | 'finalizadas'>('todas'); @@ -229,13 +231,14 @@ export function Internaciones({

Gestión de internaciones en sala

- - - - +
+ + + + Nuevo Ingreso @@ -367,7 +370,17 @@ export function Internaciones({
-
+ + + + {/* Filtros */} diff --git a/src/sections/PendientesSala.tsx b/src/sections/PendientesSala.tsx new file mode 100644 index 0000000..4af18bb --- /dev/null +++ b/src/sections/PendientesSala.tsx @@ -0,0 +1,373 @@ +import { useState } from 'react'; +import { + ListTodo, + Search, + ArrowLeft, + Clock, + Bed, + Trash2, + FileText, + Copy, + CheckSquare, + Square +} 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 { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'; +import { toast } from 'sonner'; +import type { Pendiente, Internacion, Paciente, Cama } from '@/types'; +import { formatDateDDMMYYYY, getNombreProfesional } from '@/lib/utils'; +import { useHospitalStore } from '@/hooks/useHospitalStore'; + +interface PendientesSalaProps { + pendientes: Pendiente[]; + internaciones: Internacion[]; + pacientes: Paciente[]; + camas?: Cama[]; + onActualizarPendiente: (id: string, datos: Partial) => Promise | void; + onEliminarPendiente: (id: string) => Promise | void; + onVerHC: (internacionId: string) => void; + onVolver: () => void; + getPacienteById: (id: string) => Paciente | undefined; + getCamaById: (id: string) => Cama | undefined; +} + +export function PendientesSala({ + pendientes, + internaciones, + onActualizarPendiente, + onEliminarPendiente, + onVerHC, + onVolver, + getPacienteById, + getCamaById, +}: PendientesSalaProps) { + const { currentUser } = useHospitalStore(); + const [busqueda, setBusqueda] = useState(''); + const [filtroCategoria, setFiltroCategoria] = useState('todos'); + const [filtroEstado, setFiltroEstado] = useState('pendiente'); + const [filtroFechaProgramada, setFiltroFechaProgramada] = useState(''); + + // Active internaciones map for quick lookup + const internacionesActivas = internaciones.filter(i => i.activa); + const activeInternacionMap = new Map(); + internacionesActivas.forEach(i => { + if (i.id) activeInternacionMap.set(i.id, i); + if (i.pacienteId) activeInternacionMap.set(i.pacienteId, i); + }); + + // Filter pending items belonging to active interned patients + const pendientesSala = pendientes.filter(p => { + const internacion = (p.internacionId ? activeInternacionMap.get(p.internacionId) : undefined) || + (p.pacienteId ? activeInternacionMap.get(p.pacienteId) : undefined); + return !!internacion; + }); + + // Apply search, category, status, and scheduled date filters + const pendientesFiltrados = pendientesSala.filter(p => { + const internacion = (p.internacionId ? activeInternacionMap.get(p.internacionId) : undefined) || + (p.pacienteId ? activeInternacionMap.get(p.pacienteId) : undefined); + const paciente = p.pacienteId ? getPacienteById(p.pacienteId) : undefined; + const cama = internacion ? getCamaById(internacion.camaId) : undefined; + + const textoMatch = !busqueda || + (paciente && (paciente.nombre.toLowerCase().includes(busqueda.toLowerCase()) || paciente.apellido.toLowerCase().includes(busqueda.toLowerCase()) || paciente.dni.includes(busqueda))) || + (cama && cama.numero.toLowerCase().includes(busqueda.toLowerCase())) || + p.descripcion.toLowerCase().includes(busqueda.toLowerCase()) || + (p.observaciones && p.observaciones.toLowerCase().includes(busqueda.toLowerCase())); + + const categoriaMatch = filtroCategoria === 'todos' || p.categoria === filtroCategoria; + const estadoMatch = filtroEstado === 'todos' || p.estado === filtroEstado; + + // Filter by scheduled date (fechaProgramada), not creation date + const fechaProgMatch = !filtroFechaProgramada || p.fechaProgramada === filtroFechaProgramada; + + return textoMatch && categoriaMatch && estadoMatch && fechaProgMatch; + }).sort((a, b) => { + // Sort by scheduled date if available, then creation date + const fechaA = a.fechaProgramada || a.fechaCreacion || ''; + const fechaB = b.fechaProgramada || b.fechaCreacion || ''; + return fechaB.localeCompare(fechaA); + }); + + const handleToggleEstado = async (p: Pendiente) => { + const nuevoEstado = p.estado === 'realizado' ? 'pendiente' : 'realizado'; + try { + await onActualizarPendiente(p.id, { + estado: nuevoEstado, + fechaRealizado: nuevoEstado === 'realizado' ? new Date().toISOString().split('T')[0] : undefined, + usuarioRealizado: nuevoEstado === 'realizado' && currentUser ? getNombreProfesional(currentUser) : undefined, + }); + toast.success(nuevoEstado === 'realizado' ? 'Marcado como realizado' : 'Marcado como pendiente'); + } catch { + toast.error('Error al cambiar estado'); + } + }; + + const handleEliminar = async (id: string) => { + try { + await onEliminarPendiente(id); + toast.success('Pendiente eliminado'); + } catch { + toast.error('Error al eliminar'); + } + }; + + const handleCopiarPendientes = () => { + const activos = pendientesSala.filter(p => p.estado === 'pendiente'); + if (activos.length === 0) { + toast.error('No hay pendientes activos en la sala'); + return; + } + const texto = activos.map((p, idx) => { + const internacion = (p.internacionId ? activeInternacionMap.get(p.internacionId) : undefined) || + (p.pacienteId ? activeInternacionMap.get(p.pacienteId) : undefined); + const paciente = p.pacienteId ? getPacienteById(p.pacienteId) : undefined; + const cama = internacion ? getCamaById(internacion.camaId) : undefined; + const infoPaciente = paciente ? `${paciente.apellido}, ${paciente.nombre}` : 'Paciente'; + const infoCama = cama ? `[Cama ${cama.numero}]` : ''; + + let line = `${idx + 1}. ${infoCama} ${infoPaciente} - [${(p.categoria || 'General').toUpperCase()}] ${p.descripcion}`; + if (p.fechaProgramada) line += ` (Prog: ${formatDateDDMMYYYY(p.fechaProgramada)}${p.horaProgramada ? ' ' + p.horaProgramada : ''})`; + if (p.prioridad === 'alta') line += ' (ALTA)'; + if (p.observaciones) line += ` - Obs: ${p.observaciones}`; + return line; + }).join('\n'); + + navigator.clipboard.writeText(`PENDIENTES DE SALA:\n${texto}`); + toast.success('Pendientes copiados al portapapeles'); + }; + + return ( +
+ {/* Header */} +
+
+ +
+

+ + Pendientes de Sala +

+

+ Gestión y seguimiento de pendientes de todos los pacientes internados ({pendientesSala.filter(p => p.estado === 'pendiente').length} pendientes activos) +

+
+
+
+ +
+
+ + {/* Filters Card */} + + +
+
+ + setBusqueda(e.target.value)} + /> +
+ +
+ +
+ +
+ +
+ +
+
+ setFiltroFechaProgramada(e.target.value)} + placeholder="Fecha programada" + /> +
+ {filtroFechaProgramada && ( + + )} +
+
+
+
+ + {/* Lista de Pendientes en Tabla / Tarjetas */} + {pendientesFiltrados.length === 0 ? ( +
+ +

No se encontraron pendientes

+

Pruebe cambiando los filtros de búsqueda o fecha programada.

+
+ ) : ( +
+
+ + + + Estado + Cama + Paciente + Categoría + Prioridad + Descripción / Observaciones + Fecha Programada + Acciones + + + + {pendientesFiltrados.map((p) => { + const internacion = (p.internacionId ? activeInternacionMap.get(p.internacionId) : undefined) || + (p.pacienteId ? activeInternacionMap.get(p.pacienteId) : undefined); + const paciente = p.pacienteId ? getPacienteById(p.pacienteId) : undefined; + const cama = internacion ? getCamaById(internacion.camaId) : undefined; + const esRealizado = p.estado === 'realizado'; + + return ( + + + + + +
+ + {cama ? cama.numero : '-'} +
+
+ +
+ {paciente ? `${paciente.apellido}, ${paciente.nombre}` : 'Paciente desconocido'} +
+
+ DNI: {paciente?.dni || 'N/A'} +
+
+ + + {p.categoria || 'General'} + + + + {p.prioridad === 'alta' ? ( + + Alta + + ) : p.prioridad === 'media' ? ( + + Media + + ) : ( + + Baja + + )} + + +
+ {p.descripcion} +
+ {p.observaciones && ( +
+ Obs: {p.observaciones} +
+ )} +
+ + {p.fechaProgramada ? ( +
+ + {formatDateDDMMYYYY(p.fechaProgramada)} {p.horaProgramada ? `a las ${p.horaProgramada}` : ''} +
+ ) : ( + Sin programar + )} +
+ +
+ {internacion && ( + + )} + +
+
+
+ ); + })} +
+
+
+
+ )} +
+ ); +} diff --git a/src/types/index.ts b/src/types/index.ts index a95db04..9faf7d4 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -236,7 +236,7 @@ export interface MovimientoIndicacion { observaciones?: string; } -export type Vista = 'dashboard' | 'camas' | 'pacientes' | 'internaciones' | 'evoluciones' | 'laboratorios' | 'acidobase' | 'cultivos' | 'historiaclinica' | 'nuevoingreso' | 'editaringreso' | 'usuarios'; +export type Vista = 'dashboard' | 'camas' | 'pacientes' | 'internaciones' | 'evoluciones' | 'laboratorios' | 'acidobase' | 'cultivos' | 'historiaclinica' | 'nuevoingreso' | 'editaringreso' | 'usuarios' | 'pendientessala'; export type RolUsuario = 'admin' | 'medico' | 'enfermero';