Descripción
Durante la ronda funcional de mantenimiento del 13-08-2026, se descubrió que la paleta de stencils del editor de racks mostraba ~148 entradas cuando el catálogo real son 57 stencils.
La causa: la función apply_full_restore_from_zip() en racks/api/export/restore.py creaba stencils sin comprobar si ya existían. Cada restauración repetida de un backup duplicaba el catálogo completo.
Síntomas en PROD
- Pack global de stencils de sistema: ×2 (ids 150–174, calco exacto del seed de migración 0011).
- Stencils de la org principal (org-1): hasta ×4 (98 filas cuando debería haber 32 distintos).
- UX: paleta del editor con 91 entradas sobrantes, haciendo difícil localizar el stencil correcto.
Root cause
Línea 150 en restore.py usaba Stencil.objects.create() incondicionalmente:
Stencil.objects.create(
organization=org,
name=s["name"],
category=s.get("category", ""),
image_path=s.get("image_path", ""),
...
)
Contrasta con las BoxCategory (3 líneas más arriba) que ya usaban get_or_create().
Fix aplicado (v1.66.8)
Cambio a get_or_create() con unique lógico organization + name + image_path:
_, created = Stencil.objects.get_or_create(
organization=org,
name=s["name"],
image_path=s.get("image_path", ""),
defaults={
"category": s.get("category", ""),
"default_u_height": s.get("default_u_height", 1),
"manufacturer": s.get("manufacturer", ""),
"extra_data": s.get("extra_data", ""),
},
)
if created:
counts["stencils"] += 1
Contrato de idempotencia: test nuevo en tests/api/test_backup_restore_async.py::test_stencils_no_se_duplican_al_restaurar_dos_veces verifica que restaurar el mismo backup dos veces genera exactamente 1 Stencil en la BD.
Remediación de datos
- Purga verificada: eliminadas 91 filas sobrantes en PROD en el mismo ciclo.
- Validación pre-purga: audit previo confirmó que 0 registros en
suggested_stencil_idapuntaban a duplicados → sin remapeos necesarios. - Resultado: 58 stencils, 0 grupos duplicados.
Deuda técnica declarada
La hipótesis del duplicado del pack global (ids 150–174, que aparecieron 2 veces) es razonada pero no observada directamente:
- Teoría: la migración 0011 (seed del catálogo global) fue re-ejecutada con aislamiento de filas (
RLSyGUC) pero viendo 0 filas, lo que activó unCREATEincondicionalmente. - Anotada en task #224 por si reaparece en una futura migración con seed.
Impacto usuario
Ninguno visible: mismo comportamiento, pero catálogo limpio. El editor sigue funcionando igual, solo con la paleta correcta (57 stencils en lugar de 148).
Véase también
- [[entity—racks—model—stencil]]
- [[entity—racks—endpoint—list-stencils]]
- [[concept—saas—multi-tenancy]]
- [[feature—racks—backup-restore]]