@icarusmx/creta 0.11.0 → 1.0.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/ascii-logo.txt +11 -0
- package/bin/creta.js +122 -5
- package/lessons/lesson3-only-way.js +327 -0
- package/lessons/lesson4-operation-signatures.js +327 -0
- package/lessons/lesson5-interface-set.js +330 -0
- package/package.json +1 -1
package/ascii-logo.txt
ADDED
package/bin/creta.js
CHANGED
|
@@ -8,6 +8,9 @@ import { fileURLToPath } from 'url'
|
|
|
8
8
|
import { CretaCodeSession } from '../lib/session.js'
|
|
9
9
|
import { Lesson1SystemDecomposition } from '../lessons/lesson1-system-decomposition.js'
|
|
10
10
|
import { Lesson2ObjectRequests } from '../lessons/lesson2-object-requests.js'
|
|
11
|
+
import { Lesson3OnlyWay } from '../lessons/lesson3-only-way.js'
|
|
12
|
+
import { Lesson4OperationSignatures } from '../lessons/lesson4-operation-signatures.js'
|
|
13
|
+
import { Lesson5InterfaceSet } from '../lessons/lesson5-interface-set.js'
|
|
11
14
|
import { Lesson6InterfaceDesign } from '../lessons/lesson6-interface-design.js'
|
|
12
15
|
|
|
13
16
|
const ENUNCIADOS = [
|
|
@@ -52,9 +55,21 @@ const ENUNCIADOS = [
|
|
|
52
55
|
const args = process.argv.slice(2)
|
|
53
56
|
const command = args[0]
|
|
54
57
|
|
|
55
|
-
//
|
|
56
|
-
console.log(
|
|
57
|
-
|
|
58
|
+
// ASCII Logo
|
|
59
|
+
console.log(`
|
|
60
|
+
█████████████████████████████
|
|
61
|
+
█ █ █ █ █ █
|
|
62
|
+
█ █ █ ███ █ ███ █ ███ █ ███ █
|
|
63
|
+
█ █ █ █ █ █
|
|
64
|
+
█ ███ █ ███ █ ███ █ ███ █ █ █
|
|
65
|
+
█ C R E T A █
|
|
66
|
+
█ ███ █ ███ █ ███ █ ███ █ █ █
|
|
67
|
+
█ █ █ █ █ █
|
|
68
|
+
█████████████████████████████
|
|
69
|
+
|
|
70
|
+
🏛️ Salgamos de este laberinto 🏛️
|
|
71
|
+
La escuela de software de icarus.mx
|
|
72
|
+
`)
|
|
58
73
|
|
|
59
74
|
if (!command) {
|
|
60
75
|
// Default behavior: show main menu
|
|
@@ -623,8 +638,20 @@ async function startMainMenuInteractive() {
|
|
|
623
638
|
process.stdout.write('\x1b[2J')
|
|
624
639
|
process.stdout.write('\x1b[H')
|
|
625
640
|
|
|
626
|
-
console.log(
|
|
627
|
-
|
|
641
|
+
console.log(`
|
|
642
|
+
█████████████████████████████
|
|
643
|
+
█ █ █ █ █ █
|
|
644
|
+
█ █ █ ███ █ ███ █ ███ █ ███ █
|
|
645
|
+
█ █ █ █ █ █
|
|
646
|
+
█ ███ █ ███ █ ███ █ ███ █ █ █
|
|
647
|
+
█ C R E T A █
|
|
648
|
+
█ ███ █ ███ █ ███ █ ███ █ █ █
|
|
649
|
+
█ █ █ █ █ █
|
|
650
|
+
█████████████████████████████
|
|
651
|
+
|
|
652
|
+
🏛️ Salgamos de este laberinto 🏛️
|
|
653
|
+
La escuela de software de icarus.mx
|
|
654
|
+
`)
|
|
628
655
|
console.log("Te ofrecemos las siguientes opciones")
|
|
629
656
|
console.log("Usa ↑/↓ para navegar, Enter para seleccionar, 'q' para salir\n")
|
|
630
657
|
|
|
@@ -856,6 +883,48 @@ async function startEnunciadosSelectorInteractive() {
|
|
|
856
883
|
console.log("- Ejercicios prácticos que ilustren el concepto")
|
|
857
884
|
console.log("- Proyectos específicos para internalizar la idea")
|
|
858
885
|
}
|
|
886
|
+
} else if (enunciadoSeleccionado.id === 3) {
|
|
887
|
+
console.log("\n🚀 ¡Lección disponible! Iniciando sesión de estudio dirigida...")
|
|
888
|
+
await new Promise(resolve => setTimeout(resolve, 1500)) // Brief pause
|
|
889
|
+
|
|
890
|
+
try {
|
|
891
|
+
const lesson3 = new Lesson3OnlyWay()
|
|
892
|
+
await lesson3.start()
|
|
893
|
+
} catch (error) {
|
|
894
|
+
console.error("\n❌ Error al ejecutar la lección:", error.message)
|
|
895
|
+
console.log("\n🚀 Próximamente:")
|
|
896
|
+
console.log("- Sesiones de estudio dirigidas basadas en este enunciado")
|
|
897
|
+
console.log("- Ejercicios prácticos que ilustren el concepto")
|
|
898
|
+
console.log("- Proyectos específicos para internalizar la idea")
|
|
899
|
+
}
|
|
900
|
+
} else if (enunciadoSeleccionado.id === 4) {
|
|
901
|
+
console.log("\n🚀 ¡Lección disponible! Iniciando sesión de estudio dirigida...")
|
|
902
|
+
await new Promise(resolve => setTimeout(resolve, 1500)) // Brief pause
|
|
903
|
+
|
|
904
|
+
try {
|
|
905
|
+
const lesson4 = new Lesson4OperationSignatures()
|
|
906
|
+
await lesson4.start()
|
|
907
|
+
} catch (error) {
|
|
908
|
+
console.error("\n❌ Error al ejecutar la lección:", error.message)
|
|
909
|
+
console.log("\n🚀 Próximamente:")
|
|
910
|
+
console.log("- Sesiones de estudio dirigidas basadas en este enunciado")
|
|
911
|
+
console.log("- Ejercicios prácticos que ilustren el concepto")
|
|
912
|
+
console.log("- Proyectos específicos para internalizar la idea")
|
|
913
|
+
}
|
|
914
|
+
} else if (enunciadoSeleccionado.id === 5) {
|
|
915
|
+
console.log("\n🚀 ¡Lección disponible! Iniciando sesión de estudio dirigida...")
|
|
916
|
+
await new Promise(resolve => setTimeout(resolve, 1500)) // Brief pause
|
|
917
|
+
|
|
918
|
+
try {
|
|
919
|
+
const lesson5 = new Lesson5InterfaceSet()
|
|
920
|
+
await lesson5.start()
|
|
921
|
+
} catch (error) {
|
|
922
|
+
console.error("\n❌ Error al ejecutar la lección:", error.message)
|
|
923
|
+
console.log("\n🚀 Próximamente:")
|
|
924
|
+
console.log("- Sesiones de estudio dirigidas basadas en este enunciado")
|
|
925
|
+
console.log("- Ejercicios prácticos que ilustren el concepto")
|
|
926
|
+
console.log("- Proyectos específicos para internalizar la idea")
|
|
927
|
+
}
|
|
859
928
|
} else if (enunciadoSeleccionado.id === 6) {
|
|
860
929
|
console.log("\n🚀 ¡Lección disponible! Iniciando sesión de estudio dirigida...")
|
|
861
930
|
await new Promise(resolve => setTimeout(resolve, 1500)) // Brief pause
|
|
@@ -987,6 +1056,54 @@ async function startEnunciadosSelectorFallback() {
|
|
|
987
1056
|
console.log("- Proyectos específicos para internalizar la idea")
|
|
988
1057
|
}
|
|
989
1058
|
return
|
|
1059
|
+
} else if (enunciadoSeleccionado.id === 3) {
|
|
1060
|
+
console.log("\n🚀 ¡Lección disponible! Iniciando sesión de estudio dirigida...")
|
|
1061
|
+
rl.close()
|
|
1062
|
+
await new Promise(resolve => setTimeout(resolve, 1500)) // Brief pause
|
|
1063
|
+
|
|
1064
|
+
try {
|
|
1065
|
+
const lesson3 = new Lesson3OnlyWay()
|
|
1066
|
+
await lesson3.start()
|
|
1067
|
+
} catch (error) {
|
|
1068
|
+
console.error("\n❌ Error al ejecutar la lección:", error.message)
|
|
1069
|
+
console.log("\n🚀 Próximamente:")
|
|
1070
|
+
console.log("- Sesiones de estudio dirigidas basadas en este enunciado")
|
|
1071
|
+
console.log("- Ejercicios prácticos que ilustren el concepto")
|
|
1072
|
+
console.log("- Proyectos específicos para internalizar la idea")
|
|
1073
|
+
}
|
|
1074
|
+
return
|
|
1075
|
+
} else if (enunciadoSeleccionado.id === 4) {
|
|
1076
|
+
console.log("\n🚀 ¡Lección disponible! Iniciando sesión de estudio dirigida...")
|
|
1077
|
+
rl.close()
|
|
1078
|
+
await new Promise(resolve => setTimeout(resolve, 1500)) // Brief pause
|
|
1079
|
+
|
|
1080
|
+
try {
|
|
1081
|
+
const lesson4 = new Lesson4OperationSignatures()
|
|
1082
|
+
await lesson4.start()
|
|
1083
|
+
} catch (error) {
|
|
1084
|
+
console.error("\n❌ Error al ejecutar la lección:", error.message)
|
|
1085
|
+
console.log("\n🚀 Próximamente:")
|
|
1086
|
+
console.log("- Sesiones de estudio dirigidas basadas en este enunciado")
|
|
1087
|
+
console.log("- Ejercicios prácticos que ilustren el concepto")
|
|
1088
|
+
console.log("- Proyectos específicos para internalizar la idea")
|
|
1089
|
+
}
|
|
1090
|
+
return
|
|
1091
|
+
} else if (enunciadoSeleccionado.id === 5) {
|
|
1092
|
+
console.log("\n🚀 ¡Lección disponible! Iniciando sesión de estudio dirigida...")
|
|
1093
|
+
rl.close()
|
|
1094
|
+
await new Promise(resolve => setTimeout(resolve, 1500)) // Brief pause
|
|
1095
|
+
|
|
1096
|
+
try {
|
|
1097
|
+
const lesson5 = new Lesson5InterfaceSet()
|
|
1098
|
+
await lesson5.start()
|
|
1099
|
+
} catch (error) {
|
|
1100
|
+
console.error("\n❌ Error al ejecutar la lección:", error.message)
|
|
1101
|
+
console.log("\n🚀 Próximamente:")
|
|
1102
|
+
console.log("- Sesiones de estudio dirigidas basadas en este enunciado")
|
|
1103
|
+
console.log("- Ejercicios prácticos que ilustren el concepto")
|
|
1104
|
+
console.log("- Proyectos específicos para internalizar la idea")
|
|
1105
|
+
}
|
|
1106
|
+
return
|
|
990
1107
|
} else if (enunciadoSeleccionado.id === 6) {
|
|
991
1108
|
console.log("\n🚀 ¡Lección disponible! Iniciando sesión de estudio dirigida...")
|
|
992
1109
|
rl.close()
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// LECCIÓN 3: Las solicitudes son la única forma de conseguir que un objeto lleve a cabo una operación
|
|
4
|
+
// Enfoque práctico: Demostrar que NO hay alternativas a las solicitudes para ejecutar operaciones
|
|
5
|
+
|
|
6
|
+
import { createInterface } from 'readline'
|
|
7
|
+
|
|
8
|
+
export class Lesson3OnlyWay {
|
|
9
|
+
constructor() {
|
|
10
|
+
this.rl = createInterface({
|
|
11
|
+
input: process.stdin,
|
|
12
|
+
output: process.stdout
|
|
13
|
+
})
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async start() {
|
|
17
|
+
console.log("🎯 LECCIÓN 3: Solicitudes como Única Forma")
|
|
18
|
+
console.log("=" .repeat(50))
|
|
19
|
+
console.log("📚 Enunciado: Las solicitudes son la única forma de conseguir que un objeto lleve a cabo una operación.")
|
|
20
|
+
console.log("\n💡 ¿Por qué ÚNICA forma?")
|
|
21
|
+
console.log("- No puedes forzar a un objeto a ejecutar algo")
|
|
22
|
+
console.log("- No puedes manipular directamente su estado interno")
|
|
23
|
+
console.log("- Toda operación DEBE pasar por una solicitud")
|
|
24
|
+
console.log("- El objeto decide si acepta o rechaza la solicitud")
|
|
25
|
+
|
|
26
|
+
await this.waitForEnter("\nPresiona Enter para ver qué significa esto en la práctica...")
|
|
27
|
+
|
|
28
|
+
await this.practicalExercise()
|
|
29
|
+
await this.conclusion()
|
|
30
|
+
|
|
31
|
+
this.rl.close()
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async practicalExercise() {
|
|
35
|
+
console.clear()
|
|
36
|
+
console.log("🛠️ EJERCICIO PRÁCTICO: Caja Fuerte Digital")
|
|
37
|
+
console.log("=" .repeat(50))
|
|
38
|
+
console.log("🎯 Vamos a demostrar por qué las solicitudes son la ÚNICA forma")
|
|
39
|
+
console.log("\nSistema: Una caja fuerte que protege documentos importantes")
|
|
40
|
+
console.log("Objeto: DigitalSafe")
|
|
41
|
+
|
|
42
|
+
await this.waitForEnter("\nPresiona Enter para ver todas las formas que NO funcionan...")
|
|
43
|
+
|
|
44
|
+
await this.step1_ShowWhatDoesntWork()
|
|
45
|
+
await this.step2_ShowOnlyWay()
|
|
46
|
+
await this.step3_ObjectDecision()
|
|
47
|
+
await this.step4_CompleteDemo()
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async step1_ShowWhatDoesntWork() {
|
|
51
|
+
console.clear()
|
|
52
|
+
console.log("❌ FORMAS QUE NO FUNCIONAN")
|
|
53
|
+
console.log("=" .repeat(35))
|
|
54
|
+
console.log("Veamos todas las cosas que NO puedes hacer:")
|
|
55
|
+
|
|
56
|
+
console.log("\n🚫 1. ACCESO DIRECTO A DATOS (Imposible)")
|
|
57
|
+
console.log(`// ❌ Esto NO funciona en objetos bien diseñados
|
|
58
|
+
class DigitalSafe {
|
|
59
|
+
constructor() {
|
|
60
|
+
this.#documents = [] // PRIVADO
|
|
61
|
+
this.#isLocked = true // PRIVADO
|
|
62
|
+
this.#password = "1234" // PRIVADO
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const safe = new DigitalSafe()
|
|
67
|
+
// ❌ ERROR: No puedes acceder a datos privados
|
|
68
|
+
console.log(safe.#documents) // SyntaxError!
|
|
69
|
+
safe.#isLocked = false // SyntaxError!`)
|
|
70
|
+
|
|
71
|
+
await this.waitForEnter("\nPresiona Enter para ver más formas que no funcionan...")
|
|
72
|
+
|
|
73
|
+
console.log("\n🚫 2. MANIPULACIÓN EXTERNA (Imposible)")
|
|
74
|
+
console.log(`// ❌ No puedes forzar cambios desde afuera
|
|
75
|
+
safe.documents = ["documento_falso.pdf"] // undefined (no existe)
|
|
76
|
+
safe.isLocked = false // undefined (no existe)
|
|
77
|
+
safe.forceOpen() // TypeError: no es función
|
|
78
|
+
|
|
79
|
+
// ❌ No puedes inyectar funciones
|
|
80
|
+
safe.hackOpen = function() { this.#isLocked = false } // No funciona`)
|
|
81
|
+
|
|
82
|
+
await this.waitForEnter("\nPresiona Enter para ver la última forma que no funciona...")
|
|
83
|
+
|
|
84
|
+
console.log("\n🚫 3. HERENCIA FORZADA (Violación de diseño)")
|
|
85
|
+
console.log(`// ❌ Incluso extendiendo la clase, no puedes saltarte las reglas
|
|
86
|
+
class HackedSafe extends DigitalSafe {
|
|
87
|
+
forceOpen() {
|
|
88
|
+
// ❌ ERROR: No puedes acceder a campos privados del padre
|
|
89
|
+
this.#isLocked = false // SyntaxError!
|
|
90
|
+
return this.#documents // SyntaxError!
|
|
91
|
+
}
|
|
92
|
+
}`)
|
|
93
|
+
|
|
94
|
+
console.log("\n💡 Observación clave:")
|
|
95
|
+
console.log("El objeto está completamente protegido. No hay forma de")
|
|
96
|
+
console.log("manipularlo externamente sin su consentimiento.")
|
|
97
|
+
|
|
98
|
+
await this.waitForEnter("\nPresiona Enter para ver la ÚNICA forma que funciona...")
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async step2_ShowOnlyWay() {
|
|
102
|
+
console.clear()
|
|
103
|
+
console.log("✅ LA ÚNICA FORMA: SOLICITUDES")
|
|
104
|
+
console.log("=" .repeat(40))
|
|
105
|
+
console.log("Solo hay UNA manera de que el objeto ejecute operaciones:")
|
|
106
|
+
|
|
107
|
+
console.log("\n🔑 IMPLEMENTACIÓN COMPLETA:")
|
|
108
|
+
console.log(`class DigitalSafe {
|
|
109
|
+
constructor() {
|
|
110
|
+
this.#documents = []
|
|
111
|
+
this.#isLocked = true
|
|
112
|
+
this.#password = "1234"
|
|
113
|
+
this.#attempts = 0
|
|
114
|
+
this.#maxAttempts = 3
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ✅ SOLICITUD: Intentar desbloquear
|
|
118
|
+
unlock(password) {
|
|
119
|
+
if (this.#attempts >= this.#maxAttempts) {
|
|
120
|
+
return { success: false, message: "Bloqueado por seguridad" }
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (password === this.#password) {
|
|
124
|
+
this.#isLocked = false
|
|
125
|
+
this.#attempts = 0
|
|
126
|
+
return { success: true, message: "Caja fuerte desbloqueada" }
|
|
127
|
+
} else {
|
|
128
|
+
this.#attempts++
|
|
129
|
+
return {
|
|
130
|
+
success: false,
|
|
131
|
+
message: \`Contraseña incorrecta. Intentos restantes: \${this.#maxAttempts - this.#attempts}\`
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// ✅ SOLICITUD: Agregar documento
|
|
137
|
+
addDocument(document) {
|
|
138
|
+
if (this.#isLocked) {
|
|
139
|
+
return { success: false, message: "Caja fuerte bloqueada" }
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
this.#documents.push(document)
|
|
143
|
+
return { success: true, message: "Documento agregado" }
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// ✅ SOLICITUD: Obtener documentos
|
|
147
|
+
getDocuments() {
|
|
148
|
+
if (this.#isLocked) {
|
|
149
|
+
return { success: false, message: "Caja fuerte bloqueada", documents: [] }
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return { success: true, documents: [...this.#documents] }
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// ✅ SOLICITUD: Bloquear
|
|
156
|
+
lock() {
|
|
157
|
+
this.#isLocked = true
|
|
158
|
+
return { success: true, message: "Caja fuerte bloqueada" }
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// ✅ SOLICITUD: Consultar estado
|
|
162
|
+
getStatus() {
|
|
163
|
+
return {
|
|
164
|
+
isLocked: this.#isLocked,
|
|
165
|
+
documentsCount: this.#documents.length,
|
|
166
|
+
attemptsRemaining: this.#maxAttempts - this.#attempts
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}`)
|
|
170
|
+
|
|
171
|
+
await this.waitForEnter("\nPresiona Enter para ver por qué esto es la ÚNICA forma...")
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
async step3_ObjectDecision() {
|
|
175
|
+
console.clear()
|
|
176
|
+
console.log("🧠 EL OBJETO DECIDE")
|
|
177
|
+
console.log("=" .repeat(25))
|
|
178
|
+
console.log("Observa que cada solicitud permite al objeto DECIDIR:")
|
|
179
|
+
|
|
180
|
+
console.log("\n🔍 EJEMPLO 1: Solicitud aceptada")
|
|
181
|
+
console.log(`const safe = new DigitalSafe()
|
|
182
|
+
|
|
183
|
+
// SOLICITUD: unlock con contraseña correcta
|
|
184
|
+
const result = safe.unlock("1234")
|
|
185
|
+
console.log(result) // { success: true, message: "Caja fuerte desbloqueada" }
|
|
186
|
+
|
|
187
|
+
// El OBJETO decidió aceptar la solicitud porque:
|
|
188
|
+
// - La contraseña era correcta
|
|
189
|
+
// - No se habían agotado los intentos`)
|
|
190
|
+
|
|
191
|
+
await this.waitForEnter("\nPresiona Enter para ver una solicitud rechazada...")
|
|
192
|
+
|
|
193
|
+
console.log("\n🚫 EJEMPLO 2: Solicitud rechazada")
|
|
194
|
+
console.log(`// SOLICITUD: addDocument sin desbloquear
|
|
195
|
+
const result2 = safe.addDocument("documento_secreto.pdf")
|
|
196
|
+
console.log(result2) // { success: false, message: "Caja fuerte bloqueada" }
|
|
197
|
+
|
|
198
|
+
// El OBJETO decidió rechazar la solicitud porque:
|
|
199
|
+
// - La caja fuerte está bloqueada
|
|
200
|
+
// - No cumple las condiciones internas`)
|
|
201
|
+
|
|
202
|
+
await this.waitForEnter("\nPresiona Enter para ver el control total del objeto...")
|
|
203
|
+
|
|
204
|
+
console.log("\n🛡️ EJEMPLO 3: Protección automática")
|
|
205
|
+
console.log(`// Intentos fallidos múltiples
|
|
206
|
+
safe.unlock("wrong1") // { success: false, message: "Contraseña incorrecta..." }
|
|
207
|
+
safe.unlock("wrong2") // { success: false, message: "Contraseña incorrecta..." }
|
|
208
|
+
safe.unlock("wrong3") // { success: false, message: "Contraseña incorrecta..." }
|
|
209
|
+
safe.unlock("1234") // { success: false, message: "Bloqueado por seguridad" }
|
|
210
|
+
|
|
211
|
+
// El OBJETO decidió bloquearse completamente porque:
|
|
212
|
+
// - Se agotaron los intentos permitidos
|
|
213
|
+
// - Sus reglas internas de seguridad se activaron`)
|
|
214
|
+
|
|
215
|
+
console.log("\n💡 Punto clave:")
|
|
216
|
+
console.log("Incluso con la contraseña correcta, el objeto puede rechazar")
|
|
217
|
+
console.log("la solicitud si sus condiciones internas no se cumplen.")
|
|
218
|
+
|
|
219
|
+
await this.waitForEnter("\nPresiona Enter para la demostración completa...")
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async step4_CompleteDemo() {
|
|
223
|
+
console.clear()
|
|
224
|
+
console.log("🎭 DEMOSTRACIÓN: Flujo Completo")
|
|
225
|
+
console.log("=" .repeat(40))
|
|
226
|
+
console.log("Veamos un flujo completo de solicitudes:")
|
|
227
|
+
|
|
228
|
+
console.log("\n// Crear caja fuerte")
|
|
229
|
+
console.log("const safe = new DigitalSafe()")
|
|
230
|
+
console.log("")
|
|
231
|
+
console.log("// 🔍 SOLICITUD: Consultar estado inicial")
|
|
232
|
+
console.log("console.log(safe.getStatus())")
|
|
233
|
+
console.log("// { isLocked: true, documentsCount: 0, attemptsRemaining: 3 }")
|
|
234
|
+
console.log("")
|
|
235
|
+
console.log("// 🚫 SOLICITUD: Intentar agregar documento (será rechazada)")
|
|
236
|
+
console.log("safe.addDocument('mi_testamento.pdf')")
|
|
237
|
+
console.log("// { success: false, message: 'Caja fuerte bloqueada' }")
|
|
238
|
+
console.log("")
|
|
239
|
+
console.log("// 🔑 SOLICITUD: Desbloquear")
|
|
240
|
+
console.log("safe.unlock('1234')")
|
|
241
|
+
console.log("// { success: true, message: 'Caja fuerte desbloqueada' }")
|
|
242
|
+
console.log("")
|
|
243
|
+
console.log("// ✅ SOLICITUD: Ahora sí agregar documento")
|
|
244
|
+
console.log("safe.addDocument('mi_testamento.pdf')")
|
|
245
|
+
console.log("// { success: true, message: 'Documento agregado' }")
|
|
246
|
+
console.log("")
|
|
247
|
+
console.log("safe.addDocument('contrato_importante.pdf')")
|
|
248
|
+
console.log("// { success: true, message: 'Documento agregado' }")
|
|
249
|
+
|
|
250
|
+
await this.waitForEnter("\nPresiona Enter para ver la recuperación...")
|
|
251
|
+
|
|
252
|
+
console.log("\n// 📄 SOLICITUD: Recuperar documentos")
|
|
253
|
+
console.log("const docs = safe.getDocuments()")
|
|
254
|
+
console.log("console.log(docs)")
|
|
255
|
+
console.log("// { success: true, documents: ['mi_testamento.pdf', 'contrato_importante.pdf'] }")
|
|
256
|
+
console.log("")
|
|
257
|
+
console.log("// 🔒 SOLICITUD: Bloquear nuevamente")
|
|
258
|
+
console.log("safe.lock()")
|
|
259
|
+
console.log("// { success: true, message: 'Caja fuerte bloqueada' }")
|
|
260
|
+
console.log("")
|
|
261
|
+
console.log("// 🚫 SOLICITUD: Intentar recuperar (será rechazada)")
|
|
262
|
+
console.log("safe.getDocuments()")
|
|
263
|
+
console.log("// { success: false, message: 'Caja fuerte bloqueada', documents: [] }")
|
|
264
|
+
|
|
265
|
+
console.log("\n🎯 Observaciones finales:")
|
|
266
|
+
console.log("• Cada operación requirió una SOLICITUD específica")
|
|
267
|
+
console.log("• El objeto controló completamente qué solicitudes aceptar")
|
|
268
|
+
console.log("• No hubo forma de saltarse este mecanismo")
|
|
269
|
+
console.log("• Las solicitudes son verdaderamente la ÚNICA forma")
|
|
270
|
+
|
|
271
|
+
await this.waitForEnter("\nPresiona Enter para la conclusión...")
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
async conclusion() {
|
|
275
|
+
console.clear()
|
|
276
|
+
console.log("🎓 CONCLUSIÓN: Solicitudes como Única Forma")
|
|
277
|
+
console.log("=" .repeat(50))
|
|
278
|
+
console.log("🎯 Hemos demostrado que:")
|
|
279
|
+
|
|
280
|
+
console.log("\n1️⃣ NO es posible acceder directamente a datos privados")
|
|
281
|
+
console.log("2️⃣ NO es posible manipular el objeto externamente")
|
|
282
|
+
console.log("3️⃣ NO es posible forzar operaciones sin consentimiento")
|
|
283
|
+
console.log("4️⃣ Las SOLICITUDES son literalmente la única forma")
|
|
284
|
+
|
|
285
|
+
console.log("\n🔑 Características de las solicitudes:")
|
|
286
|
+
console.log("• El objeto DECIDE si acepta o rechaza")
|
|
287
|
+
console.log("• Cada solicitud pasa por validaciones internas")
|
|
288
|
+
console.log("• El objeto mantiene control total de su estado")
|
|
289
|
+
console.log("• Las reglas internas se respetan siempre")
|
|
290
|
+
|
|
291
|
+
console.log("\n🛡️ Beneficios de este diseño:")
|
|
292
|
+
console.log("• Seguridad garantizada")
|
|
293
|
+
console.log("• Integridad de datos preservada")
|
|
294
|
+
console.log("• Comportamiento predecible")
|
|
295
|
+
console.log("• Control total del objeto sobre sí mismo")
|
|
296
|
+
|
|
297
|
+
console.log("\n📚 Conexión con otras lecciones:")
|
|
298
|
+
console.log("• Lección 1: Los objetos que identificamos tienen este comportamiento")
|
|
299
|
+
console.log("• Lección 2: Esas interacciones son siempre solicitudes")
|
|
300
|
+
console.log("• Lección 3: ✅ Y son la ÚNICA forma de ejecutar operaciones")
|
|
301
|
+
console.log("• Lecciones 4-6: Cómo definir formalmente estas solicitudes")
|
|
302
|
+
|
|
303
|
+
console.log("\n💭 Reflexión:")
|
|
304
|
+
console.log("Imagina que tu casa tuviera esta protección: nadie puede entrar")
|
|
305
|
+
console.log("a menos que tú decidas abrir la puerta después de verificar")
|
|
306
|
+
console.log("quién es. Los objetos funcionan exactamente igual.")
|
|
307
|
+
|
|
308
|
+
console.log("\n🏆 Ahora entiendes por qué las solicitudes son la ÚNICA forma:")
|
|
309
|
+
console.log("porque los objetos están diseñados para protegerse y decidir.")
|
|
310
|
+
|
|
311
|
+
await this.waitForEnter("\n✨ ¡Lección 3 completada! Presiona Enter para salir...")
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
async waitForEnter(message) {
|
|
315
|
+
return new Promise((resolve) => {
|
|
316
|
+
this.rl.question(message, () => {
|
|
317
|
+
resolve()
|
|
318
|
+
})
|
|
319
|
+
})
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Para usar la lección independientemente
|
|
324
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
325
|
+
const lesson = new Lesson3OnlyWay()
|
|
326
|
+
await lesson.start()
|
|
327
|
+
}
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// 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
|
+
// Enfoque práctico: Diseñar firmas de operación precisas y completas
|
|
5
|
+
|
|
6
|
+
import { createInterface } from 'readline'
|
|
7
|
+
|
|
8
|
+
export class Lesson4OperationSignatures {
|
|
9
|
+
constructor() {
|
|
10
|
+
this.rl = createInterface({
|
|
11
|
+
input: process.stdin,
|
|
12
|
+
output: process.stdout
|
|
13
|
+
})
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async start() {
|
|
17
|
+
console.log("🎯 LECCIÓN 4: Firmas de Operación")
|
|
18
|
+
console.log("=" .repeat(50))
|
|
19
|
+
console.log("📚 Enunciado: Cada operación declarada por un objeto debe incluir:")
|
|
20
|
+
console.log(" a) nombre de la operación")
|
|
21
|
+
console.log(" b) insumos necesarios para realizar la operación")
|
|
22
|
+
console.log(" c) el valor que regresa tras ejecutar la operación")
|
|
23
|
+
console.log(" Estos tres elementos constituyen la firma de operación.")
|
|
24
|
+
|
|
25
|
+
console.log("\n💡 ¿Por qué son importantes las firmas?")
|
|
26
|
+
console.log("- Define exactamente QUÉ hace la operación (nombre)")
|
|
27
|
+
console.log("- Define exactamente QUÉ necesita (insumos)")
|
|
28
|
+
console.log("- Define exactamente QUÉ devuelve (valor de retorno)")
|
|
29
|
+
console.log("- Permite predecir el comportamiento sin ver la implementación")
|
|
30
|
+
|
|
31
|
+
await this.waitForEnter("\nPresiona Enter para comenzar con ejemplos prácticos...")
|
|
32
|
+
|
|
33
|
+
await this.practicalExercise()
|
|
34
|
+
await this.conclusion()
|
|
35
|
+
|
|
36
|
+
this.rl.close()
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async practicalExercise() {
|
|
40
|
+
console.clear()
|
|
41
|
+
console.log("🛠️ EJERCICIO PRÁCTICO: Calculadora Financiera")
|
|
42
|
+
console.log("=" .repeat(50))
|
|
43
|
+
console.log("🎯 Vamos a diseñar firmas de operación precisas y completas")
|
|
44
|
+
console.log("\nSistema: Calculadora para operaciones financieras")
|
|
45
|
+
console.log("Objeto: FinancialCalculator")
|
|
46
|
+
|
|
47
|
+
await this.waitForEnter("\nPresiona Enter para ver firmas mal diseñadas...")
|
|
48
|
+
|
|
49
|
+
await this.step1_BadSignatures()
|
|
50
|
+
await this.step2_GoodSignatures()
|
|
51
|
+
await this.step3_SignatureAnalysis()
|
|
52
|
+
await this.step4_CompleteExample()
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async step1_BadSignatures() {
|
|
56
|
+
console.clear()
|
|
57
|
+
console.log("❌ FIRMAS MAL DISEÑADAS")
|
|
58
|
+
console.log("=" .repeat(30))
|
|
59
|
+
console.log("Veamos firmas incompletas o confusas:")
|
|
60
|
+
|
|
61
|
+
console.log("\n🚫 PROBLEMA 1: Nombre poco claro")
|
|
62
|
+
console.log(`// ❌ ¿Qué hace exactamente 'calc'?
|
|
63
|
+
calc(x, y) → ?
|
|
64
|
+
|
|
65
|
+
// ❌ ¿Es 'do' suma, resta, multiplicación?
|
|
66
|
+
do(amount, rate) → ?`)
|
|
67
|
+
|
|
68
|
+
console.log("\n🚫 PROBLEMA 2: Insumos ambiguos")
|
|
69
|
+
console.log(`// ❌ ¿Qué tipo de datos espera?
|
|
70
|
+
calculateInterest(amount, rate) → number
|
|
71
|
+
// ¿amount es string o number?
|
|
72
|
+
// ¿rate es porcentaje (0.05) o entero (5)?`)
|
|
73
|
+
|
|
74
|
+
console.log("\n🚫 PROBLEMA 3: Valor de retorno incierto")
|
|
75
|
+
console.log(`// ❌ ¿Qué devuelve exactamente?
|
|
76
|
+
calculatePayment() → ?
|
|
77
|
+
// ¿Un número? ¿Un objeto? ¿null si falla?
|
|
78
|
+
|
|
79
|
+
// ❌ ¿Y si hay error?
|
|
80
|
+
divideNumbers(a, b) → number
|
|
81
|
+
// ¿Qué pasa si b es 0?`)
|
|
82
|
+
|
|
83
|
+
await this.waitForEnter("\nPresiona Enter para ver más problemas...")
|
|
84
|
+
|
|
85
|
+
console.log("\n🚫 PROBLEMA 4: Firmas inconsistentes")
|
|
86
|
+
console.log(`// ❌ Métodos similares con firmas diferentes
|
|
87
|
+
addMoney(amount) → boolean
|
|
88
|
+
subtractCash(value, currency) → {success: boolean, balance: number}
|
|
89
|
+
multiply(x, y, precision) → number | null
|
|
90
|
+
|
|
91
|
+
// ❌ Imposible predecir qué esperar de cada método`)
|
|
92
|
+
|
|
93
|
+
console.log("\n💥 Consecuencias:")
|
|
94
|
+
console.log("• Imposible usar el objeto sin ver el código interno")
|
|
95
|
+
console.log("• Errores constantes por malentender los parámetros")
|
|
96
|
+
console.log("• Código frágil y difícil de mantener")
|
|
97
|
+
console.log("• Colaboración entre objetos impredecible")
|
|
98
|
+
|
|
99
|
+
await this.waitForEnter("\nPresiona Enter para ver cómo debe ser...")
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async step2_GoodSignatures() {
|
|
103
|
+
console.clear()
|
|
104
|
+
console.log("✅ FIRMAS BIEN DISEÑADAS")
|
|
105
|
+
console.log("=" .repeat(35))
|
|
106
|
+
console.log("Ahora veamos firmas completas y precisas:")
|
|
107
|
+
|
|
108
|
+
console.log("\n🎯 ELEMENTO A: Nombre claro y específico")
|
|
109
|
+
console.log(`// ✅ Nombres que describen exactamente la operación
|
|
110
|
+
calculateSimpleInterest(principal, rate, time) → number
|
|
111
|
+
calculateCompoundInterest(principal, rate, time, frequency) → number
|
|
112
|
+
calculateMonthlyPayment(loanAmount, interestRate, termInMonths) → number`)
|
|
113
|
+
|
|
114
|
+
console.log("\n📥 ELEMENTO B: Insumos específicos y tipados")
|
|
115
|
+
console.log(`// ✅ Cada parámetro claramente definido
|
|
116
|
+
calculateSimpleInterest(
|
|
117
|
+
principal: number, // Monto principal en pesos
|
|
118
|
+
rate: number, // Tasa anual como decimal (0.05 = 5%)
|
|
119
|
+
time: number // Tiempo en años
|
|
120
|
+
) → number
|
|
121
|
+
|
|
122
|
+
calculateMonthlyPayment(
|
|
123
|
+
loanAmount: number, // Monto del préstamo en pesos
|
|
124
|
+
interestRate: number, // Tasa mensual como decimal
|
|
125
|
+
termInMonths: number // Plazo en meses
|
|
126
|
+
) → number`)
|
|
127
|
+
|
|
128
|
+
console.log("\n📤 ELEMENTO C: Valor de retorno específico")
|
|
129
|
+
console.log(`// ✅ Especifica exactamente qué devuelve y cuándo
|
|
130
|
+
calculateLoanPayment(amount, rate, term) → {
|
|
131
|
+
success: boolean,
|
|
132
|
+
monthlyPayment: number | null,
|
|
133
|
+
totalInterest: number | null,
|
|
134
|
+
error: string | null
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
validateLoanAmount(amount) → {
|
|
138
|
+
isValid: boolean,
|
|
139
|
+
minimumRequired: number,
|
|
140
|
+
maximumAllowed: number
|
|
141
|
+
}`)
|
|
142
|
+
|
|
143
|
+
await this.waitForEnter("\nPresiona Enter para ver la implementación completa...")
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async step3_SignatureAnalysis() {
|
|
147
|
+
console.clear()
|
|
148
|
+
console.log("🔍 ANÁLISIS DE FIRMAS COMPLETAS")
|
|
149
|
+
console.log("=" .repeat(40))
|
|
150
|
+
console.log("Examinemos cada elemento de las firmas:")
|
|
151
|
+
|
|
152
|
+
console.log("\n💼 EJEMPLO: Calculadora financiera completa")
|
|
153
|
+
console.log(`class FinancialCalculator {
|
|
154
|
+
// 🎯 FIRMA 1: Interés simple
|
|
155
|
+
calculateSimpleInterest(
|
|
156
|
+
principal: number, // a) Monto principal en pesos
|
|
157
|
+
rate: number, // b) Tasa anual (0.05 = 5%)
|
|
158
|
+
time: number // c) Tiempo en años
|
|
159
|
+
): number { // RETORNA: Interés total calculado
|
|
160
|
+
return principal * rate * time
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// 🎯 FIRMA 2: Validación con resultado estructurado
|
|
164
|
+
validateLoanApplication(
|
|
165
|
+
income: number, // a) Ingreso mensual
|
|
166
|
+
existingDebt: number, // b) Deuda actual
|
|
167
|
+
requestedAmount: number // c) Monto solicitado
|
|
168
|
+
): { // RETORNA: Objeto con análisis completo
|
|
169
|
+
approved: boolean,
|
|
170
|
+
maxLoanAmount: number,
|
|
171
|
+
debtToIncomeRatio: number,
|
|
172
|
+
reasons: string[]
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// 🎯 FIRMA 3: Cálculo que puede fallar
|
|
176
|
+
calculateCompoundInterest(
|
|
177
|
+
principal: number, // a) Monto principal
|
|
178
|
+
rate: number, // b) Tasa anual
|
|
179
|
+
time: number, // c) Años
|
|
180
|
+
frequency: number // d) Frecuencia de composición
|
|
181
|
+
): number | null { // RETORNA: Resultado o null si hay error
|
|
182
|
+
if (principal <= 0 || rate < 0 || time <= 0 || frequency <= 0) {
|
|
183
|
+
return null
|
|
184
|
+
}
|
|
185
|
+
return principal * Math.pow(1 + rate/frequency, frequency * time)
|
|
186
|
+
}
|
|
187
|
+
}`)
|
|
188
|
+
|
|
189
|
+
await this.waitForEnter("\nPresiona Enter para analizar cada firma...")
|
|
190
|
+
|
|
191
|
+
console.log("\n📋 ANÁLISIS FIRMA POR FIRMA:")
|
|
192
|
+
console.log("\n1️⃣ calculateSimpleInterest:")
|
|
193
|
+
console.log(" • NOMBRE: 'calculateSimpleInterest' - claro y específico")
|
|
194
|
+
console.log(" • INSUMOS: (principal, rate, time) - 3 números con propósito definido")
|
|
195
|
+
console.log(" • RETORNO: number - interés calculado, siempre un número")
|
|
196
|
+
|
|
197
|
+
console.log("\n2️⃣ validateLoanApplication:")
|
|
198
|
+
console.log(" • NOMBRE: 'validateLoanApplication' - describe validación completa")
|
|
199
|
+
console.log(" • INSUMOS: (income, existingDebt, requestedAmount) - datos financieros")
|
|
200
|
+
console.log(" • RETORNO: objeto estructurado - análisis completo con múltiples campos")
|
|
201
|
+
|
|
202
|
+
console.log("\n3️⃣ calculateCompoundInterest:")
|
|
203
|
+
console.log(" • NOMBRE: 'calculateCompoundInterest' - específico del tipo de interés")
|
|
204
|
+
console.log(" • INSUMOS: (principal, rate, time, frequency) - 4 parámetros necesarios")
|
|
205
|
+
console.log(" • RETORNO: number | null - puede fallar si datos inválidos")
|
|
206
|
+
|
|
207
|
+
await this.waitForEnter("\nPresiona Enter para ver cómo usar estas firmas...")
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
async step4_CompleteExample() {
|
|
211
|
+
console.clear()
|
|
212
|
+
console.log("🎭 DEMOSTRACIÓN: Uso con Firmas Claras")
|
|
213
|
+
console.log("=" .repeat(45))
|
|
214
|
+
console.log("Veamos cómo las firmas bien diseñadas facilitan el uso:")
|
|
215
|
+
|
|
216
|
+
console.log("\n// Crear calculadora")
|
|
217
|
+
console.log("const calc = new FinancialCalculator()")
|
|
218
|
+
console.log("")
|
|
219
|
+
console.log("// 💡 Con las firmas claras, sabemos exactamente qué esperar:")
|
|
220
|
+
console.log("")
|
|
221
|
+
console.log("// FIRMA: calculateSimpleInterest(principal, rate, time) → number")
|
|
222
|
+
console.log("const interest = calc.calculateSimpleInterest(10000, 0.05, 2)")
|
|
223
|
+
console.log("console.log(interest) // 1000 (siempre un número)")
|
|
224
|
+
|
|
225
|
+
await this.waitForEnter("\nPresiona Enter para ver validación...")
|
|
226
|
+
|
|
227
|
+
console.log("\n// FIRMA: validateLoanApplication(income, debt, amount) → objeto")
|
|
228
|
+
console.log("const validation = calc.validateLoanApplication(5000, 1000, 50000)")
|
|
229
|
+
console.log("console.log(validation)")
|
|
230
|
+
console.log("// {")
|
|
231
|
+
console.log("// approved: false,")
|
|
232
|
+
console.log("// maxLoanAmount: 20000,")
|
|
233
|
+
console.log("// debtToIncomeRatio: 0.2,")
|
|
234
|
+
console.log("// reasons: ['Monto solicitado excede capacidad de pago']")
|
|
235
|
+
console.log("// }")
|
|
236
|
+
console.log("")
|
|
237
|
+
console.log("// 💡 Podemos usar cada campo porque la firma nos dice qué esperar")
|
|
238
|
+
console.log("if (validation.approved) {")
|
|
239
|
+
console.log(" console.log('Préstamo aprobado!')")
|
|
240
|
+
console.log("} else {")
|
|
241
|
+
console.log(" console.log('Máximo permitido:', validation.maxLoanAmount)")
|
|
242
|
+
console.log(" validation.reasons.forEach(reason => console.log('❌', reason))")
|
|
243
|
+
console.log("}")
|
|
244
|
+
|
|
245
|
+
await this.waitForEnter("\nPresiona Enter para ver manejo de errores...")
|
|
246
|
+
|
|
247
|
+
console.log("\n// FIRMA: calculateCompoundInterest(...) → number | null")
|
|
248
|
+
console.log("const compound1 = calc.calculateCompoundInterest(10000, 0.05, 2, 12)")
|
|
249
|
+
console.log("const compound2 = calc.calculateCompoundInterest(-1000, 0.05, 2, 12)")
|
|
250
|
+
console.log("")
|
|
251
|
+
console.log("// 💡 La firma nos dice que puede retornar null, así que verificamos")
|
|
252
|
+
console.log("if (compound1 !== null) {")
|
|
253
|
+
console.log(" console.log('Interés compuesto:', compound1) // 11049.41")
|
|
254
|
+
console.log("}")
|
|
255
|
+
console.log("")
|
|
256
|
+
console.log("if (compound2 === null) {")
|
|
257
|
+
console.log(" console.log('Error: datos inválidos') // Se ejecuta")
|
|
258
|
+
console.log("}")
|
|
259
|
+
|
|
260
|
+
console.log("\n🎯 Beneficios observados:")
|
|
261
|
+
console.log("• Sabemos exactamente qué pasar a cada método")
|
|
262
|
+
console.log("• Sabemos exactamente qué esperar de vuelta")
|
|
263
|
+
console.log("• Podemos manejar errores apropiadamente")
|
|
264
|
+
console.log("• El código es predecible y confiable")
|
|
265
|
+
|
|
266
|
+
await this.waitForEnter("\nPresiona Enter para la conclusión...")
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
async conclusion() {
|
|
270
|
+
console.clear()
|
|
271
|
+
console.log("🎓 CONCLUSIÓN: Firmas de Operación")
|
|
272
|
+
console.log("=" .repeat(40))
|
|
273
|
+
console.log("🎯 Hemos demostrado que cada firma debe incluir:")
|
|
274
|
+
|
|
275
|
+
console.log("\n🎯 A) NOMBRE de la operación:")
|
|
276
|
+
console.log(" • Claro y específico")
|
|
277
|
+
console.log(" • Describe exactamente qué hace")
|
|
278
|
+
console.log(" • Ejemplo: 'calculateSimpleInterest' vs 'calc'")
|
|
279
|
+
|
|
280
|
+
console.log("\n📥 B) INSUMOS necesarios:")
|
|
281
|
+
console.log(" • Tipo y propósito de cada parámetro")
|
|
282
|
+
console.log(" • Valores esperados y restricciones")
|
|
283
|
+
console.log(" • Ejemplo: 'rate: number (decimal, 0.05 = 5%)'")
|
|
284
|
+
|
|
285
|
+
console.log("\n📤 C) VALOR de retorno:")
|
|
286
|
+
console.log(" • Tipo exacto que retorna")
|
|
287
|
+
console.log(" • Qué significa el valor")
|
|
288
|
+
console.log(" • Cómo manejar casos de error")
|
|
289
|
+
console.log(" • Ejemplo: 'number | null' vs solo 'number'")
|
|
290
|
+
|
|
291
|
+
console.log("\n🌟 Beneficios de firmas completas:")
|
|
292
|
+
console.log("• Predictibilidad total del comportamiento")
|
|
293
|
+
console.log("• Colaboración segura entre objetos")
|
|
294
|
+
console.log("• Código autodocumentado")
|
|
295
|
+
console.log("• Detección temprana de errores")
|
|
296
|
+
|
|
297
|
+
console.log("\n📚 Conexión con otras lecciones:")
|
|
298
|
+
console.log("• Lección 1-3: Las operaciones que identificamos necesitan firmas")
|
|
299
|
+
console.log("• Lección 4: ✅ Cada operación tiene nombre, insumos y retorno")
|
|
300
|
+
console.log("• Lección 5: El conjunto de firmas forma la interfaz")
|
|
301
|
+
console.log("• Lección 6: El énfasis está en diseñar estas firmas primero")
|
|
302
|
+
|
|
303
|
+
console.log("\n💭 Reflexión:")
|
|
304
|
+
console.log("¿Te imaginas usar una función de la que no sabes qué parámetros")
|
|
305
|
+
console.log("espera ni qué devuelve? ¡Sería imposible! Las firmas son el")
|
|
306
|
+
console.log("contrato que hace posible la colaboración entre objetos.")
|
|
307
|
+
|
|
308
|
+
console.log("\n🏆 Ahora entiendes por qué cada operación necesita una firma")
|
|
309
|
+
console.log("completa: es la base de la comunicación predecible.")
|
|
310
|
+
|
|
311
|
+
await this.waitForEnter("\n✨ ¡Lección 4 completada! Presiona Enter para salir...")
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
async waitForEnter(message) {
|
|
315
|
+
return new Promise((resolve) => {
|
|
316
|
+
this.rl.question(message, () => {
|
|
317
|
+
resolve()
|
|
318
|
+
})
|
|
319
|
+
})
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Para usar la lección independientemente
|
|
324
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
325
|
+
const lesson = new Lesson4OperationSignatures()
|
|
326
|
+
await lesson.start()
|
|
327
|
+
}
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// LECCIÓN 5: La interfaz de un objeto es el conjunto de todas sus firmas de operación
|
|
4
|
+
// Enfoque práctico: Demostrar cómo las firmas individuales forman una interfaz coherente
|
|
5
|
+
|
|
6
|
+
import { createInterface } from 'readline'
|
|
7
|
+
|
|
8
|
+
export class Lesson5InterfaceSet {
|
|
9
|
+
constructor() {
|
|
10
|
+
this.rl = createInterface({
|
|
11
|
+
input: process.stdin,
|
|
12
|
+
output: process.stdout
|
|
13
|
+
})
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async start() {
|
|
17
|
+
console.log("🎯 LECCIÓN 5: Interfaz como Conjunto de Firmas")
|
|
18
|
+
console.log("=" .repeat(50))
|
|
19
|
+
console.log("📚 Enunciado: La interfaz de un objeto es el conjunto de todas sus firmas de operación.")
|
|
20
|
+
|
|
21
|
+
console.log("\n💡 ¿Qué significa esto?")
|
|
22
|
+
console.log("- Una interfaz NO es una sola operación")
|
|
23
|
+
console.log("- Es el CONJUNTO COMPLETO de todas las firmas")
|
|
24
|
+
console.log("- Define TODO lo que el objeto puede hacer")
|
|
25
|
+
console.log("- Es el 'contrato público' del objeto")
|
|
26
|
+
|
|
27
|
+
await this.waitForEnter("\nPresiona Enter para comenzar con ejemplos prácticos...")
|
|
28
|
+
|
|
29
|
+
await this.practicalExercise()
|
|
30
|
+
await this.conclusion()
|
|
31
|
+
|
|
32
|
+
this.rl.close()
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async practicalExercise() {
|
|
36
|
+
console.clear()
|
|
37
|
+
console.log("🛠️ EJERCICIO PRÁCTICO: Reproductor de Música")
|
|
38
|
+
console.log("=" .repeat(50))
|
|
39
|
+
console.log("🎯 Vamos a construir una interfaz completa paso a paso")
|
|
40
|
+
console.log("\nSistema: Reproductor de música digital")
|
|
41
|
+
console.log("Objeto: MusicPlayer")
|
|
42
|
+
|
|
43
|
+
await this.waitForEnter("\nPresiona Enter para ver cómo NO diseñar una interfaz...")
|
|
44
|
+
|
|
45
|
+
await this.step1_IncompleteInterface()
|
|
46
|
+
await this.step2_CompleteInterface()
|
|
47
|
+
await this.step3_InterfaceCoherence()
|
|
48
|
+
await this.step4_InterfaceUsage()
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async step1_IncompleteInterface() {
|
|
52
|
+
console.clear()
|
|
53
|
+
console.log("❌ INTERFAZ INCOMPLETA")
|
|
54
|
+
console.log("=" .repeat(30))
|
|
55
|
+
console.log("Veamos un objeto con interfaz parcial:")
|
|
56
|
+
|
|
57
|
+
console.log("\n🚫 PROBLEMA: Solo algunas operaciones definidas")
|
|
58
|
+
console.log(`class MusicPlayer {
|
|
59
|
+
// ✅ Operación bien definida
|
|
60
|
+
play(songId: string): boolean {
|
|
61
|
+
// implementación...
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ❌ Otras operaciones sin firma clara
|
|
65
|
+
stop() {
|
|
66
|
+
// ¿Qué devuelve? ¿Puede fallar?
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
volume(level) {
|
|
70
|
+
// ¿Qué tipo es level? ¿Qué rango?
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// ❌ Operaciones faltantes
|
|
74
|
+
// ¿Cómo pauso? ¿Cómo cambio de canción?
|
|
75
|
+
// ¿Cómo consulto qué está sonando?
|
|
76
|
+
}`)
|
|
77
|
+
|
|
78
|
+
console.log("\n💥 Problemas de interfaz incompleta:")
|
|
79
|
+
console.log("• Imposible predecir qué operaciones están disponibles")
|
|
80
|
+
console.log("• Firmas inconsistentes entre operaciones")
|
|
81
|
+
console.log("• Funcionalidad básica faltante")
|
|
82
|
+
console.log("• Colaboración con otros objetos impredecible")
|
|
83
|
+
|
|
84
|
+
await this.waitForEnter("\nPresiona Enter para ver otro problema...")
|
|
85
|
+
|
|
86
|
+
console.log("\n🚫 PROBLEMA: Firmas sin relación")
|
|
87
|
+
console.log(`class BadMusicPlayer {
|
|
88
|
+
// ❌ Firmas que no forman un conjunto coherente
|
|
89
|
+
startMusic(file: string): void // Usa archivos
|
|
90
|
+
playTrack(id: number): boolean // Usa IDs numéricos
|
|
91
|
+
beginSong(song: object): string // Usa objetos
|
|
92
|
+
|
|
93
|
+
setVol(x: number): void // Volumen sin validación
|
|
94
|
+
adjustAudio(level: string): boolean // Volumen como string??
|
|
95
|
+
|
|
96
|
+
getCurrentStatus(): any // Retorna 'any' (inútil)
|
|
97
|
+
getInfo(): string // Retorna string (ambiguo)
|
|
98
|
+
}`)
|
|
99
|
+
|
|
100
|
+
console.log("\n📉 Resultado:")
|
|
101
|
+
console.log("No es una interfaz, es una colección caótica de operaciones")
|
|
102
|
+
console.log("sin relación ni coherencia entre sí.")
|
|
103
|
+
|
|
104
|
+
await this.waitForEnter("\nPresiona Enter para ver cómo debe ser...")
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async step2_CompleteInterface() {
|
|
108
|
+
console.clear()
|
|
109
|
+
console.log("✅ INTERFAZ COMPLETA Y COHERENTE")
|
|
110
|
+
console.log("=" .repeat(40))
|
|
111
|
+
console.log("Veamos una interfaz bien diseñada:")
|
|
112
|
+
|
|
113
|
+
console.log("\n🎵 CONJUNTO COMPLETO de firmas de operación:")
|
|
114
|
+
console.log(`interface MusicPlayerInterface {
|
|
115
|
+
// 🎮 GRUPO: Control de reproducción
|
|
116
|
+
play(songId: string): Promise<{success: boolean, message: string}>
|
|
117
|
+
pause(): Promise<{success: boolean, currentPosition: number}>
|
|
118
|
+
stop(): Promise<{success: boolean}>
|
|
119
|
+
resume(): Promise<{success: boolean, message: string}>
|
|
120
|
+
|
|
121
|
+
// 🔊 GRUPO: Control de audio
|
|
122
|
+
setVolume(level: number): {success: boolean, currentVolume: number} // 0-100
|
|
123
|
+
getVolume(): number
|
|
124
|
+
mute(): {success: boolean, previousVolume: number}
|
|
125
|
+
unmute(): {success: boolean, restoredVolume: number}
|
|
126
|
+
|
|
127
|
+
// 📋 GRUPO: Gestión de playlist
|
|
128
|
+
addSong(song: Song): {success: boolean, playlistLength: number}
|
|
129
|
+
removeSong(songId: string): {success: boolean, wasPlaying: boolean}
|
|
130
|
+
getPlaylist(): Song[]
|
|
131
|
+
shufflePlaylist(): {success: boolean, newOrder: string[]}
|
|
132
|
+
|
|
133
|
+
// 📊 GRUPO: Información de estado
|
|
134
|
+
getCurrentSong(): Song | null
|
|
135
|
+
getPlaybackStatus(): {
|
|
136
|
+
isPlaying: boolean,
|
|
137
|
+
isPaused: boolean,
|
|
138
|
+
currentTime: number,
|
|
139
|
+
totalTime: number,
|
|
140
|
+
volume: number
|
|
141
|
+
}
|
|
142
|
+
getDuration(songId: string): number | null
|
|
143
|
+
|
|
144
|
+
// ⚙️ GRUPO: Configuración
|
|
145
|
+
setRepeatMode(mode: 'none' | 'one' | 'all'): boolean
|
|
146
|
+
getRepeatMode(): 'none' | 'one' | 'all'
|
|
147
|
+
setShuffleMode(enabled: boolean): boolean
|
|
148
|
+
isShuffleEnabled(): boolean
|
|
149
|
+
}`)
|
|
150
|
+
|
|
151
|
+
await this.waitForEnter("\nPresiona Enter para analizar esta interfaz...")
|
|
152
|
+
|
|
153
|
+
console.log("\n🔍 ANÁLISIS del conjunto:")
|
|
154
|
+
console.log("\n1️⃣ COMPLETITUD:")
|
|
155
|
+
console.log(" • Todas las operaciones necesarias están presentes")
|
|
156
|
+
console.log(" • No hay funcionalidad faltante básica")
|
|
157
|
+
console.log(" • Cubre todo el comportamiento esperado")
|
|
158
|
+
|
|
159
|
+
console.log("\n2️⃣ COHERENCIA:")
|
|
160
|
+
console.log(" • Todas las firmas usan tipos consistentes")
|
|
161
|
+
console.log(" • Patrones de retorno consistentes")
|
|
162
|
+
console.log(" • Nomenclatura uniforme")
|
|
163
|
+
|
|
164
|
+
console.log("\n3️⃣ ORGANIZACIÓN:")
|
|
165
|
+
console.log(" • Operaciones agrupadas por funcionalidad")
|
|
166
|
+
console.log(" • Cada grupo tiene propósito claro")
|
|
167
|
+
console.log(" • Fácil de entender y recordar")
|
|
168
|
+
|
|
169
|
+
await this.waitForEnter("\nPresiona Enter para ver la implementación...")
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async step3_InterfaceCoherence() {
|
|
173
|
+
console.clear()
|
|
174
|
+
console.log("🧩 COHERENCIA DE LA INTERFAZ")
|
|
175
|
+
console.log("=" .repeat(35))
|
|
176
|
+
console.log("Veamos cómo las firmas forman un conjunto coherente:")
|
|
177
|
+
|
|
178
|
+
console.log("\n📏 CONSISTENCIA EN TIPOS:")
|
|
179
|
+
console.log(`// ✅ Todas las operaciones de canciones usan string
|
|
180
|
+
play(songId: string)
|
|
181
|
+
removeSong(songId: string)
|
|
182
|
+
getDuration(songId: string)
|
|
183
|
+
|
|
184
|
+
// ✅ Todas las operaciones de volumen usan number (0-100)
|
|
185
|
+
setVolume(level: number) // 0-100
|
|
186
|
+
getVolume(): number // 0-100
|
|
187
|
+
mute(): {previousVolume: number} // 0-100`)
|
|
188
|
+
|
|
189
|
+
console.log("\n🔄 CONSISTENCIA EN PATRONES:")
|
|
190
|
+
console.log(`// ✅ Operaciones de configuración siguen patrón set/get
|
|
191
|
+
setRepeatMode(mode) / getRepeatMode()
|
|
192
|
+
setShuffleMode(enabled) / isShuffleEnabled()
|
|
193
|
+
setVolume(level) / getVolume()
|
|
194
|
+
|
|
195
|
+
// ✅ Operaciones de estado siguen patrón de objeto estructurado
|
|
196
|
+
getPlaybackStatus(): {isPlaying, isPaused, currentTime, ...}
|
|
197
|
+
pause(): {success, currentPosition}
|
|
198
|
+
mute(): {success, previousVolume}`)
|
|
199
|
+
|
|
200
|
+
console.log("\n🎯 COMPLETITUD FUNCIONAL:")
|
|
201
|
+
console.log(`// ✅ Para cada acción, hay su contraparte
|
|
202
|
+
play() ↔ pause() ↔ stop() ↔ resume()
|
|
203
|
+
mute() ↔ unmute()
|
|
204
|
+
addSong() ↔ removeSong()
|
|
205
|
+
|
|
206
|
+
// ✅ Para cada operación de cambio, hay consulta
|
|
207
|
+
setVolume() → getVolume()
|
|
208
|
+
setRepeatMode() → getRepeatMode()`)
|
|
209
|
+
|
|
210
|
+
await this.waitForEnter("\nPresiona Enter para ver cómo se usa...")
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
async step4_InterfaceUsage() {
|
|
214
|
+
console.clear()
|
|
215
|
+
console.log("🎭 DEMOSTRACIÓN: Uso de Interfaz Completa")
|
|
216
|
+
console.log("=" .repeat(45))
|
|
217
|
+
console.log("Veamos cómo se usa una interfaz bien diseñada:")
|
|
218
|
+
|
|
219
|
+
console.log("\n// Crear reproductor")
|
|
220
|
+
console.log("const player = new MusicPlayer()")
|
|
221
|
+
console.log("")
|
|
222
|
+
console.log("// 🎵 Usar GRUPO de reproducción")
|
|
223
|
+
console.log("const song = new Song('123', 'Bohemian Rhapsody', 'Queen')")
|
|
224
|
+
console.log("await player.addSong(song)")
|
|
225
|
+
console.log("")
|
|
226
|
+
console.log("const playResult = await player.play('123')")
|
|
227
|
+
console.log("if (playResult.success) {")
|
|
228
|
+
console.log(" console.log('Reproduciendo:', playResult.message)")
|
|
229
|
+
console.log("}")
|
|
230
|
+
|
|
231
|
+
await this.waitForEnter("\nPresiona Enter para ver más uso...")
|
|
232
|
+
|
|
233
|
+
console.log("\n// 🔊 Usar GRUPO de audio")
|
|
234
|
+
console.log("const volumeResult = player.setVolume(75)")
|
|
235
|
+
console.log("console.log('Volumen actual:', volumeResult.currentVolume)")
|
|
236
|
+
console.log("")
|
|
237
|
+
console.log("// 📊 Usar GRUPO de información")
|
|
238
|
+
console.log("const status = player.getPlaybackStatus()")
|
|
239
|
+
console.log("console.log(`Reproduciendo: ${status.isPlaying}`)")
|
|
240
|
+
console.log("console.log(`Tiempo: ${status.currentTime}/${status.totalTime}`)")
|
|
241
|
+
console.log("console.log(`Volumen: ${status.volume}`)")
|
|
242
|
+
|
|
243
|
+
await this.waitForEnter("\nPresiona Enter para ver uso avanzado...")
|
|
244
|
+
|
|
245
|
+
console.log("\n// 🎮 Combinando múltiples grupos")
|
|
246
|
+
console.log("async function createMusicSession() {")
|
|
247
|
+
console.log(" // Configurar reproductor")
|
|
248
|
+
console.log(" player.setRepeatMode('all')")
|
|
249
|
+
console.log(" player.setShuffleMode(true)")
|
|
250
|
+
console.log(" player.setVolume(60)")
|
|
251
|
+
console.log(" ")
|
|
252
|
+
console.log(" // Agregar canciones")
|
|
253
|
+
console.log(" const songs = ['123', '456', '789']")
|
|
254
|
+
console.log(" for (const songId of songs) {")
|
|
255
|
+
console.log(" await player.addSong(getSong(songId))")
|
|
256
|
+
console.log(" }")
|
|
257
|
+
console.log(" ")
|
|
258
|
+
console.log(" // Iniciar reproducción")
|
|
259
|
+
console.log(" await player.play(songs[0])")
|
|
260
|
+
console.log(" ")
|
|
261
|
+
console.log(" // Monitorear estado cada segundo")
|
|
262
|
+
console.log(" setInterval(() => {")
|
|
263
|
+
console.log(" const status = player.getPlaybackStatus()")
|
|
264
|
+
console.log(" updateUI(status)")
|
|
265
|
+
console.log(" }, 1000)")
|
|
266
|
+
console.log("}")
|
|
267
|
+
|
|
268
|
+
console.log("\n🌟 Beneficios observados:")
|
|
269
|
+
console.log("• Cada operación se complementa con las demás")
|
|
270
|
+
console.log("• Podemos crear flujos complejos combinando firmas")
|
|
271
|
+
console.log("• La interfaz completa permite cualquier funcionalidad")
|
|
272
|
+
console.log("• El conjunto es mayor que la suma de sus partes")
|
|
273
|
+
|
|
274
|
+
await this.waitForEnter("\nPresiona Enter para la conclusión...")
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
async conclusion() {
|
|
278
|
+
console.clear()
|
|
279
|
+
console.log("🎓 CONCLUSIÓN: Interfaz como Conjunto de Firmas")
|
|
280
|
+
console.log("=" .repeat(50))
|
|
281
|
+
console.log("🎯 Hemos demostrado que:")
|
|
282
|
+
|
|
283
|
+
console.log("\n1️⃣ Una interfaz es el CONJUNTO COMPLETO de firmas")
|
|
284
|
+
console.log("2️⃣ No es una operación individual, sino TODAS juntas")
|
|
285
|
+
console.log("3️⃣ Las firmas deben ser coherentes entre sí")
|
|
286
|
+
console.log("4️⃣ El conjunto debe cubrir toda la funcionalidad necesaria")
|
|
287
|
+
|
|
288
|
+
console.log("\n🧩 Características de una buena interfaz:")
|
|
289
|
+
console.log("• COMPLETITUD - todas las operaciones necesarias")
|
|
290
|
+
console.log("• COHERENCIA - tipos y patrones consistentes")
|
|
291
|
+
console.log("• ORGANIZACIÓN - agrupación lógica de funcionalidades")
|
|
292
|
+
console.log("• PREDICTIBILIDAD - comportamiento uniforme")
|
|
293
|
+
|
|
294
|
+
console.log("\n🎯 La interfaz como contrato:")
|
|
295
|
+
console.log("• Define TODO lo que el objeto puede hacer")
|
|
296
|
+
console.log("• Especifica exactamente cómo interactuar")
|
|
297
|
+
console.log("• Garantiza comportamiento predecible")
|
|
298
|
+
console.log("• Permite colaboración confiable")
|
|
299
|
+
|
|
300
|
+
console.log("\n📚 Conexión con otras lecciones:")
|
|
301
|
+
console.log("• Lección 1-3: Los objetos necesitan interfaces para interactuar")
|
|
302
|
+
console.log("• Lección 4: Cada operación de la interfaz tiene firma completa")
|
|
303
|
+
console.log("• Lección 5: ✅ La interfaz es el conjunto de TODAS las firmas")
|
|
304
|
+
console.log("• Lección 6: El énfasis debe estar en diseñar este conjunto")
|
|
305
|
+
|
|
306
|
+
console.log("\n💭 Reflexión:")
|
|
307
|
+
console.log("Imagina un control remoto que solo tuviera algunos botones:")
|
|
308
|
+
console.log("play pero no pause, volumen+ pero no volumen-. ¡Sería inútil!")
|
|
309
|
+
console.log("Las interfaces necesitan ser conjuntos completos y coherentes.")
|
|
310
|
+
|
|
311
|
+
console.log("\n🏆 Ahora entiendes qué es realmente una interfaz:")
|
|
312
|
+
console.log("el conjunto completo de todas las firmas de operación.")
|
|
313
|
+
|
|
314
|
+
await this.waitForEnter("\n✨ ¡Lección 5 completada! Presiona Enter para salir...")
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
async waitForEnter(message) {
|
|
318
|
+
return new Promise((resolve) => {
|
|
319
|
+
this.rl.question(message, () => {
|
|
320
|
+
resolve()
|
|
321
|
+
})
|
|
322
|
+
})
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Para usar la lección independientemente
|
|
327
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
328
|
+
const lesson = new Lesson5InterfaceSet()
|
|
329
|
+
await lesson.start()
|
|
330
|
+
}
|