CreaRack-SL

Auto-Plan async — análisis IA de planos en segundo plano (v1.55.0)

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

  1. Digitas un plano (PDF, PNG, JPG) en Auto-Plan
  2. El servidor responde inmediatamente: “Analizando tu plano…”
    • Antes: el servidor estaba ocupado 4 minutos durante la IA
    • Ahora: free para atender otros usuarios
  3. Polling invisible: la pantalla sigue mostrando la animación de carga, consultando cada 2.5 seg
  4. Redirección al mapa cuando el análisis esté listo (o error si algo falló)
  5. 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:

  1. mark_processing() → notifica cliente
  2. Validate imagen (scaling)
  3. Resuelve API key desde settings (NO del payload → seguridad)
  4. Consulta prompt configurado en la org
  5. Análisis IA lento (~4 min)
  6. Crea Blueprint + sus entidades (racks, slots, items)
  7. 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)

  1. Endpoint encola tarea → responde 202 + job_id
  2. AsyncJob tracks estado
  3. Cliente polls GET /api/jobs/{job_id} cada N ms
  4. Task actualiza el AsyncJob (processing → done/error)
  5. Cliente redirige o muestra error

Aplicable a cualquier operación larga.

Aislamiento tenant

  • AsyncJob tiene organization_id y RLS
  • Endpoint de polling filtra por org (get_current_org)
  • Un job de otra org devuelve 404 (no confirma existencia)
  • Task escribe con org_id recibido → 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 — consultar status, created_at para dashboards
  • Índices: (organization, -created_at), (status, -created_at) → queries rápidas

Rollback / Desactivación

Si hay problema crítico antes de despliegue:

  1. Mantén el endpoint POST /api/blueprints/autoplan/import → sigue respondiendo 202
  2. Deshabilita el encolado (run_autoplan_import) si es necesario
  3. 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]]