Resumen ejecutivo
Versión: v1.55.0
Fecha: 2026-07-16
Impacto usuario: la digitalización de planos ya no bloquea el servidor; análisis IA (~4 min) corre en background
Migramos Auto-Plan al patrón async job + polling (ADR T2 / R5). Antes, el análisis IA de ~4 minutos bloqueaba completamente un worker ASGI; con múltiples planos simultáneos, el sistema se resentía. Ahora el cliente recibe 202 Accepted + job_id al instante y hace polling periódico, manteniendo visible la barra de carga existente. Para el usuario final, la experiencia es igual que antes, pero más robusta.
Lo que cambia para el usuario
- Digitas un plano (PDF, PNG, JPG) en Auto-Plan
- El servidor responde inmediatamente: “Analizando tu plano…”
- Antes: el servidor estaba ocupado 4 minutos durante la IA
- Ahora: free para atender otros usuarios
- Polling invisible: la pantalla sigue mostrando la animación de carga, consultando cada 2.5 seg
- Redirección al mapa cuando el análisis esté listo (o error si algo falló)
- Timeout de seguridad: si tarda más de 6 minutos, alerta (en vez de “colgarse”)
Lo que cambia en el backend
1. Endpoint /api/blueprints/autoplan/import (refactor)
Antes:
POST /api/blueprints/autoplan/import
Content-Type: multipart/form-data
file=<imagen>
name=Mi Plano
→ 201 Created
{
"id": 12,
"racks_count": 8
}
El análisis IA corría síncrono en el request. Bloqueaba el worker 4 minutos.
Ahora:
POST /api/blueprints/autoplan/import
→ 202 Accepted
{
"job_id": "550e8400-e29b-41d4-a716-446655440000",
"status": "pending"
}
El endpoint valida la imagen (guard de bomba + que sea real) y encola la tarea. Responde al instante.
2. Nuevo modelo AsyncJob (core/models_async_job.py)
Tabla única para todas las operaciones largas:
- Campos:
job_id,organization,kind,status,progress,result,error,created_by, timestamps - RLS: aislamiento por tenant (policy 0029)
- Métodos:
mark_processing(),mark_done(result),mark_error(message)
Reusable por backup/restore full, análisis IA de config, etc. (próxima fase ADR T2).
3. Nuevo endpoint GET /api/jobs/{job_id} (core/api/jobs.py)
Cliente hace polling a este endpoint:
{
"job_id": "550e8400-...",
"kind": "autoplan",
"status": "processing",
"progress": 45,
"result": null,
"error": ""
}
Respuesta tras ~4 minutos de IA:
{
"status": "done",
"progress": 100,
"result": {"blueprint_id": 12, "racks_count": 8},
"error": ""
}
Aislamiento tenant: un job de otra org devuelve 404.
4. Nueva task Huey run_autoplan_import (blueprints/tasks.py)
Encapsulación de la lógica que antes era síncrona:
mark_processing()→ notifica cliente- Validate imagen (scaling)
- Resuelve API key desde
settings(NO del payload → seguridad) - Consulta prompt configurado en la org
- Análisis IA lento (~4 min)
- Crea
Blueprint+ sus entidades (racks, slots, items) mark_done(result)→ cliente ve el resultado y redirige
Seguridad clave: credenciales se obtienen dentro de la task desde settings, nunca viajan en el payload de Redis.
5. Frontend: polling en JS (MapInteraction.js)
La animación de carga existente sigue funcionando, pero ahora hace polling real:
const poll = async () => {
const job = await fetch(`/api/jobs/${jobId}`);
const { status, result, error } = await job.json();
if (status === 'done') {
// Redirige al blueprint
window.location.href = `/blueprints/${result.blueprint_id}`;
} else if (status === 'error') {
// Muestra error
alert(`Failed: ${error}`);
} else {
// Sigue esperando
setTimeout(poll, 2500);
}
};
setTimeout(poll, 2500); // Espera inicial
Arquitectura y patrones
ADR T2 / Regla R5
“Lo que tarda >~1 segundo debe ir a Huey + polling, nunca síncrono.”
- ✅ Auto-Plan: ~4 min de IA
- 🔄 Análisis IA de config: pendiente
- 🔄 Backup/Restore full: pendiente
Patrón job + polling (reusable)
- Endpoint encola tarea → responde
202 + job_id - AsyncJob tracks estado
- Cliente polls
GET /api/jobs/{job_id}cada N ms - Task actualiza el AsyncJob (processing → done/error)
- Cliente redirige o muestra error
Aplicable a cualquier operación larga.
Aislamiento tenant
- AsyncJob tiene
organization_idy RLS - Endpoint de polling filtra por org (
get_current_org) - Un job de otra org devuelve
404(no confirma existencia) - Task escribe con
org_idrecibido → sin cruces de tenant
Testing
Archivo: tests/api/test_async_jobs.py (6 tests)
TestAsyncJobPolling:
- test_get_job_returns_status: Polling devuelve estado correcto
- test_done_job_exposes_result: Result visible en estado done
- test_error_job_exposes_message: Mensaje de error visible
- test_job_of_other_org_is_404: Aislamiento tenant (404 para job ajeno)
- test_unknown_job_is_404: Job inexistente → 404
TestAutoPlanEnqueues:
- test_import_enqueues_and_returns_202: POST responde 202 + job_id,
task corre (immediate mode en tests)
Cambios en CHANGELOG / RELEASE_NOTES
## [1.55.0] - 2026-07-16
### Added
- Patrón de jobs asíncronos reusable (AsyncJob + polling)
- Auto-Plan asíncrono: análisis IA en background, no bloquea worker
- Endpoint GET /api/jobs/{job_id} con aislamiento tenant
### Security
- API keys NO se serializan en payloads Redis (resueltas en task desde settings)
- AsyncJob nace con RLS (tenant_isolation)
### Notes
- Próximas migraciones ADR T2: análisis IA de config, backup/restore
Integración con monitoring
- Logs:
[AutoPlan] Enqueued,[AutoPlan] Async import OK,[AutoPlan] Async import error - Tabla:
core_asyncjob— consultarstatus,created_atpara dashboards - Índices:
(organization, -created_at),(status, -created_at)→ queries rápidas
Rollback / Desactivación
Si hay problema crítico antes de despliegue:
- Mantén el endpoint POST
/api/blueprints/autoplan/import→ sigue respondiendo 202 - Deshabilita el encolado (
run_autoplan_import) si es necesario - Los jobs pendientes se quedan en estado
pending(el cliente eventualmente timeout a 6 min)
No hay cambio en esquema revertible (migrations 0028/0029 son forward-only). Tolerable en early access.
Métricas de éxito
- ✅ Workers ASGI no pasan >1s bloqueados por IA
- ✅ Múltiples planos simultáneos sin saturación visible
- ✅ Tasa de error de Auto-Plan: igual o menor que antes
- ✅ Latencia de POST
/api/blueprints/autoplan/import: <200 ms (vs ~4 min antes) - ✅ Polling latency: <50 ms (trivial)
Véase también
- [[concept—infra—async-job-pattern]]
- [[entity—core—model—asyncjob]]
- [[entity—core—endpoint—jobs-polling]]
- [[entity—blueprints—service—run-autoplan-import]]
- [[entity—blueprints—model—blueprint]]
Referenciado desde
- ¿Qué operaciones síncronas se ejecutan en el request-response y deberían pasar a tareas Huey en background? (Auto-Plan, generación IA, upload_mib, trigger_backup) — ADR T2 de la Auditoría Suprema
- blueprints.services.uploads — validación compartida de subida de imágenes
- Endpoint GET /api/jobs/{job_id} — consulta estado de operaciones async
- Modelo AsyncJob — estado de operaciones largas en background
- Patrón de trabajos asíncronos: job + polling
- Provisioning de MonitoringTargets sale del GET — reconciliación en Huey (sa6-G1, v1.89.0)
- Task Huey run_autoplan_import — análisis IA asíncrono de planos