novatec-cli 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/create.js ADDED
@@ -0,0 +1,278 @@
1
+ import path from 'path';
2
+ import chalk from 'chalk';
3
+ import fse from 'fs-extra';
4
+ import { askProjectOptions } from './prompts.js';
5
+ import { generateFrontend } from './generators/frontend.js';
6
+ import { generateBackend } from './generators/backend.js';
7
+ import { generateReadme } from './generators/readme.js';
8
+ import { run, isInstalled } from './utils.js';
9
+
10
+ const boxen = await import('boxen');
11
+ const gradient = await import('gradient-string');
12
+
13
+ async function runCreate(nameArg, options) {
14
+ const config = await askProjectOptions(nameArg, options);
15
+ const projectPath = path.resolve(process.cwd(), config.name);
16
+
17
+ if (await fse.pathExists(projectPath)) {
18
+ throw new Error(`La carpeta "${config.name}" ya existe en este directorio.`);
19
+ }
20
+
21
+ console.log(chalk.cyan(`\nπŸ“ Creando proyecto en: ${chalk.bold(projectPath)}\n`));
22
+ await fse.ensureDir(projectPath);
23
+
24
+ await generateFrontend(config, projectPath);
25
+ await generateBackend(config, projectPath);
26
+ await generateReadme(config, projectPath);
27
+
28
+ const extras = config.extras || [];
29
+
30
+ if (extras.includes('env')) await generateEnvFiles(config, projectPath);
31
+ if (extras.includes('editorconfig')) await fse.writeFile(path.join(projectPath, '.editorconfig'), EDITORCONFIG);
32
+ if (extras.includes('eslint')) await setupEslint(config, projectPath);
33
+ if (extras.includes('husky')) await setupHusky(config, projectPath);
34
+ if (extras.includes('testing')) await setupTesting(config, projectPath);
35
+ if (extras.includes('ci')) await setupCI(config, projectPath);
36
+ if (extras.includes('docker')) await generateDocker(config, projectPath);
37
+
38
+ if (extras.includes('git') && isInstalled('git')) {
39
+ try { run('git init', projectPath, 'Inicializando repositorio git'); } catch {}
40
+ await fse.writeFile(path.join(projectPath, '.gitignore'), GITIGNORE);
41
+ }
42
+
43
+ // Resumen final
44
+ const novatecGradient = gradient.default(['#00C6FF', '#0072FF', '#7B2FF7']);
45
+ const backendCmd = {
46
+ express: 'npm run dev',
47
+ nestjs: 'npm run start:dev',
48
+ fastify: 'npm run dev',
49
+ hono: 'npm run dev',
50
+ fastapi: 'uvicorn main:app --reload',
51
+ django: 'python manage.py runserver',
52
+ flask: 'python run.py',
53
+ spring: 'mvn spring-boot:run',
54
+ deno: 'deno task dev',
55
+ gin: 'go run main.go'
56
+ };
57
+
58
+ console.log(
59
+ boxen.default(
60
+ novatecGradient(' β—† FULLSTACK CLI β—†\n\n') +
61
+ chalk.green.bold(' βœ… Proyecto creado exitosamente!\n\n') +
62
+ chalk.white(' πŸ“ Proyecto : ') + chalk.cyan.bold(config.name) + '\n' +
63
+ chalk.white(' βš› Frontend : ') + chalk.cyan(config.frontend) + '\n' +
64
+ chalk.white(' πŸ”§ Backend : ') + chalk.cyan(config.backend) + '\n\n' +
65
+ chalk.bold.white(' ─── CΓ³mo iniciar ───────────────────────────\n\n') +
66
+ chalk.yellow(' Frontend:\n') +
67
+ chalk.white(` cd ${config.name}\\frontend\n`) +
68
+ chalk.cyan(' npm run dev\n\n') +
69
+ chalk.yellow(' Backend:\n') +
70
+ chalk.white(` cd ${config.name}\\backend\n`) +
71
+ chalk.cyan(` ${backendCmd[config.backend] || 'npm start'}`),
72
+ {
73
+ padding: 1,
74
+ margin: { top: 1, left: 2 },
75
+ borderStyle: 'double',
76
+ borderColor: 'cyan',
77
+ }
78
+ )
79
+ );
80
+ }
81
+
82
+ // ── ESLint + Prettier ─────────────────────────────────────────────────────────
83
+ async function setupEslint(config, projectPath) {
84
+ const frontendPath = path.join(projectPath, 'frontend');
85
+ const eslintConfig = `{
86
+ "env": { "browser": true, "es2021": true, "node": true },
87
+ "extends": ["eslint:recommended"],
88
+ "parserOptions": { "ecmaVersion": "latest", "sourceType": "module" },
89
+ "rules": { "no-unused-vars": "warn", "no-console": "off" }
90
+ }\n`;
91
+ const prettierConfig = `{
92
+ "semi": true,
93
+ "singleQuote": true,
94
+ "tabWidth": 2,
95
+ "trailingComma": "es5"
96
+ }\n`;
97
+
98
+ await fse.writeFile(path.join(frontendPath, '.eslintrc.json'), eslintConfig);
99
+ await fse.writeFile(path.join(frontendPath, '.prettierrc'), prettierConfig);
100
+ run('npm install -D eslint prettier eslint-config-prettier', frontendPath, 'Instalando ESLint + Prettier');
101
+ console.log(chalk.green('βœ” ESLint + Prettier configurados'));
102
+ }
103
+
104
+ // ── Husky + lint-staged ───────────────────────────────────────────────────────
105
+ async function setupHusky(config, projectPath) {
106
+ const frontendPath = path.join(projectPath, 'frontend');
107
+ try {
108
+ run('npm install -D husky lint-staged', frontendPath, 'Instalando Husky + lint-staged');
109
+ run('npx husky init', frontendPath, 'Inicializando Husky');
110
+ await fse.writeFile(path.join(frontendPath, '.husky', 'pre-commit'), '#!/bin/sh\nnpx lint-staged\n');
111
+ const pkgPath = path.join(frontendPath, 'package.json');
112
+ const pkg = await fse.readJSON(pkgPath);
113
+ pkg['lint-staged'] = { '*.{js,jsx,ts,tsx,vue}': ['eslint --fix', 'prettier --write'] };
114
+ await fse.writeJSON(pkgPath, pkg, { spaces: 2 });
115
+ console.log(chalk.green('βœ” Husky + lint-staged configurados'));
116
+ } catch { console.log(chalk.yellow('⚠ Husky requiere git init primero')); }
117
+ }
118
+
119
+ // ── Testing ───────────────────────────────────────────────────────────────────
120
+ async function setupTesting(config, projectPath) {
121
+ const frontendPath = path.join(projectPath, 'frontend');
122
+ const isReactLike = ['react', 'next', 'solid', 'remix', 'qwik'].includes(config.frontend);
123
+
124
+ run('npm install -D vitest @vitest/ui', frontendPath, 'Instalando Vitest');
125
+
126
+ const testDir = path.join(frontendPath, 'src', '__tests__');
127
+ await fse.ensureDir(testDir);
128
+
129
+ const sampleTest = isReactLike
130
+ ? `import { describe, it, expect } from 'vitest';\n\ndescribe('App', () => {\n it('should work', () => {\n expect(1 + 1).toBe(2);\n });\n});\n`
131
+ : `import { describe, it, expect } from 'vitest';\n\ndescribe('App', () => {\n it('should work', () => {\n expect(true).toBe(true);\n });\n});\n`;
132
+
133
+ await fse.writeFile(path.join(testDir, 'app.test.js'), sampleTest);
134
+
135
+ const pkgPath = path.join(frontendPath, 'package.json');
136
+ const pkg = await fse.readJSON(pkgPath);
137
+ pkg.scripts = { ...pkg.scripts, test: 'vitest', 'test:ui': 'vitest --ui' };
138
+ await fse.writeJSON(pkgPath, pkg, { spaces: 2 });
139
+ console.log(chalk.green('βœ” Vitest configurado con test de ejemplo'));
140
+ }
141
+
142
+ // ── GitHub Actions CI ─────────────────────────────────────────────────────────
143
+ async function setupCI(config, projectPath) {
144
+ const ciDir = path.join(projectPath, '.github', 'workflows');
145
+ await fse.ensureDir(ciDir);
146
+
147
+ const ci = `name: CI
148
+
149
+ on:
150
+ push:
151
+ branches: [main, develop]
152
+ pull_request:
153
+ branches: [main]
154
+
155
+ jobs:
156
+ frontend:
157
+ runs-on: ubuntu-latest
158
+ defaults:
159
+ run:
160
+ working-directory: frontend
161
+ steps:
162
+ - uses: actions/checkout@v4
163
+ - uses: actions/setup-node@v4
164
+ with:
165
+ node-version: 20
166
+ cache: npm
167
+ cache-dependency-path: frontend/package-lock.json
168
+ - run: npm ci
169
+ - run: npm run build
170
+ ${config.extras.includes('testing') ? ' - run: npm test\n' : ''}
171
+ backend:
172
+ runs-on: ubuntu-latest
173
+ defaults:
174
+ run:
175
+ working-directory: backend
176
+ steps:
177
+ - uses: actions/checkout@v4
178
+ ${['fastapi', 'django', 'flask'].includes(config.backend) ? ` - uses: actions/setup-python@v5
179
+ with:
180
+ python-version: '3.11'
181
+ - run: pip install -r requirements.txt` : ` - uses: actions/setup-node@v4
182
+ with:
183
+ node-version: 20
184
+ - run: npm ci`}
185
+ `;
186
+ await fse.writeFile(path.join(ciDir, 'ci.yml'), ci);
187
+ console.log(chalk.green('βœ” GitHub Actions CI configurado'));
188
+ }
189
+
190
+ // ── Docker ────────────────────────────────────────────────────────────────────
191
+ async function generateDocker(config, projectPath) {
192
+ const isPython = ['fastapi', 'django', 'flask'].includes(config.backend);
193
+ const backendPort = isPython ? (config.backend === 'gin' ? '8080' : '8000') : '3001';
194
+
195
+ const backendService = isPython
196
+ ? ` backend:
197
+ build: ./backend
198
+ ports:
199
+ - "${backendPort}:${backendPort}"
200
+ volumes:
201
+ - ./backend:/app
202
+ environment:
203
+ - DEBUG=True`
204
+ : ` backend:
205
+ build: ./backend
206
+ ports:
207
+ - "${backendPort}:${backendPort}"
208
+ volumes:
209
+ - ./backend:/app
210
+ - /app/node_modules
211
+ environment:
212
+ - NODE_ENV=development`;
213
+
214
+ const compose = `version: '3.9'
215
+ services:
216
+ frontend:
217
+ build: ./frontend
218
+ ports:
219
+ - "5173:5173"
220
+ volumes:
221
+ - ./frontend:/app
222
+ - /app/node_modules
223
+ environment:
224
+ - NODE_ENV=development
225
+ ${backendService}
226
+ db:
227
+ image: postgres:16-alpine
228
+ environment:
229
+ POSTGRES_DB: mydb
230
+ POSTGRES_USER: user
231
+ POSTGRES_PASSWORD: password
232
+ ports:
233
+ - "5432:5432"
234
+ volumes:
235
+ - pgdata:/var/lib/postgresql/data
236
+
237
+ volumes:
238
+ pgdata:
239
+ `;
240
+ await fse.writeFile(path.join(projectPath, 'docker-compose.yml'), compose);
241
+ console.log(chalk.green('βœ” docker-compose.yml generado (con PostgreSQL)'));
242
+ }
243
+
244
+ // ── Archivos .env ─────────────────────────────────────────────────────────────
245
+ async function generateEnvFiles(config, projectPath) {
246
+ await fse.writeFile(path.join(projectPath, 'frontend', '.env.example'), 'VITE_API_URL=http://localhost:3001\n');
247
+ const isPython = ['fastapi', 'django', 'flask'].includes(config.backend);
248
+ if (!isPython) {
249
+ await fse.writeFile(path.join(projectPath, 'backend', '.env.example'), 'PORT=3001\nDB_URL=\nJWT_SECRET=change_me\nNODE_ENV=development\n');
250
+ }
251
+ console.log(chalk.green('βœ” Archivos .env.example generados'));
252
+ }
253
+
254
+ const EDITORCONFIG = `root = true
255
+ [*]
256
+ indent_style = space
257
+ indent_size = 2
258
+ end_of_line = lf
259
+ charset = utf-8
260
+ trim_trailing_whitespace = true
261
+ insert_final_newline = true
262
+ `;
263
+
264
+ const GITIGNORE = `node_modules/
265
+ dist/
266
+ .next/
267
+ .nuxt/
268
+ .svelte-kit/
269
+ .env
270
+ *.log
271
+ __pycache__/
272
+ .venv/
273
+ *.pyc
274
+ target/
275
+ *.class
276
+ `;
277
+
278
+ export { runCreate };
@@ -0,0 +1,337 @@
1
+ import path from 'path';
2
+ import chalk from 'chalk';
3
+ import fse from 'fs-extra';
4
+ import { run, isInstalled } from '../utils.js';
5
+
6
+ async function generateBackend(config, projectPath) {
7
+ console.log(chalk.blue(`\nπŸ”§ Generando backend (${chalk.bold(config.backend)})...`));
8
+ const handlers = { express, nestjs, fastify, hono, fastapi, django, flask, spring, deno, gin };
9
+ const fn = handlers[config.backend];
10
+ if (!fn) throw new Error(`Backend no soportado: ${config.backend}`);
11
+ await fn(config, projectPath);
12
+ }
13
+
14
+ // ── HELPERS ───────────────────────────────────────────────────────────────────
15
+ function backendPath(projectPath) { return path.join(projectPath, 'backend'); }
16
+
17
+ async function writeDotenv(bPath, content) {
18
+ await fse.writeFile(path.join(bPath, '.env'), content);
19
+ await fse.writeFile(path.join(bPath, '.env.example'), content.replace(/=.+/g, '='));
20
+ }
21
+
22
+ // ── EXPRESS ───────────────────────────────────────────────────────────────────
23
+ async function express(config, projectPath) {
24
+ const bp = backendPath(projectPath);
25
+ await fse.ensureDir(path.join(bp, 'src', 'routes'));
26
+ await fse.ensureDir(path.join(bp, 'src', 'middlewares'));
27
+ await fse.ensureDir(path.join(bp, 'src', 'controllers'));
28
+
29
+ const deps = config.backendDeps || [];
30
+ const hasDotenv = deps.includes('dotenv');
31
+ const hasCors = deps.includes('cors');
32
+ const hasHelmet = deps.includes('helmet');
33
+ const hasMorgan = deps.includes('morgan');
34
+ const hasSwagger = deps.includes('swagger-ui-express');
35
+ const hasSocket = deps.includes('socket.io');
36
+
37
+ const pkg = {
38
+ name: 'backend', version: '1.0.0', main: 'src/server.js',
39
+ scripts: { start: 'node src/server.js', dev: 'nodemon src/server.js' },
40
+ };
41
+ await fse.writeJSON(path.join(bp, 'package.json'), pkg, { spaces: 2 });
42
+
43
+ const server = `${hasDotenv ? "require('dotenv').config();\n" : ''}const express = require('express');
44
+ ${hasCors ? "const cors = require('cors');" : ''}
45
+ ${hasHelmet ? "const helmet = require('helmet');" : ''}
46
+ ${hasMorgan ? "const morgan = require('morgan');" : ''}
47
+ ${hasSwagger ? "const swaggerUi = require('swagger-ui-express');\nconst swaggerDoc = require('./swagger.json');" : ''}
48
+ ${hasSocket ? "const { createServer } = require('http');\nconst { Server } = require('socket.io');" : ''}
49
+
50
+ const app = express();
51
+ ${hasSocket ? 'const httpServer = createServer(app);\nconst io = new Server(httpServer, { cors: { origin: "*" } });' : ''}
52
+ ${hasCors ? 'app.use(cors());' : ''}
53
+ ${hasHelmet ? 'app.use(helmet());' : ''}
54
+ ${hasMorgan ? "app.use(morgan('dev'));" : ''}
55
+ app.use(express.json());
56
+ app.use(express.urlencoded({ extended: true }));
57
+
58
+ app.use('/api', require('./routes/index'));
59
+ ${hasSwagger ? "app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerDoc));" : ''}
60
+ ${hasSocket ? "\nio.on('connection', (socket) => {\n console.log('Cliente conectado:', socket.id);\n socket.on('disconnect', () => console.log('Cliente desconectado'));\n});" : ''}
61
+
62
+ app.get('/', (req, res) => res.json({ status: 'ok', message: 'API funcionando βœ…' }));
63
+
64
+ app.use((err, req, res, next) => {
65
+ console.error(err.stack);
66
+ res.status(500).json({ error: err.message });
67
+ });
68
+
69
+ const PORT = process.env.PORT || 3001;
70
+ const server${hasSocket ? ' = httpServer' : 'Inst = app'}.listen(PORT, () =>
71
+ console.log(\`πŸš€ Servidor en http://localhost:\${PORT}\`)
72
+ );
73
+ `;
74
+
75
+ const routes = `const router = require('express').Router();
76
+ router.get('/health', (req, res) => res.json({ status: 'healthy', timestamp: new Date() }));
77
+ module.exports = router;
78
+ `;
79
+
80
+ await fse.writeFile(path.join(bp, 'src', 'server.js'), server);
81
+ await fse.writeFile(path.join(bp, 'src', 'routes', 'index.js'), routes);
82
+ if (hasDotenv) await writeDotenv(bp, 'PORT=3001\nDB_URL=\nJWT_SECRET=change_me\nNODE_ENV=development\n');
83
+
84
+ await installNodeDeps(['express', ...deps], [], bp, 'Instalando dependencias Express');
85
+ }
86
+
87
+ // ── NESTJS ────────────────────────────────────────────────────────────────────
88
+ async function nestjs(config, projectPath) {
89
+ run('npx @nestjs/cli@latest new backend --skip-git --package-manager npm', projectPath, 'Creando proyecto NestJS');
90
+ const bp = backendPath(projectPath);
91
+ const deps = (config.backendDeps || []).filter(d => d !== 'nodemon');
92
+ if (deps.length) run(`npm install ${deps.join(' ')}`, bp, `Instalando: ${deps.join(', ')}`);
93
+ if (deps.includes('@nestjs/config')) await writeDotenv(bp, 'PORT=3001\nDB_URL=\nJWT_SECRET=change_me\n');
94
+ }
95
+
96
+ // ── FASTIFY ───────────────────────────────────────────────────────────────────
97
+ async function fastify(config, projectPath) {
98
+ const bp = backendPath(projectPath);
99
+ await fse.ensureDir(path.join(bp, 'src', 'routes'));
100
+
101
+ const pkg = {
102
+ name: 'backend', version: '1.0.0', main: 'src/server.js',
103
+ scripts: { start: 'node src/server.js', dev: 'nodemon src/server.js' },
104
+ };
105
+ await fse.writeJSON(path.join(bp, 'package.json'), pkg, { spaces: 2 });
106
+
107
+ const deps = config.backendDeps || [];
108
+ const hasDotenv = deps.includes('dotenv');
109
+
110
+ const server = `${hasDotenv ? "require('dotenv').config();\n" : ''}const fastify = require('fastify')({ logger: true });
111
+ ${deps.includes('@fastify/cors') ? "fastify.register(require('@fastify/cors'));" : ''}
112
+ ${deps.includes('@fastify/rate-limit') ? "fastify.register(require('@fastify/rate-limit'), { max: 100, timeWindow: '1 minute' });" : ''}
113
+
114
+ fastify.get('/', async () => ({ status: 'ok', message: 'API funcionando βœ…' }));
115
+ fastify.get('/api/health', async () => ({ status: 'healthy', timestamp: new Date() }));
116
+
117
+ const start = async () => {
118
+ try {
119
+ await fastify.listen({ port: process.env.PORT || 3001, host: '0.0.0.0' });
120
+ } catch (err) {
121
+ fastify.log.error(err);
122
+ process.exit(1);
123
+ }
124
+ };
125
+ start();
126
+ `;
127
+ await fse.writeFile(path.join(bp, 'src', 'server.js'), server);
128
+ if (hasDotenv) await writeDotenv(bp, 'PORT=3001\nDB_URL=\nJWT_SECRET=change_me\n');
129
+ await installNodeDeps(['fastify', ...deps], [], bp, 'Instalando dependencias Fastify');
130
+ }
131
+
132
+ // ── HONO ──────────────────────────────────────────────────────────────────────
133
+ async function hono(config, projectPath) {
134
+ const bp = backendPath(projectPath);
135
+ await fse.ensureDir(bp);
136
+
137
+ const pkg = {
138
+ name: 'backend', version: '1.0.0', main: 'src/index.js',
139
+ scripts: { start: 'node src/index.js', dev: 'nodemon src/index.js' },
140
+ };
141
+ await fse.writeJSON(path.join(bp, 'package.json'), pkg, { spaces: 2 });
142
+
143
+ const deps = config.backendDeps || [];
144
+ const server = `const { Hono } = require('hono');
145
+ const { serve } = require('@hono/node-server');
146
+
147
+ const app = new Hono();
148
+
149
+ app.get('/', (c) => c.json({ status: 'ok', message: 'API funcionando βœ…' }));
150
+ app.get('/api/health', (c) => c.json({ status: 'healthy', timestamp: new Date() }));
151
+
152
+ serve({ fetch: app.fetch, port: process.env.PORT || 3001 }, (info) =>
153
+ console.log(\`πŸš€ Servidor en http://localhost:\${info.port}\`)
154
+ );
155
+ `;
156
+ await fse.ensureDir(path.join(bp, 'src'));
157
+ await fse.writeFile(path.join(bp, 'src', 'index.js'), server);
158
+ if (deps.includes('dotenv')) await writeDotenv(bp, 'PORT=3001\nDB_URL=\nJWT_SECRET=change_me\n');
159
+ await installNodeDeps(['hono', '@hono/node-server', ...deps], ['nodemon'], bp, 'Instalando dependencias Hono');
160
+ }
161
+
162
+ // ── FASTAPI ───────────────────────────────────────────────────────────────────
163
+ async function fastapi(config, projectPath) {
164
+ const bp = backendPath(projectPath);
165
+ await fse.ensureDir(path.join(bp, 'app', 'routers'));
166
+ await fse.ensureDir(path.join(bp, 'app', 'models'));
167
+ await fse.ensureDir(path.join(bp, 'app', 'schemas'));
168
+
169
+ const deps = config.backendDeps || [];
170
+ const hasDotenv = deps.includes('python-dotenv');
171
+
172
+ const main = `${hasDotenv ? 'from dotenv import load_dotenv\nload_dotenv()\n\n' : ''}from fastapi import FastAPI
173
+ from fastapi.middleware.cors import CORSMiddleware
174
+ from app.routers import health
175
+
176
+ app = FastAPI(title="My API", version="1.0.0", docs_url="/api/docs")
177
+
178
+ app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"])
179
+ app.include_router(health.router, prefix="/api", tags=["health"])
180
+
181
+ @app.get("/")
182
+ def root():
183
+ return {"status": "ok", "message": "API funcionando βœ…"}
184
+ `;
185
+ const healthRouter = `from fastapi import APIRouter
186
+ router = APIRouter()
187
+
188
+ @router.get("/health")
189
+ def health_check():
190
+ return {"status": "healthy"}
191
+ `;
192
+ const requirements = ['fastapi', 'uvicorn[standard]', ...deps].join('\n') + '\n';
193
+
194
+ await fse.writeFile(path.join(bp, 'main.py'), main);
195
+ await fse.writeFile(path.join(bp, 'app', '__init__.py'), '');
196
+ await fse.writeFile(path.join(bp, 'app', 'routers', '__init__.py'), '');
197
+ await fse.writeFile(path.join(bp, 'app', 'routers', 'health.py'), healthRouter);
198
+ await fse.writeFile(path.join(bp, 'app', 'models', '__init__.py'), '');
199
+ await fse.writeFile(path.join(bp, 'app', 'schemas', '__init__.py'), '');
200
+ await fse.writeFile(path.join(bp, 'requirements.txt'), requirements);
201
+ if (hasDotenv) await writeDotenv(bp, 'DATABASE_URL=\nSECRET_KEY=change_me\nDEBUG=True\n');
202
+
203
+ if (isInstalled('pip')) run('pip install -r requirements.txt', bp, 'Instalando dependencias FastAPI');
204
+ else console.log(chalk.yellow('\n ⚠ pip no encontrado. Ejecuta: pip install -r requirements.txt'));
205
+ }
206
+
207
+ // ── DJANGO ────────────────────────────────────────────────────────────────────
208
+ async function django(config, projectPath) {
209
+ const bp = backendPath(projectPath);
210
+ await fse.ensureDir(bp);
211
+
212
+ const deps = config.backendDeps || [];
213
+ const requirements = ['django', 'gunicorn', ...deps].join('\n') + '\n';
214
+ await fse.writeFile(path.join(bp, 'requirements.txt'), requirements);
215
+ await fse.writeFile(path.join(bp, 'README_SETUP.md'),
216
+ `# Django Backend\n\n\`\`\`bash\npip install -r requirements.txt\ndjango-admin startproject core .\npython manage.py migrate\npython manage.py runserver\n\`\`\`\n`
217
+ );
218
+ if (isInstalled('pip')) {
219
+ run('pip install -r requirements.txt', bp, 'Instalando Django y dependencias');
220
+ run('django-admin startproject core .', bp, 'Creando proyecto Django');
221
+ } else {
222
+ console.log(chalk.yellow('\n ⚠ pip no encontrado. Instala manualmente.'));
223
+ }
224
+ }
225
+
226
+ // ── FLASK ─────────────────────────────────────────────────────────────────────
227
+ async function flask(config, projectPath) {
228
+ const bp = backendPath(projectPath);
229
+ await fse.ensureDir(path.join(bp, 'app'));
230
+
231
+ const deps = config.backendDeps || [];
232
+ const hasDotenv = deps.includes('python-dotenv');
233
+
234
+ const appInit = `from flask import Flask
235
+ from flask_cors import CORS
236
+
237
+ def create_app():
238
+ app = Flask(__name__)
239
+ CORS(app)
240
+
241
+ @app.route('/')
242
+ def index():
243
+ return {'status': 'ok', 'message': 'API funcionando βœ…'}
244
+
245
+ @app.route('/api/health')
246
+ def health():
247
+ return {'status': 'healthy'}
248
+
249
+ return app
250
+ `;
251
+ const run_py = `${hasDotenv ? 'from dotenv import load_dotenv\nload_dotenv()\n\n' : ''}from app import create_app
252
+ app = create_app()
253
+
254
+ if __name__ == '__main__':
255
+ app.run(debug=True, port=5000)
256
+ `;
257
+ const requirements = ['flask', 'flask-cors', ...deps].join('\n') + '\n';
258
+
259
+ await fse.writeFile(path.join(bp, 'app', '__init__.py'), appInit);
260
+ await fse.writeFile(path.join(bp, 'run.py'), run_py);
261
+ await fse.writeFile(path.join(bp, 'requirements.txt'), requirements);
262
+ if (hasDotenv) await writeDotenv(bp, 'FLASK_ENV=development\nSECRET_KEY=change_me\nDATABASE_URL=\n');
263
+
264
+ if (isInstalled('pip')) run('pip install -r requirements.txt', bp, 'Instalando Flask y dependencias');
265
+ else console.log(chalk.yellow('\n ⚠ pip no encontrado. Ejecuta: pip install -r requirements.txt'));
266
+ }
267
+
268
+ // ── SPRING BOOT ───────────────────────────────────────────────────────────────
269
+ async function spring(config, projectPath) {
270
+ const bp = backendPath(projectPath);
271
+ await fse.ensureDir(bp);
272
+ await fse.writeFile(path.join(bp, 'README_SETUP.md'),
273
+ `# Spring Boot Backend\n\nGenera el proyecto en: https://start.spring.io\n\nDependencias sugeridas:\n${(config.backendDeps || []).map(d => `- ${d}`).join('\n')}\n\nO usa Spring CLI:\n\`\`\`bash\nspring init --dependencies=web,data-jpa,security backend\n\`\`\`\n`
274
+ );
275
+ console.log(chalk.yellow('\n β„Ή Spring Boot: genera el proyecto en https://start.spring.io'));
276
+ console.log(chalk.gray(' Se creΓ³ un README_SETUP.md con instrucciones.\n'));
277
+ }
278
+
279
+ // ── DENO/OAK ──────────────────────────────────────────────────────────────────
280
+ async function deno(config, projectPath) {
281
+ const bp = backendPath(projectPath);
282
+ await fse.ensureDir(bp);
283
+
284
+ const server = `import { Application, Router } from "https://deno.land/x/oak/mod.ts";
285
+
286
+ const app = new Application();
287
+ const router = new Router();
288
+
289
+ router.get("/", (ctx) => { ctx.response.body = { status: "ok", message: "API funcionando βœ…" }; });
290
+ router.get("/api/health", (ctx) => { ctx.response.body = { status: "healthy" }; });
291
+
292
+ app.use(router.routes());
293
+ app.use(router.allowedMethods());
294
+
295
+ console.log("πŸš€ Servidor en http://localhost:8000");
296
+ await app.listen({ port: 8000 });
297
+ `;
298
+ await fse.writeFile(path.join(bp, 'main.ts'), server);
299
+ await fse.writeFile(path.join(bp, 'deno.json'), JSON.stringify({ tasks: { dev: 'deno run --allow-net main.ts' } }, null, 2));
300
+ console.log(chalk.green('βœ” Backend Deno/Oak generado'));
301
+ if (!isInstalled('deno')) console.log(chalk.yellow(' ⚠ Deno no instalado. Instala desde https://deno.land'));
302
+ }
303
+
304
+ // ── GO/GIN ────────────────────────────────────────────────────────────────────
305
+ async function gin(config, projectPath) {
306
+ const bp = backendPath(projectPath);
307
+ await fse.ensureDir(bp);
308
+
309
+ const main = `package main
310
+
311
+ import (
312
+ "net/http"
313
+ "github.com/gin-gonic/gin"
314
+ )
315
+
316
+ func main() {
317
+ r := gin.Default()
318
+
319
+ r.GET("/", func(c *gin.Context) {
320
+ c.JSON(http.StatusOK, gin.H{"status": "ok", "message": "API funcionando βœ…"})
321
+ })
322
+ r.GET("/api/health", func(c *gin.Context) {
323
+ c.JSON(http.StatusOK, gin.H{"status": "healthy"})
324
+ })
325
+
326
+ r.Run(":8080")
327
+ }
328
+ `;
329
+ await fse.writeFile(path.join(bp, 'main.go'), main);
330
+ await fse.writeFile(path.join(bp, 'README_SETUP.md'),
331
+ `# Go/Gin Backend\n\n\`\`\`bash\ngo mod init backend\ngo get github.com/gin-gonic/gin\ngo run main.go\n\`\`\`\n`
332
+ );
333
+ console.log(chalk.green('βœ” Backend Go/Gin generado'));
334
+ if (!isInstalled('go')) console.log(chalk.yellow(' ⚠ Go no instalado. Instala desde https://go.dev'));
335
+ }
336
+
337
+ export { generateBackend };