Initial commit: Sistema de Gestión Hospitalaria
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
import * as React from "react"
|
||||
|
||||
const MOBILE_BREAKPOINT = 768
|
||||
|
||||
export function useIsMobile() {
|
||||
const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined)
|
||||
|
||||
React.useEffect(() => {
|
||||
const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
|
||||
const onChange = () => {
|
||||
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
|
||||
}
|
||||
mql.addEventListener("change", onChange)
|
||||
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
|
||||
return () => mql.removeEventListener("change", onChange)
|
||||
}, [])
|
||||
|
||||
return !!isMobile
|
||||
}
|
||||
@@ -0,0 +1,451 @@
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import type {
|
||||
Paciente,
|
||||
Cama,
|
||||
Area,
|
||||
Internacion,
|
||||
Evolucion,
|
||||
Laboratorio,
|
||||
AcidoBase,
|
||||
Cultivo,
|
||||
Vista
|
||||
} from '@/types';
|
||||
|
||||
// Fallback UUID generator for non-secure contexts (HTTP without localhost)
|
||||
function generateUUID(): string {
|
||||
if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
|
||||
return crypto.randomUUID();
|
||||
}
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
|
||||
const r = (Math.random() * 16) | 0;
|
||||
const v = c === 'x' ? r : (r & 0x3) | 0x8;
|
||||
return v.toString(16);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
interface HospitalState {
|
||||
pacientes: Paciente[];
|
||||
camas: Cama[];
|
||||
areas: Area[];
|
||||
internaciones: Internacion[];
|
||||
evoluciones: Evolucion[];
|
||||
laboratorios: Laboratorio[];
|
||||
acidosBase: AcidoBase[];
|
||||
cultivos: Cultivo[];
|
||||
vistaActual: Vista;
|
||||
currentInternacionId?: string | null;
|
||||
}
|
||||
|
||||
|
||||
const API_BASE = import.meta.env.VITE_API_URL ?? `${window.location.origin}/api`;
|
||||
|
||||
const defaultState = (): HospitalState => ({
|
||||
pacientes: [],
|
||||
areas: [],
|
||||
internaciones: [],
|
||||
evoluciones: [],
|
||||
laboratorios: [],
|
||||
acidosBase: [],
|
||||
cultivos: [],
|
||||
vistaActual: 'dashboard',
|
||||
camas: []
|
||||
});
|
||||
|
||||
export function useHospitalStore() {
|
||||
const [state, setState] = useState<HospitalState>(defaultState);
|
||||
const [isLoaded, setIsLoaded] = useState(false);
|
||||
|
||||
// Load state from backend on mount
|
||||
useEffect(() => {
|
||||
let mounted = true;
|
||||
(async () => {
|
||||
try {
|
||||
const res = await fetch(`${API_BASE}/state`);
|
||||
if (!res.ok) throw new Error('no state');
|
||||
const body = await res.json();
|
||||
if (mounted && body) {
|
||||
setState(body as HospitalState);
|
||||
}
|
||||
} catch (err) {
|
||||
// no remote state or unreachable — keep defaults
|
||||
} finally {
|
||||
if (mounted) setIsLoaded(true);
|
||||
}
|
||||
})();
|
||||
return () => { mounted = false; };
|
||||
}, []);
|
||||
|
||||
// Persist state to backend when it changes (debounced)
|
||||
useEffect(() => {
|
||||
if (!isLoaded) return;
|
||||
const t = setTimeout(() => {
|
||||
(async () => {
|
||||
try {
|
||||
await fetch(`${API_BASE}/state`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(state),
|
||||
});
|
||||
} catch (err) {
|
||||
// ignore save errors for now
|
||||
}
|
||||
})();
|
||||
}, 300);
|
||||
return () => clearTimeout(t);
|
||||
}, [state, isLoaded]);
|
||||
|
||||
// Acciones de navegación
|
||||
const setVista = useCallback((vista: Vista) => {
|
||||
setState(prev => ({ ...prev, vistaActual: vista }));
|
||||
}, []);
|
||||
|
||||
// Acciones de pacientes
|
||||
const agregarPaciente = useCallback((paciente: Omit<Paciente, 'id' | 'fechaRegistro'>) => {
|
||||
const nuevoPaciente: Paciente = {
|
||||
...paciente,
|
||||
id: generateUUID(),
|
||||
fechaRegistro: new Date().toISOString().split('T')[0],
|
||||
};
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
pacientes: [...prev.pacientes, nuevoPaciente],
|
||||
}));
|
||||
return nuevoPaciente.id;
|
||||
}, []);
|
||||
|
||||
const actualizarPaciente = useCallback((id: string, datos: Partial<Paciente>) => {
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
pacientes: prev.pacientes.map(p => p.id === id ? { ...p, ...datos } : p),
|
||||
}));
|
||||
}, []);
|
||||
|
||||
const eliminarPaciente = useCallback((id: string) => {
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
pacientes: prev.pacientes.filter(p => p.id !== id),
|
||||
}));
|
||||
}, []);
|
||||
|
||||
// Acciones de camas
|
||||
const actualizarCama = useCallback((id: string, datos: Partial<Cama>) => {
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
camas: prev.camas.map(c => c.id === id ? { ...c, ...datos } : c),
|
||||
}));
|
||||
}, []);
|
||||
|
||||
const agregarCama = useCallback((cama: Omit<Cama, 'id'>) => {
|
||||
const nuevaCama: Cama = {
|
||||
...cama,
|
||||
id: generateUUID(),
|
||||
} as Cama;
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
camas: [...prev.camas, nuevaCama],
|
||||
}));
|
||||
return nuevaCama.id;
|
||||
}, []);
|
||||
|
||||
const eliminarCama = useCallback((id: string) => {
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
camas: prev.camas.filter(c => c.id !== id),
|
||||
internaciones: prev.internaciones.map(i => i.camaId === id ? { ...i, activa: false } : i),
|
||||
}));
|
||||
}, []);
|
||||
|
||||
// Acciones de internaciones
|
||||
const iniciarInternacion = useCallback((internacion: Omit<Internacion, 'id' | 'fechaIngreso' | 'activa'>) => {
|
||||
const nuevaInternacion: Internacion = {
|
||||
...internacion,
|
||||
id: generateUUID(),
|
||||
fechaIngreso: new Date().toISOString().split('T')[0],
|
||||
activa: true,
|
||||
};
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
internaciones: [...prev.internaciones, nuevaInternacion],
|
||||
camas: prev.camas.map(c =>
|
||||
c.id === internacion.camaId
|
||||
? { ...c, estado: 'Ocupada', pacienteId: internacion.pacienteId, internacionId: nuevaInternacion.id }
|
||||
: c
|
||||
),
|
||||
}));
|
||||
return nuevaInternacion.id;
|
||||
}, []);
|
||||
|
||||
const finalizarInternacion = useCallback((internacionId: string, datos: {
|
||||
fechaEgreso: string;
|
||||
diagnosticoEgreso: string;
|
||||
motivoEgreso: Internacion['motivoEgreso']
|
||||
}) => {
|
||||
setState(prev => {
|
||||
const internacion = prev.internaciones.find(i => i.id === internacionId);
|
||||
if (!internacion) return prev;
|
||||
|
||||
return {
|
||||
...prev,
|
||||
internaciones: prev.internaciones.map(i =>
|
||||
i.id === internacionId
|
||||
? { ...i, ...datos, activa: false }
|
||||
: i
|
||||
),
|
||||
cams: prev.camas.map(c =>
|
||||
c.id === internacion.camaId
|
||||
? { ...c, estado: 'Disponible', pacienteId: undefined, internacionId: undefined }
|
||||
: c
|
||||
),
|
||||
};
|
||||
});
|
||||
}, []);
|
||||
|
||||
const actualizarInternacion = useCallback((internacionId: string, datos: Partial<Internacion>) => {
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
internaciones: prev.internaciones.map(i =>
|
||||
i.id === internacionId
|
||||
? { ...i, ...datos }
|
||||
: i
|
||||
),
|
||||
}));
|
||||
}, []);
|
||||
|
||||
// Acciones de evoluciones
|
||||
const agregarEvolucion = useCallback((evolucion: Omit<Evolucion, 'id'>) => {
|
||||
const nuevaEvolucion: Evolucion = {
|
||||
...evolucion,
|
||||
id: generateUUID(),
|
||||
};
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
evoluciones: [...prev.evoluciones, nuevaEvolucion],
|
||||
}));
|
||||
return nuevaEvolucion.id;
|
||||
}, []);
|
||||
|
||||
const eliminarEvolucion = useCallback((id: string) => {
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
evoluciones: prev.evoluciones.filter(e => e.id !== id),
|
||||
}));
|
||||
}, []);
|
||||
|
||||
const actualizarEvolucion = useCallback((id: string, datos: Partial<Evolucion>) => {
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
evoluciones: prev.evoluciones.map(e => e.id === id ? { ...e, ...datos } : e),
|
||||
}));
|
||||
}, []);
|
||||
|
||||
// Acciones de laboratorios
|
||||
const agregarLaboratorio = useCallback((laboratorio: Omit<Laboratorio, 'id'>) => {
|
||||
const nuevoLaboratorio: Laboratorio = {
|
||||
...laboratorio,
|
||||
id: generateUUID(),
|
||||
};
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
laboratorios: [...prev.laboratorios, nuevoLaboratorio],
|
||||
}));
|
||||
return nuevoLaboratorio.id;
|
||||
}, []);
|
||||
|
||||
const eliminarLaboratorio = useCallback((id: string) => {
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
laboratorios: prev.laboratorios.filter(l => l.id !== id),
|
||||
}));
|
||||
}, []);
|
||||
|
||||
const actualizarLaboratorio = useCallback((id: string, datos: Partial<Laboratorio>) => {
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
laboratorios: prev.laboratorios.map(l => l.id === id ? { ...l, ...datos } : l),
|
||||
}));
|
||||
}, []);
|
||||
|
||||
// Acciones de ácido-base
|
||||
const agregarAcidoBase = useCallback((acidoBase: Omit<AcidoBase, 'id'>) => {
|
||||
const nuevoAcidoBase: AcidoBase = {
|
||||
...acidoBase,
|
||||
id: generateUUID(),
|
||||
};
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
acidosBase: [...prev.acidosBase, nuevoAcidoBase],
|
||||
}));
|
||||
return nuevoAcidoBase.id;
|
||||
}, []);
|
||||
|
||||
const eliminarAcidoBase = useCallback((id: string) => {
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
acidosBase: prev.acidosBase.filter(a => a.id !== id),
|
||||
}));
|
||||
}, []);
|
||||
|
||||
// Acciones de cultivos
|
||||
const agregarCultivo = useCallback((cultivo: Omit<Cultivo, 'id'>) => {
|
||||
const nuevoCultivo: Cultivo = {
|
||||
...cultivo,
|
||||
id: generateUUID(),
|
||||
};
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
cultivos: [...prev.cultivos, nuevoCultivo],
|
||||
}));
|
||||
return nuevoCultivo.id;
|
||||
}, []);
|
||||
|
||||
const actualizarCultivo = useCallback((id: string, datos: Partial<Cultivo>) => {
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
cultivos: prev.cultivos.map(c => c.id === id ? { ...c, ...datos } : c),
|
||||
}));
|
||||
}, []);
|
||||
|
||||
const eliminarCultivo = useCallback((id: string) => {
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
cultivos: prev.cultivos.filter(c => c.id !== id),
|
||||
}));
|
||||
}, []);
|
||||
|
||||
// Acciones de areas
|
||||
const agregarArea = useCallback((area: Omit<Area, 'id'>) => {
|
||||
const nuevaArea: Area = { ...area, id: generateUUID() };
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
areas: [...prev.areas, nuevaArea],
|
||||
}));
|
||||
return nuevaArea.id;
|
||||
}, []);
|
||||
|
||||
const actualizarArea = useCallback((id: string, datos: Partial<Area>) => {
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
areas: prev.areas.map(a => a.id === id ? { ...a, ...datos } : a),
|
||||
}));
|
||||
}, []);
|
||||
|
||||
const eliminarArea = useCallback((id: string) => {
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
areas: prev.areas.filter(a => a.id !== id),
|
||||
camas: prev.camas.map(c => c.areaId === id ? { ...c, areaId: undefined } : c),
|
||||
}));
|
||||
}, []);
|
||||
|
||||
// Funciones de utilidad
|
||||
const getPacienteById = useCallback((id: string) => {
|
||||
return state.pacientes.find(p => p.id === id);
|
||||
}, [state.pacientes]);
|
||||
|
||||
const getCamaById = useCallback((id: string) => {
|
||||
return state.camas.find(c => c.id === id);
|
||||
}, [state.camas]);
|
||||
|
||||
const getInternacionById = useCallback((id: string) => {
|
||||
return state.internaciones.find(i => i.id === id);
|
||||
}, [state.internaciones]);
|
||||
|
||||
const getInternacionActivaByPaciente = useCallback((pacienteId: string) => {
|
||||
return state.internaciones.find(i => i.pacienteId === pacienteId && i.activa);
|
||||
}, [state.internaciones]);
|
||||
|
||||
const getEvolucionesByInternacion = useCallback((internacionId: string) => {
|
||||
return state.evoluciones
|
||||
.filter(e => e.internacionId === internacionId)
|
||||
.sort((a, b) => new Date(b.fecha + 'T' + b.hora).getTime() - new Date(a.fecha + 'T' + a.hora).getTime());
|
||||
}, [state.evoluciones]);
|
||||
|
||||
const getLaboratoriosByPaciente = useCallback((pacienteId: string) => {
|
||||
return state.laboratorios
|
||||
.filter(l => l.pacienteId === pacienteId)
|
||||
.sort((a, b) => new Date(b.fecha).getTime() - new Date(a.fecha).getTime());
|
||||
}, [state.laboratorios]);
|
||||
|
||||
const getAcidosBaseByPaciente = useCallback((pacienteId: string) => {
|
||||
return state.acidosBase
|
||||
.filter(a => a.pacienteId === pacienteId)
|
||||
.sort((a, b) => new Date(b.fecha + 'T' + b.hora).getTime() - new Date(a.fecha + 'T' + a.hora).getTime());
|
||||
}, [state.acidosBase]);
|
||||
|
||||
const getCultivosByPaciente = useCallback((pacienteId: string) => {
|
||||
return state.cultivos
|
||||
.filter(c => c.pacienteId === pacienteId)
|
||||
.sort((a, b) => new Date(b.fechaToma).getTime() - new Date(a.fechaToma).getTime());
|
||||
}, [state.cultivos]);
|
||||
|
||||
const getEstadisticas = useCallback(() => {
|
||||
const fueraDeAreaList = state.camas.filter(c => c.areaId === 'Fuera de Area');
|
||||
const camasActivas = state.camas.filter(c => c.areaId !== 'Fuera de Area');
|
||||
const camaPrincipal = state.camas.filter(c => c.areaId !== 'Fuera de Area');
|
||||
const camaOcupadas = state.camas.filter(c => c.estado === 'Ocupada' && c.areaId !== 'Fuera de Area').length;
|
||||
const camasDisponibles = state.camas.filter(c => c.estado === 'Disponible' && c.areaId !== 'Fuera de Area').length;
|
||||
const camasMantenimiento = state.camas.filter(c => c.estado === 'Reparacion').length;
|
||||
const internacionesActivas = state.internaciones.filter(i => i.activa).length;
|
||||
const totalPacientes = state.pacientes.length;
|
||||
const cultivosPendientes = state.cultivos.filter(c => c.estado === 'NAF/Pendiente').length;
|
||||
|
||||
return {
|
||||
camaOcupadas,
|
||||
camasOcupadas: camaOcupadas,
|
||||
camasDisponibles,
|
||||
camasMantenimiento,
|
||||
totalCamas: state.camas.length,
|
||||
totalCamasActivas: camasActivas.length,
|
||||
camasFueraDeArea: fueraDeAreaList.length,
|
||||
internacionesActivas,
|
||||
totalPacientes,
|
||||
cultivosPendientes,
|
||||
porcentajeOcupacion: camaPrincipal.length > 0 ? Math.round((camaOcupadas / camaPrincipal.length) * 100) : 0,
|
||||
};
|
||||
}, [state]);
|
||||
|
||||
// Current internacion selection (for HC page)
|
||||
const setCurrentInternacion = useCallback((id?: string | null) => {
|
||||
setState(prev => ({ ...prev, currentInternacionId: id ?? null }));
|
||||
}, []);
|
||||
|
||||
return {
|
||||
...state,
|
||||
isLoaded,
|
||||
setVista,
|
||||
agregarPaciente,
|
||||
actualizarPaciente,
|
||||
eliminarPaciente,
|
||||
actualizarCama,
|
||||
iniciarInternacion,
|
||||
finalizarInternacion,
|
||||
actualizarInternacion,
|
||||
agregarCama,
|
||||
eliminarCama,
|
||||
agregarEvolucion,
|
||||
eliminarEvolucion,
|
||||
agregarLaboratorio,
|
||||
eliminarLaboratorio,
|
||||
agregarAcidoBase,
|
||||
eliminarAcidoBase,
|
||||
agregarCultivo,
|
||||
actualizarCultivo,
|
||||
eliminarCultivo,
|
||||
agregarArea,
|
||||
actualizarArea,
|
||||
eliminarArea,
|
||||
getPacienteById,
|
||||
getCamaById,
|
||||
getInternacionById,
|
||||
getInternacionActivaByPaciente,
|
||||
getEvolucionesByInternacion,
|
||||
getLaboratoriosByPaciente,
|
||||
getAcidosBaseByPaciente,
|
||||
getCultivosByPaciente,
|
||||
actualizarEvolucion,
|
||||
actualizarLaboratorio,
|
||||
setCurrentInternacion,
|
||||
getEstadisticas,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user