En vez de esperar el render dentro de la request (imposible con la cola nocturna de H3),
ahora el ciclo es async y la cola/worker viven server-side en h3api:
- submit no bloqueante: POST /v1/jobs → se persiste una variación `queued` con h3_job_id y se
responde al instante. video-service expone submitVideoJob() + getJobStatus() (sin polling).
- nueva acción `sync-jobs` en el route de vídeos: avanza los jobs pendientes del capítulo
(queued→processing→ready/error); en `done` descarga el mp4, lo guarda y auto-selecciona la
primera variación lista. Reintenta en el siguiente tick ante fallos de red (no marca error).
- SQLite: columnas `status`/`h3_job_id`/`error` en video_variations + migración idempotente
(filas existentes → 'ready'). video_url pasa a DEFAULT '' (queued aún no tiene vídeo).
- UI: la tarjeta de variación muestra spinner 'En cola'/'Generando…' y estado 'error'; el
player solo con variación 'ready'. La página hace polling cada 45s solo si hay pendientes.
- resolución por plano (16:9 832×480 / 9:16 480×832 vía body.aspect). cost_usd = 0.
NO mergear a master hasta que essia-server dé "live" + H3API_KEY.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sustituye BytePlus Ark/Seedance por el contrato async de essia-server (h3api.essia.coop):
POST /v1/jobs (multipart, mode=ref2va) -> poll GET /v1/jobs/{id} -> download /video (X-API-Key).
Firma de generateVideo/downloadVideo sin cambios (el route no se toca). Env: H3API_BASE_URL, H3API_KEY.
Coste 0 (self-hosted). Sin key no rompe el arranque (lazy).
Limitación anotada: jobs pedidos fuera de la ventana nocturna pueden tardar horas; el polling
síncrono solo es viable en ventana. Cola SQLite + worker pendiente de decidir UX. NO mergear a
master hasta que essia-server confirme "live" + provea H3API_KEY.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>