Fix: www/ movido a raíz, estructura final

This commit is contained in:
Ricardo Monla
2026-05-04 13:52:29 -03:00
parent 1e13888679
commit 17ff243bfb
337 changed files with 46456 additions and 19 deletions
@@ -0,0 +1,233 @@
#!/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("<!DOCTYPE") || buffer.toString("utf8", 0, 200).includes("<html")) {
ext = ".html";
}
if (ext && !filepath.endsWith(ext)) {
const newPath = filepath.replace(/\.[^.]+$/, ext);
fs.renameSync(filepath, newPath);
return newPath;
}
return filepath;
}
async function main() {
console.log("📚 Descargando recursos de lectura de todas las secciones\n");
const browser = await chromium.launch({ headless: true, args: ["--no-sandbox"] });
const context = await browser.newContext({
viewport: { width: 1280, height: 900 },
locale: "es-AR",
acceptDownloads: true,
});
const page = await context.newPage();
// Login
console.log("🔑 Login...");
await page.goto(`${CAMPUS_BASE}/m/19096/s/34868/study-resources/list`, { waitUntil: "networkidle", timeout: 30000 });
if (!page.url().includes("/campus/")) {
await page.waitForSelector("#login-email", { timeout: 10000 });
await page.fill("#login-email", CREDENTIALS.username);
await page.fill("#login-password", CREDENTIALS.password);
await page.click("#btn-login");
await page.waitForURL("**/campus/**", { timeout: 30000 });
await context.storageState({ path: SESSION_FILE });
}
console.log(" ✅ Autenticado\n");
let totalDescargados = 0;
let totalErrores = 0;
for (const sec of SECCIONES) {
const url = `${CAMPUS_BASE}/${sec.moduloPath}/${sec.id}/study-resources/list`;
const dirLocal = path.join(DOWNLOADS_DIR, "secciones", sec.modulo, sec.nombre);
fs.mkdirSync(dirLocal, { recursive: true });
console.log(`📂 ${sec.nombre}`);
try {
await page.goto(url, { waitUntil: "networkidle", timeout: 30000 });
await sleep(2000);
// Guardar HTML actualizado
const html = await page.content();
if (!html.includes("login-email")) {
fs.writeFileSync(path.join(dirLocal, "pagina.html"), html);
}
// Extraer todos los enlaces de recursos
const recursos = await page.evaluate(() => {
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();