@icarusmx/creta 1.5.12 → 1.5.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. package/bin/creta.js +31 -1
  2. package/lib/data/command-help/aws-ec2.js +34 -0
  3. package/lib/data/command-help/grep.js +76 -0
  4. package/lib/data/command-help/index.js +11 -1
  5. package/lib/data/command-help/lz.js +57 -0
  6. package/lib/executors/CommandHelpExecutor.js +6 -1
  7. package/lib/exercises/.claude/settings.local.json +12 -0
  8. package/lib/exercises/01-developing-muscle-for-nvim.md +528 -0
  9. package/lib/exercises/{iterm2-pane-navigation.md → 02-iterm2-pane-navigation.md} +1 -1
  10. package/lib/exercises/05-svelte-first-steps.md +1340 -0
  11. package/lib/exercises/{curl-and-pipes.md → 06-curl-and-pipes.md} +187 -72
  12. package/lib/exercises/07-claude-api-first-steps.md +855 -0
  13. package/lib/exercises/08-playwright-svelte-guide.md +1384 -0
  14. package/lib/exercises/09-docker-first-steps.md +1475 -0
  15. package/lib/exercises/{railway-deployment.md → 10-railway-deployment.md} +1 -0
  16. package/lib/exercises/{aws-billing-detective.md → 11-aws-billing-detective.md} +215 -35
  17. package/lib/exercises/12-install-skills.md +755 -0
  18. package/lib/exercises/13-shell-aliases.md +134 -0
  19. package/lib/exercises/README.md +187 -0
  20. package/lib/exercises/utils/booklet-2up.js +133 -0
  21. package/lib/exercises/utils/booklet-manual-duplex.js +159 -0
  22. package/lib/exercises/utils/booklet-simple.js +136 -0
  23. package/lib/exercises/utils/create-booklet.js +116 -0
  24. package/lib/scripts/aws-ec2-all.sh +58 -0
  25. package/package.json +3 -2
  26. /package/lib/exercises/{git-stash-workflow.md → 03-git-stash-workflow.md} +0 -0
  27. /package/lib/exercises/{array-object-manipulation.md → 04-array-object-manipulation.md} +0 -0
@@ -0,0 +1,134 @@
1
+ # Shell Aliases: Personaliza tu Terminal
2
+
3
+ ## Objetivo
4
+
5
+ Aprende a crear alias de shell para hacer comandos más intuitivos y personalizados. Crearemos el alias `lz` (localiza recurso) para `curl`.
6
+
7
+ ## ¿Qué es un Alias?
8
+
9
+ Un alias es un atajo personalizado para un comando. Por ejemplo:
10
+ - En vez de escribir `curl https://icarus.mx`
11
+ - Escribes `lz https://icarus.mx`
12
+
13
+ ## Por qué `lz` para curl
14
+
15
+ **curl** = Client URL (Cliente de URL)
16
+ - **Cliente**: localiza recursos para ti
17
+ - **Universal**: usa HTTP (HyperText Transfer Protocol)
18
+ - **lz**: "localiza recurso" - alias en español
19
+
20
+ ## Paso 1: Identificar tu Shell
21
+
22
+ En macOS Catalina+ se usa `zsh` por defecto:
23
+
24
+ ```bash
25
+ echo $SHELL
26
+ ```
27
+
28
+ Si ves `/bin/zsh` → usas zsh (archivo: `~/.zshrc`)
29
+ Si ves `/bin/bash` → usas bash (archivo: `~/.bashrc`)
30
+
31
+ ## Paso 2: Abrir el Archivo de Configuración
32
+
33
+ Para zsh (más común en Mac):
34
+
35
+ ```bash
36
+ nvim ~/.zshrc
37
+ ```
38
+
39
+ O si prefieres VS Code:
40
+
41
+ ```bash
42
+ code ~/.zshrc
43
+ ```
44
+
45
+ ## Paso 3: Agregar el Alias
46
+
47
+ Al final del archivo, agrega:
48
+
49
+ ```bash
50
+ # Alias personalizados
51
+ alias lz='curl'
52
+ ```
53
+
54
+ Guarda el archivo:
55
+ - En nvim: presiona `ESC`, luego escribe `:wq` y `ENTER`
56
+ - En VS Code: `Cmd + S`
57
+
58
+ ## Paso 4: Recargar la Configuración
59
+
60
+ Tienes dos opciones:
61
+
62
+ **Opción 1: Recargar el archivo**
63
+ ```bash
64
+ source ~/.zshrc
65
+ ```
66
+
67
+ **Opción 2: Cerrar y abrir una nueva terminal**
68
+
69
+ ## Paso 5: Probar el Alias
70
+
71
+ ```bash
72
+ lz icarus.mx
73
+ ```
74
+
75
+ Deberías ver el HTML del sitio de Icarus.
76
+
77
+ ## Alias Útiles para Agregar
78
+
79
+ Mientras estás editando `~/.zshrc`, considera agregar:
80
+
81
+ ```bash
82
+ # Alias para comandos comunes
83
+ alias ls='ls -G' # ls con colores en Mac
84
+ alias ll='ls -lah' # ls largo con todo
85
+ alias ..='cd ..' # sube un nivel
86
+ alias ...='cd ../..' # sube dos niveles
87
+ alias gs='git status' # git status rápido
88
+ alias ga='git add' # git add rápido
89
+ alias gc='git commit -m' # git commit con mensaje
90
+ alias gp='git push' # git push rápido
91
+ alias lz='curl' # localiza recurso (nuestro nuevo alias)
92
+
93
+ # Alias para navegación rápida
94
+ alias proyectos='cd ~/proyectos'
95
+ alias icarus='cd ~/icarus'
96
+ ```
97
+
98
+ ## Verificar que Funciona
99
+
100
+ ```bash
101
+ # Probar lz
102
+ lz -I icarus.mx
103
+
104
+ # Debería mostrar los headers del sitio
105
+ ```
106
+
107
+ ## Troubleshooting
108
+
109
+ **Problema: "command not found: lz"**
110
+ - Solución: Verifica que ejecutaste `source ~/.zshrc`
111
+
112
+ **Problema: "No such file or directory: ~/.zshrc"**
113
+ - Solución: Crea el archivo primero: `touch ~/.zshrc`
114
+
115
+ **Problema: Sigo usando bash en vez de zsh**
116
+ - Solución: Edita `~/.bashrc` en vez de `~/.zshrc`
117
+
118
+ ## Siguiente Nivel
119
+
120
+ Una vez que domines los alias básicos, aprende sobre:
121
+ - **Funciones de shell**: alias con argumentos complejos
122
+ - **Dotfiles**: versionar tu configuración con Git
123
+ - **Oh My Zsh**: framework para mejorar tu terminal
124
+
125
+ ## Recursos
126
+
127
+ - `man zsh` - Manual de zsh
128
+ - `man bash` - Manual de bash
129
+ - `alias` - Ver todos tus alias actuales
130
+ - `unalias lz` - Eliminar un alias temporal
131
+
132
+ ---
133
+
134
+ **Pro tip**: Los alias se cargan cada vez que abres una terminal nueva. Si quieres un alias temporal (solo para la sesión actual), simplemente escribe `alias lz='curl'` directo en la terminal sin agregarlo al archivo.
@@ -0,0 +1,187 @@
1
+ # Creta Exercise Guides
2
+
3
+ Progressive learning paths for modern web development - from terminal comfort to production deployment.
4
+
5
+ ---
6
+
7
+ ## 🗺️ Learning Journey
8
+
9
+ These exercises follow a natural progression: **Setup → Build → Ship → Scale**
10
+
11
+ Each phase builds on knowledge from previous phases.
12
+
13
+ ---
14
+
15
+ ## Phase 1: Setup & Basics
16
+
17
+ **Get comfortable with your tools and avoid common mistakes**
18
+
19
+ ### 1. [Developing Muscle Memory for Neovim](./01-developing-muscle-for-nvim.md)
20
+ **What:** Master vim navigation with `}`, `{`, and fold commands
21
+ **Why:** Efficient code reading without touching the mouse
22
+ **Time:** 15 minutes
23
+ **Level:** 🟢 Beginner
24
+
25
+ ### 2. [iTerm2 Pane Navigation](./02-iterm2-pane-navigation.md)
26
+ **What:** Custom keyboard shortcuts for terminal pane switching
27
+ **Why:** Stop using 3-key combos to move between panes
28
+ **Time:** 10 minutes
29
+ **Level:** 🟢 Beginner
30
+
31
+ ### 3. [Git Stash Workflow](./03-git-stash-workflow.md)
32
+ **What:** Rescue changes made on the wrong branch
33
+ **Why:** Clean recovery when you code on `main` instead of `dev`
34
+ **Time:** 15 minutes
35
+ **Level:** 🟢 Beginner
36
+
37
+ ### 13. [Shell Aliases: Personaliza tu Terminal](./13-shell-aliases.md)
38
+ **What:** Crear alias personalizados (lz para curl)
39
+ **Why:** Comandos más intuitivos en español - piensa "localiza recurso"
40
+ **Time:** 10 minutes
41
+ **Level:** 🟢 Beginner
42
+
43
+ ### 4. [Array & Object Manipulation](./04-array-object-manipulation.md)
44
+ **What:** JavaScript warmups - map, filter, reduce, destructuring
45
+ **Why:** Foundation for real app data transformations
46
+ **Time:** 45 minutes
47
+ **Level:** 🟡 Intermediate
48
+
49
+ ---
50
+
51
+ ## Phase 2: Build
52
+
53
+ **Create real applications with modern frameworks**
54
+
55
+ ### 5. [SvelteKit First Steps](./05-svelte-first-steps.md)
56
+ **What:** Understand SvelteKit project structure and file conventions
57
+ **Why:** Stop guessing where files go - learn the routing system
58
+ **Time:** 30 minutes
59
+ **Level:** 🟡 Intermediate
60
+
61
+ ### 6. [curl + Pipes](./06-curl-and-pipes.md)
62
+ **What:** Process APIs in real-time with curl and bash pipes
63
+ **Why:** Test endpoints, debug requests, automate data workflows
64
+ **Time:** 45 minutes
65
+ **Level:** 🟡 Intermediate
66
+
67
+ ### 7. [Claude API First Steps](./07-claude-api-first-steps.md)
68
+ **What:** Call Claude API directly from terminal with curl
69
+ **Why:** Get AI assistance without leaving your workflow
70
+ **Time:** 30 minutes
71
+ **Level:** 🟡 Intermediate
72
+
73
+ ### 8. [Playwright + Svelte Testing](./08-playwright-svelte-guide.md)
74
+ **What:** E2E testing for SvelteKit apps with Tailwind
75
+ **Why:** Test user flows without breaking when you change CSS
76
+ **Time:** 2-3 hours
77
+ **Level:** 🟡 Intermediate
78
+
79
+ ---
80
+
81
+ ## Phase 3: Ship
82
+
83
+ **Deploy applications to production**
84
+
85
+ ### 9. [Docker First Steps](./09-docker-first-steps.md)
86
+ **What:** Containerize Node.js apps to solve "works on my machine"
87
+ **Why:** Consistent environments from dev to production
88
+ **Time:** 1 hour
89
+ **Level:** 🟡 Intermediate
90
+
91
+ ### 10. [Railway Deployment](./10-railway-deployment.md)
92
+ **What:** Deploy Node.js/Express apps with custom domains
93
+ **Why:** Ship to production in minutes, not hours
94
+ **Time:** 45 minutes
95
+ **Level:** 🟡 Intermediate
96
+
97
+ ### 11. [AWS Billing Detective](./11-aws-billing-detective.md)
98
+ **What:** Hunt down hidden AWS costs across all regions
99
+ **Why:** Stop getting charged for forgotten EBS volumes and Elastic IPs
100
+ **Time:** 30-60 minutes
101
+ **Level:** 🔴 Advanced
102
+
103
+ ---
104
+
105
+ ## Phase 4: Scale
106
+
107
+ **Advanced tooling and workflow optimization**
108
+
109
+ ### 12. [Install Skills Command](./12-install-skills.md)
110
+ **What:** Design a CLI skill installer with project analysis
111
+ **Why:** Automate development workflow setup
112
+ **Time:** Design exercise
113
+ **Level:** 🔴 Advanced
114
+
115
+ ---
116
+
117
+ ## 🎯 How to Use These Guides
118
+
119
+ ### Reading in Neovim/LazyVim
120
+ All guides have vim fold markers for structured navigation:
121
+
122
+ ```vim
123
+ zM " Close all folds (see just headers)
124
+ za " Toggle current fold
125
+ zR " Open all folds
126
+ } " Jump to next paragraph
127
+ { " Jump to previous paragraph
128
+ ```
129
+
130
+ ### Progressive Learning
131
+ - **New to CLI?** Start with Phase 1
132
+ - **Know basics?** Jump to Phase 2 (SvelteKit)
133
+ - **Ready to deploy?** Go straight to Phase 3
134
+ - **Optimizing workflows?** Phase 4 has advanced patterns
135
+
136
+ ### Hands-On Practice
137
+ Every guide includes:
138
+ - 🚨 **Problem** - What confusion does this solve?
139
+ - ✅ **Solution** - Step-by-step walkthrough
140
+ - 💪 **Practice** - Real commands to run
141
+ - 🎯 **Outcomes** - What you'll be able to do
142
+
143
+ ---
144
+
145
+ ## 🧭 By Topic
146
+
147
+ Prefer to browse by subject?
148
+
149
+ ### Terminal & Workflow
150
+ - [Developing Muscle Memory for Neovim](./01-developing-muscle-for-nvim.md)
151
+ - [iTerm2 Pane Navigation](./02-iterm2-pane-navigation.md)
152
+ - [Git Stash Workflow](./03-git-stash-workflow.md)
153
+ - [Shell Aliases](./13-shell-aliases.md)
154
+ - [curl + Pipes](./06-curl-and-pipes.md)
155
+
156
+ ### JavaScript & Development
157
+ - [Array & Object Manipulation](./04-array-object-manipulation.md)
158
+ - [SvelteKit First Steps](./05-svelte-first-steps.md)
159
+
160
+ ### AI & APIs
161
+ - [Claude API First Steps](./07-claude-api-first-steps.md)
162
+
163
+ ### Testing
164
+ - [Playwright + Svelte Testing](./08-playwright-svelte-guide.md)
165
+
166
+ ### Deployment & Infrastructure
167
+ - [Docker First Steps](./09-docker-first-steps.md)
168
+ - [Railway Deployment](./10-railway-deployment.md)
169
+ - [AWS Billing Detective](./11-aws-billing-detective.md)
170
+
171
+ ### Meta/Tooling
172
+ - [Install Skills Command](./12-install-skills.md)
173
+
174
+ ---
175
+
176
+ ## 📚 Philosophy
177
+
178
+ These guides follow Creta's design principles:
179
+
180
+ **Spanish-First** - Native language where it matters (coming soon)
181
+ **Simple Solutions** - No over-engineering, practical examples
182
+ **Evolution Over Replacement** - Skills that grow with you
183
+ **CLI-Native** - Work where developers work
184
+
185
+ ---
186
+
187
+ **Questions? Issues?** Open an issue in the main Creta repo.
@@ -0,0 +1,133 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { PDFDocument } from 'pdf-lib';
4
+ import fs from 'fs/promises';
5
+ import path from 'path';
6
+
7
+ async function createBooklet2Up(inputPath) {
8
+ try {
9
+ const filename = path.basename(inputPath, '.pdf');
10
+ console.log(`\n📚 Creando booklet 2-up para: ${filename}`);
11
+
12
+ const srcFile = await fs.readFile(inputPath);
13
+ const srcDoc = await PDFDocument.load(srcFile);
14
+ const totalPages = srcDoc.getPageCount();
15
+
16
+ console.log(`📄 Páginas originales: ${totalPages}`);
17
+
18
+ // Páginas necesarias (múltiplo de 4)
19
+ const sheetsNeeded = Math.ceil(totalPages / 4);
20
+ const paddedPages = sheetsNeeded * 4;
21
+
22
+ console.log(`📄 Hojas físicas: ${sheetsNeeded}`);
23
+
24
+ // Obtener tamaño de página
25
+ const firstPage = srcDoc.getPage(0);
26
+ const { width: pageWidth, height: pageHeight } = firstPage.getSize();
27
+
28
+ // Crear docs finales
29
+ const frontDoc = await PDFDocument.create();
30
+ const backDoc = await PDFDocument.create();
31
+
32
+ // Para cada hoja física
33
+ for (let sheet = 0; sheet < sheetsNeeded; sheet++) {
34
+ // Índices para booklet (0-indexed)
35
+ const indices = {
36
+ frontRight: sheet * 2, // 0, 2, 4, 6...
37
+ frontLeft: paddedPages - (sheet * 2) - 1, // 23, 21, 19, 17...
38
+ backLeft: sheet * 2 + 1, // 1, 3, 5, 7...
39
+ backRight: paddedPages - (sheet * 2) - 2 // 22, 20, 18, 16...
40
+ };
41
+
42
+ console.log(`Hoja ${sheet + 1}:`);
43
+ console.log(` Frente: [${indices.frontLeft < totalPages ? indices.frontLeft + 1 : 'blank'}] [${indices.frontRight < totalPages ? indices.frontRight + 1 : 'blank'}]`);
44
+ console.log(` Reverso: [${indices.backLeft < totalPages ? indices.backLeft + 1 : 'blank'}] [${indices.backRight < totalPages ? indices.backRight + 1 : 'blank'}]`);
45
+
46
+ // FRENTE: crear hoja 2-up (landscape)
47
+ const frontSheet = frontDoc.addPage([pageWidth * 2, pageHeight]);
48
+
49
+ // Página derecha del frente
50
+ if (indices.frontRight < totalPages) {
51
+ const [page] = await frontDoc.copyPages(srcDoc, [indices.frontRight]);
52
+ const embeddedPage = await frontDoc.embedPage(page);
53
+ frontSheet.drawPage(embeddedPage, {
54
+ x: pageWidth,
55
+ y: 0,
56
+ width: pageWidth,
57
+ height: pageHeight
58
+ });
59
+ }
60
+
61
+ // Página izquierda del frente
62
+ if (indices.frontLeft < totalPages) {
63
+ const [page] = await frontDoc.copyPages(srcDoc, [indices.frontLeft]);
64
+ const embeddedPage = await frontDoc.embedPage(page);
65
+ frontSheet.drawPage(embeddedPage, {
66
+ x: 0,
67
+ y: 0,
68
+ width: pageWidth,
69
+ height: pageHeight
70
+ });
71
+ }
72
+
73
+ // REVERSO: crear hoja 2-up (landscape)
74
+ const backSheet = backDoc.addPage([pageWidth * 2, pageHeight]);
75
+
76
+ // Página izquierda del reverso
77
+ if (indices.backLeft < totalPages) {
78
+ const [page] = await backDoc.copyPages(srcDoc, [indices.backLeft]);
79
+ const embeddedPage = await backDoc.embedPage(page);
80
+ backSheet.drawPage(embeddedPage, {
81
+ x: 0,
82
+ y: 0,
83
+ width: pageWidth,
84
+ height: pageHeight
85
+ });
86
+ }
87
+
88
+ // Página derecha del reverso
89
+ if (indices.backRight < totalPages) {
90
+ const [page] = await backDoc.copyPages(srcDoc, [indices.backRight]);
91
+ const embeddedPage = await backDoc.embedPage(page);
92
+ backSheet.drawPage(embeddedPage, {
93
+ x: pageWidth,
94
+ y: 0,
95
+ width: pageWidth,
96
+ height: pageHeight
97
+ });
98
+ }
99
+ }
100
+
101
+ // Guardar
102
+ const frontPath = path.join(path.dirname(inputPath), `${filename}-FRENTE-v3.pdf`);
103
+ const backPath = path.join(path.dirname(inputPath), `${filename}-REVERSO-v3.pdf`);
104
+
105
+ await fs.writeFile(frontPath, await frontDoc.save());
106
+ await fs.writeFile(backPath, await backDoc.save());
107
+
108
+ console.log('\n✅ Creados:\n');
109
+ console.log(` ${path.basename(frontPath)}`);
110
+ console.log(` ${path.basename(backPath)}`);
111
+ console.log('\n📖 IMPRIME: FRENTE → mete hojas SIN voltear → REVERSO\n');
112
+
113
+ return { frontPath, backPath };
114
+
115
+ } catch (error) {
116
+ console.error('❌ Error:', error.message);
117
+ console.error(error.stack);
118
+ throw error;
119
+ }
120
+ }
121
+
122
+ async function main() {
123
+ const args = process.argv.slice(2);
124
+ if (args.length === 0) {
125
+ console.log('Uso: node booklet-2up.js <archivo.pdf>');
126
+ process.exit(1);
127
+ }
128
+
129
+ await createBooklet2Up(path.resolve(args[0]));
130
+ console.log('¡Listo! 🎉\n');
131
+ }
132
+
133
+ main().catch(console.error);
@@ -0,0 +1,159 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { PDFDocument } from 'pdf-lib';
4
+ import fs from 'fs/promises';
5
+ import path from 'path';
6
+
7
+ /**
8
+ * Crea un booklet para impresoras de duplex manual SIN voltear
9
+ * (para impresoras que imprimen en la cara de abajo)
10
+ */
11
+ async function createManualDuplexBooklet(inputPath) {
12
+ try {
13
+ const filename = path.basename(inputPath, '.pdf');
14
+ console.log(`\n📚 Creando booklet para: ${filename}`);
15
+
16
+ // Leer PDF original
17
+ const srcFile = await fs.readFile(inputPath);
18
+ const srcDoc = await PDFDocument.load(srcFile);
19
+ const totalPages = srcDoc.getPageCount();
20
+
21
+ console.log(`📄 Páginas originales: ${totalPages}`);
22
+
23
+ // Calcular páginas necesarias (múltiplo de 4)
24
+ const sheetsNeeded = Math.ceil(totalPages / 4);
25
+ const paddedPages = sheetsNeeded * 4;
26
+ const blankPages = paddedPages - totalPages;
27
+
28
+ if (blankPages > 0) {
29
+ console.log(`📄 Páginas en blanco añadidas: ${blankPages}`);
30
+ }
31
+
32
+ console.log(`📄 Total hojas físicas: ${sheetsNeeded}`);
33
+
34
+ // Crear documentos para frente y reverso
35
+ const frontDoc = await PDFDocument.create();
36
+ const backDoc = await PDFDocument.create();
37
+
38
+ // Crear página en blanco del mismo tamaño que la primera
39
+ const firstPage = srcDoc.getPage(0);
40
+ const { width, height } = firstPage.getSize();
41
+ const blankPage = await PDFDocument.create();
42
+ const blank = blankPage.addPage([width, height]);
43
+
44
+ // Algoritmo de booklet para duplex manual SIN voltear
45
+ // Para cada hoja física, el orden es:
46
+ // Frente: [última | primera]
47
+ // Reverso: [segunda | penúltima]
48
+
49
+ for (let sheet = 0; sheet < sheetsNeeded; sheet++) {
50
+ // Calcular números de página para esta hoja física (0-indexed)
51
+ const frontLeft = paddedPages - (sheet * 2) - 1; // última, antepenúltima, etc
52
+ const frontRight = (sheet * 2); // primera, tercera, etc
53
+ const backLeft = (sheet * 2) + 1; // segunda, cuarta, etc
54
+ const backRight = paddedPages - (sheet * 2) - 2; // penúltima, etc
55
+
56
+ console.log(`Hoja ${sheet + 1}: Frente[${frontLeft >= 0 && frontLeft < totalPages ? frontLeft + 1 : 'blank'}, ${frontRight >= 0 && frontRight < totalPages ? frontRight + 1 : 'blank'}] Reverso[${backLeft >= 0 && backLeft < totalPages ? backLeft + 1 : 'blank'}, ${backRight >= 0 && backRight < totalPages ? backRight + 1 : 'blank'}]`);
57
+
58
+ // Crear página de FRENTE (2 páginas lado a lado)
59
+ const frontPageDoc = await PDFDocument.create();
60
+ const frontPageSheet = frontPageDoc.addPage([width * 2, height]);
61
+
62
+ // Izquierda del frente
63
+ if (frontLeft >= 0 && frontLeft < totalPages) {
64
+ const [leftPage] = await frontPageDoc.copyPages(srcDoc, [frontLeft]);
65
+ frontPageSheet.drawPage(leftPage, { x: 0, y: 0, width, height });
66
+ }
67
+
68
+ // Derecha del frente
69
+ if (frontRight >= 0 && frontRight < totalPages) {
70
+ const [rightPage] = await frontPageDoc.copyPages(srcDoc, [frontRight]);
71
+ frontPageSheet.drawPage(rightPage, { x: width, y: 0, width, height });
72
+ }
73
+
74
+ // Copiar hoja de frente al documento final
75
+ const [finalFrontPage] = await frontDoc.copyPages(frontPageDoc, [0]);
76
+ frontDoc.addPage(finalFrontPage);
77
+
78
+ // Crear página de REVERSO (2 páginas lado a lado)
79
+ const backPageDoc = await PDFDocument.create();
80
+ const backPageSheet = backPageDoc.addPage([width * 2, height]);
81
+
82
+ // Izquierda del reverso
83
+ if (backLeft >= 0 && backLeft < totalPages) {
84
+ const [leftPage] = await backPageDoc.copyPages(srcDoc, [backLeft]);
85
+ backPageSheet.drawPage(leftPage, { x: 0, y: 0, width, height });
86
+ }
87
+
88
+ // Derecha del reverso
89
+ if (backRight >= 0 && backRight < totalPages) {
90
+ const [rightPage] = await backPageDoc.copyPages(srcDoc, [backRight]);
91
+ backPageSheet.drawPage(rightPage, { x: width, y: 0, width, height });
92
+ }
93
+
94
+ // Copiar hoja de reverso al documento final
95
+ const [finalBackPage] = await backDoc.copyPages(backPageDoc, [0]);
96
+ backDoc.addPage(finalBackPage);
97
+ }
98
+
99
+ // Como la impresora imprime del lado de abajo y las hojas salen al revés,
100
+ // necesitamos INVERTIR el orden de las páginas del REVERSO
101
+ const backPagesReversed = await PDFDocument.create();
102
+ const backPageCount = backDoc.getPageCount();
103
+ for (let i = backPageCount - 1; i >= 0; i--) {
104
+ const [page] = await backPagesReversed.copyPages(backDoc, [i]);
105
+ backPagesReversed.addPage(page);
106
+ }
107
+
108
+ // Guardar PDFs
109
+ const frontPath = path.join(path.dirname(inputPath), `${filename}-FRENTE.pdf`);
110
+ const backPath = path.join(path.dirname(inputPath), `${filename}-REVERSO.pdf`);
111
+
112
+ await fs.writeFile(frontPath, await frontDoc.save());
113
+ await fs.writeFile(backPath, await backPagesReversed.save());
114
+
115
+ console.log('\n✅ Booklets creados:\n');
116
+ console.log(` 📄 FRENTE: ${path.basename(frontPath)}`);
117
+ console.log(` 📄 REVERSO: ${path.basename(backPath)}`);
118
+
119
+ console.log('\n📖 INSTRUCCIONES DE IMPRESIÓN:\n');
120
+ console.log('1. Imprime el archivo FRENTE (todas las páginas)');
121
+ console.log('2. Toma la pila tal como salió');
122
+ console.log('3. Métela de nuevo en la bandeja SIN voltear');
123
+ console.log(' → Cara impresa ARRIBA (visible)');
124
+ console.log(' → Cara blanca ABAJO (donde imprimirá)');
125
+ console.log('4. Imprime el archivo REVERSO');
126
+ console.log('5. Dobla todas las hojas por la mitad');
127
+ console.log('6. Encuaderna por el lado IZQUIERDO\n');
128
+
129
+ return { frontPath, backPath };
130
+
131
+ } catch (error) {
132
+ console.error('❌ Error:', error.message);
133
+ throw error;
134
+ }
135
+ }
136
+
137
+ async function main() {
138
+ const args = process.argv.slice(2);
139
+
140
+ if (args.length === 0) {
141
+ console.log('Uso: node booklet-manual-duplex.js <archivo.pdf>');
142
+ console.log('Ejemplo: node booklet-manual-duplex.js railway-deployment.pdf');
143
+ process.exit(1);
144
+ }
145
+
146
+ const inputPath = path.resolve(args[0]);
147
+
148
+ try {
149
+ await fs.access(inputPath);
150
+ } catch {
151
+ console.error(`❌ Archivo no encontrado: ${inputPath}`);
152
+ process.exit(1);
153
+ }
154
+
155
+ await createManualDuplexBooklet(inputPath);
156
+ console.log('\n¡Listo! 🎉\n');
157
+ }
158
+
159
+ main().catch(console.error);