Resumen
La restauración de backups en CreaRack es aditiva por diseño: no reemplaza datos existentes, sino que los suma a los ya presentes en la organización. El 16-07-2026, esta característica causó un incidente: una restauración duplicó 100 racks de una organización real sin aviso alguno, porque la UI mentía (“overwrite” + “safety backup” inexistente).
v1.66.2 (06-08-2026) añade:
- Validación de 409 con confirmación dual: si la org ya tiene datos vivos, el servidor rechaza el restore sin
confirm_add=true. - UI honesta: el diálogo ahora dice “AÑADE” (no “reemplaza”), muestra los números reales de racks/planos existentes, y pide segundo sí explícito.
- Preservación de metadatos: los campos
modeysort_orderde blueprints sobreviven al round-trip backup→restore (antes renacían con valores por defecto). - Limpieza PROD: los 100 racks + 4 planos duplicados fueron movidos a la Papelera (reversibles).
Arquitectura de la Fix
Endpoint: POST /api/racks/restore/full
Cambio de firma:
def restore_full(request, file: UploadedFile = File(...), confirm_add: bool = Form(False)):
Validación previa (antes de encolar):
existing_racks = Rack.objects.filter(organization=org, deleted_at__isnull=True).count()
existing_bps = Blueprint.objects.filter(organization=org, deleted_at__isnull=True).count()
if (existing_racks or existing_bps) and not confirm_add:
return JsonResponse({
"error": "confirmation_required",
"message": "This organization already has N racks and M maps...",
"existing_racks": existing_racks,
"existing_blueprints": existing_bps,
}, status=409)
Comportamiento:
- Org vacía (p.ej. caso DR real): POST sin
confirm_add→ encolación inmediata, 202. - Org con datos: POST sin
confirm_add→ 409 con recuentos. - Org con datos +
confirm_add=true: POST → encolación, 202.
Serialización de Blueprints (Backup)
En racks/api/export/backup.py, función _collect_backup_data():
blueprint_data = {
# ... campos previos ...
"mode": bp.mode, # NEW: type='map'|'row'|'room'
"sort_order": bp.sort_order, # NEW: orden manual
# ... resto ...
}
Desserialización de Blueprints (Restore)
En racks/api/export/restore.py, función apply_full_restore_from_zip():
blueprint = Blueprint.objects.create(
organization=org,
name=bp["name"],
mode=bp.get("mode", "map"), # Default "map" para compat ZIPs antiguos
sort_order=bp.get("sort_order"), # None si no está (compat)
# ... resto de campos ...
)
Esto asegura que:
- ZIPs nuevos preservan exactamente
modeysort_order. - ZIPs antiguos (sin esos campos) no rompen; usan defaults.
Lógica Frontend (static/js/base.js)
// Paso 1: primer confirm con texto honesto
if (!confirm(t('FULL SYSTEM RESTORE\n\nThis ADDS the backup contents...'))) {
return;
}
// Paso 2: POST sin confirm_add
try {
started = await ApiService.postForm('/api/racks/restore/full', formData);
} catch (err) {
if (err?.status === 409 && err?.error === 'confirmation_required') {
// Paso 3: 409 → segundo confirm con números reales
if (!confirm(`${err.message}\n\n${t('Are you absolutely sure...')}`)) {
return;
}
// Paso 4: reintento con confirm_add=true
formData.append('confirm_add', 'true');
started = await ApiService.postForm('/api/racks/restore/full', formData);
} else {
throw err;
}
}
Flujo usuario:
- Selecciona ZIP → primer diálogo (texto honesto).
- Si org tiene datos → error 409 mostrando los números.
- Usuario ve “N racks y M planos existentes” → segundo sí explícito.
- Sistema encola la restauración.
Mensajes Localizados
Se añadieron dos msgids nuevos a locale/es/LC_MESSAGES/djangojs.po (compilado en Docker):
msgid ""
"FULL SYSTEM RESTORE\n"
"\n"
"This ADDS the backup contents alongside your current data. Nothing is "
"deleted, and items present in both will appear DUPLICATED.\n"
"\n"
"Proceed?"
msgstr ""
"RESTAURACIÓN COMPLETA DEL SISTEMA\n"
"\n"
"Esto AÑADE el contenido del backup junto a tus datos actuales. No se borra "
"nada, y los elementos presentes en ambos aparecerán DUPLICADOS.\n"
"\n"
"¿Continuar?"
msgid "Are you absolutely sure you want to add it on top?"
msgstr "¿Seguro del todo que quieres añadirlo encima?"
Cobertura de Tests
Se añadieron 4 casos clave en tests/api/test_backup_restore_async.py:
1. test_restore_over_populated_org_requires_confirmation
- Org con 1 rack existente.
- POST sin
confirm_add→ 409 + recuentos. - Validación:
existing_racks == 1, nada se encola.
2. test_restore_enqueues_and_applies_with_confirmation
- Org con 1 rack existente.
- POST con
confirm_add=true→ 202 + encolación. - Tras aplicar: org tiene 2 racks (aditivo).
3. test_restore_into_empty_org_needs_no_confirmation
- Org vacía (sin racks).
- POST sin
confirm_add→ 202 directamente (sin fricción DR).
4. test_blueprint_mode_and_sort_order_survive_roundtrip
- Blueprint creado con
mode='row',sort_order=7. - Backup → ZIP.
- Restore desde ZIP.
- Validación: blueprint restaurado tiene
mode='row'ysort_order=7(antes era ‘map’ + None).
Cambios en Versionado
- CHANGELOG.md: Entrada v1.66.2 con descripción de fixes y tests.
- README.md: versión bumped 1.66.1 → 1.66.2.
- RELEASE_NOTES.md: entrada narrativa para usuarios (116 palabras, tono usuario final).
- config/settings/base.py:
APP_VERSION = "1.66.2".
Impacto de Negocio
Antes (hasta 16-07-2026)
- Riesgo: Restauración aditiva sin confirmación → duplicación silenciosa de datos masivos.
- UX: Diálogos engañosos (“overwrite”, “safety backup” inexistente).
- Confianza: Backup/restore como función de riesgo.
Después (v1.66.2)
- Seguridad: Validación 409 + confirmación dual antes de encolación.
- Transparencia: UI honesta (números reales, lenguaje “añade” vs. “reemplaza”).
- DR sin fricción: Org vacía restaura sin confirmación extra.
- Limpieza reversible: 100 racks + 4 planos duplicados movidos a Papelera.
Metadatos de Campos (Round-trip)
| Campo | Tipo | Serialización | Desserialización | Default (compat) |
|---|---|---|---|---|
Blueprint.mode | str enum | ✅ Backup incluye | ✅ Restore aplica | “map” |
Blueprint.sort_order | int | ✅ Backup incluye | ✅ Restore aplica | None |
Véase también
- [[entity—racks—endpoint—backup-full]]
- [[entity—racks—endpoint—backup-full-download]]
- [[entity—racks—endpoint—backup-full-start]]
- [[entity—racks—model—rack]]
- [[entity—blueprints—model—blueprint]]
- [[entity—blueprints—model—blueprint-sort-order]]
Referenciado desde
- El backup adelgaza: los vídeos de Signage son opcionales (v1.72.0)
- El backup cubre ahora cables, salas DCIM, ITSM y Signage (v1.70.0)
- El restore aprende a actualizar: machaca lo que coincide, nunca borra (v1.75.0)
- Manifest y verificación de integridad al crear un backup (v1.69.0 · task #236 PR-1)
- Restore selectivo por rack + informe de verificación del restore (v1.71.0)
- Servicio / Tarea Huey `apply_full_restore_from_zip()` — Restaurar desde ZIP (v1.56.0)