From e927cafc908fbc262c528bab03467755951eadd4 Mon Sep 17 00:00:00 2001 From: Adrian Flores Cortes Date: Sun, 25 Jan 2026 05:11:02 -0600 Subject: [PATCH] [MAE-015] feat: Add assets schema DDL with 11 tables and 9 ENUMs - asset_categories: Categorias de activos - assets: Catalogo principal de activos - asset_assignments: Asignaciones a proyectos - maintenance_plans: Planes de mantenimiento preventivo - maintenance_schedules: Programacion de mantenimientos - work_orders: Ordenes de trabajo - work_order_parts: Refacciones utilizadas - maintenance_history: Historial de mantenimientos - asset_costs: Costos TCO - asset_locations: Ubicaciones GPS - fuel_logs: Registro de combustible Includes: RLS, indices, triggers, TCO calculation function Co-Authored-By: Claude Opus 4.5 --- schemas/09-assets-schema-ddl.sql | 964 +++++++++++++++++++++++++++++++ 1 file changed, 964 insertions(+) create mode 100644 schemas/09-assets-schema-ddl.sql diff --git a/schemas/09-assets-schema-ddl.sql b/schemas/09-assets-schema-ddl.sql new file mode 100644 index 0000000..bd36dce --- /dev/null +++ b/schemas/09-assets-schema-ddl.sql @@ -0,0 +1,964 @@ +-- ============================================================================ +-- 09-assets-schema-ddl.sql +-- Schema: assets +-- ERP Construccion - Modulo Activos, Maquinaria y Mantenimiento (MAE-015) +-- ============================================================================ +-- Descripcion: Gestion de activos fijos y maquinaria incluyendo: +-- - Catalogo de activos (maquinaria, equipo, vehiculos) +-- - Asignaciones a obras/proyectos +-- - Planes de mantenimiento preventivo +-- - Ordenes de trabajo de mantenimiento +-- - Historial de mantenimientos +-- - Costeo TCO (Total Cost of Ownership) +-- - Rastreo GPS (IoT) +-- ============================================================================ +-- Autor: Claude-Arquitecto-Orquestador +-- Fecha: 2026-01-25 +-- Version: 1.0.0 +-- ============================================================================ + +-- Crear schema si no existe +CREATE SCHEMA IF NOT EXISTS assets; + +-- ============================================================================ +-- ENUMS +-- ============================================================================ + +-- Tipo de activo +CREATE TYPE assets.asset_type AS ENUM ( + 'heavy_machinery', -- Maquinaria pesada (excavadoras, gruas) + 'light_equipment', -- Equipo ligero (compactadoras, mezcladoras) + 'vehicle', -- Vehiculos (camiones, camionetas) + 'tool', -- Herramientas (taladros, sierras) + 'computer', -- Equipo de computo + 'furniture', -- Mobiliario + 'other' -- Otro +); + +-- Estado del activo +CREATE TYPE assets.asset_status AS ENUM ( + 'available', -- Disponible + 'assigned', -- Asignado a obra + 'in_maintenance', -- En mantenimiento + 'in_transit', -- En transito entre obras + 'inactive', -- Inactivo + 'retired', -- Dado de baja + 'sold' -- Vendido +); + +-- Tipo de propiedad +CREATE TYPE assets.ownership_type AS ENUM ( + 'owned', -- Propio + 'leased', -- Arrendado + 'rented', -- Rentado + 'borrowed' -- Prestamo +); + +-- Tipo de mantenimiento +CREATE TYPE assets.maintenance_type AS ENUM ( + 'preventive', -- Preventivo + 'corrective', -- Correctivo + 'predictive', -- Predictivo + 'emergency' -- Emergencia +); + +-- Frecuencia de mantenimiento +CREATE TYPE assets.maintenance_frequency AS ENUM ( + 'daily', -- Diario + 'weekly', -- Semanal + 'biweekly', -- Quincenal + 'monthly', -- Mensual + 'quarterly', -- Trimestral + 'semiannual', -- Semestral + 'annual', -- Anual + 'by_hours', -- Por horas de operacion + 'by_kilometers' -- Por kilometros +); + +-- Estado de orden de trabajo +CREATE TYPE assets.work_order_status AS ENUM ( + 'draft', -- Borrador + 'scheduled', -- Programada + 'in_progress', -- En progreso + 'on_hold', -- En espera + 'completed', -- Completada + 'cancelled' -- Cancelada +); + +-- Prioridad de orden de trabajo +CREATE TYPE assets.work_order_priority AS ENUM ( + 'low', -- Baja + 'medium', -- Media + 'high', -- Alta + 'critical' -- Critica +); + +-- Tipo de costo +CREATE TYPE assets.cost_type AS ENUM ( + 'maintenance', -- Mantenimiento + 'repair', -- Reparacion + 'fuel', -- Combustible + 'insurance', -- Seguro + 'tax', -- Impuestos + 'depreciation', -- Depreciacion + 'operator', -- Operador + 'other' -- Otro +); + +-- ============================================================================ +-- TABLAS +-- ============================================================================ + +-- ---------------------------------------------------------------------------- +-- 1. Categorias de Activos +-- ---------------------------------------------------------------------------- +CREATE TABLE assets.asset_categories ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + tenant_id UUID NOT NULL, + + -- Informacion basica + code VARCHAR(20) NOT NULL, + name VARCHAR(100) NOT NULL, + description TEXT, + + -- Jerarquia + parent_id UUID REFERENCES assets.asset_categories(id), + level INTEGER NOT NULL DEFAULT 1, + + -- Configuracion de depreciacion + useful_life_years INTEGER, + depreciation_method VARCHAR(50), -- straight_line, declining_balance, units_of_production + salvage_value_percentage DECIMAL(5,2), + + -- Estado + is_active BOOLEAN NOT NULL DEFAULT TRUE, + + -- 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_asset_categories_tenant_code UNIQUE (tenant_id, code) +); + +-- ---------------------------------------------------------------------------- +-- 2. Activos (Catalogo Principal) +-- ---------------------------------------------------------------------------- +CREATE TABLE assets.assets ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + tenant_id UUID NOT NULL, + + -- Identificacion + asset_code VARCHAR(50) NOT NULL, + name VARCHAR(255) NOT NULL, + description TEXT, + + -- Clasificacion + category_id UUID REFERENCES assets.asset_categories(id), + asset_type assets.asset_type NOT NULL, + status assets.asset_status NOT NULL DEFAULT 'available', + ownership_type assets.ownership_type NOT NULL DEFAULT 'owned', + + -- Especificaciones tecnicas + brand VARCHAR(100), + model VARCHAR(100), + serial_number VARCHAR(100), + year_manufactured INTEGER, + specifications JSONB, + + -- Capacidades + capacity VARCHAR(100), + power_rating VARCHAR(50), + fuel_type VARCHAR(50), + fuel_capacity DECIMAL(10,2), + fuel_consumption_rate DECIMAL(10,2), -- litros/hora o km/litro + + -- Metricas de uso + current_hours DECIMAL(12,2) DEFAULT 0, + current_kilometers DECIMAL(12,2) DEFAULT 0, + last_usage_update TIMESTAMPTZ, + + -- Ubicacion actual + current_project_id UUID, + current_location_name VARCHAR(255), + current_latitude DECIMAL(10,8), + current_longitude DECIMAL(11,8), + last_location_update TIMESTAMPTZ, + + -- Informacion financiera + purchase_date DATE, + purchase_price DECIMAL(18,2), + purchase_currency VARCHAR(3) DEFAULT 'MXN', + supplier_id UUID, + invoice_number VARCHAR(100), + + -- Depreciacion + useful_life_years INTEGER, + salvage_value DECIMAL(18,2), + current_book_value DECIMAL(18,2), + accumulated_depreciation DECIMAL(18,2) DEFAULT 0, + depreciation_method VARCHAR(50), + last_depreciation_date DATE, + + -- Arrendamiento (si aplica) + lease_start_date DATE, + lease_end_date DATE, + lease_monthly_rate DECIMAL(18,2), + lease_contract_number VARCHAR(100), + lessor_name VARCHAR(255), + + -- Seguro + insurance_policy_number VARCHAR(100), + insurance_company VARCHAR(255), + insurance_expiry_date DATE, + insurance_coverage_amount DECIMAL(18,2), + + -- Documentos + photo_url VARCHAR(500), + manual_url VARCHAR(500), + registration_document_url VARCHAR(500), + + -- Proximo mantenimiento + next_maintenance_date DATE, + next_maintenance_hours DECIMAL(12,2), + next_maintenance_kilometers DECIMAL(12,2), + + -- Operador asignado + assigned_operator_id UUID, + + -- Notas y metadatos + notes TEXT, + tags VARCHAR(255)[], + 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_assets_tenant_code UNIQUE (tenant_id, asset_code) +); + +-- ---------------------------------------------------------------------------- +-- 3. Asignaciones de Activos a Obras +-- ---------------------------------------------------------------------------- +CREATE TABLE assets.asset_assignments ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + tenant_id UUID NOT NULL, + + -- Activo y proyecto + asset_id UUID NOT NULL REFERENCES assets.assets(id), + project_id UUID NOT NULL, + project_code VARCHAR(50), + project_name VARCHAR(255), + + -- Periodo de asignacion + start_date DATE NOT NULL, + end_date DATE, + is_current BOOLEAN NOT NULL DEFAULT TRUE, + + -- Ubicacion especifica en obra + location_in_project VARCHAR(255), + latitude DECIMAL(10,8), + longitude DECIMAL(11,8), + + -- Operador asignado + operator_id UUID, + operator_name VARCHAR(255), + + -- Responsable + responsible_id UUID, + responsible_name VARCHAR(255), + + -- Metricas al inicio/fin + hours_at_start DECIMAL(12,2), + hours_at_end DECIMAL(12,2), + kilometers_at_start DECIMAL(12,2), + kilometers_at_end DECIMAL(12,2), + + -- Tarifas + daily_rate DECIMAL(18,2), + hourly_rate DECIMAL(18,2), + + -- Razon de transferencia + transfer_reason TEXT, + transfer_notes TEXT, + + -- Documento de entrega + delivery_document_url VARCHAR(500), + return_document_url VARCHAR(500), + + -- Metadatos + metadata JSONB, + + -- Auditoria + created_by UUID, + updated_by UUID, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +-- ---------------------------------------------------------------------------- +-- 4. Planes de Mantenimiento +-- ---------------------------------------------------------------------------- +CREATE TABLE assets.maintenance_plans ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + tenant_id UUID NOT NULL, + + -- Identificacion + plan_code VARCHAR(50) NOT NULL, + name VARCHAR(255) NOT NULL, + description TEXT, + + -- Aplica a + asset_id UUID REFERENCES assets.assets(id), -- Si es especifico de un activo + category_id UUID REFERENCES assets.asset_categories(id), -- Si aplica a categoria + asset_type assets.asset_type, -- Si aplica a tipo + + -- Tipo y frecuencia + maintenance_type assets.maintenance_type NOT NULL DEFAULT 'preventive', + frequency assets.maintenance_frequency NOT NULL, + frequency_value INTEGER, -- Cada cuantas unidades (dias, horas, km) + + -- Actividades del plan + activities JSONB NOT NULL, -- Array de actividades con checklist + + -- Duracion estimada + estimated_duration_hours DECIMAL(5,2), + + -- Recursos necesarios + required_parts JSONB, -- Refacciones necesarias + required_tools JSONB, -- Herramientas necesarias + required_skills VARCHAR(255)[], -- Habilidades requeridas + + -- Costos estimados + estimated_labor_cost DECIMAL(18,2), + estimated_parts_cost DECIMAL(18,2), + estimated_total_cost DECIMAL(18,2), + + -- Estado + is_active BOOLEAN NOT NULL DEFAULT TRUE, + + -- 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_maintenance_plans_tenant_code UNIQUE (tenant_id, plan_code) +); + +-- ---------------------------------------------------------------------------- +-- 5. Programacion de Mantenimientos +-- ---------------------------------------------------------------------------- +CREATE TABLE assets.maintenance_schedules ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + tenant_id UUID NOT NULL, + + -- Activo y plan + asset_id UUID NOT NULL REFERENCES assets.assets(id), + plan_id UUID REFERENCES assets.maintenance_plans(id), + + -- Programacion + scheduled_date DATE NOT NULL, + scheduled_hours DECIMAL(12,2), -- Horas del equipo cuando debe hacerse + scheduled_kilometers DECIMAL(12,2), -- KM cuando debe hacerse + + -- Estado + status VARCHAR(20) NOT NULL DEFAULT 'pending', -- pending, completed, skipped, overdue + completed_date DATE, + + -- Orden de trabajo generada + work_order_id UUID, + + -- Alertas + alert_days_before INTEGER DEFAULT 7, + alert_sent BOOLEAN DEFAULT FALSE, + alert_sent_at TIMESTAMPTZ, + + -- Notas + notes TEXT, + + -- Auditoria + created_by UUID, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +-- ---------------------------------------------------------------------------- +-- 6. Ordenes de Trabajo de Mantenimiento +-- ---------------------------------------------------------------------------- +CREATE TABLE assets.work_orders ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + tenant_id UUID NOT NULL, + + -- Identificacion + work_order_number VARCHAR(50) NOT NULL, + + -- Activo + asset_id UUID NOT NULL REFERENCES assets.assets(id), + asset_code VARCHAR(50), + asset_name VARCHAR(255), + + -- Tipo y estado + maintenance_type assets.maintenance_type NOT NULL, + status assets.work_order_status NOT NULL DEFAULT 'draft', + priority assets.work_order_priority NOT NULL DEFAULT 'medium', + + -- Origen + schedule_id UUID REFERENCES assets.maintenance_schedules(id), + plan_id UUID REFERENCES assets.maintenance_plans(id), + is_scheduled BOOLEAN NOT NULL DEFAULT FALSE, + + -- Descripcion + title VARCHAR(255) NOT NULL, + description TEXT, + problem_reported TEXT, + diagnosis TEXT, + + -- Ubicacion + project_id UUID, + project_name VARCHAR(255), + location_description VARCHAR(255), + + -- Fechas + requested_date DATE NOT NULL, + scheduled_start_date DATE, + scheduled_end_date DATE, + actual_start_date DATE, + actual_end_date DATE, + + -- Metricas del equipo al momento + hours_at_work_order DECIMAL(12,2), + kilometers_at_work_order DECIMAL(12,2), + + -- Asignacion + assigned_to_id UUID, + assigned_to_name VARCHAR(255), + team_ids UUID[], + + -- Solicitante + requested_by_id UUID, + requested_by_name VARCHAR(255), + + -- Aprobacion + approved_by_id UUID, + approved_at TIMESTAMPTZ, + + -- Trabajo realizado + work_performed TEXT, + findings TEXT, + recommendations TEXT, + + -- Checklist de actividades + activities_checklist JSONB, + + -- Tiempos + estimated_hours DECIMAL(5,2), + actual_hours DECIMAL(5,2), + + -- Costos + labor_cost DECIMAL(18,2) DEFAULT 0, + parts_cost DECIMAL(18,2) DEFAULT 0, + external_service_cost DECIMAL(18,2) DEFAULT 0, + other_costs DECIMAL(18,2) DEFAULT 0, + total_cost DECIMAL(18,2) DEFAULT 0, + + -- Partes utilizadas (detalle en tabla separada) + parts_used_count INTEGER DEFAULT 0, + + -- Documentos + photos_before JSONB, -- URLs de fotos antes + photos_after JSONB, -- URLs de fotos despues + documents JSONB, -- URLs de documentos adjuntos + + -- Firma de conformidad + completed_by_id UUID, + completed_by_name VARCHAR(255), + completion_signature_url VARCHAR(500), + completion_notes TEXT, + + -- Seguimiento + requires_followup BOOLEAN DEFAULT FALSE, + followup_notes TEXT, + followup_work_order_id UUID, + + -- Notas y metadatos + 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(), + deleted_at TIMESTAMPTZ, + + CONSTRAINT uq_work_orders_tenant_number UNIQUE (tenant_id, work_order_number) +); + +-- ---------------------------------------------------------------------------- +-- 7. Partes/Refacciones Utilizadas en Ordenes de Trabajo +-- ---------------------------------------------------------------------------- +CREATE TABLE assets.work_order_parts ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + tenant_id UUID NOT NULL, + + -- Orden de trabajo + work_order_id UUID NOT NULL REFERENCES assets.work_orders(id) ON DELETE CASCADE, + + -- Parte/Refaccion + part_id UUID, -- Referencia a inventario si existe + part_code VARCHAR(50), + part_name VARCHAR(255) NOT NULL, + part_description TEXT, + + -- Cantidades + quantity_required DECIMAL(10,2) NOT NULL, + quantity_used DECIMAL(10,2), + + -- Costos + unit_cost DECIMAL(18,2), + total_cost DECIMAL(18,2), + + -- Origen + from_inventory BOOLEAN DEFAULT FALSE, + purchase_order_id UUID, -- Si se compro especificamente + + -- Notas + notes TEXT, + + -- Auditoria + created_by UUID, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +-- ---------------------------------------------------------------------------- +-- 8. Historial de Mantenimientos +-- ---------------------------------------------------------------------------- +CREATE TABLE assets.maintenance_history ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + tenant_id UUID NOT NULL, + + -- Activo + asset_id UUID NOT NULL REFERENCES assets.assets(id), + + -- Orden de trabajo (si aplica) + work_order_id UUID REFERENCES assets.work_orders(id), + + -- Fecha y tipo + maintenance_date DATE NOT NULL, + maintenance_type assets.maintenance_type NOT NULL, + + -- Descripcion + description TEXT NOT NULL, + work_performed TEXT, + + -- Metricas al momento del mantenimiento + hours_at_maintenance DECIMAL(12,2), + kilometers_at_maintenance DECIMAL(12,2), + + -- Costos + labor_cost DECIMAL(18,2) DEFAULT 0, + parts_cost DECIMAL(18,2) DEFAULT 0, + total_cost DECIMAL(18,2) DEFAULT 0, + + -- Ejecutor + performed_by_id UUID, + performed_by_name VARCHAR(255), + vendor_name VARCHAR(255), -- Si fue externo + + -- Documentos + documents JSONB, + + -- Notas + notes TEXT, + + -- Auditoria + created_by UUID, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +-- ---------------------------------------------------------------------------- +-- 9. Costos de Activos +-- ---------------------------------------------------------------------------- +CREATE TABLE assets.asset_costs ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + tenant_id UUID NOT NULL, + + -- Activo + asset_id UUID NOT NULL REFERENCES assets.assets(id), + + -- Periodo + period_start DATE NOT NULL, + period_end DATE NOT NULL, + fiscal_year INTEGER NOT NULL, + fiscal_month INTEGER NOT NULL, + + -- Proyecto (si aplica) + project_id UUID, + project_code VARCHAR(50), + + -- Tipo de costo + cost_type assets.cost_type NOT NULL, + + -- Descripcion + description VARCHAR(255), + reference_document VARCHAR(100), + + -- Monto + amount DECIMAL(18,2) NOT NULL, + currency VARCHAR(3) DEFAULT 'MXN', + + -- Uso asociado + hours_in_period DECIMAL(12,2), + kilometers_in_period DECIMAL(12,2), + + -- Calculo de tarifa + cost_per_hour DECIMAL(18,4), + cost_per_kilometer DECIMAL(18,4), + + -- Origen + source_module VARCHAR(50), -- work_order, fuel_log, invoice, etc. + source_id UUID, + + -- Notas + notes TEXT, + metadata JSONB, + + -- Auditoria + created_by UUID, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +-- ---------------------------------------------------------------------------- +-- 10. Ubicaciones GPS (Historico) +-- ---------------------------------------------------------------------------- +CREATE TABLE assets.asset_locations ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + tenant_id UUID NOT NULL, + + -- Activo + asset_id UUID NOT NULL REFERENCES assets.assets(id), + + -- Ubicacion + latitude DECIMAL(10,8) NOT NULL, + longitude DECIMAL(11,8) NOT NULL, + altitude DECIMAL(8,2), + accuracy DECIMAL(6,2), + heading DECIMAL(5,2), + speed DECIMAL(6,2), + + -- Timestamp + recorded_at TIMESTAMPTZ NOT NULL, + + -- Contexto + project_id UUID, + location_name VARCHAR(255), + address VARCHAR(500), + + -- Telemetria adicional + engine_status VARCHAR(20), -- on, off, idle + fuel_level DECIMAL(5,2), + odometer DECIMAL(12,2), + engine_hours DECIMAL(12,2), + battery_voltage DECIMAL(5,2), + + -- Dispositivo + device_id VARCHAR(100), + device_type VARCHAR(50), + + -- Metadatos + raw_data JSONB, + + -- Auditoria + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +-- ---------------------------------------------------------------------------- +-- 11. Registro de Combustible +-- ---------------------------------------------------------------------------- +CREATE TABLE assets.fuel_logs ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + tenant_id UUID NOT NULL, + + -- Activo + asset_id UUID NOT NULL REFERENCES assets.assets(id), + + -- Fecha y ubicacion + log_date DATE NOT NULL, + log_time TIME, + project_id UUID, + location VARCHAR(255), + + -- Combustible + fuel_type VARCHAR(50) NOT NULL, + quantity_liters DECIMAL(10,2) NOT NULL, + unit_price DECIMAL(18,4) NOT NULL, + total_cost DECIMAL(18,2) NOT NULL, + + -- Metricas al cargar + odometer_reading DECIMAL(12,2), + hours_reading DECIMAL(12,2), + + -- Rendimiento calculado + kilometers_since_last DECIMAL(12,2), + hours_since_last DECIMAL(12,2), + liters_per_100km DECIMAL(8,2), + liters_per_hour DECIMAL(8,2), + + -- Proveedor + vendor_name VARCHAR(255), + invoice_number VARCHAR(100), + + -- Operador + operator_id UUID, + operator_name VARCHAR(255), + + -- Notas + notes TEXT, + + -- Auditoria + created_by UUID, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +-- ============================================================================ +-- INDICES +-- ============================================================================ + +-- Asset Categories +CREATE INDEX idx_asset_categories_tenant ON assets.asset_categories(tenant_id); +CREATE INDEX idx_asset_categories_parent ON assets.asset_categories(parent_id); + +-- Assets +CREATE INDEX idx_assets_tenant ON assets.assets(tenant_id); +CREATE INDEX idx_assets_tenant_type ON assets.assets(tenant_id, asset_type); +CREATE INDEX idx_assets_tenant_status ON assets.assets(tenant_id, status); +CREATE INDEX idx_assets_tenant_category ON assets.assets(tenant_id, category_id); +CREATE INDEX idx_assets_current_project ON assets.assets(tenant_id, current_project_id); +CREATE INDEX idx_assets_serial ON assets.assets(tenant_id, serial_number); + +-- Asset Assignments +CREATE INDEX idx_asset_assignments_tenant ON assets.asset_assignments(tenant_id); +CREATE INDEX idx_asset_assignments_asset ON assets.asset_assignments(tenant_id, asset_id); +CREATE INDEX idx_asset_assignments_project ON assets.asset_assignments(tenant_id, project_id); +CREATE INDEX idx_asset_assignments_current ON assets.asset_assignments(tenant_id, is_current) WHERE is_current = TRUE; + +-- Maintenance Plans +CREATE INDEX idx_maintenance_plans_tenant ON assets.maintenance_plans(tenant_id); +CREATE INDEX idx_maintenance_plans_asset ON assets.maintenance_plans(tenant_id, asset_id); +CREATE INDEX idx_maintenance_plans_category ON assets.maintenance_plans(tenant_id, category_id); + +-- Maintenance Schedules +CREATE INDEX idx_maintenance_schedules_tenant ON assets.maintenance_schedules(tenant_id); +CREATE INDEX idx_maintenance_schedules_asset ON assets.maintenance_schedules(tenant_id, asset_id); +CREATE INDEX idx_maintenance_schedules_date ON assets.maintenance_schedules(tenant_id, scheduled_date); +CREATE INDEX idx_maintenance_schedules_status ON assets.maintenance_schedules(tenant_id, status); + +-- Work Orders +CREATE INDEX idx_work_orders_tenant ON assets.work_orders(tenant_id); +CREATE INDEX idx_work_orders_asset ON assets.work_orders(tenant_id, asset_id); +CREATE INDEX idx_work_orders_status ON assets.work_orders(tenant_id, status); +CREATE INDEX idx_work_orders_scheduled_date ON assets.work_orders(tenant_id, scheduled_start_date); +CREATE INDEX idx_work_orders_project ON assets.work_orders(tenant_id, project_id); + +-- Work Order Parts +CREATE INDEX idx_work_order_parts_work_order ON assets.work_order_parts(work_order_id); + +-- Maintenance History +CREATE INDEX idx_maintenance_history_tenant ON assets.maintenance_history(tenant_id); +CREATE INDEX idx_maintenance_history_asset ON assets.maintenance_history(tenant_id, asset_id); +CREATE INDEX idx_maintenance_history_date ON assets.maintenance_history(tenant_id, maintenance_date); + +-- Asset Costs +CREATE INDEX idx_asset_costs_tenant ON assets.asset_costs(tenant_id); +CREATE INDEX idx_asset_costs_asset ON assets.asset_costs(tenant_id, asset_id); +CREATE INDEX idx_asset_costs_period ON assets.asset_costs(tenant_id, period_start, period_end); +CREATE INDEX idx_asset_costs_type ON assets.asset_costs(tenant_id, cost_type); +CREATE INDEX idx_asset_costs_project ON assets.asset_costs(tenant_id, project_id); + +-- Asset Locations +CREATE INDEX idx_asset_locations_tenant ON assets.asset_locations(tenant_id); +CREATE INDEX idx_asset_locations_asset ON assets.asset_locations(tenant_id, asset_id); +CREATE INDEX idx_asset_locations_recorded ON assets.asset_locations(tenant_id, recorded_at); +CREATE INDEX idx_asset_locations_geo ON assets.asset_locations USING gist ( + ST_SetSRID(ST_MakePoint(longitude, latitude), 4326) +) WHERE longitude IS NOT NULL AND latitude IS NOT NULL; + +-- Fuel Logs +CREATE INDEX idx_fuel_logs_tenant ON assets.fuel_logs(tenant_id); +CREATE INDEX idx_fuel_logs_asset ON assets.fuel_logs(tenant_id, asset_id); +CREATE INDEX idx_fuel_logs_date ON assets.fuel_logs(tenant_id, log_date); + +-- ============================================================================ +-- ROW LEVEL SECURITY (RLS) +-- ============================================================================ + +ALTER TABLE assets.asset_categories ENABLE ROW LEVEL SECURITY; +ALTER TABLE assets.assets ENABLE ROW LEVEL SECURITY; +ALTER TABLE assets.asset_assignments ENABLE ROW LEVEL SECURITY; +ALTER TABLE assets.maintenance_plans ENABLE ROW LEVEL SECURITY; +ALTER TABLE assets.maintenance_schedules ENABLE ROW LEVEL SECURITY; +ALTER TABLE assets.work_orders ENABLE ROW LEVEL SECURITY; +ALTER TABLE assets.work_order_parts ENABLE ROW LEVEL SECURITY; +ALTER TABLE assets.maintenance_history ENABLE ROW LEVEL SECURITY; +ALTER TABLE assets.asset_costs ENABLE ROW LEVEL SECURITY; +ALTER TABLE assets.asset_locations ENABLE ROW LEVEL SECURITY; +ALTER TABLE assets.fuel_logs ENABLE ROW LEVEL SECURITY; + +-- ============================================================================ +-- TRIGGERS DE AUDITORIA +-- ============================================================================ + +CREATE OR REPLACE FUNCTION assets.set_updated_at() +RETURNS TRIGGER AS $$ +BEGIN + NEW.updated_at = NOW(); + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER trg_asset_categories_updated_at + BEFORE UPDATE ON assets.asset_categories + FOR EACH ROW EXECUTE FUNCTION assets.set_updated_at(); + +CREATE TRIGGER trg_assets_updated_at + BEFORE UPDATE ON assets.assets + FOR EACH ROW EXECUTE FUNCTION assets.set_updated_at(); + +CREATE TRIGGER trg_asset_assignments_updated_at + BEFORE UPDATE ON assets.asset_assignments + FOR EACH ROW EXECUTE FUNCTION assets.set_updated_at(); + +CREATE TRIGGER trg_maintenance_plans_updated_at + BEFORE UPDATE ON assets.maintenance_plans + FOR EACH ROW EXECUTE FUNCTION assets.set_updated_at(); + +CREATE TRIGGER trg_maintenance_schedules_updated_at + BEFORE UPDATE ON assets.maintenance_schedules + FOR EACH ROW EXECUTE FUNCTION assets.set_updated_at(); + +CREATE TRIGGER trg_work_orders_updated_at + BEFORE UPDATE ON assets.work_orders + FOR EACH ROW EXECUTE FUNCTION assets.set_updated_at(); + +CREATE TRIGGER trg_work_order_parts_updated_at + BEFORE UPDATE ON assets.work_order_parts + FOR EACH ROW EXECUTE FUNCTION assets.set_updated_at(); + +CREATE TRIGGER trg_asset_costs_updated_at + BEFORE UPDATE ON assets.asset_costs + FOR EACH ROW EXECUTE FUNCTION assets.set_updated_at(); + +CREATE TRIGGER trg_fuel_logs_updated_at + BEFORE UPDATE ON assets.fuel_logs + FOR EACH ROW EXECUTE FUNCTION assets.set_updated_at(); + +-- ============================================================================ +-- FUNCIONES AUXILIARES +-- ============================================================================ + +-- Funcion para calcular TCO de un activo +CREATE OR REPLACE FUNCTION assets.calculate_asset_tco( + p_asset_id UUID, + p_tenant_id UUID, + p_from_date DATE DEFAULT NULL, + p_to_date DATE DEFAULT NULL +) +RETURNS TABLE ( + total_cost DECIMAL(18,2), + maintenance_cost DECIMAL(18,2), + fuel_cost DECIMAL(18,2), + depreciation_cost DECIMAL(18,2), + other_costs DECIMAL(18,2), + total_hours DECIMAL(12,2), + total_kilometers DECIMAL(12,2), + cost_per_hour DECIMAL(18,4), + cost_per_kilometer DECIMAL(18,4) +) AS $$ +DECLARE + v_from_date DATE; + v_to_date DATE; +BEGIN + v_from_date := COALESCE(p_from_date, '1900-01-01'::DATE); + v_to_date := COALESCE(p_to_date, CURRENT_DATE); + + RETURN QUERY + SELECT + COALESCE(SUM(ac.amount), 0) AS total_cost, + COALESCE(SUM(CASE WHEN ac.cost_type IN ('maintenance', 'repair') THEN ac.amount END), 0) AS maintenance_cost, + COALESCE(SUM(CASE WHEN ac.cost_type = 'fuel' THEN ac.amount END), 0) AS fuel_cost, + COALESCE(SUM(CASE WHEN ac.cost_type = 'depreciation' THEN ac.amount END), 0) AS depreciation_cost, + COALESCE(SUM(CASE WHEN ac.cost_type NOT IN ('maintenance', 'repair', 'fuel', 'depreciation') THEN ac.amount END), 0) AS other_costs, + COALESCE(SUM(ac.hours_in_period), 0) AS total_hours, + COALESCE(SUM(ac.kilometers_in_period), 0) AS total_kilometers, + CASE WHEN SUM(ac.hours_in_period) > 0 THEN SUM(ac.amount) / SUM(ac.hours_in_period) ELSE 0 END AS cost_per_hour, + CASE WHEN SUM(ac.kilometers_in_period) > 0 THEN SUM(ac.amount) / SUM(ac.kilometers_in_period) ELSE 0 END AS cost_per_kilometer + FROM assets.asset_costs ac + WHERE ac.asset_id = p_asset_id + AND ac.tenant_id = p_tenant_id + AND ac.period_start >= v_from_date + AND ac.period_end <= v_to_date; +END; +$$ LANGUAGE plpgsql; + +-- Funcion para actualizar proximo mantenimiento de un activo +CREATE OR REPLACE FUNCTION assets.update_next_maintenance() +RETURNS TRIGGER AS $$ +BEGIN + -- Actualizar proximo mantenimiento del activo + UPDATE assets.assets a + SET + next_maintenance_date = ( + SELECT MIN(scheduled_date) + FROM assets.maintenance_schedules ms + WHERE ms.asset_id = a.id + AND ms.status = 'pending' + ), + updated_at = NOW() + WHERE a.id = COALESCE(NEW.asset_id, OLD.asset_id); + + RETURN COALESCE(NEW, OLD); +END; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER trg_update_next_maintenance + AFTER INSERT OR UPDATE OR DELETE ON assets.maintenance_schedules + FOR EACH ROW EXECUTE FUNCTION assets.update_next_maintenance(); + +-- ============================================================================ +-- COMENTARIOS DE DOCUMENTACION +-- ============================================================================ + +COMMENT ON SCHEMA assets IS 'Modulo de Activos, Maquinaria y Mantenimiento (MAE-015) - ERP Construccion'; + +COMMENT ON TABLE assets.asset_categories IS 'Categorias de activos para clasificacion'; +COMMENT ON TABLE assets.assets IS 'Catalogo principal de activos (maquinaria, equipo, vehiculos)'; +COMMENT ON TABLE assets.asset_assignments IS 'Asignaciones de activos a proyectos/obras'; +COMMENT ON TABLE assets.maintenance_plans IS 'Planes de mantenimiento preventivo'; +COMMENT ON TABLE assets.maintenance_schedules IS 'Programacion de mantenimientos por activo'; +COMMENT ON TABLE assets.work_orders IS 'Ordenes de trabajo de mantenimiento'; +COMMENT ON TABLE assets.work_order_parts IS 'Partes/refacciones utilizadas en ordenes de trabajo'; +COMMENT ON TABLE assets.maintenance_history IS 'Historial de mantenimientos realizados'; +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.fuel_logs IS 'Registro de cargas de combustible'; + +-- ============================================================================ +-- FIN DEL SCRIPT +-- ============================================================================