# 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
