PageSourceSearch

https://www.aulaformacion.org/campus/theme/aulaformacion26/progreso.js?v=1

js aulaformacion.org collected 2026-10-04 13:25:25 UTC 39,831 bytes, 873 lines download raw bytes

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.