@icarusmx/creta 1.3.5 → 1.4.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/lib/builders/LessonBuilder.js +166 -6
- package/lib/cli/index.js +4 -0
- package/lib/data/lessons/lesson1-system-decomposition.js +54 -115
- package/lib/data/lessons/lesson2-object-requests.js +5 -0
- package/lib/data/lessons/lesson3-only-way.js +5 -0
- package/lib/data/lessons/lesson4-operation-signatures.js +5 -0
- package/lib/data/lessons/lesson5-interface-set.js +5 -0
- package/lib/data/lessons/lesson6-interface-design.js +5 -0
- package/lib/data/lessons/lesson7-object-definition.js +5 -0
- package/lib/data/lessons/sintaxis/terminal-basico.js +5 -1
- package/lib/executors/enunciados-executor.js +10 -0
- package/lib/executors/sintaxis-executor.js +11 -0
- package/lib/utils/greeting.js +32 -0
- package/lib/utils/user-state.js +115 -0
- package/package.json +1 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createPromptInterface, askQuestion } from '../utils/input.js'
|
|
2
2
|
import { clearConsole } from '../utils/output.js'
|
|
3
|
+
import { UserState } from '../utils/user-state.js'
|
|
3
4
|
|
|
4
5
|
const DEFAULT_WAIT_MESSAGE = '\nPresiona Enter para continuar...'
|
|
5
6
|
|
|
@@ -19,6 +20,11 @@ export class LessonBuilder {
|
|
|
19
20
|
try {
|
|
20
21
|
const flow = this.collectActions()
|
|
21
22
|
await this.executeActions(flow)
|
|
23
|
+
|
|
24
|
+
// Track lesson completion if lesson has an ID
|
|
25
|
+
if (this.lesson.id) {
|
|
26
|
+
UserState.completLesson(this.lesson.id)
|
|
27
|
+
}
|
|
22
28
|
} finally {
|
|
23
29
|
this.rl.close()
|
|
24
30
|
this.rl = null
|
|
@@ -85,6 +91,9 @@ export class LessonBuilder {
|
|
|
85
91
|
case 'command-intro':
|
|
86
92
|
await this.handleCommandIntro(action)
|
|
87
93
|
break
|
|
94
|
+
case 'maze-completion':
|
|
95
|
+
await this.handleMazeCompletion(action)
|
|
96
|
+
break
|
|
88
97
|
case 'text':
|
|
89
98
|
default:
|
|
90
99
|
this.handleText(action)
|
|
@@ -194,21 +203,27 @@ export class LessonBuilder {
|
|
|
194
203
|
}
|
|
195
204
|
|
|
196
205
|
// Wait for Enter
|
|
197
|
-
await askQuestion(this.rl, '\nPresiona Enter para
|
|
206
|
+
await askQuestion(this.rl, '\nPresiona Enter para continuar...')
|
|
198
207
|
|
|
199
|
-
//
|
|
208
|
+
// Clear screen and show explanation
|
|
200
209
|
if (action.explanation) {
|
|
201
|
-
|
|
210
|
+
clearConsole()
|
|
211
|
+
console.log(action.explanation)
|
|
212
|
+
await askQuestion(this.rl, '\nPresiona Enter para continuar...')
|
|
202
213
|
}
|
|
203
214
|
|
|
215
|
+
// Clear screen and show detail
|
|
204
216
|
if (action.detail) {
|
|
205
|
-
|
|
217
|
+
clearConsole()
|
|
206
218
|
console.log(action.detail)
|
|
219
|
+
await askQuestion(this.rl, '\nPresiona Enter para continuar...')
|
|
207
220
|
}
|
|
208
221
|
}
|
|
209
222
|
|
|
210
223
|
async handleCommandIntro(action) {
|
|
211
|
-
|
|
224
|
+
// Clear screen before showing command
|
|
225
|
+
clearConsole()
|
|
226
|
+
|
|
212
227
|
console.log(action.description)
|
|
213
228
|
console.log('')
|
|
214
229
|
console.log(action.explanation)
|
|
@@ -222,7 +237,152 @@ export class LessonBuilder {
|
|
|
222
237
|
console.log(action.instruction)
|
|
223
238
|
|
|
224
239
|
if (!action.exitOnComplete) {
|
|
225
|
-
await askQuestion(this.rl,
|
|
240
|
+
await askQuestion(this.rl, '\nPresiona Enter para continuar...')
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
async handleMazeCompletion(action) {
|
|
245
|
+
const frames = [
|
|
246
|
+
// Frame 1: Cow enters maze
|
|
247
|
+
[
|
|
248
|
+
'███████████████',
|
|
249
|
+
'█🐄░░░███░░░░░█',
|
|
250
|
+
'█░███░███░███░█',
|
|
251
|
+
'█░███░░░░░███░█',
|
|
252
|
+
'█░░░░░███░░░░░█',
|
|
253
|
+
'███████████████'
|
|
254
|
+
],
|
|
255
|
+
// Frame 2: Cow moves forward
|
|
256
|
+
[
|
|
257
|
+
'███████████████',
|
|
258
|
+
'█░🐄░░███░░░░░█',
|
|
259
|
+
'█░███░███░███░█',
|
|
260
|
+
'█░███░░░░░███░█',
|
|
261
|
+
'█░░░░░███░░░░░█',
|
|
262
|
+
'███████████████'
|
|
263
|
+
],
|
|
264
|
+
// Frame 3: Cow continues
|
|
265
|
+
[
|
|
266
|
+
'███████████████',
|
|
267
|
+
'█░░🐄░███░░░░░█',
|
|
268
|
+
'█░███░███░███░█',
|
|
269
|
+
'█░███░░░░░███░█',
|
|
270
|
+
'█░░░░░███░░░░░█',
|
|
271
|
+
'███████████████'
|
|
272
|
+
],
|
|
273
|
+
// Frame 4: Cow turns down
|
|
274
|
+
[
|
|
275
|
+
'███████████████',
|
|
276
|
+
'█░░░🐄███░░░░░█',
|
|
277
|
+
'█░███░███░███░█',
|
|
278
|
+
'█░███░░░░░███░█',
|
|
279
|
+
'█░░░░░███░░░░░█',
|
|
280
|
+
'███████████████'
|
|
281
|
+
],
|
|
282
|
+
// Frame 5: Cow goes down
|
|
283
|
+
[
|
|
284
|
+
'███████████████',
|
|
285
|
+
'█░░░░░███░░░░░█',
|
|
286
|
+
'█░███🐄███░███░█',
|
|
287
|
+
'█░███░░░░░███░█',
|
|
288
|
+
'█░░░░░███░░░░░█',
|
|
289
|
+
'███████████████'
|
|
290
|
+
],
|
|
291
|
+
// Frame 6: Cow continues down
|
|
292
|
+
[
|
|
293
|
+
'███████████████',
|
|
294
|
+
'█░░░░░███░░░░░█',
|
|
295
|
+
'█░███░███░███░█',
|
|
296
|
+
'█░███🐄░░░███░█',
|
|
297
|
+
'█░░░░░███░░░░░█',
|
|
298
|
+
'███████████████'
|
|
299
|
+
],
|
|
300
|
+
// Frame 7: Cow moves right
|
|
301
|
+
[
|
|
302
|
+
'███████████████',
|
|
303
|
+
'█░░░░░███░░░░░█',
|
|
304
|
+
'█░███░███░███░█',
|
|
305
|
+
'█░███░🐄░░███░█',
|
|
306
|
+
'█░░░░░███░░░░░█',
|
|
307
|
+
'███████████████'
|
|
308
|
+
],
|
|
309
|
+
// Frame 8: Cow reaches bottom
|
|
310
|
+
[
|
|
311
|
+
'███████████████',
|
|
312
|
+
'█░░░░░███░░░░░█',
|
|
313
|
+
'█░███░███░███░█',
|
|
314
|
+
'█░███░░🐄░███░█',
|
|
315
|
+
'█░░░░░███░░░░░█',
|
|
316
|
+
'███████████████'
|
|
317
|
+
],
|
|
318
|
+
// Frame 9: Cow moves down to exit
|
|
319
|
+
[
|
|
320
|
+
'███████████████',
|
|
321
|
+
'█░░░░░███░░░░░█',
|
|
322
|
+
'█░███░███░███░█',
|
|
323
|
+
'█░███░░░░░███░█',
|
|
324
|
+
'█░░░░🐄███░░░░█',
|
|
325
|
+
'███████████████'
|
|
326
|
+
],
|
|
327
|
+
// Frame 10: Cow approaches exit
|
|
328
|
+
[
|
|
329
|
+
'███████████████',
|
|
330
|
+
'█░░░░░███░░░░░█',
|
|
331
|
+
'█░███░███░███░█',
|
|
332
|
+
'█░███░░░░░███░█',
|
|
333
|
+
'█░░░░░███🐄░░░█',
|
|
334
|
+
'███████████████'
|
|
335
|
+
],
|
|
336
|
+
// Frame 11: Cow exits!
|
|
337
|
+
[
|
|
338
|
+
'███████████████',
|
|
339
|
+
'█░░░░░███░░░░░█',
|
|
340
|
+
'█░███░███░███░█',
|
|
341
|
+
'█░███░░░░░███░█',
|
|
342
|
+
'█░░░░░███░░🐄░█',
|
|
343
|
+
'███████████████'
|
|
344
|
+
],
|
|
345
|
+
// Frame 12: Cow is free!
|
|
346
|
+
[
|
|
347
|
+
'███████████████',
|
|
348
|
+
'█░░░░░███░░░░░█',
|
|
349
|
+
'█░███░███░███░█',
|
|
350
|
+
'█░███░░░░░███░█',
|
|
351
|
+
'█░░░░░███░░░🐄█',
|
|
352
|
+
'███████████████'
|
|
353
|
+
],
|
|
354
|
+
// Frame 13: Cow outside the maze
|
|
355
|
+
[
|
|
356
|
+
'███████████████',
|
|
357
|
+
'█░░░░░███░░░░░█',
|
|
358
|
+
'█░███░███░███░█',
|
|
359
|
+
'█░███░░░░░███░█',
|
|
360
|
+
'█░░░░░███░░░░░█',
|
|
361
|
+
'███████████████ 🐄'
|
|
362
|
+
]
|
|
363
|
+
]
|
|
364
|
+
|
|
365
|
+
console.log('\n')
|
|
366
|
+
|
|
367
|
+
// Animate through frames
|
|
368
|
+
for (const frame of frames) {
|
|
369
|
+
clearConsole()
|
|
370
|
+
console.log('')
|
|
371
|
+
frame.forEach(line => console.log(' ' + line))
|
|
372
|
+
await new Promise(resolve => setTimeout(resolve, 300))
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Show completion message
|
|
376
|
+
await new Promise(resolve => setTimeout(resolve, 500))
|
|
377
|
+
console.log('')
|
|
378
|
+
console.log(` ✓ ${action.message}`)
|
|
379
|
+
|
|
380
|
+
if (action.unlocked) {
|
|
381
|
+
await new Promise(resolve => setTimeout(resolve, 300))
|
|
382
|
+
console.log(` 🔓 Desbloqueaste: ${action.unlocked}`)
|
|
226
383
|
}
|
|
384
|
+
|
|
385
|
+
console.log('')
|
|
386
|
+
await askQuestion(this.rl, '\nPresiona Enter para continuar...')
|
|
227
387
|
}
|
|
228
388
|
}
|
package/lib/cli/index.js
CHANGED
|
@@ -6,6 +6,7 @@ import { executeSintaxis } from '../executors/sintaxis-executor.js'
|
|
|
6
6
|
import { executePortfolio } from '../executors/portfolio-executor.js'
|
|
7
7
|
import { showHelp } from '../commands/help.js'
|
|
8
8
|
import { CretaCodeSession } from '../session.js'
|
|
9
|
+
import { greetUser } from '../utils/greeting.js'
|
|
9
10
|
|
|
10
11
|
async function executeMainMenu() {
|
|
11
12
|
const menu = new MenuBuilder(MAIN_MENU_CONFIG)
|
|
@@ -71,6 +72,9 @@ export async function handleCommand(command, args = []) {
|
|
|
71
72
|
export async function runCLI(args = []) {
|
|
72
73
|
const [command, ...rest] = args
|
|
73
74
|
|
|
75
|
+
// Greet user (ask name if first time, or show stats if returning)
|
|
76
|
+
await greetUser()
|
|
77
|
+
|
|
74
78
|
if (!command) {
|
|
75
79
|
await executeMainMenu()
|
|
76
80
|
return 0
|
|
@@ -115,27 +115,13 @@ export const LESSON_1_SYSTEM_DECOMPOSITION = {
|
|
|
115
115
|
type: 'text'
|
|
116
116
|
},
|
|
117
117
|
{
|
|
118
|
-
code: '
|
|
119
|
-
'
|
|
120
|
-
'
|
|
121
|
-
'
|
|
122
|
-
' this.isbn = isbn\n' +
|
|
123
|
-
' this.isLoaned = false\n' +
|
|
124
|
-
' }\n' +
|
|
125
|
-
'\n' +
|
|
126
|
-
' isAvailable() {\n' +
|
|
127
|
-
' return !this.isLoaned\n' +
|
|
128
|
-
' }\n' +
|
|
129
|
-
'\n' +
|
|
130
|
-
' markAsLoaned() {\n' +
|
|
131
|
-
' this.isLoaned = true\n' +
|
|
132
|
-
' }\n' +
|
|
133
|
-
'\n' +
|
|
134
|
-
' markAsReturned() {\n' +
|
|
135
|
-
' this.isLoaned = false\n' +
|
|
136
|
-
' }\n' +
|
|
118
|
+
code: 'interface Book {\n' +
|
|
119
|
+
' isAvailable(): boolean\n' +
|
|
120
|
+
' markAsLoaned(): void\n' +
|
|
121
|
+
' markAsReturned(): void\n' +
|
|
137
122
|
'}',
|
|
138
|
-
title: '📄
|
|
123
|
+
title: '📄 Objeto: Book',
|
|
124
|
+
after: 'Responsabilidad: Conocer su propio estado de disponibilidad',
|
|
139
125
|
type: 'code'
|
|
140
126
|
},
|
|
141
127
|
{
|
|
@@ -143,28 +129,13 @@ export const LESSON_1_SYSTEM_DECOMPOSITION = {
|
|
|
143
129
|
type: 'pause'
|
|
144
130
|
},
|
|
145
131
|
{
|
|
146
|
-
code: '
|
|
147
|
-
'
|
|
148
|
-
'
|
|
149
|
-
'
|
|
150
|
-
' this.maxLoans = maxLoans\n' +
|
|
151
|
-
' this.currentLoans = []\n' +
|
|
152
|
-
' }\n' +
|
|
153
|
-
'\n' +
|
|
154
|
-
' canBorrow() {\n' +
|
|
155
|
-
' return this.currentLoans.length < this.maxLoans\n' +
|
|
156
|
-
' }\n' +
|
|
157
|
-
'\n' +
|
|
158
|
-
' addLoan(loan) {\n' +
|
|
159
|
-
' this.currentLoans.push(loan)\n' +
|
|
160
|
-
' }\n' +
|
|
161
|
-
'\n' +
|
|
162
|
-
' removeLoan(loan) {\n' +
|
|
163
|
-
' const index = this.currentLoans.indexOf(loan)\n' +
|
|
164
|
-
' if (index > -1) this.currentLoans.splice(index, 1)\n' +
|
|
165
|
-
' }\n' +
|
|
132
|
+
code: 'interface User {\n' +
|
|
133
|
+
' canBorrow(): boolean\n' +
|
|
134
|
+
' addLoan(loan: Loan): void\n' +
|
|
135
|
+
' removeLoan(loan: Loan): void\n' +
|
|
166
136
|
'}',
|
|
167
|
-
title: '📄
|
|
137
|
+
title: '📄 Objeto: User',
|
|
138
|
+
after: 'Responsabilidad: Conocer sus propios límites de préstamo',
|
|
168
139
|
type: 'code'
|
|
169
140
|
},
|
|
170
141
|
{
|
|
@@ -172,32 +143,13 @@ export const LESSON_1_SYSTEM_DECOMPOSITION = {
|
|
|
172
143
|
type: 'pause'
|
|
173
144
|
},
|
|
174
145
|
{
|
|
175
|
-
code: '
|
|
176
|
-
'
|
|
177
|
-
'
|
|
178
|
-
'
|
|
179
|
-
' this.startDate = new Date()\n' +
|
|
180
|
-
' this.dueDate = new Date(Date.now() + durationDays * 24 * 60 * 60 * 1000)\n' +
|
|
181
|
-
' this.returned = false\n' +
|
|
182
|
-
' }\n' +
|
|
183
|
-
'\n' +
|
|
184
|
-
' isOverdue() {\n' +
|
|
185
|
-
' return !this.returned && new Date() > this.dueDate\n' +
|
|
186
|
-
' }\n' +
|
|
187
|
-
'\n' +
|
|
188
|
-
' calculateFine() {\n' +
|
|
189
|
-
' if (!this.isOverdue()) return 0\n' +
|
|
190
|
-
' const daysLate = Math.ceil((new Date() - this.dueDate) / (24 * 60 * 60 * 1000))\n' +
|
|
191
|
-
' return daysLate * 5 // $5 por día\n' +
|
|
192
|
-
' }\n' +
|
|
193
|
-
'\n' +
|
|
194
|
-
' returnBook() {\n' +
|
|
195
|
-
' this.returned = true\n' +
|
|
196
|
-
' this.book.markAsReturned()\n' +
|
|
197
|
-
' this.user.removeLoan(this)\n' +
|
|
198
|
-
' }\n' +
|
|
146
|
+
code: 'interface Loan {\n' +
|
|
147
|
+
' isOverdue(): boolean\n' +
|
|
148
|
+
' calculateFine(): number\n' +
|
|
149
|
+
' returnBook(): void\n' +
|
|
199
150
|
'}',
|
|
200
|
-
title: '📄
|
|
151
|
+
title: '📄 Objeto: Loan',
|
|
152
|
+
after: 'Responsabilidad: Gestionar la relación temporal entre usuario y libro',
|
|
201
153
|
type: 'code'
|
|
202
154
|
},
|
|
203
155
|
{
|
|
@@ -205,36 +157,14 @@ export const LESSON_1_SYSTEM_DECOMPOSITION = {
|
|
|
205
157
|
type: 'pause'
|
|
206
158
|
},
|
|
207
159
|
{
|
|
208
|
-
|
|
209
|
-
'
|
|
210
|
-
'
|
|
211
|
-
'
|
|
212
|
-
'
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
' lendBook(user, book) {',
|
|
217
|
-
' if (!user.canBorrow()) return null',
|
|
218
|
-
' if (!book.isAvailable()) return null',
|
|
219
|
-
'',
|
|
220
|
-
' const loan = new Loan(user, book)',
|
|
221
|
-
' book.markAsLoaned()',
|
|
222
|
-
' user.addLoan(loan)',
|
|
223
|
-
' this.loans.push(loan)',
|
|
224
|
-
' return loan',
|
|
225
|
-
' }',
|
|
226
|
-
'',
|
|
227
|
-
' returnBook(loan) {',
|
|
228
|
-
' loan.returnBook()',
|
|
229
|
-
' return loan.calculateFine()',
|
|
230
|
-
' }',
|
|
231
|
-
'',
|
|
232
|
-
' getOverdueLoans() {',
|
|
233
|
-
' return this.loans.filter(loan => loan.isOverdue())',
|
|
234
|
-
' }',
|
|
235
|
-
'}'
|
|
236
|
-
],
|
|
237
|
-
type: 'text'
|
|
160
|
+
code: 'interface Library {\n' +
|
|
161
|
+
' lendBook(user: User, book: Book): Loan\n' +
|
|
162
|
+
' returnBook(loan: Loan): number\n' +
|
|
163
|
+
' getOverdueLoans(): Loan[]\n' +
|
|
164
|
+
'}',
|
|
165
|
+
title: '📄 Objeto: Library',
|
|
166
|
+
after: 'Responsabilidad: Coordinar las operaciones del sistema',
|
|
167
|
+
type: 'code'
|
|
238
168
|
},
|
|
239
169
|
{
|
|
240
170
|
message: '\nPresiona Enter para ver la interacción...',
|
|
@@ -245,25 +175,29 @@ export const LESSON_1_SYSTEM_DECOMPOSITION = {
|
|
|
245
175
|
},
|
|
246
176
|
{
|
|
247
177
|
lines: [
|
|
248
|
-
'
|
|
178
|
+
'🔄 Interacciones entre objetos:',
|
|
179
|
+
'━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━',
|
|
249
180
|
'',
|
|
250
|
-
'
|
|
251
|
-
|
|
252
|
-
|
|
181
|
+
'1. Library solicita a User:',
|
|
182
|
+
' → user.canBorrow()',
|
|
183
|
+
' ← responde: true/false',
|
|
253
184
|
'',
|
|
254
|
-
'
|
|
255
|
-
'
|
|
256
|
-
'
|
|
257
|
-
'console.log(user.canBorrow()) // true (2 préstamos disponibles)',
|
|
185
|
+
'2. Library solicita a Book:',
|
|
186
|
+
' → book.isAvailable()',
|
|
187
|
+
' ← responde: true/false',
|
|
258
188
|
'',
|
|
259
|
-
'
|
|
260
|
-
'
|
|
261
|
-
'
|
|
262
|
-
'console.log(loan.calculateFine()) // 5',
|
|
189
|
+
'3. Library crea Loan y coordina:',
|
|
190
|
+
' → book.markAsLoaned()',
|
|
191
|
+
' → user.addLoan(loan)',
|
|
263
192
|
'',
|
|
264
|
-
'
|
|
265
|
-
'
|
|
266
|
-
'
|
|
193
|
+
'4. Al devolver, Library solicita a Loan:',
|
|
194
|
+
' → loan.isOverdue()',
|
|
195
|
+
' ← responde: true/false',
|
|
196
|
+
' → loan.calculateFine()',
|
|
197
|
+
' ← responde: 25 (ejemplo)',
|
|
198
|
+
'',
|
|
199
|
+
'💡 Nota: Los objetos NO acceden directamente a datos de otros.',
|
|
200
|
+
' Solo se comunican a través de solicitudes a sus interfaces.'
|
|
267
201
|
],
|
|
268
202
|
type: 'text'
|
|
269
203
|
},
|
|
@@ -290,10 +224,10 @@ export const LESSON_1_SYSTEM_DECOMPOSITION = {
|
|
|
290
224
|
'¿Cómo decidimos que necesitábamos exactamente estos 4 objetos?',
|
|
291
225
|
'¿Por qué no 3? ¿Por qué no 10? ¿Cuál es el criterio?',
|
|
292
226
|
'\n🔍 Criterio de descomposición:',
|
|
293
|
-
'• Book -
|
|
294
|
-
'• User -
|
|
295
|
-
'• Loan -
|
|
296
|
-
'• Library -
|
|
227
|
+
'• Book - define operaciones para gestionar su disponibilidad',
|
|
228
|
+
'• User - define operaciones para gestionar sus límites',
|
|
229
|
+
'• Loan - define operaciones para gestionar la relación temporal',
|
|
230
|
+
'• Library - define operaciones para coordinar el sistema',
|
|
297
231
|
'\n📚 Conexión con lecciones siguientes:',
|
|
298
232
|
'• Lección 2: Los objetos interactúan a través de solicitudes',
|
|
299
233
|
'• Lección 3: Las solicitudes son la única forma de ejecutar operaciones',
|
|
@@ -307,6 +241,11 @@ export const LESSON_1_SYSTEM_DECOMPOSITION = {
|
|
|
307
241
|
{
|
|
308
242
|
message: '\n✨ ¡Lección 1 completada! Presiona Enter para continuar...',
|
|
309
243
|
type: 'pause'
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
type: 'maze-completion',
|
|
247
|
+
message: 'Lección 1: Sistema completado!',
|
|
248
|
+
unlocked: 'Lección 2: Solicitudes'
|
|
310
249
|
}
|
|
311
250
|
]
|
|
312
251
|
}
|
|
@@ -313,6 +313,11 @@ export const LESSON_2_OBJECT_REQUESTS = {
|
|
|
313
313
|
{
|
|
314
314
|
message: '\n✨ ¡Lección 2 completada! Presiona Enter para salir...',
|
|
315
315
|
type: 'pause'
|
|
316
|
+
},
|
|
317
|
+
{
|
|
318
|
+
type: 'maze-completion',
|
|
319
|
+
message: 'Lección 2: Solicitudes completado!',
|
|
320
|
+
unlocked: 'Lección 3: Única forma'
|
|
316
321
|
}
|
|
317
322
|
]
|
|
318
323
|
}
|
|
@@ -344,6 +344,11 @@ export const LESSON_3_ONLY_WAY = {
|
|
|
344
344
|
{
|
|
345
345
|
message: '\n✨ ¡Lección 3 completada! Presiona Enter para salir...',
|
|
346
346
|
type: 'pause'
|
|
347
|
+
},
|
|
348
|
+
{
|
|
349
|
+
type: 'maze-completion',
|
|
350
|
+
message: 'Lección 3: Única forma completado!',
|
|
351
|
+
unlocked: 'Lección 4: Firmas de operación'
|
|
347
352
|
}
|
|
348
353
|
]
|
|
349
354
|
}
|
|
@@ -327,6 +327,11 @@ export const LESSON_4_OPERATION_SIGNATURES = {
|
|
|
327
327
|
{
|
|
328
328
|
message: '\n✨ ¡Lección 4 completada! Presiona Enter para salir...',
|
|
329
329
|
type: 'pause'
|
|
330
|
+
},
|
|
331
|
+
{
|
|
332
|
+
type: 'maze-completion',
|
|
333
|
+
message: 'Lección 4: Firmas de operación completado!',
|
|
334
|
+
unlocked: 'Lección 5: Conjunto de firmas'
|
|
330
335
|
}
|
|
331
336
|
]
|
|
332
337
|
}
|
|
@@ -336,6 +336,11 @@ export const LESSON_5_INTERFACE_SET = {
|
|
|
336
336
|
{
|
|
337
337
|
message: '\n✨ ¡Lección 5 completada! Presiona Enter para salir...',
|
|
338
338
|
type: 'pause'
|
|
339
|
+
},
|
|
340
|
+
{
|
|
341
|
+
type: 'maze-completion',
|
|
342
|
+
message: 'Lección 5: Conjunto de firmas completado!',
|
|
343
|
+
unlocked: 'Lección 6: Énfasis en interfaces'
|
|
339
344
|
}
|
|
340
345
|
]
|
|
341
346
|
}
|
|
@@ -402,6 +402,11 @@ export const LESSON_6_INTERFACE_DESIGN = {
|
|
|
402
402
|
{
|
|
403
403
|
message: '\n✨ ¡Lección 6 completada! Presiona Enter para salir...',
|
|
404
404
|
type: 'pause'
|
|
405
|
+
},
|
|
406
|
+
{
|
|
407
|
+
type: 'maze-completion',
|
|
408
|
+
message: 'Lección 6: Énfasis en interfaces completado!',
|
|
409
|
+
unlocked: 'Lección 7: Definición de objeto'
|
|
405
410
|
}
|
|
406
411
|
]
|
|
407
412
|
}
|
|
@@ -370,6 +370,11 @@ export const LESSON_7_OBJECT_DEFINITION = {
|
|
|
370
370
|
{
|
|
371
371
|
message: '\n✨ ¡Lección 7 completada! Presiona Enter para continuar...',
|
|
372
372
|
type: 'pause'
|
|
373
|
+
},
|
|
374
|
+
{
|
|
375
|
+
type: 'maze-completion',
|
|
376
|
+
message: 'Lección 7: Definición de objeto completado!',
|
|
377
|
+
unlocked: '¡Completaste todos los enunciados fundamentales!'
|
|
373
378
|
}
|
|
374
379
|
]
|
|
375
380
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
// Terminal Básico - Comandos fundamentales de navegación
|
|
2
2
|
export const TERMINAL_BASICO = {
|
|
3
3
|
id: 'terminal-basico',
|
|
4
|
-
title: 'Terminal Básico',
|
|
5
4
|
|
|
6
5
|
intro: {
|
|
7
6
|
definition: 'Sintaxis: estudia el modo en que se combinan las palabras y los distintos grupos que forman para expresar significado.',
|
|
@@ -41,6 +40,11 @@ export const TERMINAL_BASICO = {
|
|
|
41
40
|
explanation: 'mkdir crea un nuevo directorio (carpeta).',
|
|
42
41
|
example: 'mkdir mi-proyecto',
|
|
43
42
|
instruction: 'Crea una carpeta nueva. Prueba: mkdir prueba'
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
type: 'maze-completion',
|
|
46
|
+
message: 'Terminal básico completado!',
|
|
47
|
+
unlocked: 'Git básico'
|
|
44
48
|
}
|
|
45
49
|
]
|
|
46
50
|
}
|
|
@@ -3,6 +3,7 @@ import { ENUNCIADOS_MENU_CONFIG } from '../data/menus.js'
|
|
|
3
3
|
import { LESSONS_BY_ID } from '../data/lessons/index.js'
|
|
4
4
|
import { LessonBuilder } from '../builders/LessonBuilder.js'
|
|
5
5
|
import { createPromptInterface } from '../utils/input.js'
|
|
6
|
+
import { clearConsole } from '../utils/output.js'
|
|
6
7
|
|
|
7
8
|
const UPCOMING_MESSAGE = [
|
|
8
9
|
'\n🚀 Próximamente:',
|
|
@@ -30,6 +31,15 @@ async function runLesson(lessonId, enunciadoTexto, { LessonCtor = LessonBuilder,
|
|
|
30
31
|
return
|
|
31
32
|
}
|
|
32
33
|
|
|
34
|
+
// Show selection confirmation (NEW UX PATTERN)
|
|
35
|
+
console.log(`\nElegiste: Enunciado ${lessonId}`)
|
|
36
|
+
console.log('Cargando...')
|
|
37
|
+
await new Promise((resolve) => delay(resolve, 1000))
|
|
38
|
+
|
|
39
|
+
// Clear screen but keep scroll history (NEW UX PATTERN)
|
|
40
|
+
clearConsole()
|
|
41
|
+
|
|
42
|
+
// Show enunciado text
|
|
33
43
|
console.log(enunciadoTexto)
|
|
34
44
|
await new Promise((resolve) => delay(resolve, 1500))
|
|
35
45
|
|
|
@@ -1,7 +1,18 @@
|
|
|
1
1
|
import { LessonBuilder } from '../builders/LessonBuilder.js'
|
|
2
2
|
import { TERMINAL_BASICO } from '../data/lessons/sintaxis/terminal-basico.js'
|
|
3
|
+
import { clearConsole } from '../utils/output.js'
|
|
3
4
|
|
|
4
5
|
export async function executeSintaxis() {
|
|
6
|
+
// Show selection confirmation
|
|
7
|
+
console.log('\nElegiste: Aprender sintaxis')
|
|
8
|
+
console.log('Cargando...')
|
|
9
|
+
|
|
10
|
+
await new Promise(resolve => setTimeout(resolve, 1000))
|
|
11
|
+
|
|
12
|
+
// Clear screen but keep scroll history
|
|
13
|
+
clearConsole()
|
|
14
|
+
|
|
15
|
+
// Start lesson
|
|
5
16
|
const lesson = new LessonBuilder(TERMINAL_BASICO)
|
|
6
17
|
await lesson.start()
|
|
7
18
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import chalk from 'chalk'
|
|
2
|
+
import { UserState } from './user-state.js'
|
|
3
|
+
import { askQuestion, createPromptInterface } from './input.js'
|
|
4
|
+
|
|
5
|
+
export async function greetUser() {
|
|
6
|
+
const state = UserState.updateLastSeen()
|
|
7
|
+
|
|
8
|
+
// First time user - ask for name
|
|
9
|
+
if (!state.name) {
|
|
10
|
+
const rl = createPromptInterface()
|
|
11
|
+
|
|
12
|
+
console.log('')
|
|
13
|
+
const name = await askQuestion(rl, chalk.cyan('👋 ¿Cómo te llamas? '))
|
|
14
|
+
|
|
15
|
+
if (name && name.trim()) {
|
|
16
|
+
UserState.setName(name.trim())
|
|
17
|
+
console.log(chalk.green(`\n¡Bienvenido, ${name.trim()}!`))
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
rl.close()
|
|
21
|
+
console.log('')
|
|
22
|
+
return
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Returning user - show personalized greeting
|
|
26
|
+
const lessonsCount = state.lessonsCompleted.length
|
|
27
|
+
const greeting = lessonsCount > 0
|
|
28
|
+
? `¡Hola de vuelta, ${state.name}! 📊 ${lessonsCount} ${lessonsCount === 1 ? 'lección completada' : 'lecciones completadas'}`
|
|
29
|
+
: `¡Hola de vuelta, ${state.name}!`
|
|
30
|
+
|
|
31
|
+
console.log(chalk.cyan(`\n${greeting}\n`))
|
|
32
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import fs from 'fs'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
import os from 'os'
|
|
4
|
+
|
|
5
|
+
const STATE_DIR = path.join(os.homedir(), '.creta')
|
|
6
|
+
const STATE_FILE = path.join(STATE_DIR, 'user.json')
|
|
7
|
+
|
|
8
|
+
export class UserState {
|
|
9
|
+
static load() {
|
|
10
|
+
try {
|
|
11
|
+
if (!fs.existsSync(STATE_FILE)) {
|
|
12
|
+
return this.createDefault()
|
|
13
|
+
}
|
|
14
|
+
return JSON.parse(fs.readFileSync(STATE_FILE, 'utf8'))
|
|
15
|
+
} catch {
|
|
16
|
+
return this.createDefault()
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
static save(data) {
|
|
21
|
+
try {
|
|
22
|
+
if (!fs.existsSync(STATE_DIR)) {
|
|
23
|
+
fs.mkdirSync(STATE_DIR, { recursive: true })
|
|
24
|
+
}
|
|
25
|
+
fs.writeFileSync(STATE_FILE, JSON.stringify(data, null, 2))
|
|
26
|
+
return true
|
|
27
|
+
} catch {
|
|
28
|
+
return false
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
static createDefault() {
|
|
33
|
+
return {
|
|
34
|
+
name: null,
|
|
35
|
+
createdAt: new Date().toISOString(),
|
|
36
|
+
lessonsCompleted: [],
|
|
37
|
+
projectsCreated: [],
|
|
38
|
+
discord: null,
|
|
39
|
+
stats: {
|
|
40
|
+
totalSessions: 0,
|
|
41
|
+
lastSeen: null
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
static updateLastSeen() {
|
|
47
|
+
const state = this.load()
|
|
48
|
+
state.stats.lastSeen = new Date().toISOString()
|
|
49
|
+
state.stats.totalSessions += 1
|
|
50
|
+
this.save(state)
|
|
51
|
+
return state
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
static completLesson(lessonId) {
|
|
55
|
+
const state = this.load()
|
|
56
|
+
|
|
57
|
+
// Check if already completed
|
|
58
|
+
const alreadyCompleted = state.lessonsCompleted.find(l => l.id === lessonId)
|
|
59
|
+
if (alreadyCompleted) return state
|
|
60
|
+
|
|
61
|
+
state.lessonsCompleted.push({
|
|
62
|
+
id: lessonId,
|
|
63
|
+
completedAt: new Date().toISOString()
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
this.save(state)
|
|
67
|
+
return state
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
static addProject(projectName, projectType, level = 0) {
|
|
71
|
+
const state = this.load()
|
|
72
|
+
|
|
73
|
+
state.projectsCreated.push({
|
|
74
|
+
name: projectName,
|
|
75
|
+
type: projectType,
|
|
76
|
+
level,
|
|
77
|
+
createdAt: new Date().toISOString()
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
this.save(state)
|
|
81
|
+
return state
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
static setName(name) {
|
|
85
|
+
const state = this.load()
|
|
86
|
+
state.name = name
|
|
87
|
+
this.save(state)
|
|
88
|
+
return state
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
static getPath() {
|
|
92
|
+
return STATE_FILE
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
static hasName() {
|
|
96
|
+
const state = this.load()
|
|
97
|
+
return state.name !== null && state.name.trim() !== ''
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
static getName() {
|
|
101
|
+
const state = this.load()
|
|
102
|
+
return state.name
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
static getStats() {
|
|
106
|
+
const state = this.load()
|
|
107
|
+
return {
|
|
108
|
+
name: state.name,
|
|
109
|
+
lessonsCompleted: state.lessonsCompleted.length,
|
|
110
|
+
projectsCreated: state.projectsCreated.length,
|
|
111
|
+
totalSessions: state.stats.totalSessions,
|
|
112
|
+
lastSeen: state.stats.lastSeen
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|