New projects created: - michangarrito (marketplace mobile) - template-saas (SaaS template) - clinica-dental (dental ERP) - clinica-veterinaria (veterinary ERP) Architecture updates: - Move catalog from core/ to shared/ - Add MCP servers structure and templates - Add git management scripts - Update SUBREPOSITORIOS.md with 15 new repos - Update .gitignore for new projects Repository infrastructure: - 4 main repositories - 11 subrepositorios - Gitea remotes configured 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
8.5 KiB
8.5 KiB
Feature Flags Dinámicos
Versión: 1.0.0 Origen: projects/gamilit Estado: Producción Última actualización: 2025-12-08
Descripción
Sistema de feature flags para activación gradual de funcionalidades:
- Activar/desactivar features sin redespliegue
- Rollout gradual por porcentaje de usuarios
- Target por usuarios específicos o roles
- Condiciones personalizadas via JSONB
- Período de validez (starts_at/ends_at)
- Multi-tenant opcional
Características
| Característica | Descripción |
|---|---|
| Toggle dinámico | On/off sin redespliegue |
| Rollout gradual | 0-100% con hash consistente |
| Target users | Early access para usuarios específicos |
| Target roles | Habilitar por rol (admin, user, etc.) |
| Condiciones | Reglas personalizadas vía JSONB |
| Período | Fecha inicio/fin automático |
| Multi-tenant | Flags globales o por tenant |
| Auditoría | Tracking de quién creó/modificó |
Stack Tecnológico
backend:
framework: NestJS
orm: TypeORM
database: PostgreSQL
algoritmos:
- SHA256 hash para rollout consistente
- Determinístico por userId + featureKey
Dependencias NPM
{
"typeorm": "^0.3.x",
"@nestjs/typeorm": "^10.x"
}
Nota: No requiere librerías externas de feature flags (LaunchDarkly, Unleash, etc.)
Tablas Requeridas
| Tabla | Propósito |
|---|---|
| system_configuration.feature_flags | Configuración de flags |
Estructura del Módulo
feature-flags/
├── entities/
│ └── feature-flag.entity.ts
├── services/
│ └── feature-flags.service.ts
├── controllers/
│ └── feature-flags.controller.ts
├── dto/
│ ├── create-feature-flag.dto.ts
│ ├── update-feature-flag.dto.ts
│ └── feature-flag-query.dto.ts
├── decorators/
│ └── feature-flag.decorator.ts # @FeatureFlag('key')
└── guards/
└── feature-flag.guard.ts
Modelo de Datos
FeatureFlag
interface FeatureFlag {
id: string; // UUID
tenant_id?: string; // null = global
feature_key: string; // "new_checkout" (único)
feature_name: string; // "Nuevo Checkout v2"
description?: string;
is_enabled: boolean; // Toggle principal
rollout_percentage: number; // 0-100
target_users?: string[]; // UUIDs con early access
target_roles?: string[]; // ['admin', 'beta_tester']
target_conditions: Record<string, any>; // { min_level: 5 }
starts_at?: Date; // Fecha inicio
ends_at?: Date; // Fecha fin
metadata: Record<string, any>; // { category: 'checkout' }
created_by?: string;
updated_by?: string;
created_at: Date;
updated_at: Date;
}
Flujo de Evaluación
isEnabled(key, userId, userRoles)?
│
▼
1. Feature habilitada globalmente?
└─► NO → return false
│
▼
2. Usuario en target_users?
└─► SÍ → return true (early access)
│
▼
3. Usuario tiene target_role?
└─► SÍ → return true
│
▼
4. rollout_percentage == 100?
└─► SÍ → return true
│
▼
5. rollout_percentage == 0?
└─► SÍ → return false
│
▼
6. Hash(userId + key) < rollout_percentage?
└─► SÍ → return true
└─► NO → return false
Uso Rápido
1. Verificar feature programáticamente
import { FeatureFlagsService } from '@/modules/feature-flags/services';
// En un servicio
const result = await featureFlagsService.isEnabled(
'new_checkout', // feature key
userId, // opcional
userRoles, // opcional: ['admin', 'user']
);
if (result.enabled) {
// Usar nueva funcionalidad
} else {
// Usar funcionalidad legacy
}
2. Guard para proteger rutas
// Proteger endpoint completo
@Get('beta-feature')
@UseGuards(JwtAuthGuard, FeatureFlagGuard)
@FeatureFlag('beta_feature')
async betaFeature() {
return { message: 'Solo para usuarios con feature habilitada' };
}
3. Decorador para check inline
@Get('dashboard')
async getDashboard(
@CurrentUser() user: User,
@CheckFeature('new_dashboard') hasNewDashboard: boolean,
) {
if (hasNewDashboard) {
return this.newDashboardService.getData(user.id);
}
return this.legacyDashboardService.getData(user.id);
}
4. En frontend (API call)
// GET /api/feature-flags/check/new_checkout
const response = await api.get(`/feature-flags/check/${featureKey}`, {
params: { userId },
});
if (response.data.enabled) {
showNewCheckout();
} else {
showLegacyCheckout();
}
Rollout Gradual
El rollout usa un hash SHA256 del userId + featureKey para determinar si un usuario está en el porcentaje:
// Algoritmo de hash consistente
private hashUserId(userId: string, featureKey: string): number {
const hash = createHash('sha256')
.update(`${userId}:${featureKey}`)
.digest('hex');
const hashInt = parseInt(hash.substring(0, 8), 16);
return hashInt % 101; // 0-100
}
// Verificación
const userHash = hashUserId(userId, featureKey);
const isInRollout = userHash < rollout_percentage;
Beneficios:
- Mismo usuario siempre obtiene mismo resultado
- Distribución uniforme entre usuarios
- Diferente distribución por feature (gracias al featureKey)
Estrategias de Rollout
Canary Release
// 1. Crear flag deshabilitada
await featureFlagsService.create({
key: 'new_payment_gateway',
name: 'Nuevo Gateway de Pagos',
isEnabled: false,
rolloutPercentage: 0,
});
// 2. Habilitar para equipo interno
await featureFlagsService.update('new_payment_gateway', {
isEnabled: true,
targetUsers: ['dev-team-uuid-1', 'dev-team-uuid-2'],
});
// 3. Expandir a 5% de usuarios
await featureFlagsService.updateRollout('new_payment_gateway', 5);
// 4. Si todo OK, expandir gradualmente
await featureFlagsService.updateRollout('new_payment_gateway', 25);
await featureFlagsService.updateRollout('new_payment_gateway', 50);
await featureFlagsService.updateRollout('new_payment_gateway', 100);
Beta por Roles
await featureFlagsService.create({
key: 'advanced_analytics',
name: 'Analytics Avanzados',
isEnabled: true,
rolloutPercentage: 0, // No rollout general
targetRoles: ['admin', 'premium_user'],
});
Feature Temporal
await featureFlagsService.create({
key: 'black_friday_banner',
name: 'Banner Black Friday',
isEnabled: true,
rolloutPercentage: 100,
startsAt: new Date('2025-11-25'),
endsAt: new Date('2025-11-30'),
});
Variables de Entorno
# Feature flags
FEATURE_FLAGS_CACHE_TTL=300 # Cache de 5 minutos
FEATURE_FLAGS_DEFAULT_ENABLED=false
Endpoints Principales
| Método | Ruta | Descripción |
|---|---|---|
| GET | /admin/feature-flags | Listar todas las flags |
| GET | /admin/feature-flags/:key | Obtener flag por key |
| POST | /admin/feature-flags | Crear nueva flag |
| PUT | /admin/feature-flags/:key | Actualizar flag |
| DELETE | /admin/feature-flags/:key | Eliminar flag |
| POST | /admin/feature-flags/:key/enable | Habilitar flag |
| POST | /admin/feature-flags/:key/disable | Deshabilitar flag |
| PUT | /admin/feature-flags/:key/rollout | Actualizar porcentaje |
| POST | /feature-flags/:key/check | Verificar si está habilitada |
Casos de Uso Comunes
| Caso | Configuración |
|---|---|
| A/B Testing | Rollout 50%, metadata con grupo |
| Beta cerrada | targetUsers con UUIDs específicos |
| Kill switch | is_enabled = false cuando hay problemas |
| Feature por plan | targetRoles: ['premium', 'enterprise'] |
| Lanzamiento programado | starts_at + ends_at |
| Migración gradual | Rollout 10% → 25% → 50% → 100% |
Adaptaciones Necesarias
- Roles: Ajustar enum de roles según tu sistema
- Cache: Implementar cache si hay muchas verificaciones
- Multi-tenant: Decidir si flags son globales o por tenant
- UI Admin: Crear interfaz para gestionar flags
- Métricas: Integrar con sistema de analytics
Referencias
Mantenido por: Sistema NEXUS Proyecto origen: Gamilit Platform