25 KiB
25 KiB
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
-- 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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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
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
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
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
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)
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
-- 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
-- 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
Ultima actualizacion: 2025-12-05