🎓 Primer commit: estructura del proyecto y material de estudio del Curso de Vóley
This commit is contained in:
@@ -0,0 +1,673 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Script para descargar el contenido del curso de Voley desde
|
||||
* Online Education Center usando Playwright.
|
||||
*
|
||||
* Versión mejorada: navega directamente a cada sección del curso,
|
||||
* extrae contenido textual, PDFs, y referencias a videos.
|
||||
*
|
||||
* Modo de uso:
|
||||
* node download_course.mjs
|
||||
*
|
||||
* Requisitos:
|
||||
* - npm install playwright
|
||||
* - npx playwright install chromium
|
||||
*
|
||||
* Para reanudar una descarga interrumpida:
|
||||
* Simplemente se vuelve a ejecutar, saltea archivos ya existentes.
|
||||
*/
|
||||
|
||||
import { chromium } from "playwright";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const DOWNLOADS_DIR = path.join(__dirname, "descargas");
|
||||
const CREDENTIALS = {
|
||||
username: "saldis_karina@hotmail.com",
|
||||
password: "Martes13#",
|
||||
};
|
||||
|
||||
// Lista de todas las secciones del curso (Módulo 1 y Módulo 2)
|
||||
const SECCIONES = [
|
||||
// Módulo 1 (m/19096)
|
||||
{
|
||||
modulo: "Modulo_1_Planificacion",
|
||||
nombre: "Planificacion",
|
||||
id: "s/34868",
|
||||
seccion: 34868,
|
||||
},
|
||||
{
|
||||
modulo: "Modulo_1_Planificacion",
|
||||
nombre: "Preparacion_Atletica",
|
||||
id: "s/34869",
|
||||
seccion: 34869,
|
||||
},
|
||||
{
|
||||
modulo: "Modulo_1_Planificacion",
|
||||
nombre: "Explicacion_TP",
|
||||
id: "s/34870",
|
||||
seccion: 34870,
|
||||
},
|
||||
{
|
||||
modulo: "Modulo_1_Planificacion",
|
||||
nombre: "Seguimientos_Equipos",
|
||||
id: "s/35523",
|
||||
seccion: 35523,
|
||||
},
|
||||
{
|
||||
modulo: "Modulo_1_Planificacion",
|
||||
nombre: "Analisis_Equipos_1",
|
||||
id: "s/35524",
|
||||
seccion: 35524,
|
||||
},
|
||||
{
|
||||
modulo: "Modulo_1_Planificacion",
|
||||
nombre: "Analisis_Equipos_2",
|
||||
id: "s/35525",
|
||||
seccion: 35525,
|
||||
},
|
||||
{
|
||||
modulo: "Modulo_1_Planificacion",
|
||||
nombre: "TP_Final_Unidad",
|
||||
id: "s/35526",
|
||||
seccion: 35526,
|
||||
},
|
||||
// Módulo 2 (m/19097)
|
||||
{
|
||||
modulo: "Modulo_2_Entrenamiento",
|
||||
nombre: "Centrales_y_Oponentes",
|
||||
id: "s/35519",
|
||||
seccion: 35519,
|
||||
},
|
||||
{
|
||||
modulo: "Modulo_2_Entrenamiento",
|
||||
nombre: "Puntas_y_Liberos",
|
||||
id: "s/35520",
|
||||
seccion: 35520,
|
||||
},
|
||||
{
|
||||
modulo: "Modulo_2_Entrenamiento",
|
||||
nombre: "Entrenamiento_Armadoras",
|
||||
id: "s/35521",
|
||||
seccion: 35521,
|
||||
},
|
||||
{
|
||||
modulo: "Modulo_2_Entrenamiento",
|
||||
nombre: "Explicacion_TP",
|
||||
id: "s/35522",
|
||||
seccion: 35522,
|
||||
},
|
||||
];
|
||||
|
||||
// IDs de los módulos (necesarios para construir las URLs)
|
||||
const MODULO_1 = "m/19096";
|
||||
const MODULO_2 = "m/19097";
|
||||
|
||||
const CAMPUS_BASE =
|
||||
"https://voley.onlineeducation.center/es/campus/student/training/e/14197";
|
||||
|
||||
// ============================================================
|
||||
// UTILIDADES
|
||||
// ============================================================
|
||||
|
||||
function ensureDir(dir) {
|
||||
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
||||
}
|
||||
|
||||
function log(msg) {
|
||||
const ts = new Date().toISOString().replace("T", " ").substring(0, 19);
|
||||
console.log(`[${ts}] ${msg}`);
|
||||
}
|
||||
|
||||
function sleep(ms) {
|
||||
return new Promise((r) => setTimeout(r, ms));
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// DESCARGA DE ARCHIVOS
|
||||
// ============================================================
|
||||
|
||||
/**
|
||||
* Descarga un archivo desde una URL usando el evento download de Playwright.
|
||||
* La URL corresponde a un enlace <a href=".../study-resources/{id}/show?me={me_id}">Abrir Archivo</a>
|
||||
* que inicia una descarga nativa del navegador.
|
||||
*
|
||||
* En lugar de navegar con page.goto() (que carga la respuesta como página),
|
||||
* buscamos el enlace por su href y hacemos click para disparar el evento download.
|
||||
*/
|
||||
async function downloadFile(page, url, filepath) {
|
||||
if (fs.existsSync(filepath)) {
|
||||
log(` ↺ Ya existe: ${path.basename(filepath)}`);
|
||||
return filepath;
|
||||
}
|
||||
|
||||
try {
|
||||
// Buscar el enlace en la página por su URL exacta
|
||||
const link = page.locator(`a[href="${url}"]`);
|
||||
const linkCount = await link.count();
|
||||
|
||||
if (linkCount === 0) {
|
||||
throw new Error(`No se encontró el enlace: ${url}`);
|
||||
}
|
||||
|
||||
// Configurar la espera del evento download antes de hacer click
|
||||
const [download] = await Promise.all([
|
||||
page.waitForEvent("download", { timeout: 30000 }),
|
||||
link.first().click(),
|
||||
]);
|
||||
|
||||
// Guardar el archivo descargado
|
||||
await download.saveAs(filepath);
|
||||
const suggestedName = download.suggestedFilename();
|
||||
const sizeKB = (fs.statSync(filepath).size / 1024).toFixed(1);
|
||||
log(
|
||||
` ✓ Descargado: ${path.basename(filepath)} (${sizeKB} KB) — original: ${suggestedName || "desconocido"}`,
|
||||
);
|
||||
return filepath;
|
||||
} catch (err) {
|
||||
log(` ✗ Error descarga: ${err.message}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Toma screenshot de la página actual.
|
||||
*/
|
||||
async function screenshot(page, name) {
|
||||
const dir = path.join(DOWNLOADS_DIR, "screenshots");
|
||||
ensureDir(dir);
|
||||
const filepath = path.join(dir, name);
|
||||
await page.screenshot({ path: filepath, fullPage: true });
|
||||
return filepath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extrae el contenido textual de la página como Markdown simple.
|
||||
*/
|
||||
async function extractTextAsMarkdown(page, titulo) {
|
||||
const text = await page.evaluate(() => {
|
||||
// Intentar extraer el contenido principal
|
||||
const main = document.querySelector(
|
||||
"main, .main-content, .content, #content, .panel-body, .tab-content",
|
||||
);
|
||||
if (!main) return "";
|
||||
|
||||
// Clonar para no modificar el DOM real
|
||||
const clone = main.cloneNode(true);
|
||||
|
||||
// Remover elementos no deseados
|
||||
clone
|
||||
.querySelectorAll(
|
||||
"script, style, nav, footer, header, .btn, .dropdown-menu",
|
||||
)
|
||||
.forEach((el) => el.remove());
|
||||
|
||||
// Obtener el texto
|
||||
return clone.innerText.trim();
|
||||
});
|
||||
|
||||
if (!text) return "";
|
||||
|
||||
const lines = text.split("\n").filter((l) => l.trim());
|
||||
const md = [
|
||||
`# ${titulo}`,
|
||||
"",
|
||||
`> Extraído el ${new Date().toISOString().split("T")[0]}`,
|
||||
"",
|
||||
...lines.map((l) => l.trim()),
|
||||
"",
|
||||
].join("\n");
|
||||
return md;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// PROCESAR UNA SECCIÓN DEL CURSO
|
||||
// ============================================================
|
||||
|
||||
async function procesarSeccion(page, seccion) {
|
||||
// Determinar qué módulo le corresponde
|
||||
const moduloPath = seccion.seccion >= 35519 ? MODULO_2 : MODULO_1;
|
||||
|
||||
// URL de la página de recursos de esta sección
|
||||
const seccionUrl = `${CAMPUS_BASE}/${moduloPath}/${seccion.id}/study-resources/list`;
|
||||
|
||||
const subdir = path.join("secciones", seccion.modulo, seccion.nombre);
|
||||
const dirLocal = path.join(DOWNLOADS_DIR, subdir);
|
||||
ensureDir(dirLocal);
|
||||
|
||||
log(` ── ${seccion.modulo}/${seccion.nombre}`);
|
||||
|
||||
try {
|
||||
await page.goto(seccionUrl, { waitUntil: "networkidle", timeout: 30000 });
|
||||
await sleep(2000);
|
||||
|
||||
// ---- 1. Guardar HTML completo ----
|
||||
const htmlContent = await page.content();
|
||||
fs.writeFileSync(path.join(dirLocal, "pagina.html"), htmlContent);
|
||||
|
||||
// ---- 2. Extraer texto como Markdown ----
|
||||
const md = await extractTextAsMarkdown(
|
||||
page,
|
||||
seccion.nombre.replace(/_/g, " "),
|
||||
);
|
||||
if (md) {
|
||||
fs.writeFileSync(path.join(dirLocal, "contenido.md"), md);
|
||||
} else {
|
||||
// Si no se encontró contenido principal, guardar todo el texto visible
|
||||
const fullText = await page.evaluate(() =>
|
||||
document.body.innerText.trim(),
|
||||
);
|
||||
if (fullText) {
|
||||
fs.writeFileSync(
|
||||
path.join(dirLocal, "contenido.md"),
|
||||
`# ${seccion.nombre.replace(/_/g, " ")}\n\n${fullText}\n`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ---- 3. Buscar recursos en la página ----
|
||||
const recursos = await page.evaluate((seccionUrlLocal) => {
|
||||
const items = [];
|
||||
|
||||
// Buscar enlaces a recursos/archivos del curso
|
||||
document.querySelectorAll("a[href]").forEach((el) => {
|
||||
const href = el.href;
|
||||
// Enlaces a archivos del curso (study-resources/{id}/show)
|
||||
if (href.includes("/study-resources/") && href.includes("/show")) {
|
||||
items.push({
|
||||
tipo: "archivo-curso",
|
||||
url: href,
|
||||
texto: el.textContent.trim(),
|
||||
id: href.match(/study-resources\/(\d+)\/show/)?.[1] || "",
|
||||
});
|
||||
}
|
||||
// PDFs directos
|
||||
else if (
|
||||
href.match(/\.(pdf|doc|docx|xls|xlsx|ppt|pptx|zip|rar)(\?|$)/i)
|
||||
) {
|
||||
items.push({
|
||||
tipo: "documento",
|
||||
url: href,
|
||||
texto: el.textContent.trim(),
|
||||
});
|
||||
}
|
||||
// Enlaces de descarga
|
||||
else if (
|
||||
href.includes("/download/") ||
|
||||
href.includes("download=") ||
|
||||
el.hasAttribute("download")
|
||||
) {
|
||||
items.push({
|
||||
tipo: "descarga",
|
||||
url: href,
|
||||
texto: el.textContent.trim(),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Buscar iframes (videos Vimeo/YouTube embebidos)
|
||||
document.querySelectorAll("iframe[src]").forEach((el) => {
|
||||
const src = el.src;
|
||||
if (
|
||||
src.includes("vimeo.com") ||
|
||||
src.includes("youtube.com") ||
|
||||
src.includes("youtu.be")
|
||||
) {
|
||||
const vimeoMatch = src.match(/vimeo\.com\/video\/(\d+)/);
|
||||
const youtubeMatch = src.match(
|
||||
/(?:youtube\.com\/embed\/|youtu\.be\/)([a-zA-Z0-9_-]+)/,
|
||||
);
|
||||
items.push({
|
||||
tipo: "video-embed",
|
||||
plataforma: src.includes("vimeo") ? "Vimeo" : "YouTube",
|
||||
url: src,
|
||||
id: vimeoMatch?.[1] || youtubeMatch?.[1] || "",
|
||||
titulo: el.title || "",
|
||||
});
|
||||
}
|
||||
// Otros iframes (posiblemente contenido útil)
|
||||
else {
|
||||
items.push({
|
||||
tipo: "iframe",
|
||||
url: src,
|
||||
titulo: el.title || "",
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Buscar videos directos
|
||||
document
|
||||
.querySelectorAll("video[src], video source[src]")
|
||||
.forEach((el) => {
|
||||
items.push({
|
||||
tipo: "video-directo",
|
||||
url: el.src || el.getAttribute("src") || "",
|
||||
});
|
||||
});
|
||||
|
||||
// Buscar imágenes relevantes (saltar iconos pequeños)
|
||||
document.querySelectorAll("img[src]").forEach((el) => {
|
||||
const src = el.src;
|
||||
if (
|
||||
src &&
|
||||
!src.includes("data:") &&
|
||||
!src.includes("user-default") &&
|
||||
!src.includes("logo")
|
||||
) {
|
||||
items.push({
|
||||
tipo: "imagen",
|
||||
url: src,
|
||||
alt: el.alt || "",
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Buscar contenido en etiquetas <embed> o <object>
|
||||
document.querySelectorAll("embed[src], object[data]").forEach((el) => {
|
||||
const src = el.src || el.getAttribute("data") || "";
|
||||
if (src) {
|
||||
items.push({
|
||||
tipo: "embed",
|
||||
url: src,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return items;
|
||||
});
|
||||
|
||||
// ---- 4. Guardar JSON de recursos encontrados ----
|
||||
if (recursos.length > 0) {
|
||||
fs.writeFileSync(
|
||||
path.join(dirLocal, "recursos.json"),
|
||||
JSON.stringify(recursos, null, 2),
|
||||
);
|
||||
}
|
||||
|
||||
log(
|
||||
` Recursos: ${recursos.length} (${recursos.filter((r) => r.tipo === "archivo-curso").length} archivos, ${recursos.filter((r) => r.tipo === "video-embed").length} videos)`,
|
||||
);
|
||||
|
||||
// ---- 5. Descargar archivos del curso (PDFs, docs, etc.) ----
|
||||
// Distinguir entre "Abrir Archivo" (descarga real de archivo) y
|
||||
// "Abrir Enlace" (página HTML que ya capturamos como pagina.html).
|
||||
const archivosCurso = recursos.filter((r) => r.tipo === "archivo-curso");
|
||||
for (let i = 0; i < archivosCurso.length; i++) {
|
||||
const archivo = archivosCurso[i];
|
||||
const texto = archivo.texto.toLowerCase();
|
||||
|
||||
// Saltar "Abrir Enlace": ya se capturó como HTML en pagina.html
|
||||
if (texto.includes("abrir enlace")) {
|
||||
log(
|
||||
` ↺ Saltando "${archivo.texto}" (${archivo.id}): ya capturado como HTML`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Solo procesar "Abrir Archivo" (descarga real)
|
||||
if (!texto.includes("abrir archivo")) {
|
||||
log(
|
||||
` ↺ Saltando "${archivo.texto}" (${archivo.id}): tipo de recurso no manejado`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
const ext = ".bin"; // placeholder, lo detectaremos del contenido
|
||||
const filename = `archivo_${archivo.id}_${archivo.texto.replace(/[^a-zA-Z0-9_-]/g, "_").substring(0, 30) || "documento"}${ext}`;
|
||||
const filepath = path.join(dirLocal, filename);
|
||||
|
||||
log(
|
||||
` Descargando archivo ${i + 1}/${archivosCurso.length}: "${archivo.texto}" (${archivo.id})...`,
|
||||
);
|
||||
const result = await downloadFile(page, archivo.url, filepath);
|
||||
|
||||
// Clasificar el archivo por su contenido (magic bytes) y renombrar
|
||||
if (result) {
|
||||
const stat = fs.statSync(result);
|
||||
if (stat.size > 0) {
|
||||
const buffer = fs.readFileSync(result);
|
||||
let newPath = null;
|
||||
|
||||
if (
|
||||
buffer[0] === 0x25 &&
|
||||
buffer[1] === 0x50 &&
|
||||
buffer[2] === 0x44 &&
|
||||
buffer[3] === 0x46
|
||||
) {
|
||||
// Es PDF
|
||||
newPath = result.replace(/\.bin$/, ".pdf");
|
||||
fs.renameSync(result, newPath);
|
||||
log(` ✓ Identificado como PDF: ${path.basename(newPath)}`);
|
||||
} else if (buffer[0] === 0x50 && buffer[1] === 0x4b) {
|
||||
// Es ZIP (posiblemente .docx, .xlsx, .pptx)
|
||||
newPath = result.replace(/\.bin$/, ".zip");
|
||||
fs.renameSync(result, newPath);
|
||||
log(
|
||||
` ⚠ Identificado como ZIP (Office doc?): ${path.basename(newPath)}`,
|
||||
);
|
||||
} else if (
|
||||
buffer[0] === 0x3c || // '<' posible HTML
|
||||
(buffer[0] === 0xef && buffer[1] === 0xbb && buffer[2] === 0xbf) || // BOM UTF-8
|
||||
buffer.toString("utf8", 0, 100).includes("<!DOCTYPE") ||
|
||||
buffer.toString("utf8", 0, 100).includes("<html")
|
||||
) {
|
||||
// Es HTML (enlace que se abrió como página en lugar de descarga)
|
||||
newPath = result.replace(/\.bin$/, ".html");
|
||||
fs.renameSync(result, newPath);
|
||||
log(
|
||||
` ⚠ Era HTML (posible "Abrir Enlace"): ${path.basename(newPath)}`,
|
||||
);
|
||||
} else {
|
||||
// Formato desconocido, mantener .bin
|
||||
log(` ⚠ Formato desconocido: ${path.basename(result)}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---- 6. Guardar referencias a videos ----
|
||||
const videos = recursos.filter((r) => r.tipo === "video-embed");
|
||||
if (videos.length > 0) {
|
||||
let videoMd = `# Videos - ${seccion.nombre.replace(/_/g, " ")}\n\n`;
|
||||
videoMd += `> Sección: ${seccionUrl}\n\n`;
|
||||
for (const v of videos) {
|
||||
videoMd += `## ${v.titulo || `Video ${v.plataforma} ID: ${v.id}`}\n\n`;
|
||||
videoMd += `- **Plataforma**: ${v.plataforma}\n`;
|
||||
videoMd += `- **URL**: ${v.url}\n`;
|
||||
videoMd += `- **ID**: ${v.id}\n\n`;
|
||||
if (v.plataforma === "Vimeo") {
|
||||
videoMd += `Para descargar: yt-dlp "https://player.vimeo.com/video/${v.id}"\n\n`;
|
||||
}
|
||||
}
|
||||
fs.writeFileSync(path.join(dirLocal, "videos.md"), videoMd);
|
||||
}
|
||||
|
||||
// ---- 7. Tomar screenshot de referencia ----
|
||||
await screenshot(page, `${seccion.modulo}_${seccion.nombre}.png`);
|
||||
|
||||
log(` ✓ Completado: ${seccion.nombre}`);
|
||||
|
||||
return { ok: true, recursos: recursos.length };
|
||||
} catch (err) {
|
||||
log(` ✗ Error en sección ${seccion.nombre}: ${err.message}`);
|
||||
try {
|
||||
await screenshot(page, `ERROR_${seccion.modulo}_${seccion.nombre}.png`);
|
||||
} catch (_) {}
|
||||
return { ok: false, error: err.message };
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// FUNCIÓN PRINCIPAL
|
||||
// ============================================================
|
||||
|
||||
async function main() {
|
||||
log("═══════════════════════════════════════════════════════");
|
||||
log(" DESCARGA DEL CURSO DE VOLEY - Online Education Center");
|
||||
log("═══════════════════════════════════════════════════════");
|
||||
log("");
|
||||
|
||||
// 1. Login
|
||||
log("Paso 1: Login en la plataforma...");
|
||||
const browser = await chromium.launch({
|
||||
headless: true,
|
||||
args: ["--no-sandbox", "--disable-setuid-sandbox"],
|
||||
});
|
||||
|
||||
const context = await browser.newContext({
|
||||
viewport: { width: 1280, height: 900 },
|
||||
locale: "es-AR",
|
||||
});
|
||||
|
||||
const page = await context.newPage();
|
||||
|
||||
const sessionFile = path.join(__dirname, "session.json");
|
||||
let loggedIn = false;
|
||||
let activeContext = context;
|
||||
let activePage = page;
|
||||
|
||||
try {
|
||||
if (fs.existsSync(sessionFile)) {
|
||||
log(" Cargando sesión guardada...");
|
||||
try {
|
||||
const sessionContext = await browser.newContext({
|
||||
storageState: sessionFile,
|
||||
});
|
||||
const sessionPage = await sessionContext.newPage();
|
||||
await sessionPage.goto(CAMPUS_BASE, {
|
||||
waitUntil: "networkidle",
|
||||
timeout: 20000,
|
||||
});
|
||||
const url = sessionPage.url();
|
||||
if (url.includes("/campus/")) {
|
||||
loggedIn = true;
|
||||
log(" ✓ Sesión recuperada exitosamente");
|
||||
// Cerrar page/context original, usar los nuevos
|
||||
await activePage.close();
|
||||
await activeContext.close();
|
||||
activePage = sessionPage;
|
||||
activeContext = sessionContext;
|
||||
} else {
|
||||
await sessionPage.close();
|
||||
await sessionContext.close();
|
||||
}
|
||||
} catch (err) {
|
||||
log(` ⚠ No se pudo reusar sesión: ${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (!loggedIn) {
|
||||
log(" Abriendo página de login...");
|
||||
await page.goto(
|
||||
"https://voley.onlineeducation.center/es/campus/student/training/e/14197/m/19097/s/35519/study-resources/list",
|
||||
{ waitUntil: "networkidle", timeout: 30000 },
|
||||
);
|
||||
|
||||
await page.waitForSelector("#login-email", { timeout: 15000 });
|
||||
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 });
|
||||
log(" ✓ Login exitoso");
|
||||
|
||||
// Guardar sesión para futuras ejecuciones
|
||||
await activeContext.storageState({ path: sessionFile });
|
||||
log(" ✓ Sesión guardada en session.json");
|
||||
}
|
||||
|
||||
// 2. Navegar cada sección del curso
|
||||
log("");
|
||||
log("Paso 2: Descargando contenido de cada sección...");
|
||||
log(`Total de secciones: ${SECCIONES.length}`);
|
||||
log("");
|
||||
|
||||
const resultados = [];
|
||||
for (let i = 0; i < SECCIONES.length; i++) {
|
||||
log(`[${i + 1}/${SECCIONES.length}] Procesando sección...`);
|
||||
const resultado = await procesarSeccion(activePage, SECCIONES[i]);
|
||||
resultados.push({
|
||||
seccion: SECCIONES[i].nombre,
|
||||
modulo: SECCIONES[i].modulo,
|
||||
...resultado,
|
||||
});
|
||||
log("");
|
||||
}
|
||||
|
||||
// 3. Resumen final
|
||||
log("═══════════════════════════════════════════════════════");
|
||||
log(" RESUMEN DE DESCARGA");
|
||||
log("═══════════════════════════════════════════════════════");
|
||||
log("");
|
||||
log(
|
||||
` Secciones procesadas: ${resultados.filter((r) => r.ok).length}/${resultados.length}`,
|
||||
);
|
||||
log(` Secciones con error: ${resultados.filter((r) => !r.ok).length}`);
|
||||
log("");
|
||||
|
||||
for (const r of resultados) {
|
||||
const icono = r.ok ? "✓" : "✗";
|
||||
const detalles = r.ok ? ` (${r.recursos} recursos)` : ` (${r.error})`;
|
||||
log(` ${icono} ${r.modulo}/${r.seccion}${detalles}`);
|
||||
}
|
||||
|
||||
log("");
|
||||
log(" Directorio de descarga:");
|
||||
log(` ${DOWNLOADS_DIR}`);
|
||||
log("");
|
||||
log(" Estructura creada:");
|
||||
log(" secciones/");
|
||||
log(" Modulo_1_Planificacion/");
|
||||
for (const sec of SECCIONES.filter(
|
||||
(s) => s.modulo === "Modulo_1_Planificacion",
|
||||
)) {
|
||||
log(
|
||||
` ${sec.nombre}/ → pagina.html, contenido.md, recursos.json, videos.md, screenshots`,
|
||||
);
|
||||
}
|
||||
log(" Modulo_2_Entrenamiento/");
|
||||
for (const sec of SECCIONES.filter(
|
||||
(s) => s.modulo === "Modulo_2_Entrenamiento",
|
||||
)) {
|
||||
log(
|
||||
` ${sec.nombre}/ → pagina.html, contenido.md, recursos.json, videos.md, screenshots`,
|
||||
);
|
||||
}
|
||||
log("");
|
||||
log(" Para descargar videos de Vimeo:");
|
||||
log(
|
||||
' yt-dlp --cookies session.json "https://player.vimeo.com/video/{ID}"',
|
||||
);
|
||||
log("");
|
||||
|
||||
// Guardar resultados
|
||||
const reportePath = path.join(DOWNLOADS_DIR, "resultado_descarga.json");
|
||||
fs.writeFileSync(reportePath, JSON.stringify(resultados, null, 2));
|
||||
log(` Reporte guardado: resultado_descarga.json`);
|
||||
|
||||
log("");
|
||||
log("═══════════════════════════════════════════════════════");
|
||||
} catch (err) {
|
||||
log(`\n✗ ERROR GENERAL: ${err.message}`);
|
||||
if (err.stack) log(err.stack);
|
||||
try {
|
||||
await screenshot(activePage, "ERROR_general.png");
|
||||
log(" Screenshot guardado");
|
||||
} catch (_) {}
|
||||
} finally {
|
||||
if (activeContext && activeContext !== context) {
|
||||
await activePage.close().catch(() => {});
|
||||
await activeContext.close().catch(() => {});
|
||||
} else {
|
||||
await page.close().catch(() => {});
|
||||
await context.close().catch(() => {});
|
||||
}
|
||||
await browser.close();
|
||||
log("Navegador cerrado.");
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
Reference in New Issue
Block a user