> ## Documentation Index
> Fetch the complete documentation index at: https://docs.apimart.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Buenas prácticas

> Modo de sondeo, diseño de prompts, generación guiada por imagen, estrategia de reintentos ante errores, concurrencia y resolución de problemas para integrar Midjourney

Reúne las buenas prácticas sobre preguntas frecuentes, optimización del rendimiento y manejo de errores. **Se recomienda leerlo por completo antes de integrar.**

## Envío de tareas y sondeo

Todos los endpoints de envío son tareas asíncronas: tras el envío devuelven un `task_id`, luego se consulta periódicamente `GET /v1/midjourney/{task_id}` para obtener el estado, hasta `SUCCESS` / `FAILURE`.

```python theme={null}
import time, httpx

def wait_task(task_id, timeout=300):
    deadline = time.time() + timeout
    while time.time() < deadline:
        resp = httpx.get(f"{HOST}/v1/midjourney/{task_id}",
                         headers={"Authorization": f"Bearer {API_KEY}"}).json()
        if resp["status"] in ("SUCCESS", "FAILURE"):
            return resp
        if resp["status"] == "MODAL":
            raise RuntimeError(f"task {task_id} necesita llamar a /modal para completar parámetros")
        time.sleep(3)
    raise TimeoutError(task_id)
```

* **Ritmo de sondeo**: se recomienda una vez cada 3–5 s; una frecuencia mayor no aporta nada y desperdicia cuota.
* **No bloquee de forma síncrona dentro de una petición web** esperando a que la tarea termine: tras el envío devuelva de inmediato el `task_id` y deje que el frontend sondee de forma asíncrona.

## Diseño de prompts

**Un buen prompt:**

```text theme={null}
a serene mountain lake at sunrise, photorealistic, soft golden light,
mist rising from water, snow-capped peaks in distance --ar 16:9 --v 8.1 --s 100
```

* **El sujeto primero**: primero el sujeto, luego la descripción de la escena y al final los modificadores.
* **Parámetros estructurados explícitos**: usar `--ar` / `--v` / `--s` (o los campos del body correspondientes) es más controlable que depender de los valores por defecto.
* **Evite palabras ambiguas**: `photorealistic` es más preciso que `realistic`.

**Evite:** ser demasiado abstracto ("make it good"), sujetos dispersos (varios objetos en paralelo sin jerarquía) y poner comillas a las palabras (se interpretan como valores literales).

**Anime Niji:** pase `niji: true` + `version: "7"`; la plataforma lo normaliza a `--niji 7` y la facturación va por `midjourney@imagine-niji7`.

## Buenas prácticas de generación guiada por imagen

| Origen                      | Práctica recomendada                                  | Nota                                                                     |
| --------------------------- | ----------------------------------------------------- | ------------------------------------------------------------------------ |
| Subida de usuario           | Guarde primero en su propio OSS / CDN y envíe esa URL | No envíe base64 directamente (desperdicia ancho de banda)                |
| URL pública                 | Envíela directamente                                  | Atención al SSRF (debe ser accesible públicamente) y al límite de 12 MiB |
| Terceros / otros artefactos | Guárdelo primero en su propio OSS                     | Las URLs de terceros pueden caducar                                      |

* **Comprima a \< 5 MiB**: el límite de la plataforma es 12 MiB, pero las imágenes pequeñas se transmiten y procesan más rápido.
* Se aceptan los formatos PNG / JPG / WebP; se recomienda JPG de alta calidad.
* Una resolución de 1024–2048 px es suficiente; más es desperdicio.
* Peso de imagen `iw` (0–3, por defecto 1): >1 se ajusta más a la imagen original, \<1 da más libertad.

## Manejo de errores y estrategia de reintentos

| code                  | Significado                                     | Estrategia de reintento                                                                         |
| --------------------- | ----------------------------------------------- | ----------------------------------------------------------------------------------------------- |
| `1` / `200`           | Éxito                                           | ✅                                                                                               |
| `4` VALIDATION\_ERROR | Parámetro incorrecto                            | ❌ No reintentar, corrija el parámetro                                                           |
| `3` NOT\_FOUND        | Sin instancia disponible / task\_id inexistente | Si la instancia no está disponible, reintente más tarde; si el task\_id no existe, no reintente |
| `9` FAILURE           | Rechazo del servicio / error interno            | ⏳ Reintentable, con retroceso exponencial (1s, 4s, 16s)                                         |
| `21` MODAL            | Estado no terminal                              | ✅ Continúe llamando a `/modal`                                                                  |
| `24` BANNED\_PROMPT   | Palabra sensible                                | ❌ No reintentar, cambie el prompt; **el reembolso es automático**                               |
| `429`                 | Límite de tasa                                  | ⏳ Retroceso exponencial + jitter                                                                |
| `5xx` / error de red  | Servidor / red                                  | ⏳ Retroceso exponencial; ante error de red puede reintentar 1 vez de inmediato                  |

```python theme={null}
import time, random, httpx

def submit_with_retry(payload, max_attempts=5):
    for attempt in range(max_attempts):
        try:
            r = httpx.post(f"{HOST}/v1/midjourney/generations/imagine",
                           json=payload,
                           headers={"Authorization": f"Bearer {API_KEY}"},
                           timeout=30)
            data = r.json()
            if r.status_code == 200 and data["code"] in (1, 200):
                return data
            if data["code"] in (4, 24):
                raise ValueError(data["description"])      # no reintentable
            if data["code"] == 3 and "task" in data["description"]:
                raise ValueError(data["description"])      # task_id inexistente
            # el resto (9 / 429 / 5xx) es reintentable
        except httpx.RequestError:
            pass
        time.sleep((4 ** attempt) + random.uniform(0, 1))  # 1s / 4s / 16s ...
    raise RuntimeError(f"se alcanzó el número máximo de reintentos {max_attempts}")
```

## Flujo de operaciones secundarias

```python theme={null}
# imagine → sondeo → upscale
imagine_id = submit({"prompt": "a cat"})["data"][0]["task_id"]
result = wait_task(imagine_id)           # grid_image_url + 4 image_urls + buttons
upscale_id = submit_to("/upscale", {"task_id": imagine_id, "index": 2})["data"][0]["task_id"]
final = wait_task(upscale_id)            # upscale se compone localmente, 1–2s
single_image = final["image_urls"][0]
```

Redibujado local (inpaint → modal en dos pasos):

```python theme={null}
imagine_id = submit({"prompt": "a portrait"})["data"][0]["task_id"]; wait_task(imagine_id)
upscale_id = submit_to("/upscale", {"task_id": imagine_id, "index": 1})["data"][0]["task_id"]; wait_task(upscale_id)

inpaint_id = submit_to("/inpaint", {"task_id": upscale_id})["data"][0]["task_id"]  # status=modal
# el frontend dibuja la mask (transparente=zona a redibujar), la sube a su propio OSS y obtiene mask_url
final = submit_to("/modal", {
    "task_id": inpaint_id,
    "prompt": "replace the eyes with cybernetic blue eyes",
    "mask_url": "https://your-oss.com/mask.png"
})
wait_task(final["data"][0]["task_id"])
```

> ⚠️ Tras entrar en MODAL, inpaint debe llamar a `/modal` **en un plazo de 30 minutos**, de lo contrario el backend hace CANCEL automático + reembolso.

## Control de facturación de video

* Un solo segmento: `batch_size: 1` → descuenta 1 × `midjourney@video`
* Lote de 4 segmentos: `batch_size: 4` → descuenta 4 × `midjourney@video`
* Un segmento en alta definición: `video_type: "vid_1.1_i2v_720"` + `batch_size: 1` → descuenta 1 × `midjourney@video-720p`

**Recomendación**: si solo necesita 1 segmento para el resultado final, use `batch_size=1`; reserve 4 únicamente para comparar borradores en lote, no lo deje en 4 por defecto (multiplica el coste por N).

## Concurrencia y rendimiento

```python theme={null}
import asyncio
sem = asyncio.Semaphore(10)  # como máximo 10 envíos concurrentes en el cliente

async def submit_one(prompt):
    async with sem:
        return await submit({"prompt": prompt})
```

* La plataforma impone un límite de envíos por minuto; al superarlo devuelve `429` y hay que reintentar con retroceso.
* La concurrencia real de generación la determina la capacidad del sistema; lo que la supere se pone en cola. Si una tarea permanece mucho tiempo en `SUBMITTED`, normalmente está en cola.
* El sondeo debe incluir siempre un `sleep`; no haga un bucle infinito sin sleep.

## Recomendaciones de monitorización

| Métrica                             | Umbral de referencia | Significado                                                              |
| ----------------------------------- | -------------------- | ------------------------------------------------------------------------ |
| Tasa de SUCCESS (última 1h)         | > 95%                | Si es baja indica anomalía del servicio / red                            |
| Tiempo medio de finalización        | \< 90s               | Si es alto indica cola                                                   |
| Número de tareas detenidas en MODAL | Cercano a 0          | Si es alto indica que el cliente no llamó a `/modal`                     |
| Proporción de `code=24`             | \< 5%                | Si es alta indica que el prompt activa palabras sensibles con frecuencia |

## Lista de resolución de problemas

| Síntoma                               | Dirección de diagnóstico                                                                          |
| ------------------------------------- | ------------------------------------------------------------------------------------------------- |
| Tarea mucho tiempo en `SUBMITTED`     | En cola en el sistema, consulte más tarde                                                         |
| Tarea mucho tiempo en `NOT_START`     | La plataforma la agotará y reembolsará automáticamente más tarde; no requiere intervención manual |
| Tarea `MODAL` más de 30 minutos       | El cliente no llamó a `/modal`; ya fue CANCELada automáticamente + reembolsada                    |
| Campo `prompt` vacío                  | El resultado de texto de una tarea describe está en el campo `description`                        |
| Falta una imagen en `image_urls`      | La revisión del sistema bloqueó parte de las imágenes; revise `fail_reason`                       |
| Facturación por encima de lo esperado | Revise el campo `quota`; en video recuerde × `batch_size`                                         |
