const { useState, useEffect, useRef, useMemo } = React; window.UserDashboard = function({ profile, logs, reminders = [], onLogout, setView, allRoles }) { const [status, setStatus] = useState('fuera'); const [showCam, setShowCam] = useState(false); const [purpose, setPurpose] = useState(''); const [clientName, setClientName] = useState(''); const [loadingMark, setLoadingMark] = useState(false); const videoRef = useRef(null); const canvasRef = useRef(null); const todayStr = window.getNICDate(); const todayLogs = useMemo(() => logs.filter(l => l.date === todayStr).sort((a,b) => a.timestamp - b.timestamp), [logs, todayStr]); useEffect(() => { if (todayLogs.length > 0) setStatus(todayLogs[todayLogs.length - 1].type); else setStatus('fuera'); }, [todayLogs]); // --- NUEVO: Filtrar notificaciones activas para este usuario --- const activeReminders = useMemo(() => { const now = Date.now(); return reminders.filter(r => r.targetUsers?.includes(profile.id) && !(r.completedBy && r.completedBy[profile.id]) && (!r.expiresAt || r.expiresAt > now) ); }, [reminders, profile.id]); // --- NUEVO: Marcar notificación como leída --- const markReminderCompleted = async (rId) => { try { const rRef = db.collection('artifacts').doc(appId).collection('public').doc('data').collection('reminders').doc(rId); const rDoc = await rRef.get(); const completedBy = rDoc.data().completedBy || {}; completedBy[profile.id] = Date.now(); await rRef.update({ completedBy }); } catch (e) { alert("Error al confirmar lectura."); } }; // Obtener los permisos del rol actual (Configurados por el administrador) const userRoleConfig = useMemo(() => allRoles.find(r => (r.name || r) === profile.role), [allRoles, profile.role]); const permissions = userRoleConfig && userRoleConfig.permissions ? userRoleConfig.permissions : []; // Bloqueo de jornada si ya marcó salida (con bypass administrativo) const isShiftCompleted = useMemo(() => { const hasSalida = todayLogs.some(l => l.type === 'salida'); return hasSalida && !profile.overrideCheckOut; }, [todayLogs, profile.overrideCheckOut]); const startAction = async (p) => { if (isShiftCompleted) return; // Acciones sin cámara (ahora incluye las nuevas opciones configurables) if (['almuerzo', 'vuelta_almuerzo', 'break', 'capacitacion', 'reunion', 'gestiones'].includes(p)) { return saveMark(p, null, null); } if (p === 'visita_in') { const name = prompt("Escriba el nombre del Cliente:"); if (!name) return; setClientName(name); } setPurpose(p); setShowCam(true); const useRear = p.includes('visita'); const constraints = { video: { facingMode: useRear ? 'environment' : 'user', width: { ideal: 1280 }, height: { ideal: 720 } } }; try { const s = await navigator.mediaDevices.getUserMedia(constraints); if (videoRef.current) { videoRef.current.srcObject = s; if (!useRear) videoRef.current.classList.add('mirror'); else videoRef.current.classList.remove('mirror'); } } catch (err) { alert("Error con la cámara."); setShowCam(false); } }; const capture = () => { const canvas = canvasRef.current; const video = videoRef.current; canvas.width = video.videoWidth; canvas.height = video.videoHeight; const ctx = canvas.getContext('2d'); if (!purpose.includes('visita')) { ctx.translate(canvas.width, 0); ctx.scale(-1, 1); } ctx.drawImage(video, 0, 0); const photo = canvas.toDataURL('image/jpeg', 0.6); navigator.geolocation.getCurrentPosition( (pos) => saveMark(purpose, photo, { lat: pos.coords.latitude, lng: pos.coords.longitude }), () => { alert("GPS obligatorio."); setShowCam(false); } ); }; const saveMark = async (type, photo, loc) => { setLoadingMark(true); try { await db.collection('artifacts').doc(appId).collection('public').doc('data').collection('logs').add({ userId: profile.id, userName: profile.name, type, timestamp: Date.now(), location: loc, photo, client: clientName, date: todayStr, role: profile.role }); if (type === 'salida' && profile.overrideCheckOut) { await db.collection('artifacts').doc(appId).collection('public').doc('data').collection('users').doc(profile.id).update({ overrideCheckOut: false }); } if (videoRef.current?.srcObject) videoRef.current.srcObject.getTracks().forEach(t => t.stop()); setShowCam(false); setClientName(''); } catch (e) { alert("Error al guardar."); } finally { setLoadingMark(false); } }; if (profile.status === 'subsidio' || profile.status === 'feriado') return (
Acceso Inhabilitado
{r.message}
{profile.role}
Se habilitará mañana a las 12:00 AM
{window.formatNICFull ? window.formatNICFull(Date.now()) : new Date().toLocaleDateString()}
Sin registros aún
: ({l.type.replace('_', ' ')} {l.client && - {l.client}}
{window.formatNICTime ? window.formatNICTime(l.timestamp) : new Date(l.timestamp).toLocaleTimeString()}