## Documentation - Align MCH-029 to MCH-032 with template-saas modules (SAAS-008 to SAAS-015) - Create MCH-034 (Analytics) and MCH-035 (Reports) from SAAS-016/017 - Update PLAN-DESARROLLO.md with Phase 7 and 8 - Update _MAP.md indexes (35 total epics) ## Database (5 new schemas, 14 tables) - Add storage schema: buckets, files, signed_urls - Add webhooks schema: endpoints, deliveries - Add audit schema: logs, retention_policies - Add features schema: flags, tenant_flags (14 seeds) - Add analytics schema: metrics, events, reports, report_schedules - Add auth.oauth_connections for MCH-030 - Add timestamptz_to_date() IMMUTABLE function - Update EXPECTED_SCHEMAS in recreate-database.sh ## Analysis Reports - ANALISIS-INTEGRACION-TEMPLATE-SAAS-2026-01-13.md - VALIDACION-COHERENCIA-2026-01-13.md - GAP-ANALYSIS-BD-2026-01-13.md - REPORTE-EJECUCION-2026-01-13.md Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
183 lines
7.4 KiB
PL/PgSQL
183 lines
7.4 KiB
PL/PgSQL
-- =============================================================================
|
|
-- MICHANGARRITO - 20 FEATURES (MCH-032: Feature Flags por Plan)
|
|
-- =============================================================================
|
|
-- Sistema de feature flags para controlar funcionalidades por plan/tenant
|
|
-- Permite habilitar/deshabilitar features sin deployments
|
|
-- =============================================================================
|
|
|
|
-- Categorias de features
|
|
CREATE TYPE feature_category AS ENUM (
|
|
'core', -- Funcionalidades core (siempre disponibles)
|
|
'analytics', -- Reportes y analytics
|
|
'automation', -- Automatizaciones
|
|
'integrations', -- Integraciones externas
|
|
'ai', -- Funcionalidades de IA
|
|
'advanced', -- Funcionalidades avanzadas
|
|
'beta' -- Features en beta
|
|
);
|
|
|
|
-- Feature flags globales (definicion)
|
|
CREATE TABLE IF NOT EXISTS features.flags (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
|
|
-- Identificacion
|
|
key VARCHAR(100) NOT NULL UNIQUE,
|
|
name VARCHAR(255) NOT NULL,
|
|
description TEXT,
|
|
category feature_category DEFAULT 'core',
|
|
|
|
-- Valor por defecto
|
|
default_value BOOLEAN DEFAULT false,
|
|
|
|
-- Planes donde esta habilitado
|
|
plans_enabled TEXT[] DEFAULT '{}',
|
|
-- Ejemplo: {'tiendita', 'tiendita_plus'} = habilitado para estos planes
|
|
-- Vacio = usa default_value
|
|
|
|
-- Control
|
|
is_public BOOLEAN DEFAULT true, -- Si se muestra en UI de configuracion
|
|
requires_restart BOOLEAN DEFAULT false, -- Si requiere reinicio del app
|
|
|
|
-- Metadata
|
|
metadata JSONB DEFAULT '{}',
|
|
|
|
-- Timestamps
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX idx_features_flags_key ON features.flags(key);
|
|
CREATE INDEX idx_features_flags_category ON features.flags(category);
|
|
|
|
CREATE TRIGGER update_features_flags_updated_at
|
|
BEFORE UPDATE ON features.flags
|
|
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
|
|
|
|
COMMENT ON TABLE features.flags IS 'Definicion de feature flags globales';
|
|
COMMENT ON COLUMN features.flags.key IS 'Identificador unico del feature (snake_case)';
|
|
COMMENT ON COLUMN features.flags.plans_enabled IS 'Array de planes donde esta habilitado';
|
|
|
|
-- Feature flags por tenant (override)
|
|
CREATE TABLE IF NOT EXISTS features.tenant_flags (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
|
|
flag_id UUID NOT NULL REFERENCES features.flags(id) ON DELETE CASCADE,
|
|
|
|
-- Override
|
|
enabled BOOLEAN NOT NULL,
|
|
|
|
-- Razon del override
|
|
reason TEXT,
|
|
|
|
-- Control
|
|
expires_at TIMESTAMPTZ, -- Para features temporales (pruebas, demos)
|
|
|
|
-- Audit
|
|
created_by UUID REFERENCES auth.users(id) ON DELETE SET NULL,
|
|
|
|
-- Timestamps
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
|
|
|
-- Constraints
|
|
UNIQUE(tenant_id, flag_id)
|
|
);
|
|
|
|
CREATE INDEX idx_features_tenant_flags_tenant ON features.tenant_flags(tenant_id);
|
|
CREATE INDEX idx_features_tenant_flags_flag ON features.tenant_flags(flag_id);
|
|
|
|
CREATE TRIGGER update_features_tenant_flags_updated_at
|
|
BEFORE UPDATE ON features.tenant_flags
|
|
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
|
|
|
|
COMMENT ON TABLE features.tenant_flags IS 'Override de feature flags por tenant';
|
|
COMMENT ON COLUMN features.tenant_flags.reason IS 'Razon del override (promo, beta tester, etc.)';
|
|
COMMENT ON COLUMN features.tenant_flags.expires_at IS 'Fecha de expiracion del override';
|
|
|
|
-- =============================================================================
|
|
-- FUNCION: Verificar si un feature esta habilitado para un tenant
|
|
-- =============================================================================
|
|
|
|
CREATE OR REPLACE FUNCTION features.is_enabled(
|
|
p_tenant_id UUID,
|
|
p_flag_key VARCHAR(100)
|
|
) RETURNS BOOLEAN AS $$
|
|
DECLARE
|
|
v_flag RECORD;
|
|
v_override RECORD;
|
|
v_tenant_plan VARCHAR(50);
|
|
BEGIN
|
|
-- Obtener la definicion del flag
|
|
SELECT * INTO v_flag
|
|
FROM features.flags
|
|
WHERE key = p_flag_key;
|
|
|
|
IF NOT FOUND THEN
|
|
RETURN false; -- Flag no existe = deshabilitado
|
|
END IF;
|
|
|
|
-- Buscar override para el tenant
|
|
SELECT * INTO v_override
|
|
FROM features.tenant_flags tf
|
|
WHERE tf.tenant_id = p_tenant_id
|
|
AND tf.flag_id = v_flag.id
|
|
AND (tf.expires_at IS NULL OR tf.expires_at > NOW());
|
|
|
|
IF FOUND THEN
|
|
RETURN v_override.enabled; -- Usar override
|
|
END IF;
|
|
|
|
-- Obtener plan del tenant
|
|
SELECT s.plan_id INTO v_tenant_plan
|
|
FROM subscriptions.subscriptions s
|
|
WHERE s.tenant_id = p_tenant_id
|
|
AND s.status = 'active'
|
|
ORDER BY s.created_at DESC
|
|
LIMIT 1;
|
|
|
|
-- Verificar si el plan esta en la lista de planes habilitados
|
|
IF v_tenant_plan IS NOT NULL AND v_flag.plans_enabled IS NOT NULL AND array_length(v_flag.plans_enabled, 1) > 0 THEN
|
|
RETURN v_tenant_plan = ANY(v_flag.plans_enabled);
|
|
END IF;
|
|
|
|
-- Usar valor por defecto
|
|
RETURN v_flag.default_value;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
COMMENT ON FUNCTION features.is_enabled IS 'Verifica si un feature esta habilitado para un tenant';
|
|
|
|
-- =============================================================================
|
|
-- SEEDS: Feature flags iniciales
|
|
-- =============================================================================
|
|
|
|
INSERT INTO features.flags (key, name, description, category, default_value, plans_enabled) VALUES
|
|
-- Core (siempre disponibles)
|
|
('pos_basic', 'Punto de Venta Basico', 'Registro de ventas y carrito', 'core', true, '{}'),
|
|
('catalog_basic', 'Catalogo Basico', 'Gestion de productos y categorias', 'core', true, '{}'),
|
|
|
|
-- Changarrito (plan basico)
|
|
('inventory_basic', 'Control de Inventario', 'Stock y alertas de bajo inventario', 'core', true, ARRAY['changarrito', 'tiendita', 'tiendita_plus']),
|
|
('customers_basic', 'Gestion de Clientes', 'Registro y historial de clientes', 'core', true, ARRAY['changarrito', 'tiendita', 'tiendita_plus']),
|
|
|
|
-- Tiendita (plan medio)
|
|
('fiados', 'Sistema de Fiados', 'Credito a clientes con recordatorios', 'advanced', false, ARRAY['tiendita', 'tiendita_plus']),
|
|
('whatsapp_orders', 'Pedidos por WhatsApp', 'Recibir pedidos via WhatsApp', 'integrations', false, ARRAY['tiendita', 'tiendita_plus']),
|
|
('ai_assistant_basic', 'Asistente IA Basico', 'Consultas y reportes via chat', 'ai', false, ARRAY['tiendita', 'tiendita_plus']),
|
|
|
|
-- Tiendita Plus (plan premium)
|
|
('analytics_advanced', 'Analytics Avanzado', 'Dashboards y metricas detalladas', 'analytics', false, ARRAY['tiendita_plus']),
|
|
('reports_export', 'Exportacion de Reportes', 'Exportar reportes PDF/Excel/CSV', 'analytics', false, ARRAY['tiendita_plus']),
|
|
('webhooks', 'Webhooks', 'Notificaciones a sistemas externos', 'integrations', false, ARRAY['tiendita_plus']),
|
|
('multi_location', 'Multi-sucursal', 'Gestion de multiples ubicaciones', 'advanced', false, ARRAY['tiendita_plus']),
|
|
('ai_assistant_advanced', 'Asistente IA Avanzado', 'Predicciones y recomendaciones', 'ai', false, ARRAY['tiendita_plus']),
|
|
|
|
-- Beta
|
|
('delivery_tracking', 'Tracking de Entregas', 'Seguimiento de entregas en tiempo real', 'beta', false, '{}'),
|
|
('marketplace', 'Marketplace Proveedores', 'Conexion con distribuidores', 'beta', false, '{}')
|
|
ON CONFLICT (key) DO UPDATE SET
|
|
name = EXCLUDED.name,
|
|
description = EXCLUDED.description,
|
|
plans_enabled = EXCLUDED.plans_enabled,
|
|
updated_at = NOW();
|