[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>
This commit is contained in:
parent
094cbe3ffb
commit
8a9db6f9d1
@ -941,6 +941,287 @@ CREATE TRIGGER trg_update_next_maintenance
|
|||||||
AFTER INSERT OR UPDATE OR DELETE ON assets.maintenance_schedules
|
AFTER INSERT OR UPDATE OR DELETE ON assets.maintenance_schedules
|
||||||
FOR EACH ROW EXECUTE FUNCTION assets.update_next_maintenance();
|
FOR EACH ROW EXECUTE FUNCTION assets.update_next_maintenance();
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
-- GAP-002: PRESTAMOS DE HERRAMIENTAS (2026-02-04)
|
||||||
|
-- ============================================================================
|
||||||
|
|
||||||
|
-- Enum para estado de prestamo
|
||||||
|
CREATE TYPE assets.loan_status AS ENUM (
|
||||||
|
'active', -- Prestamo activo
|
||||||
|
'returned', -- Devuelto
|
||||||
|
'overdue', -- Vencido
|
||||||
|
'lost', -- Extraviado
|
||||||
|
'damaged' -- Danado
|
||||||
|
);
|
||||||
|
|
||||||
|
-- ----------------------------------------------------------------------------
|
||||||
|
-- 12. Prestamos de Herramientas
|
||||||
|
-- Registro de prestamos de herramientas entre obras o a empleados
|
||||||
|
-- ----------------------------------------------------------------------------
|
||||||
|
CREATE TABLE assets.tool_loans (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
tenant_id UUID NOT NULL,
|
||||||
|
|
||||||
|
-- Herramienta prestada
|
||||||
|
tool_id UUID NOT NULL REFERENCES assets.assets(id),
|
||||||
|
|
||||||
|
-- Quien recibe el prestamo
|
||||||
|
employee_id UUID NOT NULL,
|
||||||
|
employee_name VARCHAR(255),
|
||||||
|
|
||||||
|
-- Origen del prestamo
|
||||||
|
fraccionamiento_origen_id UUID,
|
||||||
|
fraccionamiento_origen_name VARCHAR(255),
|
||||||
|
|
||||||
|
-- Destino del prestamo
|
||||||
|
fraccionamiento_destino_id UUID,
|
||||||
|
fraccionamiento_destino_name VARCHAR(255),
|
||||||
|
|
||||||
|
-- Fechas
|
||||||
|
loan_date DATE NOT NULL,
|
||||||
|
expected_return_date DATE,
|
||||||
|
actual_return_date DATE,
|
||||||
|
|
||||||
|
-- Estado
|
||||||
|
status assets.loan_status NOT NULL DEFAULT 'active',
|
||||||
|
|
||||||
|
-- Condicion
|
||||||
|
condition_out TEXT,
|
||||||
|
condition_out_photos JSONB,
|
||||||
|
condition_in TEXT,
|
||||||
|
condition_in_photos JSONB,
|
||||||
|
|
||||||
|
-- Aprobacion
|
||||||
|
approved_by_id UUID,
|
||||||
|
approved_by_name VARCHAR(255),
|
||||||
|
approved_at TIMESTAMPTZ,
|
||||||
|
|
||||||
|
-- Devolucion
|
||||||
|
received_by_id UUID,
|
||||||
|
received_by_name VARCHAR(255),
|
||||||
|
|
||||||
|
-- Notas
|
||||||
|
notes TEXT,
|
||||||
|
metadata JSONB,
|
||||||
|
|
||||||
|
-- Auditoria
|
||||||
|
created_by UUID,
|
||||||
|
updated_by UUID,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Indices para tool_loans
|
||||||
|
CREATE INDEX idx_tool_loans_tenant ON assets.tool_loans(tenant_id);
|
||||||
|
CREATE INDEX idx_tool_loans_tool ON assets.tool_loans(tenant_id, tool_id);
|
||||||
|
CREATE INDEX idx_tool_loans_employee ON assets.tool_loans(tenant_id, employee_id);
|
||||||
|
CREATE INDEX idx_tool_loans_status ON assets.tool_loans(tenant_id, status);
|
||||||
|
CREATE INDEX idx_tool_loans_origen ON assets.tool_loans(tenant_id, fraccionamiento_origen_id);
|
||||||
|
CREATE INDEX idx_tool_loans_destino ON assets.tool_loans(tenant_id, fraccionamiento_destino_id);
|
||||||
|
CREATE INDEX idx_tool_loans_active ON assets.tool_loans(tenant_id, status) WHERE status = 'active';
|
||||||
|
CREATE INDEX idx_tool_loans_overdue ON assets.tool_loans(tenant_id, expected_return_date)
|
||||||
|
WHERE status = 'active' AND expected_return_date < CURRENT_DATE;
|
||||||
|
|
||||||
|
ALTER TABLE assets.tool_loans ENABLE ROW LEVEL SECURITY;
|
||||||
|
|
||||||
|
CREATE TRIGGER trg_tool_loans_updated_at
|
||||||
|
BEFORE UPDATE ON assets.tool_loans
|
||||||
|
FOR EACH ROW EXECUTE FUNCTION assets.set_updated_at();
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
-- GAP-003: DEPRECIACION DE ACTIVOS (2026-02-04)
|
||||||
|
-- ============================================================================
|
||||||
|
|
||||||
|
-- Enum para metodo de depreciacion
|
||||||
|
CREATE TYPE assets.depreciation_method AS ENUM (
|
||||||
|
'straight_line', -- Linea recta
|
||||||
|
'declining_balance', -- Saldo decreciente
|
||||||
|
'double_declining', -- Doble saldo decreciente
|
||||||
|
'sum_of_years', -- Suma de digitos de los anos
|
||||||
|
'units_of_production' -- Unidades de produccion
|
||||||
|
);
|
||||||
|
|
||||||
|
-- ----------------------------------------------------------------------------
|
||||||
|
-- 13. Programacion de Depreciacion
|
||||||
|
-- Configuracion de depreciacion para cada activo
|
||||||
|
-- ----------------------------------------------------------------------------
|
||||||
|
CREATE TABLE assets.depreciation_schedule (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
tenant_id UUID NOT NULL,
|
||||||
|
|
||||||
|
-- Activo
|
||||||
|
asset_id UUID NOT NULL REFERENCES assets.assets(id),
|
||||||
|
|
||||||
|
-- Tipo de activo
|
||||||
|
asset_type VARCHAR(20) NOT NULL, -- equipment, machinery, vehicle, tool
|
||||||
|
|
||||||
|
-- Metodo de depreciacion
|
||||||
|
method assets.depreciation_method NOT NULL DEFAULT 'straight_line',
|
||||||
|
|
||||||
|
-- Valores
|
||||||
|
original_value DECIMAL(18,2) NOT NULL,
|
||||||
|
salvage_value DECIMAL(18,2) DEFAULT 0,
|
||||||
|
depreciable_amount DECIMAL(18,2) GENERATED ALWAYS AS
|
||||||
|
(original_value - COALESCE(salvage_value, 0)) STORED,
|
||||||
|
|
||||||
|
-- Vida util
|
||||||
|
useful_life_months INTEGER NOT NULL,
|
||||||
|
useful_life_units INTEGER, -- Para units_of_production
|
||||||
|
|
||||||
|
-- Depreciacion mensual calculada (para straight_line)
|
||||||
|
monthly_depreciation DECIMAL(12,2) GENERATED ALWAYS AS
|
||||||
|
(CASE WHEN useful_life_months > 0
|
||||||
|
THEN (original_value - COALESCE(salvage_value, 0)) / useful_life_months
|
||||||
|
ELSE 0 END) STORED,
|
||||||
|
|
||||||
|
-- Fechas
|
||||||
|
depreciation_start_date DATE NOT NULL,
|
||||||
|
depreciation_end_date DATE,
|
||||||
|
|
||||||
|
-- Estado actual
|
||||||
|
accumulated_depreciation DECIMAL(18,2) DEFAULT 0,
|
||||||
|
current_book_value DECIMAL(18,2),
|
||||||
|
last_entry_date DATE,
|
||||||
|
|
||||||
|
-- Estado
|
||||||
|
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
||||||
|
is_fully_depreciated BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
|
|
||||||
|
-- Notas
|
||||||
|
notes TEXT,
|
||||||
|
metadata JSONB,
|
||||||
|
|
||||||
|
-- Auditoria
|
||||||
|
created_by UUID,
|
||||||
|
updated_by UUID,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||||
|
|
||||||
|
CONSTRAINT uq_depreciation_schedule_asset UNIQUE (tenant_id, asset_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- ----------------------------------------------------------------------------
|
||||||
|
-- 14. Entradas de Depreciacion
|
||||||
|
-- Registro mensual de depreciacion aplicada
|
||||||
|
-- ----------------------------------------------------------------------------
|
||||||
|
CREATE TABLE assets.depreciation_entries (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
tenant_id UUID NOT NULL,
|
||||||
|
|
||||||
|
-- Programacion
|
||||||
|
schedule_id UUID NOT NULL REFERENCES assets.depreciation_schedule(id) ON DELETE CASCADE,
|
||||||
|
|
||||||
|
-- Periodo
|
||||||
|
period_date DATE NOT NULL, -- Primer dia del mes
|
||||||
|
fiscal_year INTEGER NOT NULL,
|
||||||
|
fiscal_month INTEGER NOT NULL,
|
||||||
|
|
||||||
|
-- Valores
|
||||||
|
depreciation_amount DECIMAL(12,2) NOT NULL,
|
||||||
|
accumulated_depreciation DECIMAL(18,2) NOT NULL,
|
||||||
|
book_value DECIMAL(18,2) NOT NULL,
|
||||||
|
|
||||||
|
-- Para units_of_production
|
||||||
|
units_used INTEGER,
|
||||||
|
|
||||||
|
-- Contabilidad
|
||||||
|
journal_entry_id UUID,
|
||||||
|
is_posted BOOLEAN DEFAULT FALSE,
|
||||||
|
posted_at TIMESTAMPTZ,
|
||||||
|
posted_by UUID,
|
||||||
|
|
||||||
|
-- Estado
|
||||||
|
status VARCHAR(20) NOT NULL DEFAULT 'draft', -- draft, posted, reversed
|
||||||
|
|
||||||
|
-- Notas
|
||||||
|
notes TEXT,
|
||||||
|
|
||||||
|
-- Auditoria
|
||||||
|
created_by UUID,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||||
|
|
||||||
|
CONSTRAINT uq_depreciation_entries_period UNIQUE (schedule_id, period_date)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Indices para depreciation_schedule
|
||||||
|
CREATE INDEX idx_depreciation_schedule_tenant ON assets.depreciation_schedule(tenant_id);
|
||||||
|
CREATE INDEX idx_depreciation_schedule_asset ON assets.depreciation_schedule(tenant_id, asset_id);
|
||||||
|
CREATE INDEX idx_depreciation_schedule_active ON assets.depreciation_schedule(tenant_id, is_active) WHERE is_active = TRUE;
|
||||||
|
CREATE INDEX idx_depreciation_schedule_not_depreciated ON assets.depreciation_schedule(tenant_id, is_fully_depreciated)
|
||||||
|
WHERE is_fully_depreciated = FALSE;
|
||||||
|
|
||||||
|
-- Indices para depreciation_entries
|
||||||
|
CREATE INDEX idx_depreciation_entries_tenant ON assets.depreciation_entries(tenant_id);
|
||||||
|
CREATE INDEX idx_depreciation_entries_schedule ON assets.depreciation_entries(schedule_id);
|
||||||
|
CREATE INDEX idx_depreciation_entries_period ON assets.depreciation_entries(tenant_id, period_date);
|
||||||
|
CREATE INDEX idx_depreciation_entries_fiscal ON assets.depreciation_entries(tenant_id, fiscal_year, fiscal_month);
|
||||||
|
CREATE INDEX idx_depreciation_entries_pending ON assets.depreciation_entries(tenant_id, status) WHERE status = 'draft';
|
||||||
|
|
||||||
|
ALTER TABLE assets.depreciation_schedule ENABLE ROW LEVEL SECURITY;
|
||||||
|
ALTER TABLE assets.depreciation_entries ENABLE ROW LEVEL SECURITY;
|
||||||
|
|
||||||
|
CREATE TRIGGER trg_depreciation_schedule_updated_at
|
||||||
|
BEFORE UPDATE ON assets.depreciation_schedule
|
||||||
|
FOR EACH ROW EXECUTE FUNCTION assets.set_updated_at();
|
||||||
|
|
||||||
|
CREATE TRIGGER trg_depreciation_entries_updated_at
|
||||||
|
BEFORE UPDATE ON assets.depreciation_entries
|
||||||
|
FOR EACH ROW EXECUTE FUNCTION assets.set_updated_at();
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
-- FUNCION: Calcular depreciacion mensual
|
||||||
|
-- ============================================================================
|
||||||
|
CREATE OR REPLACE FUNCTION assets.calculate_monthly_depreciation(
|
||||||
|
p_schedule_id UUID,
|
||||||
|
p_period_date DATE
|
||||||
|
)
|
||||||
|
RETURNS DECIMAL(12,2) AS $$
|
||||||
|
DECLARE
|
||||||
|
v_schedule RECORD;
|
||||||
|
v_months_elapsed INTEGER;
|
||||||
|
v_depreciation DECIMAL(12,2);
|
||||||
|
BEGIN
|
||||||
|
SELECT * INTO v_schedule
|
||||||
|
FROM assets.depreciation_schedule
|
||||||
|
WHERE id = p_schedule_id AND is_active = TRUE;
|
||||||
|
|
||||||
|
IF NOT FOUND THEN
|
||||||
|
RETURN 0;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- Calcular meses transcurridos
|
||||||
|
v_months_elapsed := (
|
||||||
|
EXTRACT(YEAR FROM p_period_date) * 12 + EXTRACT(MONTH FROM p_period_date)
|
||||||
|
) - (
|
||||||
|
EXTRACT(YEAR FROM v_schedule.depreciation_start_date) * 12 + EXTRACT(MONTH FROM v_schedule.depreciation_start_date)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Si ya esta completamente depreciado
|
||||||
|
IF v_schedule.accumulated_depreciation >= v_schedule.depreciable_amount THEN
|
||||||
|
RETURN 0;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- Calcular segun metodo
|
||||||
|
CASE v_schedule.method
|
||||||
|
WHEN 'straight_line' THEN
|
||||||
|
v_depreciation := v_schedule.monthly_depreciation;
|
||||||
|
WHEN 'declining_balance' THEN
|
||||||
|
v_depreciation := (v_schedule.original_value - v_schedule.accumulated_depreciation) *
|
||||||
|
(1.0 / v_schedule.useful_life_months) * 2;
|
||||||
|
ELSE
|
||||||
|
v_depreciation := v_schedule.monthly_depreciation;
|
||||||
|
END CASE;
|
||||||
|
|
||||||
|
-- No depreciar mas del valor depreciable restante
|
||||||
|
IF (v_schedule.accumulated_depreciation + v_depreciation) > v_schedule.depreciable_amount THEN
|
||||||
|
v_depreciation := v_schedule.depreciable_amount - v_schedule.accumulated_depreciation;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
RETURN COALESCE(v_depreciation, 0);
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
-- ============================================================================
|
-- ============================================================================
|
||||||
-- COMENTARIOS DE DOCUMENTACION
|
-- COMENTARIOS DE DOCUMENTACION
|
||||||
-- ============================================================================
|
-- ============================================================================
|
||||||
@ -958,6 +1239,9 @@ COMMENT ON TABLE assets.maintenance_history IS 'Historial de mantenimientos real
|
|||||||
COMMENT ON TABLE assets.asset_costs IS 'Costos de operacion y mantenimiento de activos';
|
COMMENT ON TABLE assets.asset_costs IS 'Costos de operacion y mantenimiento de activos';
|
||||||
COMMENT ON TABLE assets.asset_locations IS 'Historial de ubicaciones GPS de activos';
|
COMMENT ON TABLE assets.asset_locations IS 'Historial de ubicaciones GPS de activos';
|
||||||
COMMENT ON TABLE assets.fuel_logs IS 'Registro de cargas de combustible';
|
COMMENT ON TABLE assets.fuel_logs IS 'Registro de cargas de combustible';
|
||||||
|
COMMENT ON TABLE assets.tool_loans IS 'Prestamos de herramientas entre obras/empleados (GAP-002)';
|
||||||
|
COMMENT ON TABLE assets.depreciation_schedule IS 'Configuracion de depreciacion por activo (GAP-003)';
|
||||||
|
COMMENT ON TABLE assets.depreciation_entries IS 'Entradas mensuales de depreciacion (GAP-003)';
|
||||||
|
|
||||||
-- ============================================================================
|
-- ============================================================================
|
||||||
-- FIN DEL SCRIPT
|
-- FIN DEL SCRIPT
|
||||||
|
|||||||
274
schemas/12-analytics-kpis-ddl.sql
Normal file
274
schemas/12-analytics-kpis-ddl.sql
Normal file
@ -0,0 +1,274 @@
|
|||||||
|
-- ============================================================================
|
||||||
|
-- 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
|
||||||
|
-- ============================================================================
|
||||||
Loading…
Reference in New Issue
Block a user