create-openclass-uniminuto 1.6.2 → 1.6.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,8 +3,15 @@
3
3
  *
4
4
  * Uso principal:
5
5
  * npm run config
6
+ * npm run config -- --force
6
7
  * node scripts/generar-desde-config.mjs --config config/openclass.config.iot-ejemplo.json --force
8
+ *
9
+ * Principio:
10
+ * - openclass.config.json es la fuente de verdad.
11
+ * - generation.activeWeeks define qué semanas aparecen en el portal, exportaciones y deploy.
12
+ * - No sobrescribe el contenido académico en semanas/*.md salvo con --force o overwriteWeekContent=true.
7
13
  */
14
+
8
15
  import fs from "node:fs";
9
16
  import path from "node:path";
10
17
 
@@ -13,8 +20,10 @@ const args = process.argv.slice(2);
13
20
  function valueAfter(flag, fallback) {
14
21
  const i = args.indexOf(flag);
15
22
  if (i >= 0 && args[i + 1]) return args[i + 1];
23
+
16
24
  const pair = args.find((arg) => arg.startsWith(`${flag}=`));
17
25
  if (pair) return pair.slice(flag.length + 1);
26
+
18
27
  return fallback;
19
28
  }
20
29
 
@@ -22,7 +31,18 @@ const configPath = valueAfter("--config", "openclass.config.json");
22
31
  const force = args.includes("--force") || args.includes("-f");
23
32
  const dryRun = args.includes("--dry-run");
24
33
  const forceCleanDemo = args.includes("--clean-demo");
25
- const FALLBACK_FAVICON_PNG_BASE64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+/p9sAAAAASUVORK5CYII=";
34
+
35
+ const FALLBACK_FAVICON_PNG_BASE64 =
36
+ "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+/p9sAAAAASUVORK5CYII=";
37
+
38
+ const REQUIRED_THEME_ASSETS = [
39
+ "public/fondos/slide-01-portada.png",
40
+ "public/fondos/slide-05-template.png",
41
+ "public/fondos/slide-06-cierre.png",
42
+ "public/imagenes/avion.png",
43
+ "public/imagenes/favicon.png",
44
+ "public/favicon.png",
45
+ ];
26
46
 
27
47
  function fail(message) {
28
48
  console.error(`\n❌ ${message}\n`);
@@ -30,7 +50,10 @@ function fail(message) {
30
50
  }
31
51
 
32
52
  function readJson(filePath) {
33
- if (!fs.existsSync(filePath)) fail(`No se encontró el archivo de configuración: ${filePath}`);
53
+ if (!fs.existsSync(filePath)) {
54
+ fail(`No se encontró el archivo de configuración: ${filePath}`);
55
+ }
56
+
34
57
  try {
35
58
  return JSON.parse(fs.readFileSync(filePath, "utf-8"));
36
59
  } catch (error) {
@@ -67,6 +90,7 @@ function writeBinaryFileIfMissing(filePath, bufferOrBase64, { label = filePath }
67
90
  const buffer = Buffer.isBuffer(bufferOrBase64)
68
91
  ? bufferOrBase64
69
92
  : Buffer.from(bufferOrBase64, "base64");
93
+
70
94
  fs.writeFileSync(filePath, buffer);
71
95
  }
72
96
 
@@ -76,8 +100,10 @@ function writeBinaryFileIfMissing(filePath, bufferOrBase64, { label = filePath }
76
100
 
77
101
  function removeFileIfExists(filePath, { label = filePath } = {}) {
78
102
  if (!fs.existsSync(filePath)) return false;
103
+
79
104
  if (!dryRun) fs.rmSync(filePath, { force: true });
80
105
  console.log(`${dryRun ? "🔎" : "🧹"} Eliminado: ${label}`);
106
+
81
107
  return true;
82
108
  }
83
109
 
@@ -94,44 +120,66 @@ function asArray(value) {
94
120
  return Array.isArray(value) ? value : [];
95
121
  }
96
122
 
123
+ function uniqueNumbers(values) {
124
+ return [...new Set(values.map(Number))]
125
+ .filter((value) => Number.isInteger(value))
126
+ .sort((a, b) => a - b);
127
+ }
128
+
97
129
  function replaceTokens(template, tokens) {
98
- return Object.entries(tokens).reduce(
99
- (content, [key, value]) => content.replaceAll(`{{${key}}}`, String(value ?? "")),
100
- template,
101
- );
130
+ return Object.entries(tokens).reduce((content, [key, value]) => {
131
+ return content.replaceAll(`{{${key}}}`, String(value ?? ""));
132
+ }, template);
102
133
  }
103
134
 
104
135
  function readTemplate(filePath, fallback) {
105
136
  return fs.existsSync(filePath) ? fs.readFileSync(filePath, "utf-8") : fallback;
106
137
  }
107
138
 
108
- function normalizeBase(value) {
109
- let base = value || "/";
110
- if (!base.startsWith("/")) base = `/${base}`;
111
- if (!base.endsWith("/")) base = `${base}/`;
112
- return base;
113
- }
114
-
115
139
  const config = readJson(configPath);
116
140
  const course = config.course || {};
117
141
  const generation = config.generation || {};
118
142
 
119
- const courseShort = cleanShortName(course.shortName || config.shortName || config.courseCode || "curso");
120
- const courseName = String(course.fullName || course.name || config.courseName || "Nombre del curso").trim();
143
+ const courseShort = cleanShortName(
144
+ course.shortName || config.shortName || config.courseCode || "curso",
145
+ );
146
+
147
+ const courseName = String(
148
+ course.fullName || course.name || config.courseName || "Nombre del curso",
149
+ ).trim();
150
+
121
151
  const courseYear = String(course.year || config.year || new Date().getFullYear()).trim();
122
- const description = String(course.description || config.description || "Agrega aquí la descripción general del curso.").trim();
152
+
153
+ const description = String(
154
+ course.description || config.description || "Agrega aquí la descripción general del curso.",
155
+ ).trim();
156
+
123
157
  const openClassLabel = String(course.openClassLabel || "Open Class").trim();
124
- const weeksTotal = Number(generation.weeksTotal || config.weeksTotal || config.totalWeeks || asArray(config.weeks).length || 8);
158
+
159
+ const weeksTotal = Number(
160
+ generation.weeksTotal ||
161
+ config.weeksTotal ||
162
+ config.totalWeeks ||
163
+ asArray(config.weeks).length ||
164
+ 8,
165
+ );
125
166
 
126
167
  if (!courseShort) fail("El campo course.shortName es obligatorio.");
127
168
  if (!courseName) fail("El campo course.fullName es obligatorio.");
128
- if (!Number.isInteger(weeksTotal) || weeksTotal < 1) fail("generation.weeksTotal debe ser un número entero mayor o igual a 1.");
169
+ if (!Number.isInteger(weeksTotal) || weeksTotal < 1) {
170
+ fail("generation.weeksTotal debe ser un número entero mayor o igual a 1.");
171
+ }
129
172
 
130
- const configuredWeeks = new Map(asArray(config.weeks).map((week) => [Number(week.number), week]));
173
+ const configuredWeeks = new Map(
174
+ asArray(config.weeks)
175
+ .filter((week) => Number.isInteger(Number(week.number)))
176
+ .map((week) => [Number(week.number), week]),
177
+ );
131
178
 
132
179
  function weekInfo(number) {
133
180
  const week = configuredWeeks.get(number) || {};
134
181
  const title = String(week.title || `Título semana ${number}`).trim();
182
+
135
183
  return {
136
184
  number,
137
185
  title,
@@ -144,12 +192,17 @@ function weekInfo(number) {
144
192
  }
145
193
 
146
194
  const allWeeks = Array.from({ length: weeksTotal }, (_, i) => weekInfo(i + 1));
147
- const activeWeeksRaw = asArray(generation.activeWeeks || config.activeWeeks);
195
+
196
+ const activeWeeksRaw = asArray(generation.activeWeeks ?? config.activeWeeks);
197
+
148
198
  const activeWeekNumbers = activeWeeksRaw.length
149
- ? activeWeeksRaw.map(Number).filter((n) => Number.isInteger(n) && n >= 1 && n <= weeksTotal)
150
- : allWeeks.filter((week) => week.status !== "draft" && week.status !== "inactive").map((week) => week.number);
199
+ ? uniqueNumbers(activeWeeksRaw).filter((number) => number >= 1 && number <= weeksTotal)
200
+ : allWeeks
201
+ .filter((week) => week.status !== "draft" && week.status !== "inactive")
202
+ .map((week) => week.number);
151
203
 
152
204
  const activeWeeks = allWeeks.filter((week) => activeWeekNumbers.includes(week.number));
205
+
153
206
  const overwriteLaunchers = generation.overwriteLaunchers !== false || force;
154
207
  const overwritePortal = generation.overwritePortal !== false || force;
155
208
  const overwriteDecks = generation.overwriteDecks !== false || force;
@@ -159,12 +212,49 @@ const exportPortal = generation.exportPortal === true;
159
212
 
160
213
  const semanaTemplate = readTemplate(
161
214
  "plantillas/semana.md",
162
- `---\nlayout: slide-01-portada\n---\n\n::title::\n{{COURSE_NAME}}\n\n::week::\nSemana {{WEEK_NUMBER}} — {{WEEK_TITLE}}\n\n::date::\n{{WEEK_DATE}}\n\n---\nlayout: slide-12-cierre\n---\n`,
215
+ `---
216
+ layout: slide-01-portada
217
+ ---
218
+
219
+ ::title::
220
+ {{COURSE_NAME}}
221
+
222
+ ::week::
223
+ Semana {{WEEK_NUMBER}} — {{WEEK_TITLE}}
224
+
225
+ ::date::
226
+ {{WEEK_DATE}}
227
+
228
+ ---
229
+ layout: slide-08-titulo-texto
230
+ ---
231
+
232
+ ::title::
233
+ Tema central
234
+
235
+ ::content::
236
+ {{WEEK_THEME}}
237
+
238
+ ---
239
+ layout: slide-12-cierre
240
+ ---
241
+ `,
163
242
  );
164
243
 
165
244
  const launcherTemplate = readTemplate(
166
245
  "plantillas/launcher.md",
167
- `---\ntheme: ./theme/uniminuto\ntitle: {{COURSE_NAME}} — Semana {{WEEK_NUMBER}} — {{WEEK_TITLE}}\nfavicon: /favicon.png\ncodeCopy: true\ntransition: fade\nrouterMode: hash\ndrawings:\n persist: false\nsrc: ./semanas/{{COURSE_SHORT}}_semana{{WEEK_NUMBER}}.md\n---\n`,
246
+ `---
247
+ theme: ./theme/uniminuto
248
+ title: {{COURSE_NAME}} — Semana {{WEEK_NUMBER}} — {{WEEK_TITLE}}
249
+ favicon: /favicon.png
250
+ codeCopy: true
251
+ transition: fade
252
+ routerMode: hash
253
+ drawings:
254
+ persist: false
255
+ src: ./semanas/{{COURSE_SHORT}}_semana{{WEEK_NUMBER}}.md
256
+ ---
257
+ `,
168
258
  );
169
259
 
170
260
  function weekTokens(week) {
@@ -184,29 +274,142 @@ function weekTokens(week) {
184
274
 
185
275
  function portalWeekItem(week) {
186
276
  const slug = `${courseShort}_semana${week.number}`;
187
- return `### **Semana ${week.number}**\n\n<a href="./semanas/${slug}/#/1" target="_self">${week.title}</a>\n\n<a href="./descargas/${slug}.pdf" download>Descargar PDF</a> · <a href="./descargas/${slug}.pptx" download>Descargar PPTX</a>`;
277
+
278
+ return `### **Semana ${week.number}**
279
+
280
+ <a href="./semanas/${slug}/#/1" target="_self">${week.title}</a>
281
+
282
+ <a href="./descargas/${slug}.pdf" download>Descargar PDF</a> · <a href="./descargas/${slug}.pptx" download>Descargar PPTX</a>`;
188
283
  }
189
284
 
190
285
  function buildPortal() {
286
+ const midpoint = Math.ceil(activeWeeks.length / 2);
287
+
191
288
  const left = activeWeeks.length
192
- ? activeWeeks.filter((_, index) => index < Math.ceil(activeWeeks.length / 2)).map(portalWeekItem).join("\n\n")
289
+ ? activeWeeks
290
+ .filter((_, index) => index < midpoint)
291
+ .map(portalWeekItem)
292
+ .join("\n\n")
193
293
  : "### Sin semanas activas\n\nEjecuta `npm run semana -- 1` para generar la primera semana.";
194
- const right = activeWeeks.filter((_, index) => index >= Math.ceil(activeWeeks.length / 2)).map(portalWeekItem).join("\n\n") || "### Próximamente\n\nActiva más semanas con `npm run semana -- 2`, `npm run semana -- 3` y así sucesivamente.";
195
294
 
196
- return `---\ntheme: ./theme/uniminuto\ntitle: ${courseName} — ${openClassLabel}\nfavicon: /favicon.png\ncodeCopy: true\ntransition: fade\nrouterMode: hash\ndrawings:\n persist: false\nlayout: slide-01-portada\n---\n\n::title::\n${courseName}\n\n::week::\n${openClassLabel}\n\n::date::\n${courseYear}\n\n---\nlayout: slide-08-titulo-texto\n---\n\n::title::\nDescripción general del curso\n\n::content::\n${description}\n\n---\nlayout: slide-08-titulo-texto\n---\n\n::title::\nRuta de aprendizaje\n\n::content::\nEl curso se organiza en semanas. Cada semana cuenta con un lanzador raíz y un archivo interno en la carpeta \`semanas/\`.\n\nLas presentaciones activas se controlan desde \`openclass.config.json\` mediante el campo \`generation.activeWeeks\`.\n\n---\nlayout: slide-10-titulo-dos-columnas\n---\n\n::title::\nPresentaciones disponibles\n\n::left::\n${left}\n\n::right::\n${right}\n\n---\nlayout: slide-12-cierre\n---\n`;
295
+ const right =
296
+ activeWeeks
297
+ .filter((_, index) => index >= midpoint)
298
+ .map(portalWeekItem)
299
+ .join("\n\n") ||
300
+ "### Próximamente\n\nActiva más semanas con `npm run semana -- 2`, `npm run semana -- 3` y así sucesivamente.";
301
+
302
+ return `---
303
+ theme: ./theme/uniminuto
304
+ title: ${courseName} — ${openClassLabel}
305
+ favicon: /favicon.png
306
+ codeCopy: true
307
+ transition: fade
308
+ routerMode: hash
309
+ drawings:
310
+ persist: false
311
+ layout: slide-01-portada
312
+ ---
313
+
314
+ ::title::
315
+ ${courseName}
316
+
317
+ ::week::
318
+ ${openClassLabel}
319
+
320
+ ::date::
321
+ ${courseYear}
322
+
323
+ ---
324
+ layout: slide-08-titulo-texto
325
+ ---
326
+
327
+ ::title::
328
+ Descripción general del curso
329
+
330
+ ::content::
331
+ ${description}
332
+
333
+ ---
334
+ layout: slide-08-titulo-texto
335
+ ---
336
+
337
+ ::title::
338
+ Ruta de aprendizaje
339
+
340
+ ::content::
341
+ El curso se organiza en semanas. Cada semana cuenta con un lanzador raíz y un archivo interno en la carpeta \`semanas/\`.
342
+
343
+ Las presentaciones activas se controlan desde \`openclass.config.json\` mediante el campo \`generation.activeWeeks\`.
344
+
345
+ ---
346
+ layout: slide-10-titulo-dos-columnas
347
+ ---
348
+
349
+ ::title::
350
+ Presentaciones disponibles
351
+
352
+ ::left::
353
+ ${left}
354
+
355
+ ::right::
356
+ ${right}
357
+
358
+ ---
359
+ layout: slide-12-cierre
360
+ ---
361
+ `;
197
362
  }
198
363
 
199
364
  function buildDecks() {
200
365
  const entries = [
201
- `function normalizeBase(value) {\n let base = value || "/";\n if (!base.startsWith("/")) base = \`/\${base}\`;\n if (!base.endsWith("/")) base = \`\${base}/\`;\n return base;\n}\n\nconst SITE_BASE = normalizeBase(process.env.SITE_BASE || "/");\n\nfunction withBase(path = "") {\n return \`\${SITE_BASE}\${path.replace(/^\\/+/, "")}\`;\n}\n\nexport const decks = [\n {\n name: "openclass-${courseShort}",\n entry: "slides.md",\n out: "dist",\n base: SITE_BASE,\n exportable: ${exportPortal ? "true" : "false"},\n },`,
366
+ `function normalizeBase(value) {
367
+ let base = value || "/";
368
+
369
+ if (!base.startsWith("/")) {
370
+ base = \`/\${base}\`;
371
+ }
372
+
373
+ if (!base.endsWith("/")) {
374
+ base = \`\${base}/\`;
375
+ }
376
+
377
+ return base;
378
+ }
379
+
380
+ const SITE_BASE = normalizeBase(process.env.SITE_BASE || "/");
381
+
382
+ function withBase(value = "") {
383
+ return \`\${SITE_BASE}\${value.replace(/^[/]+/, "")}\`;
384
+ }
385
+
386
+ export const decks = [
387
+ {
388
+ name: "openclass-${courseShort}",
389
+ entry: "slides.md",
390
+ out: "dist",
391
+ base: SITE_BASE,
392
+ exportable: ${exportPortal ? "true" : "false"},
393
+ },`,
202
394
  ];
203
395
 
204
396
  for (const week of activeWeeks) {
205
397
  const slug = `${courseShort}_semana${week.number}`;
206
- entries.push(` {\n name: "${slug}",\n entry: "${slug}.md",\n out: "dist/semanas/${slug}",\n base: withBase("semanas/${slug}/"),\n exportable: true,\n },`);
398
+
399
+ entries.push(` {
400
+ name: "${slug}",
401
+ entry: "${slug}.md",
402
+ out: "dist/semanas/${slug}",
403
+ base: withBase("semanas/${slug}/"),
404
+ exportable: true,
405
+ },`);
207
406
  }
208
407
 
209
- entries.push(`];\n`);
408
+ entries.push(`];
409
+
410
+ export default decks;
411
+ `);
412
+
210
413
  return entries.join("\n");
211
414
  }
212
415
 
@@ -217,7 +420,7 @@ function buildScripts() {
217
420
  semana: "node scripts/semana.mjs",
218
421
  "pages:check": "node scripts/preparar-github-pages.mjs",
219
422
  "actualizar:tema": "npm create openclass-uniminuto@latest . -- --update-theme",
220
- "actualizar:tema:preview": "npm run actualizar:tema && npm run dev",
423
+ "actualizar:tema:preview": "npm run actualizar:tema && npm run config && npm run dev",
221
424
  };
222
425
 
223
426
  for (let i = 1; i <= weeksTotal; i++) {
@@ -226,27 +429,38 @@ function buildScripts() {
226
429
  }
227
430
 
228
431
  scripts.clean = "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"";
229
- scripts["clean:downloads"] = "node -e \"require('fs').rmSync('public/descargas',{recursive:true,force:true}); require('fs').mkdirSync('public/descargas',{recursive:true})\"";
230
- scripts["clean:cache"] = "node -e \"require('fs').rmSync('.slidev',{recursive:true,force:true}); require('fs').rmSync('node_modules/.vite',{recursive:true,force:true})\"";
432
+ scripts["clean:downloads"] =
433
+ "node -e \"require('fs').rmSync('public/descargas',{recursive:true,force:true}); require('fs').mkdirSync('public/descargas',{recursive:true})\"";
434
+ scripts["clean:cache"] =
435
+ "node -e \"require('fs').rmSync('.slidev',{recursive:true,force:true}); require('fs').rmSync('node_modules/.vite',{recursive:true,force:true})\"";
436
+
231
437
  scripts["build:index"] = "slidev build slides.md --out dist --base / --without-notes";
232
438
 
233
439
  for (let i = 1; i <= weeksTotal; i++) {
234
440
  const slug = `${courseShort}_semana${i}`;
235
- scripts[`build:s${i}`] = `slidev build ${slug}.md --out dist/semanas/${slug} --base /semanas/${slug}/ --without-notes`;
441
+ scripts[`build:s${i}`] =
442
+ `slidev build ${slug}.md --out dist/semanas/${slug} --base /semanas/${slug}/ --without-notes`;
236
443
  }
237
444
 
238
445
  scripts["build:all"] = "node scripts/build-site.mjs";
239
446
  scripts["build:incremental"] = "node scripts/build-incremental.mjs";
447
+
240
448
  scripts["export:downloads"] = "node scripts/export-downloads.mjs";
241
449
  scripts["export:incremental"] = "node scripts/export-incremental.mjs";
242
- scripts.vista = "npm run clean && npm run export:downloads && npm run build:all && http-server dist -p 4173 -c-1 -o";
450
+
451
+ scripts.vista =
452
+ "npm run config && npm run clean && npm run export:downloads && npm run build:all && http-server dist -p 4173 -c-1 -o";
453
+
243
454
  scripts.publicar = "node scripts/publicar.mjs";
244
455
  scripts["dev:todo"] = "node scripts/dev-all.mjs";
245
456
  scripts.nuevo = "node scripts/nuevo-curso.mjs";
246
457
  scripts["zip:template"] = "node scripts/zip-template.mjs";
247
- scripts["preview:static"] = "npm run build:all && http-server dist -p 4173 -c-1";
248
- scripts["preview:pages"] = "npm run export:downloads && npm run build:all && http-server dist -p 4173 -c-1";
249
- scripts["pages:build"] = "npm run export:downloads && npm run build:all";
458
+
459
+ scripts["preview:static"] = "npm run config && npm run build:all && http-server dist -p 4173 -c-1";
460
+ scripts["preview:pages"] =
461
+ "npm run config && npm run export:downloads && npm run build:all && http-server dist -p 4173 -c-1";
462
+
463
+ scripts["pages:build"] = "npm run config && npm run export:downloads && npm run build:all";
250
464
  scripts["pages:preview"] = "npm run preview:pages";
251
465
 
252
466
  return scripts;
@@ -254,10 +468,21 @@ function buildScripts() {
254
468
 
255
469
  function ensureProjectAssets() {
256
470
  ensureDir("public");
471
+ ensureDir("public/fondos");
257
472
  ensureDir("public/descargas");
258
473
  ensureDir("public/imagenes");
259
474
  ensureDir("public/videos");
260
475
 
476
+ writeFileSafe("public/descargas/.gitkeep", "", {
477
+ overwrite: false,
478
+ label: "public/descargas/.gitkeep",
479
+ });
480
+
481
+ writeFileSafe("public/videos/.gitkeep", "", {
482
+ overwrite: false,
483
+ label: "public/videos/.gitkeep",
484
+ });
485
+
261
486
  const rootFavicon = "public/favicon.png";
262
487
  const imageFavicon = "public/imagenes/favicon.png";
263
488
 
@@ -272,20 +497,39 @@ function ensureProjectAssets() {
272
497
  }
273
498
 
274
499
  if (!fs.existsSync(rootFavicon) && !fs.existsSync(imageFavicon)) {
275
- writeBinaryFileIfMissing(rootFavicon, FALLBACK_FAVICON_PNG_BASE64, { label: rootFavicon });
276
- writeBinaryFileIfMissing(imageFavicon, FALLBACK_FAVICON_PNG_BASE64, { label: imageFavicon });
500
+ writeBinaryFileIfMissing(rootFavicon, FALLBACK_FAVICON_PNG_BASE64, {
501
+ label: rootFavicon,
502
+ });
503
+
504
+ writeBinaryFileIfMissing(imageFavicon, FALLBACK_FAVICON_PNG_BASE64, {
505
+ label: imageFavicon,
506
+ });
507
+ }
508
+
509
+ const missingAssets = REQUIRED_THEME_ASSETS.filter((asset) => !fs.existsSync(asset));
510
+
511
+ if (missingAssets.length) {
512
+ console.log("\n⚠️ Recursos visuales obligatorios faltantes:");
513
+ for (const asset of missingAssets) {
514
+ console.log(` - ${asset}`);
515
+ }
516
+ console.log(" El proyecto puede generarse, pero algunas diapositivas podrían verse incompletas.\n");
277
517
  }
278
518
  }
279
519
 
280
520
  function cleanLegacyDemoFiles() {
281
521
  const shouldClean = forceCleanDemo || courseShort !== "demo";
522
+
282
523
  if (!shouldClean) {
283
524
  console.log("⏭ Archivos demo conservados.");
284
525
  return;
285
526
  }
286
527
 
287
528
  for (let i = 1; i <= 8; i += 1) {
288
- removeFileIfExists(`demo_semana${i}.md`, { label: `demo_semana${i}.md` });
529
+ removeFileIfExists(`demo_semana${i}.md`, {
530
+ label: `demo_semana${i}.md`,
531
+ });
532
+
289
533
  removeFileIfExists(path.join("semanas", `demo_semana${i}.md`), {
290
534
  label: path.join("semanas", `demo_semana${i}.md`),
291
535
  });
@@ -299,22 +543,31 @@ console.log(`Configuración : ${configPath}`);
299
543
  console.log(`Curso : ${courseName}`);
300
544
  console.log(`Nombre corto : ${courseShort}`);
301
545
  console.log(`Semanas : ${weeksTotal}`);
302
- console.log(`Activas : ${activeWeeks.map((w) => w.number).join(", ") || "ninguna"}\n`);
546
+ console.log(`Activas : ${activeWeeks.map((week) => week.number).join(", ") || "ninguna"}\n`);
303
547
 
304
548
  ensureDir("semanas");
305
549
  ensureProjectAssets();
306
550
  cleanLegacyDemoFiles();
307
551
 
308
- writeFileSafe("slides.md", buildPortal(), { overwrite: overwritePortal, label: "slides.md" });
309
- writeFileSafe("scripts/decks.mjs", buildDecks(), { overwrite: overwriteDecks, label: "scripts/decks.mjs" });
552
+ writeFileSafe("slides.md", buildPortal(), {
553
+ overwrite: overwritePortal,
554
+ label: "slides.md",
555
+ });
556
+
557
+ writeFileSafe("scripts/decks.mjs", buildDecks(), {
558
+ overwrite: overwriteDecks,
559
+ label: "scripts/decks.mjs",
560
+ });
310
561
 
311
562
  for (const week of allWeeks) {
312
563
  const slug = `${courseShort}_semana${week.number}`;
313
564
  const tokens = weekTokens(week);
565
+
314
566
  writeFileSafe(`${slug}.md`, replaceTokens(launcherTemplate, tokens), {
315
567
  overwrite: overwriteLaunchers,
316
568
  label: `${slug}.md`,
317
569
  });
570
+
318
571
  writeFileSafe(path.join("semanas", `${slug}.md`), replaceTokens(semanaTemplate, tokens), {
319
572
  overwrite: overwriteWeekContent,
320
573
  label: path.join("semanas", `${slug}.md`),
@@ -323,20 +576,31 @@ for (const week of allWeeks) {
323
576
 
324
577
  if (overwritePackageScripts && fs.existsSync("package.json")) {
325
578
  const pkg = JSON.parse(fs.readFileSync("package.json", "utf-8"));
579
+
326
580
  pkg.name = `openclass-${courseShort}`;
327
581
  pkg.description = `Presentaciones Open Class del curso ${courseName}.`;
328
582
  pkg.scripts = buildScripts();
329
- writeFileSafe("package.json", JSON.stringify(pkg, null, 2) + "\n", { overwrite: true, label: "package.json" });
583
+
584
+ writeFileSafe("package.json", JSON.stringify(pkg, null, 2) + "\n", {
585
+ overwrite: true,
586
+ label: "package.json",
587
+ });
330
588
  }
331
589
 
332
590
  if (fs.existsSync("package-lock.json")) {
333
591
  try {
334
592
  const lock = JSON.parse(fs.readFileSync("package-lock.json", "utf-8"));
593
+
335
594
  lock.name = `openclass-${courseShort}`;
595
+
336
596
  if (lock.packages && lock.packages[""]) {
337
597
  lock.packages[""].name = `openclass-${courseShort}`;
338
598
  }
339
- writeFileSafe("package-lock.json", JSON.stringify(lock, null, 2) + "\n", { overwrite: true, label: "package-lock.json" });
599
+
600
+ writeFileSafe("package-lock.json", JSON.stringify(lock, null, 2) + "\n", {
601
+ overwrite: true,
602
+ label: "package-lock.json",
603
+ });
340
604
  } catch {
341
605
  console.log("⚠️ package-lock.json no se actualizó porque no parece ser JSON válido.");
342
606
  }
@@ -345,4 +609,4 @@ if (fs.existsSync("package-lock.json")) {
345
609
  console.log("\n✅ Configuración generada.");
346
610
  console.log(" Próximo paso sugerido: npm install");
347
611
  console.log(" Vista de una semana: npm run dev:s1");
348
- console.log(" Vista completa: npm run vista\n");
612
+ console.log(" Vista completa: npm run vista\n");
@@ -58,7 +58,7 @@ Presentaciones disponibles
58
58
  ::right::
59
59
  ### Próximamente
60
60
 
61
- Activa más semanas en `openclass.config.json` y ejecuta `npm run config`.
61
+ Activa más semanas con `npm run semana -- 2`, `npm run semana -- 3` y así sucesivamente.
62
62
 
63
63
  ---
64
64
  layout: slide-12-cierre