> For the complete documentation index, see [llms.txt](https://navixy.com/docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://navixy.com/docs/analytics/es/iot-query/schema-overview/bronze-layer/bdr.md).

# Esquema BDR

BDR organiza dispositivos y entidades de negocio en activos, con Campos personalizados, relaciones y rastros de auditoría. Explore la definición del esquema y las descripciones clave de las tablas

{% hint style="warning" %}
**Este esquema está actualmente en desarrollo.** BDR (Business Data Repository) organiza los datos alrededor de activos: una combinación unificada de un dispositivo y una entidad de negocio, como un vehículo y su conductor, o un dispositivo y sus sensores. IoT Query lee de BDR para exponer estos datos para analítica. Si le interesa el acceso anticipado o tiene preguntas sobre esta funcionalidad, póngase en contacto con <iotquery@navixy.com>.
{% endhint %}

BDR proporciona un marco integral para administrar estructuras organizacionales, actores, dispositivos, activos y sus relaciones en entornos multicliente. Construido sobre PostgreSQL 18 con las `ltree` y `pg_trgm` extensiones, el esquema admite organizaciones jerárquicas, definiciones de Campos personalizados para cualquier tipo de entidad, control de acceso basado en actores y papeles con restricciones a nivel de objeto, bloqueo optimista para ediciones concurrentes y registros de auditoría completos con seguimiento de cambios a nivel de campo. Todas las entidades pueden ampliarse sin modificaciones al esquema, localizarse para implementaciones internacionales y vincularse mediante relaciones polimórficas flexibles.

El esquema aborda escenarios complejos de gestión de datos, incluidas jerarquías de activos de flota en distintos niveles organizacionales, plataformas SaaS multicliente que requieren aislamiento de datos, operaciones impulsadas por cumplimiento con requisitos detallados de auditoría y sistemas que necesitan modelos de datos dinámicos adaptables mediante Campos personalizados en lugar de migraciones de base de datos.

{% hint style="info" %}
El diagrama interactivo del esquema de datos de BDR está disponible en **dbdiagram.io**: <https://dbdiagram.io/d/Navixy-Repo-data-schema-68ad788c1e7a611967a0930e>
{% endhint %}

Encuentre los detalles del esquema de BDR a continuación.

{% code title="esquema de datos de bdr" expandable="true" %}

```sql
// ============================================
// Esquema de BDR (Business Data Repository) - recorrido del cliente
// PostgreSQL 18 con las extensiones ltree, pg_trgm
// Versión: 2.2
// ============================================

// ============================================
// TABLAS BASE DE REFERENCIA (jerarquía ci_base - herencia de tabla única)
// ============================================

Tabla ci_base {
  id uuid [primary key, note: 'Código de tipo de entidad de 4 caracteres incrustado en los bytes 5-8 del UUID, extraído mediante uuid_type_code()']
  code text [not null, note: 'Código legible por máquina, debe comenzar con una letra']
  title_en text [not null, note: 'Intercalación sin distinción entre mayúsculas y minúsculas (ICU)']
  description_en text
  order int [not null, default: 0]
  is_system boolean [not null, default: false]
  discriminator text [not null, note: 'Se autocompleta a partir del id mediante trg_ci_set_discriminator']
  catalog_id uuid [note: 'Catálogo padre para los elementos del catálogo de usuario. NULL para los catálogos del sistema y para las propias definiciones de catálogo']
  organization_id uuid
  parent_id uuid
  path ltree [note: 'Mantenido automáticamente por un trigger cuando is_hierarchical = true']
  is_hierarchical boolean [not null, default: false]
  extra jsonb [not null, default: `{}`]
  version int [not null, default: 1, note: 'Bloqueo optimista']
  created_at timestamptz [no nulo, default: `CURRENT_TIMESTAMP`]
  updated_at timestamptz [no nulo, default: `CURRENT_TIMESTAMP`]
  deleted_at timestamptz

  índices {
    (id) [name: 'idx_ci_active', note: 'parcial: WHERE deleted_at IS NULL']
    (parent_id) [name: 'idx_ci_parent']
    (path) [type: gist, name: 'idx_ci_path_gist']
    (catalog_id) [name: 'idx_ci_catalog']
    (organization_id) [name: 'idx_ci_org']
    (discriminator) [name: 'idx_ci_discriminator']
    (title_en) [nombre: 'idx_ci_title_en']
    (discriminador, código) [único, nombre: 'uq_ci_code_discriminator']
    (catalog_id, code) [único, name: 'uq_ci_code_catalog']
  }
}

Tabla ci_module {
  id uuid [clave primaria]
}

Tabla ci_catalog_category {
  id uuid [clave primaria]
}

Tabla ci_country {
  id uuid [clave primaria]
}

Tabla ci_role {
  id uuid [clave primaria]
}

Tabla ci_entity_type {
  id uuid [clave primaria]
  uuid_discriminator char(4) [not null, note: 'código de 4 caracteres incrustado en los UUID de las entidades']
  is_customizable boolean [not null, default: true, note: 'Si las entidades de este tipo admiten Campos personalizados']
}

Tabla ci_device_status {
  id uuid [clave primaria]
}

Tabla ci_permission_scope {
  id uuid [clave primaria]
  module_id uuid [not null]
  entity_type_id uuid [not null]
  category text [not null]
}

Tabla ci_device_vendor {
  id uuid [primary key, note: 'Fabricantes/proveedores de dispositivos']
}

Tabla ci_device_model {
  id uuid [clave primaria]
  vendor_id uuid [not null]
}

Tabla ci_device_type {
  id uuid [clave primaria]
}

Tabla ci_asset_type {
  id uuid [clave primaria, nota: 'Jerárquico mediante ci_base parent_id/path']
}

Tabla ci_geo_object_type {
  id uuid [clave primaria]
}

Tabla ci_schedule_type {
  id uuid [clave primaria]
}

Tabla ci_asset_group_type {
  id uuid [clave primaria]
}

Tabla Activo_Grupo_type_to_Activo_type_relation {
  id uuid [clave primaria]
  group_type_id uuid [no nulo]
  asset_type_id uuid [no nulo]
  max_items int [nota: 'Máximo de activos de este tipo en Grupo. NULL = ilimitado']

  índices {
    (group_type_id) [nombre: 'idx_agt_at_rel_group']
    (asset_type_id) [name: 'idx_agt_at_rel_asset']
    (group_type_id, asset_type_id) [único]
  }
}

Tabla ci_device_relation_type {
  id uuid [clave primaria]
}

Tabla ci_tag {
  id uuid [clave primaria]
  entity_type_id uuid [nota: 'Tipo de entidad aplicable. NULL = etiqueta universal']
}

Tabla ci_user_catalog_item {
  id uuid [clave primaria, nota: 'Elementos de catálogos definidos por el usuario']
}

Tabla ci_catalog {
  id uuid [clave primaria, nota: 'Definiciones de catálogo. Los catálogos son, a su vez, elementos de catálogo']
  organization_id uuid [no nulo]
  module_id uuid [not null]
  category_id uuid
  fields_schema jsonb [nota: 'Esquema JSON para la validación de item.extra y la interfaz de usuario']

  índices {
    (organization_id) [nombre: 'idx_ci_catalog_org']
    (module_id) [nombre: 'idx_ci_catalog_module']
  }
}

Tabla ci_custom_field_definition {
  id uuid [clave primaria]
  owner_catalog_item_id uuid [not null, note: 'Propietario: entity_type (system) or a specific type such as asset_type']
  target_entity_type_id uuid [no nulo]
  field_type texto [no nulo, nota: 'STRING, TEXT, NUMBER, BOOLEAN, DATE, DATETIME, GEOJSON, SCHEDULE, OPTIONS, DEVICE, REFERENCE, CATALOG, Etiqueta']
  is_required booleano [no nulo, predeterminado: false]
  params jsonb [nota: 'Parámetros específicos del tipo (min/max, opciones, ref, etc.)']

  índices {
    (owner_catalog_item_id) [nombre: 'idx_ci_cfd_owner']
    (target_entity_type_id) [nombre: 'idx_ci_cfd_target']
  }
}

// ============================================
// ENTIDADES PRINCIPALES
// ============================================

Organización de la tabla {
  id uuid [clave primaria]
  parent_id uuid
  ruta ltree
  texto de código [no nulo]
  title_en texto [no nulo]
  external_id texto [nota: 'Identificador del sistema externo para integraciones']
  is_active boolean [no nulo, predeterminado: true]
  is_dealer boolean [no nulo, valor predeterminado: false, nota: 'Si la org puede crear organizaciones hijas']
  version int [no nulo, valor predeterminado: 1]
  created_at timestamptz [no nulo, default: `CURRENT_TIMESTAMP`]
  updated_at timestamptz [no nulo, default: `CURRENT_TIMESTAMP`]
  deleted_at timestamptz
  deleted_by uuid

  índices {
    (parent_id) [name: 'idx_org_parent']
    (path) [type: gist, name: 'idx_org_path_gist']
    (code) [unique, name: 'uq_org_code']
    (external_id) [name: 'idx_org_external_id']
    (title_en) [name: 'idx_org_title', nota: 'El DDL de origen apunta a una columna title inexistente, corregido aquí a title_en']
  }
}

Tabla actor {
  id uuid [clave primaria, nota: 'Entidad abstracta para usuarios e integraciones. Base para ACL']
  actor_type text [no nulo, nota: 'USER, INTEGRATION o SYSTEM']
  version int [no nulo, valor predeterminado: 1]
  created_at timestamptz [no nulo, default: `CURRENT_TIMESTAMP`]
  updated_at timestamptz [no nulo, default: `CURRENT_TIMESTAMP`]
  deleted_at timestamptz
  deleted_by uuid

  índices {
    (actor_type) [name: 'idx_actor_type']
  }
}

Tabla user {
  id uuid [clave primaria]
  identity_provider text [no nulo, default: 'keycloak']
  identity_provider_id uuid [no nulo]
  full_name text [no nulo]
  external_id text
  is_active boolean [no nulo, predeterminado: true]
  created_at timestamptz [no nulo, default: `CURRENT_TIMESTAMP`]
  updated_at timestamptz [no nulo, default: `CURRENT_TIMESTAMP`]
  deleted_at timestamptz
  deleted_by uuid

  índices {
    (identity_provider, identity_provider_id) [unique, name: 'uq_user_idp']
    (external_id) [name: 'idx_user_external_id']
  }
}

Tabla member {
  id uuid [clave primaria, nota: 'Pertenencia de un usuario a una organización']
  user_id uuid [no nulo]
  organization_id uuid [no nulo]
  is_active boolean [no nulo, predeterminado: true]
  custom_fields_data jsonb [no nulo, predeterminado: `{}`, nota: 'Campos personalizados del miembro (puesto, departamento, etc.)']
  created_at timestamptz [no nulo, default: `CURRENT_TIMESTAMP`]
  assigned_at timestamptz [no nulo, predeterminado: `CURRENT_TIMESTAMP`]
  deleted_at timestamptz
  deleted_by uuid

  índices {
    (user_id) [nombre: 'idx_member_user']
    (organization_id) [nombre: 'idx_member_org']
    (user_id, organization_id) [único, nombre: 'uq_member_user_org']
  }
}

Tabla integration {
  id uuid [clave primaria, nota: 'Actor de integración de un sistema externo']
  name text [no nulo]
  credential_ref texto [nota: 'Referencia a las credenciales en una bóveda segura']
  is_active boolean [no nulo, predeterminado: true]
  created_at timestamptz [no nulo, default: `CURRENT_TIMESTAMP`]
  updated_at timestamptz [no nulo, default: `CURRENT_TIMESTAMP`]
  deleted_at timestamptz
  deleted_by uuid
}

// ============================================
// CONTROL DE ACCESO (ACL)
// ============================================

Tabla actor_role {
  id uuid [clave primaria]
  actor_id uuid [no nulo]
  role_id uuid [no nulo]
  assigned_at timestamptz [no nulo, predeterminado: `CURRENT_TIMESTAMP`]
  uuid asignado por
  expire_date timestamptz [nota: 'NULL = permanente']

  índices {
    (actor_id) [nombre: 'idx_actor_role_actor']
    (role_id) [nombre: 'idx_actor_role_role']
    (actor_id, role_id) [único]
  }
}

Tabla acl_role_permission {
  id uuid [clave primaria]
  role_id uuid [no nulo]
  permission_scope_id uuid [no nulo]
  target_entity_id uuid [nota: 'Entidad específica. NULL = todas las entidades del tipo. Polimórfico, sin FK']
  actions int [no nulo, nota: 'Máscara de bits: READ=1, CREATE=2, UPDATE=4, DELETE=8']
  granted_at timestamptz [not null, default: `CURRENT_TIMESTAMP`]
  granted_by uuid

  índices {
    (id_de_Papel) [nombre: 'idx_acl_Papel_perm_Papel']
    (permission_scope_id) [nombre: 'idx_acl_role_perm_scope']
    (role_id, permission_scope_id, target_entity_id) [único]
  }
}

Tabla acl_user_scope {
  id uuid [clave primaria, note: 'Filtro de lista blanca. Vacío = acceso completo al Papel. Lleno = intersección con roles']
  actor_id uuid [no nulo]
  permission_scope_id uuid [no nulo]
  target_entity_id uuid [no nulo]
  actions int [no nulo]

  índices {
    (actor_id, permission_scope_id) [nombre: 'idx_acl_user_scope_actor']
    (actor_id, permission_scope_id, target_entity_id) [único]
  }
}

// ============================================
// ENTIDADES DE NEGOCIO
// ============================================

Tabla device {
  id uuid [clave primaria]
  organization_id uuid [no nulo]
  device_type_id uuid [no nulo]
  model_id uuid [nota: 'Opcional']
  status_id uuid [no nulo]
  texto del título [no nulo]
  custom_fields_data jsonb [no nulo, valor predeterminado: `{}`]
  version int [no nulo, valor predeterminado: 1]
  created_at timestamptz [no nulo, default: `CURRENT_TIMESTAMP`]
  updated_at timestamptz [no nulo, default: `CURRENT_TIMESTAMP`]
  deleted_at timestamptz
  deleted_by uuid

  índices {
    (organization_id) [nombre: 'idx_device_org']
    (device_type_id) [nombre: 'idx_device_type']
    (model_id) [nombre: 'idx_device_model']
    (status_id) [nombre: 'idx_device_status']
    (title) [tipo: gin, nombre: 'idx_device_title_trgm', nota: 'gin_trgm_ops for fuzzy search']
  }
}

Tabla device_identifier {
  id uuid [clave primaria]
  device_id uuid [no nulo]
  type text [no nulo, nota: 'UUID, IMEI, MEID_HEX, MEID_DEC, MAC_ADDRESS, SERIAL_NUMBER, CUSTOM']
  texto de valor [no nulo]
  texto de espacio de nombres [nota: 'Ámbito de unicidad. NULL = global']
  created_at timestamptz [no nulo, default: `CURRENT_TIMESTAMP`]
  updated_at timestamptz [no nulo, default: `CURRENT_TIMESTAMP`]

  índices {
    (device_id) [nombre: 'idx_device_identifier_device']
    (value) [nombre: 'idx_device_identifier_value']
    (type, value) [único, nombre: 'uq_device_identifier_global', nota: 'parcial: WHERE namespace IS NULL']
    (type, value, namespace) [único, nombre: 'uq_device_identifier_namespaced', nota: 'parcial: WHERE namespace IS NOT NULL']
  }
}

Tabla device_relation {
  id uuid [clave primaria]
  first_id uuid [no nulo]
  second_id uuid [no nulo]
  relation_type_id uuid [no nulo, nota: 'Restricción de verificación: first_id <> second_id']

  índices {
    (first_id) [nombre: 'idx_device_relation_first']
    (second_id) [nombre: 'idx_device_relation_second']
    (first_id, second_id, relation_type_id) [único]
  }
}

Tabla inventory {
  id uuid [clave primaria]
  organization_id uuid [no nulo]
  texto del título [no nulo]
  texto de código [no nulo]
  created_at timestamptz [no nulo, default: `CURRENT_TIMESTAMP`]
  updated_at timestamptz [no nulo, default: `CURRENT_TIMESTAMP`]
  deleted_at timestamptz
  deleted_by uuid

  índices {
    (organization_id) [nombre: 'idx_inventory_org']
    (organization_id, code) [único, nombre: 'uq_inventory_org_code']
  }
}

Tabla device_inventory_relation {
  id uuid [clave primaria]
  device_id uuid [no nulo]
  inventory_id uuid [no nulo]
  assigned_at timestamptz [no nulo, predeterminado: `CURRENT_TIMESTAMP`]
  uuid asignado por
  deleted_at timestamptz
  deleted_by uuid

  índices {
    (device_id) [único, nombre: 'uq_device_inventory_active', nota: 'parcial: WHERE deleted_at IS NULL - un inventario activo por dispositivo']
    (inventory_id) [nombre: 'idx_device_inventory_rel_inventory']
  }
}

Activo de la tabla {
  id uuid [clave primaria]
  organization_id uuid [no nulo]
  asset_type_id uuid [no nulo]
  texto del título [no nulo]
  custom_fields_data jsonb [no nulo, valor predeterminado: `{}`]
  version int [no nulo, valor predeterminado: 1]
  created_at timestamptz [no nulo, default: `CURRENT_TIMESTAMP`]
  updated_at timestamptz [no nulo, default: `CURRENT_TIMESTAMP`]
  deleted_at timestamptz
  deleted_by uuid

  índices {
    (organization_id) [name: 'idx_asset_org']
    (asset_type_id) [nombre: 'idx_asset_type']
    (título) [tipo: gin, nombre: 'idx_asset_title_trgm']
  }
}

Tabla activo_grupo {
  id uuid [clave primaria]
  organization_id uuid [no nulo]
  group_type_id uuid [no nulo]
  title_en texto [no nulo]
  texto de color
  version int [no nulo, valor predeterminado: 1]
  created_at timestamptz [no nulo, default: `CURRENT_TIMESTAMP`]
  updated_at timestamptz [no nulo, default: `CURRENT_TIMESTAMP`]
  deleted_at timestamptz
  deleted_by uuid

  índices {
    (organization_id) [name: 'idx_activo_grupo_org']
    (group_type_id) [nombre: 'idx_asset_group_type']
  }
}

Tabla activo_grupo_elemento {
  id uuid [clave primaria]
  id de grupo uuid [no nulo]
  asset_id uuid [no nulo]
  attached_at timestamptz [no nulo, predeterminado: `CURRENT_TIMESTAMP`]
  detached_at timestamptz [nota: 'NULL = actualmente adjunto']

  índices {
    (grupo_id) [nombre: 'idx_activo_grupo_item_grupo']
    (asset_id) [nombre: 'idx_asset_group_item_asset']
    (group_id, asset_id, detached_at) [único]
  }
}

Objeto geo_object {
  id uuid [clave primaria, nota: 'geocercas, puntos de interés, rutas']
  organization_id uuid [no nulo]
  geo_object_type_id uuid [no nulo]
  texto del título [no nulo]
  custom_fields_data jsonb [not null, default: `{}`, note: 'Incluye geometría geojson']
  version int [no nulo, valor predeterminado: 1]
  created_at timestamptz [no nulo, default: `CURRENT_TIMESTAMP`]
  updated_at timestamptz [no nulo, default: `CURRENT_TIMESTAMP`]
  deleted_at timestamptz
  deleted_by uuid

  índices {
    (organization_id) [nombre: 'idx_geo_Objeto_org']
    (geo_object_type_id) [nombre: 'idx_geo_object_type']
    (título) [tipo: gin, nombre: 'idx_geo_objeto_title_trgm']
  }
}

Programación de la tabla {
  id uuid [clave primaria]
  organization_id uuid [no nulo]
  schedule_type_id uuid [no nulo]
  texto del título [no nulo]
  custom_fields_data jsonb [not null, default: `{}`, nota: 'Aquí se almacenan los datos de la programación']
  version int [no nulo, valor predeterminado: 1]
  created_at timestamptz [no nulo, default: `CURRENT_TIMESTAMP`]
  updated_at timestamptz [no nulo, default: `CURRENT_TIMESTAMP`]
  deleted_at timestamptz
  deleted_by uuid

  índices {
    (organization_id) [nombre: 'idx_schedule_org']
    (schedule_type_id) [name: 'idx_schedule_type']
    (título) [type: gin, name: 'idx_schedule_title_trgm']
  }
}

// ============================================
// LOCALIZACIÓN
// ============================================

Tabla i18n_text {
  entity_id uuid [pk]
  field_code text [pk]
  locale text [pk]
  texto de valor [no nulo]

  índices {
    (entity_id) [name: 'idx_i18n_entity']
    (locale) [name: 'idx_i18n_locale']
    (value) [type: gin, name: 'idx_i18n_text_trgm']
  }
}

// ============================================
// Campos personalizados - valores (tablas de caché, una por field_type)
// ============================================

Tabla custom_field_value_text {
  entity_id uuid [pk]
  field_definition_id uuid [pk]
  value_index smallint [pk, default: 0]
  texto de valor [no nulo]

  índices {
    (field_definition_id, value) [name: 'idx_cfv_text_value']
  }
}

Tabla custom_field_value_number {
  entity_id uuid [pk]
  field_definition_id uuid [pk]
  value_index smallint [pk, default: 0]
  value numeric [not null]
}

Tabla custom_field_value_boolean {
  entity_id uuid [pk]
  field_definition_id uuid [pk]
  value_index smallint [pk, default: 0]
  value boolean [not null]
}

Tabla custom_field_value_date {
  entity_id uuid [pk]
  field_definition_id uuid [pk]
  value_index smallint [pk, default: 0]
  value date [not null]
}

Tabla custom_field_value_datetime {
  entity_id uuid [pk]
  field_definition_id uuid [pk]
  value_index smallint [pk, default: 0]
  value timestamptz [not null]
}

Tabla custom_field_value_geojson {
  entity_id uuid [pk]
  field_definition_id uuid [pk]
  value_index smallint [pk, default: 0]
  value jsonb [no nulo]
}

Tabla custom_field_value_schedule {
  entity_id uuid [pk]
  field_definition_id uuid [pk]
  value_index smallint [pk, default: 0]
  value jsonb [no nulo]
}

Tabla custom_field_value_option {
  entity_id uuid [pk]
  field_definition_id uuid [pk]
  value_index smallint [pk, default: 0]
  ref_item_id uuid [no nulo, nota: 'Hace referencia a ci_user_catalog_item']
}

Tabla custom_field_value_device {
  entity_id uuid [pk]
  field_definition_id uuid [pk]
  value_index smallint [pk, default: 0]
  ref_device_id uuid [no nulo]
}

Tabla custom_field_value_entity {
  entity_id uuid [pk]
  field_definition_id uuid [pk]
  value_index smallint [pk, default: 0]
  ref_entity_id uuid [no nulo, nota: 'Referencia polimórfica, sin clave foránea']
}

Tabla custom_field_value_catalog {
  entity_id uuid [pk]
  field_definition_id uuid [pk]
  value_index smallint [pk, default: 0]
  ref_item_id uuid [no nulo, nota: 'Hace referencia a ci_base']
}

Tabla custom_field_value_etiqueta {
  entity_id uuid [pk]
  field_definition_id uuid [pk]
  value_index smallint [pk, default: 0]
  ref_tag_id uuid [no nulo]
}

// ============================================
// AUDITORÍA
// ============================================

Enum source_type {
  WEB
  MÓVIL
  API
  INTERNO
  Integración
}

Tabla audit_event {
  id uuid [no nulo]
  organization_id uuid
  event_category texto [no nulo, nota: 'auth o dominio']
  actor_id uuid
  ip_address inet
  texto de user_agent
  source_type source_type [no nulo, valor predeterminado: 'API']
  texto de source_name [nota: 'Nombre de la aplicación de origen']
  trace_id uuid [nota: «ID de trazado distribuido para la correlación de registros»]
  aggregate_type texto [nota: 'Tipo de entidad: dispositivo, Activo, usuario, etc.']
  aggregate_id uuid [nota: 'Polimórfico, sin FK']
  event_type text [not null, nota: 'CREATED, UPDATED, DELETED, RESTORED, ROLE_ASSIGNED, etc.']
  event_data jsonb [nota: 'Carga útil con delta de changed_fields']
  occurred_at timestamptz [no nulo, predeterminado: `CURRENT_TIMESTAMP`]

  Nota: 'Particionado POR RANGO (occurred_at), particiones mensuales, creadas automáticamente mediante create_audit_partition_if_needed()'

  índices {
    (id, occurred_at) [pk]
    (actor_id, occurred_at) [name: 'idx_audit_event_actor']
    (aggregate_type, aggregate_id, occurred_at) [nombre: 'idx_audit_event_aggregate']
    (event_category, occurred_at) [name: 'idx_audit_event_category']
    (event_type, occurred_at) [name: 'idx_audit_event_type']
    (organization_id, occurred_at) [name: 'idx_audit_event_org']
    (trace_id) [name: 'idx_audit_event_trace']
  }
}

// ============================================
// RELACIONES
// ============================================

Ref.: ci_base.catalog_id > ci_catalog.id
Ref.: ci_base.organization_id > organization.id
Ref.: ci_base.parent_id > ci_base.id

Ref.: ci_module.id - ci_base.id
Ref.: ci_catalog_category.id - ci_base.id
Ref.: ci_country.id - ci_base.id
Ref: ci_role.id - ci_base.id
Ref.: ci_entity_type.id - ci_base.id
Ref.: ci_device_status.id - ci_base.id
Ref.: ci_permission_scope.id - ci_base.id
Ref: ci_device_vendor.id - ci_base.id
Referencia: ci_device_model.id - ci_base.id
Ref.: ci_device_type.id - ci_base.id
Ref.: ci_Activo_type.id - ci_base.id
Ref: ci_geo_object_type.id - ci_base.id
Referencia: ci_schedule_type.id - ci_base.id
Ref.: ci_activo_grupo_type.id - ci_base.id
Ref: ci_device_relation_type.id - ci_base.id
Ref: ci_tag.id - ci_base.id
Ref.: ci_user_catalog_item.id - ci_base.id
Referencia: ci_catalog.id - ci_base.id
Ref: ci_custom_field_definition.id - ci_base.id

Ref.: ci_device_model.vendor_id > ci_device_vendor.id
Ref.: ci_permission_scope.module_id > ci_module.id
Ref.: ci_permission_scope.entity_type_id > ci_entity_type.id
Ref: Activo_Grupo_type_to_Activo_type_relation.Grupo_type_id > ci_Activo_Grupo_type.id
Ref: asset_group_type_to_asset_type_relation.asset_type_id > ci_asset_type.id
Ref: ci_tag.entity_type_id > ci_entity_type.id
Ref: ci_catalog.organization_id > organization.id
Ref.: ci_catalog.module_id > ci_module.id
Ref: ci_catalog.category_id > ci_catalog_category.id
Ref: ci_custom_field_definition.Propietario_catalog_item_id > ci_base.id
Ref.: ci_custom_field_definition.target_entity_type_id > ci_entity_type.id

Ref: organization.parent_id > organization.id
Ref: organization.deleted_by > actor.id

Ref: actor.deleted_by > actor.id

Referencia: user.id - actor.id
Referencia: user.deleted_by > actor.id

Ref.: member.user_id > user.id
Ref: member.organization_id > organization.id
Ref: member.deleted_by > actor.id

Referencia: integration.id - actor.id
Ref: integration.deleted_by > actor.id

Ref: actor_role.actor_id > actor.id
Ref: actor_role.role_id > ci_role.id
Ref: actor_role.assigned_by > actor.id

Ref: acl_role_permission.role_id > ci_role.id
Ref: acl_role_permission.permission_scope_id > ci_permission_scope.id
Ref: acl_role_permission.granted_by > actor.id

Ref: acl_user_scope.actor_id > actor.id
Ref: acl_user_scope.permission_scope_id > ci_permission_scope.id

Ref.: device.organization_id > organization.id
Referencia: device.device_type_id > ci_device_type.id
Ref.: device.model_id > ci_device_model.id
Referencia: device.status_id > ci_device_status.id
Ref: device.deleted_by > actor.id

Ref: device_identifier.device_id > device.id

Ref.: device_relation.first_id > device.id
Ref.: device_relation.second_id > device.id
Ref.: device_relation.relation_type_id > ci_device_relation_type.id

Ref.: inventory.organization_id > organization.id
Ref.: inventory.deleted_by > actor.id

Ref.: device_inventory_relation.device_id > device.id
Ref.: device_inventory_relation.inventory_id > inventory.id
Ref.: device_inventory_relation.assigned_by > actor.id
Ref.: device_inventory_relation.deleted_by > actor.id

Ref.: Activo.organization_id > organization.id
Ref: Activo.asset_type_id > ci_asset_type.id
Ref: Activo.deleted_by > actor.id

Ref: Activo_Grupo.organization_id > organization.id
Ref: asset_group.group_type_id > ci_asset_group_type.id
Ref: Activo_Grupo.deleted_by > actor.id

Ref: asset_group_item.group_id > asset_group.id
Ref: asset_group_item.activo_id > activo.id

Ref: geo_object.organization_id > organization.id
Ref: geo_object.geo_object_type_id > ci_geo_object_type.id
Ref: geo_object.deleted_by > actor.id

Ref: schedule.organization_id > organization.id
Ref: schedule.schedule_type_id > ci_schedule_type.id
Ref: schedule.deleted_by > actor.id

Ref: custom_field_value_text.field_definition_id > ci_custom_field_definition.id
Ref: custom_field_value_number.field_definition_id > ci_custom_field_definition.id
Ref: custom_field_value_boolean.field_definition_id > ci_custom_field_definition.id
Ref: custom_field_value_date.field_definition_id > ci_custom_field_definition.id
Ref: custom_field_value_datetime.field_definition_id > ci_custom_field_definition.id
Ref: custom_field_value_geojson.field_definition_id > ci_custom_field_definition.id
Ref: custom_field_value_schedule.field_definition_id > ci_custom_field_definition.id
Ref.: custom_field_value_option.field_definition_id > ci_custom_field_definition.id
Ref: custom_field_value_option.ref_item_id > ci_user_catalog_item.id
Ref.: custom_field_value_device.field_definition_id > ci_custom_field_definition.id
Ref.: custom_field_value_device.ref_device_id > device.id
Referencia: custom_field_value_entity.field_definition_id > ci_custom_field_definition.id
Ref: custom_field_value_catalog.field_definition_id > ci_custom_field_definition.id
Ref: custom_field_value_catalog.ref_item_id > ci_base.id
Ref: custom_field_value_tag.field_definition_id > ci_custom_field_definition.id
Ref: custom_field_value_tag.ref_tag_id > ci_tag.id

Ref.: audit_event.organization_id > organization.id
Ref: audit_event.actor_id > actor.id
```

{% endcode %}

## Frecuencia de actualización

Los datos en BDR se sincronizan en tiempo real con los sistemas de origen. Las actualizaciones ocurren de inmediato a medida que se producen los cambios, y las trazas de auditoría registran todas las modificaciones para el cumplimiento normativo y el análisis histórico.

## `ci_base`

BDR utiliza un **patrón de herencia de tabla única** patrón para todos los datos de referencia a través de la `ci_base` tabla. Este diseño consolida los diccionarios del sistema, las clasificaciones y los elementos de referencia definidos por el usuario en una estructura unificada, proporcionando consistencia y flexibilidad en todo el esquema.

**Arquitectura:**

El `ci_base` tabla sirve como base para todos los datos de referencia, usando un `discriminador` campo para identificar el tipo de referencia específico. Cada tipo de referencia tiene una tabla correspondiente (como `ci_device_type`, `ci_Activo_type`) que comparte el mismo `id` como `ci_base`, creando una relación de herencia segura en cuanto a tipos. El `discriminador` no se establece manualmente. Un disparador lo deriva automáticamente del código de tipo de 4 caracteres incrustado en el UUID de la entidad, y la inserción falla si el código de tipo del UUID no se reconoce.

Las jerarquías (por ejemplo, tipos de Activo anidados o Etiquetas) se manejan directamente en `ci_base` a través de su propio `parent_id`/`path` columnas en lugar de mediante tablas de categorías separadas. Una sola `is_hierarchical` la bandera activa esto por cada elemento del catálogo.

**Cómo se conectan las entidades de negocio a ci\_base:**

Todas las entidades de negocio en BDR hacen referencia a `ci_base` subtipos para definir su clasificación y comportamiento:

* `organización` y `usuario`/`miembro` son *no* tipados a través de `ci_base`, ya que las organizaciones y los usuarios son entidades principales en sí mismas, no clasificadas por un elemento de catálogo
* `dispositivo` → referencias `ci_device_type`, opcionalmente `ci_device_model` (que a su vez hace referencia a `ci_device_vendor`), y `ci_device_status`
* `Activo` → referencias `ci_Activo_type`
* `Activo_Grupo` → referencias `ci_Activo_Grupo_type`, con los tipos de miembros permitidos restringidos mediante `Activo_Grupo_type_to_Activo_type_relation`
* `geo_Objeto` → referencias `ci_geo_Objeto_type`
* `programación` → referencias `ci_schedule_type`
* `inventario` no tiene un tipo de elemento de catálogo propio

**Categorías de tipos de referencia:**

| Categoría                            | Tablas                                                                                                                 | Propósito                                                                                                               |
| ------------------------------------ | ---------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| **Configuración del sistema**        | `ci_module`, `ci_country`, `ci_papel`                                                                                  | Defina los módulos del sistema, las referencias geográficas y los roles de usuario                                      |
| **Definiciones de tipos de entidad** | `ci_entity_type`, `ci_device_type`, `ci_Activo_type`, `ci_geo_Objeto_type`, `ci_schedule_type`, `ci_Activo_Grupo_type` | Clasifique todas las entidades de negocio por tipo                                                                      |
| **Catálogo de dispositivos**         | `ci_device_vendor`, `ci_device_model`, `ci_device_status`                                                              | Modele los dispositivos por fabricante, modelo y estado actual                                                          |
| **Control de acceso**                | `ci_permission_scope`                                                                                                  | Defina qué permisos se pueden otorgar (vinculado con `ci_module` y `ci_entity_type`)                                    |
| **Relaciones**                       | `ci_device_relation_type`                                                                                              | Definir tipos de relaciones entre dispositivos (maestro-esclavo, respaldo, etc.)                                        |
| **Categorización**                   | `ci_Etiqueta`, `ci_catalog_category`, `ci_user_catalog_item`                                                           | Activar el etiquetado flexible y los catálogos definidos por el usuario                                                 |
| **Catálogo / Campos personalizados** | `ci_catalog`, `ci_custom_field_definition`                                                                             | Metadatos del catálogo de catálogos y de campos personalizados, ambos modelados como elementos de catálogo en sí mismos |

<details>

<summary><strong>Patrones de consulta de ejemplo</strong></summary>

```sql
-- Obtener todos los tipos de dispositivo para una organización (del sistema + personalizados)
SELECT cb.id, cb.code, cb.title_en, cb.is_system
FROM bdr.ci_base cb
JOIN bdr.ci_device_type dt ON dt.id = cb.id
WHERE cb.discriminator = 'device_type'
  AND (cb.is_system = true OR cb.organization_id = $org_id)
  AND cb.deleted_at IS NULL;

-- Obtener modelos de dispositivo con su proveedor
SELECT
  cb.code as model_code,
  cb.title_en as model_name,
  vendor_cb.title_en as vendor_name
FROM bdr.ci_base cb
JOIN bdr.ci_device_model dm ON dm.id = cb.id
JOIN bdr.ci_device_vendor v ON v.id = dm.vendor_id
JOIN bdr.ci_base vendor_cb ON vendor_cb.id = v.id
WHERE cb.discriminator = 'device_model'
  AND cb.deleted_at IS NULL;

-- Obtener la estructura jerárquica de etiquetas
SELECT cb.id, cb.code, cb.title_en, cb.path, cb.parent_id
FROM bdr.ci_base cb
JOIN bdr.ci_tag t ON t.id = cb.id
WHERE cb.discriminator = 'Etiqueta'
  AND cb.deleted_at IS NULL
ORDER BY cb.path;
```

</details>

## Tablas clave por categoría

Las tablas en BDR están organizadas en categorías funcionales. Las descripciones a continuación resumen las tablas más importantes según su propósito comercial.

<details>

<summary><code>organización</code></summary>

**Propósito:** Gestión jerárquica de la organización

<table><thead><tr><th width="139">Atributo</th><th>Detalles</th></tr></thead><tbody><tr><td><strong>Campos clave</strong></td><td><code>id</code>, <code>parent_id</code>, <code>path</code>, <code>code</code>, <code>title_en</code>, <code>is_active</code>, <code>is_dealer</code>, <code>version</code>, <code>deleted_at</code></td></tr><tr><td><strong>Indexación</strong></td><td>Índice GiST en <code>path</code> para consultas jerárquicas, índice único en <code>code</code>, índice en <code>parent_id</code></td></tr><tr><td><strong>Notas especiales</strong></td><td>Utiliza ltree para jerarquías multinivel. <code>is_dealer</code> otorga el derecho a crear organizaciones hijas. <code>version</code> admite bloqueo optimista</td></tr></tbody></table>

</details>

<details>

<summary><code>actor</code></summary>

**Propósito:** Abstracción compartida por cada principal de ACL: usuarios, integraciones y futuros actores del sistema

<table><thead><tr><th width="139">Atributo</th><th>Detalles</th></tr></thead><tbody><tr><td><strong>Campos clave</strong></td><td><code>id</code>, <code>actor_type</code> (<code>USUARIO</code>, <code>Integración</code>, <code>SISTEMA</code>), <code>version</code></td></tr><tr><td><strong>Notas especiales</strong></td><td>Todas las tablas ACL (<code>actor_papel</code>, <code>acl_role_permission</code>, <code>acl_user_scope</code>) y cada <code>eliminado por</code> Columna de ignición apagada <code>actor_id</code>, no <code>user_id</code>, por lo que las integraciones también pueden tener roles y eliminar registros</td></tr></tbody></table>

</details>

<details>

<summary><code>usuario</code> / <code>miembro</code></summary>

**Propósito:** Cuentas de usuario y sus membresías en organizaciones

<table><thead><tr><th width="139">Atributo</th><th>Detalles</th></tr></thead><tbody><tr><td><strong>Campos clave</strong></td><td><code>usuario</code>: <code>id</code> (compartido con <code>actor</code>), <code>identity_provider</code>, <code>identity_provider_id</code>, <code>full_name</code>. <code>miembro</code>: <code>user_id</code>, <code>organization_id</code>, <code>custom_fields_data</code></td></tr><tr><td><strong>Indexación</strong></td><td>Índice único sobre (<code>identity_provider</code>, <code>identity_provider_id</code>). Índice único sobre (<code>user_id</code>, <code>organization_id</code>) en <code>miembro</code></td></tr><tr><td><strong>Notas especiales</strong></td><td>La pertenencia de un usuario a una organización es una fila separada en <code>miembro</code>, por lo que un usuario puede pertenecer a varias organizaciones. Los usuarios se autentican a través de un proveedor de identidad externo (Keycloak de forma predeterminada)</td></tr></tbody></table>

</details>

<details>

<summary><code>integración</code></summary>

**Propósito:** Sistemas externos que actúan sobre la plataforma (p. ej., mediante Llaves API)

<table><thead><tr><th width="139">Atributo</th><th>Detalles</th></tr></thead><tbody><tr><td><strong>Campos clave</strong></td><td><code>id</code> (compartido con <code>actor</code>), <code>nombre</code>, <code>referencia_de_credencial</code>, <code>is_active</code></td></tr><tr><td><strong>Notas especiales</strong></td><td><code>referencia_de_credencial</code> apunta a una bóveda externa segura, por lo que no se almacenan credenciales en esta tabla</td></tr></tbody></table>

</details>

<details>

<summary><code>dispositivo</code> / <code>identificador_del_dispositivo</code></summary>

**Propósito:** Dispositivos físicos de Seguimiento y sus identificadores de hardware

<table><thead><tr><th width="139">Atributo</th><th>Detalles</th></tr></thead><tbody><tr><td><strong>Campos clave</strong></td><td><code>dispositivo</code>: <code>id</code>, <code>organization_id</code>, <code>id_del_tipo_de_dispositivo</code>, <code>model_id</code>, <code>status_id</code>, <code>título</code>, <code>custom_fields_data</code>, <code>version</code>. <code>identificador_del_dispositivo</code>: <code>device_id</code>, <code>tipo</code>, <code>value</code>, <code>espacio_de_nombres</code></td></tr><tr><td><strong>Indexación</strong></td><td>Índice de trigramas GIN sobre <code>título</code> para búsqueda difusa. Índice único sobre (<code>tipo</code>, <code>value</code>) para identificadores globales, o (<code>tipo</code>, <code>value</code>, <code>espacio_de_nombres</code>) cuando usa espacio de nombres</td></tr><tr><td><strong>Notas especiales</strong></td><td>Un dispositivo puede contener varios identificadores (IMEI, MAC, número de serie, etc.) en lugar de una sola columna de ID de hardware. Los Campos personalizados se almacenan directamente en <code>custom_fields_data</code> en la fila del dispositivo</td></tr></tbody></table>

</details>

<details>

<summary><code>Activo</code></summary>

**Propósito:** Activos físicos o virtuales

<table><thead><tr><th width="139">Atributo</th><th>Detalles</th></tr></thead><tbody><tr><td><strong>Campos clave</strong></td><td><code>id</code>, <code>organization_id</code>, <code>tipo_de_activo_id</code>, <code>título</code>, <code>custom_fields_data</code>, <code>version</code></td></tr><tr><td><strong>Indexación</strong></td><td>Índices en <code>organization_id</code> y <code>tipo_de_activo_id</code>. Índice trigram GIN en <code>título</code></td></tr><tr><td><strong>Notas especiales</strong></td><td>Campos personalizados se almacenan directamente en la fila, no mediante una tabla separada de entidad personalizable</td></tr></tbody></table>

</details>

<details>

<summary><code>inventario</code> / <code>relación_dispositivo_inventario</code></summary>

**Propósito:** Registros de inventario y de almacén, y asignación de dispositivos al inventario

<table><thead><tr><th width="139">Atributo</th><th>Detalles</th></tr></thead><tbody><tr><td><strong>Campos clave</strong></td><td><code>inventario</code>: <code>id</code>, <code>organization_id</code>, <code>título</code>, <code>code</code>. <code>relación_dispositivo_inventario</code>: <code>device_id</code>, <code>inventory_id</code>, <code>assigned_at</code>, <code>deleted_at</code></td></tr><tr><td><strong>Indexación</strong></td><td>Índice único sobre (<code>organization_id</code>, <code>code</code>). Un índice único parcial garantiza que un dispositivo tenga como máximo una asignación de inventario activa</td></tr><tr><td><strong>Notas especiales</strong></td><td>La asignación se puede eliminar de forma lógica, por lo que se conserva el historial del inventario de dispositivos</td></tr></tbody></table>

</details>

<details>

<summary><code>geo_Objeto</code></summary>

**Propósito:** Geocercas, puntos de interés y rutas

<table><thead><tr><th width="139">Atributo</th><th>Detalles</th></tr></thead><tbody><tr><td><strong>Campos clave</strong></td><td><code>id</code>, <code>organization_id</code>, <code>geo_object_type_id</code>, <code>título</code>, <code>custom_fields_data</code></td></tr><tr><td><strong>Notas especiales</strong></td><td>Geometría (anteriormente un dedicado <code>geojson</code> la columna) ahora se almacena como un campo personalizado dentro de <code>custom_fields_data</code></td></tr></tbody></table>

</details>

<details>

<summary><code>programación</code></summary>

**Propósito:** Programaciones reutilizables referenciadas por otras entidades

<table><thead><tr><th width="139">Atributo</th><th>Detalles</th></tr></thead><tbody><tr><td><strong>Campos clave</strong></td><td><code>id</code>, <code>organization_id</code>, <code>schedule_type_id</code>, <code>título</code>, <code>custom_fields_data</code></td></tr><tr><td><strong>Notas especiales</strong></td><td>Los datos de la programación en sí (reglas de recurrencia, ventanas de tiempo) se almacenan en <code>custom_fields_data</code></td></tr></tbody></table>

</details>

<details>

<summary><code>Activo_Grupo</code></summary>

**Propósito:** Agrupación de Activo con seguimiento histórico

<table><thead><tr><th width="139">Atributo</th><th>Detalles</th></tr></thead><tbody><tr><td><strong>Campos clave</strong></td><td><code>id</code>, <code>organization_id</code>, <code>id_tipo_grupo</code>, <code>title_en</code>, <code>color</code></td></tr><tr><td><strong>Relaciones</strong></td><td><code>FROM bdr.Activo_Grupo AS ag JOIN bdr.Activo_Grupo_item AS agi ON agi.Grupo_id = ag.id JOIN bdr.Activo AS a ON a.id = agi.Activo_id WHERE agi.detached_at IS NULL</code></td></tr><tr><td><strong>Notas especiales</strong></td><td>Membresía basada en el tiempo mediante <code>Activo_Grupo_elemento</code>, consultar los miembros actuales con <code>WHERE detached_at IS NULL</code>. Los tipos de miembros permitidos por tipo de grupo se restringen mediante <code>Activo_Grupo_type_to_Activo_type_relation</code></td></tr></tbody></table>

</details>

<details>

<summary><code>ci_custom_field_definition</code></summary>

**Propósito:** Definiciones y metadatos de campos personalizados, modelados como un elemento de catálogo

<table><thead><tr><th width="139">Atributo</th><th>Detalles</th></tr></thead><tbody><tr><td><strong>Campos clave</strong></td><td><code>id</code> (compartido con <code>ci_base</code>), <code>Propietario_catalog_item_id</code>, <code>target_entity_type_id</code>, <code>field_type</code>, <code>is_required</code>, <code>params</code></td></tr><tr><td><strong>Contenido</strong></td><td>12 tipos de campos: <code>STRING</code>, <code>TEXT</code>, <code>NUMBER</code>, <code>BOOLEAN</code>, <code>DATE</code>, <code>DATETIME</code>, <code>GEOJSON</code>, <code>PROGRAMACIÓN</code>, <code>OPCIONES</code>, <code>DISPOSITIVO</code>, <code>REFERENCIA</code>, <code>CATÁLOGO</code>, <code>ETIQUETA</code></td></tr><tr><td><strong>Notas especiales</strong></td><td>Los valores se almacenan tanto como fuente de verdad (<code>custom_fields_data</code> JSONB en la entidad propietaria) y, por tipo, en un <code>custom_field_value_*</code> tabla de caché para filtrar/ordenar. <code>OPCIONES</code> es un envoltorio de <code>CATÁLOGO</code>, y cada <code>OPCIONES</code> el campo obtiene su propio catálogo de usuarios oculto internamente</td></tr></tbody></table>

</details>

<details>

<summary><code>acl_role_permission</code></summary>

**Propósito:** Gestión de permisos basada en Papel

<table><thead><tr><th width="139">Atributo</th><th>Detalles</th></tr></thead><tbody><tr><td><strong>Campos clave</strong></td><td><code>id</code>, <code>identificador_de_rol</code>, <code>id_del_alcance_de_permisos</code>, <code>target_entity_id</code>, <code>acciones</code></td></tr><tr><td><strong>Contenido</strong></td><td>Máscara de bits de acciones (READ=1, CREATE=2, UPDATE=4, DELETE=8), permisos específicos del destino o de todo el tipo de entidad</td></tr><tr><td><strong>Relaciones</strong></td><td><code>FROM bdr.actor_role AS ar JOIN bdr.acl_role_permission AS rp ON rp.role_id = ar.role_id WHERE ar.actor_id = $actor_id</code></td></tr><tr><td><strong>Notas especiales</strong></td><td>Permisos efectivos = permisos de Papel ∩ <code>acl_user_scope</code>. De un actor <code>acl_user_scope</code> es un filtro de lista blanca: vacío significa acceso sin restricciones por rol, lleno significa que el acceso se restringe a los objetos listados</td></tr></tbody></table>

</details>

<details>

<summary><code>evento de auditoría</code></summary>

**Propósito:** Registro de auditoría unificado de todos los cambios del sistema

<table><thead><tr><th width="139">Atributo</th><th>Detalles</th></tr></thead><tbody><tr><td><strong>Campos clave</strong></td><td><code>id</code>, <code>organization_id</code>, <code>categoría de evento</code>, <code>actor_id</code>, <code>source_type</code>, <code>trace_id</code>, <code>aggregate_type</code>, <code>aggregate_id</code>, <code>event_type</code>, <code>event_data</code>, <code>occurred_at</code></td></tr><tr><td><strong>Indexación</strong></td><td>Índices en (<code>actor_id</code>, <code>occurred_at</code>), (<code>aggregate_type</code>, <code>aggregate_id</code>, <code>occurred_at</code>), (<code>categoría de evento</code>, <code>occurred_at</code>), (<code>organization_id</code>, <code>occurred_at</code>)</td></tr><tr><td><strong>Notas especiales</strong></td><td>Particionado físicamente por <code>occurred_at</code> (particiones de rango mensuales, creadas automáticamente). <code>categoría de evento</code> es <code>auth</code> (LOGIN, LOGOUT, FAILED_LOGIN, PASSWORD_RESET, SESSION_EXPIRED) o <code>dominio</code> (CREATED, UPDATED, DELETED, RESTORED, ROLE_ASSIGNED, ROLE_REVOKED, PERMISSION_GRANTED, PERMISSION_REVOKED, LINKED, UNLINKED, ATTACHED, DETACHED). <code>source_type</code> y <code>trace_id</code> admitir el rastreo distribuido, y la tabla almacena los deltas de cambio a nivel de campo en <code>event_data</code> JSONB</td></tr></tbody></table>

</details>

## Relaciones de datos

BDR implementa patrones de relación sofisticados para un modelado de datos flexible:

**Estructuras jerárquicas**

* Las organizaciones usan rutas ltree para consultas eficientes de árboles
* Elementos de referencia (`ci_base`) admiten jerarquías opcionales mediante sus propias `parent_id`/`path`, restringido por `is_hierarchical`
* Mantenimiento automático de rutas mediante disparadores de base de datos

**Patrones de herencia**

* Herencia de ID: `ci_base` → tablas de tipo de referencia (`ci_device_type`, `ci_Activo_type`, etc.), y `actor` → `usuario` y `integración`
* Campos personalizados se adjuntan por entidad mediante una `custom_fields_data` columna JSONB directamente en cada tabla de negocio (`dispositivo`, `Activo`, `geo_Objeto`, `programación`), en lugar de a través de una tabla base de entidad compartida
* Discriminación de tipo mediante la `discriminador` campo en `ci_base`, derivado automáticamente del UUID de la entidad

**Bloqueo optimista**

entidades de negocio y `ci_base` las filas llevan un `version` entero. Las actualizaciones usan `SET version = version + 1 WHERE id = $id AND version = $expected_version`. Cero filas afectadas señala un conflicto de versión, lo que permite a los clientes detectar y resolver ediciones concurrentes sin bloqueos a nivel de base de datos. `evento de auditoría` y otras tablas de solo inserción o de operaciones masivas no usan este patrón.

**Relaciones polimórficas**

Ciertas tablas usan referencias polimórficas sin restricciones de clave foránea para máxima flexibilidad:

* `acl_role_permission.target_entity_id` y `acl_user_scope.target_entity_id` → cualquier entidad comercial
* `audit_event.aggregate_id` → cualquier entidad comercial, asociada con `aggregate_type`
* `custom_field_value_entity.ref_entity_id` → cualquier entidad comercial

Estas relaciones se validan a nivel de la aplicación.

## Información adicional

### Validación de datos

BDR garantiza la integridad de los datos mediante múltiples mecanismos:

**Restricciones de base de datos**

* Restricciones UNIQUE con compatibilidad con eliminación lógica (índices parciales WHERE `deleted_at` IS NULL)
* Restricciones CHECK (p. ej., `device_relation` garantiza `first_id` ≠ `second_id`, y `ci_base.code` debe comenzar con una letra)
* restricciones NOT NULL en campos obligatorios
* valores DEFAULT para columnas de timestamp, booleanas y JSONB

**Validación a nivel de aplicación**

* Validación del tipo de entidad para referencias polimórficas
* Validación del catálogo para referencias de campos personalizados
* Validación del tipo de campo personalizado
* Comprobaciones de versión de bloqueo optimista al actualizar

### Optimización de consultas

Las tablas se organizan con estrategias de indexación específicas:

**Índices estándar:**

* Todas las claves foráneas tienen índices dedicados
* Índices basados en tiempo en `created_at`, `updated_at`, `deleted_at`
* Índices compuestos para columnas que se unen con frecuencia

**Índices especializados:**

* Índices GiST sobre rutas ltree para consultas jerárquicas
* Índices únicos parciales que admiten el borrado lógico
* trigrama GIN (`pg_trgm`) índices para búsqueda difusa en `título`/`title_en` y texto traducido
* Índices de valores de campos personalizados para filtrar y ordenar
* Índices de eventos de auditoría por tiempo + entidad para búsquedas eficientes

**Consideraciones de rendimiento:**

* Se recomienda la agrupación de conexiones (PgBouncer)
* Mantenimiento regular de VACUUM para tablas grandes
* `evento de auditoría` está particionada físicamente por mes, por lo que las particiones más antiguas pueden separarse o archivarse de forma independiente
* Vistas materializadas para cálculos complejos de control de acceso


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://navixy.com/docs/analytics/es/iot-query/schema-overview/bronze-layer/bdr.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
