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
JavaScript
/**
* 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;