erp-construccion/docs/04-modelado/database-design/schemas/DDL-SPEC-assets.md

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