erp-construccion-database-v2/schemas/12-analytics-kpis-ddl.sql
Adrian Flores Cortes 8a9db6f9d1 [GAP-001,002,003] feat(ddl): Add KPIs, tool loans and depreciation tables
- 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>
2026-02-04 00:46:00 -06:00

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