Skip to main content
Universidade Nilton Lins

Log in to Universidade Nilton Lins

Lost password?

Is this your first time here?

Hi!

For full access to courses you'll need to create yourself an account.

All you need to do is make up a username and password and use it in the form on this page!

If someone else has already chosen your username then you'll have to try again using a different username.

Some courses may allow guest access

English ‎(en)‎
English ‎(en)‎ Português - Brasil ‎(pt_br)‎
You are not logged in.
Data retention summary
Get the mobile app
* * Novidades v2: * - Detecta páginas de módulo (diary, page, forum, quiz, wiki, assign, etc.) * - Registra o tempo ativo em cada ferramenta/recurso com duration * - YouTube: rastreia tempo de interação com iframes embarcados * - sendBeacon no beforeunload para garantir envio ao sair da página */ (function () { // ── Configuração ──────────────────────────────────────────────────────────── const API_URL = 'https://gestao-unl.manuskrypton.com.br/api/tracking/student'; const SAVE_INTERVAL_MS = 15000; // Sync a cada 15 s const IDLE_TIMEOUT_MS = 2 * 60 * 1000; // Inativo após 2 min sem interação // Labels amigáveis por tipo de módulo Moodle const MOD_LABELS = { diary: 'Diário', page: 'Página Web', forum: 'Fórum', quiz: 'Quiz/Questionário', wiki: 'Wiki', assign: 'Tarefa', resource: 'Arquivo/PDF', book: 'Livro', choice: 'Enquete', workshop: 'Workshop', chat: 'Chat', glossary: 'Glossário', lesson: 'Lição', scorm: 'SCORM', url: 'URL Externa', lti: 'LTI', h5pactivity: 'H5P', feedback: 'Feedback', survey: 'Questionário', data: 'Banco de Dados', label: 'Rótulo', folder: 'Pasta', imscp: 'IMS CP' }; // ── Estado Global ─────────────────────────────────────────────────────────── var sessionTimeSecs = 0; var isActive = true; var lastActivityTime = Date.now(); var maxScrollDepth = 0; var eventsLog = []; // eventos de clique (sem duration) var moodleUser = null; var courseId = null; var isTracking = false; // Recurso atual (se estamos numa página de módulo) var currentResource = null; // { type, title, cmid, url } var resourceTimeSecs = 0; // segundos ativos neste recurso desde o último sync // Acumulador de recursos do YouTube (por iframe) var youtubeTimers = {}; // { iframeTitle: { seconds, title } } // ── 1. Inicialização ──────────────────────────────────────────────────────── function initTracker() { try { courseId = detectCourseId(); if (!courseId || parseInt(courseId) <= 1) { console.warn('[Manuskrypton Tracker] Ignorado: Curso não detectado.'); return; } // Usuário logado var userName = getUserName(); if (!userName) { console.warn('[Manuskrypton Tracker] Ignorado: Usuário não identificado.'); return; } moodleUser = { name: userName, email: userName }; isTracking = true; // Detecta se estamos numa página de módulo específico currentResource = detectModulePage(); resourceTimeSecs = 0; bindEvents(); startTimers(); var ctx = currentResource ? ('módulo ' + currentResource.type + ' "' + currentResource.title + '"') : 'página do curso'; console.log('[Manuskrypton v2] Inicializado com sucesso! Usuário:', userName, '| Curso:', courseId, '|', ctx); } catch (e) { console.error('[Manuskrypton Tracker] Erro init:', e); } } function detectCourseId() { // 1. M.cfg.courseId ou M.cfg.courseid if (window.M && window.M.cfg) { var cid = window.M.cfg.courseId || window.M.cfg.courseid; if (cid && parseInt(cid) > 1) return String(cid); } // 2. URL parameters (?id= ou ?course= ou ?c=) var p = new URLSearchParams(window.location.search); var raw = p.get('id') || p.get('course') || p.get('c'); if (raw && parseInt(raw) > 1) { // Só usa se não estivermos na página do módulo (pois na página do módulo, id é o cmid) if (!window.location.pathname.match(/\/mod\//)) { return String(raw); } } // 3. Classe do body (Moodle costuma adicionar 'course-XXX' ao body) if (document.body) { var bodyClass = document.body.className; var bodyMatch = bodyClass.match(/course-(\d+)/); if (bodyMatch && parseInt(bodyMatch[1]) > 1) { return bodyMatch[1]; } } // 4. Links do curso nas migalhas de pão ou menu de navegação var courseLinks = document.querySelectorAll('a[href*="/course/view.php?id="], a[href*="/course/view.php?course="]'); for (var i = 0; i < courseLinks.length; i++) { var href = courseLinks[i].href; var match = href.match(/id=(\d+)/) || href.match(/course=(\d+)/); if (match && parseInt(match[1]) > 1) { return match[1]; } } return null; } // ── 2. Detecção de Página de Módulo ───────────────────────────────────────── function detectModulePage() { var path = window.location.pathname; var match = path.match(/\/mod\/([a-z_]+)\//); if (!match) return null; var modType = match[1]; var params = new URLSearchParams(window.location.search); var cmid = params.get('id') || params.get('cmid'); var title = getPageTitle(); return { type: modType, label: MOD_LABELS[modType] || (modType.charAt(0).toUpperCase() + modType.slice(1)), title: title, cmid: cmid, url: window.location.href }; } function getPageTitle() { // Tenta várias formas de obter o título da página/módulo var candidates = [ document.querySelector('.page-header-headings h1'), document.querySelector('.activity-name h1'), document.querySelector('h1.h2'), document.querySelector('#region-main h1'), document.querySelector('h1') ]; for (var i = 0; i < candidates.length; i++) { if (candidates[i]) { return candidates[i].textContent.trim().substring(0, 80); } } return document.title.replace(/\|.*$/, '').trim().substring(0, 80) || 'Recurso'; } function getUserName() { // Ordem de preferência para obter o nome do usuário var selectors = [ '.usertext', '.userbutton .usertext', '.logininfo a', '[data-region="user-menu"] .username', '.usermenu .usertext', '[data-login] .usertext', 'nav .usertext', // Temas Boost e variantes '.navbar .usertext', '[data-region="action-menu-trigger"] .usertext', '.user-menu .usertext', '.nav-link.dropdown-toggle .usertext', // Fullname em qualquer elemento visível '[data-fullname]', '.profile-user-box h4', '.userfullname', // Avatar title 'a[title][href*="profile"]' ]; for (var i = 0; i < selectors.length; i++) { var el = document.querySelector(selectors[i]); if (el) { var name = (el.getAttribute('data-fullname') || el.getAttribute('title') || el.textContent || '').trim(); if (name && !isGuestUser(name)) return name; } } // Fallback via M.cfg (userid é mais estável que sesskey) if (window.M && window.M.cfg) { if (window.M.cfg.userid && window.M.cfg.userid > 0) { return 'Usuário_' + window.M.cfg.userid; } if (window.M.cfg.sesskey) { return 'Aluno_' + window.M.cfg.sesskey.substring(0, 6); } } return null; } function isGuestUser(name) { var lower = name.toLowerCase(); return lower.includes('guest') || lower.includes('visitante') || lower === 'você não acessou' || lower.includes('login'); } // ── 3. Eventos de Interatividade ───────────────────────────────────────────── function bindEvents() { var activityEvents = ['mousemove', 'mousedown', 'keydown', 'touchstart', 'scroll', 'click']; activityEvents.forEach(function (evt) { document.addEventListener(evt, handleActivity, { passive: true }); }); document.addEventListener('visibilitychange', function () { if (document.hidden) { isActive = false; } else { resumeTracking(); } }); // Scroll depth window.addEventListener('scroll', function () { var scrollable = document.body.scrollHeight - window.innerHeight; if (scrollable <= 0) return; var pct = Math.min(100, Math.max(0, Math.round((window.scrollY / scrollable) * 100))); if (pct > maxScrollDepth) maxScrollDepth = pct; }, { passive: true }); // Cliques em links (PDFs, YouTube, Sagah, módulos Moodle) document.body.addEventListener('click', handleLinkClick); // Rastrear iframes YouTube na página setupYouTubeTracking(); // Sincronizar ao sair da página (mais confiável) window.addEventListener('beforeunload', function () { syncWithServer(true); }); window.addEventListener('pagehide', function () { syncWithServer(true); }); } function handleLinkClick(e) { var target = e.target.closest('a[href]'); if (!target) return; var href = target.href || ''; var text = (target.textContent || target.title || '').trim().substring(0, 80); // PDF if (/\.pdf(\?|$)/i.test(href)) { logClickEvent('pdf', text || 'Arquivo PDF', null); return; } // YouTube externo if (/youtube\.com|youtu\.be/i.test(href)) { logClickEvent('youtube', text || 'Vídeo YouTube', null); return; } // Sagah / LTI if (/sagah/i.test(href)) { logClickEvent('sagah', text || 'Sagah', null); return; } // Link para módulo Moodle (clique na página do curso) var modMatch = href.match(/\/mod\/([a-z_]+)\/view\.php\?(?:.*&)?id=(\d+)/); if (modMatch) { var modType = modMatch[1]; var cmid = modMatch[2]; logClickEvent(modType, text || MOD_LABELS[modType] || modType, cmid); } } function logClickEvent(type, title, cmid) { // Evita duplicar o mesmo evento em < 5 s var last = eventsLog[eventsLog.length - 1]; if (last && last.type === type && last.cmid === cmid && (Date.now() - last._ts < 5000)) return; var ev = { type: type, title: title, timestamp: Date.now() }; if (cmid) ev.cmid = cmid; ev._ts = Date.now(); // campo interno, removido antes de enviar eventsLog.push(ev); } // ── 4. YouTube Iframes ────────────────────────────────────────────────────── function setupYouTubeTracking() { var iframes = document.querySelectorAll('iframe'); iframes.forEach(function (iframe) { var src = iframe.src || iframe.getAttribute('data-src') || ''; if (!/youtube/i.test(src)) return; var title = getYouTubeTitle(iframe); var key = title; youtubeTimers[key] = { seconds: 0, title: title, active: false }; // Rastrear hover sobre o iframe como proxy de visualização var hoverInterval = null; iframe.addEventListener('mouseenter', function () { youtubeTimers[key].active = true; hoverInterval = setInterval(function () { if (isActive && youtubeTimers[key].active) { youtubeTimers[key].seconds++; } }, 1000); }); iframe.addEventListener('mouseleave', function () { youtubeTimers[key].active = false; if (hoverInterval) { clearInterval(hoverInterval); hoverInterval = null; } }); }); } function getYouTubeTitle(iframe) { // Tenta pegar o título da atividade mais próxima var activity = iframe.closest('.activity, [data-instance-id], .mod-indent'); if (activity) { var name = activity.querySelector('.instancename, .activity-name, h3, h4'); if (name) return name.textContent.trim().replace(/\s+/g, ' ').substring(0, 80); } // Fallback: texto próximo var p = iframe.parentElement; if (p) { var sibling = p.previousElementSibling || p.nextElementSibling; if (sibling) return sibling.textContent.trim().substring(0, 80); } return 'Vídeo YouTube'; } // ── 5. Atividade e Timers ─────────────────────────────────────────────────── function handleActivity() { lastActivityTime = Date.now(); if (!isActive && !document.hidden) resumeTracking(); } function resumeTracking() { isActive = true; lastActivityTime = Date.now(); } function startTimers() { // Relógio de segundos (tempo total + tempo do recurso atual) setInterval(function () { if (!isTracking) return; var now = Date.now(); if (now - lastActivityTime > IDLE_TIMEOUT_MS) isActive = false; if (isActive) { sessionTimeSecs++; if (currentResource) resourceTimeSecs++; } }, 1000); // Sincronização periódica setInterval(function () { syncWithServer(false); }, SAVE_INTERVAL_MS); } // ── 6. Sincronização com Servidor ─────────────────────────────────────────── function syncWithServer(isUnload) { if (!isTracking || sessionTimeSecs === 0) return; var timeToSync = sessionTimeSecs; var resourceTime = resourceTimeSecs; var currentScroll = maxScrollDepth; var eventsToSend = []; // Copia e limpa eventsLog.forEach(function (e) { var clean = { type: e.type, title: e.title, timestamp: e.timestamp }; if (e.cmid) clean.cmid = e.cmid; if (e.duration) clean.duration = e.duration; eventsToSend.push(clean); }); sessionTimeSecs = 0; resourceTimeSecs = 0; eventsLog = []; // Adiciona o tempo acumulado no recurso atual (se estivermos num módulo) if (currentResource && resourceTime > 0) { eventsToSend.push({ type: currentResource.type, title: currentResource.title, cmid: currentResource.cmid, duration: resourceTime, timestamp: Date.now() }); } // Adiciona tempo de vídeos YouTube (hover) Object.keys(youtubeTimers).forEach(function (key) { var yt = youtubeTimers[key]; if (yt.seconds > 0) { eventsToSend.push({ type: 'youtube', title: yt.title, duration: yt.seconds, timestamp: Date.now() }); yt.seconds = 0; } }); var payload = { user: moodleUser, courseId: courseId, timeSeconds: timeToSync, scrollDepth: currentScroll, events: eventsToSend, userAgent: navigator.userAgent, timestamp: new Date().toISOString() }; if (isUnload && navigator.sendBeacon) { // Mais confiável ao fechar/navegar a página try { var blob = new Blob([JSON.stringify(payload)], { type: 'application/json' }); navigator.sendBeacon(API_URL, blob); } catch (_) { // Ignora erros no beacon } return; } // Envio normal async fetch(API_URL, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }).then(function (res) { if (res.ok) { console.log('[Manuskrypton v2] Sincronização realizada com sucesso. ' + payload.timeSeconds + 's enviados.'); } else { console.warn('[Manuskrypton v2] Servidor retornou erro na sincronização: ' + res.status); } }).catch(function (err) { console.warn('[Manuskrypton] Falha ao enviar. Dados devolvidos ao buffer.', err); sessionTimeSecs += timeToSync; resourceTimeSecs += resourceTime; eventsLog = eventsToSend.concat(eventsLog); }); } // ── Inicializa ────────────────────────────────────────────────────────────── if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initTracker); } else { initTracker(); } })();
Powered by Moodle