## 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>
292 lines
8.7 KiB
PL/PgSQL
292 lines
8.7 KiB
PL/PgSQL
-- =============================================================================
|
|
-- MICHANGARRITO - 21 ANALYTICS (MCH-034, MCH-035: Analytics y Reportes)
|
|
-- =============================================================================
|
|
-- Sistema de metricas, analytics y generacion de reportes
|
|
-- Almacena metricas agregadas y permite exportacion de reportes
|
|
-- =============================================================================
|
|
|
|
-- Tipos de metrica
|
|
CREATE TYPE metric_type AS ENUM (
|
|
-- Ventas
|
|
'sales_total',
|
|
'sales_count',
|
|
'sales_average_ticket',
|
|
'sales_by_category',
|
|
'sales_by_payment_method',
|
|
|
|
-- Clientes
|
|
'customers_total',
|
|
'customers_new',
|
|
'customers_active',
|
|
'customers_retention_rate',
|
|
|
|
-- Fiados
|
|
'credits_total_amount',
|
|
'credits_pending_amount',
|
|
'credits_overdue_amount',
|
|
'credits_recovery_rate',
|
|
|
|
-- Inventario
|
|
'inventory_total_value',
|
|
'inventory_low_stock_count',
|
|
'inventory_turnover_rate',
|
|
|
|
-- Productos
|
|
'products_total',
|
|
'products_top_selling',
|
|
'products_slow_moving'
|
|
);
|
|
|
|
-- Tipos de periodo
|
|
CREATE TYPE metric_period AS ENUM (
|
|
'hourly',
|
|
'daily',
|
|
'weekly',
|
|
'monthly',
|
|
'yearly'
|
|
);
|
|
|
|
-- Metricas agregadas (pre-calculadas)
|
|
CREATE TABLE IF NOT EXISTS analytics.metrics (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
|
|
|
|
-- Tipo y periodo
|
|
metric_type metric_type NOT NULL,
|
|
period_type metric_period NOT NULL,
|
|
period_start TIMESTAMPTZ NOT NULL,
|
|
period_end TIMESTAMPTZ NOT NULL,
|
|
|
|
-- Valor
|
|
value DECIMAL(15, 2) NOT NULL,
|
|
|
|
-- Metadata adicional (desglose, comparacion, etc.)
|
|
metadata JSONB DEFAULT '{}',
|
|
|
|
-- Timestamp
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
|
|
-- Constraint: una metrica por tenant/tipo/periodo
|
|
UNIQUE(tenant_id, metric_type, period_type, period_start)
|
|
);
|
|
|
|
CREATE INDEX idx_analytics_metrics_tenant ON analytics.metrics(tenant_id);
|
|
CREATE INDEX idx_analytics_metrics_type ON analytics.metrics(metric_type);
|
|
CREATE INDEX idx_analytics_metrics_period ON analytics.metrics(period_start DESC);
|
|
CREATE INDEX idx_analytics_metrics_lookup ON analytics.metrics(tenant_id, metric_type, period_type, period_start DESC);
|
|
|
|
COMMENT ON TABLE analytics.metrics IS 'Metricas pre-calculadas para dashboards';
|
|
COMMENT ON COLUMN analytics.metrics.metadata IS 'Desglose adicional: {by_category: {...}, comparison: {...}}';
|
|
|
|
-- Eventos de analytics (tracking en tiempo real)
|
|
CREATE TABLE IF NOT EXISTS analytics.events (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
|
|
|
|
-- Evento
|
|
event_type VARCHAR(100) NOT NULL,
|
|
event_name VARCHAR(255),
|
|
|
|
-- Propiedades
|
|
properties JSONB DEFAULT '{}',
|
|
|
|
-- Contexto
|
|
user_id UUID REFERENCES auth.users(id) ON DELETE SET NULL,
|
|
session_id VARCHAR(100),
|
|
|
|
-- Timestamp
|
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX idx_analytics_events_tenant ON analytics.events(tenant_id);
|
|
CREATE INDEX idx_analytics_events_type ON analytics.events(event_type);
|
|
CREATE INDEX idx_analytics_events_created ON analytics.events(created_at DESC);
|
|
|
|
-- Particionado por fecha (para performance en grandes volumenes)
|
|
-- En produccion considerar particionado real
|
|
-- Usa funcion IMMUTABLE timestamptz_to_date() definida en 02-functions.sql
|
|
CREATE INDEX idx_analytics_events_date ON analytics.events(public.timestamptz_to_date(created_at));
|
|
|
|
COMMENT ON TABLE analytics.events IS 'Eventos de tracking para analytics detallado';
|
|
|
|
-- Tipos de reporte
|
|
CREATE TYPE report_type AS ENUM (
|
|
-- Ventas
|
|
'sales_summary',
|
|
'sales_detail',
|
|
'sales_by_product',
|
|
'sales_by_category',
|
|
'sales_by_period',
|
|
|
|
-- Inventario
|
|
'inventory_status',
|
|
'inventory_movements',
|
|
'inventory_valuation',
|
|
|
|
-- Clientes
|
|
'customers_list',
|
|
'customers_top',
|
|
'customers_inactive',
|
|
|
|
-- Fiados
|
|
'credits_pending',
|
|
'credits_overdue',
|
|
'credits_history',
|
|
|
|
-- Financiero
|
|
'cash_flow',
|
|
'profit_loss',
|
|
|
|
-- Operativo
|
|
'corte_caja'
|
|
);
|
|
|
|
-- Formato de exportacion
|
|
CREATE TYPE report_format AS ENUM (
|
|
'pdf',
|
|
'xlsx',
|
|
'csv',
|
|
'json'
|
|
);
|
|
|
|
-- Estado del reporte
|
|
CREATE TYPE report_status AS ENUM (
|
|
'pending',
|
|
'processing',
|
|
'completed',
|
|
'failed',
|
|
'expired'
|
|
);
|
|
|
|
-- Reportes generados
|
|
CREATE TABLE IF NOT EXISTS analytics.reports (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
|
|
|
|
-- Tipo y nombre
|
|
report_type report_type NOT NULL,
|
|
name VARCHAR(255) NOT NULL,
|
|
|
|
-- Parametros del reporte
|
|
parameters JSONB DEFAULT '{}',
|
|
-- Ejemplo: {date_from, date_to, category_id, include_details}
|
|
|
|
-- Resultado
|
|
status report_status DEFAULT 'pending',
|
|
file_format report_format,
|
|
file_url TEXT, -- URL firmada de storage
|
|
file_size INTEGER, -- bytes
|
|
|
|
-- Errores
|
|
error_message TEXT,
|
|
|
|
-- Solicitante
|
|
requested_by UUID REFERENCES auth.users(id) ON DELETE SET NULL,
|
|
|
|
-- Timestamps
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
started_at TIMESTAMPTZ,
|
|
completed_at TIMESTAMPTZ,
|
|
expires_at TIMESTAMPTZ -- Cuando expira el archivo
|
|
);
|
|
|
|
CREATE INDEX idx_analytics_reports_tenant ON analytics.reports(tenant_id);
|
|
CREATE INDEX idx_analytics_reports_status ON analytics.reports(status);
|
|
CREATE INDEX idx_analytics_reports_type ON analytics.reports(report_type);
|
|
CREATE INDEX idx_analytics_reports_created ON analytics.reports(created_at DESC);
|
|
|
|
COMMENT ON TABLE analytics.reports IS 'Reportes generados para exportacion';
|
|
COMMENT ON COLUMN analytics.reports.parameters IS 'Parametros usados para generar el reporte';
|
|
COMMENT ON COLUMN analytics.reports.expires_at IS 'Fecha de expiracion del archivo (auto-limpieza)';
|
|
|
|
-- Programacion de reportes (reportes automaticos)
|
|
CREATE TABLE IF NOT EXISTS analytics.report_schedules (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
|
|
|
|
-- Configuracion
|
|
name VARCHAR(255) NOT NULL,
|
|
report_type report_type NOT NULL,
|
|
file_format report_format DEFAULT 'pdf',
|
|
parameters JSONB DEFAULT '{}',
|
|
|
|
-- Programacion (cron-like)
|
|
schedule_type VARCHAR(20) NOT NULL, -- 'daily', 'weekly', 'monthly'
|
|
schedule_day INTEGER, -- 1-7 para weekly, 1-31 para monthly
|
|
schedule_hour INTEGER DEFAULT 8, -- Hora del dia (0-23)
|
|
timezone VARCHAR(50) DEFAULT 'America/Mexico_City',
|
|
|
|
-- Entrega
|
|
delivery_email VARCHAR(255), -- Email para enviar reporte
|
|
delivery_webhook_url TEXT, -- Webhook para notificar
|
|
|
|
-- Estado
|
|
is_active BOOLEAN DEFAULT true,
|
|
last_run_at TIMESTAMPTZ,
|
|
next_run_at TIMESTAMPTZ,
|
|
|
|
-- Timestamps
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX idx_analytics_schedules_tenant ON analytics.report_schedules(tenant_id);
|
|
CREATE INDEX idx_analytics_schedules_next_run ON analytics.report_schedules(next_run_at) WHERE is_active = true;
|
|
|
|
CREATE TRIGGER update_analytics_report_schedules_updated_at
|
|
BEFORE UPDATE ON analytics.report_schedules
|
|
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
|
|
|
|
COMMENT ON TABLE analytics.report_schedules IS 'Programacion de reportes automaticos';
|
|
|
|
-- =============================================================================
|
|
-- FUNCIONES DE ANALYTICS
|
|
-- =============================================================================
|
|
|
|
-- Funcion para obtener metricas de un periodo
|
|
CREATE OR REPLACE FUNCTION analytics.get_metrics(
|
|
p_tenant_id UUID,
|
|
p_metric_type metric_type,
|
|
p_period_type metric_period,
|
|
p_start_date TIMESTAMPTZ,
|
|
p_end_date TIMESTAMPTZ
|
|
) RETURNS TABLE (
|
|
period_start TIMESTAMPTZ,
|
|
value DECIMAL(15, 2),
|
|
metadata JSONB
|
|
) AS $$
|
|
BEGIN
|
|
RETURN QUERY
|
|
SELECT m.period_start, m.value, m.metadata
|
|
FROM analytics.metrics m
|
|
WHERE m.tenant_id = p_tenant_id
|
|
AND m.metric_type = p_metric_type
|
|
AND m.period_type = p_period_type
|
|
AND m.period_start >= p_start_date
|
|
AND m.period_start < p_end_date
|
|
ORDER BY m.period_start;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
COMMENT ON FUNCTION analytics.get_metrics IS 'Obtiene metricas de un periodo especifico';
|
|
|
|
-- Funcion para limpiar reportes expirados
|
|
CREATE OR REPLACE FUNCTION analytics.cleanup_expired_reports()
|
|
RETURNS INTEGER AS $$
|
|
DECLARE
|
|
v_deleted INTEGER;
|
|
BEGIN
|
|
WITH deleted AS (
|
|
DELETE FROM analytics.reports
|
|
WHERE expires_at < NOW()
|
|
AND status = 'completed'
|
|
RETURNING id
|
|
)
|
|
SELECT COUNT(*) INTO v_deleted FROM deleted;
|
|
|
|
RETURN v_deleted;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
COMMENT ON FUNCTION analytics.cleanup_expired_reports IS 'Elimina reportes expirados';
|