@icarusmx/creta 1.5.3 → 1.5.6

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.
Files changed (33) hide show
  1. package/bin/creta.js +14 -4
  2. package/lib/builders/MenuBuilder.js +23 -4
  3. package/lib/executors/ExercisesExecutor.js +271 -0
  4. package/lib/exercises/array-object-manipulation.md +1281 -0
  5. package/lib/exercises/git-stash-workflow.md +426 -0
  6. package/lib/exercises/railway-deployment.md +1185 -0
  7. package/lib/papers/bitcoin/bitcoin.md +92 -0
  8. package/lib/papers/mapreduce/mapreduce.md +476 -0
  9. package/lib/papers/spark/spark.md +49 -0
  10. package/package.json +5 -1
  11. package/ascii-logo.txt +0 -8
  12. package/codex-refactor.txt +0 -13
  13. package/docs/diagrams/README.md +0 -131
  14. package/docs/diagrams/architecture-overview.mmd +0 -71
  15. package/docs/diagrams/architecture.svg +0 -1
  16. package/docs/diagrams/ecosystem-integration.mmd +0 -49
  17. package/docs/diagrams/evolution-phases.mmd +0 -49
  18. package/docs/diagrams/output.svg +0 -1
  19. package/docs/diagrams/phase2-command-help-flow.mmd +0 -51
  20. package/docs/diagrams/user-journey.mmd +0 -78
  21. package/ejemplo.sh +0 -3
  22. package/refactor.txt +0 -581
  23. package/templates/sveltekit-portfolio/package.json +0 -20
  24. package/templates/sveltekit-portfolio/src/app.css +0 -51
  25. package/templates/sveltekit-portfolio/src/app.html +0 -12
  26. package/templates/sveltekit-portfolio/src/routes/+layout.svelte +0 -108
  27. package/templates/sveltekit-portfolio/src/routes/+page.svelte +0 -79
  28. package/templates/sveltekit-portfolio/static/favicon.png +0 -0
  29. package/templates/sveltekit-portfolio/svelte.config.js +0 -10
  30. package/templates/sveltekit-portfolio/vite.config.js +0 -10
  31. package/test/enunciados.test.js +0 -72
  32. package/test/sintaxis-menu.test.js +0 -45
  33. package/wea-fome-qlia.sh +0 -92
package/bin/creta.js CHANGED
@@ -9,6 +9,7 @@ import { CretaCodeSession } from '../lib/session.js'
9
9
  import { PullRequestTutorial } from '../lib/pr-tutorial.js'
10
10
  import { CommandHelpExecutor } from '../lib/executors/CommandHelpExecutor.js'
11
11
  import { PapersExecutor } from '../lib/papers/PapersExecutor.js'
12
+ import { ExercisesExecutor } from '../lib/executors/ExercisesExecutor.js'
12
13
  import { LessonBuilder } from '../lib/builders/LessonBuilder.js'
13
14
  import { LESSON_1_SYSTEM_DECOMPOSITION } from '../lib/data/lessons/lesson1-system-decomposition.js'
14
15
  import { LESSON_2_OBJECT_REQUESTS } from '../lib/data/lessons/lesson2-object-requests.js'
@@ -638,7 +639,8 @@ async function startMainMenuInteractive() {
638
639
  { id: 1, title: "Aprender sintaxis" },
639
640
  { id: 2, title: "Aprender conceptos de diseño" },
640
641
  { id: 3, title: "Construir proyectos" },
641
- { id: 4, title: "📄 Recrear papers clásicos" }
642
+ { id: 4, title: "Recrear papers clásicos" },
643
+ { id: 5, title: "Practicar con ejercicios" }
642
644
  ]
643
645
 
644
646
  let selectedIndex = 0
@@ -711,6 +713,9 @@ Salgamos de este laberinto 🏛️
711
713
  } else if (selectedOption.id === 4) {
712
714
  const executor = new PapersExecutor()
713
715
  executor.execute().then(resolve)
716
+ } else if (selectedOption.id === 5) {
717
+ const executor = new ExercisesExecutor()
718
+ executor.execute().then(resolve)
714
719
  }
715
720
  return
716
721
  }
@@ -747,10 +752,11 @@ async function startMainMenuFallback() {
747
752
  console.log("1. Aprender sintaxis")
748
753
  console.log("2. Aprender conceptos de diseño")
749
754
  console.log("3. Construir proyectos")
750
- console.log("4. 📄 Recrear papers clásicos")
755
+ console.log("4. Recrear papers clásicos")
756
+ console.log("5. Practicar con ejercicios")
751
757
  console.log("")
752
758
 
753
- const respuesta = await askQuestion("Elige una opción (1-4) o 'q' para salir: ")
759
+ const respuesta = await askQuestion("Elige una opción (1-5) o 'q' para salir: ")
754
760
 
755
761
  if (respuesta.toLowerCase() === 'q') {
756
762
  console.log("Hecho con <3 por icarus.mx")
@@ -773,8 +779,12 @@ async function startMainMenuFallback() {
773
779
  rl.close()
774
780
  const executor = new PapersExecutor()
775
781
  await executor.execute()
782
+ } else if (opcionSeleccionada === 5) {
783
+ rl.close()
784
+ const executor = new ExercisesExecutor()
785
+ await executor.execute()
776
786
  } else {
777
- console.log("❌ Opción no válida. Elige 1, 2, 3 o 4.")
787
+ console.log("❌ Opción no válida. Elige 1, 2, 3, 4 o 5.")
778
788
  rl.close()
779
789
  }
780
790
 
@@ -53,12 +53,13 @@ export class MenuBuilder {
53
53
 
54
54
  renderOption(option, isSelected, index) {
55
55
  const prefix = isSelected ? '▶ ' : ' '
56
+ const number = `${index + 1}.`
56
57
  const label = option.title || option.label || `Opción ${index + 1}`
57
58
 
58
59
  if (isSelected) {
59
- console.log(`\x1b[36m${prefix}${label}\x1b[0m`)
60
+ console.log(`\x1b[36m${prefix}${number} ${label}\x1b[0m`)
60
61
  } else {
61
- console.log(`${prefix}${label}`)
62
+ console.log(`${prefix}${number} ${label}`)
62
63
  }
63
64
 
64
65
  if (option.description) {
@@ -117,10 +118,28 @@ export class MenuBuilder {
117
118
  return
118
119
  }
119
120
 
120
- if (key === '\u001b[A') {
121
+ // Handle number keys (1-9)
122
+ const numberMatch = key.match(/^[1-9]$/)
123
+ if (numberMatch) {
124
+ const num = parseInt(key, 10)
125
+ const targetIndex = num - 1
126
+
127
+ // Check if the number is within range
128
+ if (targetIndex >= 0 && targetIndex < this.config.options.length) {
129
+ const choice = this.config.options[targetIndex]
130
+ cleanup()
131
+ resolve(choice)
132
+ return
133
+ }
134
+ }
135
+
136
+ // Arrow keys or vim-style navigation
137
+ if (key === '\u001b[A' || key === 'k') {
138
+ // Up arrow or 'k'
121
139
  selectedIndex = selectedIndex > 0 ? selectedIndex - 1 : this.config.options.length - 1
122
140
  render()
123
- } else if (key === '\u001b[B') {
141
+ } else if (key === '\u001b[B' || key === 'j') {
142
+ // Down arrow or 'j'
124
143
  selectedIndex = selectedIndex < this.config.options.length - 1 ? selectedIndex + 1 : 0
125
144
  render()
126
145
  }
@@ -0,0 +1,271 @@
1
+ import { createInterface } from 'readline'
2
+ import { execSync } from 'child_process'
3
+ import path from 'path'
4
+ import { fileURLToPath } from 'url'
5
+ import fs from 'fs'
6
+
7
+ const __filename = fileURLToPath(import.meta.url)
8
+ const __dirname = path.dirname(__filename)
9
+
10
+ export class ExercisesExecutor {
11
+ constructor() {
12
+ this.exercises = [
13
+ {
14
+ id: 1,
15
+ title: "Array & Object Manipulation Warmups",
16
+ description: "JavaScript fundamentals with LazyVim tips",
17
+ file: "array-object-manipulation.md",
18
+ lines: 1282,
19
+ topics: ["JavaScript", "Arrays", "Objects", "LazyVim"]
20
+ },
21
+ {
22
+ id: 2,
23
+ title: "Git Stash Workflow",
24
+ description: "Wrong branch recovery patterns",
25
+ file: "git-stash-workflow.md",
26
+ lines: 427,
27
+ topics: ["Git", "Stash", "Branching", "Workflow"]
28
+ },
29
+ {
30
+ id: 3,
31
+ title: "Railway Deployment Guide",
32
+ description: "Deploy Node.js apps with Railway CLI",
33
+ file: "railway-deployment.md",
34
+ lines: 500,
35
+ topics: ["Railway", "Deployment", "Node.js", "DevOps"]
36
+ }
37
+ ]
38
+ }
39
+
40
+ async execute() {
41
+ // Check if setRawMode is available
42
+ if (typeof process.stdin.setRawMode === 'function') {
43
+ return await this.executeInteractive()
44
+ } else {
45
+ return await this.executeFallback()
46
+ }
47
+ }
48
+
49
+ async executeInteractive() {
50
+ console.log("\n📝 Ejercicios de Práctica")
51
+ console.log("Elige qué ejercicio quieres practicar:")
52
+ console.log("")
53
+
54
+ let selectedIndex = 0
55
+
56
+ // Enable raw mode
57
+ process.stdin.setRawMode(true)
58
+ process.stdin.resume()
59
+ process.stdin.setEncoding('utf8')
60
+
61
+ const renderOptions = () => {
62
+ // Clear screen
63
+ process.stdout.write('\x1b[2J')
64
+ process.stdout.write('\x1b[H')
65
+
66
+ console.log("📝 Ejercicios de Práctica")
67
+ console.log("Elige qué ejercicio quieres practicar:")
68
+ console.log("")
69
+
70
+ this.exercises.forEach((exercise, index) => {
71
+ const isSelected = index === selectedIndex
72
+ const prefix = isSelected ? '▶ ' : ' '
73
+ const highlight = isSelected ? '\x1b[36m' : '\x1b[37m'
74
+ const reset = '\x1b[0m'
75
+
76
+ console.log(`${highlight}${prefix}${exercise.id}. ${exercise.title}${reset}`)
77
+ if (isSelected) {
78
+ console.log(`${highlight} ${exercise.description} (${exercise.lines} líneas)${reset}`)
79
+ }
80
+ console.log("")
81
+ })
82
+
83
+ console.log("\n💡 Tip: Estos archivos están optimizados para LazyVim")
84
+ console.log(" Usa 'nvim' para abrirlos y aprovechar los fold markers")
85
+ }
86
+
87
+ return new Promise((resolve) => {
88
+ renderOptions()
89
+
90
+ const onKeyPress = async (key) => {
91
+ if (key === 'q' || key === '\x03') { // q or Ctrl+C
92
+ process.stdin.setRawMode(false)
93
+ process.stdin.removeListener('data', onKeyPress)
94
+ console.log("\nHecho con <3 por icarus.mx")
95
+ resolve()
96
+ return
97
+ }
98
+
99
+ if (key === '\r' || key === '\n') { // Enter
100
+ process.stdin.setRawMode(false)
101
+ process.stdin.removeListener('data', onKeyPress)
102
+
103
+ const selectedExercise = this.exercises[selectedIndex]
104
+
105
+ // Clear screen
106
+ process.stdout.write('\x1b[2J')
107
+ process.stdout.write('\x1b[H')
108
+
109
+ await this.openExercise(selectedExercise)
110
+ resolve()
111
+ return
112
+ }
113
+
114
+ // Arrow keys
115
+ if (key === '\u001b[A') { // Up
116
+ selectedIndex = selectedIndex > 0 ? selectedIndex - 1 : this.exercises.length - 1
117
+ renderOptions()
118
+ } else if (key === '\u001b[B') { // Down
119
+ selectedIndex = selectedIndex < this.exercises.length - 1 ? selectedIndex + 1 : 0
120
+ renderOptions()
121
+ }
122
+ }
123
+
124
+ process.stdin.on('data', onKeyPress)
125
+ })
126
+ }
127
+
128
+ async executeFallback() {
129
+ const rl = createInterface({
130
+ input: process.stdin,
131
+ output: process.stdout
132
+ })
133
+
134
+ const askQuestion = (question) => {
135
+ return new Promise((resolve) => {
136
+ rl.question(question, resolve)
137
+ })
138
+ }
139
+
140
+ try {
141
+ console.log("\n📝 Ejercicios de Práctica")
142
+ console.log("Elige qué ejercicio quieres practicar:")
143
+ console.log("")
144
+
145
+ this.exercises.forEach((exercise) => {
146
+ console.log(`${exercise.id}. ${exercise.title}`)
147
+ console.log(` ${exercise.description} (${exercise.lines} líneas)`)
148
+ console.log("")
149
+ })
150
+
151
+ console.log("💡 Tip: Estos archivos están optimizados para LazyVim")
152
+ console.log(" Usa 'nvim' para abrirlos y aprovechar los fold markers")
153
+ console.log("")
154
+
155
+ const respuesta = await askQuestion(`Elige una opción (1-${this.exercises.length}) o 'q' para salir: `)
156
+
157
+ if (respuesta.toLowerCase() === 'q') {
158
+ console.log("Hecho con <3 por icarus.mx")
159
+ rl.close()
160
+ return
161
+ }
162
+
163
+ const opcionSeleccionada = parseInt(respuesta)
164
+
165
+ if (opcionSeleccionada >= 1 && opcionSeleccionada <= this.exercises.length) {
166
+ const selectedExercise = this.exercises[opcionSeleccionada - 1]
167
+ rl.close()
168
+ await this.openExercise(selectedExercise)
169
+ } else {
170
+ console.log(`❌ Opción no válida. Elige un número entre 1 y ${this.exercises.length}.`)
171
+ rl.close()
172
+ }
173
+
174
+ } catch (error) {
175
+ console.error('Error:', error.message)
176
+ rl.close()
177
+ process.exit(1)
178
+ }
179
+ }
180
+
181
+ async openExercise(exercise) {
182
+ const exercisePath = path.join(__dirname, '..', 'exercises', exercise.file)
183
+
184
+ console.log(`\n📖 ${exercise.title}`)
185
+ console.log("=" .repeat(50))
186
+ console.log("")
187
+ console.log(`📍 Ubicación: ${exercisePath}`)
188
+ console.log(`📊 Líneas: ${exercise.lines}`)
189
+ console.log(`🏷️ Temas: ${exercise.topics.join(', ')}`)
190
+ console.log("")
191
+
192
+ // Check if file exists
193
+ if (!fs.existsSync(exercisePath)) {
194
+ console.log(`❌ Error: No se encontró el archivo ${exercise.file}`)
195
+ console.log("")
196
+ console.log("💡 Tip: Asegúrate de estar ejecutando Creta desde el directorio correcto")
197
+ return
198
+ }
199
+
200
+ console.log("📚 Opciones:")
201
+ console.log("")
202
+ console.log("1. Abrir con nvim (recomendado para aprovechar fold markers)")
203
+ console.log("2. Mostrar la ruta (copiar al portapapeles)")
204
+ console.log("3. Ver los primeros 20 líneas")
205
+ console.log("")
206
+
207
+ const rl = createInterface({
208
+ input: process.stdin,
209
+ output: process.stdout
210
+ })
211
+
212
+ const askQuestion = (question) => {
213
+ return new Promise((resolve) => {
214
+ rl.question(question, resolve)
215
+ })
216
+ }
217
+
218
+ const choice = await askQuestion("Elige una opción (1-3) o Enter para abrir con nvim: ")
219
+ rl.close()
220
+
221
+ if (choice === '' || choice === '1') {
222
+ // Open with nvim
223
+ console.log("\n🚀 Abriendo con nvim...")
224
+ console.log("💡 Tip: Usa 'zM' para cerrar todos los folds y ver la estructura")
225
+ console.log("")
226
+
227
+ try {
228
+ // Try to open with nvim
229
+ execSync(`nvim "${exercisePath}"`, { stdio: 'inherit' })
230
+ } catch (error) {
231
+ console.log("\n⚠️ No se pudo abrir nvim. Intentando con el editor por defecto...")
232
+ try {
233
+ execSync(`$EDITOR "${exercisePath}" || vi "${exercisePath}"`, { stdio: 'inherit' })
234
+ } catch (fallbackError) {
235
+ console.log("\n❌ No se pudo abrir ningún editor.")
236
+ console.log(`📍 Abre manualmente: ${exercisePath}`)
237
+ }
238
+ }
239
+ } else if (choice === '2') {
240
+ // Show path
241
+ console.log("\n📋 Ruta copiada al portapapeles (en algunos sistemas):")
242
+ console.log(exercisePath)
243
+ console.log("")
244
+ console.log("💡 Tip: Usa este comando para abrirlo:")
245
+ console.log(` nvim "${exercisePath}"`)
246
+
247
+ // Try to copy to clipboard (macOS)
248
+ try {
249
+ execSync(`echo "${exercisePath}" | pbcopy`, { stdio: 'ignore' })
250
+ console.log("✓ Ruta copiada al portapapeles (macOS)")
251
+ } catch (error) {
252
+ // Silently fail if pbcopy not available
253
+ }
254
+ } else if (choice === '3') {
255
+ // Show preview
256
+ console.log("\n📄 Primeras 20 líneas:")
257
+ console.log("=" .repeat(50))
258
+ try {
259
+ const preview = execSync(`head -20 "${exercisePath}"`, { encoding: 'utf8' })
260
+ console.log(preview)
261
+ } catch (error) {
262
+ console.log("❌ Error al leer el archivo")
263
+ }
264
+ console.log("=" .repeat(50))
265
+ console.log("")
266
+ console.log(`💡 Abre el archivo completo con: nvim "${exercisePath}"`)
267
+ }
268
+
269
+ console.log("")
270
+ }
271
+ }