CreaRack-SL

Arquitectura de Base de Datos - CreaRack Pro v1.0.50

Arquitectura de Base de Datos - CreaRack Pro v1.0.50

Última actualización: 03-04-2026 Versión de la aplicación: v1.0.50 Motor: PostgreSQL (16 en producción, 17 en desarrollo) ORM: Django 6.0.3 Total de modelos: 42 Total de migraciones: 112


1. Visión General

CreaRack Pro utiliza PostgreSQL como base de datos relacional principal, gestionada a través del ORM de Django 6. La aplicación está organizada en 7 apps Django, cada una con sus propios modelos y migraciones.

Distribución de Modelos por App

AppModelosMigracionesDescripción
core1016Usuarios, organizaciones, permisos, credenciales, logs
racks610Racks, dispositivos, stencils, backups
blueprints46Mapas, planos, anotaciones, prompts AI
network444Perfiles de red, vendors, MIBs, conexiones de puerto
monitoring1617Monitoreo, alertas, métricas, CNS, ITSM
signage118Digital Signage CMS, playlists, schedules, portal
terminal22Scripts de terminal, instancias de agente

Configuración de Conexión

Desarrollo (config/settings/dev.py)

DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.postgresql",
        "NAME": "crearack_pro",
        "USER": "crearack_user",
        "PASSWORD": "dev_password_123",
        "HOST": os.getenv("POSTGRES_HOST", "localhost"),  # 'db' en Docker
        "PORT": "5432",
        "OPTIONS": {"connect_timeout": 10},
        # Sin CONN_MAX_AGE (nueva conexión por request)
    }
}

Producción (config/settings/production.py)

DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.postgresql",
        "NAME": os.getenv("POSTGRES_DB", "crearack_pro"),
        "USER": os.getenv("POSTGRES_USER", "crearack_user"),
        "PASSWORD": os.getenv("POSTGRES_PASSWORD"),
        "HOST": os.getenv("POSTGRES_HOST", "db"),
        "PORT": "5432",
        "CONN_MAX_AGE": 600,          # Reutilizar conexiones 10 min
        "CONN_HEALTH_CHECKS": True,   # Django 6: verificar conexión antes de reutilizar
        "OPTIONS": {"connect_timeout": 10},
    }
}

2. Configuración Docker

Desarrollo (compose.yml)

db:
  image: postgres:18-alpine
  container_name: crearack_db
  environment:
    POSTGRES_DB: crearack_pro
    POSTGRES_USER: crearack_user
    POSTGRES_PASSWORD: dev_password_123
  volumes:
    - pg_data:/var/lib/postgresql/data
  ports:
    - "5432:5432"
  healthcheck:
    test: ["CMD-SHELL", "pg_isready -U crearack_user -d crearack_pro"]
    interval: 5s
    timeout: 5s
    retries: 5
  • Imagen: postgres:18-alpine
  • max_connections: Por defecto PostgreSQL (100)
  • Volumen: pg_data (Docker named volume)
  • Red: crearack_internal (bridge)

Producción (compose.prod.yml)

db:
  image: postgres:18-alpine
  restart: unless-stopped
  command: >-
    postgres
    -c max_connections=200
    -c shared_buffers=256MB
    -c shared_preload_libraries=pg_stat_statements
    -c pg_stat_statements.track=all
  environment:
    POSTGRES_DB: ${POSTGRES_DB:-crearack_pro}
    POSTGRES_USER: ${POSTGRES_USER:-crearack_user}
    POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
  volumes:
    - pg_data_prod:/var/lib/postgresql/data

pgbouncer:
  image: edoburu/pgbouncer:latest
  environment:
    DATABASE_URL: postgres://...@db:5432/crearack_pro
    POOL_MODE: transaction
    DEFAULT_POOL_SIZE: 30
    MAX_CLIENT_CONN: 300
  depends_on:
    db: { condition: service_healthy }
  • Imagen: postgres:18-alpine (migrado desde 16 el 04-04-2026)
  • max_connections: 200 (bajado desde 500 gracias a pgbouncer)
  • shared_buffers: 256MB
  • pg_stat_statements: Activado (slow query tracking)
  • pgbouncer: Transaction pooling, pool=30, max_client=300
  • CONN_MAX_AGE: 0 en Django (obligatorio con pgbouncer transaction mode)
  • Volumen: pg_data_prod (Docker named volume)
  • Red: crearack_internal (bridge)
  • Contenedor en Hetzner: crearack-pro-zcmvsl-db-1
  • Monitoring: Huey task monitor_db_connections cada 5 min, alerta en SystemLog al 80%
  • Backup verification: testzip() tras cada backup diario (3:30 AM)

3. Modelo Multi-Tenancy

CreaRack Pro implementa multi-tenancy con dos capas de aislamiento:

  1. Capa aplicación (Django ORM): Todos los queries filtran por organization=org
  2. Capa base de datos (PostgreSQL RLS): Políticas Row-Level Security como red de seguridad (v1.0.50)

Patrón de Aislamiento (ORM)

Organization (tenant raíz)
├── User (FK directa → organization)
├── Rack (FK directa → organization)
│   └── Device (FK indirecta vía rack.organization)
│       └── ConfigBackup (FK indirecta vía device.rack.organization)
├── Blueprint (FK directa → organization)
├── DeviceProfile (FK directa → organization)
├── MonitoringTarget (FK directa → organization)
├── AIInsight (FK directa → organization)
├── SignagePlayer (FK directa → organization)
├── AgentInstance (FK directa → organization)
├── Script (FK directa → organization)
├── StoredCredential (FK directa → organization)
├── SystemLog (FK directa → organization, nullable)
└── ScriptTemplate (FK directa → organization, nullable = global)

Modelos Globales (sin Organization FK)

ModeloRazón
SaaSModuleCatálogo global de módulos
PlanCatálogo global de planes
VendorProfileBase de datos global de vendors de red
SignageVendorAdapterAdaptadores de vendor globales
StencilGlobal si organization=NULL, tenant-specific si tiene FK

Soft Delete

Los siguientes modelos implementan borrado lógico con campo deleted_at:

ModeloCampoComportamiento
Organizationdeleted_at (DateTimeField, nullable)Reciclaje de organizaciones
Rackdeleted_at (DateTimeField, nullable, db_index)Papelera de racks
Blueprintdeleted_at (DateTimeField, nullable, db_index)Papelera de mapas

Métodos: soft_delete(), restore(), propiedad is_deleted.

PostgreSQL Row-Level Security (v1.0.50)

Migration: core/0017_rls_policies

RLS añade protección a nivel de base de datos. Si un bug en el ORM omite el filtro por organización, PostgreSQL bloquea el acceso.

Mecanismo: TenantRLSMiddleware ejecuta SET app.current_org_id por request. PostgreSQL aplica políticas USING (organization_id = current_setting('app.current_org_id')::int).

Cobertura: 35 tablas con políticas:

  • 29 tablas strict (org NOT NULL): Solo filas de la organización del request
  • 6 tablas nullable (Stencil, User, etc.): Filas propias + globales (org IS NULL)
  • Bypass: Superusers (org_id=0), requests sin auth

Tablas con FK heredada (Device, ConfigBackup, BlueprintPlacement, etc.) no necesitan política propia — la integridad referencial protege automáticamente.

Tenant Lifecycle

FaseMecanismoTiming
OnboardingPOST /api/signup — transaction atómicaOn demand
Backuprun_org_backups Huey task — ZIP per-orgDiario 3:30 AM
Soft-deleteOrganization.deleted_at → inaccesibleInmediato
Purgepurge_deleted_organizations — hard-deleteDiario 4:00 AM (>90 días)
Integridadcheck_tenant_integrity — anomalías cross-tenantDiario 4:30 AM

4. Referencia de Modelos por App

4.1 App core

4.1.1 SaaSModule

Tabla: core_saasmodule

Registro de módulos de aplicación disponibles para suscripción.

CampoTipoConstraintsDescripción
idAutoFieldPK
slugSlugField(50)UNIQUEClave única del módulo
nameCharField(100)NOT NULLNombre visible
descriptionTextFieldblankDescripción
url_prefixesJSONFielddefault=listPrefijos URL gateados, ej: ["/editor/"]
is_coreBooleanFielddefault=FalseSi es core, siempre habilitado
sort_orderIntegerFielddefault=0Orden de visualización

Meta: ordering = ['sort_order', 'name']


4.1.2 Plan

Tabla: core_plan

Plan de suscripción que define módulos base para una organización.

CampoTipoConstraintsDescripción
idAutoFieldPK
slugSlugField(50)UNIQUEClave única del plan
nameCharField(100)NOT NULLNombre del plan
descriptionTextFieldblankDescripción
sort_orderIntegerFielddefault=0Orden

M2M: modules → SaaSModule (through: core_plan_modules, related_name: plans)

Meta: ordering = ['sort_order']

Choices para slug: starter, pro, custom


4.1.3 Organization

Tabla: core_organization

Tenant SaaS. Todas las entidades pertenecen a una organización.

CampoTipoConstraintsDescripción
idAutoFieldPK
nameCharField(255)NOT NULLNombre de la organización
addressTextFieldblankDirección
created_atDateTimeFieldauto_now_addFecha de creación
updated_atDateTimeFieldauto_nowFecha de última actualización
is_activeBooleanFielddefault=TrueEstado activo
max_racksIntegerFielddefault=10Límite de racks
plan_idFK → PlanSET_NULL, nullablePlan de suscripción
disable_reasonCharField(300)blank, default=”Razón de deshabilitación
deleted_atDateTimeFieldnullableSoft-delete timestamp
logo_pathCharField(255)blank, default=”Ruta del logo
session_timeout_minutesIntegerFielddefault=10Timeout de sesión
onboarding_completedBooleanFielddefault=FalseOnboarding completado
sentinel_configJSONFielddefault=dictConfig de Sentinel Mode
backup_retention_daysIntegerFielddefault=0Días retención backups (0=default plan)

M2M: extra_modules → SaaSModule (through: core_organization_extra_modules, related_name: extra_organizations)


4.1.4 User

Tabla: core_user

Modelo de usuario custom que extiende AbstractUser de Django. Definido como AUTH_USER_MODEL = "core.User".

CampoTipoConstraintsDescripción
idAutoFieldPK
Campos heredados de AbstractUserusername, first_name, last_name, email, password, is_staff, is_active, is_superuser, date_joined, last_login
organization_idFK → OrganizationCASCADE, nullableOrganización del usuario
roleCharField(20)choices, default=‘readonly’Rol: admin, operator, readonly

Constraints:

  • UniqueConstraint(fields=['email'], condition=~Q(email=''), name='unique_email_when_set') — email único cuando no está vacío

4.1.5 SystemLog

Tabla: core_systemlog

Log de auditoría del sistema.

CampoTipoConstraintsDescripción
idAutoFieldPK
timestampDateTimeFieldauto_now_add, db_indexFecha/hora
levelCharField(20)choicesINFO, WARNING, ERROR
categoryCharField(50)choicesAUTH, RACK, BLUEPRINT, SYSTEM, NETWORK
actionCharField(200)NOT NULLAcción registrada
detailsTextFieldblankDetalles
user_idFK → UserSET_NULL, nullableUsuario que realizó la acción
organization_idFK → OrganizationCASCADE, nullableOrganización

Indexes:

  • (organization, -timestamp)
  • (user, -timestamp)
  • (level, -timestamp)

Meta: ordering = ['-timestamp']


4.1.6 ScriptTemplate

Tabla: core_scripttemplate

Plantillas de scripts de configuración de red reutilizables.

CampoTipoConstraintsDescripción
idAutoFieldPK
nameCharField(200)NOT NULLNombre
descriptionTextFieldblankDescripción
script_contentTextFieldNOT NULLContenido del script
languageCharField(50)choices, default=‘cisco_ios’cisco_ios, junos, aruba, bash, python
organization_idFK → OrganizationCASCADE, nullableNULL = plantilla global
created_by_idFK → UserSET_NULL, nullableCreador
created_atDateTimeFieldauto_now_add
updated_atDateTimeFieldauto_now

Constraints: unique_together = ('name', 'organization')


4.1.7 ImpersonationLog

Tabla: core_impersonationlog

Log de auditoría para sesiones de impersonación de superusuario.

CampoTipoConstraintsDescripción
idAutoFieldPK
admin_idFK → UserCASCADESuperusuario que impersona
target_idFK → UserCASCADEUsuario impersonado
started_atDateTimeFieldauto_now_addInicio de sesión
ended_atDateTimeFieldnullableFin de sesión
reasonCharField(200)NOT NULLRazón de la impersonación
ip_addressGenericIPAddressFieldnullableIP del admin

Meta: ordering = ['-started_at']


4.1.8 ModulePermission

Tabla: core_modulepermission

Permisos granulares por usuario (override sobre el rol).

CampoTipoConstraintsDescripción
idAutoFieldPK
user_idOneToOneField → UserCASCADE, UNIQUEUsuario
permissionsJSONFielddefault=dict{"scope": "level"}, ej: {"terminal": "none"}
updated_by_idFK → UserSET_NULL, nullableQuién actualizó
updated_atDateTimeFieldauto_now

4.1.9 TemporaryAccess

Tabla: core_temporaryaccess

Elevación temporal de permisos con tiempo limitado.

CampoTipoConstraintsDescripción
idAutoFieldPK
user_idFK → UserCASCADEUsuario beneficiario
scopeCharField(20)NOT NULLall o scope específico
levelCharField(10)NOT NULLview, edit, admin
granted_by_idFK → UserCASCADEQuién otorgó
reasonCharField(200)NOT NULLRazón
granted_atDateTimeFieldauto_now_add
expires_atDateTimeFieldNOT NULLExpiración
revoked_atDateTimeFieldnullableRevocación anticipada

Meta: ordering = ['-granted_at']


4.1.10 StoredCredential

Tabla: core_storedcredential

Almacenamiento cifrado (Fernet AES) de credenciales SNMP/SSH/HTTP.

CampoTipoConstraintsDescripción
idAutoFieldPK
organization_idFK → OrganizationCASCADE
nameCharField(200)NOT NULLNombre descriptivo
credential_typeCharField(10)choicessnmp_v2, snmp_v3, ssh, multi
encrypted_dataJSONFielddefault=dictDatos cifrados con Fernet
created_by_idFK → UserSET_NULL, nullable
created_atDateTimeFieldauto_now_add
updated_atDateTimeFieldauto_now

Constraints: unique_together = ('name', 'organization')

Cifrado: Los campos sensibles (community, auth_key, priv_key, ssh_password, enable_password, http_password) se cifran con Fernet AES via CredentialManager. El método get_section(section_name) descifra bajo demanda.


4.1.11 LoginLog

Tabla: core_loginlog

Registro de cada intento de login (exitoso y fallido).

CampoTipoConstraintsDescripción
idAutoFieldPK
user_idFK → UserCASCADE, nullableNULL para logins fallidos
timestampDateTimeFieldauto_now_add, db_index
ip_addressGenericIPAddressFieldnullableIP del intento
user_agentCharField(512)blank, default=”User-Agent del navegador
successBooleanFielddefault=TrueLogin exitoso o fallido
username_attemptedCharField(150)blank, default=”Username intentado
methodCharField(20)choices, default=‘password’password, passkey, social, token

Indexes:

  • (user, -timestamp)
  • (success, -timestamp)
  • (ip_address, -timestamp)

Meta: ordering = ['-timestamp']


4.2 App racks

4.2.1 BoxCategory

Tabla: racks_boxcategory

Categorías para dispositivos.

CampoTipoConstraintsDescripción
idAutoFieldPK
nameCharField(100)NOT NULLNombre de la categoría
organization_idFK → OrganizationCASCADE, nullableNULL = categoría global

Constraints: unique_together = ('name', 'organization')


4.2.2 RackGroup

Tabla: racks_rackgroup

Agrupación lógica de racks (ej: ‘Data Center A’, ‘Row 1’). También utilizado por DeviceProfile, SignagePlayer y Device como grupo genérico.

CampoTipoConstraintsDescripción
idAutoFieldPK
nameCharField(100)NOT NULLNombre del grupo
colorCharField(20)default=‘#6c757d’Color para la UI
organization_idFK → OrganizationCASCADE
created_atDateTimeFieldauto_now_add

Constraints: unique_together = ('name', 'organization')


4.2.3 Stencil

Tabla: racks_stencil

Plantillas de dispositivos (device templates) para el editor de racks.

CampoTipoConstraintsDescripción
idAutoFieldPK
nameCharField(100)NOT NULLNombre del stencil
image_pathCharField(200)NOT NULLRuta de la imagen
default_u_heightIntegerFielddefault=1Altura en unidades de rack
manufacturerCharField(100)blankFabricante
categoryCharField(100)default=‘General’Categoría (legacy string)
extra_dataTextFieldblank, default=’{}’JSON con datos extra (half_width, etc.)
organization_idFK → OrganizationCASCADE, nullableNULL = stencil global

4.2.4 Rack

Tabla: racks_rack

Rack/armario de servidor.

CampoTipoConstraintsDescripción
idAutoFieldPK
nameCharField(100)NOT NULLNombre
locationCharField(100)blankUbicación
notesTextFieldblankNotas
height_uIntegerFielddefault=42Altura en Us
power_consumptionFloatFielddefault=0.0Consumo en kWh
statusCharField(50)choices, default=‘active’active, planned, deprecated, maintenance
organization_idFK → OrganizationCASCADE
parent_rack_idFK → selfSET_NULL, nullableRack padre (topología)
created_atDateTimeFieldauto_now_add
updated_atDateTimeFieldauto_now
deleted_atDateTimeFieldnullable, db_indexSoft delete
is_templateBooleanFielddefault=False, db_indexSi es plantilla

M2M: groups → RackGroup (through: racks_rack_groups, related_name: racks)


4.2.5 Device

Tabla: racks_device

Dispositivo dentro de un rack.

CampoTipoConstraintsDescripción
idAutoFieldPK
rack_idFK → RackCASCADERack contenedor
nameCharField(100)NOT NULLNombre del dispositivo
u_positionIntegerFieldNOT NULLPosición U inferior
u_heightIntegerFielddefault=1Altura en Us
notesTextFieldblankNotas
model_dataTextFieldblank, default=’{}’JSON con metadatos (half_width, slot, stencil info)
statusCharField(20)choices, default=‘unknown’online, offline, warning, unknown
last_status_checkDateTimeFieldnullableÚltimo check
management_configTextFieldblank, default=’{}’JSON con credenciales/IP (valores sensibles cifrados con Fernet)

M2M: groups → RackGroup (through: racks_device_groups, related_name: devices)


4.2.6 ConfigBackup

Tabla: racks_configbackup

Backups de configuración de dispositivos de red.

CampoTipoConstraintsDescripción
idAutoFieldPK
device_idFK → DeviceCASCADEDispositivo
rack_idFK → RackCASCADERack
config_textTextFieldNOT NULLRunning-config completo
config_hashCharField(64)NOT NULLSHA256 para detección de cambios
backup_typeCharField(20)choices, default=‘auto’auto, manual, pre_change
created_atDateTimeFieldauto_now_add, db_index
created_by_idFK → UserSET_NULL, nullable
vendorCharField(50)blankVendor (cisco_ios, etc.)
device_versionCharField(100)blankVersión IOS, etc.
is_activeBooleanFielddefault=True
notesTextFieldblank

Indexes:

  • (device, -created_at)
  • (rack, -created_at)

Meta: ordering = ['-created_at']


4.3 App blueprints

4.3.1 Blueprint

Tabla: blueprints_blueprint

Mapa de canvas infinito (plano o topología).

CampoTipoConstraintsDescripción
idAutoFieldPK
nameCharField(100)NOT NULLNombre
image_pathCharField(200)blankRuta imagen de fondo
scaleFloatFielddefault=1.0Escala del mapa
bg_opacityFloatFielddefault=1.0Opacidad del fondo
show_bgBooleanFielddefault=TrueMostrar fondo
routing_modeCharField(20)default=‘manhattan’Modo de enrutamiento cables
dark_modeBooleanFielddefault=FalseModo oscuro
cable_spreadIntegerFielddefault=12Separación de cables
cable_curvatureFloatFielddefault=1.0Curvatura de cables
cable_widthFloatFielddefault=2.0Grosor de cables
organization_idFK → OrganizationCASCADE
created_atDateTimeFieldauto_now_add
deleted_atDateTimeFieldnullable, db_indexSoft delete

4.3.2 BlueprintPlacement

Tabla: blueprints_blueprintplacement

Posición de un rack sobre un plano.

CampoTipoConstraintsDescripción
idAutoFieldPK
blueprint_idFK → BlueprintCASCADEPlano
rack_idFK → RackCASCADERack posicionado
pos_xFloatFielddefault=0.0Posición X
pos_yFloatFielddefault=0.0Posición Y
rotationIntegerFielddefault=0Rotación en grados
style_propsTextFieldblank, default=’{}’JSON con propiedades visuales

Constraints: unique_together = ('blueprint', 'rack')


4.3.3 MapAnnotation

Tabla: blueprints_mapannotation

Paredes, texto, zonas en el mapa.

CampoTipoConstraintsDescripción
idAutoFieldPK
blueprint_idFK → BlueprintCASCADEPlano
typeCharField(50)NOT NULLwall, text, zone
dataTextFieldNOT NULLJSON con coordenadas
created_atDateTimeFieldauto_now_add

4.3.4 AIPrompt

Tabla: blueprints_aiprompt

Prompts de AI Brain guardados (sistema Save As).

CampoTipoConstraintsDescripción
idAutoFieldPK
organization_idFK → OrganizationCASCADE
nameCharField(100)NOT NULLNombre del prompt
contentTextFieldNOT NULLContenido del prompt
is_defaultBooleanFielddefault=FalseSi es el prompt por defecto
created_atDateTimeFieldauto_now_add
updated_atDateTimeFieldauto_now

Constraints: unique_together = [('organization', 'name')]

Meta: ordering = ['-updated_at']


4.4 App network

4.4.1 VendorProfile

Tabla: vendor_profiles (custom db_table)

Base de datos centralizada de vendors de red. Scope global (no per-organization).

CampoTipoConstraintsDescripción
idAutoFieldPK
Identidad
slugSlugField(50)UNIQUEClave única (cisco, juniper)
display_nameCharField(100)NOT NULLNombre visible
aliasesArrayField(CharField(100))default=listNombres alternativos para matching
SNMP
snmp_communitiesArrayField(CharField(100))default=listCommunities vendor-specific
sys_descr_patternsArrayField(CharField(500))default=listRegex para detectar vendor en sysDescr
sys_descr_model_regexCharField(500)blankRegex para extraer modelo
sys_descr_version_regexCharField(500)blankRegex para extraer versión OS
sys_object_id_prefixCharField(255)blankPrefijo sysObjectID
MIB
mib_modulesArrayField(CharField(100))default=listMIBs vendor-specific
deep_discovery_oidsJSONFielddefault=dictOIDs para deep discovery
monitoring_oidsJSONFielddefault=dictOIDs para monitoreo continuo
fast_poll_oidsJSONFielddefault=dictOIDs ligeros para fast polling (15s)
HTTP Fingerprinting
http_server_patternsArrayField(CharField(500))default=listPatrones HTTP Server header
http_body_patternsArrayField(CharField(500))default=listPatrones HTTP body
http_product_linesJSONFielddefault=dictMapa keyword → product line
http_firmware_regexCharField(500)blankRegex para firmware desde HTTP
SSH
scrapli_platformCharField(50)default=‘generic’Identificador Scrapli
ssh_show_version_cmdCharField(200)default=‘show version’
ssh_show_interfaces_cmdCharField(200)default=‘show interfaces’
ssh_show_inventory_cmdCharField(200)blank, default=‘show inventory’
ssh_model_regexCharField(500)blankRegex modelo desde show version
ssh_version_regexCharField(500)blankRegex versión desde show version
Device Type
default_device_typeCharField(50)choices, default=‘other’router, switch, firewall, load_balancer, wireless_controller, access_point, other
device_type_keywordsJSONFielddefault=dictMapa tipo → keywords
os_typesArrayField(CharField(100))default=listOS conocidos
Metadata
priorityIntegerFielddefault=100Orden de detección (menor = primero)
is_activeBooleanFielddefault=True
notesTextFieldblank
created_atDateTimeFieldauto_now_add
updated_atDateTimeFieldauto_now

Meta: ordering = ['priority', 'display_name']


4.4.2 CustomMib

Tabla: custom_mibs (custom db_table)

MIBs custom subidos por usuarios para enriquecer Deep Discovery.

CampoTipoConstraintsDescripción
idAutoFieldPK
vendor_profile_idFK → VendorProfileCASCADEVendor asociado
module_nameCharField(100)NOT NULLNombre del módulo MIB
original_filenameCharField(255)NOT NULLNombre del archivo original
source_contentTextFieldblankFuente ASN.1 como backup
file_sizeIntegerFielddefault=0Tamaño en bytes
compiledBooleanFielddefault=FalseSi está compilado
compile_errorTextFieldblankError de compilación
extracted_oidsJSONFielddefault=listOIDs extraídos
uploaded_by_idFK → UserSET_NULL, nullable
uploaded_atDateTimeFieldauto_now_add
organization_idFK → OrganizationCASCADE

Constraints: unique_together = [['vendor_profile', 'module_name']]

Meta: ordering = ['-uploaded_at']


4.4.3 DeviceProfile

Tabla: device_profiles (custom db_table)

Perfil completo de dispositivo de red descubierto via Auto-Provision Tool.

CampoTipoConstraintsDescripción
idAutoFieldPK
Identificación
organization_idFK → OrganizationCASCADE
ip_addressGenericIPAddressFielddb_indexIP del dispositivo
hostnameCharField(255)blankHostname
fqdnCharField(500)blankFQDN
Sistema
vendorCharField(100)blank, db_indexFabricante
modelCharField(200)blank, db_indexModelo
serial_numberCharField(200)blankNúmero de serie
mac_addressCharField(17)blankMAC principal
Software
os_typeCharField(100)blankTipo de OS
os_versionCharField(200)blankVersión OS
firmware_versionCharField(200)blankVersión firmware
Capacidades
device_typeCharField(50)choices, default=‘other’Tipo inferido
capabilitiesArrayField(CharField(50))default=listCapabilities
Protocolos
supports_snmpBooleanFielddefault=False
supports_sshBooleanFielddefault=False
supports_telnetBooleanFielddefault=False
supports_netconfBooleanFielddefault=False
supports_restconfBooleanFielddefault=False
SNMP
snmp_versionCharField(10)blankv2c, v3
snmp_communityCharField(100)blank, nullableCommunity string
snmp_v3_usernameCharField(100)blankUSM username
snmp_v3_auth_protocolCharField(20)blankMD5, SHA, SHA256, etc.
snmp_v3_auth_keyCharField(255)blankAuth passphrase
snmp_v3_priv_protocolCharField(20)blankDES, AES128, etc.
snmp_v3_priv_keyCharField(255)blankPrivacy passphrase
snmp_sys_descrTextFieldblanksysDescr
snmp_sys_object_idCharField(255)blanksysObjectID
snmp_uptimeBigIntegerFieldnullablesysUpTime en ticks
interfacesJSONFielddefault=dictifTable walk completo
Hardware
cpu_countIntegerFieldnullable
memory_total_mbBigIntegerFieldnullable
chassis_typeCharField(200)blank
Deep Discovery
deep_snmp_dataJSONFielddefault=dictDatos MIB-enriched
mib_modules_loadedArrayField(CharField(100))default=listMIBs usados en discovery
Stencil Matching
suggested_stencil_idIntegerFieldnullableID del stencil sugerido (sin FK constraint)
stencil_confidenceIntegerFielddefault=0Confianza 0-100%
stencil_match_reasonTextFieldblankRazón del match
Topología
lldp_neighborsJSONFielddefault=listVecinos LLDP
cdp_neighborsJSONFielddefault=listVecinos CDP
Discovery Metadata
discovery_methodCharField(20)choices, default=‘snmp’snmp, ssh, snmp+ssh, manual
confidence_scoreIntegerFielddefault=0, db_indexScore 0-100%
discovered_atDateTimeFieldauto_now_add
last_updatedDateTimeFieldauto_now
last_verifiedDateTimeFieldnullable
Raw Data
raw_snmp_dataJSONFielddefault=dictDump SNMP para debug
raw_ssh_dataJSONFielddefault=dictOutput SSH para debug
Links
linked_monitoring_target_idFK → MonitoringTargetSET_NULL, nullable
linked_device_idFK → DeviceSET_NULL, nullable

M2M: groups → RackGroup (through auto, related_name: device_profiles)

Constraints: unique_together = [['organization', 'ip_address']]

Indexes:

  • (organization, ip_address)
  • (vendor, model)
  • (confidence_score)
  • (discovered_at)

Meta: ordering = ['-confidence_score', '-discovered_at']


4.4.4 PortConnection

Tabla: port_connections (custom db_table)

Mapeo de puertos de dispositivo a DeviceProfiles o Devices (switch-to-switch, etc.). Exactamente UNO de profile o connected_device debe estar presente (XOR constraint).

CampoTipoConstraintsDescripción
idAutoFieldPK
organization_idFK → OrganizationCASCADE
device_idFK → DeviceCASCADEDispositivo fuente
port_keyCharField(10)NOT NULLClave del puerto
profile_idOneToOneField → DeviceProfileCASCADE, nullablePerfil conectado (XOR)
connected_device_idFK → DeviceCASCADE, nullableDispositivo conectado (XOR)
connected_port_keyCharField(10)blank, default=”Puerto destino
cable_typeCharField(30)blank, default=”Tipo de cable
cable_colorCharField(20)blank, default=”Color del cable
cable_lengthCharField(20)blank, default=”Longitud del cable
notesCharField(255)blank
created_atDateTimeFieldauto_now_add

Constraints:

  • unique_together = [('device', 'port_key')]
  • CheckConstraint: (profile NOT NULL AND connected_device NULL) OR (profile NULL AND connected_device NOT NULL) — nombre: port_conn_one_target
  • UniqueConstraint(fields=['connected_device', 'connected_port_key'], condition=Q(connected_port_key__gt='')) — nombre: port_conn_unique_dest_port

Indexes: (organization)


4.5 App monitoring

4.5.1 MonitoringTarget

Tabla: monitoring_monitoringtarget

Dispositivo o IP a monitorear.

CampoTipoConstraintsDescripción
idAutoFieldPK
nameCharField(100)NOT NULLNombre
ip_addressGenericIPAddressFieldNOT NULLIP
device_idFK → DeviceSET_NULL, nullableDispositivo de rack
ping_enabledBooleanFielddefault=TrueICMP ping
snmp_enabledBooleanFielddefault=TrueSNMP bandwidth
http_enabledBooleanFielddefault=TrueHTTP health check
interval_secondsIntegerFielddefault=60Cadencia de polling por target (floors por protocolo en el Agent)
timeout_msIntegerFielddefault=5000Timeout
enabledBooleanFielddefault=True
configJSONFielddefault=dictConfig adicional (SNMP community, OIDs, etc.)
last_statusCharField(20)choices, default=‘unknown’unknown, up, down, degraded
last_latency_msFloatFieldnullable
last_packet_lossFloatFieldnullablePérdida de paquetes %
last_checkDateTimeFieldnullable
created_atDateTimeFieldauto_now_add
updated_atDateTimeFieldauto_now
organization_idFK → OrganizationCASCADE

Constraints: unique_together = ['organization', 'ip_address'] — el target es 1:1 por host y org. El campo scope fue eliminado (Ficha Central F4, migración monitoring/0025): la pertenencia a las páginas Wireless/UPS/Signage la decide DeviceProfile.assigned_page, no el target.

Meta: ordering = ['name'] · index (organization, enabled) (idx_target_org_enabled)


4.5.2 MetricSample

Tabla: monitoring_metricsample

Muestra individual de métrica (histórico). Nota: Candidato a eliminación futura — la mayoría de métricas se almacenan en VictoriaMetrics, pero este modelo aún tiene 38+ referencias activas.

CampoTipoConstraintsDescripción
idAutoFieldPK
target_idFK → MonitoringTargetCASCADE
timestampDateTimeFielddefault=now, db_index
metric_typeCharField(20)choiceslatency, packet_loss, bandwidth_in, bandwidth_out, uptime, jitter
valueFloatFieldNOT NULL

Indexes:

  • (target, metric_type, -timestamp)
  • (timestamp)

Meta: ordering = ['-timestamp']


4.5.3 MonitoringAlert

Tabla: monitoring_monitoringalert

Configuración de alertas (per-device o global).

CampoTipoConstraintsDescripción
idAutoFieldPK
target_idFK → MonitoringTargetCASCADE, nullableNULL para alertas globales
organization_idFK → OrganizationCASCADE, nullable
is_globalBooleanFielddefault=FalseAplica a TODOS los targets
nameCharField(100)NOT NULL
condition_typeCharField(30)choiceslatency_above, latency_below, packet_loss_above, packet_loss_below, bandwidth_above, bandwidth_below, http_response_time_above, http_response_time_below, down_for
threshold_valueFloatFieldNOT NULLValor umbral
threshold_duration_secondsIntegerFielddefault=60Duración antes de trigger
severityCharField(10)choices, default=‘red’green, blue, red
enabledBooleanFielddefault=True
last_triggeredDateTimeFieldnullable
created_atDateTimeFieldauto_now_add

Meta: ordering = ['name']


4.5.4 AlertEvent

Tabla: monitoring_alertevent

Historial de alertas disparadas.

CampoTipoConstraintsDescripción
idAutoFieldPK
alert_idFK → MonitoringAlertCASCADE
target_idFK → MonitoringTargetSET_NULL, nullableTarget que disparó el evento
triggered_atDateTimeFieldauto_now_add
resolved_atDateTimeFieldnullable
value_at_triggerFloatFieldNOT NULLValor cuando se disparó
acknowledgedBooleanFielddefault=False
acknowledged_atDateTimeFieldnullable
acknowledged_by_idFK → UserSET_NULL, nullable
notesTextFieldblank

Meta: ordering = ['-triggered_at']


4.5.5 AggregatedMetric

Tabla: monitoring_aggregatedmetric

Métricas agregadas por hora para consultas históricas eficientes.

CampoTipoConstraintsDescripción
idAutoFieldPK
target_idFK → MonitoringTargetCASCADE
hourDateTimeFielddb_indexInicio del bucket horario
metric_typeCharField(20)choicesTipo de métrica
avg_valueFloatFieldNOT NULLPromedio
min_valueFloatFieldNOT NULLMínimo
max_valueFloatFieldNOT NULLMáximo
sample_countIntegerFieldNOT NULLNúmero de muestras

Constraints: unique_together = ['target', 'hour', 'metric_type']

Indexes: (target, metric_type, -hour)

Meta: ordering = ['-hour']


4.5.6 AIInsight

Tabla: monitoring_aiinsight

Diagnóstico AI generado por CreaRack Network Sentinel (CNS).

CampoTipoConstraintsDescripción
idAutoFieldPK
Relaciones
organization_idFK → OrganizationCASCADE
target_idFK → MonitoringTargetCASCADE
Identificación
incident_idUUIDFieldUNIQUE, auto uuid4ID de incidente
case_numberPositiveIntegerFieldnullableNúmero de caso (auto-incrementado por org)
Diagnóstico
summaryCharField(200)NOT NULLResumen
root_causeTextFieldNOT NULLCausa raíz
confidence_scoreFloatFieldNOT NULL0.0-1.0
osi_layerIntegerFieldnullableCapa OSI 1-7
telemetry_insightTextFieldblank, default=”
Recomendación
action_labelCharField(200)NOT NULLEtiqueta de acción
risk_levelCharField(10)choices, default=‘LOW’LOW, MEDIUM, HIGH
impact_descriptionTextFieldblank, default=”
commandsJSONFielddefault=listComandos sugeridos
rollback_commandsJSONFielddefault=listComandos de rollback
Estado
statusCharField(20)choices, default=‘pending’pending, executing, applied, acknowledged, expired, failed
ai_providerCharField(20)NOT NULLgemini, static
Contexto original
raw_snmp_dataJSONFieldnullable
raw_ssh_logsTextFieldnullable
anomaly_triggerTextFieldNOT NULL
Ejecución
applied_by_idFK → UserSET_NULL, nullable
applied_atDateTimeFieldnullable
execution_outputTextFieldnullable
rollback_executedBooleanFielddefault=False
Acknowledge
acknowledged_by_idFK → UserSET_NULL, nullable
acknowledged_atDateTimeFieldnullable
acknowledged_notesTextFieldblank, default=”
Revisión
is_revisedBooleanFielddefault=False
original_summaryCharField(200)blank, default=”
original_root_causeTextFieldblank, default=”
revised_atDateTimeFieldnullable
revised_by_idFK → UserSET_NULL, nullable
ITSM
sla_ack_deadlineDateTimeFieldnullable
sla_resolve_deadlineDateTimeFieldnullable
sla_ack_breachedBooleanFielddefault=False
sla_resolve_breachedBooleanFielddefault=False
resolved_atDateTimeFieldnullable
escalation_levelIntegerFielddefault=0
escalated_atDateTimeFieldnullable
incident_group_idFK → IncidentGroupSET_NULL, nullable
known_issue_idFK → KnownIssueSET_NULL, nullable
suggested_runbook_idFK → RunbookSET_NULL, nullable
Timestamps
created_atDateTimeFieldauto_now_add
expires_atDateTimeFieldNOT NULLDefault: ahora + 4 horas

Constraints:

  • UniqueConstraint(fields=['organization', 'case_number'], name='unique_case_number_per_org')

Indexes:

  • (organization, status)
  • (target, -created_at)

Meta: ordering = ['-created_at']


4.5.7 AIInsightAuditLog

Tabla: monitoring_aiinsightauditlog

Log de auditoría para cada acción sobre un insight.

CampoTipoConstraintsDescripción
idAutoFieldPK
insight_idFK → AIInsightCASCADE
actionCharField(30)NOT NULLcreated, applied, dry_run, acknowledged, rollback, expired
user_idFK → UserSET_NULL, nullable
detailsJSONFieldnullable
created_atDateTimeFieldauto_now_add

Meta: ordering = ['-created_at']


4.5.8 InsightConversation

Tabla: monitoring_insightconversation

Conversación Q&A para un AI Insight (interacciones Explain).

CampoTipoConstraintsDescripción
idAutoFieldPK
insight_idFK → AIInsightCASCADE
questionTextFieldNOT NULLPregunta del usuario
answerTextFieldNOT NULLRespuesta del AI
ai_providerCharField(20)NOT NULL
asked_by_idFK → UserSET_NULL, nullable
created_atDateTimeFieldauto_now_add

Meta: ordering = ['created_at']


4.5.9 SLAPolicy

Tabla: monitoring_slapolicy

Políticas SLA configurables por nivel de riesgo.

CampoTipoConstraintsDescripción
idAutoFieldPK
organization_idFK → OrganizationCASCADE
risk_levelCharField(10)NOT NULLHIGH, MEDIUM, LOW
ack_minutesIntegerFielddefault=30Minutos para acknowledgement
resolve_minutesIntegerFielddefault=240Minutos para resolución
enabledBooleanFielddefault=True

Constraints: unique_together = ['organization', 'risk_level']


4.5.10 NotificationChannel

Tabla: monitoring_notificationchannel

Canal de notificación (webhook, email, Slack, Teams).

CampoTipoConstraintsDescripción
idAutoFieldPK
organization_idFK → OrganizationCASCADE
nameCharField(100)NOT NULL
channel_typeCharField(20)choiceswebhook, email, slack, teams
configJSONFielddefault=dictURL, headers, addresses, etc.
risk_levelsJSONFielddefault=list["HIGH", "MEDIUM"]
enabledBooleanFielddefault=True
created_atDateTimeFieldauto_now_add

4.5.11 NotificationLog

Tabla: monitoring_notificationlog

Log de notificaciones enviadas.

CampoTipoConstraintsDescripción
idAutoFieldPK
channel_idFK → NotificationChannelCASCADE
insight_idFK → AIInsightCASCADE
eventCharField(30)NOT NULLcreated, sla_breached, escalated
statusCharField(20)NOT NULLsent, failed, retrying
response_codeIntegerFieldnullable
error_messageTextFieldblank, default=”
created_atDateTimeFieldauto_now_add

Meta: ordering = ['-created_at']


4.5.12 EscalationPolicy

Tabla: monitoring_escalationpolicy

Política de escalamiento multi-nivel por nivel de riesgo.

CampoTipoConstraintsDescripción
idAutoFieldPK
organization_idFK → OrganizationCASCADE
nameCharField(100)NOT NULL
risk_levelCharField(10)NOT NULL
enabledBooleanFielddefault=True

4.5.13 EscalationLevel

Tabla: monitoring_escalationlevel

Paso individual dentro de una política de escalamiento.

CampoTipoConstraintsDescripción
idAutoFieldPK
policy_idFK → EscalationPolicyCASCADE
levelIntegerFieldNOT NULL1, 2, 3…
delay_minutesIntegerFieldNOT NULLMinutos desde creación del insight
notification_channel_idFK → NotificationChannelCASCADE

Constraints: unique_together = ['policy', 'level']

Meta: ordering = ['level']


4.5.14 IncidentGroup

Tabla: monitoring_incidentgroup

Agrupa insights relacionados con la misma causa raíz.

CampoTipoConstraintsDescripción
idAutoFieldPK
organization_idFK → OrganizationCASCADE
titleCharField(200)NOT NULL
root_insight_idFK → AIInsightSET_NULL, nullableInsight raíz
statusCharField(20)choices, default=‘open’open, resolved
created_atDateTimeFieldauto_now_add
resolved_atDateTimeFieldnullable

Meta: ordering = ['-created_at']


4.5.15 MaintenanceWindow

Tabla: monitoring_maintenancewindow

Ventana de mantenimiento para suprimir creación de insights.

CampoTipoConstraintsDescripción
idAutoFieldPK
organization_idFK → OrganizationCASCADE
titleCharField(200)NOT NULL
start_atDateTimeFieldNOT NULL
end_atDateTimeFieldNOT NULL
suppress_insightsBooleanFielddefault=True
suppress_notificationsBooleanFielddefault=True
created_by_idFK → UserSET_NULL, nullable
notesTextFieldblank, default=”
created_atDateTimeFieldauto_now_add

M2M: targets → MonitoringTarget (related_name: maintenance_windows) — vacío = todos los targets

Meta: ordering = ['-start_at']


4.5.16 KnownIssue

Tabla: monitoring_knownissue

Issue conocido con resolución documentada.

CampoTipoConstraintsDescripción
idAutoFieldPK
organization_idFK → OrganizationCASCADE
titleCharField(200)NOT NULL
descriptionTextFieldNOT NULL
resolutionTextFieldNOT NULL
match_keywordsJSONFielddefault=listKeywords para auto-matching
risk_levelCharField(10)blank, default=”
occurrence_countIntegerFielddefault=0
last_seenDateTimeFieldnullable
created_by_idFK → UserSET_NULL, nullable
created_atDateTimeFieldauto_now_add

Meta: ordering = ['-occurrence_count']


Tabla: monitoring_insightlink

Link manual entre dos insights relacionados.

CampoTipoConstraintsDescripción
idAutoFieldPK
source_idFK → AIInsightCASCADE
target_idFK → AIInsightCASCADE
link_typeCharField(10)choicesparent, related
created_by_idFK → UserSET_NULL, nullable
created_atDateTimeFieldauto_now_add

Constraints: unique_together = ['source', 'target']


4.5.18 Runbook

Tabla: monitoring_runbook

Librería de procedimientos de remediación.

CampoTipoConstraintsDescripción
idAutoFieldPK
organization_idFK → OrganizationCASCADE
titleCharField(200)NOT NULL
descriptionTextFieldNOT NULL
stepsJSONFielddefault=list[{"step": 1, "action": "...", "commands": [...]}]
applicable_triggersJSONFielddefault=listKeywords de anomaly_trigger
vendorCharField(50)blank, default=”cisco_ios, juniper, '' = genérico
created_by_idFK → UserSET_NULL, nullable
usage_countIntegerFielddefault=0
created_atDateTimeFieldauto_now_add

Meta: ordering = ['-usage_count']


4.5.19 RecurringPattern

Tabla: monitoring_recurringpattern

Patrón de incidentes recurrentes detectado para alertas proactivas.

CampoTipoConstraintsDescripción
idAutoFieldPK
organization_idFK → OrganizationCASCADE
target_idFK → MonitoringTargetCASCADE
anomaly_triggerCharField(200)NOT NULL
pattern_typeCharField(30)NOT NULLdaily, weekly, hourly
descriptionCharField(200)NOT NULL
occurrence_timesJSONFielddefault=list
confidenceFloatFieldNOT NULL0.0-1.0
insight_countIntegerFieldNOT NULL
first_seenDateTimeFieldNOT NULL
last_seenDateTimeFieldNOT NULL
acknowledgedBooleanFielddefault=False
created_atDateTimeFieldauto_now_add

Meta: ordering = ['-confidence']


4.6 App signage

4.6.1 SignageVendorAdapter

Tabla: signage_signagevendoradapter

Configuración de adaptador vendor-specific para comunicación con dispositivos de signage. Scope global (sin FK a Organization).

CampoTipoConstraintsDescripción
idAutoFieldPK
nameCharField(100)UNIQUENombre del adaptador
slugSlugField(50)UNIQUE
vendor_profile_idFK → VendorProfileSET_NULL, nullable
adapter_typeCharField(20)choicesjson_rpc, rest_api, snmp_only
api_url_templateCharField(500)blank, default=”Template URL con {ip}
auth_typeCharField(20)choices, default=‘basic’none, basic, bearer, api_key, oauth2, digest
capabilitiesJSONFielddefault=dict{"push_content": true, ...}
content_push_methodCharField(20)choices, default=‘none’webdav, rest_upload, cloud_api, smil_http, none
display_control_protocolCharField(20)choices, default=‘none’none, mdc, lg_protocol, pjlink, cip, sicp
display_control_portIntegerFieldnullable
enabledBooleanFielddefault=True

Meta: ordering = ['name']


4.6.2 SignagePlayer

Tabla: signage_signageplayer

Reproductor/player de signage físico vinculado a network discovery.

CampoTipoConstraintsDescripción
idAutoFieldPK
publish_tokenUUIDFieldUNIQUE, auto uuid4, no editableToken para URL pública de contenido
organization_idFK → OrganizationCASCADE
display_nameCharField(200)NOT NULL
locationCharField(200)blank, default=”Ubicación física
device_profile_idFK → DeviceProfileSET_NULL, nullable
monitoring_target_idFK → MonitoringTargetSET_NULL, nullable
adapter_idFK → SignageVendorAdapterSET_NULL, nullable
api_credentialsJSONFielddefault=dictCifrado via CredentialManager
display_infoJSONFielddefault=dictResolución, modelo, firmware
statusCharField(20)choices, default=‘offline’online, offline, error, maintenance
current_playlist_idFK → PlaylistSET_NULL, nullable
current_schedule_idFK → ScheduleSET_NULL, nullable
last_screenshotCharField(500)blank, default=”
last_heartbeatDateTimeFieldnullable
created_atDateTimeFieldauto_now_add
updated_atDateTimeFieldauto_now

M2M: groups → RackGroup (related_name: signage_players)

Constraints: unique_together = ['organization', 'device_profile']

Meta: ordering = ['display_name']


4.6.3 SignageOperation

Tabla: signage_signageoperation

Log de auditoría de operaciones de signage con rollback.

CampoTipoConstraintsDescripción
idAutoFieldPK
organization_idFK → OrganizationCASCADE
player_idFK → SignagePlayerCASCADE
operation_typeCharField(20)choicespublish, pause, resume, black, test, corporate, clear, restart
previous_stateJSONFielddefault=dictEstado antes de la operación
new_stateJSONFielddefault=dictLo que se aplicó
descriptionCharField(300)blank, default=”
performed_by_idFK → UserSET_NULL, nullable
created_atDateTimeFieldauto_now_add

Meta: ordering = ['-created_at']

Auto-purge: Mantiene últimas 50 por organización.


4.6.4 MediaAsset

Tabla: signage_mediaasset

Contenido multimedia subido para playlists de digital signage.

CampoTipoConstraintsDescripción
idAutoFieldPK
organization_idFK → OrganizationCASCADE
nameCharField(255)NOT NULL
file_pathCharField(500)blank, default=”Ruta relativa bajo MEDIA_ROOT/signage/
thumbnail_pathCharField(500)blank, default=”
asset_typeCharField(10)choices, default=‘image’image, video, html5, url, stream
statusCharField(20)choices, default=‘uploading’uploading, processing, ready, error
mime_typeCharField(100)blank, default=”
file_sizeBigIntegerFielddefault=0Bytes
durationFloatFieldnullableSegundos (video/stream)
resolutionCharField(20)blank, default=”ej: 1920x1080
metadataJSONFielddefault=dictCodec, bitrate, etc.
transcoded_variantsJSONFielddefault=dict{"1080p": "path", ...}
tagsArrayField(CharField(50))default=listEtiquetas
created_atDateTimeFieldauto_now_add
updated_atDateTimeFieldauto_now

Meta: ordering = ['-created_at']


4.6.5 Playlist

Tabla: signage_playlist

Colección ordenada de media assets con timing y transiciones.

CampoTipoConstraintsDescripción
idAutoFieldPK
organization_idFK → OrganizationCASCADE
nameCharField(200)NOT NULL
descriptionTextFieldblank, default=”
itemsJSONFielddefault=list[{asset_id, duration_seconds, transition, order}]
total_durationFloatFielddefault=0Segundos (auto-calculado)
loopBooleanFielddefault=True
is_autoBooleanFielddefault=FalseAuto-generada desde Content publish
created_atDateTimeFieldauto_now_add
updated_atDateTimeFieldauto_now

Meta: ordering = ['-updated_at']

Auto-purge: Elimina auto-playlists >24h (mantiene 20 más recientes).


4.6.6 Schedule

Tabla: signage_schedule

Reglas basadas en tiempo para mapear playlists a franjas horarias.

CampoTipoConstraintsDescripción
idAutoFieldPK
organization_idFK → OrganizationCASCADE
nameCharField(200)NOT NULL
descriptionTextFieldblank, default=”
rulesJSONFielddefault=list[{type, start, end, days, playlist_id, priority}]
default_playlist_idFK → PlaylistSET_NULL, nullable
timezoneCharField(50)default=‘UTC’
valid_fromDateTimeFieldnullable
valid_untilDateTimeFieldnullable
created_atDateTimeFieldauto_now_add
updated_atDateTimeFieldauto_now

Meta: ordering = ['name']


4.6.7 ClientProject

Tabla: signage_clientproject

Proyecto de cliente que agrupa dispositivos, contenido, playlists y schedules.

CampoTipoConstraintsDescripción
idAutoFieldPK
organization_idFK → OrganizationCASCADE
nameCharField(200)NOT NULL
client_nameCharField(200)blank, default=”
client_emailEmailFieldblank, default=”
client_phoneCharField(50)blank, default=”
descriptionTextFieldblank, default=”
share_tokenUUIDFieldUNIQUE, auto uuid4, no editableToken público para portal
pinCharField(20)blank, default=”PIN opcional
permissionsJSONFielddefault=dict{"upload": true, ...}
is_activeBooleanFielddefault=True
expires_atDateTimeFieldnullable
created_atDateTimeFieldauto_now_add
updated_atDateTimeFieldauto_now

M2M:

  • devices → DeviceProfile (related_name: client_projects)
  • playlists → Playlist (related_name: client_projects)

Meta: ordering = ['name']


4.6.8 PlaybackLog

Tabla: signage_playbacklog

Proof-of-play: registro de lo que se mostró, cuándo y por cuánto tiempo.

CampoTipoConstraintsDescripción
idAutoFieldPK
organization_idFK → OrganizationCASCADE
player_idFK → SignagePlayerCASCADE
asset_idFK → MediaAssetSET_NULL, nullable
playlist_idFK → PlaylistSET_NULL, nullable
started_atDateTimeFieldNOT NULL
ended_atDateTimeFieldnullable
durationFloatFieldnullableDuración real en segundos
verification_methodCharField(20)choices, default=‘api’api, screenshot, heartbeat, manual
screenshot_pathCharField(500)blank, default=”

Indexes:

  • (organization, -started_at)
  • (player, -started_at)

Meta: ordering = ['-started_at']


4.6.9 ContentDeployment

Tabla: signage_contentdeployment

Tracking de despliegue de playlists/schedules a players.

CampoTipoConstraintsDescripción
idAutoFieldPK
organization_idFK → OrganizationCASCADE
playlist_idFK → PlaylistSET_NULL, nullable
schedule_idFK → ScheduleSET_NULL, nullable
statusCharField(20)choices, default=‘pending’pending, deploying, deployed, failed, partial
deployment_resultsJSONFielddefault=dictResultados por player
error_messageTextFieldblank, default=”
created_atDateTimeFieldauto_now_add
updated_atDateTimeFieldauto_now

M2M: players → SignagePlayer (related_name: deployments)

Meta: ordering = ['-created_at']


Tabla: signage_clientsharelink

Link de acceso basado en token para clientes externos del portal de signage.

CampoTipoConstraintsDescripción
idAutoFieldPK
organization_idFK → OrganizationCASCADE
tokenUUIDFieldUNIQUE, auto uuid4, no editable, db_index
nameCharField(200)NOT NULL
client_nameCharField(200)blank, default=”
client_emailEmailFieldblank, default=”
pinCharField(10)blank, default=”PIN opcional (4-6 dígitos)
permission_levelCharField(20)choices, default=‘playlist’content_only, playlist, schedule, view_only
require_approvalBooleanFielddefault=FalseCambios van a cola de aprobación
is_activeBooleanFielddefault=True
expires_atDateTimeFieldnullableNULL = no expira
last_accessedDateTimeFieldnullable
access_countIntegerFielddefault=0
created_atDateTimeFieldauto_now_add
updated_atDateTimeFieldauto_now
created_by_idFK → UserSET_NULL, nullable

M2M:

  • players → SignagePlayer (related_name: share_links)
  • playlists → Playlist (related_name: share_links)
  • schedules → Schedule (related_name: share_links)

Meta: ordering = ['-created_at']


4.6.11 PortalChangeLog

Tabla: signage_portalchangelog

Audit trail y cola de cambios pendientes del portal de clientes.

CampoTipoConstraintsDescripción
idAutoFieldPK
share_link_idFK → ClientShareLinkCASCADE
actionCharField(20)choicesupload, replace, reorder, duration, schedule, delete
statusCharField(20)choices, default=‘applied’applied, pending, approved, rejected
detailsJSONFielddefault=dictQué cambió
pending_payloadJSONFielddefault=dictPayload para aplicar al aprobar
reviewed_by_idFK → UserSET_NULL, nullable
reviewed_atDateTimeFieldnullable
review_noteTextFieldblank, default=”
ip_addressGenericIPAddressFieldnullable
created_atDateTimeFieldauto_now_add

Indexes: (status, -created_at)

Meta: ordering = ['-created_at']


4.7 App terminal

4.7.1 Script

Tabla: terminal_script

Scripts de comandos de red.

CampoTipoConstraintsDescripción
idAutoFieldPK
nameCharField(100)NOT NULL
vendorCharField(50)blank, default=‘Generic’
categoryCharField(50)default=‘General’
descriptionTextFieldblank
is_safeBooleanFielddefault=FalseSi es seguro ejecutar
commandsJSONFielddefault=list["cmd1", "cmd2"]
created_by_idFK → UserSET_NULL, nullable
organization_idFK → OrganizationCASCADE
created_atDateTimeFieldauto_now_add
updated_atDateTimeFieldauto_now

4.7.2 AgentInstance

Tabla: terminal_agentinstance

Representa una instalación del Local Agent conectada al SaaS. Soporta roles Primary/Secondary para failover de Sentinel Mode.

CampoTipoConstraintsDescripción
idAutoFieldPK
agent_idCharField(64)UNIQUE, db_indexID único del agente
organization_idFK → OrganizationCASCADE
roleCharField(16)choices, default=‘secondary’primary, secondary
statusCharField(16)choices, default=‘offline’online, offline
hostnameCharField(255)blank, default=”
ip_addressGenericIPAddressFieldnullable
agent_versionCharField(32)blank, default=”
sentinel_activeBooleanFielddefault=False
last_seenDateTimeFieldnullable
connected_atDateTimeFieldnullable
disconnected_atDateTimeFieldnullable
created_atDateTimeFieldauto_now_add
updated_atDateTimeFieldauto_now

Indexes:

  • (organization, role)
  • (organization, status)

Meta: ordering = ['-connected_at']


5. Diagrama de Relaciones entre Entidades

┌─────────────────────────────────────────────────────────────────────┐
│                         CORE (Tenant Root)                          │
│                                                                     │
│  ┌──────────┐    ┌──────────────┐    ┌──────┐                      │
│  │ SaaSModule│◄──M2M──┤     Plan      │    │      │                │
│  └──────────┘    └──────┬───────┘    │      │                      │
│       ▲                  │FK          │      │                      │
│       │M2M          ┌────▼────────┐  │      │                      │
│       └─────────────┤Organization  │◄─┘      │                      │
│                     └──┬──┬──┬────┘          │                      │
│                        │  │  │               │                      │
│     ┌──────────────────┘  │  └──────────┐    │                      │
│     │FK                   │FK            │FK  │                      │
│  ┌──▼──┐  ┌──────────┐   │  ┌───────────▼─┐ │  ┌──────────────┐   │
│  │User  │  │SystemLog  │   │  │StoredCred   │ │  │ LoginLog     │   │
│  └──┬───┘  └──────────┘   │  └─────────────┘ │  └──────────────┘   │
│     │                      │                   │                     │
│     │1:1                   │                   │                     │
│  ┌──▼──────────────┐      │                   │                     │
│  │ModulePermission  │      │                   │                     │
│  └─────────────────┘      │                   │                     │
└───────────────────────────┼───────────────────┼─────────────────────┘
                            │                   │
┌───────────────────────────┼───────────────────┼─────────────────────┐
│                      RACKS │                   │                     │
│                            │                   │                     │
│  ┌──────────┐    ┌────────▼──┐    ┌──────────▼──┐                  │
│  │RackGroup  │◄──M2M──┤   Rack     │    │  Stencil    │            │
│  └────┬─────┘    └──┬────────┘    └─────────────┘                  │
│       │              │FK                                            │
│       │M2M     ┌─────▼────┐    ┌──────────────┐                    │
│       └────────┤  Device   ├───►ConfigBackup   │                    │
│                └─────┬─────┘    └──────────────┘                    │
│                      │FK                                            │
└──────────────────────┼──────────────────────────────────────────────┘
                       │
┌──────────────────────┼──────────────────────────────────────────────┐
│                BLUEPRINTS                                            │
│                       │                                             │
│  ┌──────────┐   ┌────▼────────────────┐    ┌──────────────┐        │
│  │AIPrompt   │   │BlueprintPlacement   │    │MapAnnotation  │       │
│  └──────────┘   └────┬────────────────┘    └──────┬───────┘        │
│                      │FK                          │FK               │
│                 ┌────▼───────┐                    │                  │
│                 │ Blueprint   │◄───────────────────┘                 │
│                 └────────────┘                                       │
└─────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────┐
│                        NETWORK                                       │
│                                                                     │
│  ┌──────────────┐    ┌───────────────┐    ┌──────────────┐          │
│  │VendorProfile  │◄──FK──┤  CustomMib    │    │PortConnection │     │
│  └──────────────┘    └───────────────┘    └──────────────┘          │
│                                                                     │
│  ┌──────────────────────────────┐                                   │
│  │       DeviceProfile           │                                  │
│  │  FK → Organization            │                                  │
│  │  FK → MonitoringTarget (opt)  │                                  │
│  │  FK → Device (opt)            │                                  │
│  │  M2M → RackGroup              │                                  │
│  └──────────────────────────────┘                                   │
└─────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────┐
│                       MONITORING                                     │
│                                                                     │
│  ┌────────────────┐    ┌──────────────┐    ┌──────────────┐         │
│  │MonitoringTarget │◄──FK──┤MetricSample  │    │AggregatedMetric│   │
│  └───────┬────────┘    └──────────────┘    └──────────────┘         │
│          │FK                                                        │
│          │         ┌──────────────────┐                              │
│          ├────────►│  AIInsight        │◄──FK── IncidentGroup       │
│          │         │  FK → KnownIssue  │◄──FK── Runbook             │
│          │         └────────┬─────────┘                             │
│          │                  │FK                                      │
│          │    ┌─────────────┼──────────────┐                        │
│          │    │             │              │                         │
│          │  AuditLog  Conversation  InsightLink                     │
│          │                                                          │
│          │    ┌───────────────┐    ┌──────────────────┐             │
│          ├───►│MonitoringAlert │───►│   AlertEvent      │           │
│          │    └───────────────┘    └──────────────────┘             │
│          │                                                          │
│  ┌───────┴───────────────────────────────────────────┐              │
│  │ ITSM: SLAPolicy, NotificationChannel,            │              │
│  │       EscalationPolicy → EscalationLevel,         │              │
│  │       MaintenanceWindow (M2M targets),            │              │
│  │       KnownIssue, Runbook, RecurringPattern       │              │
│  └───────────────────────────────────────────────────┘              │
└─────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────┐
│                        SIGNAGE                                       │
│                                                                     │
│  ┌────────────────────┐    ┌────────────────┐                       │
│  │SignageVendorAdapter │    │  MediaAsset     │                      │
│  │  (global)           │    └───────┬────────┘                      │
│  └────────┬───────────┘            │FK                              │
│           │FK                      │                                │
│  ┌────────▼───────────┐    ┌──────▼─────────┐    ┌──────────┐      │
│  │  SignagePlayer      │◄──M2M──┤ContentDeployment│    │ Schedule │  │
│  │  FK → Playlist      │    └────────────────┘    └──────────┘      │
│  │  FK → Schedule      │                                            │
│  └────────┬───────────┘    ┌──────────┐                             │
│           │FK              │ Playlist  │                             │
│  ┌────────▼────────┐      └──────────┘                              │
│  │SignageOperation  │                                                │
│  │PlaybackLog       │      ┌──────────────────┐                     │
│  └─────────────────┘      │ClientShareLink    │                     │
│                           │  M2M → Players    │                     │
│  ┌──────────────┐         │  M2M → Playlists  │                     │
│  │ClientProject  │         │  M2M → Schedules  │                    │
│  │ M2M → Devices │         └────────┬─────────┘                     │
│  │ M2M → Playlists│                │FK                              │
│  └──────────────┘         ┌────────▼─────────┐                      │
│                           │PortalChangeLog    │                      │
│                           └──────────────────┘                       │
└─────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────┐
│                        TERMINAL                                      │
│                                                                     │
│  ┌──────────┐    ┌───────────────┐                                  │
│  │  Script   │    │AgentInstance   │                                 │
│  │  FK→Org   │    │  FK→Org        │                                │
│  │  FK→User  │    │  unique agent_id│                               │
│  └──────────┘    └───────────────┘                                  │
└─────────────────────────────────────────────────────────────────────┘

6. Patrones de Diseño Clave

6.1 Soft Delete (deleted_at)

Tres modelos implementan borrado lógico: Organization, Rack y Blueprint. Cada uno tiene:

  • Campo deleted_at (DateTimeField, nullable)
  • Métodos soft_delete() y restore()
  • Propiedad is_deleted
  • Los queries regulares no filtran automáticamente por deleted_at — el filtrado se hace en las vistas/API

6.2 Campos JSON

El uso de JSONField es extensivo para datos semi-estructurados:

ModeloCampoUso
Device.model_dataTextField (JSON manual)Metadatos del dispositivo (half_width, slot, stencil)
Device.management_configTextField (JSON manual)IP, credenciales cifradas, vendor
Organization.sentinel_configJSONFieldConfiguración de Sentinel Mode
DeviceProfile.interfacesJSONFieldifTable SNMP walk completo
DeviceProfile.deep_snmp_dataJSONFieldDatos de deep discovery MIB-enriched
DeviceProfile.lldp_neighborsJSONFieldVecinos LLDP (lista)
MonitoringTarget.configJSONFieldConfig adicional SNMP
AIInsight.commandsJSONFieldComandos sugeridos (lista)
Playlist.itemsJSONFieldItems de playlist (lista de objetos)
Schedule.rulesJSONFieldReglas de scheduling (lista)
StoredCredential.encrypted_dataJSONFieldDatos cifrados Fernet

Nota: Device.model_data y Device.management_config usan TextField con JSON manual (legacy) en lugar de JSONField nativo de PostgreSQL.

6.3 Cifrado Fernet (AES-128-CBC)

El modelo StoredCredential almacena credenciales sensibles cifradas con Fernet (basado en AES-128-CBC + HMAC-SHA256). Implementado via core.security.credential_manager.CredentialManager.

Campos cifrados:

  • community (SNMP v2c)
  • auth_key, priv_key (SNMPv3)
  • ssh_password, enable_password (SSH)
  • http_password (HTTP)

Adicionalmente, Device.management_config puede contener valores cifrados con Fernet (detectados por el prefijo gAAAA).

6.4 Audit Logging

El sistema tiene múltiples capas de auditoría:

NivelModeloPropósito
SistemaSystemLogAcciones generales (AUTH, RACK, etc.)
LoginLoginLogCada intento de login (v1.0.48)
ImpersonaciónImpersonationLogSesiones de superusuario
CNSAIInsightAuditLogAcciones sobre insights
SignageSignageOperationOperaciones de publish/clear (auto-purge 50)
PortalPortalChangeLogCambios del cliente externo
NotificacionesNotificationLogEnvíos de notificaciones ITSM

6.5 ArrayField (PostgreSQL-specific)

Los siguientes campos usan ArrayField de django.contrib.postgres:

ModeloCampoTipo Base
VendorProfile.aliasesCharField(100)
VendorProfile.snmp_communitiesCharField(100)
VendorProfile.sys_descr_patternsCharField(500)
VendorProfile.mib_modulesCharField(100)
VendorProfile.http_server_patternsCharField(500)
VendorProfile.http_body_patternsCharField(500)
VendorProfile.os_typesCharField(100)
DeviceProfile.capabilitiesCharField(50)
DeviceProfile.mib_modules_loadedCharField(100)
MediaAsset.tagsCharField(50)

6.6 Tablas con db_table Custom

Modelodb_tableRazón
VendorProfilevendor_profilesNomenclatura semántica
CustomMibcustom_mibsNomenclatura semántica
DeviceProfiledevice_profilesNomenclatura semántica
PortConnectionport_connectionsNomenclatura semántica

El resto de modelos usan el naming convention de Django: {app_label}_{model_name_lower}.


7. Resumen de Migraciones

AppTotal ArchivosMigraciones Notables
core240010_storedcredential (Fernet encryption), 0011_loginlog
racks120001_initial (Organization, User, Rack, Device)
blueprints90006_aiprompt (Brain Editor Save As)
network560037_deviceprofile_snmpv3 (5 campos v3), 0038_deviceprofile_groups_m2m, 0039_portconnection (XOR constraint), 0040_ups_vendor_profiles (RunPython seed), 0055_deviceprofile_assigned_page (Ficha Central F4)
monitoring260016_add_scope_to_monitoring_target (histórica — el scope se eliminó en 0025_ficha_central_remove_scope, Ficha Central F4), múltiples RunPython para ITSM models
signage140006_signageoperation, 0007_playlist_is_auto
terminal30001_initial, 0002_agentinstance
Total144(excluyendo __init__.py en cada carpeta = ~151 archivos)

RunPython notables (data migrations):

  • Seed de VendorProfiles para ~40 vendors de red (network)
  • Seed de 9 UPS vendor profiles (network 0040)
  • Seed de 12 SignageVendorAdapters (signage)
  • Seed de SaaSModules y Plans (core)

8. Backup y Restore

Documentación detallada: Documentation/admin/BACKUP_RESTORE.md

Backup PostgreSQL

# Desarrollo
docker compose exec db pg_dump -U crearack_user crearack_pro > backup.sql

# Producción
ssh root@crearack.com "docker exec crearack-pro-zcmvsl-db-1 \
  pg_dump -U crearack_user crearack_pro" > backup_prod.sql

Restore

# Desarrollo
docker compose exec -T db psql -U crearack_user crearack_pro < backup.sql

# Producción (con precaución)
cat backup.sql | ssh root@crearack.com "docker exec -i crearack-pro-zcmvsl-db-1 \
  psql -U crearack_user crearack_pro"

Backup via Django (con remapeo de IDs)

La app incluye endpoints de backup/restore nativos que manejan:

  • Exportación de modelos seleccionados a JSON
  • Remapeo de IDs al importar (evita colisiones)
  • Preservación de relaciones (FKs, M2M, cables)

9. Notas de Rendimiento

Conexiones

ParámetroDesarrolloProducción
max_connections100 (default PG)500 (customizado)
CONN_MAX_AGE0 (nueva por request)600s (reutilizar 10 min)
CONN_HEALTH_CHECKSNoSí (Django 6)
shared_buffers128MB (default)256MB

Incidente de Saturación (01-04-2026)

El modo Sentinel con 268 targets saturó max_connections=100 tras reconexión post-downtime de 5 días. Solución temporal: subir a 500. Solución definitiva pendiente: pgbouncer como connection pooler en modo transaction.

Cuellos de Botella Conocidos

  1. MetricSample: Tabla de alto volumen (38+ referencias activas). Candidata a eliminación cuando se migre completamente a VictoriaMetrics.
  2. DeviceProfile: Modelo muy ancho (~50 campos) con múltiples JSONFields grandes (interfaces, deep_snmp_data, raw_snmp_data). Considerar partición o archivado de raw data.
  3. PlaybackLog / SignageOperation: Tablas de alto volumen con auto-purge (50 ops, 24h playlists).
  4. Sin pgbouncer: Cada worker de Daphne/Huey abre conexiones independientes a PostgreSQL.

Mejoras Pendientes

  • pgbouncer: Connection pooler en modo transaction entre Django y PostgreSQL
  • Sentinel batch status: Agrupar device status updates en bulk POST
  • Migration squash: 44 migraciones en network/ — diferido a v2.0.0 por riesgo
  • MetricSample cleanup: Eliminar cuando VictoriaMetrics cubra todas las referencias

Mantenido por: Claude (Anthropic) + Equipo CreaRack

Véase también

  • [[crearack-tech—architecture—pg18-migration-plan]]
  • [[crearack-tech—admin—cache-and-database]]
  • [[crearack-tech—guides—database-admin-guide]]
  • [[decision—20260315—postgres-18-pgbouncer]]
  • [[decision—20260201—conn-max-age-daphne]]
  • [[concept—saas—multi-tenancy]]
  • [[decision—20260101—django-ninja-vs-drf]]