UNPKG

backend-mcp

Version:

Generador automático de backends con Node.js, Express, Prisma y módulos configurables. Servidor MCP compatible con npx para agentes IA. Soporta PostgreSQL, MySQL, MongoDB y SQLite.

738 lines (671 loc) 18.7 kB
/** * Ejemplo de integración del contrato frontend-backend * * Este archivo muestra cómo el frontend puede usar el contrato generado * para auto-configurarse y generar automáticamente las interfaces necesarias. */ const fs = require('fs'); const path = require('path'); /** * Clase para procesar el contrato y generar configuración frontend */ class FrontendContractProcessor { constructor(contractPath) { this.contract = this.loadContract(contractPath); this.config = { api: {}, auth: {}, entities: [], ui: {}, routing: [] }; } /** * Carga el contrato desde el archivo JSON */ loadContract(contractPath) { try { const contractContent = fs.readFileSync(contractPath, 'utf8'); return JSON.parse(contractContent); } catch (error) { throw new Error(`Error cargando contrato: ${error.message}`); } } /** * Procesa el contrato completo */ processContract() { console.log('🔄 Procesando contrato frontend-backend...'); this.processApiConfig(); this.processAuthConfig(); this.processEntities(); this.processUIConfig(); this.generateRouting(); this.generateTypeDefinitions(); console.log('✅ Contrato procesado exitosamente'); return this.config; } /** * Procesa configuración de API */ processApiConfig() { const api = this.contract.api; this.config.api = { baseURL: api.baseUrl, timeout: 10000, headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' }, endpoints: { auth: { login: `${api.prefix}/auth/login`, register: `${api.prefix}/auth/register`, refresh: `${api.prefix}/auth/refresh`, logout: `${api.prefix}/auth/logout` } }, interceptors: { request: [ 'addAuthToken', 'addRequestId' ], response: [ 'handleErrors', 'refreshTokenOnExpiry' ] } }; console.log('📡 Configuración de API procesada'); } /** * Procesa configuración de autenticación */ processAuthConfig() { const auth = this.contract.auth; this.config.auth = { strategy: 'jwt', tokenStorage: 'localStorage', tokenKey: 'accessToken', refreshTokenKey: 'refreshToken', roles: auth.roles.map(role => ({ name: role.name, permissions: role.permissions, description: role.description })), guards: { authenticated: 'RequireAuth', roles: 'RequireRoles', permissions: 'RequirePermissions' }, redirects: { login: '/login', logout: '/login', unauthorized: '/unauthorized', forbidden: '/forbidden' } }; console.log('🔐 Configuración de autenticación procesada'); } /** * Procesa entidades CRUD */ processEntities() { const entities = this.contract.entities; this.config.entities = entities.map(entity => { const processedEntity = { name: entity.name, apiPath: entity.apiPath, primaryKey: entity.primaryKey, fields: this.processEntityFields(entity.fields), endpoints: this.processEntityEndpoints(entity.endpoints), ui: this.processEntityUI(entity.ui), validation: this.processEntityValidation(entity.validation) }; // Agregar endpoints a configuración de API this.config.api.endpoints[entity.name.toLowerCase()] = { list: `${this.contract.api.prefix}${entity.apiPath}`, show: `${this.contract.api.prefix}${entity.apiPath}/:id`, create: `${this.contract.api.prefix}${entity.apiPath}`, update: `${this.contract.api.prefix}${entity.apiPath}/:id`, delete: `${this.contract.api.prefix}${entity.apiPath}/:id` }; return processedEntity; }); console.log(`📊 ${entities.length} entidades procesadas`); } /** * Procesa campos de una entidad */ processEntityFields(fields) { return fields.map(field => ({ name: field.name, type: this.mapFieldType(field.type), required: field.required || false, validation: field.validation, ui: { component: this.getFieldComponent(field.type), label: this.generateFieldLabel(field.name), placeholder: this.generateFieldPlaceholder(field.name, field.type), hidden: ['id', 'createdAt', 'updatedAt'].includes(field.name) } })); } /** * Mapea tipos de campo backend a frontend */ mapFieldType(backendType) { const typeMap = { 'uuid': 'string', 'string': 'string', 'text': 'string', 'int': 'number', 'float': 'number', 'boolean': 'boolean', 'timestamp': 'Date', 'enum': 'string' }; return typeMap[backendType] || 'string'; } /** * Determina el componente UI para un tipo de campo */ getFieldComponent(fieldType) { const componentMap = { 'string': 'TextInput', 'text': 'TextArea', 'number': 'NumberInput', 'boolean': 'Checkbox', 'enum': 'Select', 'Date': 'DatePicker', 'email': 'EmailInput', 'password': 'PasswordInput' }; return componentMap[fieldType] || 'TextInput'; } /** * Genera label para un campo */ generateFieldLabel(fieldName) { return fieldName .replace(/([A-Z])/g, ' $1') .replace(/^./, str => str.toUpperCase()) .trim(); } /** * Genera placeholder para un campo */ generateFieldPlaceholder(fieldName, fieldType) { const placeholders = { 'name': 'Ingrese el nombre', 'email': 'ejemplo@correo.com', 'description': 'Ingrese una descripción', 'status': 'Seleccione un estado' }; return placeholders[fieldName] || `Ingrese ${this.generateFieldLabel(fieldName).toLowerCase()}`; } /** * Procesa endpoints de una entidad */ processEntityEndpoints(endpoints) { const processedEndpoints = {}; Object.keys(endpoints).forEach(key => { const endpoint = endpoints[key]; processedEndpoints[key] = { method: endpoint.method, path: endpoint.path, permissions: endpoint.permissions || [], validation: endpoint.validation, features: { pagination: endpoint.pagination || false, search: endpoint.search || [], filters: endpoint.filters || [], sorting: endpoint.sortable || [] } }; }); return processedEndpoints; } /** * Procesa configuración UI de una entidad */ processEntityUI(entityUI) { if (!entityUI) return this.generateDefaultUI(); return { listView: { columns: entityUI.listView?.columns || [], searchable: entityUI.listView?.searchable || [], filterable: entityUI.listView?.filterable || [], sortable: entityUI.listView?.sortable || [], defaultSort: entityUI.listView?.defaultSort || 'createdAt:desc', pagination: { enabled: true, pageSize: 10, pageSizes: [5, 10, 25, 50] } }, formView: { layout: entityUI.formView?.layout || 'sections', sections: entityUI.formView?.sections || [] }, detailView: { layout: 'tabs', tabs: [ { name: 'general', label: 'Información General', fields: entityUI.listView?.columns || [] } ] } }; } /** * Procesa validaciones de una entidad */ processEntityValidation(validation) { if (!validation) return {}; const processedValidation = {}; Object.keys(validation).forEach(key => { processedValidation[key] = this.parseValidationRules(validation[key]); }); return processedValidation; } /** * Parsea reglas de validación */ parseValidationRules(rules) { if (typeof rules === 'string') { const ruleArray = rules.split('|'); const parsedRules = {}; ruleArray.forEach(rule => { if (rule === 'required') { parsedRules.required = true; } else if (rule.startsWith('min:')) { parsedRules.minLength = parseInt(rule.split(':')[1]); } else if (rule.startsWith('max:')) { parsedRules.maxLength = parseInt(rule.split(':')[1]); } else if (rule === 'email') { parsedRules.pattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; } }); return parsedRules; } return rules; } /** * Procesa configuración UI general */ processUIConfig() { const frontend = this.contract.frontend; this.config.ui = { theme: { primaryColor: frontend.ui?.theme?.primaryColor || '#3b82f6', secondaryColor: frontend.ui?.theme?.secondaryColor || '#64748b', darkMode: frontend.ui?.theme?.darkMode || false }, layout: { type: frontend.ui?.layout || 'sidebar', navigation: { position: 'left', collapsible: true, items: this.generateNavigationItems() }, header: { showUserMenu: true, showNotifications: true, showSearch: true }, footer: { show: true, text: ${new Date().getFullYear()} ${this.contract.project.name}` } }, components: { table: { striped: true, hoverable: true, responsive: true, pagination: true }, form: { layout: 'vertical', showLabels: true, showValidation: true }, modal: { backdrop: true, keyboard: true, focus: true } } }; console.log('🎨 Configuración UI procesada'); } /** * Genera elementos de navegación */ generateNavigationItems() { const items = [ { label: 'Dashboard', path: '/dashboard', icon: 'dashboard', permissions: ['read'] } ]; // Agregar elementos para cada entidad this.config.entities.forEach(entity => { items.push({ label: entity.name + 's', path: `/${entity.name.toLowerCase()}s`, icon: 'table', permissions: ['read'], children: [ { label: 'Lista', path: `/${entity.name.toLowerCase()}s`, permissions: ['read'] }, { label: 'Crear', path: `/${entity.name.toLowerCase()}s/create`, permissions: ['create'] } ] }); }); items.push( { label: 'Configuración', path: '/settings', icon: 'settings', permissions: ['admin'] } ); return items; } /** * Genera configuración de routing */ generateRouting() { this.config.routing = [ { path: '/', redirect: '/dashboard' }, { path: '/login', component: 'LoginPage', meta: { requiresAuth: false } }, { path: '/dashboard', component: 'DashboardPage', meta: { requiresAuth: true } } ]; // Agregar rutas para cada entidad this.config.entities.forEach(entity => { const entityLower = entity.name.toLowerCase(); const entityPlural = entityLower + 's'; this.config.routing.push( { path: `/${entityPlural}`, component: `${entity.name}ListPage`, meta: { requiresAuth: true, permissions: ['read'] } }, { path: `/${entityPlural}/create`, component: `${entity.name}CreatePage`, meta: { requiresAuth: true, permissions: ['create'] } }, { path: `/${entityPlural}/:id`, component: `${entity.name}DetailPage`, meta: { requiresAuth: true, permissions: ['read'] } }, { path: `/${entityPlural}/:id/edit`, component: `${entity.name}EditPage`, meta: { requiresAuth: true, permissions: ['update'] } } ); }); console.log('🛣️ Configuración de routing generada'); } /** * Genera definiciones de tipos TypeScript */ generateTypeDefinitions() { const types = { // Tipos de autenticación User: { id: 'string', email: 'string', name: 'string', role: this.config.auth.roles.map(r => r.name).join(' | '), createdAt: 'Date', updatedAt: 'Date' }, LoginRequest: { email: 'string', password: 'string', rememberMe: 'boolean?' }, LoginResponse: { success: 'boolean', message: 'string', data: { user: 'User', tokens: { accessToken: 'string', refreshToken: 'string', expiresIn: 'number' } } }, // Tipos de API ApiResponse: { success: 'boolean', message: 'string', data: 'any', errors: 'string[]?' }, PaginatedResponse: { data: 'any[]', pagination: { page: 'number', limit: 'number', total: 'number', pages: 'number' } } }; // Agregar tipos para cada entidad this.config.entities.forEach(entity => { const entityType = {}; entity.fields.forEach(field => { entityType[field.name] = field.required ? field.type : `${field.type}?`; }); types[entity.name] = entityType; // Tipos para requests types[`Create${entity.name}Request`] = { ...entityType }; delete types[`Create${entity.name}Request`].id; delete types[`Create${entity.name}Request`].createdAt; delete types[`Create${entity.name}Request`].updatedAt; types[`Update${entity.name}Request`] = { ...Object.keys(entityType).reduce((acc, key) => { if (!['id', 'createdAt', 'updatedAt'].includes(key)) { acc[key] = entityType[key].replace('?', '') + '?'; } return acc; }, {}) }; }); this.config.types = types; console.log('📝 Definiciones de tipos generadas'); } /** * Genera UI por defecto */ generateDefaultUI() { return { listView: { columns: ['name', 'status'], searchable: ['name'], filterable: ['status'], sortable: ['name', 'createdAt'], defaultSort: 'createdAt:desc' }, formView: { layout: 'sections', sections: [ { title: 'Información General', fields: ['name', 'description', 'status'] } ] } }; } /** * Exporta la configuración procesada */ exportConfig(outputPath) { const configString = JSON.stringify(this.config, null, 2); fs.writeFileSync(outputPath, configString, 'utf8'); console.log(`📄 Configuración exportada a: ${outputPath}`); } /** * Genera archivos de configuración específicos por framework */ generateFrameworkConfig(framework = 'react') { const configs = { react: this.generateReactConfig(), vue: this.generateVueConfig(), angular: this.generateAngularConfig() }; return configs[framework] || configs.react; } /** * Genera configuración específica para React */ generateReactConfig() { return { dependencies: [ 'react', 'react-dom', 'react-router-dom', 'axios', 'react-hook-form', 'joi', '@tanstack/react-query', 'zustand', 'tailwindcss' ], structure: { 'src/': { 'components/': 'Componentes reutilizables', 'pages/': 'Páginas de la aplicación', 'hooks/': 'Custom hooks', 'services/': 'Servicios de API', 'stores/': 'Stores de Zustand', 'types/': 'Definiciones TypeScript', 'utils/': 'Utilidades', 'config/': 'Configuración' } }, scripts: { 'generate:pages': 'Generar páginas CRUD', 'generate:components': 'Generar componentes', 'generate:types': 'Generar tipos TypeScript', 'sync:backend': 'Sincronizar con backend' } }; } /** * Genera configuración específica para Vue */ generateVueConfig() { return { dependencies: [ 'vue', 'vue-router', 'axios', 'pinia', 'vee-validate', 'joi', 'tailwindcss' ], structure: { 'src/': { 'components/': 'Componentes reutilizables', 'views/': 'Vistas de la aplicación', 'composables/': 'Composables', 'services/': 'Servicios de API', 'stores/': 'Stores de Pinia', 'types/': 'Definiciones TypeScript' } } }; } /** * Genera configuración específica para Angular */ generateAngularConfig() { return { dependencies: [ '@angular/core', '@angular/router', '@angular/common', '@angular/forms', '@angular/http', 'rxjs' ], structure: { 'src/app/': { 'components/': 'Componentes', 'pages/': 'Páginas', 'services/': 'Servicios', 'guards/': 'Guards de routing', 'models/': 'Modelos TypeScript', 'interceptors/': 'Interceptors HTTP' } } }; } } // Ejemplo de uso if (require.main === module) { const contractPath = path.join(__dirname, '..', 'frontend-contract.json'); const outputPath = path.join(__dirname, 'frontend-config.json'); try { const processor = new FrontendContractProcessor(contractPath); const config = processor.processContract(); processor.exportConfig(outputPath); console.log('\n🎉 Procesamiento completado!'); console.log('📊 Resumen:'); console.log(` - Entidades: ${config.entities.length}`); console.log(` - Rutas: ${config.routing.length}`); console.log(` - Roles: ${config.auth.roles.length}`); console.log(` - Tipos: ${Object.keys(config.types).length}`); console.log(`\n📄 Configuración guardada en: ${outputPath}`); } catch (error) { console.error('❌ Error procesando contrato:', error.message); process.exit(1); } } module.exports = FrontendContractProcessor;