@icarusmx/creta 1.2.1 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/creta.js CHANGED
@@ -6,6 +6,7 @@ import fs from 'fs'
6
6
  import path from 'path'
7
7
  import { fileURLToPath } from 'url'
8
8
  import { CretaCodeSession } from '../lib/session.js'
9
+ import { PullRequestTutorial } from '../lib/pr-tutorial.js'
9
10
  import { Lesson1SystemDecomposition } from '../lessons/lesson1-system-decomposition.js'
10
11
  import { Lesson2ObjectRequests } from '../lessons/lesson2-object-requests.js'
11
12
  import { Lesson3OnlyWay } from '../lessons/lesson3-only-way.js'
@@ -621,8 +622,7 @@ async function startMainMenuInteractive() {
621
622
 
622
623
  const options = [
623
624
  { id: 1, title: "Aprender conceptos", description: "Explora los enunciados fundamentales" },
624
- { id: 2, title: "Construir proyectos", description: "Crea tu portafolio personal" },
625
- { id: 3, title: "Crea una pull-request", description: "Aprende el flujo de contribución con Git" }
625
+ { id: 2, title: "Construir proyectos", description: "Practica con proyectos reales" }
626
626
  ]
627
627
 
628
628
  let selectedIndex = 0
@@ -693,8 +693,6 @@ Salgamos de este laberinto 🏛️
693
693
  startEnunciadosSelector().then(resolve)
694
694
  } else if (selectedOption.id === 2) {
695
695
  startProyectosSelector().then(resolve)
696
- } else if (selectedOption.id === 3) {
697
- startPullRequestTutorial().then(resolve)
698
696
  }
699
697
  return
700
698
  }
@@ -729,11 +727,10 @@ async function startMainMenuFallback() {
729
727
  console.log("Te ofrecemos las siguientes opciones:")
730
728
  console.log("")
731
729
  console.log("1. Aprender conceptos - Explora los enunciados fundamentales")
732
- console.log("2. Construir proyectos - Crea tu portafolio personal")
733
- console.log("3. Crea una pull-request - Aprende el flujo de contribución con Git")
730
+ console.log("2. Construir proyectos - Practica con proyectos reales")
734
731
  console.log("")
735
732
 
736
- const respuesta = await askQuestion("Elige una opción (1-3) o 'q' para salir: ")
733
+ const respuesta = await askQuestion("Elige una opción (1-2) o 'q' para salir: ")
737
734
 
738
735
  if (respuesta.toLowerCase() === 'q') {
739
736
  console.log("Hecho con <3 por icarus.mx")
@@ -749,11 +746,8 @@ async function startMainMenuFallback() {
749
746
  } else if (opcionSeleccionada === 2) {
750
747
  rl.close()
751
748
  await startProyectosSelector()
752
- } else if (opcionSeleccionada === 3) {
753
- rl.close()
754
- await startPullRequestTutorial()
755
749
  } else {
756
- console.log("❌ Opción no válida. Elige 1, 2 o 3.")
750
+ console.log("❌ Opción no válida. Elige 1 o 2.")
757
751
  rl.close()
758
752
  }
759
753
 
@@ -1240,14 +1234,12 @@ async function startProyectosSelectorInteractive() {
1240
1234
  throw new Error('setRawMode not available')
1241
1235
  }
1242
1236
 
1243
- console.log("\n🚀 Proyectos Disponibles")
1244
- console.log("¿Qué proyecto quieres crear?")
1237
+ console.log("\n🚀 Construir Proyectos")
1238
+ console.log("Elige qué proyecto quieres construir:")
1245
1239
 
1246
1240
  const projects = [
1247
- { id: 1, title: "🎨 Portafolio Personal", description: "Reto completo", level: 0 },
1248
- { id: 2, title: "🔓 Portafolio Nivel 1", description: "Solo navbar", level: 1 },
1249
- { id: 3, title: "🔓 Portafolio Nivel 2", description: "Navbar + hero", level: 2 },
1250
- { id: 4, title: "🔓 Portafolio Nivel 3", description: "Solución completa", level: 3 }
1241
+ { id: 1, title: "🔀 Construye una pull-request", description: "Aprende Git y GitHub paso a paso", type: 'pr' },
1242
+ { id: 2, title: "🎨 Construye tu portafolio", description: "Crea tu sitio web personal", type: 'portfolio' }
1251
1243
  ]
1252
1244
 
1253
1245
  let selectedIndex = 0
@@ -1266,8 +1258,8 @@ async function startProyectosSelectorInteractive() {
1266
1258
  process.stdout.write('\x1b[2J')
1267
1259
  process.stdout.write('\x1b[H')
1268
1260
 
1269
- console.log("🚀 Proyectos Disponibles")
1270
- console.log("¿Qué proyecto quieres crear?")
1261
+ console.log("🚀 Construir Proyectos")
1262
+ console.log("Elige qué proyecto quieres construir:")
1271
1263
 
1272
1264
  projects.forEach((project, index) => {
1273
1265
  const isSelected = index === selectedIndex
@@ -1300,17 +1292,15 @@ async function startProyectosSelectorInteractive() {
1300
1292
  process.stdin.removeListener('data', onKeyPress)
1301
1293
 
1302
1294
  const selectedProject = projects[selectedIndex]
1303
- const level = selectedProject.level
1304
1295
 
1305
1296
  // Clear screen
1306
1297
  process.stdout.write('\x1b[2J')
1307
1298
  process.stdout.write('\x1b[H')
1308
1299
 
1309
- // Check if we're in an existing Creta project
1310
- if (level > 0 && isInCretaProject()) {
1311
- unstuckProject(level).then(resolve)
1312
- } else {
1313
- createPortfolioProject(level).then(resolve)
1300
+ if (selectedProject.type === 'pr') {
1301
+ startPullRequestTutorial().then(resolve)
1302
+ } else if (selectedProject.type === 'portfolio') {
1303
+ startPortfolioSelector().then(resolve)
1314
1304
  }
1315
1305
  return
1316
1306
  }
@@ -1370,8 +1360,162 @@ async function startProyectosSelectorFallback() {
1370
1360
  }
1371
1361
 
1372
1362
  try {
1373
- console.log("\n🚀 Proyectos Disponibles")
1374
- console.log("¿Qué proyecto quieres crear?")
1363
+ console.log("\n🚀 Construir Proyectos")
1364
+ console.log("Elige qué proyecto quieres construir:")
1365
+ console.log("")
1366
+ console.log("1. 🔀 Construye una pull-request - Aprende Git y GitHub paso a paso")
1367
+ console.log("2. 🎨 Construye tu portafolio - Crea tu sitio web personal")
1368
+ console.log("")
1369
+
1370
+ const respuesta = await askQuestion("Elige una opción (1-2) o 'q' para salir: ")
1371
+
1372
+ if (respuesta.toLowerCase() === 'q') {
1373
+ console.log("Hecho con <3 por icarus.mx")
1374
+ rl.close()
1375
+ return
1376
+ }
1377
+
1378
+ const opcionSeleccionada = parseInt(respuesta)
1379
+
1380
+ if (opcionSeleccionada === 1) {
1381
+ rl.close()
1382
+ await startPullRequestTutorial()
1383
+ } else if (opcionSeleccionada === 2) {
1384
+ rl.close()
1385
+ await startPortfolioSelector()
1386
+ } else {
1387
+ console.log("❌ Opción no válida. Elige 1 o 2.")
1388
+ rl.close()
1389
+ }
1390
+
1391
+ } catch (error) {
1392
+ console.error('Error:', error.message)
1393
+ rl.close()
1394
+ process.exit(1)
1395
+ }
1396
+ }
1397
+
1398
+ async function startPortfolioSelector() {
1399
+ // Always try interactive mode first, fall back only if it fails
1400
+ try {
1401
+ return await startPortfolioSelectorInteractive()
1402
+ } catch (error) {
1403
+ // If interactive mode fails, fall back to numbered selection
1404
+ console.log('\nModo interactivo no disponible, usando selección numérica...\n')
1405
+ return await startPortfolioSelectorFallback()
1406
+ }
1407
+ }
1408
+
1409
+ async function startPortfolioSelectorInteractive() {
1410
+ // Check if setRawMode is available before trying to use it
1411
+ if (typeof process.stdin.setRawMode !== 'function') {
1412
+ throw new Error('setRawMode not available')
1413
+ }
1414
+
1415
+ console.log("\n🎨 Construye tu Portafolio")
1416
+ console.log("Elige el nivel:")
1417
+
1418
+ const projects = [
1419
+ { id: 1, title: "🎨 Portafolio Personal", description: "Reto completo", level: 0 },
1420
+ { id: 2, title: "🔓 Portafolio Nivel 1", description: "Solo navbar", level: 1 },
1421
+ { id: 3, title: "🔓 Portafolio Nivel 2", description: "Navbar + hero", level: 2 },
1422
+ { id: 4, title: "🔓 Portafolio Nivel 3", description: "Solución completa", level: 3 }
1423
+ ]
1424
+
1425
+ let selectedIndex = 0
1426
+
1427
+ // Enable raw mode to capture arrow keys
1428
+ try {
1429
+ process.stdin.setRawMode(true)
1430
+ process.stdin.resume()
1431
+ process.stdin.setEncoding('utf8')
1432
+ } catch (error) {
1433
+ throw new Error('Failed to enable raw mode: ' + error.message)
1434
+ }
1435
+
1436
+ const renderOptions = () => {
1437
+ // Clear previous output and move cursor to top
1438
+ process.stdout.write('\x1b[2J')
1439
+ process.stdout.write('\x1b[H')
1440
+
1441
+ console.log("🎨 Construye tu Portafolio")
1442
+ console.log("Elige el nivel:")
1443
+
1444
+ projects.forEach((project, index) => {
1445
+ const isSelected = index === selectedIndex
1446
+ const prefix = isSelected ? '▶ ' : ' '
1447
+ const highlight = isSelected ? '\x1b[36m' : '\x1b[37m' // cyan for selected, white for normal
1448
+ const reset = '\x1b[0m'
1449
+
1450
+ console.log(`${highlight}${prefix}${project.title}${reset}`)
1451
+ if (isSelected) {
1452
+ console.log(`${highlight} ${project.description}${reset}`)
1453
+ }
1454
+ console.log("")
1455
+ })
1456
+ }
1457
+
1458
+ return new Promise((resolve) => {
1459
+ renderOptions()
1460
+
1461
+ const onKeyPress = (key) => {
1462
+ if (key === 'q' || key === '\x03') { // q or Ctrl+C
1463
+ process.stdin.setRawMode(false)
1464
+ process.stdin.removeListener('data', onKeyPress)
1465
+ console.log("\nHecho con <3 por icarus.mx")
1466
+ resolve()
1467
+ return
1468
+ }
1469
+
1470
+ if (key === '\r' || key === '\n') { // Enter
1471
+ process.stdin.setRawMode(false)
1472
+ process.stdin.removeListener('data', onKeyPress)
1473
+
1474
+ const selectedProject = projects[selectedIndex]
1475
+ const level = selectedProject.level
1476
+
1477
+ // Clear screen
1478
+ process.stdout.write('\x1b[2J')
1479
+ process.stdout.write('\x1b[H')
1480
+
1481
+ // Check if we're in an existing Creta project
1482
+ if (level > 0 && isInCretaProject()) {
1483
+ unstuckProject(level).then(resolve)
1484
+ } else {
1485
+ createPortfolioProject(level).then(resolve)
1486
+ }
1487
+ return
1488
+ }
1489
+
1490
+ // Handle arrow keys (escape sequences)
1491
+ if (key === '\u001b[A') { // Up arrow
1492
+ selectedIndex = selectedIndex > 0 ? selectedIndex - 1 : projects.length - 1
1493
+ renderOptions()
1494
+ } else if (key === '\u001b[B') { // Down arrow
1495
+ selectedIndex = selectedIndex < projects.length - 1 ? selectedIndex + 1 : 0
1496
+ renderOptions()
1497
+ }
1498
+ }
1499
+
1500
+ process.stdin.on('data', onKeyPress)
1501
+ })
1502
+ }
1503
+
1504
+ async function startPortfolioSelectorFallback() {
1505
+ const rl = createInterface({
1506
+ input: process.stdin,
1507
+ output: process.stdout
1508
+ })
1509
+
1510
+ const askQuestion = (question) => {
1511
+ return new Promise((resolve) => {
1512
+ rl.question(question, resolve)
1513
+ })
1514
+ }
1515
+
1516
+ try {
1517
+ console.log("\n🎨 Construye tu Portafolio")
1518
+ console.log("Elige el nivel:")
1375
1519
  console.log("")
1376
1520
  console.log("1. 🎨 Portafolio Personal - Reto completo")
1377
1521
  console.log("2. 🔓 Portafolio Nivel 1 - Solo navbar")
@@ -1414,33 +1558,28 @@ async function startProyectosSelectorFallback() {
1414
1558
  }
1415
1559
 
1416
1560
  async function startPullRequestTutorial() {
1417
- console.clear()
1418
- console.log("🔀 Tutorial: Crea una Pull Request")
1419
- console.log("=" .repeat(50))
1420
- console.log("")
1421
- console.log("📚 Aprenderás:")
1422
- console.log(" • Cómo hacer fork de un repositorio")
1423
- console.log(" • Crear una rama (branch) para tu cambio")
1424
- console.log(" • Hacer commits con buenas prácticas")
1425
- console.log(" • Crear una pull request profesional")
1426
- console.log("")
1427
- console.log("🚧 [PRÓXIMAMENTE]")
1428
- console.log("")
1429
- console.log("Esta funcionalidad está en desarrollo.")
1430
- console.log("Pronto podrás practicar el flujo completo de contribución.")
1431
- console.log("")
1561
+ try {
1562
+ const tutorial = new PullRequestTutorial()
1563
+ await tutorial.start()
1432
1564
 
1433
- const rl = createInterface({
1434
- input: process.stdin,
1435
- output: process.stdout
1436
- })
1565
+ // Volver al menú principal después de completar el tutorial
1566
+ await returnToMainMenu()
1567
+ } catch (error) {
1568
+ console.error("\n❌ Error al ejecutar el tutorial:", error.message)
1569
+ console.log("\nSi el problema persiste, contacta al equipo de Creta.")
1437
1570
 
1438
- await new Promise((resolve) => {
1439
- rl.question("\nPresiona Enter para volver al menú principal...", () => {
1440
- rl.close()
1441
- resolve()
1571
+ const rl = createInterface({
1572
+ input: process.stdin,
1573
+ output: process.stdout
1442
1574
  })
1443
- })
1444
1575
 
1445
- await startMainMenu()
1576
+ await new Promise((resolve) => {
1577
+ rl.question("\nPresiona Enter para volver al menú principal...", () => {
1578
+ rl.close()
1579
+ resolve()
1580
+ })
1581
+ })
1582
+
1583
+ await startMainMenu()
1584
+ }
1446
1585
  }
@@ -0,0 +1,604 @@
1
+ #!/usr/bin/env node
2
+
3
+ // Tutorial interactivo: Crea una Pull Request
4
+ // Guía paso a paso para aprender el flujo completo de contribución con Git
5
+
6
+ import { createInterface } from 'readline'
7
+ import { execSync } from 'child_process'
8
+ import fs from 'fs'
9
+ import path from 'path'
10
+
11
+ export class PullRequestTutorial {
12
+ constructor() {
13
+ this.rl = createInterface({
14
+ input: process.stdin,
15
+ output: process.stdout
16
+ })
17
+
18
+ // Repo de práctica (usaremos el mismo repo de Creta)
19
+ this.practiceRepo = 'icarusmx/creta-practice'
20
+ this.repoUrl = `https://github.com/${this.practiceRepo}`
21
+ this.projectDir = null
22
+ }
23
+
24
+ async start() {
25
+ await this.introduction()
26
+ await this.checkPrerequisites()
27
+ await this.step1_ForkExplanation()
28
+ await this.step2_CloneRepo()
29
+ await this.step3_CreateBranch()
30
+ await this.step4_MakeChanges()
31
+ await this.step5_CommitChanges()
32
+ await this.step6_PushBranch()
33
+ await this.step7_CreatePR()
34
+ await this.conclusion()
35
+
36
+ this.rl.close()
37
+ }
38
+
39
+ async introduction() {
40
+ console.clear()
41
+ console.log("🔀 Tutorial: Crea tu primera Pull Request")
42
+ console.log("=" .repeat(50))
43
+ console.log("")
44
+ console.log("📚 En este tutorial aprenderás:")
45
+ console.log("")
46
+ console.log("1. Qué es una Pull Request y por qué son importantes")
47
+ console.log("2. Cómo hacer fork de un repositorio")
48
+ console.log("3. Cómo crear una rama (branch) para tu cambio")
49
+ console.log("4. Cómo hacer commits con buenas prácticas")
50
+ console.log("5. Cómo crear una Pull Request profesional")
51
+ console.log("")
52
+ console.log("⏱️ Duración estimada: 10-15 minutos")
53
+ console.log("")
54
+ console.log("💡 Tip: Tendrás que ejecutar comandos reales en tu terminal.")
55
+ console.log(" ¡No te preocupes! Te guiaremos paso a paso.")
56
+
57
+ await this.waitForEnter("\nPresiona Enter para comenzar...")
58
+ }
59
+
60
+ async checkPrerequisites() {
61
+ console.clear()
62
+ console.log("🔍 Verificando prerequisitos...")
63
+ console.log("=" .repeat(50))
64
+ console.log("")
65
+
66
+ // Check git
67
+ console.log("Verificando Git...")
68
+ try {
69
+ const gitVersion = execSync('git --version', { encoding: 'utf8' }).trim()
70
+ console.log(`✅ ${gitVersion}`)
71
+ } catch (error) {
72
+ console.log("❌ Git no está instalado")
73
+ console.log("")
74
+ console.log("Por favor instala Git:")
75
+ console.log(" macOS: brew install git")
76
+ console.log(" Linux: sudo apt install git")
77
+ console.log(" Windows: https://git-scm.com/download/win")
78
+ console.log("")
79
+ await this.waitForEnter("Presiona Enter para salir...")
80
+ process.exit(1)
81
+ }
82
+
83
+ console.log("")
84
+
85
+ // Check GitHub CLI
86
+ console.log("Verificando GitHub CLI (gh)...")
87
+ try {
88
+ const ghVersion = execSync('gh --version', { encoding: 'utf8' }).trim().split('\n')[0]
89
+ console.log(`✅ ${ghVersion}`)
90
+ } catch (error) {
91
+ console.log("❌ GitHub CLI (gh) no está instalado")
92
+ console.log("")
93
+ console.log("Por favor instala gh:")
94
+ console.log(" macOS: brew install gh")
95
+ console.log(" Linux: Ver https://github.com/cli/cli/blob/trunk/docs/install_linux.md")
96
+ console.log(" Windows: Ver https://github.com/cli/cli#installation")
97
+ console.log("")
98
+ console.log("Después de instalar, ejecuta: gh auth login")
99
+ console.log("")
100
+ await this.waitForEnter("Presiona Enter para salir...")
101
+ process.exit(1)
102
+ }
103
+
104
+ console.log("")
105
+
106
+ // Check gh authentication
107
+ console.log("Verificando autenticación en GitHub...")
108
+ try {
109
+ execSync('gh auth status', { encoding: 'utf8', stdio: 'pipe' })
110
+ console.log("✅ Autenticado en GitHub")
111
+ } catch (error) {
112
+ console.log("❌ No estás autenticado en GitHub")
113
+ console.log("")
114
+ console.log("Por favor ejecuta: gh auth login")
115
+ console.log("Y sigue las instrucciones para autenticarte.")
116
+ console.log("")
117
+ await this.waitForEnter("Presiona Enter para salir...")
118
+ process.exit(1)
119
+ }
120
+
121
+ console.log("")
122
+ console.log("🎉 ¡Todo listo! Tienes todas las herramientas necesarias.")
123
+
124
+ await this.waitForEnter("\nPresiona Enter para continuar...")
125
+ }
126
+
127
+ async step1_ForkExplanation() {
128
+ console.clear()
129
+ console.log("Paso 1: Entender el Fork")
130
+ console.log("=" .repeat(50))
131
+ console.log("")
132
+ console.log("🤔 ¿Qué es un fork?")
133
+ console.log("")
134
+ console.log("Un fork es una COPIA de un repositorio que queda en TU cuenta de GitHub.")
135
+ console.log("Es como sacar una fotocopia de un libro para poder escribir en él.")
136
+ console.log("")
137
+ console.log("¿Por qué necesitamos hacer fork?")
138
+ console.log(" • No tienes permisos para modificar el repositorio original")
139
+ console.log(" • Trabajas en tu copia sin afectar el original")
140
+ console.log(" • Después propones tus cambios mediante una Pull Request")
141
+
142
+ await this.waitForEnter("\nPresiona Enter para hacer el fork...")
143
+
144
+ console.log("")
145
+ console.log("📋 Vamos a hacer fork del repositorio de práctica de Creta:")
146
+ console.log(` ${this.repoUrl}`)
147
+ console.log("")
148
+ console.log("Ejecutando: gh repo fork icarusmx/creta-practice --clone=false")
149
+ console.log("")
150
+
151
+ try {
152
+ // Primero verificar si el repo existe o si ya tiene un fork
153
+ try {
154
+ const checkFork = execSync(`gh repo view ${this.practiceRepo}`, {
155
+ encoding: 'utf8',
156
+ stdio: 'pipe'
157
+ })
158
+
159
+ // Si llegamos aquí, el repo existe, intentar hacer fork
160
+ try {
161
+ execSync(`gh repo fork ${this.practiceRepo} --clone=false`, {
162
+ encoding: 'utf8',
163
+ stdio: 'inherit'
164
+ })
165
+ console.log("✅ Fork creado exitosamente")
166
+ } catch (forkError) {
167
+ // Probablemente ya tiene un fork
168
+ console.log("ℹ️ Ya tienes un fork de este repositorio")
169
+ }
170
+ } catch (repoError) {
171
+ console.log("⚠️ El repositorio de práctica aún no existe.")
172
+ console.log("")
173
+ console.log("Por ahora, practicaremos con el repositorio de Creta CLI:")
174
+ this.practiceRepo = 'icarusmx/creta'
175
+ this.repoUrl = `https://github.com/${this.practiceRepo}`
176
+
177
+ console.log("")
178
+ console.log(`Haciendo fork de: ${this.repoUrl}`)
179
+ console.log("")
180
+
181
+ try {
182
+ execSync(`gh repo fork ${this.practiceRepo} --clone=false`, {
183
+ encoding: 'utf8',
184
+ stdio: 'inherit'
185
+ })
186
+ console.log("✅ Fork creado exitosamente")
187
+ } catch (forkError) {
188
+ console.log("ℹ️ Ya tienes un fork de este repositorio")
189
+ }
190
+ }
191
+ } catch (error) {
192
+ console.log("❌ Error al hacer fork:", error.message)
193
+ console.log("")
194
+ console.log("Puedes hacer fork manualmente en:")
195
+ console.log(`${this.repoUrl}`)
196
+ console.log("(Click en el botón 'Fork' en la esquina superior derecha)")
197
+ }
198
+
199
+ await this.waitForEnter("\nPresiona Enter para continuar al siguiente paso...")
200
+ }
201
+
202
+ async step2_CloneRepo() {
203
+ console.clear()
204
+ console.log("Paso 2: Clonar tu Fork")
205
+ console.log("=" .repeat(50))
206
+ console.log("")
207
+ console.log("🤔 ¿Qué es clonar?")
208
+ console.log("")
209
+ console.log("Clonar es DESCARGAR una copia del repositorio a tu computadora.")
210
+ console.log("Ahora trabajarás en tu máquina local, no en GitHub.")
211
+ console.log("")
212
+ console.log("¿Por qué clonar tu fork y no el original?")
213
+ console.log(" • Tu fork está en tu cuenta, puedes hacer push a él")
214
+ console.log(" • El original no aceptaría tus cambios directos")
215
+
216
+ await this.waitForEnter("\nPresiona Enter para clonar...")
217
+
218
+ console.log("")
219
+ console.log("🔧 Ahora necesitamos clonar TU fork (no el original)")
220
+ console.log("")
221
+
222
+ // Obtener el username de GitHub
223
+ let username
224
+ try {
225
+ const authStatus = execSync('gh api user --jq .login', { encoding: 'utf8' }).trim()
226
+ username = authStatus
227
+ console.log(`Tu usuario de GitHub: ${username}`)
228
+ } catch (error) {
229
+ console.log("No pudimos obtener tu usuario automáticamente.")
230
+ username = await this.askQuestion("¿Cuál es tu usuario de GitHub? ")
231
+ }
232
+
233
+ const forkUrl = `https://github.com/${username}/${this.practiceRepo.split('/')[1]}`
234
+ const projectName = this.practiceRepo.split('/')[1]
235
+
236
+ console.log("")
237
+ console.log(`Clonando: ${forkUrl}`)
238
+ console.log("")
239
+
240
+ try {
241
+ // Verificar si ya existe el directorio
242
+ if (fs.existsSync(projectName)) {
243
+ console.log(`ℹ️ El directorio '${projectName}' ya existe.`)
244
+ const useExisting = await this.askQuestion("¿Quieres usar este directorio? (s/n): ")
245
+
246
+ if (useExisting.toLowerCase() === 's') {
247
+ this.projectDir = path.resolve(projectName)
248
+ console.log(`✅ Usando directorio existente: ${this.projectDir}`)
249
+ } else {
250
+ console.log("Por favor elimina o renombra el directorio y vuelve a intentar.")
251
+ process.exit(0)
252
+ }
253
+ } else {
254
+ console.log(`Ejecutando: git clone ${forkUrl}`)
255
+ execSync(`git clone ${forkUrl}`, { encoding: 'utf8', stdio: 'inherit' })
256
+ this.projectDir = path.resolve(projectName)
257
+ console.log(`✅ Repositorio clonado en: ${this.projectDir}`)
258
+ }
259
+ } catch (error) {
260
+ console.log("❌ Error al clonar:", error.message)
261
+ process.exit(1)
262
+ }
263
+
264
+ await this.waitForEnter("\nPresiona Enter para continuar al siguiente paso...")
265
+ }
266
+
267
+ async step3_CreateBranch() {
268
+ console.clear()
269
+ console.log("Paso 3: Crear una Rama (Branch)")
270
+ console.log("=" .repeat(50))
271
+ console.log("")
272
+ console.log("🤔 ¿Qué es una rama?")
273
+ console.log("")
274
+ console.log("Una rama es una LÍNEA DE DESARROLLO independiente.")
275
+ console.log("Es como trabajar en una copia paralela del código.")
276
+ console.log("")
277
+ console.log("¿Por qué crear una rama?")
278
+ console.log(" • No modificas la rama principal (main) directamente")
279
+ console.log(" • Puedes experimentar sin romper nada")
280
+ console.log(" • Facilita el code review")
281
+ console.log(" • Es la práctica estándar en equipos profesionales")
282
+
283
+ await this.waitForEnter("\nPresiona Enter para crear tu rama...")
284
+
285
+ console.log("")
286
+ console.log("💡 Reglas para nombres de ramas:")
287
+ console.log(" • Descriptivos y cortos")
288
+ console.log(" • En minúsculas, separados por guiones")
289
+ console.log(" • Ejemplos: fix-navbar, add-login, update-readme")
290
+ console.log("")
291
+
292
+ const defaultBranchName = "mi-primera-contribucion"
293
+ const branchName = await this.askQuestion(`Nombre de tu rama [${defaultBranchName}]: `) || defaultBranchName
294
+
295
+ console.log("")
296
+ console.log(`Ejecutando: git checkout -b ${branchName}`)
297
+ console.log("")
298
+
299
+ try {
300
+ process.chdir(this.projectDir)
301
+ execSync(`git checkout -b ${branchName}`, { encoding: 'utf8', stdio: 'inherit' })
302
+ console.log(`✅ Rama '${branchName}' creada y seleccionada`)
303
+ } catch (error) {
304
+ console.log("❌ Error al crear rama:", error.message)
305
+ process.exit(1)
306
+ }
307
+
308
+ await this.waitForEnter("\nPresiona Enter para continuar al siguiente paso...")
309
+ }
310
+
311
+ async step4_MakeChanges() {
312
+ console.clear()
313
+ console.log("Paso 4: Hacer Cambios")
314
+ console.log("=" .repeat(50))
315
+ console.log("")
316
+ console.log("🤔 ¿Qué cambios haremos?")
317
+ console.log("")
318
+ console.log("Vamos a agregar tu nombre a un archivo de contribuidores.")
319
+ console.log("Este es un cambio simple y seguro para practicar.")
320
+ console.log("")
321
+
322
+ await this.waitForEnter("Presiona Enter para hacer el cambio...")
323
+
324
+ console.log("")
325
+
326
+ const contributorsFile = path.join(this.projectDir, 'CONTRIBUTORS.md')
327
+
328
+ // Crear archivo si no existe
329
+ if (!fs.existsSync(contributorsFile)) {
330
+ console.log("Creando archivo CONTRIBUTORS.md...")
331
+ fs.writeFileSync(contributorsFile, '# Contribuidores\n\nGracias a estas personas por contribuir al proyecto:\n\n')
332
+ }
333
+
334
+ const name = await this.askQuestion("¿Cómo te llamas? ")
335
+ const date = new Date().toISOString().split('T')[0]
336
+
337
+ console.log("")
338
+ console.log("Agregando tu nombre al archivo...")
339
+
340
+ try {
341
+ const content = fs.readFileSync(contributorsFile, 'utf8')
342
+ const newContent = content + `- ${name} (${date})\n`
343
+ fs.writeFileSync(contributorsFile, newContent)
344
+ console.log("✅ Cambio realizado exitosamente")
345
+ console.log("")
346
+ console.log(`Tu nombre fue agregado a: ${contributorsFile}`)
347
+ } catch (error) {
348
+ console.log("❌ Error al modificar archivo:", error.message)
349
+ process.exit(1)
350
+ }
351
+
352
+ console.log("")
353
+ console.log("🔍 Verificando cambios...")
354
+ console.log("")
355
+ console.log("Ejecutando: git status")
356
+ console.log("")
357
+
358
+ try {
359
+ execSync('git status', { encoding: 'utf8', stdio: 'inherit' })
360
+ } catch (error) {
361
+ // git status siempre funciona
362
+ }
363
+
364
+ await this.waitForEnter("\nPresiona Enter para continuar al siguiente paso...")
365
+ }
366
+
367
+ async step5_CommitChanges() {
368
+ console.clear()
369
+ console.log("Paso 5: Hacer Commit de los Cambios")
370
+ console.log("=" .repeat(50))
371
+ console.log("")
372
+ console.log("🤔 ¿Qué es un commit?")
373
+ console.log("")
374
+ console.log("Un commit es un PUNTO DE GUARDADO en la historia del proyecto.")
375
+ console.log("Es como tomar una fotografía del estado actual del código.")
376
+ console.log("")
377
+ console.log("Cada commit tiene:")
378
+ console.log(" • Los cambios que hiciste")
379
+ console.log(" • Un mensaje describiendo QUÉ y POR QUÉ")
380
+ console.log(" • Tu nombre y la fecha")
381
+
382
+ await this.waitForEnter("\nPresiona Enter para hacer el commit...")
383
+
384
+ console.log("")
385
+ console.log("📝 Primero agregamos el archivo al staging area:")
386
+ console.log("Ejecutando: git add CONTRIBUTORS.md")
387
+ console.log("")
388
+
389
+ try {
390
+ execSync('git add CONTRIBUTORS.md', { encoding: 'utf8', stdio: 'inherit' })
391
+ console.log("✅ Archivo agregado al staging area")
392
+ } catch (error) {
393
+ console.log("❌ Error al agregar archivo:", error.message)
394
+ process.exit(1)
395
+ }
396
+
397
+ console.log("")
398
+ console.log("💡 Ahora escribimos el mensaje del commit")
399
+ console.log("")
400
+ console.log("Reglas para buenos mensajes:")
401
+ console.log(" • Primera línea: resumen corto (50 caracteres)")
402
+ console.log(" • Usa imperativo: 'Agrega' no 'Agregué' ni 'Agregando'")
403
+ console.log(" • Sé específico: di QUÉ cambió y POR QUÉ")
404
+ console.log("")
405
+
406
+ const name = await this.askQuestion("¿Cómo te llamas? (para el mensaje del commit): ")
407
+ const commitMessage = `Agrega ${name} a la lista de contribuidores`
408
+
409
+ console.log("")
410
+ console.log(`Mensaje del commit: "${commitMessage}"`)
411
+ console.log("")
412
+ console.log(`Ejecutando: git commit -m "${commitMessage}"`)
413
+ console.log("")
414
+
415
+ try {
416
+ execSync(`git commit -m "${commitMessage}"`, { encoding: 'utf8', stdio: 'inherit' })
417
+ console.log("✅ Commit creado exitosamente")
418
+ } catch (error) {
419
+ console.log("❌ Error al crear commit:", error.message)
420
+ process.exit(1)
421
+ }
422
+
423
+ console.log("")
424
+ console.log("🎉 ¡Tu cambio ahora es parte de la historia del repositorio!")
425
+ console.log("")
426
+ console.log("Puedes verlo con: git log --oneline")
427
+
428
+ await this.waitForEnter("\nPresiona Enter para continuar al siguiente paso...")
429
+ }
430
+
431
+ async step6_PushBranch() {
432
+ console.clear()
433
+ console.log("Paso 6: Subir los Cambios a GitHub (Push)")
434
+ console.log("=" .repeat(50))
435
+ console.log("")
436
+ console.log("🤔 ¿Qué es push?")
437
+ console.log("")
438
+ console.log("Push es SUBIR tus commits locales a GitHub.")
439
+ console.log("Hasta ahora todo estaba solo en tu computadora.")
440
+ console.log("")
441
+ console.log("¿Por qué hacer push?")
442
+ console.log(" • Respalda tu trabajo en la nube")
443
+ console.log(" • Otros pueden ver tus cambios")
444
+ console.log(" • Es necesario para crear la Pull Request")
445
+
446
+ await this.waitForEnter("\nPresiona Enter para hacer push...")
447
+
448
+ console.log("")
449
+ console.log("🚀 Subiendo tu rama a GitHub...")
450
+ console.log("")
451
+
452
+ // Obtener el nombre de la rama actual
453
+ let branchName
454
+ try {
455
+ branchName = execSync('git branch --show-current', { encoding: 'utf8' }).trim()
456
+ } catch (error) {
457
+ branchName = 'tu-rama'
458
+ }
459
+
460
+ console.log(`Ejecutando: git push origin ${branchName}`)
461
+ console.log("")
462
+
463
+ try {
464
+ execSync(`git push -u origin ${branchName}`, { encoding: 'utf8', stdio: 'inherit' })
465
+ console.log("")
466
+ console.log("✅ Cambios subidos exitosamente a GitHub")
467
+ } catch (error) {
468
+ console.log("❌ Error al hacer push:", error.message)
469
+ console.log("")
470
+ console.log("Si el error es de autenticación, verifica que estés autenticado con gh.")
471
+ process.exit(1)
472
+ }
473
+
474
+ console.log("")
475
+ console.log("🎉 ¡Tu rama ya está en GitHub!")
476
+ console.log("Ahora cualquiera puede ver tus cambios.")
477
+
478
+ await this.waitForEnter("\nPresiona Enter para continuar al siguiente paso...")
479
+ }
480
+
481
+ async step7_CreatePR() {
482
+ console.clear()
483
+ console.log("Paso 7: Crear la Pull Request")
484
+ console.log("=" .repeat(50))
485
+ console.log("")
486
+ console.log("🤔 ¿Qué es una Pull Request (PR)?")
487
+ console.log("")
488
+ console.log("Una PR es una PROPUESTA de cambios al repositorio original.")
489
+ console.log("Es como decir: 'Mira los cambios que hice, ¿los aceptas?'")
490
+ console.log("")
491
+ console.log("¿Qué pasa después de crear una PR?")
492
+ console.log(" • Los mantenedores revisan tu código")
493
+ console.log(" • Pueden pedirte cambios")
494
+ console.log(" • Si todo está bien, la aceptan (merge)")
495
+ console.log(" • Tus cambios quedan en el proyecto original")
496
+
497
+ await this.waitForEnter("\nPresiona Enter para crear la PR...")
498
+
499
+ console.log("")
500
+ console.log("📝 Vamos a crear la PR con información descriptiva")
501
+ console.log("")
502
+
503
+ const prTitle = await this.askQuestion("Título de la PR [Agrega nuevo contribuidor]: ") || "Agrega nuevo contribuidor"
504
+
505
+ console.log("")
506
+ console.log("💡 El cuerpo de la PR debe explicar:")
507
+ console.log(" • QUÉ cambios hiciste")
508
+ console.log(" • POR QUÉ los hiciste")
509
+ console.log(" • CÓMO probaste que funcionan")
510
+ console.log("")
511
+
512
+ const prBody = `## Descripción
513
+
514
+ Agregué mi nombre a la lista de contribuidores como parte del tutorial de Creta.
515
+
516
+ ## Checklist
517
+
518
+ - [x] El código sigue las convenciones del proyecto
519
+ - [x] He probado mis cambios localmente
520
+ - [x] Los cambios no rompen funcionalidad existente
521
+
522
+ ---
523
+
524
+ 🏛️ Creado con [Creta](https://github.com/icarusmx/creta) - Tutorial de Pull Requests`
525
+
526
+ console.log("")
527
+ console.log(`Ejecutando: gh pr create --title "${prTitle}" --body "..."`)
528
+ console.log("")
529
+
530
+ try {
531
+ execSync(`gh pr create --title "${prTitle}" --body "${prBody}"`, {
532
+ encoding: 'utf8',
533
+ stdio: 'inherit'
534
+ })
535
+ console.log("")
536
+ console.log("✅ Pull Request creada exitosamente")
537
+ } catch (error) {
538
+ console.log("❌ Error al crear PR:", error.message)
539
+ console.log("")
540
+ console.log("Puedes crear la PR manualmente en GitHub:")
541
+ console.log(`${this.repoUrl}/pulls`)
542
+ }
543
+
544
+ await this.waitForEnter("\nPresiona Enter para ver la conclusión...")
545
+ }
546
+
547
+ async conclusion() {
548
+ console.clear()
549
+ console.log("🎉 ¡Felicidades! Completaste el Tutorial")
550
+ console.log("=" .repeat(50))
551
+ console.log("")
552
+ console.log("📚 Lo que aprendiste:")
553
+ console.log("")
554
+ console.log("✅ Qué es un fork y cómo hacerlo")
555
+ console.log("✅ Cómo clonar un repositorio")
556
+ console.log("✅ Cómo crear y trabajar en ramas")
557
+ console.log("✅ Cómo hacer commits con buenos mensajes")
558
+ console.log("✅ Cómo subir cambios con push")
559
+ console.log("✅ Cómo crear una Pull Request profesional")
560
+ console.log("")
561
+ console.log("🚀 Ahora estás listo para:")
562
+ console.log("")
563
+ console.log("• Contribuir a proyectos open source")
564
+ console.log("• Trabajar en equipos de desarrollo")
565
+ console.log("• Seguir las mejores prácticas de Git/GitHub")
566
+ console.log("")
567
+ console.log("💡 Próximos pasos:")
568
+ console.log("")
569
+ console.log("1. Espera feedback en tu PR")
570
+ console.log("2. Practica haciendo más PRs")
571
+ console.log("3. Busca proyectos open source para contribuir")
572
+ console.log("")
573
+ console.log("📖 Recursos adicionales:")
574
+ console.log("")
575
+ console.log("• GitHub Docs: https://docs.github.com/pull-requests")
576
+ console.log("• Git Book: https://git-scm.com/book/es")
577
+ console.log("• First Contributions: https://firstcontributions.github.io")
578
+
579
+ await this.waitForEnter("\n✨ Presiona Enter para finalizar...")
580
+ }
581
+
582
+ async waitForEnter(message) {
583
+ return new Promise((resolve) => {
584
+ this.rl.question(message, () => {
585
+ console.log("")
586
+ resolve()
587
+ })
588
+ })
589
+ }
590
+
591
+ async askQuestion(question) {
592
+ return new Promise((resolve) => {
593
+ this.rl.question(question, (answer) => {
594
+ resolve(answer)
595
+ })
596
+ })
597
+ }
598
+ }
599
+
600
+ // Para usar el tutorial independientemente
601
+ if (import.meta.url === `file://${process.argv[1]}`) {
602
+ const tutorial = new PullRequestTutorial()
603
+ await tutorial.start()
604
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@icarusmx/creta",
3
- "version": "1.2.1",
3
+ "version": "1.3.1",
4
4
  "description": "Salgamos de este laberinto.",
5
5
  "type": "module",
6
6
  "bin": {