const { useState, useEffect, useCallback, useRef } = React; const SESSION_DURATION_MS = 4 * 60 * 60 * 1000; // 4 horas en milisegundos // Función auxiliar global para verificar si un perfil tiene acceso administrativo o de supervisión window.checkHasAdminAccess = function(prof) { if (!prof) return false; const r = (prof.role || prof.role_name || '').toLowerCase().trim(); const acc = (prof.accessLevel || prof.access_level || '').toLowerCase().trim(); return ( r === 'admin' || r === 'propietario' || r === 'dueño' || acc === 'full' || acc === 'readonly' || acc === 'read_only' || Boolean(window.SHIFT_CONFIG?.isMaster) ); }; window.App = function() { const [profile, setProfile] = useState(null); const [view, setView] = useState('login'); const [loading, setLoading] = useState(true); const [globalUsers, setGlobalUsers] = useState([]); const [logs, setLogs] = useState([]); const [roles, setRoles] = useState([]); const [reminders, setReminders] = useState([]); const [vacations, setVacations] = useState([]); const [settings, setSettings] = useState({ vacationRate: 2.5, exceptionReasons: [], logoUrl: '', lunchLimitMinutes: 60 }); const [actions, setActions] = useState([]); const [absences, setAbsences] = useState([]); const [masterClients, setMasterClients] = useState([]); const [activeClientId, setActiveClientId] = useState( window.SHIFT_CONFIG && window.SHIFT_CONFIG.isMaster ? 0 : (window.SHIFT_CONFIG ? window.SHIFT_CONFIG.clientId : 1) ); const activeClientRef = useRef(activeClientId); activeClientRef.current = activeClientId; const profileRef = useRef(profile); profileRef.current = profile; useEffect(() => { window.currentActiveClientId = activeClientId; }, [activeClientId]); // 1. CARGA Y VALIDACIÓN DE SESIÓN (REDIRECCIÓN CORRECTA PARA SUPERVISORES Y DOCTORES) useEffect(() => { const savedSession = localStorage.getItem('shift_active_session'); if (savedSession) { try { const parsed = JSON.parse(savedSession); const now = Date.now(); if (parsed && parsed.profile && parsed.expiresAt && parsed.expiresAt > now) { setProfile(parsed.profile); const canAccessAdmin = window.checkHasAdminAccess(parsed.profile); setView(canAccessAdmin ? 'admin' : 'dashboard'); parsed.expiresAt = now + SESSION_DURATION_MS; localStorage.setItem('shift_active_session', JSON.stringify(parsed)); } else { localStorage.removeItem('shift_active_session'); setProfile(null); setView('login'); } } catch (e) { localStorage.removeItem('shift_active_session'); setProfile(null); setView('login'); } } else { setView('login'); } }, []); // 2. DETECTOR DE ACTIVIDAD useEffect(() => { if (!profile) return; const updateActivityTime = () => { const raw = localStorage.getItem('shift_active_session'); if (raw) { try { const parsed = JSON.parse(raw); parsed.expiresAt = Date.now() + SESSION_DURATION_MS; localStorage.setItem('shift_active_session', JSON.stringify(parsed)); } catch (e) {} } }; const checkInactivity = setInterval(() => { const raw = localStorage.getItem('shift_active_session'); if (raw) { try { const parsed = JSON.parse(raw); if (parsed.expiresAt && Date.now() >= parsed.expiresAt) { handleLogout(); alert("Tu sesión ha expirado por inactividad (4 horas). Por favor inicia sesión nuevamente."); } } catch (e) {} } }, 60000); window.addEventListener('mousemove', updateActivityTime); window.addEventListener('keydown', updateActivityTime); window.addEventListener('click', updateActivityTime); window.addEventListener('scroll', updateActivityTime); window.addEventListener('touchstart', updateActivityTime); return () => { clearInterval(checkInactivity); window.removeEventListener('mousemove', updateActivityTime); window.removeEventListener('keydown', updateActivityTime); window.removeEventListener('click', updateActivityTime); window.removeEventListener('scroll', updateActivityTime); window.removeEventListener('touchstart', updateActivityTime); }; }, [profile]); const refreshData = useCallback(async (forceClientId = null) => { const currentTargetId = forceClientId !== null ? Number(forceClientId) : Number(activeClientRef.current); if (window.SHIFT_CONFIG && window.SHIFT_CONFIG.isMaster) { const resMaster = await window.apiCall('get_master_clients', {}, 0); if (resMaster && resMaster.success) { setMasterClients(resMaster.clients || []); } } if (window.SHIFT_CONFIG && window.SHIFT_CONFIG.isMaster && currentTargetId === 0) { setGlobalUsers([]); setLogs([]); setRoles([]); setAbsences([]); setVacations([]); setLoading(false); return; } const res = await window.apiCall('get_initial_data', {}, currentTargetId); if (res && res.success && res.data) { const mappedUsers = (res.data.users || []).map(u => ({ ...u, role: (u.role_name || u.role || 'empleado').toLowerCase().trim() })); setGlobalUsers(mappedUsers); setLogs(res.data.logs || []); setRoles(res.data.roles || []); setAbsences(res.data.absences || []); setActions(res.data.actions && res.data.actions.length > 0 ? res.data.actions : [ { id: 'almuerzo', label: 'Almuerzo' }, { id: 'break', label: 'Break / Merienda' }, { id: 'visita_in', label: 'Visita Cliente' }, { id: 'reunion', label: 'Reunión' }, { id: 'capacitacion', label: 'Capacitación' }, { id: 'gestiones', label: 'Gestiones de Campo' } ]); setVacations(res.data.vacations || []); setSettings(res.data.settings || { vacationRate: 2.5, exceptionReasons: [], lunchLimitMinutes: 60 }); setReminders(res.data.reminders || []); const currentProf = profileRef.current; if (currentProf && currentProf.id) { const updated = mappedUsers.find(u => Number(u.id) === Number(currentProf.id)); if (updated) { if (updated.status === 'baja') { localStorage.removeItem('shift_active_session'); setProfile(null); setView('login'); } else { // Sincronizar el nivel de acceso del rol actualizado const userRoleObj = (res.data.roles || []).find(r => (r.name || '').toLowerCase() === (updated.role || '').toLowerCase()); const freshAccessLevel = userRoleObj ? (userRoleObj.accessLevel || userRoleObj.access_level) : (updated.accessLevel || currentProf.accessLevel || 'app_only'); if (JSON.stringify(updated.scheduleDays) !== JSON.stringify(currentProf.scheduleDays) || updated.role !== currentProf.role || freshAccessLevel !== currentProf.accessLevel) { const newProf = { ...currentProf, ...updated, accessLevel: freshAccessLevel }; setProfile(newProf); try { const activeSession = localStorage.getItem('shift_active_session'); if (activeSession) { const parsedSess = JSON.parse(activeSession); parsedSess.profile = newProf; parsedSess.expiresAt = Date.now() + SESSION_DURATION_MS; localStorage.setItem('shift_active_session', JSON.stringify(parsedSess)); } } catch(e) {} } } } } } setLoading(false); }, []); const changeActivePortal = (newId) => { const idNum = Number(newId); window.currentActiveClientId = idNum; activeClientRef.current = idNum; setActiveClientId(idNum); refreshData(idNum); }; const handleLoginSuccess = (newProfile) => { if (!newProfile) return; const sessionPayload = { profile: newProfile, expiresAt: Date.now() + SESSION_DURATION_MS }; localStorage.setItem('shift_active_session', JSON.stringify(sessionPayload)); setProfile(newProfile); // Evaluar acceso de administrador o supervisor const canAccessAdmin = window.checkHasAdminAccess(newProfile); setView(canAccessAdmin ? 'admin' : 'dashboard'); }; const handleLogout = () => { localStorage.removeItem('shift_active_session'); setProfile(null); setView('login'); }; useEffect(() => { refreshData(activeClientId); }, [activeClientId, refreshData]); useEffect(() => { const interval = setInterval(() => { refreshData(); }, 10000); return () => clearInterval(interval); }, [refreshData]); if (loading) return (
Cargando Entorno Shift...
); const hasAdminAccess = window.checkHasAdminAccess(profile); return (
{view === 'login' && ( 0} allUsers={globalUsers} /> )} {view === 'dashboard' && profile && ( Number(l.userId) === Number(profile.id))} reminders={reminders} vacations={vacations} settings={settings} actions={actions} onLogout={handleLogout} setView={setView} allRoles={roles} hasAdminAccess={hasAdminAccess} /> )} {view === 'admin' && hasAdminAccess && ( )}
); }; const rootElement = document.getElementById('root'); if (rootElement) { if (!window._shiftRoot) window._appRoot = ReactDOM.createRoot(rootElement); window._appRoot.render(); }