responsive-system 1.4.3 → 1.4.7

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.
@@ -1,827 +1,786 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * Script postinstall para automatizar la configuración inicial completa
5
- * - Instala React, TypeScript, Tailwind automáticamente
6
- * - Inicializa proyecto Vite si está vacío
7
- * - Pregunta qué layout quiere (interactivo)
8
- * - Copia solo los componentes necesarios
9
- * - Copia el hook useResponsive como archivo local
10
- * - Crea página de ejemplo en pages/
11
- * - Configura App.tsx con el layout seleccionado
12
- */
13
-
14
- import fs from 'fs'
15
- import path from 'path'
16
- import { execSync } from 'child_process'
17
- import { fileURLToPath } from 'url'
18
- import readline from 'readline'
19
-
20
- const __filename = fileURLToPath(import.meta.url)
21
- const __dirname = path.dirname(__filename)
22
-
23
- const projectRoot = process.cwd()
24
- const packageJsonPath = path.join(projectRoot, 'package.json')
25
-
26
- // Detectar si se ejecuta como postinstall o manualmente
27
- const isPostinstall = process.env.npm_lifecycle_event === 'postinstall'
28
- const isManual = process.argv[1].includes('postinstall.js') && !isPostinstall
29
-
30
- console.log('')
31
- console.log('📦 responsive-system: Iniciando configuración...')
32
- console.log(` Directorio: ${projectRoot}`)
33
- console.log('')
34
- console.log('💡 Si este es tu primer uso, este script configurará todo automáticamente.')
35
- console.log(' Si ya tienes el proyecto configurado, puedes cancelar (Ctrl+C)')
36
- console.log('')
37
-
38
- // Verificar si package.json existe
39
- if (!fs.existsSync(packageJsonPath)) {
40
- console.log('⚠️ responsive-system: No se encontró package.json, saltando postinstall')
41
- process.exit(0)
42
- }
43
-
44
- let packageJson
45
- try {
46
- packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'))
47
- } catch (error) {
48
- console.error('❌ responsive-system: Error al leer package.json:', error.message)
49
- process.exit(1)
50
- }
51
-
52
- // Verificar si el proyecto está vacío (solo tiene responsive-system)
53
- const isProjectEmpty = !packageJson.dependencies ||
54
- Object.keys(packageJson.dependencies).length === 0 ||
55
- (Object.keys(packageJson.dependencies).length === 1 && packageJson.dependencies['responsive-system'])
56
-
57
- // Verificar qué está instalado - SOLO en package.json (no en node_modules para evitar conflictos)
58
- const hasReactInPackageJson = (packageJson.dependencies && packageJson.dependencies.react) ||
59
- (packageJson.devDependencies && packageJson.devDependencies.react)
60
- const hasVite = packageJson.devDependencies && packageJson.devDependencies.vite
61
- const tailwindInDevDeps = packageJson.devDependencies && packageJson.devDependencies.tailwindcss
62
- const typescriptInDevDeps = packageJson.devDependencies && packageJson.devDependencies.typescript
63
-
64
- let needsUpdate = false
65
-
66
- // Función para preguntar al usuario qué layout quiere
67
- async function askLayout() {
68
- if (isPostinstall && !isManual) {
69
- // Si es postinstall automático, usar 'default' por defecto
70
- console.log(' ℹ️ Usando layout "default" por defecto')
71
- console.log(' 💡 Ejecuta "npx responsive-system-setup" para cambiar el layout')
72
- return 'default'
73
- }
74
-
75
- // Si es ejecución manual, preguntar interactivamente
76
- const rl = readline.createInterface({
77
- input: process.stdin,
78
- output: process.stdout
79
- })
80
-
81
- return new Promise((resolve) => {
82
- console.log('')
83
- console.log('🎨 Selecciona el layout que quieres usar:')
84
- console.log(' 1. default - Navigation + Footer')
85
- console.log(' 2. sidebar - Sidebar lateral')
86
- console.log(' 3. dashboard - Sidebar + Footer')
87
- console.log(' 4. minimal - Sin componentes (solo contenido)')
88
- console.log('')
89
-
90
- rl.question(' Ingresa el número (1-4) o el nombre del layout: ', (answer) => {
91
- rl.close()
92
-
93
- const normalized = answer.trim().toLowerCase()
94
-
95
- if (normalized === '1' || normalized === 'default') {
96
- resolve('default')
97
- } else if (normalized === '2' || normalized === 'sidebar') {
98
- resolve('sidebar')
99
- } else if (normalized === '3' || normalized === 'dashboard') {
100
- resolve('dashboard')
101
- } else if (normalized === '4' || normalized === 'minimal') {
102
- resolve('minimal')
103
- } else {
104
- console.log(' ⚠️ Opción inválida, usando "default"')
105
- resolve('default')
106
- }
107
- })
108
- })
109
- }
110
-
111
- // Función para copiar archivo desde el paquete al proyecto
112
- function copyFileFromPackage(relativePath, targetPath, isComponent = false) {
113
- const sourcePath = path.join(__dirname, '..', relativePath)
114
- const destPath = path.join(projectRoot, targetPath)
115
-
116
- if (!fs.existsSync(sourcePath)) {
117
- console.error(` ❌ No se encontró: ${relativePath}`)
118
- return false
119
- }
120
-
121
- // Crear directorio destino si no existe
122
- const destDir = path.dirname(destPath)
123
- if (!fs.existsSync(destDir)) {
124
- fs.mkdirSync(destDir, { recursive: true })
125
- }
126
-
127
- let content = fs.readFileSync(sourcePath, 'utf8')
128
-
129
- // Si es un componente, reemplazar importaciones relativas por importaciones del paquete
130
- if (isComponent) {
131
- // Reemplazar importaciones de hooks
132
- content = content.replace(
133
- /from ['"]\.\.\/\.\.\/hooks['"]/g,
134
- "from 'responsive-system'"
135
- )
136
- // Reemplazar importaciones de context
137
- content = content.replace(
138
- /from ['"]\.\.\/\.\.\/context['"]/g,
139
- "from 'responsive-system'"
140
- )
141
- // Reemplazar importaciones de componentes/layout
142
- content = content.replace(
143
- /from ['"]\.\.\/components\/layout['"]/g,
144
- "from 'responsive-system'"
145
- )
146
- }
147
-
148
- fs.writeFileSync(destPath, content)
149
- return true
150
- }
151
-
152
- // Función para generar componentes genéricos según el layout seleccionado
153
- function generateLayoutComponents(selectedLayout) {
154
- const componentsDir = path.join(projectRoot, 'src', 'components', 'layout')
155
-
156
- // Crear directorio si no existe
157
- if (!fs.existsSync(componentsDir)) {
158
- fs.mkdirSync(componentsDir, { recursive: true })
159
- }
160
-
161
- const componentsToGenerate = {
162
- default: ['Navigation', 'Footer'],
163
- sidebar: ['Sidebar'],
164
- dashboard: ['Sidebar', 'Footer'],
165
- minimal: []
166
- }
167
-
168
- const components = componentsToGenerate[selectedLayout] || []
169
-
170
- if (components.length === 0) {
171
- console.log(' Layout minimal: No se generan componentes')
172
- return
173
- }
174
-
175
- console.log(` 📦 Generando componentes genéricos para layout "${selectedLayout}":`)
176
-
177
- // Generar Navigation genérico
178
- if (components.includes('Navigation')) {
179
- const navigationContent = `import { useResponsiveLayout } from 'responsive-system'
180
-
181
- const Navigation = () => {
182
- const { isMobile, breakpoint } = useResponsiveLayout()
183
-
184
- return (
185
- <nav className="sticky top-0 z-50 bg-gray-900 border-b border-gray-800">
186
- <div className="px-4 py-4">
187
- <div className="flex items-center justify-between">
188
- <div className="flex items-center space-x-3">
189
- <div className="w-8 h-8 bg-blue-500 rounded-lg flex items-center justify-center">
190
- <span className="text-white font-bold text-sm">LO</span>
191
- </div>
192
- <h1 className="text-white font-semibold text-lg">Tu Aplicación</h1>
193
- </div>
194
-
195
- {isMobile && (
196
- <button className="p-2 text-gray-400 hover:text-white">
197
- <svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
198
- <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" />
199
- </svg>
200
- </button>
201
- )}
202
- </div>
203
- </div>
204
- </nav>
205
- )
206
- }
207
-
208
- export default Navigation
209
- `
210
- fs.writeFileSync(path.join(componentsDir, 'Navigation.tsx'), navigationContent)
211
- console.log(' ✅ Navigation.tsx (genérico)')
212
- }
213
-
214
- // Generar Footer genérico
215
- if (components.includes('Footer')) {
216
- const footerContent = `import { useResponsiveLayout } from 'responsive-system'
217
-
218
- const Footer = () => {
219
- const { breakpoint } = useResponsiveLayout()
220
-
221
- return (
222
- <footer className="bg-gray-900 border-t border-gray-800">
223
- <div className="px-4 py-6">
224
- <div className="max-w-7xl mx-auto">
225
- <div className="grid grid-cols-1 md:grid-cols-3 gap-6">
226
- <div>
227
- <h3 className="text-white font-semibold mb-2">Tu Aplicación</h3>
228
- <p className="text-gray-400 text-sm">
229
- Descripción de tu aplicación aquí.
230
- </p>
231
- </div>
232
-
233
- <div>
234
- <h3 className="text-white font-semibold mb-2">Enlaces</h3>
235
- <ul className="space-y-1 text-gray-400 text-sm">
236
- <li><a href="#" className="hover:text-white">Inicio</a></li>
237
- <li><a href="#" className="hover:text-white">Acerca</a></li>
238
- <li><a href="#" className="hover:text-white">Contacto</a></li>
239
- </ul>
240
- </div>
241
-
242
- <div>
243
- <h3 className="text-white font-semibold mb-2">Info</h3>
244
- <p className="text-gray-400 text-xs">
245
- Breakpoint: <span className="text-blue-400">{breakpoint.toUpperCase()}</span>
246
- </p>
247
- </div>
248
- </div>
249
-
250
- <div className="border-t border-gray-800 mt-6 pt-4 text-center">
251
- <p className="text-gray-500 text-xs">
252
- © {new Date().getFullYear()} Tu Aplicación. Todos los derechos reservados.
253
- </p>
254
- </div>
255
- </div>
256
- </div>
257
- </footer>
258
- )
259
- }
260
-
261
- export default Footer
262
- `
263
- fs.writeFileSync(path.join(componentsDir, 'Footer.tsx'), footerContent)
264
- console.log(' ✅ Footer.tsx (genérico)')
265
- }
266
-
267
- // Generar Sidebar genérico
268
- if (components.includes('Sidebar')) {
269
- const sidebarContent = `import { useResponsiveLayout } from 'responsive-system'
270
- import { useSidebar } from 'responsive-system'
271
-
272
- const Sidebar = () => {
273
- const { isMobile, isTablet } = useResponsiveLayout()
274
- const { sidebarOpen, setSidebarOpen } = useSidebar()
275
-
276
- const menuItems = [
277
- { id: 'home', label: 'Inicio' },
278
- { id: 'about', label: 'Acerca' },
279
- { id: 'contact', label: 'Contacto' },
280
- ]
281
-
282
- return (
283
- <>
284
- {/* Hamburger button para móvil */}
285
- {isMobile && (
286
- <button
287
- onClick={() => setSidebarOpen(true)}
288
- className="fixed top-4 left-4 z-50 p-2 rounded-lg text-gray-300 hover:text-white hover:bg-gray-800 bg-gray-900 border border-gray-700"
289
- >
290
- <svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
291
- <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" />
292
- </svg>
293
- </button>
294
- )}
295
-
296
- {/* Sidebar desktop */}
297
- <aside className={\`bg-gray-900 border-r border-gray-800 \${isMobile ? 'hidden' : 'w-64 flex-shrink-0'} \${isTablet ? 'w-56' : 'w-64'}\`}>
298
- <div className="p-6 flex flex-col h-full">
299
- <div className="flex items-center space-x-3 mb-8">
300
- <div className="w-8 h-8 bg-blue-500 rounded-lg flex items-center justify-center">
301
- <span className="text-white font-bold text-sm">LO</span>
302
- </div>
303
- <span className="text-white font-bold text-lg">Tu Aplicación</span>
304
- </div>
305
-
306
- <nav className="space-y-2">
307
- {menuItems.map((item) => (
308
- <button
309
- key={item.id}
310
- className="w-full flex items-center px-4 py-3 rounded-lg transition-all text-left text-gray-300 hover:text-white hover:bg-gray-800"
311
- >
312
- <span className="font-medium">{item.label}</span>
313
- </button>
314
- ))}
315
- </nav>
316
- </div>
317
- </aside>
318
-
319
- {/* Sidebar móvil desplegable */}
320
- {isMobile && sidebarOpen && (
321
- <div className="fixed inset-0 z-40 bg-black/50" onClick={() => setSidebarOpen(false)}>
322
- <div className="fixed top-0 left-0 w-64 h-full bg-gray-900 border-r border-gray-800">
323
- <div className="p-6 flex flex-col h-full pt-20">
324
- <div className="flex items-center space-x-3 mb-8">
325
- <div className="w-8 h-8 bg-blue-500 rounded-lg flex items-center justify-center">
326
- <span className="text-white font-bold text-sm">LO</span>
327
- </div>
328
- <span className="text-white font-bold text-lg">Tu Aplicación</span>
329
- </div>
330
-
331
- <nav className="space-y-2">
332
- {menuItems.map((item) => (
333
- <button
334
- key={item.id}
335
- onClick={() => setSidebarOpen(false)}
336
- className="w-full flex items-center px-4 py-3 rounded-lg transition-all text-left text-gray-300 hover:text-white hover:bg-gray-800"
337
- >
338
- <span className="font-medium">{item.label}</span>
339
- </button>
340
- ))}
341
- </nav>
342
- </div>
343
- </div>
344
- </div>
345
- )}
346
- </>
347
- )
348
- }
349
-
350
- export default Sidebar
351
- `
352
- fs.writeFileSync(path.join(componentsDir, 'Sidebar.tsx'), sidebarContent)
353
- console.log(' ✅ Sidebar.tsx (genérico)')
354
- }
355
-
356
- // Crear index.ts para exportar los componentes
357
- const indexContent = components.map(c => `export { default as ${c} } from './${c}'`).join('\n')
358
- fs.writeFileSync(path.join(componentsDir, 'index.ts'), indexContent)
359
- console.log(' ✅ index.ts')
360
- }
361
-
362
- // Función para copiar el hook useResponsive y sus dependencias
363
- function copyUseResponsiveHook() {
364
- console.log(' 📦 Copiando hook useResponsive y dependencias...')
365
-
366
- // Crear directorio hooks
367
- const hooksDir = path.join(projectRoot, 'src', 'hooks')
368
- if (!fs.existsSync(hooksDir)) {
369
- fs.mkdirSync(hooksDir, { recursive: true })
370
- }
371
-
372
- // Copiar tipos
373
- const typesDir = path.join(projectRoot, 'src', 'types')
374
- if (!fs.existsSync(typesDir)) {
375
- fs.mkdirSync(typesDir, { recursive: true })
376
- }
377
- copyFileFromPackage('src/types/responsive.ts', 'src/types/responsive.ts')
378
- console.log(' types/responsive.ts')
379
-
380
- // Copiar constantes
381
- const constantsDir = path.join(projectRoot, 'src', 'constants')
382
- if (!fs.existsSync(constantsDir)) {
383
- fs.mkdirSync(constantsDir, { recursive: true })
384
- }
385
- copyFileFromPackage('src/constants/breakpoints.ts', 'src/constants/breakpoints.ts')
386
- console.log(' ✅ constants/breakpoints.ts')
387
-
388
- // Copiar hook useResponsive
389
- copyFileFromPackage('src/hooks/useResponsive.ts', 'src/hooks/useResponsive.ts')
390
- console.log(' ✅ hooks/useResponsive.ts')
391
-
392
- // Crear index.ts para exportar el hook
393
- const indexContent = `export { useResponsive } from './useResponsive'
394
- export type { ResponsiveState, Breakpoint, Orientation } from '../types/responsive'
395
- export { DEFAULT_BREAKPOINTS, getCurrentBreakpoint, getBreakpointIndex, getBreakpointValue } from '../constants/breakpoints'
396
- `
397
- fs.writeFileSync(path.join(hooksDir, 'index.ts'), indexContent)
398
- console.log(' hooks/index.ts')
399
- }
400
-
401
- // ESTRATEGIA: Modificar directamente el package.json primero, luego instalar
402
- console.log('📦 responsive-system: Verificando dependencias...')
403
-
404
- // Agregar React a dependencies SOLO si NO está en package.json
405
- if (!hasReactInPackageJson) {
406
- console.log(' ➕ Agregando React a dependencies...')
407
- if (!packageJson.dependencies) {
408
- packageJson.dependencies = {}
409
- }
410
- packageJson.dependencies['react'] = '^19.1.1'
411
- packageJson.dependencies['react-dom'] = '^19.1.1'
412
- needsUpdate = true
413
- } else {
414
- console.log(' ✅ React ya está en package.json, no se modificará')
415
- }
416
-
417
- // Agregar Vite si el proyecto está vacío
418
- if (isProjectEmpty && !hasVite) {
419
- console.log(' ➕ Agregando Vite a devDependencies...')
420
- if (!packageJson.devDependencies) {
421
- packageJson.devDependencies = {}
422
- }
423
- packageJson.devDependencies['vite'] = '^7.1.7'
424
- packageJson.devDependencies['@vitejs/plugin-react'] = '^5.0.4'
425
- needsUpdate = true
426
- }
427
-
428
- // Agregar Tailwind y sus dependencias a devDependencies
429
- if (!tailwindInDevDeps) {
430
- console.log(' ➕ Agregando Tailwind y PostCSS a devDependencies...')
431
- if (!packageJson.devDependencies) {
432
- packageJson.devDependencies = {}
433
- }
434
- packageJson.devDependencies['tailwindcss'] = '^4.1.14'
435
- packageJson.devDependencies['@tailwindcss/postcss'] = '^4.1.14'
436
- packageJson.devDependencies['postcss'] = '^8.5.6'
437
- packageJson.devDependencies['autoprefixer'] = '^10.4.21'
438
- needsUpdate = true
439
- }
440
-
441
- // Agregar TypeScript y sus tipos a devDependencies
442
- if (!typescriptInDevDeps) {
443
- console.log(' ➕ Agregando TypeScript a devDependencies...')
444
- if (!packageJson.devDependencies) {
445
- packageJson.devDependencies = {}
446
- }
447
- packageJson.devDependencies['typescript'] = '~5.9.3'
448
- packageJson.devDependencies['@types/react'] = '^19.1.16'
449
- packageJson.devDependencies['@types/react-dom'] = '^19.1.9'
450
- needsUpdate = true
451
- }
452
-
453
- // Agregar "type": "module" si no existe (para evitar warnings)
454
- if (!packageJson.type) {
455
- packageJson.type = 'module'
456
- needsUpdate = true
457
- }
458
-
459
- // Escribir package.json modificado
460
- if (needsUpdate) {
461
- console.log('')
462
- console.log('📝 responsive-system: Actualizando package.json...')
463
- fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2))
464
- console.log('✅ responsive-system: package.json actualizado')
465
- console.log('')
466
-
467
- // Ahora instalar las dependencias
468
- console.log('📦 responsive-system: Instalando dependencias...')
469
- try {
470
- execSync('npm install', {
471
- stdio: 'inherit',
472
- cwd: projectRoot
473
- })
474
- console.log('✅ responsive-system: Dependencias instaladas correctamente')
475
- } catch (error) {
476
- console.error('❌ responsive-system: Error al instalar dependencias:', error.message)
477
- console.log('⚠️ responsive-system: Las dependencias están en package.json, ejecuta "npm install" manualmente')
478
- }
479
- } else {
480
- console.log('✅ responsive-system: Todas las dependencias ya están en package.json')
481
- }
482
-
483
- // Si el proyecto está vacío, crear estructura base
484
- if (isProjectEmpty) {
485
- console.log('')
486
- console.log('📦 responsive-system: Proyecto vacío detectado, creando estructura base...')
487
- console.log('')
488
-
489
- // Preguntar qué layout quiere
490
- const selectedLayout = await askLayout()
491
- console.log(` ✅ Layout seleccionado: "${selectedLayout}"`)
492
- console.log('')
493
-
494
- // Crear estructura de directorios
495
- const dirs = ['src', 'src/components', 'src/components/layout', 'src/pages', 'src/hooks', 'src/types', 'src/constants', 'public']
496
- dirs.forEach(dir => {
497
- const dirPath = path.join(projectRoot, dir)
498
- if (!fs.existsSync(dirPath)) {
499
- fs.mkdirSync(dirPath, { recursive: true })
500
- }
501
- })
502
-
503
- // Generar componentes genéricos según layout seleccionado
504
- generateLayoutComponents(selectedLayout)
505
- console.log('')
506
-
507
- // Copiar hook useResponsive
508
- copyUseResponsiveHook()
509
- console.log('')
510
-
511
- // Crear vite.config.ts
512
- const viteConfigPath = path.join(projectRoot, 'vite.config.ts')
513
- if (!fs.existsSync(viteConfigPath)) {
514
- const viteConfig = `import { defineConfig } from 'vite'
515
- import react from '@vitejs/plugin-react'
516
-
517
- export default defineConfig({
518
- plugins: [react()],
519
- })
520
- `
521
- fs.writeFileSync(viteConfigPath, viteConfig)
522
- console.log(' ✅ Creado: vite.config.ts')
523
- }
524
-
525
- // Crear tailwind.config.js
526
- const tailwindConfigPath = path.join(projectRoot, 'tailwind.config.js')
527
- if (!fs.existsSync(tailwindConfigPath)) {
528
- const tailwindConfig = `import responsiveScalePlugin from 'responsive-system/plugin'
529
-
530
- export default {
531
- content: [
532
- "./index.html",
533
- "./src/**/*.{js,ts,jsx,tsx}",
534
- ],
535
- theme: {
536
- extend: {
537
- screens: {
538
- 'xs': '475px', 'sm': '640px', 'md': '768px', 'lg': '1024px',
539
- 'xl': '1280px', '2xl': '1536px', '3xl': '1920px', '4xl': '2560px', '5xl': '3840px'
540
- }
541
- },
542
- },
543
- plugins: [
544
- responsiveScalePlugin({
545
- scaleProperties: {
546
- typography: true,
547
- spacing: true,
548
- lineHeight: true,
549
- letterSpacing: true,
550
- shadows: true,
551
- borderWidth: false,
552
- sizing: false,
553
- borderRadius: false
554
- },
555
- scales: {
556
- xs: 1.0, sm: 1.0, md: 1.0, lg: 1.0, xl: 1.0,
557
- '2xl': 1.05, '3xl': 1.15, '4xl': 1.25, '5xl': 1.35
558
- }
559
- })
560
- ],
561
- }
562
- `
563
- fs.writeFileSync(tailwindConfigPath, tailwindConfig)
564
- console.log(' ✅ Creado: tailwind.config.js')
565
- }
566
-
567
- // Crear postcss.config.js
568
- const postcssConfigPath = path.join(projectRoot, 'postcss.config.js')
569
- if (!fs.existsSync(postcssConfigPath)) {
570
- const postcssConfig = `export default {
571
- plugins: {
572
- '@tailwindcss/postcss': {},
573
- autoprefixer: {},
574
- },
575
- }
576
- `
577
- fs.writeFileSync(postcssConfigPath, postcssConfig)
578
- console.log(' ✅ Creado: postcss.config.js')
579
- }
580
-
581
- // Crear tsconfig.json
582
- const tsconfigPath = path.join(projectRoot, 'tsconfig.json')
583
- if (!fs.existsSync(tsconfigPath)) {
584
- const tsconfig = `{
585
- "compilerOptions": {
586
- "target": "ES2020",
587
- "useDefineForClassFields": true,
588
- "lib": ["ES2020", "DOM", "DOM.Iterable"],
589
- "module": "ESNext",
590
- "skipLibCheck": true,
591
- "moduleResolution": "bundler",
592
- "allowImportingTsExtensions": true,
593
- "resolveJsonModule": true,
594
- "isolatedModules": true,
595
- "noEmit": true,
596
- "jsx": "react-jsx",
597
- "strict": true,
598
- "noUnusedLocals": true,
599
- "noUnusedParameters": true,
600
- "noFallthroughCasesInSwitch": true
601
- },
602
- "include": ["src"],
603
- "references": [{ "path": "./tsconfig.node.json" }]
604
- }
605
- `
606
- fs.writeFileSync(tsconfigPath, tsconfig)
607
- console.log(' ✅ Creado: tsconfig.json')
608
- }
609
-
610
- // Crear tsconfig.node.json
611
- const tsconfigNodePath = path.join(projectRoot, 'tsconfig.node.json')
612
- if (!fs.existsSync(tsconfigNodePath)) {
613
- const tsconfigNode = `{
614
- "compilerOptions": {
615
- "composite": true,
616
- "skipLibCheck": true,
617
- "module": "ESNext",
618
- "moduleResolution": "bundler",
619
- "allowSyntheticDefaultImports": true
620
- },
621
- "include": ["vite.config.ts"]
622
- }
623
- `
624
- fs.writeFileSync(tsconfigNodePath, tsconfigNode)
625
- console.log(' ✅ Creado: tsconfig.node.json')
626
- }
627
-
628
- // Crear index.html
629
- const indexHtmlPath = path.join(projectRoot, 'index.html')
630
- if (!fs.existsSync(indexHtmlPath)) {
631
- const indexHtml = `<!doctype html>
632
- <html lang="es">
633
- <head>
634
- <meta charset="UTF-8" />
635
- <link rel="icon" type="image/svg+xml" href="/vite.svg" />
636
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
637
- <title>Responsive System Demo</title>
638
- </head>
639
- <body>
640
- <div id="root"></div>
641
- <script type="module" src="/src/main.tsx"></script>
642
- </body>
643
- </html>
644
- `
645
- fs.writeFileSync(indexHtmlPath, indexHtml)
646
- console.log(' Creado: index.html')
647
- }
648
-
649
- // Crear src/main.tsx
650
- const mainTsxPath = path.join(projectRoot, 'src', 'main.tsx')
651
- if (!fs.existsSync(mainTsxPath)) {
652
- const mainTsx = `import React from 'react'
653
- import ReactDOM from 'react-dom/client'
654
- import { ResponsiveLayoutProvider, MainLayout } from 'responsive-system'
655
- import App from './App'
656
- import './index.css'
657
-
658
- ReactDOM.createRoot(document.getElementById('root')!).render(
659
- <React.StrictMode>
660
- <ResponsiveLayoutProvider defaultLayout="${selectedLayout}">
661
- <MainLayout>
662
- <App />
663
- </MainLayout>
664
- </ResponsiveLayoutProvider>
665
- </React.StrictMode>,
666
- )
667
- `
668
- fs.writeFileSync(mainTsxPath, mainTsx)
669
- console.log(' ✅ Creado: src/main.tsx')
670
- }
671
-
672
- // Crear src/index.css
673
- const indexCssPath = path.join(projectRoot, 'src', 'index.css')
674
- if (!fs.existsSync(indexCssPath)) {
675
- const indexCss = `@import "tailwindcss";
676
- `
677
- fs.writeFileSync(indexCssPath, indexCss)
678
- console.log(' ✅ Creado: src/index.css')
679
- }
680
-
681
- // Crear src/pages/HomePage.tsx con página de ejemplo simple
682
- const homePagePath = path.join(projectRoot, 'src', 'pages', 'HomePage.tsx')
683
- if (!fs.existsSync(homePagePath)) {
684
- const homePage = `import { useResponsiveLayout } from 'responsive-system'
685
- import { useResponsive } from '../hooks'
686
-
687
- function HomePage() {
688
- const { breakpoint, isMobile, layout } = useResponsiveLayout()
689
- const responsive = useResponsive()
690
-
691
- return (
692
- <div className="min-h-screen bg-gray-50 py-8 px-4">
693
- <div className="max-w-7xl mx-auto space-y-8">
694
- {/* Hero Section */}
695
- <div className="bg-white rounded-lg shadow-md p-8 text-center">
696
- <h1 className="text-4xl font-bold text-gray-900 mb-4">
697
- Bienvenido a tu Aplicación
698
- </h1>
699
- <p className="text-lg text-gray-600 max-w-2xl mx-auto">
700
- Esta es una página de ejemplo que demuestra el sistema responsive con auto-scaling.
701
- Todo el contenido se ajusta automáticamente según el tamaño de pantalla.
702
- </p>
703
- </div>
704
-
705
- {/* Info Cards */}
706
- <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
707
- <div className="bg-blue-50 rounded-lg p-6 border border-blue-200">
708
- <h3 className="text-sm font-semibold text-blue-900 mb-2">Breakpoint</h3>
709
- <p className="text-2xl font-bold text-blue-700">{breakpoint.toUpperCase()}</p>
710
- </div>
711
- <div className="bg-green-50 rounded-lg p-6 border border-green-200">
712
- <h3 className="text-sm font-semibold text-green-900 mb-2">Dispositivo</h3>
713
- <p className="text-2xl font-bold text-green-700">{isMobile ? 'Móvil' : 'Desktop'}</p>
714
- </div>
715
- <div className="bg-purple-50 rounded-lg p-6 border border-purple-200">
716
- <h3 className="text-sm font-semibold text-purple-900 mb-2">Ancho</h3>
717
- <p className="text-2xl font-bold text-purple-700">{responsive.width}px</p>
718
- </div>
719
- <div className="bg-orange-50 rounded-lg p-6 border border-orange-200">
720
- <h3 className="text-sm font-semibold text-orange-900 mb-2">Alto</h3>
721
- <p className="text-2xl font-bold text-orange-700">{responsive.height}px</p>
722
- </div>
723
- </div>
724
-
725
- {/* Content Cards */}
726
- <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
727
- {[1, 2, 3, 4, 5, 6].map((i) => (
728
- <div key={i} className="bg-white rounded-lg shadow-md p-6 hover:shadow-lg transition-shadow">
729
- <div className="w-12 h-12 bg-blue-500 rounded-lg flex items-center justify-center mb-4">
730
- <span className="text-white font-bold text-xl">{i}</span>
731
- </div>
732
- <h3 className="text-xl font-bold text-gray-900 mb-2">Card {i}</h3>
733
- <p className="text-gray-600">
734
- Este es un ejemplo de card. El texto, espaciado y sombras se ajustan automáticamente
735
- según el tamaño de pantalla gracias al sistema de auto-scaling.
736
- </p>
737
- </div>
738
- ))}
739
- </div>
740
-
741
- {/* Info Section */}
742
- <div className="bg-white rounded-lg shadow-md p-8">
743
- <h2 className="text-2xl font-bold text-gray-900 mb-4">Sistema Responsive</h2>
744
- <div className="space-y-3 text-gray-700">
745
- <p>
746
- <strong className="text-gray-900">Auto-scaling activo:</strong> Todo el contenido escala
747
- automáticamente según el breakpoint actual (texto, espaciado, sombras).
748
- </p>
749
- <p>
750
- <strong className="text-gray-900">Hook useResponsive:</strong> Disponible en{' '}
751
- <code className="bg-gray-100 px-2 py-1 rounded text-sm">src/hooks/useResponsive.ts</code>{' '}
752
- para configuración manual cuando lo necesites.
753
- </p>
754
- <p>
755
- <strong className="text-gray-900">Layout actual:</strong> <span className="capitalize">{layout.current}</span>
756
- </p>
757
- </div>
758
- </div>
759
- </div>
760
- </div>
761
- )
762
- }
763
-
764
- export default HomePage
765
- `
766
- fs.writeFileSync(homePagePath, homePage)
767
- console.log(' Creado: src/pages/HomePage.tsx (página de ejemplo simple)')
768
- }
769
-
770
- // Crear src/App.tsx que importa la página
771
- const appTsxPath = path.join(projectRoot, 'src', 'App.tsx')
772
- if (!fs.existsSync(appTsxPath)) {
773
- const appTsx = `import HomePage from './pages/HomePage'
774
-
775
- function App() {
776
- return <HomePage />
777
- }
778
-
779
- export default App
780
- `
781
- fs.writeFileSync(appTsxPath, appTsx)
782
- console.log(' ✅ Creado: src/App.tsx')
783
- }
784
-
785
- // Actualizar package.json con scripts
786
- if (!packageJson.scripts) {
787
- packageJson.scripts = {}
788
- }
789
- if (!packageJson.scripts.dev) {
790
- packageJson.scripts.dev = 'vite'
791
- }
792
- if (!packageJson.scripts.build) {
793
- packageJson.scripts.build = 'tsc && vite build'
794
- }
795
- if (!packageJson.scripts.preview) {
796
- packageJson.scripts.preview = 'vite preview'
797
- }
798
-
799
- fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2))
800
- console.log(' ✅ Actualizado: package.json con scripts')
801
-
802
- console.log('')
803
- console.log('🎉 responsive-system: Proyecto inicializado correctamente!')
804
- console.log('')
805
- console.log('Para empezar:')
806
- console.log(' 1. npm run dev')
807
- console.log(' 2. Abre http://localhost:5173')
808
- console.log('')
809
- console.log(`Layout seleccionado: "${selectedLayout}"`)
810
- console.log(' - Componentes generados en src/components/layout/')
811
- console.log(' - Hook useResponsive disponible en src/hooks/useResponsive.ts')
812
- console.log(' - Página de ejemplo en src/pages/HomePage.tsx')
813
- console.log('')
814
- console.log('💡 Para cambiar el layout después, ejecuta: npx responsive-system-setup')
815
- console.log('')
816
- } else {
817
- console.log('✅ responsive-system: Proyecto ya inicializado')
818
- }
819
-
820
- console.log('')
821
- console.log('✅ responsive-system: postinstall completado')
822
- console.log('')
823
- console.log('📝 NOTA: Si instalaste el paquete pero este script no se ejecutó automáticamente,')
824
- console.log(' ejecuta uno de estos comandos:')
825
- console.log(' - npx responsive-system-setup')
826
- console.log(' - node node_modules/responsive-system/scripts/postinstall.js')
827
- console.log('')
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Script postinstall para automatizar la configuración inicial completa
5
+ * - Instala React, TypeScript, Tailwind automáticamente
6
+ * - Inicializa proyecto Vite si está vacío
7
+ * - Pregunta qué layout quiere (interactivo)
8
+ * - Copia solo los componentes necesarios
9
+ * - Copia el hook useResponsive como archivo local
10
+ * - Crea página de ejemplo en pages/
11
+ * - Configura App.tsx con el layout seleccionado
12
+ */
13
+
14
+ import fs from 'fs'
15
+ import path from 'path'
16
+ import { execSync } from 'child_process'
17
+ import { fileURLToPath } from 'url'
18
+ import readline from 'readline'
19
+
20
+ const __filename = fileURLToPath(import.meta.url)
21
+ const __dirname = path.dirname(__filename)
22
+
23
+ const projectRoot = process.cwd()
24
+ const packageJsonPath = path.join(projectRoot, 'package.json')
25
+
26
+ // Detectar si se ejecuta como postinstall o manualmente
27
+ const isPostinstall = process.env.npm_lifecycle_event === 'postinstall'
28
+ const isManual = process.argv[1].includes('postinstall.js') && !isPostinstall
29
+
30
+ console.log('')
31
+ console.log('📦 responsive-system: Iniciando configuración...')
32
+ console.log(` Directorio: ${projectRoot}`)
33
+ console.log('')
34
+
35
+ // Verificar si package.json existe
36
+ if (!fs.existsSync(packageJsonPath)) {
37
+ console.log('⚠️ No se encontró package.json, saltando configuración')
38
+ process.exit(0)
39
+ }
40
+
41
+ let packageJson
42
+ try {
43
+ packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'))
44
+ } catch (error) {
45
+ console.error('❌ Error al leer package.json:', error.message)
46
+ process.exit(1)
47
+ }
48
+
49
+ // Verificar si el proyecto está vacío (solo tiene responsive-system)
50
+ const isProjectEmpty = !packageJson.dependencies ||
51
+ Object.keys(packageJson.dependencies).length === 0 ||
52
+ (Object.keys(packageJson.dependencies).length === 1 && packageJson.dependencies['responsive-system'])
53
+
54
+ // Verificar qué está instalado - SOLO en package.json (no en node_modules para evitar conflictos)
55
+ const hasReactInPackageJson = (packageJson.dependencies && packageJson.dependencies.react) ||
56
+ (packageJson.devDependencies && packageJson.devDependencies.react)
57
+ const hasVite = packageJson.devDependencies && packageJson.devDependencies.vite
58
+ const tailwindInDevDeps = packageJson.devDependencies && packageJson.devDependencies.tailwindcss
59
+ const typescriptInDevDeps = packageJson.devDependencies && packageJson.devDependencies.typescript
60
+
61
+ let needsUpdate = false
62
+
63
+ // Función para preguntar al usuario qué layout quiere
64
+ async function askLayout() {
65
+ if (isPostinstall && !isManual) {
66
+ console.log(' ℹ️ Usando layout "default" por defecto')
67
+ console.log(' 💡 Ejecuta "npx responsive-system-setup" para cambiar el layout')
68
+ return 'default'
69
+ }
70
+
71
+ // Si es ejecución manual, preguntar interactivamente
72
+ const rl = readline.createInterface({
73
+ input: process.stdin,
74
+ output: process.stdout
75
+ })
76
+
77
+ return new Promise((resolve) => {
78
+ console.log('')
79
+ console.log('🎨 Selecciona el layout que quieres usar:')
80
+ console.log(' 1. default - Navigation + Footer')
81
+ console.log(' 2. sidebar - Sidebar lateral')
82
+ console.log(' 3. dashboard - Sidebar + Footer')
83
+ console.log(' 4. minimal - Sin componentes (solo contenido)')
84
+ console.log('')
85
+
86
+ rl.question(' Ingresa el número (1-4) o el nombre del layout: ', (answer) => {
87
+ rl.close()
88
+
89
+ const normalized = answer.trim().toLowerCase()
90
+
91
+ if (normalized === '1' || normalized === 'default') {
92
+ resolve('default')
93
+ } else if (normalized === '2' || normalized === 'sidebar') {
94
+ resolve('sidebar')
95
+ } else if (normalized === '3' || normalized === 'dashboard') {
96
+ resolve('dashboard')
97
+ } else if (normalized === '4' || normalized === 'minimal') {
98
+ resolve('minimal')
99
+ } else {
100
+ console.log(' ⚠️ Opción inválida, usando "default"')
101
+ resolve('default')
102
+ }
103
+ })
104
+ })
105
+ }
106
+
107
+ // Función para copiar archivo desde el paquete al proyecto
108
+ function copyFileFromPackage(relativePath, targetPath, isComponent = false) {
109
+ const sourcePath = path.join(__dirname, '..', relativePath)
110
+ const destPath = path.join(projectRoot, targetPath)
111
+
112
+ if (!fs.existsSync(sourcePath)) {
113
+ console.error(` ❌ No se encontró: ${relativePath}`)
114
+ return false
115
+ }
116
+
117
+ // Crear directorio destino si no existe
118
+ const destDir = path.dirname(destPath)
119
+ if (!fs.existsSync(destDir)) {
120
+ fs.mkdirSync(destDir, { recursive: true })
121
+ }
122
+
123
+ let content = fs.readFileSync(sourcePath, 'utf8')
124
+
125
+ // Si es un componente, reemplazar importaciones relativas por importaciones del paquete
126
+ if (isComponent) {
127
+ // Reemplazar importaciones de hooks
128
+ content = content.replace(
129
+ /from ['"]\.\.\/\.\.\/hooks['"]/g,
130
+ "from 'responsive-system'"
131
+ )
132
+ // Reemplazar importaciones de context
133
+ content = content.replace(
134
+ /from ['"]\.\.\/\.\.\/context['"]/g,
135
+ "from 'responsive-system'"
136
+ )
137
+ // Reemplazar importaciones de componentes/layout
138
+ content = content.replace(
139
+ /from ['"]\.\.\/components\/layout['"]/g,
140
+ "from 'responsive-system'"
141
+ )
142
+ }
143
+
144
+ fs.writeFileSync(destPath, content)
145
+ return true
146
+ }
147
+
148
+ // Función para generar componentes genéricos según el layout seleccionado
149
+ function generateLayoutComponents(selectedLayout) {
150
+ const componentsDir = path.join(projectRoot, 'src', 'components', 'layout')
151
+
152
+ // Crear directorio si no existe
153
+ if (!fs.existsSync(componentsDir)) {
154
+ fs.mkdirSync(componentsDir, { recursive: true })
155
+ }
156
+
157
+ const componentsToGenerate = {
158
+ default: ['Navigation', 'Footer'],
159
+ sidebar: ['Sidebar'],
160
+ dashboard: ['Sidebar', 'Footer'],
161
+ minimal: []
162
+ }
163
+
164
+ const components = componentsToGenerate[selectedLayout] || []
165
+
166
+ if (components.length === 0) {
167
+ console.log(' ✅ Layout minimal: No se generan componentes')
168
+ return
169
+ }
170
+
171
+ console.log(` 📦 Generando componentes para layout "${selectedLayout}":`)
172
+
173
+ // Generar Navigation genérico
174
+ if (components.includes('Navigation')) {
175
+ const navigationContent = `import { useResponsiveLayout } from 'responsive-system'
176
+
177
+ const Navigation = () => {
178
+ const { isMobile } = useResponsiveLayout()
179
+
180
+ return (
181
+ <nav className="sticky top-0 z-50 bg-gray-900 border-b border-gray-800">
182
+ <div className="px-4 py-4">
183
+ <div className="flex items-center justify-between">
184
+ <div className="flex items-center space-x-3">
185
+ <div className="w-8 h-8 bg-gray-700 rounded-lg flex items-center justify-center">
186
+ <span className="text-white font-bold text-sm">LO</span>
187
+ </div>
188
+ <h1 className="text-white font-semibold text-lg">Tu Aplicación</h1>
189
+ </div>
190
+
191
+ {isMobile && (
192
+ <button className="p-2 text-gray-400 hover:text-white">
193
+ <svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
194
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" />
195
+ </svg>
196
+ </button>
197
+ )}
198
+ </div>
199
+ </div>
200
+ </nav>
201
+ )
202
+ }
203
+
204
+ export default Navigation
205
+ `
206
+ fs.writeFileSync(path.join(componentsDir, 'Navigation.tsx'), navigationContent)
207
+ console.log(' ✅ Navigation.tsx')
208
+ }
209
+
210
+ // Generar Footer genérico
211
+ if (components.includes('Footer')) {
212
+ const footerContent = `const Footer = () => {
213
+ return (
214
+ <footer className="bg-gray-900 border-t border-gray-800">
215
+ <div className="px-4 py-6">
216
+ <div className="max-w-7xl mx-auto text-center">
217
+ <p className="text-gray-400 text-sm">
218
+ © {new Date().getFullYear()} Tu Aplicación. Todos los derechos reservados.
219
+ </p>
220
+ </div>
221
+ </div>
222
+ </footer>
223
+ )
224
+ }
225
+
226
+ export default Footer
227
+ `
228
+ fs.writeFileSync(path.join(componentsDir, 'Footer.tsx'), footerContent)
229
+ console.log(' ✅ Footer.tsx')
230
+ }
231
+
232
+ // Generar Sidebar genérico
233
+ if (components.includes('Sidebar')) {
234
+ const sidebarContent = `import { useResponsiveLayout } from 'responsive-system'
235
+ import { useSidebar } from 'responsive-system'
236
+
237
+ const Sidebar = () => {
238
+ const { isMobile, isTablet } = useResponsiveLayout()
239
+ const { sidebarOpen, setSidebarOpen } = useSidebar()
240
+
241
+ const menuItems = [
242
+ { id: 'home', label: 'Inicio' },
243
+ { id: 'about', label: 'Acerca' },
244
+ { id: 'contact', label: 'Contacto' },
245
+ ]
246
+
247
+ return (
248
+ <>
249
+ {/* Hamburger button para móvil */}
250
+ {isMobile && (
251
+ <button
252
+ onClick={() => setSidebarOpen(true)}
253
+ className="fixed top-4 left-4 z-50 p-2 rounded-lg text-gray-300 hover:text-white hover:bg-gray-800 bg-gray-900 border border-gray-700"
254
+ >
255
+ <svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
256
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" />
257
+ </svg>
258
+ </button>
259
+ )}
260
+
261
+ {/* Sidebar desktop */}
262
+ <aside className={\`bg-gray-900 border-r border-gray-800 \${isMobile ? 'hidden' : 'w-64 flex-shrink-0'} \${isTablet ? 'w-56' : 'w-64'}\`}>
263
+ <div className="p-6 flex flex-col h-full">
264
+ <div className="flex items-center space-x-3 mb-8">
265
+ <div className="w-8 h-8 bg-gray-700 rounded-lg flex items-center justify-center">
266
+ <span className="text-white font-bold text-sm">LO</span>
267
+ </div>
268
+ <span className="text-white font-bold text-lg">Tu Aplicación</span>
269
+ </div>
270
+
271
+ <nav className="space-y-2">
272
+ {menuItems.map((item) => (
273
+ <button
274
+ key={item.id}
275
+ className="w-full flex items-center px-4 py-3 rounded-lg transition-all text-left text-gray-300 hover:text-white hover:bg-gray-800"
276
+ >
277
+ <span className="font-medium">{item.label}</span>
278
+ </button>
279
+ ))}
280
+ </nav>
281
+ </div>
282
+ </aside>
283
+
284
+ {/* Sidebar móvil desplegable */}
285
+ {isMobile && sidebarOpen && (
286
+ <div className="fixed inset-0 z-40 bg-black/50" onClick={() => setSidebarOpen(false)}>
287
+ <div className="fixed top-0 left-0 w-64 h-full bg-gray-900 border-r border-gray-800">
288
+ <div className="p-6 flex flex-col h-full pt-20">
289
+ <div className="flex items-center space-x-3 mb-8">
290
+ <div className="w-8 h-8 bg-gray-700 rounded-lg flex items-center justify-center">
291
+ <span className="text-white font-bold text-sm">LO</span>
292
+ </div>
293
+ <span className="text-white font-bold text-lg">Tu Aplicación</span>
294
+ </div>
295
+
296
+ <nav className="space-y-2">
297
+ {menuItems.map((item) => (
298
+ <button
299
+ key={item.id}
300
+ onClick={() => setSidebarOpen(false)}
301
+ className="w-full flex items-center px-4 py-3 rounded-lg transition-all text-left text-gray-300 hover:text-white hover:bg-gray-800"
302
+ >
303
+ <span className="font-medium">{item.label}</span>
304
+ </button>
305
+ ))}
306
+ </nav>
307
+ </div>
308
+ </div>
309
+ </div>
310
+ )}
311
+ </>
312
+ )
313
+ }
314
+
315
+ export default Sidebar
316
+ `
317
+ fs.writeFileSync(path.join(componentsDir, 'Sidebar.tsx'), sidebarContent)
318
+ console.log(' ✅ Sidebar.tsx')
319
+ }
320
+
321
+ // Crear index.ts para exportar los componentes
322
+ const indexContent = components.map(c => `export { default as ${c} } from './${c}'`).join('\n')
323
+ fs.writeFileSync(path.join(componentsDir, 'index.ts'), indexContent)
324
+ console.log(' ✅ index.ts')
325
+ }
326
+
327
+ // Función para copiar el hook useResponsive y sus dependencias
328
+ function copyUseResponsiveHook() {
329
+ console.log(' 📦 Copiando hook useResponsive...')
330
+
331
+ // Crear directorio hooks
332
+ const hooksDir = path.join(projectRoot, 'src', 'hooks')
333
+ if (!fs.existsSync(hooksDir)) {
334
+ fs.mkdirSync(hooksDir, { recursive: true })
335
+ }
336
+
337
+ // Copiar tipos
338
+ const typesDir = path.join(projectRoot, 'src', 'types')
339
+ if (!fs.existsSync(typesDir)) {
340
+ fs.mkdirSync(typesDir, { recursive: true })
341
+ }
342
+ copyFileFromPackage('src/types/responsive.ts', 'src/types/responsive.ts')
343
+ console.log(' ✅ types/responsive.ts')
344
+
345
+ // Copiar constantes
346
+ const constantsDir = path.join(projectRoot, 'src', 'constants')
347
+ if (!fs.existsSync(constantsDir)) {
348
+ fs.mkdirSync(constantsDir, { recursive: true })
349
+ }
350
+ copyFileFromPackage('src/constants/breakpoints.ts', 'src/constants/breakpoints.ts')
351
+ console.log(' ✅ constants/breakpoints.ts')
352
+
353
+ // Copiar hook useResponsive
354
+ copyFileFromPackage('src/hooks/useResponsive.ts', 'src/hooks/useResponsive.ts')
355
+ console.log(' ✅ hooks/useResponsive.ts')
356
+
357
+ // Crear index.ts para exportar el hook
358
+ const indexContent = `export { useResponsive } from './useResponsive'
359
+ export type { ResponsiveState, Breakpoint, Orientation } from '../types/responsive'
360
+ export { DEFAULT_BREAKPOINTS, getCurrentBreakpoint, getBreakpointIndex, getBreakpointValue } from '../constants/breakpoints'
361
+ `
362
+ fs.writeFileSync(path.join(hooksDir, 'index.ts'), indexContent)
363
+ console.log(' ✅ hooks/index.ts')
364
+ }
365
+
366
+ console.log('📦 Verificando dependencias...')
367
+
368
+ // Agregar React a dependencies SOLO si NO está en package.json
369
+ if (!hasReactInPackageJson) {
370
+ console.log(' ➕ Agregando React a dependencies...')
371
+ if (!packageJson.dependencies) {
372
+ packageJson.dependencies = {}
373
+ }
374
+ packageJson.dependencies['react'] = '^19.1.1'
375
+ packageJson.dependencies['react-dom'] = '^19.1.1'
376
+ needsUpdate = true
377
+ } else {
378
+ console.log(' React ya está instalado')
379
+ }
380
+
381
+ // Agregar Vite si el proyecto está vacío
382
+ if (isProjectEmpty && !hasVite) {
383
+ console.log(' ➕ Agregando Vite a devDependencies...')
384
+ if (!packageJson.devDependencies) {
385
+ packageJson.devDependencies = {}
386
+ }
387
+ packageJson.devDependencies['vite'] = '^7.1.7'
388
+ packageJson.devDependencies['@vitejs/plugin-react'] = '^5.0.4'
389
+ needsUpdate = true
390
+ }
391
+
392
+ // Agregar Tailwind y sus dependencias a devDependencies
393
+ if (!tailwindInDevDeps) {
394
+ console.log(' ➕ Agregando Tailwind y PostCSS a devDependencies...')
395
+ if (!packageJson.devDependencies) {
396
+ packageJson.devDependencies = {}
397
+ }
398
+ packageJson.devDependencies['tailwindcss'] = '^4.1.14'
399
+ packageJson.devDependencies['@tailwindcss/postcss'] = '^4.1.14'
400
+ packageJson.devDependencies['postcss'] = '^8.5.6'
401
+ packageJson.devDependencies['autoprefixer'] = '^10.4.21'
402
+ needsUpdate = true
403
+ }
404
+
405
+ // Agregar TypeScript y sus tipos a devDependencies
406
+ if (!typescriptInDevDeps) {
407
+ console.log(' ➕ Agregando TypeScript a devDependencies...')
408
+ if (!packageJson.devDependencies) {
409
+ packageJson.devDependencies = {}
410
+ }
411
+ packageJson.devDependencies['typescript'] = '~5.9.3'
412
+ packageJson.devDependencies['@types/react'] = '^19.1.16'
413
+ packageJson.devDependencies['@types/react-dom'] = '^19.1.9'
414
+ needsUpdate = true
415
+ }
416
+
417
+ // Agregar "type": "module" si no existe (para evitar warnings)
418
+ if (!packageJson.type) {
419
+ packageJson.type = 'module'
420
+ needsUpdate = true
421
+ }
422
+
423
+ // Escribir package.json modificado
424
+ if (needsUpdate) {
425
+ console.log('')
426
+ console.log('📝 Actualizando package.json...')
427
+ fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2))
428
+ console.log('✅ package.json actualizado')
429
+ console.log('')
430
+
431
+ // Ahora instalar las dependencias
432
+ console.log('📦 Instalando dependencias...')
433
+ try {
434
+ execSync('npm install', {
435
+ stdio: 'inherit',
436
+ cwd: projectRoot
437
+ })
438
+ console.log('✅ Dependencias instaladas correctamente')
439
+ } catch (error) {
440
+ console.error('❌ Error al instalar dependencias:', error.message)
441
+ console.log('⚠️ Las dependencias están en package.json, ejecuta "npm install" manualmente')
442
+ }
443
+ } else {
444
+ console.log('✅ Todas las dependencias ya están instaladas')
445
+ }
446
+
447
+ // Si el proyecto está vacío, crear estructura base
448
+ if (isProjectEmpty) {
449
+ console.log('')
450
+ console.log('📦 Proyecto vacío detectado, creando estructura base...')
451
+ console.log('')
452
+
453
+ // Preguntar qué layout quiere
454
+ const selectedLayout = await askLayout()
455
+ console.log(` ✅ Layout seleccionado: "${selectedLayout}"`)
456
+ console.log('')
457
+
458
+ // Crear estructura de directorios
459
+ const dirs = ['src', 'src/components', 'src/components/layout', 'src/pages', 'src/hooks', 'src/types', 'src/constants', 'public']
460
+ dirs.forEach(dir => {
461
+ const dirPath = path.join(projectRoot, dir)
462
+ if (!fs.existsSync(dirPath)) {
463
+ fs.mkdirSync(dirPath, { recursive: true })
464
+ }
465
+ })
466
+
467
+ // Generar componentes genéricos según layout seleccionado
468
+ generateLayoutComponents(selectedLayout)
469
+ console.log('')
470
+
471
+ // Copiar hook useResponsive
472
+ copyUseResponsiveHook()
473
+ console.log('')
474
+
475
+ // Crear vite.config.ts
476
+ const viteConfigPath = path.join(projectRoot, 'vite.config.ts')
477
+ if (!fs.existsSync(viteConfigPath)) {
478
+ const viteConfig = `import { defineConfig } from 'vite'
479
+ import react from '@vitejs/plugin-react'
480
+
481
+ export default defineConfig({
482
+ plugins: [react()],
483
+ })
484
+ `
485
+ fs.writeFileSync(viteConfigPath, viteConfig)
486
+ console.log(' Creado: vite.config.ts')
487
+ }
488
+
489
+ // Crear tailwind.config.js
490
+ const tailwindConfigPath = path.join(projectRoot, 'tailwind.config.js')
491
+ if (!fs.existsSync(tailwindConfigPath)) {
492
+ const tailwindConfig = `import responsiveScalePlugin from 'responsive-system/plugin'
493
+
494
+ export default {
495
+ content: [
496
+ "./index.html",
497
+ "./src/**/*.{js,ts,jsx,tsx}",
498
+ ],
499
+ theme: {
500
+ extend: {
501
+ screens: {
502
+ 'xs': '475px', 'sm': '640px', 'md': '768px', 'lg': '1024px',
503
+ 'xl': '1280px', '2xl': '1536px', '3xl': '1920px', '4xl': '2560px', '5xl': '3840px'
504
+ }
505
+ },
506
+ },
507
+ plugins: [
508
+ responsiveScalePlugin({
509
+ scaleProperties: {
510
+ typography: true,
511
+ spacing: true,
512
+ lineHeight: true,
513
+ letterSpacing: true,
514
+ shadows: true,
515
+ borderWidth: false,
516
+ sizing: false,
517
+ borderRadius: false
518
+ },
519
+ scales: {
520
+ xs: 1.0, sm: 1.0, md: 1.0, lg: 1.0, xl: 1.0,
521
+ '2xl': 1.05, '3xl': 1.15, '4xl': 1.25, '5xl': 1.35
522
+ }
523
+ })
524
+ ],
525
+ }
526
+ `
527
+ fs.writeFileSync(tailwindConfigPath, tailwindConfig)
528
+ console.log(' ✅ Creado: tailwind.config.js')
529
+ }
530
+
531
+ // Crear postcss.config.js
532
+ const postcssConfigPath = path.join(projectRoot, 'postcss.config.js')
533
+ if (!fs.existsSync(postcssConfigPath)) {
534
+ const postcssConfig = `export default {
535
+ plugins: {
536
+ '@tailwindcss/postcss': {},
537
+ autoprefixer: {},
538
+ },
539
+ }
540
+ `
541
+ fs.writeFileSync(postcssConfigPath, postcssConfig)
542
+ console.log(' ✅ Creado: postcss.config.js')
543
+ }
544
+
545
+ // Crear tsconfig.json
546
+ const tsconfigPath = path.join(projectRoot, 'tsconfig.json')
547
+ if (!fs.existsSync(tsconfigPath)) {
548
+ const tsconfig = `{
549
+ "compilerOptions": {
550
+ "target": "ES2020",
551
+ "useDefineForClassFields": true,
552
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
553
+ "module": "ESNext",
554
+ "skipLibCheck": true,
555
+ "moduleResolution": "bundler",
556
+ "allowImportingTsExtensions": true,
557
+ "resolveJsonModule": true,
558
+ "isolatedModules": true,
559
+ "noEmit": true,
560
+ "jsx": "react-jsx",
561
+ "strict": true,
562
+ "noUnusedLocals": true,
563
+ "noUnusedParameters": true,
564
+ "noFallthroughCasesInSwitch": true
565
+ },
566
+ "include": ["src"],
567
+ "references": [{ "path": "./tsconfig.node.json" }]
568
+ }
569
+ `
570
+ fs.writeFileSync(tsconfigPath, tsconfig)
571
+ console.log(' ✅ Creado: tsconfig.json')
572
+ }
573
+
574
+ // Crear tsconfig.node.json
575
+ const tsconfigNodePath = path.join(projectRoot, 'tsconfig.node.json')
576
+ if (!fs.existsSync(tsconfigNodePath)) {
577
+ const tsconfigNode = `{
578
+ "compilerOptions": {
579
+ "composite": true,
580
+ "skipLibCheck": true,
581
+ "module": "ESNext",
582
+ "moduleResolution": "bundler",
583
+ "allowSyntheticDefaultImports": true
584
+ },
585
+ "include": ["vite.config.ts"]
586
+ }
587
+ `
588
+ fs.writeFileSync(tsconfigNodePath, tsconfigNode)
589
+ console.log(' ✅ Creado: tsconfig.node.json')
590
+ }
591
+
592
+ // Crear index.html
593
+ const indexHtmlPath = path.join(projectRoot, 'index.html')
594
+ if (!fs.existsSync(indexHtmlPath)) {
595
+ const indexHtml = `<!doctype html>
596
+ <html lang="es">
597
+ <head>
598
+ <meta charset="UTF-8" />
599
+ <link rel="icon" type="image/svg+xml" href="/vite.svg" />
600
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
601
+ <title>Responsive System Demo</title>
602
+ </head>
603
+ <body>
604
+ <div id="root"></div>
605
+ <script type="module" src="/src/main.tsx"></script>
606
+ </body>
607
+ </html>
608
+ `
609
+ fs.writeFileSync(indexHtmlPath, indexHtml)
610
+ console.log(' ✅ Creado: index.html')
611
+ }
612
+
613
+ // Crear src/main.tsx
614
+ const mainTsxPath = path.join(projectRoot, 'src', 'main.tsx')
615
+ if (!fs.existsSync(mainTsxPath)) {
616
+ const mainTsx = `import React from 'react'
617
+ import ReactDOM from 'react-dom/client'
618
+ import { ResponsiveLayoutProvider, MainLayout } from 'responsive-system'
619
+ import App from './App'
620
+ import './index.css'
621
+
622
+ ReactDOM.createRoot(document.getElementById('root')!).render(
623
+ <React.StrictMode>
624
+ <ResponsiveLayoutProvider defaultLayout="${selectedLayout}">
625
+ <MainLayout>
626
+ <App />
627
+ </MainLayout>
628
+ </ResponsiveLayoutProvider>
629
+ </React.StrictMode>,
630
+ )
631
+ `
632
+ fs.writeFileSync(mainTsxPath, mainTsx)
633
+ console.log(' ✅ Creado: src/main.tsx')
634
+ }
635
+
636
+ // Crear src/index.css
637
+ const indexCssPath = path.join(projectRoot, 'src', 'index.css')
638
+ if (!fs.existsSync(indexCssPath)) {
639
+ const indexCss = `@import "tailwindcss";
640
+ `
641
+ fs.writeFileSync(indexCssPath, indexCss)
642
+ console.log(' ✅ Creado: src/index.css')
643
+ }
644
+
645
+ // Crear src/pages/HomePage.tsx con página de ejemplo simple
646
+ const homePagePath = path.join(projectRoot, 'src', 'pages', 'HomePage.tsx')
647
+ if (!fs.existsSync(homePagePath)) {
648
+ const homePage = `import { useResponsiveLayout } from 'responsive-system'
649
+ import { useResponsive } from '../hooks'
650
+
651
+ function HomePage() {
652
+ const { breakpoint, isMobile, layout } = useResponsiveLayout()
653
+ const responsive = useResponsive()
654
+
655
+ return (
656
+ <div className="py-8 px-4">
657
+ <div className="max-w-7xl mx-auto space-y-8">
658
+ {/* Hero Section */}
659
+ <div className="bg-white rounded-lg shadow-md p-8 text-center">
660
+ <h1 className="text-4xl font-bold text-gray-900 mb-4">
661
+ Bienvenido a tu Aplicación
662
+ </h1>
663
+ <p className="text-lg text-gray-600 max-w-2xl mx-auto">
664
+ Esta es una página de ejemplo que demuestra el sistema responsive con auto-scaling.
665
+ Todo el contenido se ajusta automáticamente según el tamaño de pantalla.
666
+ </p>
667
+ </div>
668
+
669
+ {/* Info Cards */}
670
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
671
+ <div className="bg-blue-50 rounded-lg p-6 border border-blue-200">
672
+ <h3 className="text-sm font-semibold text-blue-900 mb-2">Breakpoint</h3>
673
+ <p className="text-2xl font-bold text-blue-700">{breakpoint.toUpperCase()}</p>
674
+ </div>
675
+ <div className="bg-green-50 rounded-lg p-6 border border-green-200">
676
+ <h3 className="text-sm font-semibold text-green-900 mb-2">Dispositivo</h3>
677
+ <p className="text-2xl font-bold text-green-700">{isMobile ? 'Móvil' : 'Desktop'}</p>
678
+ </div>
679
+ <div className="bg-purple-50 rounded-lg p-6 border border-purple-200">
680
+ <h3 className="text-sm font-semibold text-purple-900 mb-2">Ancho</h3>
681
+ <p className="text-2xl font-bold text-purple-700">{responsive.width}px</p>
682
+ </div>
683
+ <div className="bg-orange-50 rounded-lg p-6 border border-orange-200">
684
+ <h3 className="text-sm font-semibold text-orange-900 mb-2">Alto</h3>
685
+ <p className="text-2xl font-bold text-orange-700">{responsive.height}px</p>
686
+ </div>
687
+ </div>
688
+
689
+ {/* Content Cards */}
690
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
691
+ {[1, 2, 3, 4, 5, 6].map((i) => (
692
+ <div key={i} className="bg-white rounded-lg shadow-md p-6 hover:shadow-lg transition-shadow">
693
+ <div className="w-12 h-12 bg-blue-500 rounded-lg flex items-center justify-center mb-4">
694
+ <span className="text-white font-bold text-xl">{i}</span>
695
+ </div>
696
+ <h3 className="text-xl font-bold text-gray-900 mb-2">Card {i}</h3>
697
+ <p className="text-gray-600">
698
+ Este es un ejemplo de card. El texto, espaciado y sombras se ajustan automáticamente
699
+ según el tamaño de pantalla gracias al sistema de auto-scaling.
700
+ </p>
701
+ </div>
702
+ ))}
703
+ </div>
704
+
705
+ {/* Info Section */}
706
+ <div className="bg-white rounded-lg shadow-md p-8">
707
+ <h2 className="text-2xl font-bold text-gray-900 mb-4">Sistema Responsive</h2>
708
+ <div className="space-y-3 text-gray-700">
709
+ <p>
710
+ <strong className="text-gray-900">Auto-scaling activo:</strong> Todo el contenido escala
711
+ automáticamente según el breakpoint actual (texto, espaciado, sombras).
712
+ </p>
713
+ <p>
714
+ <strong className="text-gray-900">Hook useResponsive:</strong> Disponible en{' '}
715
+ <code className="bg-gray-100 px-2 py-1 rounded text-sm">src/hooks/useResponsive.ts</code>{' '}
716
+ para configuración manual cuando lo necesites.
717
+ </p>
718
+ <p>
719
+ <strong className="text-gray-900">Layout actual:</strong> <span className="capitalize">{layout.current}</span>
720
+ </p>
721
+ </div>
722
+ </div>
723
+ </div>
724
+ </div>
725
+ )
726
+ }
727
+
728
+ export default HomePage
729
+ `
730
+ fs.writeFileSync(homePagePath, homePage)
731
+ console.log(' ✅ Creado: src/pages/HomePage.tsx (página de ejemplo simple)')
732
+ }
733
+
734
+ // Crear src/App.tsx que importa la página
735
+ const appTsxPath = path.join(projectRoot, 'src', 'App.tsx')
736
+ if (!fs.existsSync(appTsxPath)) {
737
+ const appTsx = `import HomePage from './pages/HomePage'
738
+
739
+ function App() {
740
+ return <HomePage />
741
+ }
742
+
743
+ export default App
744
+ `
745
+ fs.writeFileSync(appTsxPath, appTsx)
746
+ console.log(' ✅ Creado: src/App.tsx')
747
+ }
748
+
749
+ // Actualizar package.json con scripts
750
+ if (!packageJson.scripts) {
751
+ packageJson.scripts = {}
752
+ }
753
+ if (!packageJson.scripts.dev) {
754
+ packageJson.scripts.dev = 'vite'
755
+ }
756
+ if (!packageJson.scripts.build) {
757
+ packageJson.scripts.build = 'tsc && vite build'
758
+ }
759
+ if (!packageJson.scripts.preview) {
760
+ packageJson.scripts.preview = 'vite preview'
761
+ }
762
+
763
+ fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2))
764
+ console.log(' ✅ Actualizado: package.json con scripts')
765
+
766
+ console.log('')
767
+ console.log('🎉 Proyecto inicializado correctamente!')
768
+ console.log('')
769
+ console.log('Para empezar:')
770
+ console.log(' 1. npm run dev')
771
+ console.log(' 2. Abre http://localhost:5173')
772
+ console.log('')
773
+ console.log(`Layout seleccionado: "${selectedLayout}"`)
774
+ console.log(' - Componentes en src/components/layout/')
775
+ console.log(' - Hook useResponsive en src/hooks/useResponsive.ts')
776
+ console.log(' - Página de ejemplo en src/pages/HomePage.tsx')
777
+ console.log('')
778
+ console.log('💡 Para cambiar el layout: npx responsive-system-setup')
779
+ console.log('')
780
+ } else {
781
+ console.log('✅ Proyecto ya inicializado')
782
+ }
783
+
784
+ console.log('')
785
+ console.log('✅ Configuración completada')
786
+ console.log('')