#!/usr/bin/env node /** * Descarga los recursos (PDFs, archivos) de todas las secciones. * Usa navegación directa a cada URL de recurso para disparar la descarga. */ import fs from "fs"; import path from "path"; import { fileURLToPath } from "url"; import { chromium } from "playwright"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const DOWNLOADS_DIR = path.join(__dirname, "descargas"); const SESSION_FILE = path.join(__dirname, "session.json"); const CREDENTIALS = { username: "saldis_karina@hotmail.com", password: "Martes13#", }; const CAMPUS_BASE = "https://voley.onlineeducation.center/es/campus/student/training/e/14197"; const SECCIONES = [ { modulo: "Modulo_1_Planificacion", nombre: "Planificacion", id: "s/34868", moduloPath: "m/19096" }, { modulo: "Modulo_1_Planificacion", nombre: "Preparacion_Atletica", id: "s/34869", moduloPath: "m/19096" }, { modulo: "Modulo_1_Planificacion", nombre: "Explicacion_TP", id: "s/34870", moduloPath: "m/19096" }, { modulo: "Modulo_1_Planificacion", nombre: "Seguimientos_Equipos", id: "s/35523", moduloPath: "m/19096" }, { modulo: "Modulo_1_Planificacion", nombre: "Analisis_Equipos_1", id: "s/35524", moduloPath: "m/19096" }, { modulo: "Modulo_1_Planificacion", nombre: "Analisis_Equipos_2", id: "s/35525", moduloPath: "m/19096" }, { modulo: "Modulo_1_Planificacion", nombre: "TP_Final_Unidad", id: "s/35526", moduloPath: "m/19096" }, { modulo: "Modulo_2_Entrenamiento", nombre: "Centrales_y_Oponentes", id: "s/35519", moduloPath: "m/19097" }, { modulo: "Modulo_2_Entrenamiento", nombre: "Puntas_y_Liberos", id: "s/35520", moduloPath: "m/19097" }, { modulo: "Modulo_2_Entrenamiento", nombre: "Entrenamiento_Armadoras", id: "s/35521", moduloPath: "m/19097" }, { modulo: "Modulo_2_Entrenamiento", nombre: "Explicacion_TP", id: "s/35522", moduloPath: "m/19097" }, ]; function sleep(ms) { return new Promise(r => setTimeout(r, ms)); } function classifyFile(filepath) { if (!fs.existsSync(filepath)) return filepath; const buffer = fs.readFileSync(filepath); let ext = null; if (buffer[0] === 0x25 && buffer[1] === 0x50 && buffer[2] === 0x44 && buffer[3] === 0x46) { ext = ".pdf"; } else if (buffer[0] === 0x50 && buffer[1] === 0x4B) { ext = ".zip"; // docx/xlsx/pptx son ZIP } else if (buffer.toString("utf8", 0, 200).includes(" { const items = []; document.querySelectorAll("a[href]").forEach(el => { const href = el.getAttribute("href"); // relative const fullHref = el.href; // absolute if (fullHref.includes("/study-resources/") && fullHref.includes("/show")) { items.push({ tipo: "archivo-curso", href: href, url: fullHref, texto: el.textContent.trim(), id: fullHref.match(/study-resources\/(\d+)\/show/)?.[1] || "", }); } }); document.querySelectorAll("iframe[src]").forEach(el => { const src = el.src; if (src.includes("vimeo.com") || src.includes("youtube.com")) { items.push({ tipo: "video-embed", url: src, id: src.match(/vimeo\.com\/video\/(\d+)/)?.[1] || "" }); } }); return items; }); fs.writeFileSync(path.join(dirLocal, "recursos.json"), JSON.stringify(recursos, null, 2)); // Filtrar solo "Abrir Archivo" (descargas reales, no enlaces a páginas) const archivos = recursos.filter(r => r.tipo === "archivo-curso" && r.texto.toLowerCase().includes("abrir archivo") ); if (archivos.length === 0) { console.log(` Sin archivos descargables\n`); continue; } console.log(` 📥 ${archivos.length} archivos`); for (const archivo of archivos) { const filename = `recurso_${archivo.id}.bin`; const filepath = path.join(dirLocal, filename); // Si ya existe en cualquier extensión, saltar const existentes = fs.readdirSync(dirLocal).filter(f => f.startsWith(`recurso_${archivo.id}.`)); if (existentes.length > 0) { console.log(` ↺ Ya existe: ${existentes[0]}`); totalDescargados++; continue; } try { // Buscar el enlace por href parcial const linkSelector = `a[href*="study-resources/${archivo.id}/show"]`; const link = page.locator(linkSelector); const hasLink = await link.count() > 0; // Intentar click + esperar download, con timeout corto let downloaded = false; if (hasLink) { try { const [download] = await Promise.all([ page.waitForEvent("download", { timeout: 8000 }), link.first().click(), ]); await download.saveAs(filepath); downloaded = true; } catch { // No fue un download, probablemente abrió una página } } if (downloaded) { const finalPath = classifyFile(filepath); const sizeKB = (fs.statSync(finalPath).size / 1024).toFixed(1); console.log(` ✅ recurso_${archivo.id}${path.extname(finalPath)} (${sizeKB} KB)`); totalDescargados++; } else { // Navegar directamente a la URL del recurso const response = await page.goto(archivo.url, { waitUntil: "load", timeout: 15000 }); const contentType = response?.headers()?.["content-type"] || ""; if (contentType.includes("pdf") || contentType.includes("octet-stream")) { const body = await response.body(); fs.writeFileSync(filepath, body); const finalPath = classifyFile(filepath); const sizeKB = (fs.statSync(finalPath).size / 1024).toFixed(1); console.log(` ✅ recurso_${archivo.id}${path.extname(finalPath)} (${sizeKB} KB) [fetch]`); totalDescargados++; } else { // Es una página HTML — guardar contenido const htmlContent = await page.content(); const htmlPath = path.join(dirLocal, `recurso_${archivo.id}.html`); fs.writeFileSync(htmlPath, htmlContent); const sizeKB = (fs.statSync(htmlPath).size / 1024).toFixed(1); console.log(` 📄 recurso_${archivo.id}.html (${sizeKB} KB) [web]`); totalDescargados++; } // Volver a la sección para continuar await page.goto(url, { waitUntil: "networkidle", timeout: 30000 }); await sleep(1000); } } catch (err) { console.log(` ❌ ${archivo.id}: ${err.message.substring(0, 80)}`); totalErrores++; try { await page.goto(url, { waitUntil: "networkidle", timeout: 30000 }); await sleep(1000); } catch {} } } console.log(""); } catch (err) { console.log(` ❌ Error sección: ${err.message}\n`); totalErrores++; } } await browser.close(); console.log(`\n📊 Resultados:`); console.log(` Descargados: ${totalDescargados}`); console.log(` Errores: ${totalErrores}`); console.log("✅ Listo. Ejecutá: node generar_dashboard.mjs"); } main();