1(function() { 2 // ========================================== 3 // 1. CONFIGURACIÃN 4 // ========================================== 5 6 const ACTIVAR_VALORACION = true; // Cambia a false si no quieres que aparezca el bloque 7 8 const ID_CURSO_OBJETIVO = "todos"; 9 const ACTIVAR_CONTROL_PROGRESO = true; 10 const MARCAR_RECURSOS_VISTOS = true; 11 12 let categoriaCursoCache = ""; // Variable global para guardar la categorÃa detectada 13 let bloqueadoPorBorrado = false; // <--- NUEVA VARIABLE DE CONTROL 14 15 // --- DETECCIÃN DE CURSO Y USUARIO --- 16 const urlParams = new URLSearchParams(window.location.search); 17 const cursoActual = parseInt(urlParams.get('id')); 18 19 // --- DETECCIÃN DE USUARIO CORREGIDA --- 20 // Buscamos especÃficamente el enlace al perfil del usuario 21 // Buscamos el ID especÃficamente en el menú de usuario o el enlace de perfil oficial 22 23 //const userLink = document.querySelector('.logininfo a[href*="user/view.php?id="], .userimg a, a[href*="user/profile.php?id="]'); 24 let usuarioActual = "anonimo"; 25 26 27 28 // LÃGICA DE EXCLUSIÃN (Reemplaza a ES_ADMIN_PROFESOR) 29 // 1. Definimos la consulta asÃncrona al nuevo PHP 30 const consultarRolUsuario = async () => { 31 try { 32 // Buscamos la ruta base del script para localizar el PHP de forma segura 33 let scriptActual = document.currentScript || Array.from(document.scripts).find(s => s.src.includes('progreso.js')); 34 const rutaBase = scriptActual ? scriptActual.src.substring(0, scriptActual.src.lastIndexOf('/') + 1) : ''; 35 36 const response = await fetch(`https://www.aulaformacion.org/campus/get_user_role.php?id=${cursoActual}`); 37 if (!response.ok) return "gestor"; 38 const rol = await response.text(); 39 return rol.trim(); 40 } catch (e) { 41 // Fallo silencioso: si hay error de red, bloqueamos por seguridad 42 return "gestor"; 43 } 44 }; 45 46 47 // ========================================== 48 // 2. ESTILOS (CSS) 49 // ========================================== 50 const inyectarCSS = async () => { 51 if (document.getElementById('css-control-progreso')) return; 52 53 try { 54 // 1. Intentamos localizar el script para sacar la ruta base 55 let scriptActual = document.currentScript; 56 if (!scriptActual) { 57 const scripts = document.getElementsByTagName('script'); 58 for (let s of scripts) { 59 if (s.src.includes('progreso.js')) { 60 scriptActual = s; 61 break; 62 } 63 } 64 } 65 66 if (scriptActual) { 67 const rutaBase = scriptActual.src.substring(0, scriptActual.src.lastIndexOf('/') + 1); 68 69 // 2. Cargamos el contenido del CSS como texto 70 const respuesta = await fetch(rutaBase + 'progreso.css'); 71 if (respuesta.ok) { 72 const cssTexto = await respuesta.text(); 73 74 // --- NUEVOS ESTILOS PARA ANIMACIÃN DE SALIDA --- 75 const animacionesExtra = ` 76 @keyframes fadeOut { 77 from { opacity: 1; transform: scale(1); } 78 to { opacity: 0; transform: scale(0.9); } 79 } 80 .btn-finalizar-salir { 81 animation: fadeOut 0.3s forwards; 82 pointer-events: none; 83 } 84 `; 85 86 // 3. Lo inyectamos directamente en el DOM (Original + Animaciones) 87 const estilo = document.createElement('style'); 88 estilo.id = 'css-control-progreso'; 89 estilo.textContent = cssTexto + animacionesExtra; 90 document.head.appendChild(estilo); 91 } 92 } 93 } catch (e) { 94 // Error silencioso: No hacemos nada si falla la red o el CORS 95 // Asà el alumno no ve mensajes en consola. 96 } 97 }; 98 99 // ========================================== 100 // 3. UI Y BARRA 101 // ========================================== 102 const crearBarraProgreso = async () => { 103 // Obtenemos la categorÃa si aún no la tenemos 104 if (!categoriaCursoCache) { 105 categoriaCursoCache = await consultarCategoriaYValidar(); 106 } 107 108 const hayCuestionarios = document.querySelector('#course-view a[href*="mod/quiz"]'); 109 110 // REGLA DE EXCEPCIÃN AMPLIADA: 111 // Ahora permitimos que MASTER UEMC también fuerce el renderizado sin quizzes 112 const categoriasManuales = ['EXPU UEMC', 'MASTER UEMC']; 113 if (!hayCuestionarios && !categoriasManuales.includes(categoriaCursoCache)) return; 114 115 if (document.getElementById('contenedor-progreso-fijo')) return; 116 117 const contenedor = document.createElement('div'); 118 contenedor.id = 'contenedor-progreso-fijo'; 119 const fill = document.createElement('div'); 120 fill.id = 'barra-progreso-fill'; 121 contenedor.appendChild(fill); 122 const texto = document.createElement('div'); 123 texto.id = 'texto-progreso'; 124 texto.innerText = 'Progreso: 0%'; // <--- AÃADIR ESTA LÃNEA 125 document.body.appendChild(contenedor); 126 document.body.appendChild(texto); 127 }; 128 129 const actualizarBarra = async () => { 130 // --- RECUPERADO: Detección de categorÃa con caché --- 131 if (!categoriaCursoCache) { 132 categoriaCursoCache = await consultarCategoriaYValidar(); 133 } 134 135 const cuestionariosEnPagina = document.querySelectorAll('#course-view .section li:has(a[href*="mod/quiz"])'); 136 const tieneCuestionarios = cuestionariosEnPagina.length > 0; 137 let total = 0; 138 let completados = 0; 139 140 // --- 1. LÃGICA DE CONTEO (Integrada lÃnea por lÃnea) --- 141 if (tieneCuestionarios) { 142 const llaveTotal = `total_quizzes_curso_${cursoActual}`; 143 const cacheNotasKey = `notas_curso_${cursoActual}`; 144 const esVistaReducida = window.location.search.includes('topic=') && !window.location.search.includes('topic=0'); 145 146 if (!esVistaReducida) { 147 total = cuestionariosEnPagina.length; 148 localStorage.setItem(llaveTotal, total); 149 } else { 150 total = parseInt(localStorage.getItem(llaveTotal)) || cuestionariosEnPagina.length; 151 } 152 const notasGuardadas = JSON.parse(localStorage.getItem(cacheNotasKey) || "[]"); 153 completados = notasGuardadas.length; 154 } 155 else if (['EXPU UEMC', 'MASTER UEMC'].includes(categoriaCursoCache)) { 156 const llaveTotalSecciones = `total_secciones_curso_${cursoActual}`; 157 const cacheCompletadasKey = `secciones_completadas_${usuarioActual}_${cursoActual}`; 158 const esVistaReducida = window.location.search.includes('topic=') && !window.location.search.includes('topic=0'); 159 160 if (!esVistaReducida) { 161 const seccionesValidas = document.querySelectorAll('.section:not(#section-0) .content > .summary'); 162 total = seccionesValidas.length; 163 if (total > 0) localStorage.setItem(llaveTotalSecciones, total); 164 } else { 165 total = parseInt(localStorage.getItem(llaveTotalSecciones)) || 1; 166 } 167 const completadasData = JSON.parse(localStorage.getItem(cacheCompletadasKey) || "[]"); 168 completados = completadasData.length; 169 } 170 171 if (total === 0) return; 172 173 // --- 2. ACTUALIZACIÃN VISUAL --- 174 //const porcentaje = Math.round((completados / total) * 100); 175 const porcentaje = Math.min(100, Math.round((completados / total) * 100)); 176 const contenedor = document.getElementById('contenedor-progreso-fijo'); 177 const fill = document.getElementById('barra-progreso-fill'); 178 const text = document.getElementById('texto-progreso'); 179 180 if (fill && contenedor) { 181 const anchoFinal = Math.max(0, (completados * (contenedor.offsetWidth / total)) - 2); 182 fill.style.width = `${anchoFinal}px`; 183 } 184 if (text) text.innerText = `Progreso: ${porcentaje}%`; 185 186 // --- RECUPERADO: ALERTA EN MENà LATERAL --- 187 const quizLinkLateral = document.querySelector('a[href*="mod/quiz/index.php"]'); 188 if (quizLinkLateral) { 189 if (porcentaje < 100) { 190 quizLinkLateral.classList.add('quiz-link-alert'); 191 } else { 192 quizLinkLateral.classList.remove('quiz-link-alert'); 193 } 194 } 195 196 // --- 3. LÃGICA DE FINALIZACIÃN (Botón y Confeti corregido) --- 197 const btnExistente = document.getElementById('btn-finalizar-curso'); 198 199 if (porcentaje === 100) { 200 bloqueadoPorBorrado = false;
201 const catParaValidar = categoriaCursoCache.toLowerCase().trim(); 202 const categoriasPermitidas = ['baremables', 'cue uemc', 'privados', 'expu uemc', 'master uemc']; 203 204 if (categoriasPermitidas.includes(catParaValidar) || catParaValidar.includes('bonificado')) { 205 // Inyectamos el botón si no existe 206 if (!btnExistente) { 207 inyectarBotonFinalizar(categoriaCursoCache); 208 } 209 210 // --- DISPARO DE CONFETI OPTIMIZADO PARA TU MÃVIL --- 211 const yaLanzado = sessionStorage.getItem(`confetti_lanzado_${cursoActual}`); 212 if (!yaLanzado && typeof confetti === 'function') { 213 // Esperamos 1 segundo completo para que el botón se renderice y se posicione 214 setTimeout(() => { 215 const btnReal = document.getElementById('btn-finalizar-curso'); 216 if (btnReal) { 217 const rect = btnReal.getBoundingClientRect(); 218 219 // Si las coordenadas son 0, el botón aún no es visible, no lanzamos nada 220 if (rect.top === 0 && rect.left === 0) return; 221 222 confetti({ 223 particleCount: 150, 224 spread: 80, 225 origin: { 226 x: (rect.left + rect.width / 2) / window.innerWidth, 227 y: (rect.top + rect.height / 2) / window.innerHeight 228 }, 229 angle: 160, // Hacia la izquierda (centro de la pantalla) 230 velocity: 45, 231 gravity: 0.9, 232 drift: -0.8, 233 ticks: 500, 234 zIndex: 2000001 235 }); 236 sessionStorage.setItem(`confetti_lanzado_${cursoActual}`, 'true'); 237 } 238 }, 1000); 239 } 240 } 241 } else if (btnExistente && !btnExistente.classList.contains('btn-finalizar-salir')) { 242 bloqueadoPorBorrado = true; 243 btnExistente.classList.add('btn-finalizar-salir'); 244 setTimeout(() => { 245 btnExistente.remove(); 246 bloqueadoPorBorrado = false; 247 }, 300); 248 } 249 }; 250 251 const aplicarUI = async (li, texto, estado) => { // Añadido async 252 if (!li) return; 253 254 li.classList.remove('actividad-completada', 'actividad-pendiente'); 255 const claseFondo = (estado === 'ok') ? 'actividad-completada' : 'actividad-pendiente'; 256 const claseBadge = (estado === 'ok') ? 'badge-azul' : 'badge-roja'; 257 258 li.classList.add(claseFondo); 259 260 let span = li.querySelector('.status-label'); 261 if (!span) { 262 span = document.createElement('span'); 263 span.className = 'status-label'; 264 const link = li.querySelector('a'); 265 if (link) { 266 link.after(span); 267 } else { 268 li.appendChild(span); 269 } 270 } 271 272 const icono = (estado === 'ok') ? 'â ' : '⢠'; 273 span.className = `status-label ${claseBadge}`; 274 span.innerText = icono + texto; 275 276 // Llamada asÃncrona a la barra 277 await actualizarBarra(); // Añadido await 278 }; 279 280 // ========================================== 281 // 4. LÃGICA DE DATOS 282 // ========================================== 283 const procesarCuestionarios = async () => { 284 const cacheKey = `notas_curso_${cursoActual}`; 285 286 // 1. CARGA INSTANTÃNEA: Intentar leer de la caché del móvil 287 const cachedData = localStorage.getItem(cacheKey); 288 if (cachedData) { 289 const datos = JSON.parse(cachedData); 290 datos.forEach(d => { 291 const linkEnCurso = document.querySelector(`#course-view a[href*="id=${d.qId}"]`); 292 if (linkEnCurso) aplicarUI(linkEnCurso.closest('li'), `REALIZADO [${d.nota}]`, 'ok'); 293 }); 294 } 295 296 // 2. ACTUALIZACIÃN: Fetch en segundo plano para refrescar datos 297 try { 298 const response = await fetch(`https://www.aulaformacion.org/campus/mod/quiz/index.php?id=${cursoActual}`); 299 if (!response.ok) return; 300 const html = await response.text(); 301 const doc = new DOMParser().parseFromString(html, 'text/html');
302 const filas = doc.querySelectorAll('tr.r0, tr.r1'); 303 304 let nuevosDatosParaCache = []; 305 306 filas.forEach((fila) => { 307 const linkCuestionario = fila.querySelector('td.c1 a'); 308 const celdaNota = fila.querySelector('td.c2'); 309 if (linkCuestionario && celdaNota) { 310 const qId = linkCuestionario.href.match(/[?&]id=(\d+)/)[1]; 311 let notaRaw = celdaNota.innerText.trim(); 312 if (notaRaw && notaRaw.includes('/')) { 313 const notaCompacta = notaRaw.replace(/\s+/g, ''); 314 nuevosDatosParaCache.push({ qId, nota: notaCompacta }); 315 316 const linkEnCurso = document.querySelector(`#course-view a[href*="id=${qId}"]`); 317 if (linkEnCurso) aplicarUI(linkEnCurso.closest('li'), `REALIZADO [${notaCompacta}]`, 'ok'); 318 } 319 } 320 }); 321 322 // Guardar los datos frescos para la próxima visita 323 localStorage.setItem(cacheKey, JSON.stringify(nuevosDatosParaCache)); 324 325 } catch (e) { console.error("Error actualizando caché", e); } 326 }; 327 328 const inicializarEstadoCuestionarios = () => { 329 const items = document.querySelectorAll('#course-view .section li'); 330 items.forEach(li => { 331 const a = li.querySelector('a'); 332 if (!a) return; 333 334 if (a.href.includes('mod/quiz')) { 335 aplicarUI(li, 'PENDIENTE', 'error'); 336 } 337 }); 338 }; 339 340 const habilitarSticky = () => { 341 // Moodle 1.9 usa tablas para las secciones; necesitamos forzar el comportamiento 342 const tablasSeccion = document.querySelectorAll('.course-content > table'); 343 tablasSeccion.forEach(tabla => { 344 tabla.style.borderCollapse = 'separate'; // Necesario para sticky en tablas 345 }); 346 }; 347 348 const prepararBotonSubir = () => { 349 // 1. Crear el botón 350 const btn = document.createElement('button'); 351 btn.id = 'btn-volver-arriba'; 352 btn.innerHTML = 'â²'; // Puedes usar un icono SVG si lo prefieres 353 document.body.appendChild(btn); 354 355 // 2. Controlar visibilidad al hacer scroll 356 window.addEventListener('scroll', () => { 357 if (window.scrollY > 400) { // Aparece tras bajar 400px 358 btn.classList.add('visible'); 359 } else { 360 btn.classList.remove('visible'); 361 } 362 }); 363 364 // 3. Acción de subir 365 btn.onclick = () => { 366 window.scrollTo({ 367 top: 0, 368 behavior: 'smooth' // Subida fluida 369 }); 370 }; 371 }; 372 373 const crearNavegacionRapida = () => { 374 const secciones = document.querySelectorAll('.section .content > .summary'); 375 if (secciones.length === 0) return; 376 377 const navContainer = document.createElement('div'); 378 navContainer.id = 'nav-rapida-container'; 379 380 const select = document.createElement('select'); 381 select.id = 'nav-rapida-select'; 382 383 const defaultOpt = document.createElement('option'); 384 defaultOpt.text = "ð Saltar a la sección..."; 385 defaultOpt.value = ""; 386 select.appendChild(defaultOpt); 387 388 // Usamos un contador separado para la numeración visual 389 let unidadReal = 1; 390 391 secciones.forEach((sec, index) => { 392 // --- CAMBIO CLAVE: Ignoramos la sección 0 --- 393 if (index === 0) return; 394 395 const titulo = sec.innerText.trim() 396 .replace(/\n/g, ' ') 397 .substring(0, 35); 398 399 if (titulo) { 400 const opt = document.createElement('option'); 401 opt.value = index; 402 // Numeración coherente: la primera unidad será la 01 403 const num = (unidadReal).toString().padStart(2, '0'); 404 opt.text = `${num} | ${titulo}...`; 405 select.appendChild(opt); 406 407 sec.id = 'ancla-seccion-' + index; 408 unidadReal++; // Solo incrementamos si no es la sección 0 409 } 410 }); 411 412 select.onchange = (e) => { 413 if (e.target.value === "") return; 414 const id = 'ancla-seccion-' + e.target.value; 415 const el = document.getElementById(id); 416 if (el) { 417 const offset = 80; 418 const bodyRect = document.body.getBoundingClientRect().top; 419 const elementRect = el.getBoundingClientRect().top; 420 const elementPosition = elementRect - bodyRect; 421 const offsetPosition = elementPosition - offset; 422 423 window.scrollTo({ 424 top: offsetPosition, 425 behavior: 'smooth' 426 }); 427 428 setTimeout(() => select.value = "", 500); 429 } 430 }; 431 432 navContainer.appendChild(select); 433 document.body.appendChild(navContainer); 434 }; 435 436 const habilitarMarcadoSecciones = () => { 437 const titulos = document.querySelectorAll('.section .content > .summary'); 438 439 // Claves únicas: Usuario + Curso 440 const cacheCompletadas = `secciones_completadas_${usuarioActual}_${cursoActual}`; 441 const cacheActual = `seccion_actual_${usuarioActual}_${cursoActual}`; 442 443 let completadas = JSON.parse(localStorage.getItem(cacheCompletadas) || "[]"); 444 let seccionActualIndex = localStorage.getItem(cacheActual); 445 446 titulos.forEach((summary, index) => { 447 if (index === 0) return; // Ignorar sección 0 448 449 const btnHecho = document.createElement('button'); 450 btnHecho.className = 'btn-check-seccion'; 451 452 const btnActual = document.createElement('button'); 453 btnActual.className = 'btn-actual-seccion'; 454 455 // --- Gestión del botón "Solo esta sección" --- 456 const linkMoodleOriginal = summary.closest('.section').querySelector('a[href*="topic="]'); 457 let btnSoloEsta = null; 458 459 if (linkMoodleOriginal) { 460 linkMoodleOriginal.style.display = 'none'; 461 462 // Si el link original apunta a topic=0, significa que ya estamos en vista única 463 const esParaVerTodas = linkMoodleOriginal.href.includes('topic=0'); 464 465 btnSoloEsta = document.createElement('button'); 466 btnSoloEsta.type = 'button'; 467 btnSoloEsta.className = 'btn-solo-seccion'; 468 469 btnSoloEsta.innerHTML = esParaVerTodas ? 'Mostrar todas las secciones' : 'Mostrar solo esta sección'; 470 471 btnSoloEsta.onclick = () => { 472 window.location.href = linkMoodleOriginal.href; 473 }; 474 } 475 476 const actualizarBotones = () => { 477 const esHecho = completadas.includes(index); 478 const esActual = seccionActualIndex == index; 479 480 btnHecho.innerHTML = esHecho ? 'â Completada' : 'Marcar como completada'; 481 btnHecho.classList.toggle('completada', esHecho); 482 483 btnActual.innerHTML = esActual ? 'ð Sección actual' : 'Establecer como actual'; 484 btnActual.classList.toggle('activa', esActual); 485 }; 486 487 btnHecho.onclick = async (e) => { 488 e.preventDefault(); 489 e.stopPropagation(); 490 491 const yaEsta = completadas.indexOf(index); 492 if (yaEsta > -1) { 493 completadas.splice(yaEsta, 1); 494 } else { 495 completadas.push(index); 496 } 497 498 localStorage.setItem(cacheCompletadas, JSON.stringify(completadas)); 499 actualizarBotones(); 500 501 // Llamamos a la función correcta definida en tu script 502 if (typeof crearBarraProgreso === 'function') { 503 //await crearBarraProgreso(); 504 await actualizarBarra(); 505 } 506 }; 507
508 btnActual.onclick = (e) => { 509 e.preventDefault(); 510 e.stopPropagation(); 511 if (seccionActualIndex == index) { 512 seccionActualIndex = null; 513 localStorage.removeItem(cacheActual); 514 } else { 515 seccionActualIndex = index; 516 localStorage.setItem(cacheActual, index); 517 } 518 519 // Refresco visual de todos los botones "Actual" 520 document.querySelectorAll('.btn-actual-seccion').forEach((b, i) => { 521 const esLaActual = seccionActualIndex == (i + 1); 522 b.innerHTML = esLaActual ? 'ð Sección actual' : 'Establecer como actual'; 523 b.classList.toggle('activa', esLaActual); 524 }); 525 }; 526 527 actualizarBotones(); 528 529 // Inyección en el DOM 530 summary.prepend(btnHecho); 531 summary.prepend(btnActual); 532 if (btnSoloEsta) { 533 btnSoloEsta.style.marginLeft = "10px"; 534 summary.prepend(btnSoloEsta); 535 } 536 }); 537 }; 538 539 const consultarCategoriaYValidar = async () => { 540 try { 541 // Hacemos la consulta de forma silenciosa 542 const response = await fetch(`https://www.aulaformacion.org/campus/get_category.php?id=${cursoActual}`); 543 544 // Si la respuesta no es 200 OK, salimos sin decir nada 545 if (!response.ok) return ""; 546 547 const categoria = (await response.text()).trim(); 548 return categoria; 549 } catch (e) { 550 // En caso de error de red o timeout, devolvemos vacÃo 551 // Esto evita que salten excepciones en la consola de tu móvil 552 return ""; 553 } 554 }; 555 556 const consultarNombreCortoCurso = async () => { 557 try { 558 const response = await fetch(`https://www.aulaformacion.org/campus/get_shortname.php?id=${cursoActual}`); 559 if (!response.ok) return "CURSO"; 560 const nombreCorto = (await response.text()).trim(); 561 return nombreCorto !== "" ? nombreCorto : "CURSO"; 562 } catch (e) { 563 return "CURSO"; 564 } 565 }; 566 567 const consultarIdUsuario = async () => { 568 try { 569 const response = await fetch(`https://www.aulaformacion.org/campus/get_user_id.php`); 570 return response.ok ? (await response.text()).trim() : "anonimo"; 571 } catch (e) { return "anonimo"; } 572 }; 573 574 // --- NUEVA LÃGICA PARA RECURSOS (PDF, FOROS, ETC.) --- 575 576 const marcarRecursoComoVisto = (idRecurso) => { 577 const storageKey = `progreso_${cursoActual}_${usuarioActual}`; 578 let vistos = JSON.parse(localStorage.getItem(storageKey) || "[]"); 579 580 if (!vistos.includes(idRecurso)) { 581 vistos.push(idRecurso); 582 localStorage.setItem(storageKey, JSON.stringify(vistos)); 583 } 584 }; 585 586 const procesarRecursosVistos = () => { 587 // Si el control está apagado en la configuración, abortamos 588 if (!MARCAR_RECURSOS_VISTOS) return; 589 590 const storageKey = `progreso_${cursoActual}_${usuarioActual}`; 591 const vistos = JSON.parse(localStorage.getItem(storageKey) || "[]"); 592 593 // Seleccionamos todos los LI que NO son cuestionarios 594 const recursos = document.querySelectorAll('#course-view .section li:not(:has(a[href*="mod/quiz"]))'); 595 596 recursos.forEach(li => { 597 const link = li.querySelector('a'); 598 if (!link) return; 599 600 // Extraer un ID único del enlace (ej: id=1234) 601 const match = link.href.match(/[?&]id=(\d+)/); 602 if (!match) return; 603 const idRecurso = match[1]; 604 605 // 1. Crear el elemento visual (el check verde) 606 if (!li.querySelector('.recurso-visto-check')) { 607 const check = document.createElement('div'); 608 check.className = 'recurso-visto-check'; 609 check.innerHTML = 'â'; 610 li.appendChild(check); 611 } 612
613 // 2. Lógica de Marcado (Historial o LocalStorage) 614 if (vistos.includes(idRecurso) || link.matches(':visited')) { 615 li.classList.add('recurso-visto-estilo'); 616 // Si estaba en historial pero no en storage, lo guardamos para persistencia 617 if (!vistos.includes(idRecurso)) marcarRecursoComoVisto(idRecurso); 618 } 619 620 // 3. Escuchar el clic para marcar nuevos 621 link.addEventListener('click', () => { 622 li.classList.add('recurso-visto-estilo'); 623 marcarRecursoComoVisto(idRecurso); 624 }); 625 }); 626 }; 627 628 // --- INTERFAZ FINALIZACIÃN DE CURSO --- 629 630 const mostrarModalFinalizacion = async (categoria) => { 631 if (document.getElementById('modal-finalizacion-overlay')) return; 632 633 usuarioActual = await consultarIdUsuario(); 634 // Obtenemos el nombre corto desde el nuevo PHP 635 const codigoCurso = await consultarNombreCortoCurso(); 636 637 const claveBase = "FmjnOikSlbG7R4DYVcAHzA3Q"; 638 639 const urlValoracion = `https://www.aulaformacion.org/valoracion/?k=${claveBase}${usuarioActual}&c=${codigoCurso}`; 640 641 let bloqueValoracion = ""; // Empezamos con el bloque vacÃo 642 643 if (ACTIVAR_VALORACION) { 644 bloqueValoracion = ` 645 <div style="margin-top: 25px; padding-top: 20px; border-top: 1px solid #e2e8f0; text-align: center;"> 646 <p style="margin: 0 0 15px 0; font-size: 13px; color: #64748b; font-weight: 600; text-transform: uppercase; letter-spacing: 1px;"> 647 Valorar la formación recibida 648 </p> 649 <a href="${urlValoracion}" target="_blank" style=" 650 display: inline-block; background: #1e40af; color: white; 651 text-decoration: none; padding: 12px 35px; border-radius: 12px; 652 font-size: 15px; font-weight: bold; box-shadow: 0 4px 12px rgba(30, 64, 175, 0.2); 653 "> 654 ð Comenzar la valoración 655 </a> 656 <div style="margin-top: 25px; border-bottom: 1px solid #e2e8f0;"></div> 657 </div> 658 `; 659 } 660 661 662 // Definimos los mensajes según el tipo de curso 663 let titulo = "¡Curso Completado!"; 664 let cuerpoHtml = ""; 665 666 if (categoria === 'Baremables') { 667 cuerpoHtml = ` 668 <p style="color: #475569; font-size: 15px; line-height: 1.5; margin-bottom: 20px;"> 669 Enhorabuena!! Has realizado todas las evaluaciones de este curso de <strong>Formación Permanente, certificado por la Universidad Europea Miguel de Cervantes.</strong>. 670 </p> 671 <div style="background: #f1f5f9; padding: 12px; border-radius: 8px; margin-bottom: 20px; text-align: left;"> 672 <p style="margin: 0; font-size: 13px; color: #64748b;"> 673 <strong>Siguiente paso:</strong><br> 674 En el apartado ESTADO DEL CURSO, del panel lateral, puedes <strong>consultar y enviar la finalización</strong> para que tu coordinador/a lo revise y te envÃe la certificación. 675 </p> 676 </div>`; 677 } else if (categoria === 'Privados') { 678 // Diseño especÃfico para cursos PRIVADOS 679 cuerpoHtml = ` 680 <p style="color: #475569; font-size: 15px; line-height: 1.5; margin-bottom: 20px;"> 681 ¡Enhorabuena! Has completado este curso de especialización</strong>. 682 </p> 683 <div style="background: #ecfdf5; padding: 12px; border-radius: 8px; margin-bottom: 20px; text-align: left; border: 1px solid #d1fae5;"> 684 <p style="margin: 0; font-size: 13px; color: #065f46;"> 685 <strong>Certificación:</strong><br> 686 El último paso que te queda es informar a tu coordinador/a la finalización de tu curso y después de revisar tus calificaciones; te enviará tu diploma acreditativo. 687 </p> 688 </div>`; 689 } else if (categoria === 'CUE UEMC') { 690 // Mensaje especÃfico para Cursos de Extensión Universitaria 691 cuerpoHtml = `
692 <p style="color: #475569; font-size: 15px; line-height: 1.5; margin-bottom: 20px;"> 693 ¡Felicidades! Has completado con éxito este<br> 694 <strong>Curso Universitario de Especialización</strong>,<br> 695 certificado por la <strong>Universidad Europea Miguel de Cervantes</strong>. 696 </p> 697 <div style="background: #eff6ff; padding: 12px; border-radius: 8px; margin-bottom: 20px; text-align: left; border: 1px solid #dbeafe;"> 698 <p style="margin: 0; font-size: 13px; color: #1e40af;"> 699 <strong>Aviso importante:</strong><br> 700 Recuerda que para que podamos certificar tu formación; además de haber realizado todos los cuestionarios, es imprescindible que se haya cumplido el tiempo mÃnimo de realización que interpone la universidad. Si tienes alguna duda a este respecto; puedes preguntar a tu coordinador/a.<br><br> 701 <strong>Trámite de certificación:</strong><br> 702 Una vez que se haya cumplido esta fecha, debes informar a tu coordinador/a que has finalizado el curso. A partir de ese momento, desde la escuela pondremos en marcha todo el proceso de certificación de cara a la universidad y te mantendremos informado/a de todo el proceso. 703 </p> 704 </div>`; 705 } else if (categoria === 'MASTER UEMC') { 706 // DISEÃO ESPECÃFICO PARA MÃSTER 707 cuerpoHtml = ` 708 <p style="color: #1e3a8a; font-size: 15px; line-height: 1.5; margin-bottom: 20px;"> 709 ¡Felicidades! Has completado todos los módulos de este<br> 710 <strong>Máster Universitario (UEMC)</strong>. 711 </p> 712 <div style="background: #f8fafc; padding: 15px; border-radius: 8px; margin-bottom: 20px; text-align: left; border: 1px solid #e2e8f0;"> 713 <p style="margin: 0; font-size: 13px; color: #334155;"> 714 <strong>Siguiente paso:</strong><br> 715 En el apartado ESTADO DEL CURSO, del panel lateral, puedes consultar y enviar la finalización para que tu coordinador/a lo revise y si todo está correcto, comenzará el proceso de ceertificación con la Universidad y del que te mantendrá informado/a. 716 </p> 717 </div>`; 718 } else if (categoria === 'EXPU UEMC') { 719 cuerpoHtml = ` 720 <p style="color: #1e3a8a; font-size: 15px; line-height: 1.5; margin-bottom: 20px;"> 721 ¡Enhorabuena! Has completado todos los módulos de este<br> 722 <strong>Experto Universitario</strong>, certificado por la<br> 723 <strong>Universidad Europea Miguel de Cervantes</strong>. 724 </p> 725 <div style="background: #eff6ff; padding: 15px; border-radius: 8px; margin-bottom: 20px; text-align: left; border: 1px solid #bfdbfe;"> 726 <p style="margin: 0; font-size: 13px; color: #1e40af;"> 727 <strong>Siguiente paso:</strong><br> 728 En el apartado ESTADO DEL CURSO, del panel lateral, puedes consultar y enviar la finalización para que tu coordinador/a lo revise y si todo está correcto, comenzará el proceso de ceertificación con la Universidad y del que te mantendrá informado/a. 729 </p> 730 </div>`; 731 } else { 732 // Mensaje para cursos BONIFICADOS 733 cuerpoHtml = ` 734 <p style="color: #475569; font-size: 15px; line-height: 1.5; margin-bottom: 20px;"> 735 ¡Excelente trabajo!<br>Has completado el 100% de este curso <strong>Bonificado</strong>. 736 </p> 737 <div style="background: #fff7ed; padding: 12px; border-radius: 8px; margin-bottom: 20px; text-align: left; border: 1px solid #ffedd5;"> 738 <p style="margin: 0; font-size: 13px; color: #9a3412;"> 739 <strong>Aviso importante:</strong><br> 740 Recuerda que para que tu empresa se pueda bonificar correctamente el curso; además de haber realizado todos los cuestionarios, es imprescindible haber cumplido con el <strong>tiempo mÃnimo de conexiones</strong> estipulado. Puedes ver tus <strong>tiempos de conexión</strong> en el apartado <strong>
740DEDICACIÃN AL CURSO</strong> -> Consultar...<br><br> 741 <strong>Finalización del curso:</strong><br> 742 No tienes que hacer hada más. Cuando se cumpla la fecha de finalización, la escuela se encargará de enviar toda la documentación a tu empresa, incluida la certificación del curso.<br> 743 Gracias. 744 </p> 745 </div>`; 746 } 747 748 //cuerpoHtml += bloqueValoracion; 749 750 const modalOverlay = document.createElement('div'); 751 modalOverlay.id = 'modal-finalizacion-overlay'; 752 modalOverlay.style = ` 753 position: fixed; top: 0; left: 0; width: 100%; height: 100%; 754 background: rgba(0,0,0,0.6); display: flex; align-items: center; 755 justify-content: center; z-index: 2000000; padding: 20px; 756 backdrop-filter: blur(3px); -webkit-backdrop-filter: blur(3px); 757 `; 758 759 const modalContenido = document.createElement('div'); 760 modalContenido.style = ` 761 background: white; padding: 25px; border-radius: 16px; 762 max-width: 500px; width: 100%; text-align: center; 763 box-shadow: 0 10px 25px rgba(0,0,0,0.2); 764 font-family: -apple-system, sans-serif; 765 animation: popIn 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275); 766 `; 767 768 modalContenido.innerHTML = ` 769 <div style="font-size: 42px; margin-bottom: 10px;">ð</div> 770 <h2 style="color: #1e40af; margin: 0 0 10px 0; font-size: 20px; font-weight: 700; text-align: center;">${titulo}</h2> 771 772 <div style="text-align: left;"> 773 ${cuerpoHtml} 774 </div> 775 776 ${bloqueValoracion} 777 778 <button id="cerrar-modal">Cerrar</button> 779 `; 780 781 modalOverlay.appendChild(modalContenido); 782 document.body.appendChild(modalOverlay); 783 784 const cerrar = () => modalOverlay.remove(); 785 document.getElementById('cerrar-modal').onclick = cerrar; 786 modalOverlay.onclick = (e) => { if (e.target === modalOverlay) cerrar(); }; 787 788 }; 789 790 const inyectarBotonFinalizar = (categoria) => { 791 // Si estamos en proceso de borrar el botón, abortamos la creación 792 if (bloqueadoPorBorrado) return; 793 794 if (document.getElementById('btn-finalizar-curso')) return; 795 796 const btn = document.createElement('button'); 797 btn.id = 'btn-finalizar-curso'; 798 //btn.className = 'btn-finalizar-fijo'; 799 btn.className = 'btn-finalizar-fijo btn-destacado'; 800 btn.innerHTML = '<span style="font-size: 19px; vertical-align: middle;">â¨</span> <span style="vertical-align: middle; letter-spacing: 0.5px;">FINALIZAR CURSO</span>'; 801 802 Object.assign(btn.style, { 803 position: 'fixed', 804 top: '45px', 805 right: '20px', 806 display: 'block', 807 width: 'auto' 808 }); 809 810 btn.onclick = () => { 811 // ELIMINAMOS EL LATIDO AL PULSAR 812 btn.classList.remove('btn-destacado'); 813 btn.classList.add('btn-pulsado'); // Opcional: añadimos un estado fijo 814 815 mostrarModalFinalizacion(categoria); 816 }; 817 818 document.body.appendChild(btn); 819 }; 820 821 822 // ========================================== 823 // 5. INICIALIZACIÃN 824 // ========================================== 825 // Nueva entrada al script (Async Gatekeeper) 826 const main = async () => { 827 if (isNaN(cursoActual) || !ACTIVAR_CONTROL_PROGRESO) return; 828 829 // Espera de validación asÃncrona 830 const rol = await consultarRolUsuario(); 831 if (rol === "gestor") return; 832 833 // Solo llegamos aquà si el PHP confirma 'alumno' 834 document.body.classList.add('control-progreso-on'); 835 836 inyectarCSS(); 837 crearBarraProgreso(); 838 inicializarEstadoCuestionarios(); 839 procesarCuestionarios(); 840 841 if (MARCAR_RECURSOS_VISTOS) { 842 procesarRecursosVistos(); 843 } 844 845 crearNavegacionRapida(); 846 habilitarSticky(); 847 habilitarMarcadoSecciones(); 848 prepararBotonSubir(); 849 actualizarBarra(); 850 851 // --- LÃGICA DE AUTOSCROLL (Paso 2) ---
852 // Usamos "seccion_actual" para que coincida con lo que guarda el botón 853 const activaKey = `seccion_actual_${usuarioActual}_${cursoActual}`; 854 const seccionGuardada = localStorage.getItem(activaKey); 855 856 if (seccionGuardada !== null) { 857 const secciones = document.querySelectorAll('.section.main'); 858 const indexParaScroll = parseInt(seccionGuardada); 859 860 if (secciones[indexParaScroll]) { 861 setTimeout(() => { 862 secciones[indexParaScroll].scrollIntoView({ 863 behavior: 'smooth', 864 block: 'start' 865 }); 866 }, 1000); // 1 segundo para que a tu móvil le dé tiempo a cargar todo 867 } 868 } 869 }; 870 871 window.addEventListener('DOMContentLoaded', main); 872 873})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.