--- id: ADR-0005 type: ADR title: "Feature Flags por Plan" status: Accepted decision_date: 2026-01-10 updated_at: 2026-01-10 simco_version: "4.0.1" stakeholders: - "Equipo MiChangarrito" tags: - feature-flags - plans - multi-tenant --- # 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: 1. Habilite/deshabilite features segun el plan 2. Permita overrides para beta testing 3. Sea rapido en evaluacion (no impacte rendimiento) 4. 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. ```typescript // 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 1. **Integracion:** Se integra directamente con plans y tenants 2. **Performance:** Cache Redis garantiza <10ms 3. **Control:** Logica de negocio en nuestro codigo 4. **Costo:** Sin gastos adicionales ### Negativas 1. **Desarrollo:** Tiempo de implementacion 2. **Features:** Menos features que soluciones SaaS 3. **Mantenimiento:** Responsabilidad propia --- ## Implementacion ### Modelo de Datos ```sql -- 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 ```typescript async evaluate(key: string, context: FlagContext): Promise { // 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 ```typescript @UseGuards(FeatureFlagGuard) @FeatureFlag('advanced_reports') @Get('reports/advanced') getAdvancedReports() { // Solo accesible si el flag esta habilitado } ``` ### Uso en Frontend ```tsx function Dashboard() { const canUseAI = useFeatureFlag('ai_assistant'); return (
{canUseAI && }
); } ``` --- ## 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 - [Feature Flags Best Practices](https://martinfowler.com/articles/feature-toggles.html) - [MCH-032: Feature Flags](../01-epicas/MCH-032-feature-flags.md) --- **Fecha decision:** 2026-01-10 **Autores:** Architecture Team