# DDL SPECIFICATION: Schema Assets **Version:** 1.0.0 **Fecha:** 2025-12-05 **Schema:** `assets` **Modulos:** MAE-015 (Activos, Maquinaria y Mantenimiento) --- ## Resumen | Metrica | Valor | |---------|-------| | Total Tablas | 12 | | ENUMs | 5 | | Funciones | 5 | | Triggers | 4 | | Indices | 30+ | --- ## 1. ENUMs ```sql -- Tipos de activo CREATE TYPE assets.asset_type AS ENUM ( 'heavy_equipment', -- Maquinaria pesada (retroexcavadora, grua, etc.) 'vehicle', -- Vehiculos 'tool', -- Herramientas 'equipment', -- Equipo menor 'computer', -- Equipo de computo 'furniture', -- Mobiliario 'other' -- Otro ); -- Estados de activo CREATE TYPE assets.asset_status AS ENUM ( 'available', -- Disponible 'in_use', -- En uso 'maintenance', -- En mantenimiento 'repair', -- En reparacion 'out_of_service', -- Fuera de servicio 'disposed' -- Dado de baja ); -- Tipos de mantenimiento CREATE TYPE assets.maintenance_type AS ENUM ( 'preventive', -- Preventivo 'corrective', -- Correctivo 'predictive', -- Predictivo 'inspection', -- Inspeccion 'calibration' -- Calibracion ); -- Estados 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 mantenimiento CREATE TYPE assets.maintenance_priority AS ENUM ( 'low', -- Baja 'medium', -- Media 'high', -- Alta 'critical' -- Critica ); ``` --- ## 2. Tablas de Activos ### 2.1 assets (Activos) ```sql CREATE TABLE assets.assets ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES core.tenants(id), -- Identificacion asset_code VARCHAR(30) NOT NULL, name VARCHAR(200) NOT NULL, description TEXT, -- Clasificacion asset_type assets.asset_type NOT NULL, category_id UUID, subcategory VARCHAR(100), -- Especificaciones brand VARCHAR(100), model VARCHAR(100), serial_number VARCHAR(100), year INTEGER, capacity VARCHAR(100), specifications JSONB, -- Ubicacion actual current_location VARCHAR(200), current_project_id UUID REFERENCES construction.projects(id), assigned_to UUID REFERENCES core.users(id), -- GPS/IoT gps_device_id VARCHAR(100), last_gps_location GEOGRAPHY(POINT, 4326), last_gps_update TIMESTAMP, -- Financiero purchase_date DATE, purchase_value DECIMAL(18,2), current_value DECIMAL(18,2), depreciation_method VARCHAR(30), useful_life_years INTEGER, salvage_value DECIMAL(18,2), accumulated_depreciation DECIMAL(18,2) DEFAULT 0, -- Operativo operating_hours DECIMAL(10,2) DEFAULT 0, odometer_reading DECIMAL(12,2), fuel_type VARCHAR(30), hourly_cost DECIMAL(12,2), -- Estado status assets.asset_status NOT NULL DEFAULT 'available', -- Documentos image_url TEXT, documents_urls TEXT[], -- Auditoria created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, created_by UUID REFERENCES core.users(id), updated_at TIMESTAMP, updated_by UUID REFERENCES core.users(id), deleted_at TIMESTAMP, CONSTRAINT uq_assets_code UNIQUE (tenant_id, asset_code) ); CREATE INDEX idx_assets_tenant ON assets.assets(tenant_id); CREATE INDEX idx_assets_type ON assets.assets(asset_type); CREATE INDEX idx_assets_status ON assets.assets(status); CREATE INDEX idx_assets_project ON assets.assets(current_project_id); CREATE INDEX idx_assets_location ON assets.assets USING GIST(last_gps_location); CREATE INDEX idx_assets_available ON assets.assets(tenant_id) WHERE status = 'available' AND deleted_at IS NULL; ``` ### 2.2 asset_categories (Categorias de Activos) ```sql CREATE TABLE assets.asset_categories ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES core.tenants(id), -- Identificacion code VARCHAR(20) NOT NULL, name VARCHAR(100) NOT NULL, description TEXT, -- Jerarquia parent_id UUID REFERENCES assets.asset_categories(id), level INTEGER DEFAULT 1, -- Contabilidad asset_account_id UUID, depreciation_account_id UUID, expense_account_id UUID, -- Depreciacion default default_useful_life INTEGER, default_depreciation_method VARCHAR(30), -- Estado is_active BOOLEAN DEFAULT true, -- Auditoria created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, created_by UUID REFERENCES core.users(id), updated_at TIMESTAMP, updated_by UUID REFERENCES core.users(id), CONSTRAINT uq_asset_categories UNIQUE (tenant_id, code) ); CREATE INDEX idx_asset_categories_tenant ON assets.asset_categories(tenant_id); CREATE INDEX idx_asset_categories_parent ON assets.asset_categories(parent_id); ``` ### 2.3 asset_assignments (Asignaciones de Activos) ```sql CREATE TABLE assets.asset_assignments ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES core.tenants(id), asset_id UUID NOT NULL REFERENCES assets.assets(id), -- Asignacion project_id UUID REFERENCES construction.projects(id), assigned_to UUID REFERENCES core.users(id), department VARCHAR(100), -- Periodo start_date DATE NOT NULL, end_date DATE, planned_end_date DATE, -- Ubicacion location VARCHAR(200), location_coordinates GEOGRAPHY(POINT, 4326), -- Estado status VARCHAR(20) DEFAULT 'active', -- active, completed, cancelled -- Uso initial_hours DECIMAL(10,2), final_hours DECIMAL(10,2), initial_odometer DECIMAL(12,2), final_odometer DECIMAL(12,2), -- Notas notes TEXT, -- Auditoria created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, created_by UUID REFERENCES core.users(id), updated_at TIMESTAMP, updated_by UUID REFERENCES core.users(id) ); CREATE INDEX idx_assignments_asset ON assets.asset_assignments(asset_id); CREATE INDEX idx_assignments_project ON assets.asset_assignments(project_id); CREATE INDEX idx_assignments_active ON assets.asset_assignments(asset_id) WHERE status = 'active'; CREATE INDEX idx_assignments_dates ON assets.asset_assignments(start_date, end_date); ``` ### 2.4 asset_usage_logs (Registro de Uso) ```sql CREATE TABLE assets.asset_usage_logs ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES core.tenants(id), asset_id UUID NOT NULL REFERENCES assets.assets(id), assignment_id UUID REFERENCES assets.asset_assignments(id), -- Fecha y periodo log_date DATE NOT NULL, start_time TIME, end_time TIME, -- Uso hours_used DECIMAL(6,2), distance_traveled DECIMAL(10,2), fuel_consumed DECIMAL(8,2), -- Proyecto project_id UUID REFERENCES construction.projects(id), activity_description TEXT, -- Operador operator_id UUID REFERENCES core.users(id), operator_name VARCHAR(200), -- Lecturas odometer_start DECIMAL(12,2), odometer_end DECIMAL(12,2), hour_meter_start DECIMAL(10,2), hour_meter_end DECIMAL(10,2), -- Notas notes TEXT, -- Auditoria created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, created_by UUID REFERENCES core.users(id) ); CREATE INDEX idx_usage_logs_asset ON assets.asset_usage_logs(asset_id); CREATE INDEX idx_usage_logs_date ON assets.asset_usage_logs(log_date); CREATE INDEX idx_usage_logs_project ON assets.asset_usage_logs(project_id); CREATE INDEX idx_usage_logs_operator ON assets.asset_usage_logs(operator_id); ``` --- ## 3. Tablas de Mantenimiento ### 3.1 maintenance_plans (Planes de Mantenimiento) ```sql CREATE TABLE assets.maintenance_plans ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES core.tenants(id), asset_id UUID NOT NULL REFERENCES assets.assets(id), -- Identificacion plan_name VARCHAR(200) NOT NULL, description TEXT, maintenance_type assets.maintenance_type NOT NULL, -- Frecuencia frequency_type VARCHAR(20) NOT NULL, -- hours, days, distance, calendar frequency_value INTEGER NOT NULL, tolerance_value INTEGER DEFAULT 0, -- Ultimo y proximo last_performed_at TIMESTAMP, last_performed_hours DECIMAL(10,2), last_performed_distance DECIMAL(12,2), next_due_at TIMESTAMP, next_due_hours DECIMAL(10,2), next_due_distance DECIMAL(12,2), -- Tareas incluidas tasks_checklist JSONB, estimated_duration_hours DECIMAL(4,2), estimated_cost DECIMAL(12,2), -- Estado is_active BOOLEAN DEFAULT true, -- Auditoria created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, created_by UUID REFERENCES core.users(id), updated_at TIMESTAMP, updated_by UUID REFERENCES core.users(id) ); CREATE INDEX idx_maintenance_plans_asset ON assets.maintenance_plans(asset_id); CREATE INDEX idx_maintenance_plans_next_due ON assets.maintenance_plans(next_due_at); CREATE INDEX idx_maintenance_plans_active ON assets.maintenance_plans(asset_id) WHERE is_active = true; ``` ### 3.2 work_orders (Ordenes de Trabajo) ```sql CREATE TABLE assets.work_orders ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES core.tenants(id), asset_id UUID NOT NULL REFERENCES assets.assets(id), -- Identificacion wo_number VARCHAR(30) NOT NULL, maintenance_type assets.maintenance_type NOT NULL, priority assets.maintenance_priority NOT NULL DEFAULT 'medium', -- Descripcion title VARCHAR(200) NOT NULL, description TEXT, problem_reported TEXT, -- Origen maintenance_plan_id UUID REFERENCES assets.maintenance_plans(id), reported_by UUID REFERENCES core.users(id), reported_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- Asignacion assigned_to UUID REFERENCES core.users(id), assigned_team VARCHAR(100), -- Fechas scheduled_date DATE, scheduled_start_time TIME, estimated_duration_hours DECIMAL(4,2), actual_start_at TIMESTAMP, actual_end_at TIMESTAMP, -- Ubicacion work_location VARCHAR(200), project_id UUID REFERENCES construction.projects(id), -- Costos estimated_cost DECIMAL(12,2), actual_labor_cost DECIMAL(12,2) DEFAULT 0, actual_parts_cost DECIMAL(12,2) DEFAULT 0, actual_total_cost DECIMAL(12,2) DEFAULT 0, -- Estado status assets.work_order_status NOT NULL DEFAULT 'draft', -- Resultado work_performed TEXT, root_cause TEXT, recommendations TEXT, -- Lecturas al momento hours_at_work DECIMAL(10,2), odometer_at_work DECIMAL(12,2), -- Auditoria created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, created_by UUID REFERENCES core.users(id), updated_at TIMESTAMP, updated_by UUID REFERENCES core.users(id), deleted_at TIMESTAMP, CONSTRAINT uq_work_orders_number UNIQUE (tenant_id, wo_number) ); CREATE INDEX idx_work_orders_asset ON assets.work_orders(asset_id); CREATE INDEX idx_work_orders_status ON assets.work_orders(status); CREATE INDEX idx_work_orders_scheduled ON assets.work_orders(scheduled_date); CREATE INDEX idx_work_orders_assigned ON assets.work_orders(assigned_to); CREATE INDEX idx_work_orders_plan ON assets.work_orders(maintenance_plan_id); CREATE INDEX idx_work_orders_pending ON assets.work_orders(tenant_id) WHERE status IN ('draft', 'scheduled', 'in_progress'); ``` ### 3.3 work_order_tasks (Tareas de Orden de Trabajo) ```sql CREATE TABLE assets.work_order_tasks ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES core.tenants(id), work_order_id UUID NOT NULL REFERENCES assets.work_orders(id), -- Tarea task_number INTEGER NOT NULL, description TEXT NOT NULL, -- Estado is_completed BOOLEAN DEFAULT false, completed_at TIMESTAMP, completed_by UUID REFERENCES core.users(id), -- Resultado result TEXT, notes TEXT, -- Orden sort_order INTEGER DEFAULT 0, -- Auditoria created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, created_by UUID REFERENCES core.users(id), updated_at TIMESTAMP, updated_by UUID REFERENCES core.users(id) ); CREATE INDEX idx_wo_tasks_work_order ON assets.work_order_tasks(work_order_id); CREATE INDEX idx_wo_tasks_pending ON assets.work_order_tasks(work_order_id) WHERE is_completed = false; ``` ### 3.4 work_order_parts (Repuestos Utilizados) ```sql CREATE TABLE assets.work_order_parts ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES core.tenants(id), work_order_id UUID NOT NULL REFERENCES assets.work_orders(id), -- Producto product_id UUID, product_code VARCHAR(50), product_name VARCHAR(200) NOT NULL, -- Cantidad quantity DECIMAL(10,4) NOT NULL, unit_code VARCHAR(10), -- Costo unit_cost DECIMAL(14,4), total_cost DECIMAL(18,2), -- Origen warehouse_id UUID, purchase_order_id UUID, -- Auditoria created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, created_by UUID REFERENCES core.users(id) ); CREATE INDEX idx_wo_parts_work_order ON assets.work_order_parts(work_order_id); CREATE INDEX idx_wo_parts_product ON assets.work_order_parts(product_id); ``` ### 3.5 work_order_labor (Mano de Obra) ```sql CREATE TABLE assets.work_order_labor ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES core.tenants(id), work_order_id UUID NOT NULL REFERENCES assets.work_orders(id), -- Tecnico technician_id UUID REFERENCES core.users(id), technician_name VARCHAR(200) NOT NULL, -- Tiempo work_date DATE NOT NULL, hours_worked DECIMAL(6,2) NOT NULL, overtime_hours DECIMAL(6,2) DEFAULT 0, -- Costo hourly_rate DECIMAL(10,2), overtime_rate DECIMAL(10,2), total_cost DECIMAL(12,2), -- Descripcion work_description TEXT, -- Auditoria created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, created_by UUID REFERENCES core.users(id) ); CREATE INDEX idx_wo_labor_work_order ON assets.work_order_labor(work_order_id); CREATE INDEX idx_wo_labor_technician ON assets.work_order_labor(technician_id); CREATE INDEX idx_wo_labor_date ON assets.work_order_labor(work_date); ``` --- ## 4. Tablas de GPS y Telemetria ### 4.1 gps_tracking (Rastreo GPS) ```sql CREATE TABLE assets.gps_tracking ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES core.tenants(id), asset_id UUID NOT NULL REFERENCES assets.assets(id), -- Ubicacion location GEOGRAPHY(POINT, 4326) NOT NULL, altitude DECIMAL(10,2), heading DECIMAL(5,2), speed DECIMAL(8,2), -- Timestamp recorded_at TIMESTAMP NOT NULL, received_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- Dispositivo device_id VARCHAR(100), device_type VARCHAR(50), -- Datos adicionales ignition_status BOOLEAN, fuel_level DECIMAL(5,2), engine_temp DECIMAL(5,2), battery_voltage DECIMAL(5,2), odometer DECIMAL(12,2), hour_meter DECIMAL(10,2), -- Raw data raw_data JSONB ); -- Particionado por fecha para mejor rendimiento CREATE INDEX idx_gps_tracking_asset ON assets.gps_tracking(asset_id); CREATE INDEX idx_gps_tracking_time ON assets.gps_tracking(recorded_at); CREATE INDEX idx_gps_tracking_location ON assets.gps_tracking USING GIST(location); ``` ### 4.2 geofences (Geocercas) ```sql CREATE TABLE assets.geofences ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES core.tenants(id), -- Identificacion name VARCHAR(200) NOT NULL, description TEXT, -- Geometria boundary GEOGRAPHY(POLYGON, 4326) NOT NULL, center_point GEOGRAPHY(POINT, 4326), radius_meters DECIMAL(10,2), -- Vinculacion project_id UUID REFERENCES construction.projects(id), -- Alertas alert_on_entry BOOLEAN DEFAULT true, alert_on_exit BOOLEAN DEFAULT true, alert_recipients UUID[], -- Estado is_active BOOLEAN DEFAULT true, -- Auditoria created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, created_by UUID REFERENCES core.users(id), updated_at TIMESTAMP, updated_by UUID REFERENCES core.users(id) ); CREATE INDEX idx_geofences_tenant ON assets.geofences(tenant_id); CREATE INDEX idx_geofences_boundary ON assets.geofences USING GIST(boundary); CREATE INDEX idx_geofences_project ON assets.geofences(project_id); ``` ### 4.3 geofence_events (Eventos de Geocerca) ```sql CREATE TABLE assets.geofence_events ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES core.tenants(id), asset_id UUID NOT NULL REFERENCES assets.assets(id), geofence_id UUID NOT NULL REFERENCES assets.geofences(id), -- Evento event_type VARCHAR(20) NOT NULL, -- entry, exit event_time TIMESTAMP NOT NULL, location GEOGRAPHY(POINT, 4326), -- Notificacion notification_sent BOOLEAN DEFAULT false, notification_sent_at TIMESTAMP, -- Auditoria created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX idx_geofence_events_asset ON assets.geofence_events(asset_id); CREATE INDEX idx_geofence_events_geofence ON assets.geofence_events(geofence_id); CREATE INDEX idx_geofence_events_time ON assets.geofence_events(event_time); ``` --- ## 5. Funciones ### 5.1 Actualizar Estado de Activo ```sql CREATE OR REPLACE FUNCTION assets.update_asset_status() RETURNS TRIGGER AS $$ BEGIN -- Actualizar estado del activo segun orden de trabajo IF NEW.status = 'in_progress' AND OLD.status != 'in_progress' THEN UPDATE assets.assets SET status = CASE WHEN NEW.maintenance_type IN ('preventive', 'predictive', 'inspection') THEN 'maintenance' ELSE 'repair' END, updated_at = CURRENT_TIMESTAMP WHERE id = NEW.asset_id; ELSIF NEW.status = 'completed' AND OLD.status = 'in_progress' THEN UPDATE assets.assets SET status = 'available', updated_at = CURRENT_TIMESTAMP WHERE id = NEW.asset_id; END IF; RETURN NEW; END; $$ LANGUAGE plpgsql; CREATE TRIGGER trg_update_asset_status AFTER UPDATE OF status ON assets.work_orders FOR EACH ROW EXECUTE FUNCTION assets.update_asset_status(); ``` ### 5.2 Calcular Costo Total de Orden de Trabajo ```sql CREATE OR REPLACE FUNCTION assets.calculate_work_order_costs() RETURNS TRIGGER AS $$ BEGIN UPDATE assets.work_orders wo SET actual_parts_cost = COALESCE(( SELECT SUM(total_cost) FROM assets.work_order_parts WHERE work_order_id = wo.id ), 0), actual_labor_cost = COALESCE(( SELECT SUM(total_cost) FROM assets.work_order_labor WHERE work_order_id = wo.id ), 0), actual_total_cost = actual_parts_cost + actual_labor_cost, updated_at = CURRENT_TIMESTAMP WHERE id = COALESCE(NEW.work_order_id, OLD.work_order_id); RETURN NEW; END; $$ LANGUAGE plpgsql; CREATE TRIGGER trg_calculate_wo_costs_parts AFTER INSERT OR UPDATE OR DELETE ON assets.work_order_parts FOR EACH ROW EXECUTE FUNCTION assets.calculate_work_order_costs(); CREATE TRIGGER trg_calculate_wo_costs_labor AFTER INSERT OR UPDATE OR DELETE ON assets.work_order_labor FOR EACH ROW EXECUTE FUNCTION assets.calculate_work_order_costs(); ``` ### 5.3 Actualizar Horas de Operacion ```sql CREATE OR REPLACE FUNCTION assets.update_operating_hours() RETURNS TRIGGER AS $$ BEGIN UPDATE assets.assets SET operating_hours = COALESCE(operating_hours, 0) + COALESCE(NEW.hours_used, 0), odometer_reading = COALESCE(NEW.odometer_end, odometer_reading), updated_at = CURRENT_TIMESTAMP WHERE id = NEW.asset_id; RETURN NEW; END; $$ LANGUAGE plpgsql; CREATE TRIGGER trg_update_operating_hours AFTER INSERT ON assets.asset_usage_logs FOR EACH ROW EXECUTE FUNCTION assets.update_operating_hours(); ``` ### 5.4 Calcular Proximo Mantenimiento ```sql CREATE OR REPLACE FUNCTION assets.calculate_next_maintenance(p_plan_id UUID) RETURNS VOID AS $$ DECLARE v_plan RECORD; v_asset RECORD; v_next_at TIMESTAMP; v_next_hours DECIMAL; v_next_distance DECIMAL; BEGIN SELECT * INTO v_plan FROM assets.maintenance_plans WHERE id = p_plan_id; SELECT * INTO v_asset FROM assets.assets WHERE id = v_plan.asset_id; CASE v_plan.frequency_type WHEN 'calendar', 'days' THEN v_next_at := COALESCE(v_plan.last_performed_at, CURRENT_TIMESTAMP) + (v_plan.frequency_value || ' days')::INTERVAL; WHEN 'hours' THEN v_next_hours := COALESCE(v_plan.last_performed_hours, 0) + v_plan.frequency_value; WHEN 'distance' THEN v_next_distance := COALESCE(v_plan.last_performed_distance, 0) + v_plan.frequency_value; END CASE; UPDATE assets.maintenance_plans SET next_due_at = v_next_at, next_due_hours = v_next_hours, next_due_distance = v_next_distance, updated_at = CURRENT_TIMESTAMP WHERE id = p_plan_id; END; $$ LANGUAGE plpgsql; ``` ### 5.5 Calcular TCO (Total Cost of Ownership) ```sql CREATE OR REPLACE FUNCTION assets.calculate_tco( p_asset_id UUID, p_start_date DATE DEFAULT NULL, p_end_date DATE DEFAULT CURRENT_DATE ) RETURNS TABLE ( purchase_cost DECIMAL, depreciation_cost DECIMAL, maintenance_cost DECIMAL, fuel_cost DECIMAL, operating_hours DECIMAL, cost_per_hour DECIMAL, total_tco DECIMAL ) AS $$ DECLARE v_asset RECORD; v_purchase DECIMAL; v_depreciation DECIMAL; v_maintenance DECIMAL; v_fuel DECIMAL; v_hours DECIMAL; BEGIN SELECT * INTO v_asset FROM assets.assets WHERE id = p_asset_id; v_purchase := COALESCE(v_asset.purchase_value, 0); v_depreciation := COALESCE(v_asset.accumulated_depreciation, 0); -- Costo de mantenimiento SELECT COALESCE(SUM(actual_total_cost), 0) INTO v_maintenance FROM assets.work_orders WHERE asset_id = p_asset_id AND status = 'completed' AND (p_start_date IS NULL OR actual_end_at >= p_start_date) AND actual_end_at <= p_end_date; -- Costo de combustible (estimado) SELECT COALESCE(SUM(fuel_consumed * 25), 0) INTO v_fuel -- Precio promedio combustible FROM assets.asset_usage_logs WHERE asset_id = p_asset_id AND (p_start_date IS NULL OR log_date >= p_start_date) AND log_date <= p_end_date; -- Horas de operacion SELECT COALESCE(SUM(hours_used), 0) INTO v_hours FROM assets.asset_usage_logs WHERE asset_id = p_asset_id AND (p_start_date IS NULL OR log_date >= p_start_date) AND log_date <= p_end_date; RETURN QUERY SELECT v_purchase, v_depreciation, v_maintenance, v_fuel, v_hours, CASE WHEN v_hours > 0 THEN (v_depreciation + v_maintenance + v_fuel) / v_hours ELSE 0 END, v_purchase + v_depreciation + v_maintenance + v_fuel; END; $$ LANGUAGE plpgsql; ``` --- ## 6. Row Level Security ```sql -- Habilitar RLS en todas las tablas ALTER TABLE assets.assets ENABLE ROW LEVEL SECURITY; ALTER TABLE assets.asset_categories ENABLE ROW LEVEL SECURITY; ALTER TABLE assets.asset_assignments ENABLE ROW LEVEL SECURITY; ALTER TABLE assets.asset_usage_logs ENABLE ROW LEVEL SECURITY; ALTER TABLE assets.maintenance_plans ENABLE ROW LEVEL SECURITY; ALTER TABLE assets.work_orders ENABLE ROW LEVEL SECURITY; ALTER TABLE assets.work_order_tasks ENABLE ROW LEVEL SECURITY; ALTER TABLE assets.work_order_parts ENABLE ROW LEVEL SECURITY; ALTER TABLE assets.work_order_labor ENABLE ROW LEVEL SECURITY; ALTER TABLE assets.gps_tracking ENABLE ROW LEVEL SECURITY; ALTER TABLE assets.geofences ENABLE ROW LEVEL SECURITY; ALTER TABLE assets.geofence_events ENABLE ROW LEVEL SECURITY; -- Crear politicas de aislamiento por tenant DO $$ DECLARE t TEXT; BEGIN FOR t IN SELECT tablename FROM pg_tables WHERE schemaname = 'assets' LOOP EXECUTE format(' CREATE POLICY tenant_isolation ON assets.%I USING (tenant_id = current_setting(''app.current_tenant_id'')::uuid) ', t); END LOOP; END $$; ``` --- ## 7. Seeds Iniciales ```sql -- Categorias de activos INSERT INTO assets.asset_categories (tenant_id, code, name, default_useful_life, default_depreciation_method) VALUES ('{{TENANT_ID}}', 'MAQ-PES', 'Maquinaria Pesada', 10, 'straight_line'), ('{{TENANT_ID}}', 'VEH', 'Vehiculos', 5, 'straight_line'), ('{{TENANT_ID}}', 'HER', 'Herramientas', 3, 'straight_line'), ('{{TENANT_ID}}', 'EQU-MEN', 'Equipo Menor', 3, 'straight_line'), ('{{TENANT_ID}}', 'COM', 'Equipo de Computo', 3, 'straight_line'); -- Activos de ejemplo INSERT INTO assets.assets (tenant_id, asset_code, name, asset_type, brand, model, status) VALUES ('{{TENANT_ID}}', 'RET-001', 'Retroexcavadora CAT 420F', 'heavy_equipment', 'Caterpillar', '420F', 'available'), ('{{TENANT_ID}}', 'CAM-001', 'Camion Volteo Kenworth', 'vehicle', 'Kenworth', 'T800', 'available'), ('{{TENANT_ID}}', 'MZC-001', 'Mezcladora de Concreto', 'equipment', 'Cipsa', 'MC-350', 'available'); ``` --- ## Referencias - [MAE-015: Activos y Maquinaria](../../02-definicion-modulos/MAE-015-activos-maquinaria/) - [ADR-007: Database Design](../../97-adr/ADR-007-database-design.md) --- *Ultima actualizacion: 2025-12-05*