@icarusmx/creta 1.4.19 → 1.5.3

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.
package/bin/creta.js CHANGED
@@ -1,40 +1,1661 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { runCLI } from '../lib/cli/index.js'
3
+ import { createInterface } from 'readline'
4
+ import { execSync } from 'child_process'
5
+ import fs from 'fs'
6
+ import path from 'path'
7
+ import { fileURLToPath } from 'url'
8
+ import { CretaCodeSession } from '../lib/session.js'
9
+ import { PullRequestTutorial } from '../lib/pr-tutorial.js'
10
+ import { CommandHelpExecutor } from '../lib/executors/CommandHelpExecutor.js'
11
+ import { PapersExecutor } from '../lib/papers/PapersExecutor.js'
12
+ import { LessonBuilder } from '../lib/builders/LessonBuilder.js'
13
+ import { LESSON_1_SYSTEM_DECOMPOSITION } from '../lib/data/lessons/lesson1-system-decomposition.js'
14
+ import { LESSON_2_OBJECT_REQUESTS } from '../lib/data/lessons/lesson2-object-requests.js'
15
+ import { LESSON_3_ONLY_WAY } from '../lib/data/lessons/lesson3-only-way.js'
16
+ import { LESSON_4_OPERATION_SIGNATURES } from '../lib/data/lessons/lesson4-operation-signatures.js'
17
+ import { LESSON_5_INTERFACE_SET } from '../lib/data/lessons/lesson5-interface-set.js'
18
+ import { LESSON_6_INTERFACE_DESIGN } from '../lib/data/lessons/lesson6-interface-design.js'
19
+ import { LESSON_7_OBJECT_DEFINITION } from '../lib/data/lessons/lesson7-object-definition.js'
4
20
 
5
- // Setup process exit handlers for sandbox cleanup
6
- function setupExitHandlers() {
7
- const cleanup = () => {
8
- // Cleanup will be handled by individual SandboxManager instances
9
- // and by the cleanupOldSessions call on startup
21
+ const ENUNCIADOS = [
22
+ {
23
+ id: 1,
24
+ texto: "La parte difícil del diseño orientado a objetos es descomponer un sistema como un conjunto de objetos que interactúan entre sí.",
25
+ nivel: "Fundacional",
26
+ enfoque: "Descomposición de sistemas"
27
+ },
28
+ {
29
+ id: 2,
30
+ texto: "Los objetos interactúan entre sí a través de solicitudes.",
31
+ nivel: "Interacción",
32
+ enfoque: "Comunicación entre objetos"
33
+ },
34
+ {
35
+ id: 3,
36
+ texto: "Las solicitudes son la única forma de conseguir que un objeto lleve a cabo una operación.",
37
+ nivel: "Operaciones",
38
+ enfoque: "Mecanismo de ejecución"
39
+ },
40
+ {
41
+ id: 4,
42
+ texto: "Cada operación declarada por un objeto debe incluir:\na) nombre de la operación\nb) insumos necesarios para realizar la operación\nc) el valor que regresa tras ejecutar la operación\n\nEstos tres elementos constituyen la firma de operación.",
43
+ nivel: "Firmas",
44
+ enfoque: "Contratos de operación"
45
+ },
46
+ {
47
+ id: 5,
48
+ texto: "La interfaz de un objeto es el conjunto de todas sus firmas de operación.",
49
+ nivel: "Interfaces",
50
+ enfoque: "Definición de contratos"
51
+ },
52
+ {
53
+ id: 6,
54
+ texto: "El énfasis al diseñar los objetos debe estar en la definición de sus solicitudes e interfaces.",
55
+ nivel: "Diseño",
56
+ enfoque: "Metodología de desarrollo"
57
+ },
58
+ {
59
+ id: 7,
60
+ texto: "Un objeto es un conjunto de datos y procedimientos que operan sobre esos datos.",
61
+ nivel: "Definición",
62
+ enfoque: "Naturaleza del objeto"
10
63
  }
64
+ ]
11
65
 
12
- // Handle Ctrl+C
13
- process.on('SIGINT', () => {
14
- cleanup()
15
- process.exit(0)
66
+ const args = process.argv.slice(2)
67
+ const command = args[0]
68
+
69
+ // Check if this is a command help request (Phase 2: Desarrollador mode)
70
+ if (command && isCommandHelpRequest(args)) {
71
+ const executor = new CommandHelpExecutor(args)
72
+ await executor.execute()
73
+ process.exit(0)
74
+ }
75
+
76
+ // ASCII Logo
77
+ console.log(`
78
+ █████████████████████████
79
+ █ █ █ █ █ █ █
80
+ █ C █ R █ E █ T █ A █ █ █
81
+ █ █ █ █ █ █ █
82
+ █████████████████████████
83
+
84
+ Bienvenido a la escuela de software de icarus.mx
85
+ Salgamos de este laberinto 🏛️
86
+ `)
87
+
88
+ if (!command) {
89
+ // Default behavior: show main menu
90
+ await startMainMenu()
91
+ process.exit(0)
92
+ }
93
+
94
+ if (command.startsWith('portafolio')) {
95
+ const level = command === 'portafolio' ? 0 : parseInt(command.split('-')[1]) || 0
96
+
97
+ // Check if we're in an existing Creta project
98
+ if (level > 0 && isInCretaProject()) {
99
+ await unstuckProject(level)
100
+ } else {
101
+ await createPortfolioProject(level)
102
+ }
103
+ } else if (command === 'enunciados') {
104
+ // Start enunciados selector
105
+ await startEnunciadosSelector()
106
+ } else if (command === 'code') {
107
+ // Start interactive coding session
108
+ const session = new CretaCodeSession()
109
+ await session.start()
110
+ } else if (command === 'papers') {
111
+ // Start papers selector
112
+ const executor = new PapersExecutor()
113
+ await executor.execute()
114
+ } else if (command === 'help' || command === 'ayuda') {
115
+ // Show available commands
116
+ showHelp()
117
+ } else {
118
+ console.log(`Comando no reconocido: ${command}`)
119
+ console.log("Escribe 'creta help' para ver comandos disponibles")
120
+ process.exit(1)
121
+ }
122
+
123
+ async function createPortfolioProject(level) {
124
+ const rl = createInterface({
125
+ input: process.stdin,
126
+ output: process.stdout
127
+ })
128
+
129
+ const askQuestion = (question) => {
130
+ return new Promise((resolve) => {
131
+ rl.question(question, resolve)
132
+ })
133
+ }
134
+
135
+ try {
136
+ const name = await askQuestion("¿Cuál es tu nombre? ")
137
+ const projectName = `${name.toLowerCase().replace(/\s+/g, '-')}-portafolio`
138
+
139
+ console.log(`\n🚀 Creando proyecto: ${projectName}`)
140
+ console.log(`📚 Nivel: ${level === 0 ? 'Reto completo' : `Con ${getLevelDescription(level)} completado`}`)
141
+
142
+ await createProjectFiles(projectName, name, level)
143
+
144
+ console.log(`\n✨ ¡Proyecto creado exitosamente!`)
145
+ console.log(`\n📁 Ingresa a tu proyecto: cd ${projectName}`)
146
+ console.log(`📦 Instala dependencias: npm install`)
147
+ console.log(`🚀 Inicia el servidor: npm run dev`)
148
+ console.log(`\n💡 Lee los comentarios en los archivos para saber qué hacer`)
149
+
150
+ rl.close()
151
+ } catch (error) {
152
+ console.error('Error:', error.message)
153
+ rl.close()
154
+ process.exit(1)
155
+ }
156
+ }
157
+
158
+ async function createProjectFiles(projectName, studentName, level) {
159
+ const __filename = fileURLToPath(import.meta.url)
160
+ const __dirname = path.dirname(__filename)
161
+ const templatePath = path.join(__dirname, '../templates/sveltekit-portfolio')
162
+ const targetPath = path.join(process.cwd(), projectName)
163
+
164
+ // Check if template exists
165
+ if (!fs.existsSync(templatePath)) {
166
+ console.error(`Debug info:`)
167
+ console.error(` - CLI location: ${__filename}`)
168
+ console.error(` - Template path: ${templatePath}`)
169
+ console.error(` - Template exists: ${fs.existsSync(templatePath)}`)
170
+ throw new Error('Template no encontrado. Es posible que necesites reinstalar el paquete: npm uninstall -g @icarusmx/creta && npm install -g @icarusmx/creta')
171
+ }
172
+
173
+ // Create target directory
174
+ if (fs.existsSync(targetPath)) {
175
+ throw new Error(`El directorio ${projectName} ya existe`)
176
+ }
177
+
178
+ fs.mkdirSync(targetPath, { recursive: true })
179
+
180
+ // Copy template files
181
+ await copyTemplate(templatePath, targetPath, projectName, studentName, level)
182
+ }
183
+
184
+ async function copyTemplate(src, dest, projectName, studentName, level) {
185
+ const items = fs.readdirSync(src)
186
+
187
+ for (const item of items) {
188
+ const srcPath = path.join(src, item)
189
+ const destPath = path.join(dest, item)
190
+
191
+ if (fs.statSync(srcPath).isDirectory()) {
192
+ fs.mkdirSync(destPath, { recursive: true })
193
+ await copyTemplate(srcPath, destPath, projectName, studentName, level)
194
+ } else {
195
+ // Check if this is a binary file
196
+ const binaryExtensions = ['.png', '.jpg', '.jpeg', '.gif', '.svg', '.ico', '.woff', '.woff2', '.ttf', '.eot']
197
+ const ext = path.extname(srcPath).toLowerCase()
198
+
199
+ if (binaryExtensions.includes(ext)) {
200
+ // Copy binary files directly without text processing
201
+ fs.copyFileSync(srcPath, destPath)
202
+ } else {
203
+ // Process text files with placeholders
204
+ let content = fs.readFileSync(srcPath, 'utf8')
205
+
206
+ // Apply level-specific modifications first
207
+ if (level > 0) {
208
+ content = applyLevelModifications(content, item, level)
209
+ }
210
+
211
+ // Then replace placeholders
212
+ content = content.replace(/\{\{PROJECT_NAME\}\}/g, projectName)
213
+ content = content.replace(/\{\{STUDENT_NAME\}\}/g, studentName)
214
+
215
+ fs.writeFileSync(destPath, content)
216
+ }
217
+ }
218
+ }
219
+ }
220
+
221
+ function applyLevelModifications(content, filename, level) {
222
+ if (filename === '+layout.svelte') {
223
+ return applyLayoutModifications(content, level)
224
+ }
225
+
226
+ if (filename === '+page.svelte') {
227
+ return applyPageModifications(content, level)
228
+ }
229
+
230
+ return content
231
+ }
232
+
233
+ function applyLayoutModifications(content, level) {
234
+ if (level >= 1) {
235
+ // Replace navbar placeholder with complete solution
236
+ content = content.replace(
237
+ '<nav class="bg-white shadow-sm">\n <!-- ⚠️ COMPLETA AQUÍ LA NAVEGACIÓN -->\n</nav>',
238
+ `<nav class="bg-white shadow-sm">
239
+ <div class="max-w-7xl mx-auto px-4">
240
+ <div class="flex justify-between items-center py-3">
241
+ <div class="flex items-center space-x-3">
242
+ <img src="https://icarus.mx/logo.png" alt="Logo" class="w-8 h-8">
243
+ <span class="text-xl font-bold text-gray-900">{{STUDENT_NAME}}</span>
244
+ </div>
245
+ <div class="hidden md:flex space-x-6">
246
+ <a href="#inicio" class="text-gray-600 hover:text-blue-600">Inicio</a>
247
+ <a href="#sobre-mi" class="text-gray-600 hover:text-blue-600">Sobre mí</a>
248
+ <a href="#proyectos" class="text-gray-600 hover:text-blue-600">Proyectos</a>
249
+ <a href="#contacto" class="text-gray-600 hover:text-blue-600">Contacto</a>
250
+ </div>
251
+ <button class="md:hidden">
252
+ <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
253
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path>
254
+ </svg>
255
+ </button>
256
+ </div>
257
+ </div>
258
+ </nav>`
259
+ )
260
+ }
261
+
262
+ if (level >= 3) {
263
+ // Replace footer placeholder with complete solution
264
+ content = content.replace(
265
+ '<footer class="bg-gray-50 border-t">\n <!-- ⚠️ COMPLETA AQUÍ EL FOOTER -->\n</footer>',
266
+ `<footer class="bg-gray-50 border-t">
267
+ <div class="max-w-7xl mx-auto py-8 px-4">
268
+ <div class="text-center text-gray-600 text-sm space-y-2">
269
+ <p>© ${new Date().getFullYear()} {{STUDENT_NAME}}. Todos los derechos reservados.</p>
270
+ <p>Hecho con ❤️ en <a href="https://creta.school" class="text-blue-600 hover:underline">Creta</a></p>
271
+ </div>
272
+ </div>
273
+ </footer>`
274
+ )
275
+ }
276
+
277
+ return content
278
+ }
279
+
280
+ function applyPageModifications(content, level) {
281
+ if (level >= 2) {
282
+ // Replace hero placeholder with complete solution
283
+ content = content.replace(
284
+ `<section class="min-h-screen flex items-center justify-center bg-gradient-to-br from-blue-50 to-indigo-100">
285
+ <!-- ⚠️ COMPLETA AQUÍ LA SECCIÓN HERO -->
286
+ <div class="max-w-4xl mx-auto px-4 text-center">
287
+ <h1 class="text-4xl md:text-6xl font-bold text-gray-900 mb-6">
288
+ Gracias por seguir aquí
289
+ </h1>
290
+ <p class="text-lg text-gray-600 mb-8">
291
+ Abre el proyecto usando <code>code .</code> y sigue las instrucciones. Si tienes dudas, apóyate en el equipo
292
+ </p>
293
+ </div>
294
+ </section>`,
295
+ `<section id="inicio" class="min-h-screen flex items-center justify-center bg-gradient-to-br from-blue-50 to-indigo-100">
296
+ <div class="max-w-4xl mx-auto px-4 text-center">
297
+ <div class="animate-fade-in">
298
+ <h1 class="text-4xl md:text-6xl font-bold text-gray-900 mb-6">
299
+ Hola, soy {{STUDENT_NAME}}
300
+ </h1>
301
+ <p class="text-lg text-gray-600 mb-8 max-w-2xl mx-auto">
302
+ Soy un desarrollador apasionado por crear productos digitales que impacten positivamente.
303
+ Aprendo construyendo y siempre busco nuevos desafíos.
304
+ </p>
305
+ <div class="flex justify-center space-x-4">
306
+ <a href="#proyectos" class="bg-blue-600 hover:bg-blue-700 text-white px-8 py-3 rounded-lg transition-colors duration-200 font-medium">
307
+ Ver proyectos
308
+ </a>
309
+ <a href="#contacto" class="border border-blue-600 text-blue-600 hover:bg-blue-50 px-8 py-3 rounded-lg transition-colors duration-200 font-medium">
310
+ Contáctame
311
+ </a>
312
+ </div>
313
+ </div>
314
+ </div>
315
+ </section>`
316
+ )
317
+ }
318
+
319
+ if (level >= 3) {
320
+ // Add complete sections for level 3
321
+ content = content.replace(
322
+ `<!-- ⚠️ AQUÍ VAN LAS DEMÁS SECCIONES (SOBRE MÍ, PROYECTOS, CONTACTO) -->
323
+ <!-- Revisa los comentarios arriba para saber qué crear -->`,
324
+ `<!-- Sobre mí -->
325
+ <section id="sobre-mi" class="py-20 bg-white">
326
+ <div class="max-w-4xl mx-auto px-4">
327
+ <div class="text-center mb-16">
328
+ <h2 class="text-3xl md:text-4xl font-bold text-gray-900 mb-6">Sobre mí</h2>
329
+ <div class="w-20 h-1 bg-blue-600 mx-auto mb-8"></div>
330
+ </div>
331
+
332
+ <div class="grid md:grid-cols-2 gap-12 items-center">
333
+ <div>
334
+ <div class="bg-gray-100 aspect-square rounded-lg flex items-center justify-center mb-6 md:mb-0">
335
+ <span class="text-gray-500 text-lg">Tu foto aquí</span>
336
+ </div>
337
+ </div>
338
+ <div>
339
+ <p class="text-lg text-gray-600 mb-6 leading-relaxed">
340
+ Soy un desarrollador apasionado por crear productos digitales que impacten positivamente.
341
+ Aprendo construyendo y siempre busco nuevos desafíos que me permitan crecer profesionalmente.
342
+ </p>
343
+ <div class="grid grid-cols-2 gap-4">
344
+ <div class="bg-blue-50 p-4 rounded-lg text-center">
345
+ <div class="text-2xl font-bold text-blue-600">2+</div>
346
+ <div class="text-sm text-gray-600">Años aprendiendo</div>
347
+ </div>
348
+ <div class="bg-green-50 p-4 rounded-lg text-center">
349
+ <div class="text-2xl font-bold text-green-600">5+</div>
350
+ <div class="text-sm text-gray-600">Proyectos</div>
351
+ </div>
352
+ </div>
353
+ </div>
354
+ </div>
355
+ </div>
356
+ </section>
357
+
358
+ <!-- Proyectos -->
359
+ <section id="proyectos" class="py-20 bg-gray-50">
360
+ <div class="max-w-6xl mx-auto px-4">
361
+ <div class="text-center mb-16">
362
+ <h2 class="text-3xl md:text-4xl font-bold text-gray-900 mb-6">Mis Proyectos</h2>
363
+ <div class="w-20 h-1 bg-blue-600 mx-auto mb-8"></div>
364
+ <p class="text-lg text-gray-600 max-w-2xl mx-auto">
365
+ Algunos de los proyectos en los que he trabajado y que demuestran mis habilidades
366
+ </p>
367
+ </div>
368
+
369
+ <div class="grid md:grid-cols-2 lg:grid-cols-3 gap-8">
370
+ <!-- Proyecto 1 -->
371
+ <div class="bg-white rounded-lg shadow-md overflow-hidden hover:shadow-lg transition-shadow duration-200 project-card">
372
+ <div class="bg-gradient-to-br from-blue-400 to-blue-600 h-48 flex items-center justify-center">
373
+ <span class="text-white text-lg font-medium">Proyecto 1</span>
374
+ </div>
375
+ <div class="p-6">
376
+ <h3 class="text-xl font-semibold text-gray-900 mb-3">Nombre del Proyecto</h3>
377
+ <p class="text-gray-600 mb-4">Descripción breve del proyecto y las tecnologías utilizadas.</p>
378
+ <div class="flex space-x-3">
379
+ <a href="#" class="text-blue-600 hover:text-blue-700 text-sm font-medium">Ver código</a>
380
+ <a href="#" class="text-blue-600 hover:text-blue-700 text-sm font-medium">Ver demo</a>
381
+ </div>
382
+ </div>
383
+ </div>
384
+
385
+ <!-- Proyecto 2 -->
386
+ <div class="bg-white rounded-lg shadow-md overflow-hidden hover:shadow-lg transition-shadow duration-200 project-card">
387
+ <div class="bg-gradient-to-br from-green-400 to-green-600 h-48 flex items-center justify-center">
388
+ <span class="text-white text-lg font-medium">Proyecto 2</span>
389
+ </div>
390
+ <div class="p-6">
391
+ <h3 class="text-xl font-semibold text-gray-900 mb-3">Nombre del Proyecto</h3>
392
+ <p class="text-gray-600 mb-4">Descripción breve del proyecto y las tecnologías utilizadas.</p>
393
+ <div class="flex space-x-3">
394
+ <a href="#" class="text-blue-600 hover:text-blue-700 text-sm font-medium">Ver código</a>
395
+ <a href="#" class="text-blue-600 hover:text-blue-700 text-sm font-medium">Ver demo</a>
396
+ </div>
397
+ </div>
398
+ </div>
399
+
400
+ <!-- Proyecto 3 -->
401
+ <div class="bg-white rounded-lg shadow-md overflow-hidden hover:shadow-lg transition-shadow duration-200 project-card">
402
+ <div class="bg-gradient-to-br from-purple-400 to-purple-600 h-48 flex items-center justify-center">
403
+ <span class="text-white text-lg font-medium">Proyecto 3</span>
404
+ </div>
405
+ <div class="p-6">
406
+ <h3 class="text-xl font-semibold text-gray-900 mb-3">Nombre del Proyecto</h3>
407
+ <p class="text-gray-600 mb-4">Descripción breve del proyecto y las tecnologías utilizadas.</p>
408
+ <div class="flex space-x-3">
409
+ <a href="#" class="text-blue-600 hover:text-blue-700 text-sm font-medium">Ver código</a>
410
+ <a href="#" class="text-blue-600 hover:text-blue-700 text-sm font-medium">Ver demo</a>
411
+ </div>
412
+ </div>
413
+ </div>
414
+ </div>
415
+ </div>
416
+ </section>
417
+
418
+ <!-- Contacto -->
419
+ <section id="contacto" class="py-20 bg-white">
420
+ <div class="max-w-4xl mx-auto px-4">
421
+ <div class="text-center mb-16">
422
+ <h2 class="text-3xl md:text-4xl font-bold text-gray-900 mb-6">Contacto</h2>
423
+ <div class="w-20 h-1 bg-blue-600 mx-auto mb-8"></div>
424
+ <p class="text-lg text-gray-600 max-w-2xl mx-auto">
425
+ ¿Tienes un proyecto en mente? ¡Hablemos y veamos cómo puedo ayudarte!
426
+ </p>
427
+ </div>
428
+
429
+ <div class="grid md:grid-cols-2 gap-12">
430
+ <div>
431
+ <h3 class="text-xl font-semibold text-gray-900 mb-6">Información de contacto</h3>
432
+ <div class="space-y-4">
433
+ <div class="flex items-center space-x-3">
434
+ <div class="w-10 h-10 bg-blue-100 rounded-lg flex items-center justify-center">
435
+ <svg class="w-5 h-5 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
436
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8l7.89 4.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"></path>
437
+ </svg>
438
+ </div>
439
+ <div>
440
+ <p class="text-gray-900 font-medium">Email</p>
441
+ <p class="text-gray-600">tu.email@ejemplo.com</p>
442
+ </div>
443
+ </div>
444
+
445
+ <div class="flex items-center space-x-3">
446
+ <div class="w-10 h-10 bg-blue-100 rounded-lg flex items-center justify-center">
447
+ <svg class="w-5 h-5 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
448
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z"></path>
449
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"></path>
450
+ </svg>
451
+ </div>
452
+ <div>
453
+ <p class="text-gray-900 font-medium">Ubicación</p>
454
+ <p class="text-gray-600">Tu ciudad, País</p>
455
+ </div>
456
+ </div>
457
+ </div>
458
+
459
+ <div class="mt-8">
460
+ <h4 class="text-lg font-medium text-gray-900 mb-4">Sígueme</h4>
461
+ <div class="flex space-x-4">
462
+ <a href="#" class="w-10 h-10 bg-gray-100 hover:bg-blue-100 rounded-lg flex items-center justify-center transition-colors duration-200">
463
+ <span class="text-gray-600 hover:text-blue-600">LI</span>
464
+ </a>
465
+ <a href="#" class="w-10 h-10 bg-gray-100 hover:bg-blue-100 rounded-lg flex items-center justify-center transition-colors duration-200">
466
+ <span class="text-gray-600 hover:text-blue-600">GH</span>
467
+ </a>
468
+ <a href="#" class="w-10 h-10 bg-gray-100 hover:bg-blue-100 rounded-lg flex items-center justify-center transition-colors duration-200">
469
+ <span class="text-gray-600 hover:text-blue-600">TW</span>
470
+ </a>
471
+ </div>
472
+ </div>
473
+ </div>
474
+
475
+ <div>
476
+ <form class="space-y-6">
477
+ <div>
478
+ <label for="name" class="block text-sm font-medium text-gray-700 mb-2">Nombre</label>
479
+ <input type="text" id="name" name="name" class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-600 focus:border-transparent">
480
+ </div>
481
+ <div>
482
+ <label for="email" class="block text-sm font-medium text-gray-700 mb-2">Email</label>
483
+ <input type="email" id="email" name="email" class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-600 focus:border-transparent">
484
+ </div>
485
+ <div>
486
+ <label for="message" class="block text-sm font-medium text-gray-700 mb-2">Mensaje</label>
487
+ <textarea id="message" name="message" rows="4" class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-600 focus:border-transparent"></textarea>
488
+ </div>
489
+ <button type="submit" class="w-full bg-blue-600 hover:bg-blue-700 text-white px-8 py-3 rounded-lg transition-colors duration-200 font-medium">
490
+ Enviar mensaje
491
+ </button>
492
+ </form>
493
+ </div>
494
+ </div>
495
+ </div>
496
+ </section>`
497
+ )
498
+ }
499
+
500
+ return content
501
+ }
502
+
503
+ function getLevelDescription(level) {
504
+ const descriptions = {
505
+ 1: 'navbar',
506
+ 2: 'navbar + hero',
507
+ 3: 'todo el portafolio'
508
+ }
509
+ return descriptions[level] || 'nivel desconocido'
510
+ }
511
+
512
+ function isInCretaProject() {
513
+ // Check if we're in a Creta project by looking for key files
514
+ let currentDir
515
+ try {
516
+ currentDir = process.cwd()
517
+ } catch (error) {
518
+ // If we can't get the current directory, we're definitely not in a Creta project
519
+ console.error('Error: No se puede acceder al directorio actual. Asegúrate de estar en un directorio válido.')
520
+ return false
521
+ }
522
+
523
+ const packageJsonPath = path.join(currentDir, 'package.json')
524
+ const layoutPath = path.join(currentDir, 'src/routes/+layout.svelte')
525
+ const pagePath = path.join(currentDir, 'src/routes/+page.svelte')
526
+
527
+ // Must have package.json and SvelteKit structure
528
+ if (!fs.existsSync(packageJsonPath) || !fs.existsSync(layoutPath) || !fs.existsSync(pagePath)) {
529
+ return false
530
+ }
531
+
532
+ // Check if layout contains Creta comments
533
+ try {
534
+ const layoutContent = fs.readFileSync(layoutPath, 'utf8')
535
+ return layoutContent.includes('RETO CRETA') || layoutContent.includes('🧭 RETO 1: NAVBAR')
536
+ } catch (error) {
537
+ return false
538
+ }
539
+ }
540
+
541
+ async function unstuckProject(level) {
542
+ console.log(`\n🔓 ¡Te ayudo a desbloquear el nivel ${level}!`)
543
+ console.log(`📚 Agregando código para: ${getLevelDescription(level)}`)
544
+
545
+ try {
546
+ let currentDir
547
+ try {
548
+ currentDir = process.cwd()
549
+ } catch (error) {
550
+ console.error('Error: No se puede acceder al directorio actual. Asegúrate de estar en un directorio válido.')
551
+ process.exit(1)
552
+ }
553
+ const layoutPath = path.join(currentDir, 'src/routes/+layout.svelte')
554
+ const pagePath = path.join(currentDir, 'src/routes/+page.svelte')
555
+
556
+ // Get student name from package.json title if possible
557
+ let studentName = 'Tu nombre'
558
+ try {
559
+ const packageJsonPath = path.join(currentDir, 'package.json')
560
+ const packageContent = fs.readFileSync(packageJsonPath, 'utf8')
561
+ const packageJson = JSON.parse(packageContent)
562
+ if (packageJson.name && packageJson.name.includes('-portafolio')) {
563
+ studentName = packageJson.name.replace('-portafolio', '').split('-').map(word =>
564
+ word.charAt(0).toUpperCase() + word.slice(1)
565
+ ).join(' ')
566
+ }
567
+ } catch (error) {
568
+ // Use default if can't parse
569
+ }
570
+
571
+ // Apply modifications to existing files
572
+ if (fs.existsSync(layoutPath)) {
573
+ let layoutContent = fs.readFileSync(layoutPath, 'utf8')
574
+ layoutContent = applyLayoutModifications(layoutContent, level)
575
+ // Replace placeholders in layout
576
+ layoutContent = layoutContent.replace(/\{\{STUDENT_NAME\}\}/g, studentName)
577
+ layoutContent = layoutContent.replace(/\{\{PROJECT_NAME\}\}/g, `${studentName.toLowerCase().replace(/\s+/g, '-')}-portafolio`)
578
+ fs.writeFileSync(layoutPath, layoutContent)
579
+ console.log(`✅ Actualizado: src/routes/+layout.svelte`)
580
+ }
581
+
582
+ if (fs.existsSync(pagePath)) {
583
+ let pageContent = fs.readFileSync(pagePath, 'utf8')
584
+ pageContent = applyPageModifications(pageContent, level)
585
+ // Replace placeholders in page
586
+ pageContent = pageContent.replace(/\{\{STUDENT_NAME\}\}/g, studentName)
587
+ pageContent = pageContent.replace(/\{\{PROJECT_NAME\}\}/g, `${studentName.toLowerCase().replace(/\s+/g, '-')}-portafolio`)
588
+ fs.writeFileSync(pagePath, pageContent)
589
+ console.log(`✅ Actualizado: src/routes/+page.svelte`)
590
+ }
591
+
592
+ console.log(`\n🎉 ¡Listo! Ahora tienes el código del nivel ${level}`)
593
+ console.log(`💡 Revisa los archivos actualizados para ver qué se agregó`)
594
+ console.log(`🚀 Continúa con: npm run dev`)
595
+
596
+ } catch (error) {
597
+ console.error('Error al actualizar el proyecto:', error.message)
598
+ process.exit(1)
599
+ }
600
+ }
601
+
602
+ function showHelp() {
603
+ console.log("\n📚 Comandos disponibles:")
604
+ console.log(" creta - Explora los 7 enunciados fundamentales (comando principal) 🧠")
605
+ console.log(" creta enunciados - Explora los 7 enunciados fundamentales 🧠")
606
+ console.log(" creta portafolio - Crea tu portafolio personal (reto completo)")
607
+ console.log(" creta portafolio-1 - Desbloquea nivel 1 (navbar) 🔓")
608
+ console.log(" creta portafolio-2 - Desbloquea nivel 2 (navbar + hero) 🔓")
609
+ console.log(" creta portafolio-3 - Desbloquea nivel 3 (solución completa) 🔓")
610
+ console.log(" creta code - Inicia sesión interactiva de programación 🤖")
611
+ console.log(" creta help - Muestra esta ayuda")
612
+ console.log("\n💡 Tip: Si estás dentro de un proyecto existente, los comandos")
613
+ console.log(" portafolio-1/2/3 actualizarán tus archivos directamente")
614
+ console.log("\n🎯 La filosofía Creta: partir de enunciados que generan 'ruido' para")
615
+ console.log(" construir comprensión real, no solo sintaxis.")
616
+ }
617
+
618
+ async function startMainMenu() {
619
+ // Always try interactive mode first, fall back only if it fails
620
+ try {
621
+ return await startMainMenuInteractive()
622
+ } catch (error) {
623
+ // If interactive mode fails, fall back to numbered selection
624
+ console.log('\nModo interactivo no disponible, usando selección numérica...\n')
625
+ return await startMainMenuFallback()
626
+ }
627
+ }
628
+
629
+ async function startMainMenuInteractive() {
630
+ // Check if setRawMode is available before trying to use it
631
+ if (typeof process.stdin.setRawMode !== 'function') {
632
+ throw new Error('setRawMode not available')
633
+ }
634
+
635
+ console.log("Te ofrecemos las siguientes opciones:")
636
+
637
+ const options = [
638
+ { id: 1, title: "Aprender sintaxis" },
639
+ { id: 2, title: "Aprender conceptos de diseño" },
640
+ { id: 3, title: "Construir proyectos" },
641
+ { id: 4, title: "📄 Recrear papers clásicos" }
642
+ ]
643
+
644
+ let selectedIndex = 0
645
+
646
+ // Enable raw mode to capture arrow keys
647
+ try {
648
+ process.stdin.setRawMode(true)
649
+ process.stdin.resume()
650
+ process.stdin.setEncoding('utf8')
651
+ } catch (error) {
652
+ throw new Error('Failed to enable raw mode: ' + error.message)
653
+ }
654
+
655
+ const renderOptions = () => {
656
+ // Clear previous output and move cursor to top
657
+ process.stdout.write('\x1b[2J')
658
+ process.stdout.write('\x1b[H')
659
+
660
+ console.log(`
661
+ █████████████████████████
662
+ █ █ █ █ █ █ █
663
+ █ C █ R █ E █ T █ A █ █ █
664
+ █ █ █ █ █ █ █
665
+ █████████████████████████
666
+
667
+ Bienvenido a la escuela de software de icarus.mx
668
+ Salgamos de este laberinto 🏛️
669
+ `)
670
+ console.log("Te ofrecemos las siguientes opciones:")
671
+
672
+ options.forEach((option, index) => {
673
+ const isSelected = index === selectedIndex
674
+ const prefix = isSelected ? '▶ ' : ' '
675
+ const highlight = isSelected ? '\x1b[36m' : '\x1b[37m' // cyan for selected, white for normal
676
+ const reset = '\x1b[0m'
677
+
678
+ console.log(`${highlight}${prefix}${index + 1}. ${option.title}${reset}`)
679
+ console.log("")
680
+ })
681
+ }
682
+
683
+ return new Promise((resolve) => {
684
+ renderOptions()
685
+
686
+ const onKeyPress = (key) => {
687
+ if (key === 'q' || key === '\x03') { // q or Ctrl+C
688
+ process.stdin.setRawMode(false)
689
+ process.stdin.removeListener('data', onKeyPress)
690
+ console.log("\nHecho con <3 por icarus.mx")
691
+ resolve()
692
+ return
693
+ }
694
+
695
+ if (key === '\r' || key === '\n') { // Enter
696
+ process.stdin.setRawMode(false)
697
+ process.stdin.removeListener('data', onKeyPress)
698
+
699
+ const selectedOption = options[selectedIndex]
700
+
701
+ // Clear screen
702
+ process.stdout.write('\x1b[2J')
703
+ process.stdout.write('\x1b[H')
704
+
705
+ if (selectedOption.id === 1) {
706
+ startSintaxisSelector().then(resolve)
707
+ } else if (selectedOption.id === 2) {
708
+ startEnunciadosSelector().then(resolve)
709
+ } else if (selectedOption.id === 3) {
710
+ startProyectosSelector().then(resolve)
711
+ } else if (selectedOption.id === 4) {
712
+ const executor = new PapersExecutor()
713
+ executor.execute().then(resolve)
714
+ }
715
+ return
716
+ }
717
+
718
+ // Handle arrow keys (escape sequences)
719
+ if (key === '\u001b[A') { // Up arrow
720
+ selectedIndex = selectedIndex > 0 ? selectedIndex - 1 : options.length - 1
721
+ renderOptions()
722
+ } else if (key === '\u001b[B') { // Down arrow
723
+ selectedIndex = selectedIndex < options.length - 1 ? selectedIndex + 1 : 0
724
+ renderOptions()
725
+ }
726
+ }
727
+
728
+ process.stdin.on('data', onKeyPress)
16
729
  })
730
+ }
17
731
 
18
- // Handle kill signal
19
- process.on('SIGTERM', () => {
20
- cleanup()
21
- process.exit(0)
732
+ async function startMainMenuFallback() {
733
+ const rl = createInterface({
734
+ input: process.stdin,
735
+ output: process.stdout
22
736
  })
23
737
 
24
- // Handle uncaught exceptions
25
- process.on('uncaughtException', (error) => {
26
- console.error('\n❌ Error inesperado:', error.message)
27
- cleanup()
738
+ const askQuestion = (question) => {
739
+ return new Promise((resolve) => {
740
+ rl.question(question, resolve)
741
+ })
742
+ }
743
+
744
+ try {
745
+ console.log("Te ofrecemos las siguientes opciones:")
746
+ console.log("")
747
+ console.log("1. Aprender sintaxis")
748
+ console.log("2. Aprender conceptos de diseño")
749
+ console.log("3. Construir proyectos")
750
+ console.log("4. 📄 Recrear papers clásicos")
751
+ console.log("")
752
+
753
+ const respuesta = await askQuestion("Elige una opción (1-4) o 'q' para salir: ")
754
+
755
+ if (respuesta.toLowerCase() === 'q') {
756
+ console.log("Hecho con <3 por icarus.mx")
757
+ rl.close()
758
+ return
759
+ }
760
+
761
+ const opcionSeleccionada = parseInt(respuesta)
762
+
763
+ if (opcionSeleccionada === 1) {
764
+ rl.close()
765
+ await startSintaxisSelector()
766
+ } else if (opcionSeleccionada === 2) {
767
+ rl.close()
768
+ await startEnunciadosSelector()
769
+ } else if (opcionSeleccionada === 3) {
770
+ rl.close()
771
+ await startProyectosSelector()
772
+ } else if (opcionSeleccionada === 4) {
773
+ rl.close()
774
+ const executor = new PapersExecutor()
775
+ await executor.execute()
776
+ } else {
777
+ console.log("❌ Opción no válida. Elige 1, 2, 3 o 4.")
778
+ rl.close()
779
+ }
780
+
781
+ } catch (error) {
782
+ console.error('Error:', error.message)
783
+ rl.close()
28
784
  process.exit(1)
785
+ }
786
+ }
787
+
788
+ async function startEnunciadosSelector() {
789
+ // Always try interactive mode first, fall back only if it fails
790
+ try {
791
+ return await startEnunciadosSelectorInteractive()
792
+ } catch (error) {
793
+ // If interactive mode fails, fall back to numbered selection
794
+ console.log('\nModo interactivo no disponible, usando selección numérica...\n')
795
+ return await startEnunciadosSelectorFallback()
796
+ }
797
+ }
798
+
799
+ async function startEnunciadosSelectorInteractive() {
800
+ // Check if setRawMode is available before trying to use it
801
+ if (typeof process.stdin.setRawMode !== 'function') {
802
+ throw new Error('setRawMode not available')
803
+ }
804
+
805
+ console.log("Los enunciados fundamentales son un conjunto de afirmaciones que tienen el propósito de ejercitar el pensamiento orientado a objetos de los estudiantes de Creta.")
806
+ console.log("")
807
+ console.log("Escogimos estos enunciados porque creemos que exhaustan los conceptos mínimos necesarios para entender el paradigma de la programación orientada a objetos.")
808
+
809
+ // Wait for user to press Enter before showing the list
810
+ const rl = createInterface({
811
+ input: process.stdin,
812
+ output: process.stdout
813
+ })
814
+
815
+ await new Promise((resolve) => {
816
+ rl.question("\nPresiona Enter para comenzar...", () => {
817
+ rl.close()
818
+ resolve()
819
+ })
820
+ })
821
+
822
+ console.log("\n\n")
823
+ console.log("Elige qué enunciado te gustaría explorar:")
824
+ console.log("")
825
+
826
+ let selectedIndex = 0
827
+ let running = true
828
+
829
+ // Enable raw mode to capture arrow keys
830
+ try {
831
+ process.stdin.setRawMode(true)
832
+ process.stdin.resume()
833
+ process.stdin.setEncoding('utf8')
834
+ } catch (error) {
835
+ throw new Error('Failed to enable raw mode: ' + error.message)
836
+ }
837
+
838
+ const renderOptions = () => {
839
+ // Clear previous output and move cursor to top
840
+ process.stdout.write('\x1b[2J')
841
+ process.stdout.write('\x1b[H')
842
+
843
+ console.log("Los enunciados fundamentales son un conjunto de afirmaciones que tienen el propósito de ejercitar el pensamiento orientado a objetos de los estudiantes de Creta.")
844
+ console.log("")
845
+ console.log("Escogimos estos enunciados porque creemos que exhaustan los conceptos mínimos necesarios para entender el paradigma de la programación orientada a objetos.")
846
+ console.log("")
847
+ console.log("Elige qué enunciado te gustaría explorar:")
848
+ console.log("")
849
+
850
+ ENUNCIADOS.forEach((enunciado, index) => {
851
+ const isSelected = index === selectedIndex
852
+ const prefix = isSelected ? '▶ ' : ' '
853
+ const highlight = isSelected ? '\x1b[36m' : '\x1b[37m' // cyan for selected, white for normal
854
+ const reset = '\x1b[0m'
855
+
856
+ // Format the enunciado text with proper indentation for lists
857
+ let formattedText = enunciado.texto
858
+ if (enunciado.id === 4) {
859
+ // Special formatting for enunciado 4 with list items
860
+ formattedText = formattedText.replace(/\n([abc])\)/g, '\n $1)')
861
+ formattedText = formattedText.replace(/\n\nEstos tres elementos/g, '\n\n Estos tres elementos')
862
+ }
863
+ console.log(`${highlight}${prefix}${index + 1}. ${formattedText}${reset}`)
864
+ console.log("")
865
+ })
866
+ }
867
+
868
+ return new Promise((resolve) => {
869
+ renderOptions()
870
+
871
+ const onKeyPress = async (key) => {
872
+ if (key === 'q' || key === '\x03') { // q or Ctrl+C
873
+ process.stdin.setRawMode(false)
874
+ process.stdin.removeListener('data', onKeyPress)
875
+ console.log("\nHecho con <3 por icarus.mx")
876
+ resolve()
877
+ return
878
+ }
879
+
880
+ if (key === '\r' || key === '\n') { // Enter
881
+ process.stdin.setRawMode(false)
882
+ process.stdin.removeListener('data', onKeyPress)
883
+
884
+ const enunciadoSeleccionado = ENUNCIADOS[selectedIndex]
885
+
886
+ // Clear screen and show selection
887
+ process.stdout.write('\x1b[2J')
888
+ process.stdout.write('\x1b[H')
889
+
890
+ // Check if we have a lesson implementation for this enunciado
891
+ if (enunciadoSeleccionado.id === 1) {
892
+ console.log(`${enunciadoSeleccionado.texto}`)
893
+ await new Promise(resolve => setTimeout(resolve, 1500)) // Brief pause
894
+
895
+ try {
896
+ const lesson1 = new LessonBuilder(LESSON_1_SYSTEM_DECOMPOSITION)
897
+ await lesson1.start()
898
+ await returnToMainMenu()
899
+ } catch (error) {
900
+ console.error("\n❌ Error al ejecutar la lección:", error.message)
901
+ console.log("\n🚀 Próximamente:")
902
+ console.log("- Sesiones de estudio dirigidas basadas en este enunciado")
903
+ console.log("- Ejercicios prácticos que ilustren el concepto")
904
+ console.log("- Proyectos específicos para internalizar la idea")
905
+ await returnToMainMenu()
906
+ }
907
+ } else if (enunciadoSeleccionado.id === 1) {
908
+ console.log(`${enunciadoSeleccionado.texto}`)
909
+ await new Promise(resolve => setTimeout(resolve, 1500)) // Brief pause
910
+
911
+ try {
912
+ const lesson1 = new LessonBuilder(LESSON_1_SYSTEM_DECOMPOSITION)
913
+ await lesson1.start()
914
+ await returnToMainMenu()
915
+ } catch (error) {
916
+ console.error("\n❌ Error al ejecutar la lección:", error.message)
917
+ console.log("\n🚀 Próximamente:")
918
+ console.log("- Sesiones de estudio dirigidas basadas en este enunciado")
919
+ console.log("- Ejercicios prácticos que ilustren el concepto")
920
+ console.log("- Proyectos específicos para internalizar la idea")
921
+ await returnToMainMenu()
922
+ }
923
+ } else if (enunciadoSeleccionado.id === 2) {
924
+ console.log(`${enunciadoSeleccionado.texto}`)
925
+ await new Promise(resolve => setTimeout(resolve, 1500)) // Brief pause
926
+
927
+ try {
928
+ const lesson2 = new LessonBuilder(LESSON_2_OBJECT_REQUESTS)
929
+ await lesson2.start()
930
+ await returnToMainMenu()
931
+ } catch (error) {
932
+ console.error("\n❌ Error al ejecutar la lección:", error.message)
933
+ console.log("\n🚀 Próximamente:")
934
+ console.log("- Sesiones de estudio dirigidas basadas en este enunciado")
935
+ console.log("- Ejercicios prácticos que ilustren el concepto")
936
+ console.log("- Proyectos específicos para internalizar la idea")
937
+ await returnToMainMenu()
938
+ }
939
+ } else if (enunciadoSeleccionado.id === 3) {
940
+ console.log(`${enunciadoSeleccionado.texto}`)
941
+ await new Promise(resolve => setTimeout(resolve, 1500)) // Brief pause
942
+
943
+ try {
944
+ const lesson3 = new LessonBuilder(LESSON_3_ONLY_WAY)
945
+ await lesson3.start()
946
+ await returnToMainMenu()
947
+ } catch (error) {
948
+ console.error("\n❌ Error al ejecutar la lección:", error.message)
949
+ console.log("\n🚀 Próximamente:")
950
+ console.log("- Sesiones de estudio dirigidas basadas en este enunciado")
951
+ console.log("- Ejercicios prácticos que ilustren el concepto")
952
+ console.log("- Proyectos específicos para internalizar la idea")
953
+ await returnToMainMenu()
954
+ }
955
+ } else if (enunciadoSeleccionado.id === 4) {
956
+ console.log(`${enunciadoSeleccionado.texto}`)
957
+ await new Promise(resolve => setTimeout(resolve, 1500)) // Brief pause
958
+
959
+ try {
960
+ const lesson4 = new LessonBuilder(LESSON_4_OPERATION_SIGNATURES)
961
+ await lesson4.start()
962
+ await returnToMainMenu()
963
+ } catch (error) {
964
+ console.error("\n❌ Error al ejecutar la lección:", error.message)
965
+ console.log("\n🚀 Próximamente:")
966
+ console.log("- Sesiones de estudio dirigidas basadas en este enunciado")
967
+ console.log("- Ejercicios prácticos que ilustren el concepto")
968
+ console.log("- Proyectos específicos para internalizar la idea")
969
+ await returnToMainMenu()
970
+ }
971
+ } else if (enunciadoSeleccionado.id === 5) {
972
+ console.log(`${enunciadoSeleccionado.texto}`)
973
+ await new Promise(resolve => setTimeout(resolve, 1500)) // Brief pause
974
+
975
+ try {
976
+ const lesson5 = new LessonBuilder(LESSON_5_INTERFACE_SET)
977
+ await lesson5.start()
978
+ await returnToMainMenu()
979
+ } catch (error) {
980
+ console.error("\n❌ Error al ejecutar la lección:", error.message)
981
+ console.log("\n🚀 Próximamente:")
982
+ console.log("- Sesiones de estudio dirigidas basadas en este enunciado")
983
+ console.log("- Ejercicios prácticos que ilustren el concepto")
984
+ console.log("- Proyectos específicos para internalizar la idea")
985
+ await returnToMainMenu()
986
+ }
987
+ } else if (enunciadoSeleccionado.id === 6) {
988
+ console.log(`${enunciadoSeleccionado.texto}`)
989
+ await new Promise(resolve => setTimeout(resolve, 1500)) // Brief pause
990
+
991
+ try {
992
+ const lesson6 = new LessonBuilder(LESSON_6_INTERFACE_DESIGN)
993
+ await lesson6.start()
994
+ await returnToMainMenu()
995
+ } catch (error) {
996
+ console.error("\n❌ Error al ejecutar la lección:", error.message)
997
+ console.log("\n🚀 Próximamente:")
998
+ console.log("- Sesiones de estudio dirigidas basadas en este enunciado")
999
+ console.log("- Ejercicios prácticos que ilustren el concepto")
1000
+ console.log("- Proyectos específicos para internalizar la idea")
1001
+ await returnToMainMenu()
1002
+ }
1003
+ } else if (enunciadoSeleccionado.id === 7) {
1004
+ console.log(`${enunciadoSeleccionado.texto}`)
1005
+ await new Promise(resolve => setTimeout(resolve, 1500)) // Brief pause
1006
+
1007
+ try {
1008
+ const lesson7 = new LessonBuilder(LESSON_7_OBJECT_DEFINITION)
1009
+ await lesson7.start()
1010
+ await returnToMainMenu()
1011
+ } catch (error) {
1012
+ console.error("\n❌ Error al ejecutar la lección:", error.message)
1013
+ console.log("\n🚀 Próximamente:")
1014
+ console.log("- Sesiones de estudio dirigidas basadas en este enunciado")
1015
+ console.log("- Ejercicios prácticos que ilustren el concepto")
1016
+ console.log("- Proyectos específicos para internalizar la idea")
1017
+ await returnToMainMenu()
1018
+ }
1019
+ } else {
1020
+ console.log("\n🚀 Próximamente:")
1021
+ console.log("- Sesiones de estudio dirigidas basadas en este enunciado")
1022
+ console.log("- Ejercicios prácticos que ilustren el concepto")
1023
+ console.log("- Proyectos específicos para internalizar la idea")
1024
+
1025
+ console.log("\n💭 Por ahora, reflexiona: ¿qué parte específica de este enunciado")
1026
+ console.log(" te genera más curiosidad o confusión?")
1027
+ }
1028
+
1029
+ resolve()
1030
+ return
1031
+ }
1032
+
1033
+ // Handle arrow keys (escape sequences)
1034
+ if (key === '\u001b[A') { // Up arrow
1035
+ selectedIndex = selectedIndex > 0 ? selectedIndex - 1 : ENUNCIADOS.length - 1
1036
+ renderOptions()
1037
+ } else if (key === '\u001b[B') { // Down arrow
1038
+ selectedIndex = selectedIndex < ENUNCIADOS.length - 1 ? selectedIndex + 1 : 0
1039
+ renderOptions()
1040
+ }
1041
+ }
1042
+
1043
+ process.stdin.on('data', onKeyPress)
29
1044
  })
30
1045
  }
31
1046
 
32
- setupExitHandlers()
1047
+ async function startEnunciadosSelectorFallback() {
1048
+ const rl = createInterface({
1049
+ input: process.stdin,
1050
+ output: process.stdout
1051
+ })
33
1052
 
34
- try {
35
- const exitCode = await runCLI(process.argv.slice(2))
36
- process.exit(exitCode)
37
- } catch (error) {
38
- console.error('\n❌ Ocurrió un error inesperado:', error.message)
39
- process.exit(1)
1053
+ const askQuestion = (question) => {
1054
+ return new Promise((resolve) => {
1055
+ rl.question(question, resolve)
1056
+ })
1057
+ }
1058
+
1059
+ try {
1060
+ console.log("Los enunciados fundamentales son un conjunto de afirmaciones que tienen el propósito de ejercitar el pensamiento orientado a objetos de los estudiantes de Creta.")
1061
+ console.log("")
1062
+ console.log("Escogimos estos enunciados porque creemos que exhaustan los conceptos mínimos necesarios para entender el paradigma de la programación orientada a objetos.")
1063
+
1064
+ await askQuestion("\nPresiona Enter para comenzar...")
1065
+
1066
+ console.log("\n\n")
1067
+ console.log("Elige qué enunciado te gustaría explorar:")
1068
+ console.log("")
1069
+
1070
+ ENUNCIADOS.forEach((enunciado, index) => {
1071
+ // Format the enunciado text with proper indentation for lists
1072
+ let formattedText = enunciado.texto
1073
+ if (enunciado.id === 4) {
1074
+ // Special formatting for enunciado 4 with list items
1075
+ formattedText = formattedText.replace(/\n([abc])\)/g, '\n $1)')
1076
+ formattedText = formattedText.replace(/\n\nEstos tres elementos/g, '\n\n Estos tres elementos')
1077
+ }
1078
+ console.log(`${index + 1}. ${formattedText}`)
1079
+ console.log("")
1080
+ })
1081
+
1082
+ const respuesta = await askQuestion("Elige un número (1-7) o 'q' para salir: ")
1083
+
1084
+ if (respuesta.toLowerCase() === 'q') {
1085
+ console.log("Hecho con <3 por icarus.mx")
1086
+ rl.close()
1087
+ return
1088
+ }
1089
+
1090
+ const numeroSeleccionado = parseInt(respuesta)
1091
+
1092
+ if (numeroSeleccionado >= 1 && numeroSeleccionado <= 7) {
1093
+ const enunciadoSeleccionado = ENUNCIADOS[numeroSeleccionado - 1]
1094
+
1095
+ // Check if we have a lesson implementation for this enunciado
1096
+ if (enunciadoSeleccionado.id === 1) {
1097
+ console.log(`${enunciadoSeleccionado.texto}`)
1098
+ rl.close()
1099
+ await new Promise(resolve => setTimeout(resolve, 1500)) // Brief pause
1100
+
1101
+ try {
1102
+ const lesson1 = new LessonBuilder(LESSON_1_SYSTEM_DECOMPOSITION)
1103
+ await lesson1.start()
1104
+ } catch (error) {
1105
+ console.error("\n❌ Error al ejecutar la lección:", error.message)
1106
+ console.log("\n🚀 Próximamente:")
1107
+ console.log("- Sesiones de estudio dirigidas basadas en este enunciado")
1108
+ console.log("- Ejercicios prácticos que ilustren el concepto")
1109
+ console.log("- Proyectos específicos para internalizar la idea")
1110
+ }
1111
+ return
1112
+ } else if (enunciadoSeleccionado.id === 1) {
1113
+ console.log(`${enunciadoSeleccionado.texto}`)
1114
+ rl.close()
1115
+ await new Promise(resolve => setTimeout(resolve, 1500)) // Brief pause
1116
+
1117
+ try {
1118
+ const lesson1 = new LessonBuilder(LESSON_1_SYSTEM_DECOMPOSITION)
1119
+ await lesson1.start()
1120
+ } catch (error) {
1121
+ console.error("\n❌ Error al ejecutar la lección:", error.message)
1122
+ console.log("\n🚀 Próximamente:")
1123
+ console.log("- Sesiones de estudio dirigidas basadas en este enunciado")
1124
+ console.log("- Ejercicios prácticos que ilustren el concepto")
1125
+ console.log("- Proyectos específicos para internalizar la idea")
1126
+ }
1127
+ return
1128
+ } else if (enunciadoSeleccionado.id === 2) {
1129
+ console.log(`${enunciadoSeleccionado.texto}`)
1130
+ rl.close()
1131
+ await new Promise(resolve => setTimeout(resolve, 1500)) // Brief pause
1132
+
1133
+ try {
1134
+ const lesson2 = new LessonBuilder(LESSON_2_OBJECT_REQUESTS)
1135
+ await lesson2.start()
1136
+ } catch (error) {
1137
+ console.error("\n❌ Error al ejecutar la lección:", error.message)
1138
+ console.log("\n🚀 Próximamente:")
1139
+ console.log("- Sesiones de estudio dirigidas basadas en este enunciado")
1140
+ console.log("- Ejercicios prácticos que ilustren el concepto")
1141
+ console.log("- Proyectos específicos para internalizar la idea")
1142
+ }
1143
+ return
1144
+ } else if (enunciadoSeleccionado.id === 3) {
1145
+ console.log(`${enunciadoSeleccionado.texto}`)
1146
+ rl.close()
1147
+ await new Promise(resolve => setTimeout(resolve, 1500)) // Brief pause
1148
+
1149
+ try {
1150
+ const lesson3 = new LessonBuilder(LESSON_3_ONLY_WAY)
1151
+ await lesson3.start()
1152
+ } catch (error) {
1153
+ console.error("\n❌ Error al ejecutar la lección:", error.message)
1154
+ console.log("\n🚀 Próximamente:")
1155
+ console.log("- Sesiones de estudio dirigidas basadas en este enunciado")
1156
+ console.log("- Ejercicios prácticos que ilustren el concepto")
1157
+ console.log("- Proyectos específicos para internalizar la idea")
1158
+ }
1159
+ return
1160
+ } else if (enunciadoSeleccionado.id === 4) {
1161
+ console.log(`${enunciadoSeleccionado.texto}`)
1162
+ rl.close()
1163
+ await new Promise(resolve => setTimeout(resolve, 1500)) // Brief pause
1164
+
1165
+ try {
1166
+ const lesson4 = new LessonBuilder(LESSON_4_OPERATION_SIGNATURES)
1167
+ await lesson4.start()
1168
+ } catch (error) {
1169
+ console.error("\n❌ Error al ejecutar la lección:", error.message)
1170
+ console.log("\n🚀 Próximamente:")
1171
+ console.log("- Sesiones de estudio dirigidas basadas en este enunciado")
1172
+ console.log("- Ejercicios prácticos que ilustren el concepto")
1173
+ console.log("- Proyectos específicos para internalizar la idea")
1174
+ }
1175
+ return
1176
+ } else if (enunciadoSeleccionado.id === 5) {
1177
+ console.log(`${enunciadoSeleccionado.texto}`)
1178
+ rl.close()
1179
+ await new Promise(resolve => setTimeout(resolve, 1500)) // Brief pause
1180
+
1181
+ try {
1182
+ const lesson5 = new LessonBuilder(LESSON_5_INTERFACE_SET)
1183
+ await lesson5.start()
1184
+ } catch (error) {
1185
+ console.error("\n❌ Error al ejecutar la lección:", error.message)
1186
+ console.log("\n🚀 Próximamente:")
1187
+ console.log("- Sesiones de estudio dirigidas basadas en este enunciado")
1188
+ console.log("- Ejercicios prácticos que ilustren el concepto")
1189
+ console.log("- Proyectos específicos para internalizar la idea")
1190
+ }
1191
+ return
1192
+ } else if (enunciadoSeleccionado.id === 6) {
1193
+ console.log(`${enunciadoSeleccionado.texto}`)
1194
+ rl.close()
1195
+ await new Promise(resolve => setTimeout(resolve, 1500)) // Brief pause
1196
+
1197
+ try {
1198
+ const lesson6 = new LessonBuilder(LESSON_6_INTERFACE_DESIGN)
1199
+ await lesson6.start()
1200
+ } catch (error) {
1201
+ console.error("\n❌ Error al ejecutar la lección:", error.message)
1202
+ console.log("\n🚀 Próximamente:")
1203
+ console.log("- Sesiones de estudio dirigidas basadas en este enunciado")
1204
+ console.log("- Ejercicios prácticos que ilustren el concepto")
1205
+ console.log("- Proyectos específicos para internalizar la idea")
1206
+ }
1207
+ return
1208
+ } else if (enunciadoSeleccionado.id === 7) {
1209
+ console.log(`${enunciadoSeleccionado.texto}`)
1210
+ rl.close()
1211
+ await new Promise(resolve => setTimeout(resolve, 1500)) // Brief pause
1212
+
1213
+ try {
1214
+ const lesson7 = new LessonBuilder(LESSON_7_OBJECT_DEFINITION)
1215
+ await lesson7.start()
1216
+ } catch (error) {
1217
+ console.error("\n❌ Error al ejecutar la lección:", error.message)
1218
+ console.log("\n🚀 Próximamente:")
1219
+ console.log("- Sesiones de estudio dirigidas basadas en este enunciado")
1220
+ console.log("- Ejercicios prácticos que ilustren el concepto")
1221
+ console.log("- Proyectos específicos para internalizar la idea")
1222
+ }
1223
+ return
1224
+ } else {
1225
+ console.log("\n🚀 Próximamente:")
1226
+ console.log("- Sesiones de estudio dirigidas basadas en este enunciado")
1227
+ console.log("- Ejercicios prácticos que ilustren el concepto")
1228
+ console.log("- Proyectos específicos para internalizar la idea")
1229
+
1230
+ console.log("\n💭 Por ahora, reflexiona: ¿qué parte específica de este enunciado")
1231
+ console.log(" te genera más curiosidad o confusión?")
1232
+ }
1233
+
1234
+ } else {
1235
+ console.log("❌ Opción no válida. Elige un número entre 1 y 7.")
1236
+ }
1237
+
1238
+ rl.close()
1239
+ } catch (error) {
1240
+ console.error('Error:', error.message)
1241
+ rl.close()
1242
+ process.exit(1)
1243
+ }
1244
+ }
1245
+
1246
+
1247
+ async function startSintaxisSelector() {
1248
+ const rl = createInterface({
1249
+ input: process.stdin,
1250
+ output: process.stdout
1251
+ })
1252
+
1253
+ console.log("Sintaxis: estudia el modo en que se combinan las palabras y los distintos grupos que forman para expresar significado.")
1254
+
1255
+ await new Promise((resolve) => {
1256
+ rl.question("\nPresiona Enter para comenzar...", () => {
1257
+ rl.close()
1258
+ resolve()
1259
+ })
1260
+ })
1261
+
1262
+ console.log("\nEn esta sección aprenderemos algo más que palabras, aprenderemos comandos.")
1263
+ console.log("")
1264
+ console.log("Al final del día un comando es una palabra, la diferencia es que está dotada de un significado que tu computadora entiende.")
1265
+ console.log("")
1266
+ console.log("🚀 Próximamente:")
1267
+ console.log("- Terminal básico (pwd, ls, cd, mkdir, touch, cat)")
1268
+ console.log("- Git básico (init, status, add, commit)")
1269
+ console.log("- Git colaboración (remote, push, pull, clone)")
1270
+ console.log("- Git avanzado (branch, checkout, merge)")
1271
+ console.log("")
1272
+ console.log("Este es tu primer comando: ls")
1273
+ console.log("")
1274
+ console.log("Presiona ctrl + c para terminar esta sesión y después ejecuta el comando ls en tu terminal")
1275
+ }
1276
+
1277
+ async function startProyectosSelector() {
1278
+ // Always try interactive mode first, fall back only if it fails
1279
+ try {
1280
+ return await startProyectosSelectorInteractive()
1281
+ } catch (error) {
1282
+ // If interactive mode fails, fall back to numbered selection
1283
+ console.log('\nModo interactivo no disponible, usando selección numérica...\n')
1284
+ return await startProyectosSelectorFallback()
1285
+ }
1286
+ }
1287
+
1288
+ async function startProyectosSelectorInteractive() {
1289
+ // Check if setRawMode is available before trying to use it
1290
+ if (typeof process.stdin.setRawMode !== 'function') {
1291
+ throw new Error('setRawMode not available')
1292
+ }
1293
+
1294
+ console.log("\n🚀 Construir Proyectos")
1295
+ console.log("Elige qué proyecto quieres construir:")
1296
+
1297
+ const projects = [
1298
+ { id: 1, title: "🔀 Construye una pull-request", type: 'pr' },
1299
+ { id: 2, title: "🎨 Construye tu portafolio", type: 'portfolio' }
1300
+ ]
1301
+
1302
+ let selectedIndex = 0
1303
+
1304
+ // Enable raw mode to capture arrow keys
1305
+ try {
1306
+ process.stdin.setRawMode(true)
1307
+ process.stdin.resume()
1308
+ process.stdin.setEncoding('utf8')
1309
+ } catch (error) {
1310
+ throw new Error('Failed to enable raw mode: ' + error.message)
1311
+ }
1312
+
1313
+ const renderOptions = () => {
1314
+ // Clear previous output and move cursor to top
1315
+ process.stdout.write('\x1b[2J')
1316
+ process.stdout.write('\x1b[H')
1317
+
1318
+ console.log("🚀 Construir Proyectos")
1319
+ console.log("Elige qué proyecto quieres construir:")
1320
+
1321
+ projects.forEach((project, index) => {
1322
+ const isSelected = index === selectedIndex
1323
+ const prefix = isSelected ? '▶ ' : ' '
1324
+ const highlight = isSelected ? '\x1b[36m' : '\x1b[37m' // cyan for selected, white for normal
1325
+ const reset = '\x1b[0m'
1326
+
1327
+ console.log(`${highlight}${prefix}${project.title}${reset}`)
1328
+ console.log("")
1329
+ })
1330
+ }
1331
+
1332
+ return new Promise((resolve) => {
1333
+ renderOptions()
1334
+
1335
+ const onKeyPress = (key) => {
1336
+ if (key === 'q' || key === '\x03') { // q or Ctrl+C
1337
+ process.stdin.setRawMode(false)
1338
+ process.stdin.removeListener('data', onKeyPress)
1339
+ console.log("\nHecho con <3 por icarus.mx")
1340
+ resolve()
1341
+ return
1342
+ }
1343
+
1344
+ if (key === '\r' || key === '\n') { // Enter
1345
+ process.stdin.setRawMode(false)
1346
+ process.stdin.removeListener('data', onKeyPress)
1347
+
1348
+ const selectedProject = projects[selectedIndex]
1349
+
1350
+ // Clear screen
1351
+ process.stdout.write('\x1b[2J')
1352
+ process.stdout.write('\x1b[H')
1353
+
1354
+ if (selectedProject.type === 'pr') {
1355
+ startPullRequestTutorial().then(resolve)
1356
+ } else if (selectedProject.type === 'portfolio') {
1357
+ startPortfolioSelector().then(resolve)
1358
+ }
1359
+ return
1360
+ }
1361
+
1362
+ // Handle arrow keys (escape sequences)
1363
+ if (key === '\u001b[A') { // Up arrow
1364
+ selectedIndex = selectedIndex > 0 ? selectedIndex - 1 : projects.length - 1
1365
+ renderOptions()
1366
+ } else if (key === '\u001b[B') { // Down arrow
1367
+ selectedIndex = selectedIndex < projects.length - 1 ? selectedIndex + 1 : 0
1368
+ renderOptions()
1369
+ }
1370
+ }
1371
+
1372
+ process.stdin.on('data', onKeyPress)
1373
+ })
1374
+ }
1375
+
1376
+ async function returnToMainMenu() {
1377
+ const rl = createInterface({
1378
+ input: process.stdin,
1379
+ output: process.stdout
1380
+ })
1381
+
1382
+ const askQuestion = (question) => {
1383
+ return new Promise((resolve) => {
1384
+ rl.question(question, resolve)
1385
+ })
1386
+ }
1387
+
1388
+ console.log("\n🏠 ¿Qué te gustaría hacer ahora?")
1389
+ console.log("1. Volver al menú principal")
1390
+ console.log("2. Salir")
1391
+
1392
+ const respuesta = await askQuestion("\nElige una opción (1-2): ")
1393
+
1394
+ if (respuesta === '1') {
1395
+ rl.close()
1396
+ console.clear()
1397
+ await startMainMenu()
1398
+ } else {
1399
+ console.log("\nHecho con <3 por icarus.mx")
1400
+ rl.close()
1401
+ }
1402
+ }
1403
+
1404
+ async function startProyectosSelectorFallback() {
1405
+ const rl = createInterface({
1406
+ input: process.stdin,
1407
+ output: process.stdout
1408
+ })
1409
+
1410
+ const askQuestion = (question) => {
1411
+ return new Promise((resolve) => {
1412
+ rl.question(question, resolve)
1413
+ })
1414
+ }
1415
+
1416
+ try {
1417
+ console.log("\n🚀 Construir Proyectos")
1418
+ console.log("Elige qué proyecto quieres construir:")
1419
+ console.log("")
1420
+ console.log("1. 🔀 Construye una pull-request")
1421
+ console.log("2. 🎨 Construye tu portafolio")
1422
+ console.log("")
1423
+
1424
+ const respuesta = await askQuestion("Elige una opción (1-2) o 'q' para salir: ")
1425
+
1426
+ if (respuesta.toLowerCase() === 'q') {
1427
+ console.log("Hecho con <3 por icarus.mx")
1428
+ rl.close()
1429
+ return
1430
+ }
1431
+
1432
+ const opcionSeleccionada = parseInt(respuesta)
1433
+
1434
+ if (opcionSeleccionada === 1) {
1435
+ rl.close()
1436
+ await startPullRequestTutorial()
1437
+ } else if (opcionSeleccionada === 2) {
1438
+ rl.close()
1439
+ await startPortfolioSelector()
1440
+ } else {
1441
+ console.log("❌ Opción no válida. Elige 1 o 2.")
1442
+ rl.close()
1443
+ }
1444
+
1445
+ } catch (error) {
1446
+ console.error('Error:', error.message)
1447
+ rl.close()
1448
+ process.exit(1)
1449
+ }
1450
+ }
1451
+
1452
+ async function startPortfolioSelector() {
1453
+ // Always try interactive mode first, fall back only if it fails
1454
+ try {
1455
+ return await startPortfolioSelectorInteractive()
1456
+ } catch (error) {
1457
+ // If interactive mode fails, fall back to numbered selection
1458
+ console.log('\nModo interactivo no disponible, usando selección numérica...\n')
1459
+ return await startPortfolioSelectorFallback()
1460
+ }
1461
+ }
1462
+
1463
+ async function startPortfolioSelectorInteractive() {
1464
+ // Check if setRawMode is available before trying to use it
1465
+ if (typeof process.stdin.setRawMode !== 'function') {
1466
+ throw new Error('setRawMode not available')
1467
+ }
1468
+
1469
+ console.log("\n🎨 Construye tu Portafolio")
1470
+ console.log("Elige el nivel:")
1471
+
1472
+ const projects = [
1473
+ { id: 1, title: "🎨 Portafolio Personal", description: "Reto completo", level: 0 },
1474
+ { id: 2, title: "🔓 Portafolio Nivel 1", description: "Solo navbar", level: 1 },
1475
+ { id: 3, title: "🔓 Portafolio Nivel 2", description: "Navbar + hero", level: 2 },
1476
+ { id: 4, title: "🔓 Portafolio Nivel 3", description: "Solución completa", level: 3 }
1477
+ ]
1478
+
1479
+ let selectedIndex = 0
1480
+
1481
+ // Enable raw mode to capture arrow keys
1482
+ try {
1483
+ process.stdin.setRawMode(true)
1484
+ process.stdin.resume()
1485
+ process.stdin.setEncoding('utf8')
1486
+ } catch (error) {
1487
+ throw new Error('Failed to enable raw mode: ' + error.message)
1488
+ }
1489
+
1490
+ const renderOptions = () => {
1491
+ // Clear previous output and move cursor to top
1492
+ process.stdout.write('\x1b[2J')
1493
+ process.stdout.write('\x1b[H')
1494
+
1495
+ console.log("🎨 Construye tu Portafolio")
1496
+ console.log("Elige el nivel:")
1497
+
1498
+ projects.forEach((project, index) => {
1499
+ const isSelected = index === selectedIndex
1500
+ const prefix = isSelected ? '▶ ' : ' '
1501
+ const highlight = isSelected ? '\x1b[36m' : '\x1b[37m' // cyan for selected, white for normal
1502
+ const reset = '\x1b[0m'
1503
+
1504
+ console.log(`${highlight}${prefix}${project.title}${reset}`)
1505
+ if (isSelected) {
1506
+ console.log(`${highlight} ${project.description}${reset}`)
1507
+ }
1508
+ console.log("")
1509
+ })
1510
+ }
1511
+
1512
+ return new Promise((resolve) => {
1513
+ renderOptions()
1514
+
1515
+ const onKeyPress = (key) => {
1516
+ if (key === 'q' || key === '\x03') { // q or Ctrl+C
1517
+ process.stdin.setRawMode(false)
1518
+ process.stdin.removeListener('data', onKeyPress)
1519
+ console.log("\nHecho con <3 por icarus.mx")
1520
+ resolve()
1521
+ return
1522
+ }
1523
+
1524
+ if (key === '\r' || key === '\n') { // Enter
1525
+ process.stdin.setRawMode(false)
1526
+ process.stdin.removeListener('data', onKeyPress)
1527
+
1528
+ const selectedProject = projects[selectedIndex]
1529
+ const level = selectedProject.level
1530
+
1531
+ // Clear screen
1532
+ process.stdout.write('\x1b[2J')
1533
+ process.stdout.write('\x1b[H')
1534
+
1535
+ // Check if we're in an existing Creta project
1536
+ if (level > 0 && isInCretaProject()) {
1537
+ unstuckProject(level).then(resolve)
1538
+ } else {
1539
+ createPortfolioProject(level).then(resolve)
1540
+ }
1541
+ return
1542
+ }
1543
+
1544
+ // Handle arrow keys (escape sequences)
1545
+ if (key === '\u001b[A') { // Up arrow
1546
+ selectedIndex = selectedIndex > 0 ? selectedIndex - 1 : projects.length - 1
1547
+ renderOptions()
1548
+ } else if (key === '\u001b[B') { // Down arrow
1549
+ selectedIndex = selectedIndex < projects.length - 1 ? selectedIndex + 1 : 0
1550
+ renderOptions()
1551
+ }
1552
+ }
1553
+
1554
+ process.stdin.on('data', onKeyPress)
1555
+ })
1556
+ }
1557
+
1558
+ async function startPortfolioSelectorFallback() {
1559
+ const rl = createInterface({
1560
+ input: process.stdin,
1561
+ output: process.stdout
1562
+ })
1563
+
1564
+ const askQuestion = (question) => {
1565
+ return new Promise((resolve) => {
1566
+ rl.question(question, resolve)
1567
+ })
1568
+ }
1569
+
1570
+ try {
1571
+ console.log("\n🎨 Construye tu Portafolio")
1572
+ console.log("Elige el nivel:")
1573
+ console.log("")
1574
+ console.log("1. 🎨 Portafolio Personal - Reto completo")
1575
+ console.log("2. 🔓 Portafolio Nivel 1 - Solo navbar")
1576
+ console.log("3. 🔓 Portafolio Nivel 2 - Navbar + hero")
1577
+ console.log("4. 🔓 Portafolio Nivel 3 - Solución completa")
1578
+ console.log("")
1579
+
1580
+ const respuesta = await askQuestion("Elige una opción (1-4) o 'q' para salir: ")
1581
+
1582
+ if (respuesta.toLowerCase() === 'q') {
1583
+ console.log("Hecho con <3 por icarus.mx")
1584
+ rl.close()
1585
+ return
1586
+ }
1587
+
1588
+ const opcionSeleccionada = parseInt(respuesta)
1589
+
1590
+ if (opcionSeleccionada >= 1 && opcionSeleccionada <= 4) {
1591
+ rl.close()
1592
+
1593
+ // Map selection to portfolio level
1594
+ const level = opcionSeleccionada === 1 ? 0 : opcionSeleccionada - 1
1595
+
1596
+ // Check if we're in an existing Creta project
1597
+ if (level > 0 && isInCretaProject()) {
1598
+ await unstuckProject(level)
1599
+ } else {
1600
+ await createPortfolioProject(level)
1601
+ }
1602
+ } else {
1603
+ console.log("❌ Opción no válida. Elige un número entre 1 y 4.")
1604
+ rl.close()
1605
+ }
1606
+
1607
+ } catch (error) {
1608
+ console.error('Error:', error.message)
1609
+ rl.close()
1610
+ process.exit(1)
1611
+ }
1612
+ }
1613
+
1614
+ async function startPullRequestTutorial() {
1615
+ try {
1616
+ const tutorial = new PullRequestTutorial()
1617
+ await tutorial.start()
1618
+
1619
+ // Volver al menú principal después de completar el tutorial
1620
+ await returnToMainMenu()
1621
+ } catch (error) {
1622
+ console.error("\n❌ Error al ejecutar el tutorial:", error.message)
1623
+ console.log("\nSi el problema persiste, contacta al equipo de Creta.")
1624
+
1625
+ const rl = createInterface({
1626
+ input: process.stdin,
1627
+ output: process.stdout
1628
+ })
1629
+
1630
+ await new Promise((resolve) => {
1631
+ rl.question("\nPresiona Enter para volver al menú principal...", () => {
1632
+ rl.close()
1633
+ resolve()
1634
+ })
1635
+ })
1636
+
1637
+ await startMainMenu()
1638
+ }
1639
+ }
1640
+
1641
+ // Helper function to detect command help requests
1642
+ function isCommandHelpRequest(args) {
1643
+ // List of supported commands for help
1644
+ const basicCommands = ['ls', 'cd', 'mkdir', 'touch', 'wc']
1645
+ const gitCommands = ['git status', 'git add', 'git commit', 'git push', 'git log']
1646
+
1647
+ const commandString = args.join(' ')
1648
+
1649
+ // Check basic commands
1650
+ if (basicCommands.includes(args[0])) {
1651
+ return true
1652
+ }
1653
+
1654
+ // Check git commands (two words)
1655
+ if (args[0] === 'git' && args.length >= 2) {
1656
+ const gitCommand = `${args[0]} ${args[1]}`
1657
+ return gitCommands.includes(gitCommand)
1658
+ }
1659
+
1660
+ return false
40
1661
  }