Base de datos CreaRack Pro · arquitectura técnica
Referencia técnica del esquema PostgreSQL. Para una explicación accesible ve a [[crearack-tech—bd—coloquial]].
1. Stack de persistencia
| Componente | Versión | Rol | Notas |
|---|---|---|---|
| PostgreSQL | 18 | BD principal, fuente de verdad | Contenedor crearack-pro-zcmvsl-db-1 |
| pgbouncer | latest | Connection pooling (transaction mode) | Contenedor crearack-pro-zcmvsl-pgbouncer-1. Necesario por Daphne ASGI + workers Huey |
| Valkey | 7.2 | Cache + broker Huey + Channels backend | NO persiste datos críticos |
| VictoriaMetrics | 1.106.1 | Métricas time-series (180 días) | Fuera de PG: separación operativa de datos cuantitativos |
| WhiteNoise + Brotli | 1.1.0 | Servir media/static (no toca BD) | — |
Conexiones
CONN_MAX_AGE = 0(obligatorio con Daphne ASGI — footgun revertido 4 veces, causa saturación si se cambia)DATABASES['default']['HOST']apunta apgbouncer:6432en PROD/STAGE- Pool default por worker. PG
max_connections=500, pgbouncer pooldefault_pool_size=20 - Desde s215 la app conecta con el rol
crearack_app(NOSUPERUSER) — condición necesaria para que RLS muerda de verdad (ver 2.2) - ⚠️ Los tests del gate pre-push usan
config.settings.testcon BD directa adb:5432sin pgbouncer (s233 — mata los falsos rojos del teardown paralelo)
2. Multi-tenancy · arquitectura de dos capas
2.1 Capa ORM (Django)
Patrón obligatorio en todos los queries:
device = get_object_or_404(Device, id=device_id, rack__organization=org)
get_current_org(request) retorna None si el user no tiene org. No hay fallback — cada endpoint debe verificar.
2.2 Capa DB (PostgreSQL RLS)
Implementado en v1.0.50 (03-04-2026), migration core/0017_rls_policies. ACTIVO DE VERDAD desde s215 (v1.48.1, 09-07-2026) en PROD y desde s224 en STAGE: la app conecta como crearack_app NOSUPERUSER (antes conectaba como superuser y las 38 políticas eran inertes — footgun footguns_rls_inerte_superuser_prod).
Mecanismo:
TenantRLSMiddleware(después deModuleGatingMiddleware) ejecutaSET app.current_org_id = '{org_id}'al inicio del request- PG aplica política
USING (organization_id = current_setting('app.current_org_id')::int)en cada SELECT/UPDATE/DELETE - Al terminar,
RESET app.current_org_idpreviene leakage en conexiones pooled
Bypass (estado post-s215):
| Condición | app.current_org_id | Acceso |
|---|---|---|
| Superuser de la app | 0 | Todas las filas |
| Anónimo / sin org | 0 | Bypass (páginas públicas) |
| Usuario con org | <org_id> | Solo filas de su org |
crearack_app sin SET explícito | 0 (default de rol, intencionado: modo admin/migraciones/Huey) | Todas las filas |
| Rol sin default y GUC jamás fijado | NULL | 0 filas (¡no es bypass!) |
postgres superuser / dani_readonly (psql diagnóstico) | — | Ve todo (RLS no aplica a superuser/roles con BYPASSRLS) |
⚠️ Trampa de verificación (cazada s224): un test crudo por psql con
crearack_apphereda el default'0'y ve TODO — engaña. Para probar el aislamiento hay que fijar el GUC a un org real:SET app.current_org_id = '1'→ 1 fila propia ·'999999'→ 0 filas. Pendiente aparte: endurecer el bypass de Huey (fijar GUC por task y retirar el default de rol — ciclo propio, necesita test de task real).
2.3 Cobertura
29 tablas con organization_id NOT NULL (aislamiento estricto):
| App | Tablas |
|---|---|
racks | rack, rackgroup |
blueprints | blueprint, aiprompt |
network | device_profiles, custom_mibs, port_connections |
monitoring | monitoringtarget, aiinsight, slapolicy, notificationchannel, escalationpolicy, incidentgroup, maintenancewindow, knownissue, runbook, recurringpattern |
signage | signageplayer, signageoperation, mediaasset, playlist, schedule, clientproject, playbacklog, contentdeployment, clientsharelink |
terminal | script, agentinstance |
core | storedcredential |
6 tablas con organization_id NULLABLE (filas propias + globales):
| Tabla | Por qué nullable |
|---|---|
core_systemlog | Logs visibles por todos |
core_scripttemplate | Templates globales compartidos |
racks_boxcategory | Categorías base pre-instaladas |
racks_stencil | Stencils de fabricantes compartidos |
monitoring_monitoringalert | Alertas globales |
core_user | Django AuthenticationMiddleware consulta usuarios antes del RLS middleware |
Tablas con FK heredada (Device→Rack→Org, ConfigBackup→Device→Rack→Org, etc.) no necesitan política propia: la integridad referencial protege automáticamente vía CASCADE.
3. Mapa ER global
erDiagram
Organization ||--o{ User : "tiene"
Organization ||--o{ Rack : "es dueña de"
Organization ||--o{ Blueprint : "es dueña de"
Organization ||--o{ MonitoringTarget : "monitoriza"
Organization ||--o{ SignagePlayer : "controla"
Organization ||--o{ AgentInstance : "tiene agents"
Organization ||--o{ StoredCredential : "almacena"
Organization }o--|| Plan : "suscrita a"
Plan }o--o{ SaaSModule : "incluye"
Organization }o--o{ SaaSModule : "extra a la carta"
User ||--o{ LoginLog : "genera"
User ||--o{ SystemLog : "audita"
User ||--o{ ImpersonationLog : "impersona"
User ||--o| ModulePermission : "permisos extra"
Rack ||--o{ Device : "contiene"
Rack ||--o{ ConfigBackup : "snapshots"
Rack ||--o{ BlueprintPlacement : "ubicado en"
Rack }o--o{ RackGroup : "agrupado en"
Blueprint ||--o{ BlueprintPlacement : "ubica"
Blueprint ||--o{ MapAnnotation : "anotaciones"
Device ||--o{ PortConnection : "conexiones"
Device }o--|| DeviceProfile : "perfil"
DeviceProfile }o--|| VendorProfile : "vendor"
SignagePlayer }o--|| SignageVendorAdapter : "adapter"
SignagePlayer ||--o{ PlaybackLog : "reproduce"
Playlist ||--o{ MediaAsset : "contiene"
Schedule }o--|| Playlist : "programa"
MonitoringTarget ||--o{ AIInsight : "genera"
AIInsight }o--o{ IncidentGroup : "agrupado en"
AIInsight ||--o{ AIInsightAuditLog : "auditoría"
AIInsight ||--o{ InsightConversation : "Q&A"
IncidentGroup }o--|| SLAPolicy : "SLA"
IncidentGroup }o--|| EscalationPolicy : "escalation"
4. Apps y modelos · referencia por archivo
4.1 core/ · multi-tenancy, auth, auditoría (11 modelos)
| Modelo | Propósito | FKs clave | Notas |
|---|---|---|---|
SaaSModule | Registry de módulos gated (Rack Editor, Observatory, Signage…) | — | is_core = siempre activo |
Plan | Tier de suscripción (Starter, Pro, Custom) | modules M2M SaaSModule | — |
Organization | Tenant SaaS (entidad raíz) | plan FK Plan, extra_modules M2M SaaSModule | Soft-delete vía deleted_at. Purga programada 90d (Huey purge_deleted_organizations) |
User | AbstractUser extendido | organization FK Organization | Roles: admin / operator / readonly. UniqueConstraint email (cuando no vacío) |
SystemLog | Auditoría de acciones | user FK User, organization FK Organization (nullable) | Indices (org, -ts), (user, -ts), (level, -ts) |
ScriptTemplate | Templates SSH reutilizables (Cisco, Juniper, Aruba, bash, python) | organization FK (nullable = global), created_by FK User | unique_together(name, organization) |
ImpersonationLog | Audit de superuser impersonation | admin FK User, target FK User | Sólo superuser |
ModulePermission | Permisos granulares per-user (JSONField) | user OneToOne User, updated_by FK User | Opt-in layer sobre rol |
TemporaryAccess | Elevación temporal de permisos | user FK User, granted_by FK User | Index (user, expires_at) |
StoredCredential | Credenciales cifradas (SNMP/SSH/HTTP) | organization FK Organization, created_by FK User | encrypted_data JSONField con Fernet. unique_together(name, organization) |
LoginLog | Auditoría de logins (success/fail) | user FK User (nullable para fails) | Métodos: password, passkey, social, token |
4.2 racks/ · Rack Editor (6 modelos)
| Modelo | Propósito | FKs clave | Notas |
|---|---|---|---|
BoxCategory | Categorías de cajas (Server, Switch, PDU, KVM, etc.) | organization FK (nullable = global) | unique_together(name, organization) |
RackGroup | Agrupación lógica (Data Center A, Row 1) | organization FK Organization | color para UI |
Stencil | Templates de dispositivos (image, U-height, manufacturer) | organization FK (nullable = system stencil) | Index (org, category). extra_data JSON con specs |
Rack | Server rack (height_u, location, status) | organization FK Organization, parent_rack FK self (children), groups M2M RackGroup | Soft-delete deleted_at. is_template para sistema de templates |
Device | Dispositivo dentro de rack | rack FK Rack, groups M2M RackGroup | u_position, u_height. management_config JSON con IP/credentials cifrados Fernet. Status: online/offline/warning/unknown |
ConfigBackup | Snapshots de running-config | device FK Device, rack FK Rack, created_by FK User | config_hash SHA256 detecta cambios. Tipos: auto / manual / pre_change |
4.3 blueprints/ · Map Editor + Auto-Plan AI (4 modelos)
| Modelo | Propósito | FKs clave | Notas |
|---|---|---|---|
Blueprint | Mapa infinito (floorplan / topología) | organization FK Organization | Soft-delete. Settings de canvas (scale, dark_mode, cable_curvature) |
BlueprintPlacement | Posición de un rack en un blueprint | blueprint FK Blueprint, rack FK Rack | unique_together(blueprint, rack). style_props JSON |
MapAnnotation | Walls, text, zones | blueprint FK Blueprint | data JSON con coordenadas |
AIPrompt | Prompts Auto-Plan guardados | organization FK Organization | unique_together(organization, name). is_default |
4.4 monitoring/ · Observatory + ITSM + CNS (19 modelos)
Repartido en 3 archivos:
models.py(5),models_insight.py(3 · CNS/AI Brain),models_itsm.py(11 · ITSM).
models.py — Network monitoring core:
| Modelo | Propósito | FKs clave |
|---|---|---|
MonitoringTarget | Dispositivo o IP a monitorear (SNMP/ping/TCP probe desde v1.63.x/#205/HTTP) | organization FK Organization |
MetricSample | Muestra individual (histórico corto) | target FK MonitoringTarget |
MonitoringAlert | Config de alertas (per-device o global) | organization FK (nullable = global) |
AlertEvent | Historial de alertas disparadas | alert FK MonitoringAlert |
AggregatedMetric | Métricas agregadas para queries históricas eficientes | target FK MonitoringTarget |
models_insight.py — Network Sentinel AI (CNS):
| Modelo | Propósito | FKs clave |
|---|---|---|
AIInsight | Diagnóstico generado por AI sobre incidente de red | organization FK Organization, target FK MonitoringTarget |
AIInsightAuditLog | Auditoría de toda acción sobre un insight | insight FK AIInsight |
InsightConversation | Q&A iterativa sobre un insight (Explain) | insight FK AIInsight |
models_itsm.py — ITSM operacional:
| Modelo | Propósito | FKs clave |
|---|---|---|
SLAPolicy | SLA timers configurables por nivel de riesgo | organization FK Organization |
NotificationChannel | Webhook / email / Slack / Teams | organization FK Organization |
NotificationLog | Log de notificaciones enviadas | channel FK NotificationChannel |
EscalationPolicy | Política multi-nivel | organization FK Organization |
EscalationLevel | Step individual dentro de policy | policy FK EscalationPolicy |
IncidentGroup | Agrupa insights con misma root cause | organization FK, sla_policy FK, escalation_policy FK |
MaintenanceWindow | Suprime creación de insights durante mantenimiento | organization FK Organization |
KnownIssue | Issue documentado con resolución | organization FK Organization |
InsightLink | Link manual entre insights | insight_a FK AIInsight, insight_b FK AIInsight |
Runbook | Procedimiento de remediación reusable | organization FK Organization |
RecurringPattern | Patrón recurrente detectado proactivamente | organization FK Organization |
4.5 network/ · Vendor intelligence + Auto-Provision (4 modelos)
| Modelo | Propósito | FKs clave | Notas |
|---|---|---|---|
VendorProfile | Catálogo global de vendors (Cisco, Juniper, Aruba, Xirrus…) | — (scope global) | Contiene SNMP communities, sysDescr patterns, MIB modules, deep discovery OIDs, monitoring OIDs, fast_poll OIDs, comandos SSH, fingerprinting HTTP. Extensible vía admin/API |
CustomMib | MIB ASN.1 subido por tenant | organization FK Organization | Compilado a pysnmp con pysmi-lextudio |
DeviceProfile | Perfil de dispositivo descubierto | organization FK Organization, vendor FK VendorProfile | OIDs/credenciales aprendidas via auto-provision |
PortConnection | Conexión cableada entre puertos de devices | organization FK Organization, device_a FK Device, device_b FK Device | Tipo de cable, longitud, label |
4.6 terminal/ · SSH + Local Agent (2 modelos)
| Modelo | Propósito | FKs clave | Notas |
|---|---|---|---|
Script | Script SSH guardado | organization FK Organization, created_by FK User | commands JSONField (lista). is_safe flag. Lenguaje: cisco_ios, junos, aruba, bash, python |
AgentInstance | Instalación de Local Agent (Sentinel failover) | organization FK Organization | Roles: primary / secondary. Solo el primary corre Sentinel monitoring. agent_id UNIQUE indexed |
4.7 signage/ · Digital Signage CMS (9 modelos)
| Modelo | Propósito | FKs clave | Notas |
|---|---|---|---|
SignageVendorAdapter | Adapter vendor-specific (Samsung MDC, LG, PJLink, Crestron, Philips SICP) | vendor_profile FK VendorProfile | Scope global. Tipos: json_rpc, rest_api, snmp_only |
SignagePlayer | Player físico de signage | organization FK Organization, adapter FK SignageVendorAdapter | Status: online/offline/error/maintenance |
SignageOperation | Operación enviada a un player (push content, reboot, etc.) | player FK SignagePlayer | Audit trail |
MediaAsset | Imagen/vídeo subido | organization FK Organization | Storage R2 |
Playlist | Lista ordenada de assets | organization FK Organization | Items M2M MediaAsset con orden |
Schedule | Programación temporal de playlists | organization FK Organization, playlist FK Playlist | Cron-like |
ClientProject | Proyecto de cliente (cartelería) | organization FK Organization | Agrupa playlists + share links |
PlaybackLog | Proof-of-play (qué reprodujo cada player y cuándo) | player FK SignagePlayer, asset FK MediaAsset | Compliance reporting |
ContentDeployment | Despliegue de contenido a un grupo de players | playlist FK Playlist, players M2M SignagePlayer | Estado de deployment |
5. Índices clave (no exhaustivo)
| Tabla | Índice | Razón |
|---|---|---|
racks_rack | (organization, deleted_at) | Filtro tenant + trash |
racks_device | (rack, u_position) | Render del rack por posición |
racks_stencil | (organization, category) | Picker de stencils |
blueprints_blueprint | (organization, deleted_at) | Lista + trash |
core_systemlog | (organization, -timestamp), (user, -timestamp), (level, -timestamp) | Filtros típicos del Activity |
core_loginlog | (user, -timestamp), (success, -timestamp), (ip_address, -timestamp), (username_attempted, -timestamp) | Seguridad / detección brute-force |
core_temporaryaccess | (user, expires_at) | Lookup permisos vigentes |
terminal_agentinstance | (organization, role), (organization, status) | Sentinel failover lookup |
core_storedcredential | unique_together(name, organization) | Evitar duplicados nombre |
racks_configbackup | (device, -created_at), (rack, -created_at) | History view |
6. Migrations y mantenimiento
6.1 Crear migrations
docker compose exec web python manage.py makemigrations
docker compose exec web python manage.py migrate
Footgun: las migraciones numéricas Django son inmutables una vez aplicadas en PROD. Si haces edit in-place y vuelves a
migrate, no se re-ejecuta (Django ya tiene la row endjango_migrations). Crear nueva migration siempre.Nota operativa: Dokploy auto-aplica migraciones en cada deploy (STAGE y PROD migran al mergear — memoria
reference_dokploy_auto_migrate).
6.2 Backup pg_dump diario (PROD)
Cron en server crearack-prod:
# /etc/cron.d/pg_backup
0 3 * * * root /opt/crearack/scripts/pg_dump_daily.sh
Output a /opt/pg-backups/crearack-YYYY-MM-DD.dump. Retención 7 días local + sync semanal a DR backup workspace. Además: WAL archiving continuo + pg_basebackup semanal (dom 04:00) a Object Storage — cadena PITR verificada E2E (21-07-2026).
6.3 Backup per-tenant (Huey)
# Huey task agendada
@huey.periodic_task(crontab(hour=3, minute=30))
def run_org_backups():
...
Output: MEDIA_ROOT/backups/{org_id}/backup_YYYY-MM-DD.zip con backup_data.json (14 entity types) + media/uploads/. Retención: Organization.backup_retention_days (default plan: Starter 7d, Pro 30d).
6.4 Integridad cross-tenant (Huey)
@huey.periodic_task(crontab(hour=4, minute=0))
def check_tenant_integrity():
...
Detecta anomalías tipo Device.org_id != Rack.org_id y registra en SystemLog.
7. Operaciones comunes (psql)
Verificar RLS activo en una tabla
SELECT relname, relrowsecurity, relforcerowsecurity
FROM pg_class WHERE relname = 'racks_rack';
SELECT * FROM pg_policies WHERE tablename = 'racks_rack';
Listar tablas con RLS habilitado
SELECT tablename, rowsecurity
FROM pg_tables
WHERE schemaname = 'public' AND rowsecurity = true;
Probar el aislamiento RLS de verdad (post-s215)
-- Como crearack_app, fijar un org REAL (el default de rol '0' es bypass y engaña):
SET app.current_org_id = '1'; -- debe devolver solo filas del org 1
SELECT count(*) FROM racks_rack;
SET app.current_org_id = '999999'; -- debe devolver 0 filas
SELECT count(*) FROM racks_rack;
Matar conexiones idle (emergencia)
SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE state = 'idle' AND pid != pg_backend_pid();
Detectar integridad cross-tenant manualmente
SELECT d.id, d.name, d.organization_id AS device_org, r.organization_id AS rack_org
FROM racks_device d
JOIN racks_rack r ON d.rack_id = r.id
WHERE d.organization_id != r.organization_id;
8. Footguns documentados
| Footgun | Síntoma | Fix |
|---|---|---|
CONN_MAX_AGE != 0 | Saturación conexiones BD bajo carga | Mantener CONN_MAX_AGE = 0 con Daphne ASGI (revertido 4 veces) |
Tuple returns Ninja (status, body) | Errores silenciosos en endpoints | Validar siempre el tuple, no asumir 200 |
| Migrations numéricas | Edit in-place no re-ejecuta | Nueva migration siempre |
dani_readonly bypasea RLS | Ves datos de TODOS los tenants en psql | Intencional, pero recordarlo |
GUC RLS jamás fijado = NULL | 0 filas (parece BD vacía, no es bypass) | Fijar app.current_org_id a un org real para probar aislamiento (s215/s224) |
| Stencils system + tenant | Query falla si solo filtras por org | Usar `Q(organization=org) |
9. Acceso operativo
| Cómo | Comando |
|---|---|
| Shell Django | docker compose exec web python manage.py shell |
psql directo (vía NetBird — proxy socat :5432 sobre wt0) | psql postgres://USER:PASS@100.96.156.31:5432/crearack |
| Ver contenedor BD | ssh root@100.96.156.31 "docker logs crearack-pro-zcmvsl-db-1 --tail 50" (SSH solo-NetBird desde s222) |
| Tamaño tablas | SELECT relname, pg_size_pretty(pg_total_relation_size(oid)) FROM pg_class WHERE relkind='r' ORDER BY pg_total_relation_size(oid) DESC LIMIT 20; |
Véase también
- [[crearack-tech—bd—coloquial]] — Versión accesible para entrada al proyecto
- [[workspace-tech—bd—tecnico]] — BD del workspace (Cloudflare D1)
- [[crearack-tech—agents—dev-core]] — Briefing técnico del módulo
core Documentation/guides/DATABASE_ADMIN_GUIDE.md— Manual operativo completoDocumentation/guides/SECURITY_GUIDE.md— Multi-tenancy security detailDocumentation/guides/CAPACITY_PLANNING.md— Plan de escalado (>200 tenants → read-replica)