Sistema NEXUS v3.4 migrado con: Estructura principal: - core/orchestration: Sistema SIMCO + CAPVED (27 directivas, 28 perfiles) - core/catalog: Catalogo de funcionalidades reutilizables - shared/knowledge-base: Base de conocimiento compartida - devtools/scripts: Herramientas de desarrollo - control-plane/registries: Control de servicios y CI/CD - orchestration/: Configuracion de orquestacion de agentes Proyectos incluidos (11): - gamilit (submodule -> GitHub) - trading-platform (OrbiquanTIA) - erp-suite con 5 verticales: - erp-core, construccion, vidrio-templado - mecanicas-diesel, retail, clinicas - betting-analytics - inmobiliaria-analytics - platform_marketing_content - pos-micro, erp-basico Configuracion: - .gitignore completo para Node.js/Python/Docker - gamilit como submodule (git@github.com:rckrdmrd/gamilit-workspace.git) - Sistema de puertos estandarizado (3005-3199) Generated with NEXUS v3.4 Migration System EPIC-010: Configuracion Git y Repositorios
20 KiB
20 KiB
PATRON DE CONFIGURACION
Version: 1.0.0 Fecha: 2025-12-08 Prioridad: RECOMENDADA - Seguir para consistencia Sistema: SIMCO + CAPVED
PROPOSITO
Definir patrones estandarizados para manejo de configuracion en todas las capas, incluyendo variables de entorno, archivos de configuracion, y validacion de settings.
1. PRINCIPIOS FUNDAMENTALES
╔══════════════════════════════════════════════════════════════════════╗
║ PRINCIPIOS DE CONFIGURACION ║
╠══════════════════════════════════════════════════════════════════════╣
║ ║
║ 1. NUNCA hardcodear valores sensibles ║
║ 2. SIEMPRE validar configuracion al iniciar ║
║ 3. FALLAR RAPIDO si configuracion es invalida ║
║ 4. SEPARAR configuracion por ambiente ║
║ 5. CENTRALIZAR acceso a configuracion ║
║ 6. DOCUMENTAR cada variable requerida ║
║ ║
╚══════════════════════════════════════════════════════════════════════╝
2. ESTRUCTURA DE ARCHIVOS
Estructura Recomendada
project/
├── .env.example # Template con TODAS las variables (sin valores reales)
├── .env # Valores locales (NUNCA en git)
├── .env.development # Override para desarrollo
├── .env.staging # Override para staging
├── .env.production # Override para produccion (solo en servidor)
│
├── apps/backend/
│ └── src/
│ └── shared/
│ └── config/
│ ├── configuration.ts # Configuracion principal
│ ├── config.validation.ts # Schema de validacion
│ ├── database.config.ts # Config de BD
│ ├── jwt.config.ts # Config de JWT
│ └── index.ts # Export centralizado
│
└── apps/frontend/
└── src/
└── shared/
└── config/
├── env.ts # Variables de entorno
└── constants.ts # Constantes de app
.env.example (Template)
# ═══════════════════════════════════════════════════════════════════
# CONFIGURACION DE BASE DE DATOS
# ═══════════════════════════════════════════════════════════════════
DB_HOST=localhost
DB_PORT=5432
DB_NAME=myapp_development
DB_USER=postgres
DB_PASSWORD= # REQUERIDO: Password de BD
DB_SSL=false
# ═══════════════════════════════════════════════════════════════════
# CONFIGURACION DE JWT/AUTH
# ═══════════════════════════════════════════════════════════════════
JWT_SECRET= # REQUERIDO: Minimo 32 caracteres
JWT_EXPIRATION=1d
JWT_REFRESH_EXPIRATION=7d
# ═══════════════════════════════════════════════════════════════════
# CONFIGURACION DE SERVIDOR
# ═══════════════════════════════════════════════════════════════════
NODE_ENV=development
PORT=3000
API_PREFIX=api
CORS_ORIGINS=http://localhost:5173
# ═══════════════════════════════════════════════════════════════════
# CONFIGURACION DE SERVICIOS EXTERNOS
# ═══════════════════════════════════════════════════════════════════
REDIS_URL=redis://localhost:6379
SMTP_HOST=
SMTP_PORT=587
SMTP_USER=
SMTP_PASSWORD=
# ═══════════════════════════════════════════════════════════════════
# CONFIGURACION DE LOGGING
# ═══════════════════════════════════════════════════════════════════
LOG_LEVEL=debug # trace|debug|info|warn|error
LOG_FORMAT=pretty # pretty|json
3. BACKEND (NestJS)
Schema de Validacion con Joi
// src/shared/config/config.validation.ts
import * as Joi from 'joi';
export const configValidationSchema = Joi.object({
// Database
DB_HOST: Joi.string().required(),
DB_PORT: Joi.number().default(5432),
DB_NAME: Joi.string().required(),
DB_USER: Joi.string().required(),
DB_PASSWORD: Joi.string().required(),
DB_SSL: Joi.boolean().default(false),
// JWT
JWT_SECRET: Joi.string().min(32).required(),
JWT_EXPIRATION: Joi.string().default('1d'),
JWT_REFRESH_EXPIRATION: Joi.string().default('7d'),
// Server
NODE_ENV: Joi.string()
.valid('development', 'staging', 'production', 'test')
.default('development'),
PORT: Joi.number().default(3000),
API_PREFIX: Joi.string().default('api'),
CORS_ORIGINS: Joi.string().default('*'),
// Redis (opcional)
REDIS_URL: Joi.string().uri().optional(),
// SMTP (opcional)
SMTP_HOST: Joi.string().optional(),
SMTP_PORT: Joi.number().optional(),
SMTP_USER: Joi.string().optional(),
SMTP_PASSWORD: Joi.string().optional(),
// Logging
LOG_LEVEL: Joi.string()
.valid('trace', 'debug', 'info', 'warn', 'error')
.default('info'),
LOG_FORMAT: Joi.string().valid('pretty', 'json').default('json'),
});
Configuracion Tipada
// src/shared/config/configuration.ts
export interface DatabaseConfig {
host: string;
port: number;
name: string;
user: string;
password: string;
ssl: boolean;
}
export interface JwtConfig {
secret: string;
expiration: string;
refreshExpiration: string;
}
export interface ServerConfig {
nodeEnv: string;
port: number;
apiPrefix: string;
corsOrigins: string[];
}
export interface AppConfig {
database: DatabaseConfig;
jwt: JwtConfig;
server: ServerConfig;
redis?: {
url: string;
};
smtp?: {
host: string;
port: number;
user: string;
password: string;
};
logging: {
level: string;
format: string;
};
}
export default (): AppConfig => ({
database: {
host: process.env.DB_HOST,
port: parseInt(process.env.DB_PORT, 10),
name: process.env.DB_NAME,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
ssl: process.env.DB_SSL === 'true',
},
jwt: {
secret: process.env.JWT_SECRET,
expiration: process.env.JWT_EXPIRATION,
refreshExpiration: process.env.JWT_REFRESH_EXPIRATION,
},
server: {
nodeEnv: process.env.NODE_ENV,
port: parseInt(process.env.PORT, 10),
apiPrefix: process.env.API_PREFIX,
corsOrigins: process.env.CORS_ORIGINS?.split(',') || ['*'],
},
redis: process.env.REDIS_URL
? { url: process.env.REDIS_URL }
: undefined,
smtp: process.env.SMTP_HOST
? {
host: process.env.SMTP_HOST,
port: parseInt(process.env.SMTP_PORT, 10),
user: process.env.SMTP_USER,
password: process.env.SMTP_PASSWORD,
}
: undefined,
logging: {
level: process.env.LOG_LEVEL,
format: process.env.LOG_FORMAT,
},
});
Registro en AppModule
// src/app.module.ts
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import configuration from './shared/config/configuration';
import { configValidationSchema } from './shared/config/config.validation';
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
load: [configuration],
validationSchema: configValidationSchema,
validationOptions: {
abortEarly: true, // Fallar en primer error
},
expandVariables: true, // Permitir ${VAR} en valores
}),
// ... otros modulos
],
})
export class AppModule {}
Uso en Services
// src/modules/auth/services/auth.service.ts
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { JwtConfig } from '@/shared/config/configuration';
@Injectable()
export class AuthService {
private readonly jwtConfig: JwtConfig;
constructor(private readonly configService: ConfigService) {
// Acceso tipado a configuracion
this.jwtConfig = this.configService.get<JwtConfig>('jwt');
}
async generateToken(userId: string): Promise<string> {
return this.jwtService.sign(
{ sub: userId },
{
secret: this.jwtConfig.secret,
expiresIn: this.jwtConfig.expiration,
},
);
}
}
Configuraciones Especificas
// src/shared/config/database.config.ts
import { registerAs } from '@nestjs/config';
import { TypeOrmModuleOptions } from '@nestjs/typeorm';
export default registerAs('database', (): TypeOrmModuleOptions => ({
type: 'postgres',
host: process.env.DB_HOST,
port: parseInt(process.env.DB_PORT, 10),
username: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
ssl: process.env.DB_SSL === 'true'
? { rejectUnauthorized: false }
: false,
autoLoadEntities: true,
synchronize: false, // NUNCA true en produccion
logging: process.env.NODE_ENV === 'development',
}));
// src/shared/config/jwt.config.ts
import { registerAs } from '@nestjs/config';
import { JwtModuleOptions } from '@nestjs/jwt';
export default registerAs('jwt', (): JwtModuleOptions => ({
secret: process.env.JWT_SECRET,
signOptions: {
expiresIn: process.env.JWT_EXPIRATION || '1d',
},
}));
4. FRONTEND (React/Vite)
Variables de Entorno
// src/shared/config/env.ts
// Vite expone variables con prefijo VITE_
interface EnvConfig {
apiUrl: string;
apiTimeout: number;
environment: 'development' | 'staging' | 'production';
enableMockApi: boolean;
sentryDsn?: string;
gaTrackingId?: string;
}
function validateEnv(): EnvConfig {
const apiUrl = import.meta.env.VITE_API_URL;
const environment = import.meta.env.VITE_ENVIRONMENT || 'development';
// Validar variables requeridas
if (!apiUrl) {
throw new Error('VITE_API_URL is required');
}
return {
apiUrl,
apiTimeout: parseInt(import.meta.env.VITE_API_TIMEOUT || '30000', 10),
environment: environment as EnvConfig['environment'],
enableMockApi: import.meta.env.VITE_ENABLE_MOCK_API === 'true',
sentryDsn: import.meta.env.VITE_SENTRY_DSN,
gaTrackingId: import.meta.env.VITE_GA_TRACKING_ID,
};
}
export const env = validateEnv();
// Helpers
export const isDev = env.environment === 'development';
export const isProd = env.environment === 'production';
export const isStaging = env.environment === 'staging';
Constantes de Aplicacion
// src/shared/config/constants.ts
import { env } from './env';
export const APP_CONFIG = {
// API
API_URL: env.apiUrl,
API_TIMEOUT: env.apiTimeout,
// Paginacion
DEFAULT_PAGE_SIZE: 20,
MAX_PAGE_SIZE: 100,
// UI
TOAST_DURATION: 5000,
DEBOUNCE_DELAY: 300,
// Storage keys
STORAGE_KEYS: {
AUTH_TOKEN: 'auth_token',
REFRESH_TOKEN: 'refresh_token',
USER_PREFERENCES: 'user_preferences',
THEME: 'theme',
},
// Feature flags (pueden venir de API)
FEATURES: {
DARK_MODE: true,
NOTIFICATIONS: true,
BETA_FEATURES: env.environment !== 'production',
},
} as const;
// Rutas
export const ROUTES = {
HOME: '/',
LOGIN: '/auth/login',
REGISTER: '/auth/register',
DASHBOARD: '/dashboard',
PROFILE: '/profile',
SETTINGS: '/settings',
USERS: '/users',
USER_DETAIL: '/users/:id',
} as const;
// API Endpoints
export const API_ENDPOINTS = {
AUTH: {
LOGIN: '/auth/login',
REGISTER: '/auth/register',
REFRESH: '/auth/refresh',
LOGOUT: '/auth/logout',
},
USERS: {
BASE: '/users',
BY_ID: (id: string) => `/users/${id}`,
ME: '/users/me',
},
// ... otros endpoints
} as const;
Uso en Componentes
// src/apps/web/hooks/useAuth.ts
import { APP_CONFIG } from '@/shared/config/constants';
export const useAuth = () => {
const login = async (credentials: LoginCredentials) => {
const response = await api.post(API_ENDPOINTS.AUTH.LOGIN, credentials);
// Guardar token usando key centralizada
localStorage.setItem(
APP_CONFIG.STORAGE_KEYS.AUTH_TOKEN,
response.data.accessToken,
);
};
return { login, /* ... */ };
};
5. DATABASE
Configuracion de Conexion
// src/shared/config/typeorm.config.ts
import { DataSource, DataSourceOptions } from 'typeorm';
import * as dotenv from 'dotenv';
dotenv.config();
export const dataSourceOptions: DataSourceOptions = {
type: 'postgres',
host: process.env.DB_HOST,
port: parseInt(process.env.DB_PORT, 10),
username: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
ssl: process.env.DB_SSL === 'true'
? { rejectUnauthorized: false }
: false,
// Entities
entities: ['dist/**/*.entity.js'],
// Migrations
migrations: ['dist/migrations/*.js'],
migrationsTableName: 'migrations',
// Logging
logging: process.env.DB_LOGGING === 'true',
maxQueryExecutionTime: 1000, // Log queries > 1s
// Pool
extra: {
max: parseInt(process.env.DB_POOL_SIZE || '10', 10),
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 10000,
},
};
// Para CLI de TypeORM
export default new DataSource(dataSourceOptions);
6. PATRON POR AMBIENTE
Configuracion Condicional
// src/shared/config/by-environment.ts
type Environment = 'development' | 'staging' | 'production' | 'test';
interface EnvironmentConfig {
api: {
rateLimit: number;
timeout: number;
};
cache: {
ttl: number;
enabled: boolean;
};
features: {
debugMode: boolean;
mockExternalApis: boolean;
};
}
const configs: Record<Environment, EnvironmentConfig> = {
development: {
api: {
rateLimit: 1000, // Muy alto para desarrollo
timeout: 60000,
},
cache: {
ttl: 60,
enabled: false, // Deshabilitado para desarrollo
},
features: {
debugMode: true,
mockExternalApis: true,
},
},
staging: {
api: {
rateLimit: 100,
timeout: 30000,
},
cache: {
ttl: 300,
enabled: true,
},
features: {
debugMode: true,
mockExternalApis: false,
},
},
production: {
api: {
rateLimit: 60,
timeout: 15000,
},
cache: {
ttl: 3600,
enabled: true,
},
features: {
debugMode: false,
mockExternalApis: false,
},
},
test: {
api: {
rateLimit: 10000,
timeout: 5000,
},
cache: {
ttl: 0,
enabled: false,
},
features: {
debugMode: true,
mockExternalApis: true,
},
},
};
export function getEnvironmentConfig(): EnvironmentConfig {
const env = (process.env.NODE_ENV || 'development') as Environment;
return configs[env];
}
7. SECRETOS Y SEGURIDAD
Nunca en Codigo
// ❌ INCORRECTO: Secretos hardcodeados
const JWT_SECRET = 'my-super-secret-key-12345';
const DB_PASSWORD = 'password123';
// ✅ CORRECTO: Desde variables de entorno
const JWT_SECRET = process.env.JWT_SECRET;
const DB_PASSWORD = process.env.DB_PASSWORD;
Validar Secretos Requeridos
// src/shared/config/secrets.validation.ts
const REQUIRED_SECRETS = [
'JWT_SECRET',
'DB_PASSWORD',
];
const OPTIONAL_SECRETS = [
'SMTP_PASSWORD',
'STRIPE_SECRET_KEY',
];
export function validateSecrets(): void {
const missing: string[] = [];
for (const secret of REQUIRED_SECRETS) {
if (!process.env[secret]) {
missing.push(secret);
}
}
if (missing.length > 0) {
throw new Error(
`Missing required secrets: ${missing.join(', ')}\n` +
'Please check your .env file or environment variables.',
);
}
// Validar formato de secretos
if (process.env.JWT_SECRET && process.env.JWT_SECRET.length < 32) {
throw new Error('JWT_SECRET must be at least 32 characters');
}
}
Rotacion de Secretos
// Soportar multiples secretos para rotacion
const JWT_SECRETS = (process.env.JWT_SECRETS || process.env.JWT_SECRET).split(',');
// Verificar token con cualquier secreto valido
async function verifyToken(token: string): Promise<TokenPayload> {
for (const secret of JWT_SECRETS) {
try {
return jwt.verify(token, secret.trim()) as TokenPayload;
} catch {
continue; // Probar siguiente secreto
}
}
throw new UnauthorizedException('Invalid token');
}
// Firmar siempre con el primer secreto (mas reciente)
function signToken(payload: TokenPayload): string {
return jwt.sign(payload, JWT_SECRETS[0].trim());
}
8. CHECKLIST DE CONFIGURACION
Setup inicial:
[ ] .env.example creado con TODAS las variables
[ ] .env en .gitignore
[ ] Schema de validacion implementado
[ ] App falla si configuracion invalida
[ ] Tipos definidos para configuracion
Seguridad:
[ ] Ningun secreto en codigo fuente
[ ] Ningun secreto en logs
[ ] Secretos rotables (multiples valores soportados)
[ ] Variables sensibles marcadas en documentacion
Por ambiente:
[ ] Configuracion diferenciada por ambiente
[ ] Defaults seguros para produccion
[ ] Modo debug deshabilitado en produccion
[ ] Rate limiting apropiado por ambiente
Documentacion:
[ ] Cada variable documentada en .env.example
[ ] README explica como configurar
[ ] Variables opcionales vs requeridas claras
9. ANTI-PATRONES
// ❌ ANTI-PATRON 1: Configuracion dispersa
// archivo1.ts
const API_URL = 'http://api.com';
// archivo2.ts
const apiUrl = process.env.API_URL || 'http://api.com';
// ✅ CORRECTO: Centralizado
// config/constants.ts
export const API_URL = process.env.API_URL;
// ❌ ANTI-PATRON 2: No validar
const port = process.env.PORT; // Puede ser undefined o string invalido
server.listen(port); // Error en runtime
// ✅ CORRECTO: Validar y convertir
const port = parseInt(process.env.PORT, 10);
if (isNaN(port)) {
throw new Error('PORT must be a valid number');
}
// ❌ ANTI-PATRON 3: Defaults inseguros
const DEBUG = process.env.DEBUG || true; // Debug activo por default
// ✅ CORRECTO: Defaults seguros
const DEBUG = process.env.DEBUG === 'true'; // False por default
Version: 1.0.0 | Sistema: SIMCO | Tipo: Patron de Codigo