-- ============================================================================= -- 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';