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

228 lines
5.1 KiB
Markdown

---
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<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
```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 (
<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
- [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