- Add 12-analytics-kpis-ddl.sql with kpis_config and kpis_values tables - Add tool_loans table with loan_status enum (GAP-002) - Add depreciation_schedule and depreciation_entries tables (GAP-003) - Add depreciation_method enum and calculate_monthly_depreciation function Implements 13 SP of critical gaps identified in EPIC-003. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
275 lines
10 KiB
PL/PgSQL
275 lines
10 KiB
PL/PgSQL
-- ============================================================================
|
|
-- 12-analytics-kpis-ddl.sql
|
|
-- Schema: reports (extension)
|
|
-- ERP Construccion - KPIs Configurables (GAP-001)
|
|
-- ============================================================================
|
|
-- Descripcion: Configuracion dinamica de KPIs incluyendo:
|
|
-- - Definicion de KPIs con formulas configurables
|
|
-- - Valores calculados historicos
|
|
-- - Umbrales y semaforizacion
|
|
-- ============================================================================
|
|
-- Autor: Claude-Especialista-BD
|
|
-- Fecha: 2026-02-04
|
|
-- Version: 1.0.0
|
|
-- Tarea: TASK-2026-02-03-ANALISIS-MODELADO-INTEGRAL / GAP-001
|
|
-- ============================================================================
|
|
|
|
-- Usar schema reports existente
|
|
-- CREATE SCHEMA IF NOT EXISTS reports;
|
|
|
|
-- ============================================================================
|
|
-- TABLAS
|
|
-- ============================================================================
|
|
|
|
-- ----------------------------------------------------------------------------
|
|
-- 1. Configuracion de KPIs
|
|
-- Permite definir KPIs dinamicos con formulas configurables
|
|
-- ----------------------------------------------------------------------------
|
|
CREATE TABLE IF NOT EXISTS reports.kpis_config (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL,
|
|
|
|
-- Identificacion
|
|
code VARCHAR(50) NOT NULL,
|
|
name VARCHAR(200) NOT NULL,
|
|
description TEXT,
|
|
|
|
-- Clasificacion
|
|
category VARCHAR(50) NOT NULL, -- financial, progress, quality, hse, hr, inventory, operational
|
|
module VARCHAR(50) NOT NULL, -- MAI-006, MAE-014, etc.
|
|
|
|
-- Formula de calculo
|
|
formula TEXT NOT NULL, -- SQL o expresion matematica
|
|
formula_type VARCHAR(20) NOT NULL DEFAULT 'sql', -- sql, expression, function
|
|
query_function VARCHAR(255), -- Nombre de funcion PL/pgSQL si aplica
|
|
|
|
-- Parametros de la formula
|
|
parameters_schema JSONB DEFAULT '{}',
|
|
|
|
-- Unidad y formato
|
|
unit VARCHAR(20), -- %, $, hrs, dias, etc.
|
|
decimal_places INTEGER DEFAULT 2,
|
|
format_pattern VARCHAR(50), -- Patron de formato para display
|
|
|
|
-- Umbrales de semaforizacion
|
|
target_value DECIMAL(18,4),
|
|
threshold_green DECIMAL(18,4), -- Valor >= este es verde
|
|
threshold_yellow DECIMAL(18,4), -- Valor >= este es amarillo, < es rojo
|
|
invert_colors BOOLEAN DEFAULT FALSE, -- TRUE si menor es mejor
|
|
|
|
-- Frecuencia de calculo
|
|
calculation_frequency VARCHAR(20) DEFAULT 'daily', -- realtime, hourly, daily, weekly, monthly
|
|
|
|
-- Visualizacion
|
|
display_order INTEGER DEFAULT 0,
|
|
icon VARCHAR(50),
|
|
color VARCHAR(20),
|
|
|
|
-- Estado
|
|
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
|
is_system BOOLEAN NOT NULL DEFAULT FALSE, -- TRUE = no editable por usuario
|
|
|
|
-- Metadatos
|
|
metadata JSONB,
|
|
|
|
-- Auditoria
|
|
created_by UUID,
|
|
updated_by UUID,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
deleted_at TIMESTAMPTZ,
|
|
|
|
CONSTRAINT uq_kpis_config_tenant_code UNIQUE (tenant_id, code)
|
|
);
|
|
|
|
-- ----------------------------------------------------------------------------
|
|
-- 2. Valores Calculados de KPIs
|
|
-- Almacena los valores calculados periodicamente
|
|
-- ----------------------------------------------------------------------------
|
|
CREATE TABLE IF NOT EXISTS reports.kpis_values (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL,
|
|
|
|
-- KPI
|
|
kpi_id UUID NOT NULL REFERENCES reports.kpis_config(id) ON DELETE CASCADE,
|
|
|
|
-- Periodo
|
|
period_start DATE NOT NULL,
|
|
period_end DATE NOT NULL,
|
|
period_type VARCHAR(20) NOT NULL DEFAULT 'daily', -- daily, weekly, monthly, quarterly, yearly
|
|
|
|
-- Contexto opcional
|
|
project_id UUID, -- Fraccionamiento/Obra especifica
|
|
department_id UUID, -- Departamento especifico
|
|
|
|
-- Valor calculado
|
|
value DECIMAL(18,4) NOT NULL,
|
|
previous_value DECIMAL(18,4),
|
|
|
|
-- Comparacion con objetivo
|
|
target_value DECIMAL(18,4),
|
|
variance_value DECIMAL(18,4), -- value - target_value
|
|
variance_percentage DECIMAL(8,2), -- ((value - target) / target) * 100
|
|
|
|
-- Semaforizacion calculada
|
|
status VARCHAR(10), -- green, yellow, red
|
|
is_on_target BOOLEAN,
|
|
|
|
-- Tendencia
|
|
trend_direction VARCHAR(10), -- up, down, stable
|
|
change_percentage DECIMAL(8,2),
|
|
|
|
-- Desglose
|
|
breakdown JSONB, -- Datos adicionales de calculo
|
|
|
|
-- Calculo
|
|
calculated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
calculation_duration_ms INTEGER,
|
|
calculation_error TEXT,
|
|
|
|
-- Auditoria
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
-- ============================================================================
|
|
-- INDICES
|
|
-- ============================================================================
|
|
|
|
-- KPIs Config
|
|
CREATE INDEX IF NOT EXISTS idx_kpis_config_tenant ON reports.kpis_config(tenant_id);
|
|
CREATE INDEX IF NOT EXISTS idx_kpis_config_tenant_category ON reports.kpis_config(tenant_id, category);
|
|
CREATE INDEX IF NOT EXISTS idx_kpis_config_tenant_module ON reports.kpis_config(tenant_id, module);
|
|
CREATE INDEX IF NOT EXISTS idx_kpis_config_active ON reports.kpis_config(tenant_id, is_active) WHERE is_active = TRUE;
|
|
|
|
-- KPIs Values
|
|
CREATE INDEX IF NOT EXISTS idx_kpis_values_tenant ON reports.kpis_values(tenant_id);
|
|
CREATE INDEX IF NOT EXISTS idx_kpis_values_kpi ON reports.kpis_values(kpi_id);
|
|
CREATE INDEX IF NOT EXISTS idx_kpis_values_period ON reports.kpis_values(tenant_id, period_start, period_end);
|
|
CREATE INDEX IF NOT EXISTS idx_kpis_values_kpi_period ON reports.kpis_values(kpi_id, period_start DESC);
|
|
CREATE INDEX IF NOT EXISTS idx_kpis_values_project ON reports.kpis_values(tenant_id, project_id) WHERE project_id IS NOT NULL;
|
|
CREATE INDEX IF NOT EXISTS idx_kpis_values_calculated ON reports.kpis_values(calculated_at DESC);
|
|
|
|
-- ============================================================================
|
|
-- ROW LEVEL SECURITY (RLS)
|
|
-- ============================================================================
|
|
|
|
ALTER TABLE reports.kpis_config ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE reports.kpis_values ENABLE ROW LEVEL SECURITY;
|
|
|
|
-- ============================================================================
|
|
-- TRIGGERS DE AUDITORIA
|
|
-- ============================================================================
|
|
|
|
CREATE OR REPLACE FUNCTION reports.set_kpis_updated_at()
|
|
RETURNS TRIGGER AS $$
|
|
BEGIN
|
|
NEW.updated_at = NOW();
|
|
RETURN NEW;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
DROP TRIGGER IF EXISTS trg_kpis_config_updated_at ON reports.kpis_config;
|
|
CREATE TRIGGER trg_kpis_config_updated_at
|
|
BEFORE UPDATE ON reports.kpis_config
|
|
FOR EACH ROW EXECUTE FUNCTION reports.set_kpis_updated_at();
|
|
|
|
-- ============================================================================
|
|
-- FUNCIONES AUXILIARES
|
|
-- ============================================================================
|
|
|
|
-- Funcion para calcular un KPI especifico
|
|
CREATE OR REPLACE FUNCTION reports.calculate_kpi(
|
|
p_kpi_id UUID,
|
|
p_tenant_id UUID,
|
|
p_period_start DATE,
|
|
p_period_end DATE,
|
|
p_project_id UUID DEFAULT NULL
|
|
)
|
|
RETURNS DECIMAL(18,4) AS $$
|
|
DECLARE
|
|
v_kpi RECORD;
|
|
v_result DECIMAL(18,4);
|
|
BEGIN
|
|
-- Obtener configuracion del KPI
|
|
SELECT * INTO v_kpi
|
|
FROM reports.kpis_config
|
|
WHERE id = p_kpi_id AND tenant_id = p_tenant_id AND is_active = TRUE;
|
|
|
|
IF NOT FOUND THEN
|
|
RAISE EXCEPTION 'KPI not found or inactive: %', p_kpi_id;
|
|
END IF;
|
|
|
|
-- Si es una funcion, ejecutarla
|
|
IF v_kpi.formula_type = 'function' AND v_kpi.query_function IS NOT NULL THEN
|
|
EXECUTE format('SELECT %s($1, $2, $3, $4)', v_kpi.query_function)
|
|
INTO v_result
|
|
USING p_tenant_id, p_period_start, p_period_end, p_project_id;
|
|
ELSE
|
|
-- Ejecutar formula SQL directamente (con cuidado de seguridad)
|
|
-- En produccion esto debe ser mas restrictivo
|
|
EXECUTE v_kpi.formula
|
|
INTO v_result
|
|
USING p_tenant_id, p_period_start, p_period_end, p_project_id;
|
|
END IF;
|
|
|
|
RETURN v_result;
|
|
EXCEPTION
|
|
WHEN OTHERS THEN
|
|
RAISE WARNING 'Error calculating KPI %: %', p_kpi_id, SQLERRM;
|
|
RETURN NULL;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
-- Funcion para determinar color de semaforo
|
|
CREATE OR REPLACE FUNCTION reports.get_kpi_status(
|
|
p_value DECIMAL(18,4),
|
|
p_threshold_green DECIMAL(18,4),
|
|
p_threshold_yellow DECIMAL(18,4),
|
|
p_invert_colors BOOLEAN DEFAULT FALSE
|
|
)
|
|
RETURNS VARCHAR(10) AS $$
|
|
BEGIN
|
|
IF p_threshold_green IS NULL OR p_threshold_yellow IS NULL THEN
|
|
RETURN NULL;
|
|
END IF;
|
|
|
|
IF p_invert_colors THEN
|
|
-- Menor es mejor
|
|
IF p_value <= p_threshold_green THEN
|
|
RETURN 'green';
|
|
ELSIF p_value <= p_threshold_yellow THEN
|
|
RETURN 'yellow';
|
|
ELSE
|
|
RETURN 'red';
|
|
END IF;
|
|
ELSE
|
|
-- Mayor es mejor
|
|
IF p_value >= p_threshold_green THEN
|
|
RETURN 'green';
|
|
ELSIF p_value >= p_threshold_yellow THEN
|
|
RETURN 'yellow';
|
|
ELSE
|
|
RETURN 'red';
|
|
END IF;
|
|
END IF;
|
|
END;
|
|
$$ LANGUAGE plpgsql IMMUTABLE;
|
|
|
|
-- ============================================================================
|
|
-- COMENTARIOS DE DOCUMENTACION
|
|
-- ============================================================================
|
|
|
|
COMMENT ON TABLE reports.kpis_config IS 'Configuracion de KPIs dinamicos con formulas (GAP-001)';
|
|
COMMENT ON COLUMN reports.kpis_config.formula IS 'Formula SQL o expresion para calcular el KPI';
|
|
COMMENT ON COLUMN reports.kpis_config.threshold_green IS 'Umbral para status verde';
|
|
COMMENT ON COLUMN reports.kpis_config.threshold_yellow IS 'Umbral para status amarillo';
|
|
COMMENT ON COLUMN reports.kpis_config.invert_colors IS 'TRUE si valores menores son mejores';
|
|
|
|
COMMENT ON TABLE reports.kpis_values IS 'Valores calculados historicos de KPIs (GAP-001)';
|
|
COMMENT ON COLUMN reports.kpis_values.variance_value IS 'Diferencia absoluta: value - target';
|
|
COMMENT ON COLUMN reports.kpis_values.variance_percentage IS 'Diferencia porcentual respecto al target';
|
|
|
|
-- ============================================================================
|
|
-- FIN DEL SCRIPT
|
|
-- ============================================================================
|