Feature s115: Búsquedas guardadas persistidas en BD
Resumen Ejecutivo
Sesión: 115 (2026-06-08)
Ámbito: Auto-Provision · Producto
Estado: Merged (commit@1cd2537)
Autor: Edu (con Claude Code Opus)
Cierra: Parte 4 de la iniciativa Auto-Provision (no desperdiciar búsquedas)
Lo que cambia
Las búsquedas de escaneo que guardabas en Auto-Provision se guardaban en el navegador, así que al limpiar la caché o recargar la página a veces desaparecían y el botón “Load Previous Results” decía que no había ninguna (reporte de Edu).
Ahora se guardan en la base de datos, de forma que no se pierden, y además las ve todo tu equipo (cualquier compañero de la misma organización). Se conservan las 20 más recientes por org.
El aspecto y el funcionamiento del listado son los mismos; solo cambia dónde se guardan — de localStorage a BD.
Antecedente: Parte 4a (s114)
La sesión 114 (2026-06-08) añadió el botón “Refresh” por dispositivo en el Step 3, que permite reutilizar las credenciales para recomprobar un dispositivo sin volver al Step 1. La s115 cierra la Parte 4 añadiendo persistencia de búsquedas.
Cambios Técnicos
Modelo Nuevo: network.ScanSession
- Tabla:
scan_sessions(BD) - Campos principales:
organization(FK): organización propietaria (no hay aislamiento por usuario — es org-wide).created_by(FK, nullable): quién guardó la búsqueda (auditoría).name(CharField): etiqueta legible del escaneo (p.ej. “10.0.0.0/24”, “Prod-Network”).profiles(M2M aDeviceProfile): lista de dispositivos capturados.created_at(DateTimeField): timestamp de creación.
- Índice:
(organization, -created_at)para búsquedas rápidas. - Límite automático: solo 20 más recientes por org (poda al crear).
- Integridad: si se borra un perfil, desaparece solo de la sesión; no elimina la búsqueda.
- Migración:
network/migrations/0048_scansession.py(2026-06-08 13:54 UTC).
API REST: /auto-provision/scans
Router: network/api/scan_sessions.py
Operaciones:
| Método | URL | Permisos | Descripción |
|---|---|---|---|
GET | /auto-provision/scans | network.view | Lista búsquedas de la org (máx 20, recientes primero) |
POST | /auto-provision/scans | network.edit | Crea una búsqueda. Poda automáticamente si ≥20. |
DELETE | /auto-provision/scans/{id} | network.edit | Borra una búsqueda. RLS: solo si pertenece a la org. |
Esquemas Ninja:
ScanSessionOut: respuesta de lectura (condevice_countcalculado en vivo).ScanSessionCreate: payload esperado en POST.
RLS: Aislamiento completo por organización. Un usuario solo ve/modifica búsquedas de su org.
Frontend: static/js/network/auto_provision/sessions.js
Cambios principales:
| Antes | Después |
|---|---|
localStorage["auto_provision_scans"] | GET /api/network/auto-provision/scans |
_getSavedScans() → JSON en localStorage | loadSavedProfiles() → API call |
_saveScanSession() → localStorage.setItem | _saveScanSession() → POST /auto-provision/scans |
_deleteSavedScan(index) → localStorage.splice | _removeScanByIndex(index) → DELETE /auto-provision/scans/{id} |
Caché local:
_savedScansCache: almacena el resultado de GET en memoria, usado para operaciones “Load by row” y “Delete by row” que usan índice.
Escaneo interrumpido (transitorio):
- Sigue en
localStorage["auto_provision_active_scan"](por-pestaña, no sincronizado org-wide). - Se limpia cuando se promociona a búsqueda completada o se recarga la pestaña.
Auditoría
- Evento:
log_action(request, "NETWORK", "auto_provision.scan_saved", name) - Disparado: Cada vez que se crea una nueva búsqueda.
- Campo registrado: nombre de la búsqueda.
Comportamiento Específico
Compartición a Nivel Organización
- Una búsqueda guardada es visible para todos los miembros de la org que tengan permiso
network.view. - No hay filtro por
created_by: un usuario puede ver y cargar búsquedas de otros colegas. created_byse guarda solo para auditoría.
Límite de 20 por Organización
- Al crear una nueva búsqueda, se cuenta cuántas hay.
- Si ya hay 20, se podan las más antiguas (hasta quedarse con 20 recientes).
- Responsable: lógica en el handler POST (no es automático a nivel DBMS).
Integridad de Perfiles
- Si se borra un
DeviceProfile, desaparece solo de la sesión M2M, no borra la sesión. - El
device_countse calcula en vivo desdeprofiles.all(), sin IDs fantasma.
Validación de Perfiles en Cross-Tenant
- Al crear una búsqueda, si incluyes un ID de perfil que pertenece a otra org, se descarta silenciosamente sin error.
- Solo los perfiles de tu org son aceptados.
Tests
Archivo: tests/api/test_network_scan_sessions.py (10 tests)
Cobertura:
✅ Permisos RLS:
- Viewer puede listar, no crear/borrar.
- Operator puede crear/borrar.
✅ Creación:
- Persiste y vincula perfiles correctamente.
- Descarta perfiles de otra org sin error.
- Poda automáticamente a MAX_SESSIONS (20).
✅ Listado:
- Filtra por org (no mezcla orgs).
- Ordena descendente por
created_at(recientes primero).
✅ Borrado:
- Borra propio exitosamente.
- Retorna 404 si intenta borrar de otra org.
Archivos Modificados
| Archivo | Cambio | Descripción |
|---|---|---|
network/models.py | Added | Nuevo modelo ScanSession (36 líneas) |
network/migrations/0048_scansession.py | Added | Migración Django (53 líneas) |
network/api/scan_sessions.py | Added | Router y handlers (103 líneas) |
network/api/__init__.py | Modified | Importa nuevo router |
static/js/network/auto_provision/sessions.js | Modified | Migra localStorage → API (~100 líneas) |
CHANGELOG.md | Modified | Entrada s115 |
RELEASE_NOTES.md | Modified | Nota para usuarios (s115) |
context/agents/dev-auto-provision.md | Modified | Sección “Persistencia de sesiones” actualizada |
tests/api/test_network_scan_sessions.py | Added | 10 tests unitarios (103 líneas) |
Total LOC: ~250+ (código + tests + migraciones)
Impacto en UX
- Modal “Saved Scans”: Idéntico en apariencia. Los datos vienen de BD en lugar de
localStorage. - Step 2 (“Load Previous Results”): Funciona igual, carga búsquedas de la org.
- Velocidad: Leve latencia de red (GET al entrar en Step 2), pero es previsible.
- Compartición: Nuevo comportamiento: un usuario ve búsquedas de otros colegas → mejora la colaboración.
Backward Compatibility
- ✅ Las búsquedas viejas en
localStorageno se migran automáticamente. Se pierden en el primer uso. - ✅ Es aceptable: la caché de navegador suele limpiarse regularmente, así que de todas formas se hubieran perdido.
- ✅ Los usuarios simplemente volverán a guardar búsquedas (una sola vez) en la BD.
Relación con Otras Features
- s114 (2026-06-08): Botón “Refresh” por dispositivo. Juntas, cierran la iniciativa “no desperdiciar búsquedas”.
- Auto-Provision Feature (general): Mejora la experiencia del asistente de descubrimiento automático.
Próximos Pasos (No Incluidos en s115)
- Sincronización real-time de búsquedas guardadas entre pestañas del mismo usuario (WebSocket o Server-Sent Events).
- Historial de escaneos exitosos (Step 4 adicional con resumen de hallazgos).
- Export/import de búsquedas.
Historial de Cambios
- 2026-06-08 14:08 UTC (commit@1cd2537): Merge de
edu/autoprovision-scans-db. Cierra Parte 4 de Auto-Provision.
Véase también
- [[entity—network—model—scan-session]]
- [[entity—network—endpoint—auto-provision-scans]]
- [[concept—saas—multi-tenancy]]
- [[concept—auto-provision—ciclo-escaneo]]
- [[feature—auto-provision—sesion-114-refresh-dispositivo]]