Nuevas Épicas (MCH-029 a MCH-033): - Infraestructura SaaS multi-tenant - Auth Social (OAuth2) - Auditoría Empresarial - Feature Flags - Onboarding Wizard Nuevas Integraciones (INT-010 a INT-014): - Email Providers (SendGrid, Mailgun, SES) - Storage Cloud (S3, GCS, Azure) - OAuth Social - Redis Cache - Webhooks Outbound Nuevos ADRs (0004 a 0011): - Notifications Realtime - Feature Flags Strategy - Storage Abstraction - Webhook Retry Strategy - Audit Log Retention - Rate Limiting - OAuth Social Implementation - Email Multi-provider Actualizados: - MASTER_INVENTORY.yml - CONTEXT-MAP.yml - HERENCIA-SIMCO.md - Mapas de documentación Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
5.1 KiB
5.1 KiB
| id | type | title | status | decision_date | updated_at | simco_version | stakeholders | tags | ||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| ADR-0005 | ADR | Feature Flags por Plan | Accepted | 2026-01-10 | 2026-01-10 | 4.0.1 |
|
|
ADR-0005: Feature Flags por Plan
Metadata
| Campo | Valor |
|---|---|
| ID | ADR-0005 |
| Estado | Accepted |
| Fecha | 2026-01-10 |
| Autor | Architecture Team |
| Supersede | - |
Contexto
MiChangarrito ofrece multiples planes de suscripcion (Basic, Pro, Enterprise) con diferentes funcionalidades. Necesitamos un sistema que:
- Habilite/deshabilite features segun el plan
- Permita overrides para beta testing
- Sea rapido en evaluacion (no impacte rendimiento)
- Funcione tanto en backend como frontend
Decision
Implementamos un sistema de feature flags propio con cache en Redis y evaluacion en tiempo real.
Los flags se definen globalmente, se asocian a planes, y pueden tener overrides por tenant o usuario. La evaluacion usa cache Redis para latencia <10ms.
// Evaluacion de flag
const hasFeature = await featureFlagService.evaluate('ai_assistant', {
tenantId,
userId,
planId,
});
Alternativas Consideradas
Opcion 1: LaunchDarkly/Flagsmith (SaaS)
- Pros:
- Funcionalidad completa
- Dashboard listo
- SDKs para todo
- Cons:
- Costo adicional
- Dependencia externa
- Datos salen del sistema
Opcion 2: Unleash (Open Source)
- Pros:
- Open source
- Self-hosted
- Feature completo
- Cons:
- Infraestructura adicional
- Complejidad de mantener
- Overhead para nuestro caso
Opcion 3: Implementacion propia (Elegida)
- Pros:
- Sin dependencias externas
- Integrado con nuestro sistema de planes
- Cache en Redis existente
- Control total
- Cons:
- Desarrollo inicial
- Sin UI avanzada (la construimos)
- Mantenimiento propio
Consecuencias
Positivas
- Integracion: Se integra directamente con plans y tenants
- Performance: Cache Redis garantiza <10ms
- Control: Logica de negocio en nuestro codigo
- Costo: Sin gastos adicionales
Negativas
- Desarrollo: Tiempo de implementacion
- Features: Menos features que soluciones SaaS
- Mantenimiento: Responsabilidad propia
Implementacion
Modelo de Datos
-- Flags globales
CREATE TABLE feature_flags.flags (
id UUID PRIMARY KEY,
key VARCHAR(100) UNIQUE NOT NULL,
description TEXT,
default_value BOOLEAN DEFAULT false
);
-- Flags por plan
CREATE TABLE feature_flags.plan_flags (
flag_id UUID REFERENCES flags(id),
plan_id UUID REFERENCES billing.plans(id),
value BOOLEAN NOT NULL,
PRIMARY KEY (flag_id, plan_id)
);
-- Overrides
CREATE TABLE feature_flags.flag_overrides (
flag_id UUID REFERENCES flags(id),
tenant_id UUID,
user_id UUID,
value BOOLEAN NOT NULL,
expires_at TIMESTAMP
);
Evaluador
async evaluate(key: string, context: FlagContext): Promise<boolean> {
// 1. Buscar en cache
const cacheKey = `flags:${key}:${context.tenantId}:${context.userId}`;
const cached = await this.redis.get(cacheKey);
if (cached !== null) return cached === 'true';
// 2. Buscar override de usuario
const userOverride = await this.findOverride(key, { userId: context.userId });
if (userOverride) {
await this.cache(cacheKey, userOverride.value);
return userOverride.value;
}
// 3. Buscar override de tenant
const tenantOverride = await this.findOverride(key, { tenantId: context.tenantId });
if (tenantOverride) {
await this.cache(cacheKey, tenantOverride.value);
return tenantOverride.value;
}
// 4. Evaluar por plan
const planValue = await this.getPlanValue(key, context.planId);
if (planValue !== null) {
await this.cache(cacheKey, planValue);
return planValue;
}
// 5. Valor por defecto
const flag = await this.getFlag(key);
await this.cache(cacheKey, flag.default_value);
return flag.default_value;
}
Uso en Backend
@UseGuards(FeatureFlagGuard)
@FeatureFlag('advanced_reports')
@Get('reports/advanced')
getAdvancedReports() {
// Solo accesible si el flag esta habilitado
}
Uso en Frontend
function Dashboard() {
const canUseAI = useFeatureFlag('ai_assistant');
return (
<div>
{canUseAI && <AIAssistant />}
</div>
);
}
Flags Iniciales
| Key | Descripcion | Basic | Pro | Enterprise |
|---|---|---|---|---|
| ai_assistant | Asistente IA | false | true | true |
| advanced_reports | Reportes avanzados | false | true | true |
| api_access | API publica | false | false | true |
| white_label | Sin branding | false | false | true |
| multi_location | Multiples ubicaciones | false | true | true |
| custom_integrations | Integraciones custom | false | false | true |
Referencias
Fecha decision: 2026-01-10 Autores: Architecture Team