@icarusmx/creta 1.5.11 → 1.5.13

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 (26) hide show
  1. package/bin/creta.js +37 -1
  2. package/lib/data/command-help/aws-ec2.js +34 -0
  3. package/lib/data/command-help/grep.js +72 -0
  4. package/lib/data/command-help/index.js +9 -1
  5. package/lib/executors/CommandHelpExecutor.js +6 -1
  6. package/lib/executors/ExercisesExecutor.js +8 -0
  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/README.md +180 -0
  19. package/lib/exercises/utils/booklet-2up.js +133 -0
  20. package/lib/exercises/utils/booklet-manual-duplex.js +159 -0
  21. package/lib/exercises/utils/booklet-simple.js +136 -0
  22. package/lib/exercises/utils/create-booklet.js +116 -0
  23. package/lib/scripts/aws-ec2-all.sh +58 -0
  24. package/package.json +3 -2
  25. /package/lib/exercises/{git-stash-workflow.md → 03-git-stash-workflow.md} +0 -0
  26. /package/lib/exercises/{array-object-manipulation.md → 04-array-object-manipulation.md} +0 -0
@@ -0,0 +1,180 @@
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
+ ### 4. [Array & Object Manipulation](./04-array-object-manipulation.md)
38
+ **What:** JavaScript warmups - map, filter, reduce, destructuring
39
+ **Why:** Foundation for real app data transformations
40
+ **Time:** 45 minutes
41
+ **Level:** 🟡 Intermediate
42
+
43
+ ---
44
+
45
+ ## Phase 2: Build
46
+
47
+ **Create real applications with modern frameworks**
48
+
49
+ ### 5. [SvelteKit First Steps](./05-svelte-first-steps.md)
50
+ **What:** Understand SvelteKit project structure and file conventions
51
+ **Why:** Stop guessing where files go - learn the routing system
52
+ **Time:** 30 minutes
53
+ **Level:** 🟡 Intermediate
54
+
55
+ ### 6. [curl + Pipes](./06-curl-and-pipes.md)
56
+ **What:** Process APIs in real-time with curl and bash pipes
57
+ **Why:** Test endpoints, debug requests, automate data workflows
58
+ **Time:** 45 minutes
59
+ **Level:** 🟡 Intermediate
60
+
61
+ ### 7. [Claude API First Steps](./07-claude-api-first-steps.md)
62
+ **What:** Call Claude API directly from terminal with curl
63
+ **Why:** Get AI assistance without leaving your workflow
64
+ **Time:** 30 minutes
65
+ **Level:** 🟡 Intermediate
66
+
67
+ ### 8. [Playwright + Svelte Testing](./08-playwright-svelte-guide.md)
68
+ **What:** E2E testing for SvelteKit apps with Tailwind
69
+ **Why:** Test user flows without breaking when you change CSS
70
+ **Time:** 2-3 hours
71
+ **Level:** 🟡 Intermediate
72
+
73
+ ---
74
+
75
+ ## Phase 3: Ship
76
+
77
+ **Deploy applications to production**
78
+
79
+ ### 9. [Docker First Steps](./09-docker-first-steps.md)
80
+ **What:** Containerize Node.js apps to solve "works on my machine"
81
+ **Why:** Consistent environments from dev to production
82
+ **Time:** 1 hour
83
+ **Level:** 🟡 Intermediate
84
+
85
+ ### 10. [Railway Deployment](./10-railway-deployment.md)
86
+ **What:** Deploy Node.js/Express apps with custom domains
87
+ **Why:** Ship to production in minutes, not hours
88
+ **Time:** 45 minutes
89
+ **Level:** 🟡 Intermediate
90
+
91
+ ### 11. [AWS Billing Detective](./11-aws-billing-detective.md)
92
+ **What:** Hunt down hidden AWS costs across all regions
93
+ **Why:** Stop getting charged for forgotten EBS volumes and Elastic IPs
94
+ **Time:** 30-60 minutes
95
+ **Level:** 🔴 Advanced
96
+
97
+ ---
98
+
99
+ ## Phase 4: Scale
100
+
101
+ **Advanced tooling and workflow optimization**
102
+
103
+ ### 12. [Install Skills Command](./12-install-skills.md)
104
+ **What:** Design a CLI skill installer with project analysis
105
+ **Why:** Automate development workflow setup
106
+ **Time:** Design exercise
107
+ **Level:** 🔴 Advanced
108
+
109
+ ---
110
+
111
+ ## 🎯 How to Use These Guides
112
+
113
+ ### Reading in Neovim/LazyVim
114
+ All guides have vim fold markers for structured navigation:
115
+
116
+ ```vim
117
+ zM " Close all folds (see just headers)
118
+ za " Toggle current fold
119
+ zR " Open all folds
120
+ } " Jump to next paragraph
121
+ { " Jump to previous paragraph
122
+ ```
123
+
124
+ ### Progressive Learning
125
+ - **New to CLI?** Start with Phase 1
126
+ - **Know basics?** Jump to Phase 2 (SvelteKit)
127
+ - **Ready to deploy?** Go straight to Phase 3
128
+ - **Optimizing workflows?** Phase 4 has advanced patterns
129
+
130
+ ### Hands-On Practice
131
+ Every guide includes:
132
+ - 🚨 **Problem** - What confusion does this solve?
133
+ - ✅ **Solution** - Step-by-step walkthrough
134
+ - 💪 **Practice** - Real commands to run
135
+ - 🎯 **Outcomes** - What you'll be able to do
136
+
137
+ ---
138
+
139
+ ## 🧭 By Topic
140
+
141
+ Prefer to browse by subject?
142
+
143
+ ### Terminal & Workflow
144
+ - [Developing Muscle Memory for Neovim](./01-developing-muscle-for-nvim.md)
145
+ - [iTerm2 Pane Navigation](./02-iterm2-pane-navigation.md)
146
+ - [Git Stash Workflow](./03-git-stash-workflow.md)
147
+ - [curl + Pipes](./06-curl-and-pipes.md)
148
+
149
+ ### JavaScript & Development
150
+ - [Array & Object Manipulation](./04-array-object-manipulation.md)
151
+ - [SvelteKit First Steps](./05-svelte-first-steps.md)
152
+
153
+ ### AI & APIs
154
+ - [Claude API First Steps](./07-claude-api-first-steps.md)
155
+
156
+ ### Testing
157
+ - [Playwright + Svelte Testing](./08-playwright-svelte-guide.md)
158
+
159
+ ### Deployment & Infrastructure
160
+ - [Docker First Steps](./09-docker-first-steps.md)
161
+ - [Railway Deployment](./10-railway-deployment.md)
162
+ - [AWS Billing Detective](./11-aws-billing-detective.md)
163
+
164
+ ### Meta/Tooling
165
+ - [Install Skills Command](./12-install-skills.md)
166
+
167
+ ---
168
+
169
+ ## 📚 Philosophy
170
+
171
+ These guides follow Creta's design principles:
172
+
173
+ **Spanish-First** - Native language where it matters (coming soon)
174
+ **Simple Solutions** - No over-engineering, practical examples
175
+ **Evolution Over Replacement** - Skills that grow with you
176
+ **CLI-Native** - Work where developers work
177
+
178
+ ---
179
+
180
+ **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);
@@ -0,0 +1,136 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { PDFDocument, degrees } from 'pdf-lib';
4
+ import fs from 'fs/promises';
5
+ import path from 'path';
6
+
7
+ async function createManualDuplexBooklet(inputPath) {
8
+ try {
9
+ const filename = path.basename(inputPath, '.pdf');
10
+ console.log(`\n📚 Creando booklet para: ${filename}`);
11
+
12
+ // Leer PDF original
13
+ const srcFile = await fs.readFile(inputPath);
14
+ const srcDoc = await PDFDocument.load(srcFile);
15
+ const totalPages = srcDoc.getPageCount();
16
+
17
+ console.log(`📄 Páginas originales: ${totalPages}`);
18
+
19
+ // Calcular páginas necesarias (múltiplo de 4)
20
+ const sheetsNeeded = Math.ceil(totalPages / 4);
21
+ const paddedPages = sheetsNeeded * 4;
22
+ const blankPages = paddedPages - totalPages;
23
+
24
+ if (blankPages > 0) {
25
+ console.log(`📄 Páginas en blanco añadidas: ${blankPages}`);
26
+ }
27
+ console.log(`📄 Total hojas físicas: ${sheetsNeeded}`);
28
+
29
+ // Crear array de páginas incluyendo blancos
30
+ const pages = [];
31
+ for (let i = 0; i < totalPages; i++) {
32
+ pages.push(srcDoc.getPage(i));
33
+ }
34
+ // Añadir páginas en blanco si es necesario
35
+ const firstPage = srcDoc.getPage(0);
36
+ const { width, height } = firstPage.getSize();
37
+ for (let i = totalPages; i < paddedPages; i++) {
38
+ // Crear página en blanco temporal (no la añadimos al doc original)
39
+ pages.push(null); // null representa página en blanco
40
+ }
41
+
42
+ // Crear documentos finales
43
+ const frontDoc = await PDFDocument.create();
44
+ const backDoc = await PDFDocument.create();
45
+
46
+ // Para cada hoja física
47
+ for (let sheet = 0; sheet < sheetsNeeded; sheet++) {
48
+ // Calcular índices de página (0-indexed)
49
+ const frontLeft = paddedPages - (sheet * 2) - 1; // última hacia adentro
50
+ const frontRight = (sheet * 2); // primera hacia afuera
51
+ const backLeft = (sheet * 2) + 1; // segunda
52
+ const backRight = paddedPages - (sheet * 2) - 2; // penúltima hacia adentro
53
+
54
+ console.log(`Hoja ${sheet + 1}: Frente[${frontLeft < totalPages ? frontLeft + 1 : 'blank'}, ${frontRight < totalPages ? frontRight + 1 : 'blank'}] Reverso[${backLeft < totalPages ? backLeft + 1 : 'blank'}, ${backRight < totalPages ? backRight + 1 : 'blank'}]`);
55
+
56
+ // FRENTE: Agregar página derecha (frontRight) y luego izquierda (frontLeft)
57
+ // El orden importa porque así queda al doblar
58
+ if (frontRight < totalPages) {
59
+ const [copied] = await frontDoc.copyPages(srcDoc, [frontRight]);
60
+ frontDoc.addPage(copied);
61
+ } else {
62
+ frontDoc.addPage(frontDoc.addPage([width, height]));
63
+ }
64
+
65
+ if (frontLeft < totalPages) {
66
+ const [copied] = await frontDoc.copyPages(srcDoc, [frontLeft]);
67
+ frontDoc.addPage(copied);
68
+ } else {
69
+ frontDoc.addPage(frontDoc.addPage([width, height]));
70
+ }
71
+
72
+ // REVERSO: Agregar en el orden correcto para duplex sin voltear
73
+ // Izquierda primero, derecha después
74
+ if (backLeft < totalPages) {
75
+ const [copied] = await backDoc.copyPages(srcDoc, [backLeft]);
76
+ backDoc.addPage(copied);
77
+ } else {
78
+ backDoc.addPage(backDoc.addPage([width, height]));
79
+ }
80
+
81
+ if (backRight < totalPages) {
82
+ const [copied] = await backDoc.copyPages(srcDoc, [backRight]);
83
+ backDoc.addPage(copied);
84
+ } else {
85
+ backDoc.addPage(backDoc.addPage([width, height]));
86
+ }
87
+ }
88
+
89
+ // Guardar PDFs
90
+ const frontPath = path.join(path.dirname(inputPath), `${filename}-FRENTE-v2.pdf`);
91
+ const backPath = path.join(path.dirname(inputPath), `${filename}-REVERSO-v2.pdf`);
92
+
93
+ await fs.writeFile(frontPath, await frontDoc.save());
94
+ await fs.writeFile(backPath, await backDoc.save());
95
+
96
+ console.log('\n✅ Booklets creados:\n');
97
+ console.log(` 📄 FRENTE: ${path.basename(frontPath)}`);
98
+ console.log(` 📄 REVERSO: ${path.basename(backPath)}`);
99
+
100
+ console.log('\n📖 INSTRUCCIONES:\n');
101
+ console.log('1. Imprime FRENTE (todas las páginas)');
102
+ console.log('2. Toma la pila y métela de nuevo SIN voltear');
103
+ console.log('3. Imprime REVERSO');
104
+ console.log('4. Dobla por la mitad y encuaderna a la izquierda\n');
105
+
106
+ return { frontPath, backPath };
107
+
108
+ } catch (error) {
109
+ console.error('❌ Error:', error.message);
110
+ console.error(error);
111
+ throw error;
112
+ }
113
+ }
114
+
115
+ async function main() {
116
+ const args = process.argv.slice(2);
117
+
118
+ if (args.length === 0) {
119
+ console.log('Uso: node booklet-simple.js <archivo.pdf>');
120
+ process.exit(1);
121
+ }
122
+
123
+ const inputPath = path.resolve(args[0]);
124
+
125
+ try {
126
+ await fs.access(inputPath);
127
+ } catch {
128
+ console.error(`❌ Archivo no encontrado: ${inputPath}`);
129
+ process.exit(1);
130
+ }
131
+
132
+ await createManualDuplexBooklet(inputPath);
133
+ console.log('\n¡Listo! 🎉\n');
134
+ }
135
+
136
+ main().catch(console.error);