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
746 lines
20 KiB
Markdown
746 lines
20 KiB
Markdown
# 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)
|
|
|
|
```bash
|
|
# ═══════════════════════════════════════════════════════════════════
|
|
# 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
|
|
|
|
```typescript
|
|
// 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
|
|
|
|
```typescript
|
|
// 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
|
|
|
|
```typescript
|
|
// 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
|
|
|
|
```typescript
|
|
// 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
|
|
|
|
```typescript
|
|
// 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',
|
|
}));
|
|
```
|
|
|
|
```typescript
|
|
// 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
|
|
|
|
```typescript
|
|
// 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
|
|
|
|
```typescript
|
|
// 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
|
|
|
|
```typescript
|
|
// 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
|
|
|
|
```typescript
|
|
// 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
|
|
|
|
```typescript
|
|
// 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
|
|
|
|
```typescript
|
|
// ❌ 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
|
|
|
|
```typescript
|
|
// 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
|
|
|
|
```typescript
|
|
// 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
|
|
|
|
```typescript
|
|
// ❌ 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;
|
|
```
|
|
|
|
```typescript
|
|
// ❌ 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');
|
|
}
|
|
```
|
|
|
|
```typescript
|
|
// ❌ 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
|