@icarusmx/creta 1.3.3 → 1.3.5
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 +8 -1576
- package/codex-refactor.txt +13 -0
- package/lib/builders/LessonBuilder.js +228 -0
- package/lib/builders/MenuBuilder.js +154 -0
- package/lib/builders/ProjectBuilder.js +56 -0
- package/lib/cli/index.js +81 -0
- package/lib/commands/help.js +5 -0
- package/lib/constants/paths.js +9 -0
- package/lib/data/enunciados.js +44 -0
- package/lib/data/lessons/index.js +25 -0
- package/lib/data/lessons/lesson1-system-decomposition.js +312 -0
- package/lib/data/lessons/lesson2-object-requests.js +318 -0
- package/lib/data/lessons/lesson3-only-way.js +349 -0
- package/lib/data/lessons/lesson4-operation-signatures.js +332 -0
- package/lib/data/lessons/lesson5-interface-set.js +341 -0
- package/lib/data/lessons/lesson6-interface-design.js +407 -0
- package/lib/data/lessons/lesson7-object-definition.js +375 -0
- package/lib/data/lessons/sintaxis/terminal-basico.js +46 -0
- package/lib/data/menus.js +43 -0
- package/lib/data/messages.js +28 -0
- package/lib/executors/enunciados-executor.js +63 -0
- package/lib/executors/portfolio-executor.js +167 -0
- package/lib/executors/proyectos-executor.js +23 -0
- package/lib/executors/sintaxis-executor.js +7 -0
- package/lib/pr-tutorial.js +6 -0
- package/lib/templates/LevelModifier.js +287 -0
- package/lib/utils/file-utils.js +18 -0
- package/lib/utils/input.js +15 -0
- package/lib/utils/output.js +4 -0
- package/package.json +4 -1
- package/refactor.txt +581 -0
- package/test/enunciados.test.js +72 -0
- package/lessons/lesson1-system-decomposition.js +0 -313
- package/lessons/lesson2-object-requests.js +0 -309
- package/lessons/lesson3-only-way.js +0 -324
- package/lessons/lesson4-operation-signatures.js +0 -319
- package/lessons/lesson5-interface-set.js +0 -326
- package/lessons/lesson6-interface-design.js +0 -391
- package/lessons/lesson7-object-definition.js +0 -300
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
export const LESSON_3_ONLY_WAY = {
|
|
2
|
+
id: 3,
|
|
3
|
+
title: 'LECCIÓN 3: Las solicitudes son la única forma de conseguir que un objeto lleve a cabo una operación',
|
|
4
|
+
subtitle: 'Enfoque práctico: Demostrar que NO hay alternativas a las solicitudes para ejecutar operaciones',
|
|
5
|
+
flow: [
|
|
6
|
+
{
|
|
7
|
+
lines: [
|
|
8
|
+
'\n💡 ¿Por qué ÚNICA forma?',
|
|
9
|
+
'- No puedes forzar a un objeto a ejecutar algo',
|
|
10
|
+
'- No puedes manipular directamente su estado interno',
|
|
11
|
+
'- Toda operación DEBE pasar por una solicitud',
|
|
12
|
+
'- El objeto decide si acepta o rechaza la solicitud'
|
|
13
|
+
],
|
|
14
|
+
type: 'text'
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
message: '\nPresiona Enter para ver qué significa esto en la práctica...',
|
|
18
|
+
type: 'pause'
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
type: 'clear'
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
lines: [
|
|
25
|
+
'🛠️ EJERCICIO PRÁCTICO: Caja Fuerte Digital',
|
|
26
|
+
'==================================================',
|
|
27
|
+
'🎯 Vamos a demostrar por qué las solicitudes son la ÚNICA forma',
|
|
28
|
+
'\nSistema: Una caja fuerte que protege documentos importantes',
|
|
29
|
+
'Objeto: DigitalSafe'
|
|
30
|
+
],
|
|
31
|
+
type: 'text'
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
message: '\nPresiona Enter para ver todas las formas que NO funcionan...',
|
|
35
|
+
type: 'pause'
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
type: 'clear'
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
lines: [
|
|
42
|
+
'❌ FORMAS QUE NO FUNCIONAN',
|
|
43
|
+
'===================================',
|
|
44
|
+
'Veamos todas las cosas que NO puedes hacer:',
|
|
45
|
+
'\n🚫 1. ACCESO DIRECTO A DATOS (Imposible)',
|
|
46
|
+
'// ❌ Esto NO funciona en objetos bien diseñados\n' +
|
|
47
|
+
'class DigitalSafe {\n' +
|
|
48
|
+
' constructor() {\n' +
|
|
49
|
+
' this.#documents = [] // PRIVADO\n' +
|
|
50
|
+
' this.#isLocked = true // PRIVADO\n' +
|
|
51
|
+
' this.#password = "1234" // PRIVADO\n' +
|
|
52
|
+
' }\n' +
|
|
53
|
+
'}\n' +
|
|
54
|
+
'\n' +
|
|
55
|
+
'const safe = new DigitalSafe()\n' +
|
|
56
|
+
'// ❌ ERROR: No puedes acceder a datos privados\n' +
|
|
57
|
+
'console.log(safe.#documents) // SyntaxError!\n' +
|
|
58
|
+
'safe.#isLocked = false // SyntaxError!'
|
|
59
|
+
],
|
|
60
|
+
type: 'text'
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
message: '\nPresiona Enter para ver más formas que no funcionan...',
|
|
64
|
+
type: 'pause'
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
lines: [
|
|
68
|
+
'\n🚫 2. MANIPULACIÓN EXTERNA (Imposible)',
|
|
69
|
+
'// ❌ No puedes forzar cambios desde afuera\n' +
|
|
70
|
+
'safe.documents = ["documento_falso.pdf"] // undefined (no existe)\n' +
|
|
71
|
+
'safe.isLocked = false // undefined (no existe)\n' +
|
|
72
|
+
'safe.forceOpen() // TypeError: no es función\n' +
|
|
73
|
+
'\n' +
|
|
74
|
+
'// ❌ No puedes inyectar funciones\n' +
|
|
75
|
+
'safe.hackOpen = function() { this.#isLocked = false } // No funciona'
|
|
76
|
+
],
|
|
77
|
+
type: 'text'
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
message: '\nPresiona Enter para ver la última forma que no funciona...',
|
|
81
|
+
type: 'pause'
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
lines: [
|
|
85
|
+
'\n🚫 3. HERENCIA FORZADA (Violación de diseño)',
|
|
86
|
+
'// ❌ Incluso extendiendo la clase, no puedes saltarte las reglas\n' +
|
|
87
|
+
'class HackedSafe extends DigitalSafe {\n' +
|
|
88
|
+
' forceOpen() {\n' +
|
|
89
|
+
' // ❌ ERROR: No puedes acceder a campos privados del padre\n' +
|
|
90
|
+
' this.#isLocked = false // SyntaxError!\n' +
|
|
91
|
+
' return this.#documents // SyntaxError!\n' +
|
|
92
|
+
' }\n' +
|
|
93
|
+
'}',
|
|
94
|
+
'\n💡 Observación clave:',
|
|
95
|
+
'El objeto está completamente protegido. No hay forma de',
|
|
96
|
+
'manipularlo externamente sin su consentimiento.'
|
|
97
|
+
],
|
|
98
|
+
type: 'text'
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
message: '\nPresiona Enter para ver la ÚNICA forma que funciona...',
|
|
102
|
+
type: 'pause'
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
type: 'clear'
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
lines: [
|
|
109
|
+
'✅ LA ÚNICA FORMA: SOLICITUDES',
|
|
110
|
+
'========================================',
|
|
111
|
+
'Solo hay UNA manera de que el objeto ejecute operaciones:',
|
|
112
|
+
'\n🔑 IMPLEMENTACIÓN COMPLETA:',
|
|
113
|
+
'class DigitalSafe {\n' +
|
|
114
|
+
' constructor() {\n' +
|
|
115
|
+
' this.#documents = []\n' +
|
|
116
|
+
' this.#isLocked = true\n' +
|
|
117
|
+
' this.#password = "1234"\n' +
|
|
118
|
+
' this.#attempts = 0\n' +
|
|
119
|
+
' this.#maxAttempts = 3\n' +
|
|
120
|
+
' }\n' +
|
|
121
|
+
'\n' +
|
|
122
|
+
' // ✅ SOLICITUD: Intentar desbloquear\n' +
|
|
123
|
+
' unlock(password) {\n' +
|
|
124
|
+
' if (this.#attempts >= this.#maxAttempts) {\n' +
|
|
125
|
+
' return { success: false, message: "Bloqueado por seguridad" }\n' +
|
|
126
|
+
' }\n' +
|
|
127
|
+
'\n' +
|
|
128
|
+
' if (password === this.#password) {\n' +
|
|
129
|
+
' this.#isLocked = false\n' +
|
|
130
|
+
' this.#attempts = 0\n' +
|
|
131
|
+
' return { success: true, message: "Caja fuerte desbloqueada" }\n' +
|
|
132
|
+
' } else {\n' +
|
|
133
|
+
' this.#attempts++\n' +
|
|
134
|
+
' return {\n' +
|
|
135
|
+
' success: false,\n' +
|
|
136
|
+
' message: `Contraseña incorrecta. Intentos restantes: ${this.#maxAttempts - this.#attempts}`\n' +
|
|
137
|
+
' }\n' +
|
|
138
|
+
' }\n' +
|
|
139
|
+
' }\n' +
|
|
140
|
+
'\n' +
|
|
141
|
+
' // ✅ SOLICITUD: Agregar documento\n' +
|
|
142
|
+
' addDocument(document) {\n' +
|
|
143
|
+
' if (this.#isLocked) {\n' +
|
|
144
|
+
' return { success: false, message: "Caja fuerte bloqueada" }\n' +
|
|
145
|
+
' }\n' +
|
|
146
|
+
'\n' +
|
|
147
|
+
' this.#documents.push(document)\n' +
|
|
148
|
+
' return { success: true, message: "Documento agregado" }\n' +
|
|
149
|
+
' }\n' +
|
|
150
|
+
'\n' +
|
|
151
|
+
' // ✅ SOLICITUD: Obtener documentos\n' +
|
|
152
|
+
' getDocuments() {\n' +
|
|
153
|
+
' if (this.#isLocked) {\n' +
|
|
154
|
+
' return { success: false, message: "Caja fuerte bloqueada", documents: [] }\n' +
|
|
155
|
+
' }\n' +
|
|
156
|
+
'\n' +
|
|
157
|
+
' return { success: true, documents: [...this.#documents] }\n' +
|
|
158
|
+
' }\n' +
|
|
159
|
+
'\n' +
|
|
160
|
+
' // ✅ SOLICITUD: Bloquear\n' +
|
|
161
|
+
' lock() {\n' +
|
|
162
|
+
' this.#isLocked = true\n' +
|
|
163
|
+
' return { success: true, message: "Caja fuerte bloqueada" }\n' +
|
|
164
|
+
' }\n' +
|
|
165
|
+
'\n' +
|
|
166
|
+
' // ✅ SOLICITUD: Consultar estado\n' +
|
|
167
|
+
' getStatus() {\n' +
|
|
168
|
+
' return {\n' +
|
|
169
|
+
' isLocked: this.#isLocked,\n' +
|
|
170
|
+
' documentsCount: this.#documents.length,\n' +
|
|
171
|
+
' attemptsRemaining: this.#maxAttempts - this.#attempts\n' +
|
|
172
|
+
' }\n' +
|
|
173
|
+
' }\n' +
|
|
174
|
+
'}'
|
|
175
|
+
],
|
|
176
|
+
type: 'text'
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
message: '\nPresiona Enter para ver por qué esto es la ÚNICA forma...',
|
|
180
|
+
type: 'pause'
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
type: 'clear'
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
lines: [
|
|
187
|
+
'🧠 EL OBJETO DECIDE',
|
|
188
|
+
'=========================',
|
|
189
|
+
'Observa que cada solicitud permite al objeto DECIDIR:',
|
|
190
|
+
'\n🔍 EJEMPLO 1: Solicitud aceptada',
|
|
191
|
+
'const safe = new DigitalSafe()\n' +
|
|
192
|
+
'\n' +
|
|
193
|
+
'// SOLICITUD: unlock con contraseña correcta\n' +
|
|
194
|
+
'const result = safe.unlock("1234")\n' +
|
|
195
|
+
'console.log(result) // { success: true, message: "Caja fuerte desbloqueada" }\n' +
|
|
196
|
+
'\n' +
|
|
197
|
+
'// El OBJETO decidió aceptar la solicitud porque:\n' +
|
|
198
|
+
'// - La contraseña era correcta\n' +
|
|
199
|
+
'// - No se habían agotado los intentos'
|
|
200
|
+
],
|
|
201
|
+
type: 'text'
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
message: '\nPresiona Enter para ver una solicitud rechazada...',
|
|
205
|
+
type: 'pause'
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
lines: [
|
|
209
|
+
'\n🚫 EJEMPLO 2: Solicitud rechazada',
|
|
210
|
+
'// SOLICITUD: addDocument sin desbloquear\n' +
|
|
211
|
+
'const result2 = safe.addDocument("documento_secreto.pdf")\n' +
|
|
212
|
+
'console.log(result2) // { success: false, message: "Caja fuerte bloqueada" }\n' +
|
|
213
|
+
'\n' +
|
|
214
|
+
'// El OBJETO decidió rechazar la solicitud porque:\n' +
|
|
215
|
+
'// - La caja fuerte está bloqueada\n' +
|
|
216
|
+
'// - No cumple las condiciones internas'
|
|
217
|
+
],
|
|
218
|
+
type: 'text'
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
message: '\nPresiona Enter para ver el control total del objeto...',
|
|
222
|
+
type: 'pause'
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
lines: [
|
|
226
|
+
'\n🛡️ EJEMPLO 3: Protección automática',
|
|
227
|
+
'// Intentos fallidos múltiples\n' +
|
|
228
|
+
'safe.unlock("wrong1") // { success: false, message: "Contraseña incorrecta..." }\n' +
|
|
229
|
+
'safe.unlock("wrong2") // { success: false, message: "Contraseña incorrecta..." }\n' +
|
|
230
|
+
'safe.unlock("wrong3") // { success: false, message: "Contraseña incorrecta..." }\n' +
|
|
231
|
+
'safe.unlock("1234") // { success: false, message: "Bloqueado por seguridad" }\n' +
|
|
232
|
+
'\n' +
|
|
233
|
+
'// El OBJETO decidió bloquearse completamente porque:\n' +
|
|
234
|
+
'// - Se agotaron los intentos permitidos\n' +
|
|
235
|
+
'// - Sus reglas internas de seguridad se activaron',
|
|
236
|
+
'\n💡 Punto clave:',
|
|
237
|
+
'Incluso con la contraseña correcta, el objeto puede rechazar',
|
|
238
|
+
'la solicitud si sus condiciones internas no se cumplen.'
|
|
239
|
+
],
|
|
240
|
+
type: 'text'
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
message: '\nPresiona Enter para la demostración completa...',
|
|
244
|
+
type: 'pause'
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
type: 'clear'
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
lines: [
|
|
251
|
+
'🎭 DEMOSTRACIÓN: Flujo Completo',
|
|
252
|
+
'========================================',
|
|
253
|
+
'Veamos un flujo completo de solicitudes:',
|
|
254
|
+
'\n// Crear caja fuerte',
|
|
255
|
+
'const safe = new DigitalSafe()',
|
|
256
|
+
'',
|
|
257
|
+
'// 🔍 SOLICITUD: Consultar estado inicial',
|
|
258
|
+
'console.log(safe.getStatus())',
|
|
259
|
+
'// { isLocked: true, documentsCount: 0, attemptsRemaining: 3 }',
|
|
260
|
+
'',
|
|
261
|
+
'// 🚫 SOLICITUD: Intentar agregar documento (será rechazada)',
|
|
262
|
+
"safe.addDocument('mi_testamento.pdf')",
|
|
263
|
+
"// { success: false, message: 'Caja fuerte bloqueada' }",
|
|
264
|
+
'',
|
|
265
|
+
'// 🔑 SOLICITUD: Desbloquear',
|
|
266
|
+
"safe.unlock('1234')",
|
|
267
|
+
"// { success: true, message: 'Caja fuerte desbloqueada' }",
|
|
268
|
+
'',
|
|
269
|
+
'// ✅ SOLICITUD: Ahora sí agregar documento',
|
|
270
|
+
"safe.addDocument('mi_testamento.pdf')",
|
|
271
|
+
"// { success: true, message: 'Documento agregado' }",
|
|
272
|
+
'',
|
|
273
|
+
"safe.addDocument('contrato_importante.pdf')",
|
|
274
|
+
"// { success: true, message: 'Documento agregado' }"
|
|
275
|
+
],
|
|
276
|
+
type: 'text'
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
message: '\nPresiona Enter para ver la recuperación...',
|
|
280
|
+
type: 'pause'
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
lines: [
|
|
284
|
+
'\n// 📄 SOLICITUD: Recuperar documentos',
|
|
285
|
+
'const docs = safe.getDocuments()',
|
|
286
|
+
'console.log(docs)',
|
|
287
|
+
"// { success: true, documents: ['mi_testamento.pdf', 'contrato_importante.pdf'] }",
|
|
288
|
+
'',
|
|
289
|
+
'// 🔒 SOLICITUD: Bloquear nuevamente',
|
|
290
|
+
'safe.lock()',
|
|
291
|
+
"// { success: true, message: 'Caja fuerte bloqueada' }",
|
|
292
|
+
'',
|
|
293
|
+
'// 🚫 SOLICITUD: Intentar recuperar (será rechazada)',
|
|
294
|
+
'safe.getDocuments()',
|
|
295
|
+
"// { success: false, message: 'Caja fuerte bloqueada', documents: [] }",
|
|
296
|
+
'\n🎯 Observaciones finales:',
|
|
297
|
+
'• Cada operación requirió una SOLICITUD específica',
|
|
298
|
+
'• El objeto controló completamente qué solicitudes aceptar',
|
|
299
|
+
'• No hubo forma de saltarse este mecanismo',
|
|
300
|
+
'• Las solicitudes son verdaderamente la ÚNICA forma'
|
|
301
|
+
],
|
|
302
|
+
type: 'text'
|
|
303
|
+
},
|
|
304
|
+
{
|
|
305
|
+
message: '\nPresiona Enter para la conclusión...',
|
|
306
|
+
type: 'pause'
|
|
307
|
+
},
|
|
308
|
+
{
|
|
309
|
+
type: 'clear'
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
lines: [
|
|
313
|
+
'🎓 CONCLUSIÓN: Solicitudes como Única Forma',
|
|
314
|
+
'==================================================',
|
|
315
|
+
'🎯 Hemos demostrado que:',
|
|
316
|
+
'\n1️⃣ NO es posible acceder directamente a datos privados',
|
|
317
|
+
'2️⃣ NO es posible manipular el objeto externamente',
|
|
318
|
+
'3️⃣ NO es posible forzar operaciones sin consentimiento',
|
|
319
|
+
'4️⃣ Las SOLICITUDES son literalmente la única forma',
|
|
320
|
+
'\n🔑 Características de las solicitudes:',
|
|
321
|
+
'• El objeto DECIDE si acepta o rechaza',
|
|
322
|
+
'• Cada solicitud pasa por validaciones internas',
|
|
323
|
+
'• El objeto mantiene control total de su estado',
|
|
324
|
+
'• Las reglas internas se respetan siempre',
|
|
325
|
+
'\n🛡️ Beneficios de este diseño:',
|
|
326
|
+
'• Seguridad garantizada',
|
|
327
|
+
'• Integridad de datos preservada',
|
|
328
|
+
'• Comportamiento predecible',
|
|
329
|
+
'• Control total del objeto sobre sí mismo',
|
|
330
|
+
'\n📚 Conexión con otras lecciones:',
|
|
331
|
+
'• Lección 1: Los objetos que identificamos tienen este comportamiento',
|
|
332
|
+
'• Lección 2: Esas interacciones son siempre solicitudes',
|
|
333
|
+
'• Lección 3: ✅ Y son la ÚNICA forma de ejecutar operaciones',
|
|
334
|
+
'• Lecciones 4-6: Cómo definir formalmente estas solicitudes',
|
|
335
|
+
'\n💭 Reflexión:',
|
|
336
|
+
'Imagina que tu casa tuviera esta protección: nadie puede entrar',
|
|
337
|
+
'a menos que tú decidas abrir la puerta después de verificar',
|
|
338
|
+
'quién es. Los objetos funcionan exactamente igual.',
|
|
339
|
+
'\n🏆 Ahora entiendes por qué las solicitudes son la ÚNICA forma:',
|
|
340
|
+
'porque los objetos están diseñados para protegerse y decidir.'
|
|
341
|
+
],
|
|
342
|
+
type: 'text'
|
|
343
|
+
},
|
|
344
|
+
{
|
|
345
|
+
message: '\n✨ ¡Lección 3 completada! Presiona Enter para salir...',
|
|
346
|
+
type: 'pause'
|
|
347
|
+
}
|
|
348
|
+
]
|
|
349
|
+
}
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
export const LESSON_4_OPERATION_SIGNATURES = {
|
|
2
|
+
id: 4,
|
|
3
|
+
title: 'LECCIÓN 4: Cada operación declarada por un objeto debe incluir: a) nombre de la operación; b) insumos necesarios para realizar la operación; c) el valor que regresa tras ejecutar la operación. Estos tres elementos constituyen la firma de operación.',
|
|
4
|
+
subtitle: 'Enfoque práctico: Diseñar firmas de operación precisas y completas',
|
|
5
|
+
flow: [
|
|
6
|
+
{
|
|
7
|
+
lines: [
|
|
8
|
+
'\n💡 ¿Por qué son importantes las firmas?',
|
|
9
|
+
'- Define exactamente QUÉ hace la operación (nombre)',
|
|
10
|
+
'- Define exactamente QUÉ necesita (insumos)',
|
|
11
|
+
'- Define exactamente QUÉ devuelve (valor de retorno)',
|
|
12
|
+
'- Permite predecir el comportamiento sin ver la implementación'
|
|
13
|
+
],
|
|
14
|
+
type: 'text'
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
message: '\nPresiona Enter para comenzar con ejemplos prácticos...',
|
|
18
|
+
type: 'pause'
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
type: 'clear'
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
lines: [
|
|
25
|
+
'🛠️ EJERCICIO PRÁCTICO: Calculadora Financiera',
|
|
26
|
+
'==================================================',
|
|
27
|
+
'🎯 Vamos a diseñar firmas de operación precisas y completas',
|
|
28
|
+
'\nSistema: Calculadora para operaciones financieras',
|
|
29
|
+
'Objeto: FinancialCalculator'
|
|
30
|
+
],
|
|
31
|
+
type: 'text'
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
message: '\nPresiona Enter para ver firmas mal diseñadas...',
|
|
35
|
+
type: 'pause'
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
type: 'clear'
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
lines: [
|
|
42
|
+
'❌ FIRMAS MAL DISEÑADAS',
|
|
43
|
+
'==============================',
|
|
44
|
+
'Veamos firmas incompletas o confusas:',
|
|
45
|
+
'\n🚫 PROBLEMA 1: Nombre poco claro',
|
|
46
|
+
"// ❌ ¿Qué hace exactamente 'calc'?\n" +
|
|
47
|
+
'calc(x, y) → ?\n' +
|
|
48
|
+
'\n' +
|
|
49
|
+
"// ❌ ¿Es 'do' suma, resta, multiplicación?\n" +
|
|
50
|
+
'do(amount, rate) → ?',
|
|
51
|
+
'\n🚫 PROBLEMA 2: Insumos ambiguos',
|
|
52
|
+
'// ❌ ¿Qué tipo de datos espera?\n' +
|
|
53
|
+
'calculateInterest(amount, rate) → number\n' +
|
|
54
|
+
'// ¿amount es string o number?\n' +
|
|
55
|
+
'// ¿rate es porcentaje (0.05) o entero (5)?',
|
|
56
|
+
'\n🚫 PROBLEMA 3: Valor de retorno incierto',
|
|
57
|
+
'// ❌ ¿Qué devuelve exactamente?\n' +
|
|
58
|
+
'calculatePayment() → ?\n' +
|
|
59
|
+
'// ¿Un número? ¿Un objeto? ¿null si falla?\n' +
|
|
60
|
+
'\n' +
|
|
61
|
+
'// ❌ ¿Y si hay error?\n' +
|
|
62
|
+
'divideNumbers(a, b) → number\n' +
|
|
63
|
+
'// ¿Qué pasa si b es 0?'
|
|
64
|
+
],
|
|
65
|
+
type: 'text'
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
message: '\nPresiona Enter para ver más problemas...',
|
|
69
|
+
type: 'pause'
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
lines: [
|
|
73
|
+
'\n🚫 PROBLEMA 4: Firmas inconsistentes',
|
|
74
|
+
'// ❌ Métodos similares con firmas diferentes\n' +
|
|
75
|
+
'addMoney(amount) → boolean\n' +
|
|
76
|
+
'subtractCash(value, currency) → {success: boolean, balance: number}\n' +
|
|
77
|
+
'multiply(x, y, precision) → number | null\n' +
|
|
78
|
+
'\n' +
|
|
79
|
+
'// ❌ Imposible predecir qué esperar de cada método',
|
|
80
|
+
'\n💥 Consecuencias:',
|
|
81
|
+
'• Imposible usar el objeto sin ver el código interno',
|
|
82
|
+
'• Errores constantes por malentender los parámetros',
|
|
83
|
+
'• Código frágil y difícil de mantener',
|
|
84
|
+
'• Colaboración entre objetos impredecible'
|
|
85
|
+
],
|
|
86
|
+
type: 'text'
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
message: '\nPresiona Enter para ver cómo debe ser...',
|
|
90
|
+
type: 'pause'
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
type: 'clear'
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
lines: [
|
|
97
|
+
'✅ FIRMAS BIEN DISEÑADAS',
|
|
98
|
+
'===================================',
|
|
99
|
+
'Ahora veamos firmas completas y precisas:',
|
|
100
|
+
'\n🎯 ELEMENTO A: Nombre claro y específico',
|
|
101
|
+
'// ✅ Nombres que describen exactamente la operación\n' +
|
|
102
|
+
'calculateSimpleInterest(principal, rate, time) → number\n' +
|
|
103
|
+
'calculateCompoundInterest(principal, rate, time, frequency) → number\n' +
|
|
104
|
+
'calculateMonthlyPayment(loanAmount, interestRate, termInMonths) → number',
|
|
105
|
+
'\n📥 ELEMENTO B: Insumos específicos y tipados',
|
|
106
|
+
'// ✅ Cada parámetro claramente definido\n' +
|
|
107
|
+
'calculateSimpleInterest(\n' +
|
|
108
|
+
' principal: number, // Monto principal en pesos\n' +
|
|
109
|
+
' rate: number, // Tasa anual como decimal (0.05 = 5%)\n' +
|
|
110
|
+
' time: number // Tiempo en años\n' +
|
|
111
|
+
') → number\n' +
|
|
112
|
+
'\n' +
|
|
113
|
+
'calculateMonthlyPayment(\n' +
|
|
114
|
+
' loanAmount: number, // Monto del préstamo en pesos\n' +
|
|
115
|
+
' interestRate: number, // Tasa mensual como decimal\n' +
|
|
116
|
+
' termInMonths: number // Plazo en meses\n' +
|
|
117
|
+
') → number',
|
|
118
|
+
'\n📤 ELEMENTO C: Valor de retorno específico',
|
|
119
|
+
'// ✅ Especifica exactamente qué devuelve y cuándo\n' +
|
|
120
|
+
'calculateLoanPayment(amount, rate, term) → {\n' +
|
|
121
|
+
' success: boolean,\n' +
|
|
122
|
+
' monthlyPayment: number | null,\n' +
|
|
123
|
+
' totalInterest: number | null,\n' +
|
|
124
|
+
' error: string | null\n' +
|
|
125
|
+
'}\n' +
|
|
126
|
+
'\n' +
|
|
127
|
+
'validateLoanAmount(amount) → {\n' +
|
|
128
|
+
' isValid: boolean,\n' +
|
|
129
|
+
' minimumRequired: number,\n' +
|
|
130
|
+
' maximumAllowed: number\n' +
|
|
131
|
+
'}'
|
|
132
|
+
],
|
|
133
|
+
type: 'text'
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
message: '\nPresiona Enter para ver la implementación completa...',
|
|
137
|
+
type: 'pause'
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
type: 'clear'
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
lines: [
|
|
144
|
+
'🔍 ANÁLISIS DE FIRMAS COMPLETAS',
|
|
145
|
+
'========================================',
|
|
146
|
+
'Examinemos cada elemento de las firmas:',
|
|
147
|
+
'\n💼 EJEMPLO: Calculadora financiera completa',
|
|
148
|
+
'class FinancialCalculator {\n' +
|
|
149
|
+
' // 🎯 FIRMA 1: Interés simple\n' +
|
|
150
|
+
' calculateSimpleInterest(\n' +
|
|
151
|
+
' principal: number, // a) Monto principal en pesos\n' +
|
|
152
|
+
' rate: number, // b) Tasa anual (0.05 = 5%)\n' +
|
|
153
|
+
' time: number // c) Tiempo en años\n' +
|
|
154
|
+
' ): number { // RETORNA: Interés total calculado\n' +
|
|
155
|
+
' return principal * rate * time\n' +
|
|
156
|
+
' }\n' +
|
|
157
|
+
'\n' +
|
|
158
|
+
' // 🎯 FIRMA 2: Validación con resultado estructurado\n' +
|
|
159
|
+
' validateLoanApplication(\n' +
|
|
160
|
+
' income: number, // a) Ingreso mensual\n' +
|
|
161
|
+
' existingDebt: number, // b) Deuda actual\n' +
|
|
162
|
+
' requestedAmount: number // c) Monto solicitado\n' +
|
|
163
|
+
' ): { // RETORNA: Objeto con análisis completo\n' +
|
|
164
|
+
' approved: boolean,\n' +
|
|
165
|
+
' maxLoanAmount: number,\n' +
|
|
166
|
+
' debtToIncomeRatio: number,\n' +
|
|
167
|
+
' reasons: string[]\n' +
|
|
168
|
+
' }\n' +
|
|
169
|
+
'\n' +
|
|
170
|
+
' // 🎯 FIRMA 3: Cálculo que puede fallar\n' +
|
|
171
|
+
' calculateCompoundInterest(\n' +
|
|
172
|
+
' principal: number, // a) Monto principal\n' +
|
|
173
|
+
' rate: number, // b) Tasa anual\n' +
|
|
174
|
+
' time: number, // c) Años\n' +
|
|
175
|
+
' frequency: number // d) Frecuencia de composición\n' +
|
|
176
|
+
' ): number | null { // RETORNA: Resultado o null si hay error\n' +
|
|
177
|
+
' if (principal <= 0 || rate < 0 || time <= 0 || frequency <= 0) {\n' +
|
|
178
|
+
' return null\n' +
|
|
179
|
+
' }\n' +
|
|
180
|
+
' return principal * Math.pow(1 + rate/frequency, frequency * time)\n' +
|
|
181
|
+
' }\n' +
|
|
182
|
+
'}'
|
|
183
|
+
],
|
|
184
|
+
type: 'text'
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
message: '\nPresiona Enter para analizar cada firma...',
|
|
188
|
+
type: 'pause'
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
lines: [
|
|
192
|
+
'\n📋 ANÁLISIS FIRMA POR FIRMA:',
|
|
193
|
+
'\n1️⃣ calculateSimpleInterest:',
|
|
194
|
+
" • NOMBRE: 'calculateSimpleInterest' - claro y específico",
|
|
195
|
+
' • INSUMOS: (principal, rate, time) - 3 números con propósito definido',
|
|
196
|
+
' • RETORNO: number - interés calculado, siempre un número',
|
|
197
|
+
'\n2️⃣ validateLoanApplication:',
|
|
198
|
+
" • NOMBRE: 'validateLoanApplication' - describe validación completa",
|
|
199
|
+
' • INSUMOS: (income, existingDebt, requestedAmount) - datos financieros',
|
|
200
|
+
' • RETORNO: objeto estructurado - análisis completo con múltiples campos',
|
|
201
|
+
'\n3️⃣ calculateCompoundInterest:',
|
|
202
|
+
" • NOMBRE: 'calculateCompoundInterest' - específico del tipo de interés",
|
|
203
|
+
' • INSUMOS: (principal, rate, time, frequency) - 4 parámetros necesarios',
|
|
204
|
+
' • RETORNO: number | null - puede fallar si datos inválidos'
|
|
205
|
+
],
|
|
206
|
+
type: 'text'
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
message: '\nPresiona Enter para ver cómo usar estas firmas...',
|
|
210
|
+
type: 'pause'
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
type: 'clear'
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
lines: [
|
|
217
|
+
'🎭 DEMOSTRACIÓN: Uso con Firmas Claras',
|
|
218
|
+
'=============================================',
|
|
219
|
+
'Veamos cómo las firmas bien diseñadas facilitan el uso:',
|
|
220
|
+
'\n// Crear calculadora',
|
|
221
|
+
'const calc = new FinancialCalculator()',
|
|
222
|
+
'',
|
|
223
|
+
'// 💡 Con las firmas claras, sabemos exactamente qué esperar:',
|
|
224
|
+
'',
|
|
225
|
+
'// FIRMA: calculateSimpleInterest(principal, rate, time) → number',
|
|
226
|
+
'const interest = calc.calculateSimpleInterest(10000, 0.05, 2)',
|
|
227
|
+
'console.log(interest) // 1000 (siempre un número)'
|
|
228
|
+
],
|
|
229
|
+
type: 'text'
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
message: '\nPresiona Enter para ver validación...',
|
|
233
|
+
type: 'pause'
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
lines: [
|
|
237
|
+
'\n// FIRMA: validateLoanApplication(income, debt, amount) → objeto',
|
|
238
|
+
'const validation = calc.validateLoanApplication(5000, 1000, 50000)',
|
|
239
|
+
'console.log(validation)',
|
|
240
|
+
'// {',
|
|
241
|
+
'// approved: false,',
|
|
242
|
+
'// maxLoanAmount: 20000,',
|
|
243
|
+
'// debtToIncomeRatio: 0.2,',
|
|
244
|
+
"// reasons: ['Monto solicitado excede capacidad de pago']",
|
|
245
|
+
'// }',
|
|
246
|
+
'',
|
|
247
|
+
'// 💡 Podemos usar cada campo porque la firma nos dice qué esperar',
|
|
248
|
+
'if (validation.approved) {',
|
|
249
|
+
" console.log('Préstamo aprobado!')",
|
|
250
|
+
'} else {',
|
|
251
|
+
" console.log('Máximo permitido:', validation.maxLoanAmount)",
|
|
252
|
+
" validation.reasons.forEach(reason => console.log('❌', reason))",
|
|
253
|
+
'}'
|
|
254
|
+
],
|
|
255
|
+
type: 'text'
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
message: '\nPresiona Enter para ver manejo de errores...',
|
|
259
|
+
type: 'pause'
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
lines: [
|
|
263
|
+
'\n// FIRMA: calculateCompoundInterest(...) → number | null',
|
|
264
|
+
'const compound1 = calc.calculateCompoundInterest(10000, 0.05, 2, 12)',
|
|
265
|
+
'const compound2 = calc.calculateCompoundInterest(-1000, 0.05, 2, 12)',
|
|
266
|
+
'',
|
|
267
|
+
'// 💡 La firma nos dice que puede retornar null, así que verificamos',
|
|
268
|
+
'if (compound1 !== null) {',
|
|
269
|
+
" console.log('Interés compuesto:', compound1) // 11049.41",
|
|
270
|
+
'}',
|
|
271
|
+
'',
|
|
272
|
+
'if (compound2 === null) {',
|
|
273
|
+
" console.log('Error: datos inválidos') // Se ejecuta",
|
|
274
|
+
'}',
|
|
275
|
+
'\n🎯 Beneficios observados:',
|
|
276
|
+
'• Sabemos exactamente qué pasar a cada método',
|
|
277
|
+
'• Sabemos exactamente qué esperar de vuelta',
|
|
278
|
+
'• Podemos manejar errores apropiadamente',
|
|
279
|
+
'• El código es predecible y confiable'
|
|
280
|
+
],
|
|
281
|
+
type: 'text'
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
message: '\nPresiona Enter para la conclusión...',
|
|
285
|
+
type: 'pause'
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
type: 'clear'
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
lines: [
|
|
292
|
+
'🎓 CONCLUSIÓN: Firmas de Operación',
|
|
293
|
+
'========================================',
|
|
294
|
+
'🎯 Hemos demostrado que cada firma debe incluir:',
|
|
295
|
+
'\n🎯 A) NOMBRE de la operación:',
|
|
296
|
+
' • Claro y específico',
|
|
297
|
+
' • Describe exactamente qué hace',
|
|
298
|
+
" • Ejemplo: 'calculateSimpleInterest' vs 'calc'",
|
|
299
|
+
'\n📥 B) INSUMOS necesarios:',
|
|
300
|
+
' • Tipo y propósito de cada parámetro',
|
|
301
|
+
' • Valores esperados y restricciones',
|
|
302
|
+
" • Ejemplo: 'rate: number (decimal, 0.05 = 5%)'",
|
|
303
|
+
'\n📤 C) VALOR de retorno:',
|
|
304
|
+
' • Tipo exacto que retorna',
|
|
305
|
+
' • Qué significa el valor',
|
|
306
|
+
' • Cómo manejar casos de error',
|
|
307
|
+
" • Ejemplo: 'number | null' vs solo 'number'",
|
|
308
|
+
'\n🌟 Beneficios de firmas completas:',
|
|
309
|
+
'• Predictibilidad total del comportamiento',
|
|
310
|
+
'• Colaboración segura entre objetos',
|
|
311
|
+
'• Código autodocumentado',
|
|
312
|
+
'• Detección temprana de errores',
|
|
313
|
+
'\n📚 Conexión con otras lecciones:',
|
|
314
|
+
'• Lección 1-3: Las operaciones que identificamos necesitan firmas',
|
|
315
|
+
'• Lección 4: ✅ Cada operación tiene nombre, insumos y retorno',
|
|
316
|
+
'• Lección 5: El conjunto de firmas forma la interfaz',
|
|
317
|
+
'• Lección 6: El énfasis está en diseñar estas firmas primero',
|
|
318
|
+
'\n💭 Reflexión:',
|
|
319
|
+
'¿Te imaginas usar una función de la que no sabes qué parámetros',
|
|
320
|
+
'espera ni qué devuelve? ¡Sería imposible! Las firmas son el',
|
|
321
|
+
'contrato que hace posible la colaboración entre objetos.',
|
|
322
|
+
'\n🏆 Ahora entiendes por qué cada operación necesita una firma',
|
|
323
|
+
'completa: es la base de la comunicación predecible.'
|
|
324
|
+
],
|
|
325
|
+
type: 'text'
|
|
326
|
+
},
|
|
327
|
+
{
|
|
328
|
+
message: '\n✨ ¡Lección 4 completada! Presiona Enter para salir...',
|
|
329
|
+
type: 'pause'
|
|
330
|
+
}
|
|
331
|
+
]
|
|
332
|
+
}
|