michangarrito/docs/97-adr/ADR-0005-feature-flags.md
rckrdmrd 2c916e75e5 [SIMCO-V4] feat: Agregar documentación SaaS, ADRs e integraciones
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>
2026-01-13 01:43:15 -06:00

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
Equipo MiChangarrito
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.

// 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

-- 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