-- ============================================================================= -- OMNIOIL: FULL DATABASE SCHEMA (Consolidated) -- FECHA: 2026-04-10 -- DESCRIPCIÓN: Esquema maestro que integra Mantenimiento de Roles, Usuarios, -- Gestion de Agentes, Activos, Telemetría (TimescaleDB) y Seguridad MQTT. -- ============================================================================= -- EXTENSIONES CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; CREATE EXTENSION IF NOT EXISTS "pgcrypto"; -- ============================================================================= -- MÓDULO 1: SEGURIDAD Y ACCESO (DASHBOARD) -- ============================================================================= CREATE TABLE IF NOT EXISTS roles ( id SERIAL PRIMARY KEY, name VARCHAR(50) UNIQUE NOT NULL, permissions JSONB NOT NULL, created_at TIMESTAMPTZ DEFAULT NOW() ); -- Semilla de roles INSERT INTO roles (name, permissions) VALUES ('admin', '{"SYNC_WRITE": true, "DATA_READ": true, "CAMPO_READ": true, "USERS_ADMIN": true}'::jsonb), ('supervisor', '{"SYNC_WRITE": true, "DATA_READ": true, "CAMPO_READ": true, "USERS_ADMIN": false}'::jsonb), ('operador', '{"SYNC_WRITE": true, "DATA_READ": true, "CAMPO_READ": false, "USERS_ADMIN": false}'::jsonb), ('anh_reader', '{"DATA_READ": true, "CAMPO_READ": true}'::jsonb) ON CONFLICT (name) DO NOTHING; CREATE TABLE IF NOT EXISTS users ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), email VARCHAR(100) UNIQUE NOT NULL, full_name VARCHAR(200), password_hash VARCHAR(255) NOT NULL, role_id INT NOT NULL REFERENCES roles(id), is_active BOOLEAN DEFAULT true, last_login TIMESTAMPTZ, created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() ); -- Soporte para sesiones seguras (JWT Refresh) CREATE TABLE IF NOT EXISTS refresh_tokens ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, token_hash TEXT NOT NULL UNIQUE, expires_at TIMESTAMPTZ NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), revoked_at TIMESTAMPTZ ); CREATE INDEX IF NOT EXISTS idx_refresh_tokens_user_id ON refresh_tokens(user_id); -- ============================================================================= -- MÓDULO 2: JERARQUÍA OPERATIVA (EDGE) -- ============================================================================= CREATE TABLE IF NOT EXISTS edge_projects ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), name VARCHAR(100) NOT NULL UNIQUE, client VARCHAR(150) NOT NULL, description TEXT, metadata JSONB DEFAULT '{}'::jsonb, is_active BOOLEAN DEFAULT true, installer_status VARCHAR(50) DEFAULT 'NOT_STARTED', installer_url TEXT, last_health_report JSONB DEFAULT '{}'::jsonb, created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW(), CONSTRAINT check_installer_status CHECK (installer_status IN ('NOT_STARTED', 'QUEUED', 'BUILDING', 'READY', 'COMPLETED', 'FAILED')) ); CREATE TABLE IF NOT EXISTS edge_assets ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), project_id UUID NOT NULL REFERENCES edge_projects(id) ON DELETE CASCADE, code VARCHAR(50) UNIQUE NOT NULL, name VARCHAR(100) NOT NULL, asset_type VARCHAR(20) NOT NULL CHECK (asset_type IN ('POZO', 'TANQUE', 'MEDIDOR_GAS', 'AGUA', 'OTRO')), is_active BOOLEAN DEFAULT true, metadata JSONB DEFAULT '{}'::jsonb, created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() ); CREATE TABLE IF NOT EXISTS edge_devices ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), asset_id UUID NOT NULL REFERENCES edge_assets(id) ON DELETE CASCADE, name VARCHAR(100) NOT NULL, protocol VARCHAR(30) NOT NULL CHECK (protocol IN ('MODBUS_TCP', 'MODBUS_RTU', 'OPC_UA', 'MQTT', 'ETHERNET_IP', 'PROFINET')), host VARCHAR(255) NOT NULL, port INTEGER NOT NULL, protocol_config JSONB DEFAULT '{}'::jsonb, is_enabled BOOLEAN DEFAULT true, created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() ); CREATE TABLE IF NOT EXISTS edge_variables ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), device_id UUID NOT NULL REFERENCES edge_devices(id) ON DELETE CASCADE, address VARCHAR(255) NOT NULL, data_type VARCHAR(30) NOT NULL CHECK (data_type IN ('bool', 'int16', 'uint16', 'int32', 'uint32', 'float32', 'float64', 'string')), alias VARCHAR(100) NOT NULL, unit VARCHAR(30), polling_interval_ms INTEGER DEFAULT 5000, byte_order VARCHAR(10) DEFAULT 'BE' CHECK (byte_order IN ('BE', 'LE', 'BE_SWAP', 'LE_SWAP')), is_enabled BOOLEAN DEFAULT true, created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() ); -- ============================================================================= -- MÓDULO 3: TELEMETRÍA (TimescaleDB) -- ============================================================================= CREATE TABLE IF NOT EXISTS telemetry_raw ( time TIMESTAMPTZ NOT NULL, project_id UUID NOT NULL REFERENCES edge_projects(id) ON DELETE CASCADE, asset_id UUID REFERENCES edge_assets(id) ON DELETE CASCADE, data JSONB NOT NULL, PRIMARY KEY (time, project_id, asset_id) ); -- Convertir a Hipertabla (TimescaleDB Optimize) SELECT create_hypertable('telemetry_raw', 'time', if_not_exists => TRUE); CREATE TABLE IF NOT EXISTS agent_logs ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), project_id UUID NOT NULL REFERENCES edge_projects(id) ON DELETE CASCADE, agent_hostname VARCHAR(255), level VARCHAR(20), category VARCHAR(50), message TEXT, context JSONB, timestamp TIMESTAMPTZ DEFAULT NOW() ); -- ============================================================================= -- MÓDULO 4: MQTT AUTH & ACL (Mosquitto Integration) -- ============================================================================= CREATE TABLE IF NOT EXISTS mqtt_users ( username VARCHAR(100) PRIMARY KEY, password_hash VARCHAR(255) NOT NULL, is_admin BOOLEAN DEFAULT false, created_at TIMESTAMPTZ DEFAULT NOW() ); CREATE TABLE IF NOT EXISTS mqtt_acls ( id SERIAL PRIMARY KEY, username VARCHAR(100) NOT NULL REFERENCES mqtt_users(username) ON DELETE CASCADE, topic VARCHAR(500) NOT NULL, rw INT DEFAULT 1, -- 1 = read, 2 = write, 3 = readwrite created_at TIMESTAMPTZ DEFAULT NOW(), UNIQUE(username, topic) ); -- ============================================================================= -- MÓDULO 5: INFRAESTRUCTURA DE DESPLIEGUE Y EVENTOS -- ============================================================================= CREATE TABLE IF NOT EXISTS edge_deployments ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), project_id UUID NOT NULL REFERENCES edge_projects(id) ON DELETE CASCADE, image_tag VARCHAR(500) NOT NULL, image_hash VARCHAR(100), status VARCHAR(30) NOT NULL DEFAULT 'BUILDING', previous_image_tag VARCHAR(500), previous_image_hash VARCHAR(100), config_snapshot JSONB, build_metadata JSONB NOT NULL DEFAULT '{}'::jsonb, error_message TEXT, deployed_at TIMESTAMPTZ, created_at TIMESTAMPTZ DEFAULT NOW() ); CREATE TABLE IF NOT EXISTS domain_outbox ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), event_type VARCHAR(100) NOT NULL, payload JSONB NOT NULL, status VARCHAR(20) DEFAULT 'PENDING', created_at TIMESTAMPTZ DEFAULT NOW() ); -- ============================================================================= -- MÓDULO 6: MOVIMIENTOS OPERATIVOS (ANH 0651) -- ============================================================================= CREATE TABLE IF NOT EXISTS operation_movements ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), asset_id UUID NOT NULL REFERENCES edge_assets(id) ON DELETE CASCADE, movement_type VARCHAR(30) NOT NULL CHECK (movement_type IN ('VENTA', 'RECIBO', 'TRANSFERENCIA', 'QUEMA', 'CONSUMO')), start_time TIMESTAMPTZ NOT NULL, end_time TIMESTAMPTZ NOT NULL, volumen_neto DECIMAL(18, 4), details JSONB DEFAULT '{}'::jsonb, created_at TIMESTAMPTZ DEFAULT NOW(), CONSTRAINT check_movement_times CHECK (end_time >= start_time) );