--- id: EPIC-MCH-032 type: Epic title: "MCH-032: Feature Flags por Plan" code: MCH-032 status: Planificado phase: 7 priority: P1 created_at: 2026-01-10 updated_at: 2026-01-10 simco_version: "4.0.1" dependencies: blocks: [] depends_on: ["MCH-029", "MCH-018"] --- # MCH-032: Feature Flags por Plan ## Metadata - **Codigo:** MCH-032 - **Fase:** 7 - Expansion - **Prioridad:** P1 - **Estado:** Planificado - **Story Points:** 5 - **Sprint Objetivo:** Sprint 8 ## Descripcion Implementar sistema de feature flags que permite habilitar/deshabilitar funcionalidades por plan de suscripcion, tenant o usuario individual. Permite diferenciacion de ofertas, beta testing controlado y rollouts graduales. ## Objetivos 1. Definir flags globales con valores por defecto 2. Asociar flags a planes de suscripcion 3. Permitir overrides por tenant para beta testing 4. Evaluar flags en tiempo real con cache 5. SDK para frontend (React hook) ## Alcance ### Incluido - Feature flags por plan (Basic, Pro, Enterprise) - Overrides por tenant y usuario - Expiracion de overrides - Cache en Redis - Guard de NestJS para backend - Hook useFeatureFlag para React ### Excluido - A/B testing (futuro) - Percentage rollouts - Feature usage analytics ## Arquitectura ``` ┌─────────────────────────────────────┐ │ MCH-032: Feature Flags │ └─────────────────────────────────────┘ │ ┌─────────────────────────────┼─────────────────────────────┐ ▼ ▼ ▼ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ Flags │ │ Plans │ │ Overrides │ │ (global) │ │ (MCH-018) │ │ (por tenant) │ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │ │ │ └──────────────────────────┼──────────────────────────┘ ▼ ┌─────────────────────────────────────┐ │ FeatureFlagEvaluator │ │ (Redis cache + fallback DB) │ └─────────────────────────────────────┘ │ ┌────────────────────┼────────────────────┐ ▼ ▼ ▼ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │FeatureFlagGuard│ │useFeatureFlag│ │ Admin UI │ │ (Backend) │ │ (React) │ │ │ └──────────────┘ └──────────────┘ └──────────────┘ ``` ## Entregables | Entregable | Estado | Sprint | Ubicacion | |------------|--------|--------|-----------| | Schema feature_flags (DDL) | Planificado | 8 | `database/schemas/13-feature-flags.sql` | | FeatureFlagService | Planificado | 8 | `apps/backend/src/modules/feature-flags/` | | FeatureFlagEvaluator | Planificado | 8 | `apps/backend/src/modules/feature-flags/` | | FeatureFlagGuard | Planificado | 8 | `apps/backend/src/common/guards/` | | useFeatureFlag hook | Planificado | 8 | `apps/web/src/hooks/` | | Admin UI | Planificado | 8 | `apps/web/src/pages/admin/feature-flags/` | ## Dependencias ### Depende de - MCH-029 (Redis para cache de evaluaciones) - MCH-018 (Plans para asociar flags) ### Bloquea a - Ninguna --- ## Historias de Usuario ### MCH-US-109: Feature Flags por Plan **Como** administrador del sistema **Quiero** habilitar/deshabilitar features segun el plan del tenant **Para** diferenciar la oferta entre planes **Story Points:** 3 **Criterios de Aceptacion:** - [CA-109-1] Definicion de flags globales con key, descripcion, valor por defecto - [CA-109-2] Asociacion de flags a planes (Basic: false, Pro: true, Enterprise: true) - [CA-109-3] Evaluacion en tiempo real (latencia <10ms con cache) - [CA-109-4] Cache en Redis con invalidacion al cambiar flag - [CA-109-5] Hook useFeatureFlag(key) retorna boolean **Tareas:** | ID | Tarea | Tipo | SP | |----|-------|------|-----| | MCH-TT-109-01 | DDL schema feature_flags | DDL | 0.25 | | MCH-TT-109-02 | FeatureFlagService (CRUD de flags) | Backend | 0.5 | | MCH-TT-109-03 | FeatureFlagEvaluator con cache Redis | Backend | 0.5 | | MCH-TT-109-04 | FeatureFlagGuard para proteger endpoints | Backend | 0.25 | | MCH-TT-109-05 | FeatureFlagsController (API) | Backend | 0.25 | | MCH-TT-109-06 | Hook useFeatureFlag en frontend | Frontend | 0.5 | | MCH-TT-109-07 | Tests | Test | 0.5 | | MCH-TT-109-08 | Documentacion | Docs | 0.25 | --- ### MCH-US-110: Overrides por Tenant **Como** administrador del sistema **Quiero** habilitar features especificos para ciertos tenants **Para** dar acceso beta o promociones especiales **Story Points:** 2 **Criterios de Aceptacion:** - [CA-110-1] Override por tenant_id habilita feature aunque plan no lo incluya - [CA-110-2] Override por user_id para usuarios especificos - [CA-110-3] Expiracion configurable del override (fecha/hora) - [CA-110-4] UI de administracion para gestionar overrides **Tareas:** | ID | Tarea | Tipo | SP | |----|-------|------|-----| | MCH-TT-110-01 | DDL tabla flag_overrides | DDL | 0.25 | | MCH-TT-110-02 | Logica de override en evaluador | Backend | 0.5 | | MCH-TT-110-03 | UI para superadmin | Frontend | 0.5 | | MCH-TT-110-04 | Tests | Test | 0.25 | | MCH-TT-110-05 | Documentacion de uso | Docs | 0.5 | --- ## Resumen de Story Points | Historia | SP | Sprint | |----------|-----|--------| | MCH-US-109: Feature Flags por Plan | 3 | 8 | | MCH-US-110: Overrides por Tenant | 2 | 8 | | **TOTAL** | **5** | 8 | --- ## Criterios de Aceptacion de Epica - [ ] Flags evaluados correctamente por plan - [ ] Cache Redis funcional (<10ms latencia) - [ ] Overrides aplicandose correctamente - [ ] UI de administracion funcional - [ ] Hook React integrado - [ ] Cobertura de tests >80% ## Notas Tecnicas ### Schema feature_flags ```sql CREATE SCHEMA IF NOT EXISTS feature_flags; CREATE TABLE feature_flags.flags ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), key VARCHAR(100) UNIQUE NOT NULL, description TEXT, default_value BOOLEAN DEFAULT false, type VARCHAR(20) DEFAULT 'boolean', -- boolean, string, number created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); CREATE TABLE feature_flags.plan_flags ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), flag_id UUID REFERENCES feature_flags.flags(id), plan_id UUID REFERENCES billing.plans(id), value BOOLEAN NOT NULL, UNIQUE(flag_id, plan_id) ); CREATE TABLE feature_flags.flag_overrides ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), flag_id UUID REFERENCES feature_flags.flags(id), tenant_id UUID, user_id UUID, value BOOLEAN NOT NULL, expires_at TIMESTAMP WITH TIME ZONE, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), CHECK (tenant_id IS NOT NULL OR user_id IS NOT NULL) ); ``` ### Ejemplo de Flags Iniciales | Flag Key | Descripcion | Basic | Pro | Enterprise | |----------|-------------|-------|-----|------------| | ai_assistant | Asistente IA en chat | false | true | true | | advanced_reports | Reportes avanzados | false | true | true | | api_access | Acceso a API publica | false | false | true | | white_label | Sin branding MiChangarrito | false | false | true | | multi_location | Multiples ubicaciones | false | true | true | ### Uso en Backend ```typescript @UseGuards(FeatureFlagGuard) @FeatureFlag('ai_assistant') @Get('ai/suggestions') async getAISuggestions() { // Solo accesible si flag habilitado } ``` ### Uso en Frontend ```tsx function Dashboard() { const hasAI = useFeatureFlag('ai_assistant'); return (
{hasAI && }
); } ``` ## ADRs Relacionados - [ADR-0005: Feature Flags Strategy](../97-adr/ADR-0005-feature-flags.md) --- ## Especificaciones Tecnicas Detalladas (Ref: SAAS-009) Esta seccion documenta las especificaciones del modulo de feature flags segun template-saas para garantizar alineacion arquitectonica. ### Estructura de Tabla feature_flags | Campo | Tipo | Descripcion | |-------|------|-------------| | id | UUID | Identificador unico | | key | VARCHAR (unique) | Clave del flag | | name | VARCHAR | Nombre descriptivo | | description | TEXT | Descripcion del flag | | type | VARCHAR | Tipo: boolean/string/number/json | | default_value | JSONB | Valor por defecto | | is_enabled | BOOLEAN | Si el flag esta activo | | rollout_percentage | INTEGER | Porcentaje de rollout (0-100) | | targeting_rules | JSONB | Reglas de targeting avanzado | | created_at | TIMESTAMP | Fecha de creacion | | updated_at | TIMESTAMP | Fecha de actualizacion | ### Tablas de Overrides **tenant_feature_overrides** | Campo | Tipo | Descripcion | |-------|------|-------------| | id | UUID | Identificador unico | | tenant_id | UUID | Tenant afectado | | feature_key | VARCHAR | Clave del flag | | value | JSONB | Valor override | | enabled | BOOLEAN | Estado del override | | expires_at | TIMESTAMP | Fecha de expiracion | **user_feature_overrides** | Campo | Tipo | Descripcion | |-------|------|-------------| | id | UUID | Identificador unico | | user_id | UUID | Usuario afectado | | feature_key | VARCHAR | Clave del flag | | value | JSONB | Valor override | | enabled | BOOLEAN | Estado del override | | expires_at | TIMESTAMP | Fecha de expiracion | ### Endpoints API del Modulo | Metodo | Endpoint | Descripcion | |--------|----------|-------------| | GET | /features | Listar todos los flags | | GET | /features/:key | Obtener flag especifico | | POST | /features | Crear nuevo flag | | PUT | /features/:key | Actualizar flag | | DELETE | /features/:key | Eliminar flag | | GET | /features/evaluate | Evaluar todos los flags para contexto actual | | POST | /features/:key/override/tenant | Crear override para tenant | | POST | /features/:key/override/user | Crear override para usuario | | DELETE | /features/:key/override/tenant | Eliminar override de tenant | ### Tipos de Flags Soportados | Tipo | Descripcion | Ejemplo | |------|-------------|---------| | boolean | Verdadero/Falso | `true` | | string | Texto | `'dark'` | | number | Numerico | `10` | | json | Objeto JSON | `{"limit": 100}` | ### Flags Predefinidos de Referencia | Key | Tipo | Default | Descripcion | |-----|------|---------|-------------| | new_dashboard | boolean | false | Nuevo dashboard beta | | ai_assistant | boolean | false | Asistente IA | | export_v2 | boolean | false | Nueva exportacion | | max_file_size_mb | number | 10 | Limite de archivo en MB | | theme | string | 'light' | Tema por defecto | ### Logica de Evaluacion ``` 1. Usuario solicita feature 2. Buscar override de usuario 3. Si existe, usar ese valor 4. Si no, buscar override de tenant 5. Si existe, usar ese valor 6. Si no, evaluar rollout % 7. Si pasa rollout, usar default_value 8. Si no pasa, feature deshabilitada ``` ### Rollout Deterministico El rollout por porcentaje es deterministico, es decir, el mismo usuario siempre obtendra el mismo resultado: ```typescript function shouldEnableForUser(userId: string, percentage: number): boolean { const hash = crypto.createHash('md5').update(userId).digest('hex'); const value = parseInt(hash.substring(0, 8), 16) % 100; return value < percentage; } ``` ### Targeting Rules (Reglas Avanzadas) ```typescript { "rules": [ { "condition": "plan", "operator": "in", "values": ["pro", "enterprise"], "enabled": true }, { "condition": "country", "operator": "equals", "values": ["MX"], "enabled": true } ] } ``` ### Caching con Redis ```typescript // Cache key format const cacheKey = `features:${tenantId}:${userId}`; // TTL: 5 minutos await redis.setex(cacheKey, 300, JSON.stringify(flags)); ``` ### Referencia > Documentacion fuente: `/projects/template-saas/docs/01-modulos/SAAS-009-feature-flags.md` > Version: 1.0.0 --- **Ultima actualizacion:** 2026-01-13 **Autor:** Architecture Team