fal.ai VisualGen SaaS · Guía de Integración

Inferencia IA Serverless

Integración completa de fal.ai en Next.js 14 — imágenes con Flux, vídeo con AnimateDiff, transcripción Whisper y música con MusicGen. Sin GPUs propias, pago por inferencia.

SDK @fal-ai/client v1.x
Next.js 14 App Router
FAL_KEY requerida
Webhooks prod-ready
TypeScript + Python
Modelos disponibles para VisualGen
🖼️
Flux Schnell
Generación más rápida. 4 pasos, ~2s. Ideal para borradores y previews en tiempo real.
~2s · Borrador
Flux Pro
Máxima calidad fotorrealista. 28 pasos, guidance 3.5. Producción de assets finales.
Alta Calidad
🎨
Flux Dev + LoRA
Fine-tuning por marca. Carga LoRAs de HuggingFace para estilos personalizados.
Brand Style
🎬
AnimateDiff
Anima creatividades estáticas. 16 frames, 8fps. MP4 en segundos.
Vídeo
🎤
Whisper v3
Transcripción de podcasts y locuciones. Auto-detección de idioma, salida por segmentos.
Audio → Texto
🎵
MusicGen
Música de fondo a medida. Prompt en texto, duración configurable, 120 BPM +.
Música
Comparativa de modelos Flux
Modelo ID Calidad Velocidad Pasos Uso recomendado
fal-ai/flux/schnell Buena ~2s 4 Previews, borradores, batch alto volumen
fal-ai/flux/dev Alta ~8s 28 Assets intermedios, iteración rápida
fal-ai/flux-pro Máxima ~15s 28 Assets finales, publicaciones cliente
fal-ai/flux-lora Alta + Brand ~12s 28 Identidad de marca, estilos personalizados
fal-ai/flux/dev/image-to-image Alta ~10s 28 Transformar imágenes existentes del cliente
Instalación y configuración
bash Instalación
# Instalar SDK JavaScript/TypeScript
npm install @fal-ai/client

# O para Python
pip install fal-client
bash Variables de entorno
# .env.local (Next.js)
FAL_KEY="fal_sk_xxxxxxxxxxxxxxxxxxxxxxxx"

# Verificar en fal.ai/dashboard → API Keys
# NUNCA exponer al cliente (usa route handlers)
Generación de imágenes — Flux
ts app/api/generate-image/route.ts — VisualGen Producción
import * as fal from "@fal-ai/client";
import { NextRequest, NextResponse } from "next/server";

fal.config({ credentials: process.env.FAL_KEY });

export async function POST(req: NextRequest) {
  const { prompt, quality, size } = await req.json();

  // Selección dinámica de modelo según calidad solicitada
  const modelId = quality === "draft"
    ? "fal-ai/flux/schnell"   // Borrador: 4 pasos, ~2s
    : "fal-ai/flux-pro";       // Final: 28 pasos, alta calidad

  const result = await fal.subscribe(modelId, {
    input: {
      prompt: prompt,
      image_size: size ?? "landscape_4_3",
      num_images: 1,
      num_inference_steps: quality === "draft" ? 4 : 28,
      guidance_scale: 3.5,
      safety_tolerance: "2",
    },
    onQueueUpdate: (update) => {
      if (update.status === "IN_PROGRESS") {
        console.log("[fal] progreso:", update.logs?.at(-1)?.message);
      }
    },
  });

  return NextResponse.json({
    url: result.data.images[0].url,
    seed: result.data.seed,
    model: modelId,
  });
}
Pipeline asíncrono con webhooks
ts Pipeline async: submit → webhook → Supabase Pipeline Producción
// 1. SUBMIT — app/api/jobs/submit/route.ts
import { supabase } from "@/lib/supabase";

export async function POST(req: NextRequest) {
  const { prompt, userId } = await req.json();

  // Enviar job y retornar inmediatamente
  const { request_id } = await fal.queue.submit("fal-ai/flux-pro", {
    input: { prompt, num_images: 1 },
    webhookUrl: `${process.env.NEXT_PUBLIC_URL}/api/webhooks/fal`,
  });

  // Guardar en Supabase con estado pendiente
  await supabase.from("generations").insert({
    request_id, userId, prompt, status: "pending"
  });

  return NextResponse.json({ request_id, status: "queued" });
}

// 2. WEBHOOK — app/api/webhooks/fal/route.ts
export async function POST(req: Request) {
  const payload = await req.json();

  if (payload.status === "OK") {
    const imageUrl = payload.payload.images[0].url;
    await supabase.from("generations").update({
      image_url: imageUrl,
      status: "completed"
    }).eq("request_id", payload.request_id);

    // Notificar al usuario vía Supabase Realtime
  }

  return Response.json({ ok: true });
}
Audio: transcripción Whisper y música MusicGen
ts Whisper — Transcripción
import * as fal from "@fal-ai/client";
import * as fs from "fs";

// 1. Subir archivo de audio
const audioBuffer = fs.readFileSync("podcast.mp3");
const audioUrl = await fal.storage.upload(
  audioBuffer, "podcast.mp3"
);

// 2. Transcribir con Whisper v3
const result = await fal.subscribe("fal-ai/whisper", {
  input: {
    audio_url: audioUrl,
    task: "transcribe",
    language: "es",
    chunk_level: "segment",
    version: "3",
  }
});

// Resultado: texto + timestamps por segmento
console.log(result.data.text);
py MusicGen — Música de fondo
import fal_client

# Generar música corporativa para vídeo
result = fal_client.subscribe(
    "fal-ai/musicgen",
    arguments={
        "prompt": (
            "Upbeat corporate background music, "
            "professional, 120 BPM, no vocals, "
            "suitable for product demo video"
        ),
        "duration": 30,   # segundos
        "top_k": 250,
        "temperature": 1.0,
    }
)

# URL del archivo .wav generado
print(result["audio"]["url"])
Procesamiento en lote — Campañas VisualGen
ts Generación paralela de múltiples creatividades Alto Volumen
// Generar todas las variantes de una campaña en paralelo
async function generateCampaignAssets(prompts: string[]): Promise<string[]> {
  // Enviar todos los jobs simultáneamente (sin esperar a que terminen)
  const jobs = await Promise.all(
    prompts.map(prompt =>
      fal.queue.submit("fal-ai/flux/schnell", {
        input: { prompt, num_images: 1, num_inference_steps: 4 }
      })
    )
  );

  // Recoger todos los resultados (se procesan en paralelo en fal.ai)
  const results = await Promise.all(
    jobs.map(({ request_id }) =>
      fal.queue.result("fal-ai/flux/schnell", { requestId: request_id })
    )
  );

  return results.map(r => r.data.images[0].url);
}

// Uso: generar 6 variantes de campaña de verano
const urls = await generateCampaignAssets([
  "Summer sale banner, beach lifestyle, vibrant colors, 16:9",
  "Summer sale banner, outdoor sports, energetic, 16:9",
  "Summer sale product shot, minimal white background, 1:1",
  "Summer sale Instagram story, vertical, gradient sunset",
  "Summer sale email header, tropical vibes, 600px wide",
  "Summer sale Facebook ad, family fun, warm tones",
]); // ~2s total para 6 imágenes en paralelo
Arquitectura de producción — VisualGen
Flujo async: Frontend → API → fal.ai → Webhook → Supabase Realtime
Next.js
Frontend
+ App Router
Route Handler
queue.submit()
server-side
fal.ai GPU
Flux Pro / Whisper
AnimateDiff
Webhook
/api/webhooks/fal
resultado listo
Supabase
Storage + DB
Realtime notify
Estimación de costes para VisualGen
Imagen
Flux Schnell
$0.003
por imagen
1.000 borradores/mes
= $3/mes
Imagen
Flux Pro
$0.055
por imagen
500 imágenes finales/mes
= $27.5/mes
Vídeo
AnimateDiff
$0.05
por 16 frames
200 animaciones/mes
= $10/mes
Audio
Whisper v3
$0.008
por minuto audio
100 podcasts × 30min
= $24/mes
Checklist de implementación
SDK instalado: npm install @fal-ai/client — sin peer-deps conflictivos con Next.js 14
FAL_KEY en servidor: Solo usar en Route Handlers / Server Actions. NUNCA en componentes cliente ni NEXT_PUBLIC_*
subscribe() vs queue.submit(): Subscribe para jobs cortos (<30s), queue+webhook para Flux Pro y vídeo
Subida de archivos: fal.storage.upload() antes de pasar URLs de audio o imágenes de referencia a los modelos
Persistir outputs: Las URLs de fal.run/files/ son temporales — descargar y subir a Supabase Storage inmediatamente
Batch en paralelo: Promise.all con queue.submit para campañas — fal.ai procesa todos simultáneamente sin overhead
onQueueUpdate: Implementar para mostrar progreso en la UI — los logs del modelo llegan en tiempo real