feat: implementar permisos por área de trabajo

- Agregar funciones de verificación de área en useHospitalStore
- canEditInArea, canEditCama, canEditInternacion, canEditPaciente
- Leer datos de tablas individuales en lugar de tabla kv
- Usar sessionStorage para persistir sesión de usuario
- Agregar prop canEdit a componentes de secciones en HC
- Ocultar botones de edición para usuarios sin permisos en el área
- MapaCamas: ocultar botones de edición para camas fuera del área
This commit is contained in:
2026-04-23 02:18:42 -03:00
parent 76e01bfd79
commit e6e1582b25
8 changed files with 411 additions and 118 deletions
+69 -8
View File
@@ -88,6 +88,8 @@ export function useHospitalStore() {
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,
@@ -95,6 +97,8 @@ export function useHospitalStore() {
interconsultas: body.interconsultas || [],
atb: body.atb || [],
movimientosIndicaciones: body.movimientosIndicaciones || [],
currentUser,
isAuthenticated: !!currentUser,
};
setState(normalized);
}
@@ -108,15 +112,24 @@ export function useHospitalStore() {
}, []);
// Persist state to backend when it changes (debounced)
const [initialLoadComplete, setInitialLoadComplete] = useState(false);
useEffect(() => {
if (!isLoaded) return;
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(state),
body: JSON.stringify(stateToSave),
});
} catch (err) {
// ignore save errors for now
@@ -124,7 +137,7 @@ export function useHospitalStore() {
})();
}, 300);
return () => clearTimeout(t);
}, [state, isLoaded]);
}, [state, initialLoadComplete]);
// Acciones de navegación
const setVista = useCallback((vista: Vista) => {
@@ -570,6 +583,7 @@ const getEstadisticas = useCallback(() => {
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) {
@@ -578,13 +592,9 @@ const getEstadisticas = useCallback(() => {
}, []);
const logout = useCallback(() => {
sessionStorage.removeItem('hospital_user');
setState(prev => {
const newState = { ...prev, currentUser: null, isAuthenticated: false };
fetch(`${API_BASE}/state`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ ...newState }),
}).catch(() => {});
return newState;
});
}, []);
@@ -644,6 +654,50 @@ const getEstadisticas = useCallback(() => {
return rol === 'admin';
}, [state.currentUser, state.internaciones, state.camas]);
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);
if (!internacion) return null;
return getCamaAreaId(internacion.camaId);
}, [state.internaciones, getCamaAreaId]);
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]);
const canEditInArea = 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 canEditCama = useCallback((camaId: string): boolean => {
const areaId = getCamaAreaId(camaId);
return canEditInArea(areaId);
}, [getCamaAreaId, canEditInArea]);
const canEditInternacion = useCallback((internacionId: string): boolean => {
const areaId = getInternacionAreaId(internacionId);
return canEditInArea(areaId);
}, [getInternacionAreaId, canEditInArea]);
const canEditPaciente = useCallback((pacienteId: string): boolean => {
const areaId = getPacienteAreaId(pacienteId);
return canEditInArea(areaId);
}, [getPacienteAreaId, canEditInArea]);
return {
...state,
isLoaded,
@@ -702,5 +756,12 @@ const getEstadisticas = useCallback(() => {
updateEmail,
hasPermission,
canAccessInternacion,
canEditInArea,
canEditCama,
canEditInternacion,
canEditPaciente,
getCamaAreaId,
getInternacionAreaId,
getPacienteAreaId,
};
}