@icarusmx/creta 1.4.18 → 1.5.1

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