Files
administracionHospital/src/hooks/useHospitalStore.ts
T
snlavaise 079e3bd82c Refactor: reorganizar arquitectura de permisos y corregir bug de camaS
- Mover funciones utilitarias y de permisos al principio del hook
- Corregir bug camaS -> camas en iniciarInternacion
- Limpiar funciones duplicadas
- Estructura de permisos clara según reglas:
  1. Cualquier usuario puede crear ingresos en cualquier cama
  2. Cualquier usuario puede ver todos los pacientes
  3. Solo usuarios del área pueden crear/modificar registros
  4. Solo usuarios del área pueden cambiar la cama
2026-04-24 01:07:02 -03:00

1005 lines
35 KiB
TypeScript

import { useState, useEffect, useCallback } from 'react';
import type {
Paciente,
Cama,
Area,
Internacion,
Evolucion,
Laboratorio,
AcidoBase,
Cultivo,
EstudioComplementario,
Interconsulta,
ATB,
Indicacion,
Vista,
Usuario,
RolUsuario
} 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[];
estudiosComplementarios: EstudioComplementario[];
interconsultas: Interconsulta[];
atb: ATB[];
indicaciones: Indicacion[];
movimientosIndicaciones: any[];
vistaActual: Vista;
currentInternacionId?: string | null;
usuarios: Usuario[];
currentUser: Usuario | null;
isAuthenticated: boolean;
}
const API_BASE = 'http://localhost:4001/api';
const defaultState = (): HospitalState => ({
pacientes: [],
areas: [],
internaciones: [],
evoluciones: [],
laboratorios: [],
acidosBase: [],
cultivos: [],
estudiosComplementarios: [],
interconsultas: [],
atb: [],
vistaActual: 'dashboard',
indicaciones: [],
movimientosIndicaciones: [],
camas: [],
usuarios: [],
currentUser: null,
isAuthenticated: false,
});
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) {
const defaults = defaultState();
const storedUser = sessionStorage.getItem('hospital_user');
const currentUser = storedUser ? JSON.parse(storedUser) : null;
const normalized = {
...defaults,
...body,
estudiosComplementarios: body.estudiosComplementarios || [],
interconsultas: body.interconsultas || [],
atb: body.atb || [],
movimientosIndicaciones: body.movimientosIndicaciones || [],
currentUser,
isAuthenticated: !!currentUser,
};
setState(normalized);
}
} 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)
const [initialLoadComplete, setInitialLoadComplete] = useState(false);
useEffect(() => {
if (isLoaded && !initialLoadComplete) {
setInitialLoadComplete(true);
}
}, [isLoaded, initialLoadComplete]);
useEffect(() => {
if (!initialLoadComplete) return;
const t = setTimeout(() => {
(async () => {
try {
const { currentUser, isAuthenticated, ...stateToSave } = state;
await fetch(`${API_BASE}/state`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(stateToSave),
});
} catch (err) {
// ignore save errors for now
}
})();
}, 300);
return () => clearTimeout(t);
}, [state, initialLoadComplete]);
// Utility functions
const getCamaAreaId = useCallback((camaId: string): string | null => {
const cama = state.camas.find(c => c.id === camaId);
return cama?.areaId || null;
}, [state.camas]);
const getInternacionAreaId = useCallback((internacionId: string): string | null => {
const internacion = state.internaciones.find(i => i.id === internacionId);
return internacion?.areaId || null;
}, [state.internaciones]);
const getPacienteAreaId = useCallback((pacienteId: string): string | null => {
const internacion = state.internaciones.find(i => i.pacienteId === pacienteId && i.activa);
if (!internacion) {
const anyInternacion = state.internaciones.find(i => i.pacienteId === pacienteId);
if (anyInternacion) return getCamaAreaId(anyInternacion.camaId);
return null;
}
return getCamaAreaId(internacion.camaId);
}, [state.internaciones, getCamaAreaId]);
// Permission functions
const canAccessArea = useCallback((areaId: string | null | undefined): boolean => {
const user = state.currentUser;
if (!user) return false;
if (user.rol === 'admin') return true;
if (!areaId) return false;
return user.areaId === areaId;
}, [state.currentUser]);
const canChangeBed = useCallback((internacionId: string): boolean => {
const internacion = state.internaciones.find(i => i.id === internacionId);
if (!internacion) return false;
return canAccessArea(internacion.areaId);
}, [state.internaciones, canAccessArea]);
const canEditInternacion = useCallback((internacionId: string): boolean => {
const areaId = getInternacionAreaId(internacionId);
return canAccessArea(areaId);
}, [getInternacionAreaId, canAccessArea]);
const canEditCama = useCallback((camaId: string): boolean => {
const areaId = getCamaAreaId(camaId);
return canAccessArea(areaId);
}, [getCamaAreaId, canAccessArea]);
const canEditPaciente = useCallback((pacienteId: string): boolean => {
const areaId = getPacienteAreaId(pacienteId);
return canAccessArea(areaId);
}, [getPacienteAreaId, canAccessArea]);
const hasPermission = useCallback((permission: 'read' | 'write', section: string): boolean => {
const user = state.currentUser;
if (!user) return false;
if (user.rol === 'admin') return true;
if (user.rol === 'enfermero') {
if (section === 'glucemias' || section === 'signosvitales') return permission === 'read' || permission === 'write';
return permission === 'read';
}
if (user.rol === 'medico') {
if (section === 'miArea') return permission === 'read' || permission === 'write';
return permission === 'read';
}
return false;
}, [state.currentUser]);
// 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>) => {
const cama = state.camas.find(c => c.id === id);
if (!cama) return;
const user = state.currentUser;
const areaId = cama.areaId || null;
if (user && user.rol !== 'admin' && areaId && user.areaId !== areaId) {
console.warn('No tiene permisos para actualizar cama en esta área');
return;
}
setState(prev => ({
...prev,
camas: prev.camas.map(c => c.id === id ? { ...c, ...datos } : c),
}));
}, [state.camas, state.currentUser]);
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' | 'activa'>) => {
const nuevaInternacion: Internacion = {
...internacion,
id: generateUUID(),
activa: true,
};
setState(prev => ({
...prev,
internaciones: [...prev.internaciones, nuevaInternacion],
camas: prev.camas.map(c =>
c.id === internacion.camaId
? { ...c, estado: 'Ocupada' as const, pacienteId: internacion.pacienteId, internacionId: nuevaInternacion.id }
: c
),
}));
return nuevaInternacion.id;
}, []);
const finalizarInternacion = useCallback((internacionId: string, datos: {
fechaEgreso: string;
diagnosticoEgreso: string;
motivoEgreso: Internacion['motivoEgreso']
}) => {
const internacion = state.internaciones.find(i => i.id === internacionId);
if (!internacion) return;
const areaFueraDeArea = state.areas.find(a => (a.nombre || '').trim().toLowerCase() === 'fuera de area');
const areaFueraDeAreaId = areaFueraDeArea?.id;
const esFueraDeArea = internacion.areaId === areaFueraDeAreaId;
setState(prev => {
return {
...prev,
internaciones: prev.internaciones.map(i =>
i.id === internacionId
? { ...i, ...datos, activa: false }
: i
),
camas: esFueraDeArea
? prev.camas.filter(c => c.id !== internacion.camaId)
: prev.camas.map(c =>
c.id === internacion.camaId
? { ...c, estado: 'Disponible', pacienteId: undefined, internacionId: undefined }
: c
),
};
});
}, [state.areas]);
const actualizarInternacion = useCallback((internacionId: string, datos: Partial<Internacion>) => {
const internacion = state.internaciones.find(i => i.id === internacionId);
if (!internacion) return;
const oldCamaId = internacion.camaId;
const newCamaId = datos.camaId;
const cambioDeCama = newCamaId && newCamaId !== oldCamaId;
if (cambioDeCama && !canChangeBed(internacionId)) {
console.warn('No tiene permisos para cambiar la cama de esta internación');
return;
}
if (!cambioDeCama && !canAccessArea(internacion.areaId)) {
console.warn('No tiene permisos para actualizar esta internación');
return;
}
const pacienteId = datos.pacienteId || internacion.pacienteId;
setState(prev => {
const result = {
...prev,
internaciones: prev.internaciones.map(i =>
i.id === internacionId
? { ...i, ...datos }
: i
),
camas: prev.camas.map(c => {
if (c.id === newCamaId && newCamaId !== oldCamaId) {
return { ...c, estado: 'Ocupada' as const, pacienteId, internacionId };
}
if (c.id === oldCamaId && oldCamaId !== newCamaId) {
return { ...c, estado: 'Disponible' as const, pacienteId: undefined, internacionId: undefined };
}
return c;
}),
};
return result;
});
if (oldCamaId !== newCamaId && newCamaId && oldCamaId) {
setTimeout(() => {
fetch(`${API_BASE}/camas/${newCamaId}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ estado: 'Ocupada' }),
}).catch(() => {});
}, 500);
setTimeout(() => {
fetch(`${API_BASE}/camas/${oldCamaId}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ estado: 'Disponible' }),
}).catch(() => {});
}, 1000);
}
}, [state.internaciones, state.currentUser]);
// Acciones de evoluciones
const agregarEvolucion = useCallback((evolucion: Omit<Evolucion, 'id'>) => {
const internacion = state.internaciones.find(i => i.id === evolucion.internacionId);
const areaId = internacion?.areaId || null;
if (!canAccessArea(areaId)) {
console.warn('No tiene permisos para agregar evolución en esta área');
return null;
}
const nuevaEvolucion: Evolucion = {
...evolucion,
id: generateUUID(),
};
setState(prev => ({
...prev,
evoluciones: [...prev.evoluciones, nuevaEvolucion],
}));
return nuevaEvolucion.id;
}, [state.internaciones, state.currentUser]);
const eliminarEvolucion = useCallback((id: string) => {
const evolucion = state.evoluciones.find(e => e.id === id);
if (!evolucion) return;
const internacion = state.internaciones.find(i => i.id === evolucion.internacionId);
const areaId = internacion?.areaId || null;
if (!canAccessArea(areaId)) {
console.warn('No tiene permisos para eliminar evolución en esta área');
return;
}
setState(prev => ({
...prev,
evoluciones: prev.evoluciones.filter(e => e.id !== id),
}));
}, []);
const actualizarEvolucion = useCallback((id: string, datos: Partial<Evolucion>) => {
const evolucion = state.evoluciones.find(e => e.id === id);
if (!evolucion) return;
const internacion = state.internaciones.find(i => i.id === evolucion.internacionId);
const areaId = internacion?.areaId || null;
if (!canAccessArea(areaId)) {
console.warn('No tiene permisos para actualizar evolución en esta área');
return;
}
setState(prev => ({
...prev,
evoluciones: prev.evoluciones.map(e => e.id === id ? { ...e, ...datos } : e),
}));
}, [state.internaciones, state.currentUser]);
// Acciones de laboratorios
const agregarLaboratorio = useCallback((laboratorio: Omit<Laboratorio, 'id'>) => {
const internacion = state.internaciones.find(i => i.id === laboratorio.internacionId);
const areaId = internacion?.areaId || null;
if (!canAccessArea(areaId)) {
console.warn('No tiene permisos para agregar laboratorio en esta área');
return null;
}
const nuevoLaboratorio: Laboratorio = {
...laboratorio,
id: generateUUID(),
};
setState(prev => ({
...prev,
laboratorios: [...prev.laboratorios, nuevoLaboratorio],
}));
return nuevoLaboratorio.id;
}, [state.internaciones, state.currentUser]);
const eliminarLaboratorio = useCallback((id: string) => {
const laboratorio = state.laboratorios.find(l => l.id === id);
if (!laboratorio) return;
const internacion = state.internaciones.find(i => i.id === laboratorio.internacionId);
const areaId = internacion?.areaId || null;
if (!canAccessArea(areaId)) {
console.warn('No tiene permisos para eliminar laboratorio en esta área');
return;
}
setState(prev => ({
...prev,
laboratorios: prev.laboratorios.filter(l => l.id !== id),
}));
}, [state.internaciones, state.currentUser]);
const actualizarLaboratorio = useCallback((id: string, datos: Partial<Laboratorio>) => {
const laboratorio = state.laboratorios.find(l => l.id === id);
if (!laboratorio) return;
const internacion = state.internaciones.find(i => i.id === laboratorio.internacionId);
const areaId = internacion?.areaId || null;
if (!canAccessArea(areaId)) {
console.warn('No tiene permisos para actualizar laboratorio en esta área');
return;
}
setState(prev => ({
...prev,
laboratorios: prev.laboratorios.map(l => l.id === id ? { ...l, ...datos } : l),
}));
}, [state.internaciones, state.currentUser]);
// Acciones de ácido-base
const agregarAcidoBase = useCallback((acidoBase: Omit<AcidoBase, 'id'>) => {
const internacion = state.internaciones.find(i => i.id === acidoBase.internacionId);
const areaId = internacion?.areaId || null;
if (!canAccessArea(areaId)) {
console.warn('No tiene permisos para agregar ácido-base en esta área');
return null;
}
const nuevoAcidoBase: AcidoBase = {
...acidoBase,
id: generateUUID(),
};
setState(prev => ({
...prev,
acidosBase: [...prev.acidosBase, nuevoAcidoBase],
}));
return nuevoAcidoBase.id;
}, [state.internaciones, state.currentUser]);
const actualizarAcidoBase = useCallback((id: string, datos: Partial<AcidoBase>) => {
const acidoBase = state.acidosBase.find(a => a.id === id);
if (!acidoBase) return;
const internacion = state.internaciones.find(i => i.id === acidoBase.internacionId);
const areaId = internacion?.areaId || null;
if (!canAccessArea(areaId)) {
console.warn('No tiene permisos para actualizar ácido-base en esta área');
return;
}
setState(prev => ({
...prev,
acidosBase: prev.acidosBase.map(a => a.id === id ? { ...a, ...datos } : a),
}));
}, [state.internaciones, state.currentUser]);
const eliminarAcidoBase = useCallback((id: string) => {
const acidoBase = state.acidosBase.find(a => a.id === id);
if (!acidoBase) return;
const internacion = state.internaciones.find(i => i.id === acidoBase.internacionId);
const areaId = internacion?.areaId || null;
if (!canAccessArea(areaId)) {
console.warn('No tiene permisos para eliminar ácido-base en esta área');
return;
}
setState(prev => ({
...prev,
acidosBase: prev.acidosBase.filter(a => a.id !== id),
}));
}, [state.internaciones, state.currentUser]);
// Acciones de cultivos
const agregarCultivo = useCallback((cultivo: Omit<Cultivo, 'id'>) => {
const internacion = state.internaciones.find(i => i.id === cultivo.internacionId);
const areaId = internacion?.areaId || null;
if (!canAccessArea(areaId)) {
console.warn('No tiene permisos para agregar cultivo en esta área');
return null;
}
const nuevoCultivo: Cultivo = {
...cultivo,
id: generateUUID(),
};
setState(prev => ({
...prev,
cultivos: [...prev.cultivos, nuevoCultivo],
}));
return nuevoCultivo.id;
}, [state.internaciones, state.currentUser]);
const actualizarCultivo = useCallback((id: string, datos: Partial<Cultivo>) => {
const cultivo = state.cultivos.find(c => c.id === id);
if (!cultivo) return;
const internacion = state.internaciones.find(i => i.id === cultivo.internacionId);
const areaId = internacion?.areaId || null;
if (!canAccessArea(areaId)) {
console.warn('No tiene permisos para actualizar cultivo en esta área');
return;
}
setState(prev => ({
...prev,
cultivos: prev.cultivos.map(c => c.id === id ? { ...c, ...datos } : c),
}));
}, [state.internaciones, state.currentUser]);
const eliminarCultivo = useCallback((id: string) => {
const cultivo = state.cultivos.find(c => c.id === id);
if (!cultivo) return;
const internacion = state.internaciones.find(i => i.id === cultivo.internacionId);
const areaId = internacion?.areaId || null;
if (!canAccessArea(areaId)) {
console.warn('No tiene permisos para eliminar cultivo en esta área');
return;
}
setState(prev => ({
...prev,
cultivos: prev.cultivos.filter(c => c.id !== id),
}));
}, [state.internaciones, state.currentUser]);
// Acciones de estudios complementarios
const agregarEstudioComplementario = useCallback((estudio: Omit<EstudioComplementario, 'id'>) => {
const internacion = state.internaciones.find(i => i.id === estudio.internacionId);
const areaId = internacion?.areaId || null;
if (!canAccessArea(areaId)) {
console.warn('No tiene permisos para agregar estudio complementario en esta área');
return null;
}
const nuevoEstudio: EstudioComplementario = {
...estudio,
id: generateUUID(),
};
setState(prev => ({
...prev,
estudiosComplementarios: [...prev.estudiosComplementarios, nuevoEstudio],
}));
return nuevoEstudio.id;
}, [state.internaciones, state.currentUser]);
const actualizarEstudioComplementario = useCallback((id: string, datos: Partial<EstudioComplementario>) => {
const estudio = state.estudiosComplementarios.find(e => e.id === id);
if (!estudio) return;
const internacion = state.internaciones.find(i => i.id === estudio.internacionId);
const areaId = internacion?.areaId || null;
if (!canAccessArea(areaId)) {
console.warn('No tiene permisos para actualizar estudio complementario en esta área');
return;
}
setState(prev => ({
...prev,
estudiosComplementarios: prev.estudiosComplementarios.map(e => e.id === id ? { ...e, ...datos } : e),
}));
}, [state.internaciones, state.currentUser]);
const eliminarEstudioComplementario = useCallback((id: string) => {
const estudio = state.estudiosComplementarios.find(e => e.id === id);
if (!estudio) return;
const internacion = state.internaciones.find(i => i.id === estudio.internacionId);
const areaId = internacion?.areaId || null;
if (!canAccessArea(areaId)) {
console.warn('No tiene permisos para eliminar estudio complementario en esta área');
return;
}
setState(prev => ({
...prev,
estudiosComplementarios: prev.estudiosComplementarios.filter(e => e.id !== id),
}));
}, [state.internaciones, state.currentUser]);
// Acciones de interconsultas
const agregarInterconsulta = useCallback((interconsulta: Omit<Interconsulta, 'id'>) => {
const internacion = state.internaciones.find(i => i.id === interconsulta.internacionId);
const areaId = internacion?.areaId || null;
if (!canAccessArea(areaId)) {
console.warn('No tiene permisos para agregar interconsulta en esta área');
return null;
}
const nuevaInterconsulta: Interconsulta = { ...interconsulta, id: generateUUID() };
setState(prev => ({
...prev,
interconsultas: [...(prev.interconsultas || []), nuevaInterconsulta],
}));
return nuevaInterconsulta.id;
}, [state.internaciones, state.currentUser]);
const actualizarInterconsulta = useCallback((id: string, datos: Partial<Interconsulta>) => {
const interconsulta = (state.interconsultas || []).find(ic => ic.id === id);
if (!interconsulta) return;
const internacion = state.internaciones.find(i => i.id === interconsulta.internacionId);
const areaId = internacion?.areaId || null;
if (!canAccessArea(areaId)) {
console.warn('No tiene permisos para actualizar interconsulta en esta área');
return;
}
setState(prev => ({
...prev,
interconsultas: (prev.interconsultas || []).map(ic => ic.id === id ? { ...ic, ...datos } : ic),
}));
}, [state.internaciones, state.currentUser]);
const eliminarInterconsulta = useCallback((id: string) => {
const interconsulta = (state.interconsultas || []).find(ic => ic.id === id);
if (!interconsulta) return;
const internacion = state.internaciones.find(i => i.id === interconsulta.internacionId);
const areaId = internacion?.areaId || null;
if (!canAccessArea(areaId)) {
console.warn('No tiene permisos para eliminar interconsulta en esta área');
return;
}
setState(prev => ({
...prev,
interconsultas: (prev.interconsultas || []).filter(ic => ic.id !== id),
}));
}, [state.internaciones, state.currentUser]);
const agregarATB = useCallback((atb: Omit<ATB, 'id'>) => {
const internacion = state.internaciones.find(i => i.id === atb.internacionId);
const areaId = internacion?.areaId || null;
if (!canAccessArea(areaId)) {
console.warn('No tiene permisos para agregar ATB en esta área');
return null;
}
const nuevoATB: ATB = { ...atb, id: generateUUID() };
setState(prev => ({
...prev,
atb: [...prev.atb, nuevoATB],
}));
return nuevoATB.id;
}, [state.internaciones, state.currentUser]);
const actualizarATB = useCallback((id: string, datos: Partial<ATB>) => {
const atb = state.atb.find(a => a.id === id);
if (!atb) return;
const internacion = state.internaciones.find(i => i.id === atb.internacionId);
const areaId = internacion?.areaId || null;
if (!canAccessArea(areaId)) {
console.warn('No tiene permisos para actualizar ATB en esta área');
return;
}
setState(prev => ({
...prev,
atb: prev.atb.map(a => a.id === id ? { ...a, ...datos } : a),
}));
}, [state.internaciones, state.currentUser]);
const eliminarATB = useCallback((id: string) => {
const atb = state.atb.find(a => a.id === id);
if (!atb) return;
const internacion = state.internaciones.find(i => i.id === atb.internacionId);
const areaId = internacion?.areaId || null;
if (!canAccessArea(areaId)) {
console.warn('No tiene permisos para eliminar ATB en esta área');
return;
}
setState(prev => ({
...prev,
atb: prev.atb.filter(a => a.id !== id),
}));
}, [state.internaciones, state.currentUser]);
const agregarIndicacion = useCallback((indicacion: Omit<Indicacion, 'id'>) => {
const internacion = state.internaciones.find(i => i.id === indicacion.internacionId);
const areaId = internacion?.areaId || null;
if (!canAccessArea(areaId)) {
console.warn('No tiene permisos para agregar indicación en esta área');
return null;
}
const nuevaIndicacion: Indicacion = { ...indicacion, id: generateUUID() };
setState(prev => ({
...prev,
indicaciones: [...prev.indicaciones, nuevaIndicacion],
}));
return nuevaIndicacion.id;
}, [state.internaciones, state.currentUser]);
const actualizarIndicacion = useCallback((id: string, datos: Partial<Indicacion>) => {
const indicacion = state.indicaciones.find(i => i.id === id);
if (!indicacion) return;
const internacion = state.internaciones.find(i => i.id === indicacion.internacionId);
const areaId = internacion?.areaId || null;
if (!canAccessArea(areaId)) {
console.warn('No tiene permisos para actualizar indicación en esta área');
return;
}
setState(prev => ({
...prev,
indicaciones: prev.indicaciones.map(i => i.id === id ? { ...i, ...datos } : i),
}));
}, [state.internaciones, state.currentUser]);
const eliminarIndicacion = useCallback((id: string) => {
const indicacion = state.indicaciones.find(i => i.id === id);
if (!indicacion) return;
const internacion = state.internaciones.find(i => i.id === indicacion.internacionId);
const areaId = internacion?.areaId || null;
if (!canAccessArea(areaId)) {
console.warn('No tiene permisos para eliminar indicación en esta área');
return;
}
setState(prev => ({
...prev,
indicaciones: prev.indicaciones.filter(i => i.id !== id),
}));
}, [state.internaciones, state.currentUser]);
const agregarMovimientoIndicacion = useCallback((movimiento: any) => {
const internacion = state.internaciones.find(i => i.id === movimiento.internacionId);
const areaId = internacion?.areaId || null;
if (!canAccessArea(areaId)) {
console.warn('No tiene permisos para agregar movimiento de indicación en esta área');
return;
}
setState(prev => ({
...prev,
movimientosIndicaciones: [...(prev.movimientosIndicaciones || []), { ...movimiento, id: generateUUID() }],
}));
}, [state.internaciones, state.currentUser]);
const getMovimientosByInternacion = useCallback((internacionId: string) => {
return (state.movimientosIndicaciones || [])
.filter((m: any) => m.internacionId === internacionId)
.sort((a: any, b: any) => new Date(b.fecha).getTime() - new Date(a.fecha).getTime());
}, [state.movimientosIndicaciones]);
// 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 para consultas
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 }));
}, []);
// Auth functions
const login = useCallback(async (dni: string, password: string) => {
try {
const res = await fetch(`${API_BASE}/auth/login`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ dni, password }),
});
if (!res.ok) {
const err = await res.json();
throw new Error(err.error || 'Error en autenticación');
}
const user = await res.json();
sessionStorage.setItem('hospital_user', JSON.stringify(user));
setState(prev => ({ ...prev, currentUser: user, isAuthenticated: true }));
return user;
} catch (err) {
throw err;
}
}, []);
const logout = useCallback(() => {
sessionStorage.removeItem('hospital_user');
setState(prev => {
const newState = { ...prev, currentUser: null, isAuthenticated: false };
return newState;
});
}, []);
const changePassword = useCallback(async (dni: string, oldPassword: string, newPassword: string) => {
const res = await fetch(`${API_BASE}/auth/change-password`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ dni, oldPassword, newPassword }),
});
if (!res.ok) {
const err = await res.json();
throw new Error(err.error || 'Error al cambiar contraseña');
}
return true;
}, []);
const updateEmail = useCallback(async (dni: string, newEmail: string) => {
const res = await fetch(`${API_BASE}/auth/update-email`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ dni, newEmail }),
});
if (!res.ok) {
const err = await res.json();
throw new Error(err.error || 'Error al actualizar email');
}
return true;
}, []);
return {
...state,
isLoaded,
setVista,
agregarPaciente,
actualizarPaciente,
eliminarPaciente,
actualizarCama,
iniciarInternacion,
finalizarInternacion,
actualizarInternacion,
agregarCama,
eliminarCama,
agregarEvolucion,
actualizarEvolucion,
eliminarEvolucion,
agregarLaboratorio,
actualizarLaboratorio,
eliminarLaboratorio,
agregarAcidoBase,
actualizarAcidoBase,
eliminarAcidoBase,
agregarCultivo,
actualizarCultivo,
eliminarCultivo,
agregarEstudioComplementario,
actualizarEstudioComplementario,
eliminarEstudioComplementario,
agregarInterconsulta,
actualizarInterconsulta,
eliminarInterconsulta,
agregarATB,
actualizarATB,
eliminarATB,
agregarIndicacion,
actualizarIndicacion,
eliminarIndicacion,
agregarMovimientoIndicacion,
getMovimientosByInternacion,
agregarArea,
actualizarArea,
eliminarArea,
getPacienteById,
getCamaById,
getInternacionById,
getInternacionActivaByPaciente,
getEvolucionesByInternacion,
getLaboratoriosByPaciente,
getAcidosBaseByPaciente,
getCultivosByPaciente,
setCurrentInternacion,
getEstadisticas,
login,
logout,
changePassword,
updateEmail,
hasPermission,
canAccessArea,
canChangeBed,
canEditInternacion,
canEditCama,
canEditPaciente,
getCamaAreaId,
getInternacionAreaId,
getPacienteAreaId,
};
}