Ürünereklam
TerminadoPegas el enlace de un producto y recibes anuncios en vídeo e imagen terminados, en turco y en inglés, cada uno puntuado por un modelo de predicción antes de gastar dinero. Nació en el BTK Akademi Hackathon de 2026.
- Mi papel
- Diseño y desarrollo, en solitario
- Año
- 2026
- Hecho con
- Next.js · Inngest · Supabase · Gemini · Veo · ElevenLabs · Remotion · PyTorch on Modal · Polar
Desplázate en horizontal para ver el diagrama completo.
El problema
Hacer anuncios en dos idiomas, al volumen que necesitan las tiendas online, suele implicar una agencia con una tarifa mensual. Y aun así nadie sabe qué versión va a funcionar antes de gastar el presupuesto.
Cómo está construido
Una app de Next.js orquesta jobs duraderos de Inngest. Un scraper en Python lee la página del producto, con una ruta de fetch reforzada para tiendas complicadas. Gemini escribe la estrategia, el copy y los fotogramas, Veo o Seedance renderiza el vídeo, ElevenLabs pone la voz y Remotion añade subtítulos sincronizados palabra por palabra, la marca de agua y la llamada a la acción. Un estudio aparte produce anuncios de imagen estática y textos publicitarios. Después, cada variante pasa por un modelo autoalojado que predice cómo reaccionará la gente (TRIBE v2) en GPUs de Modal; el trabajo se lanza y luego se consulta por polling, porque un render dura más que el timeout de una edge function. Supabase guarda el estado detrás de row-level security, con URLs firmadas de corta duración para los archivos multimedia. Los créditos viven en un ledger append-only con cargos y reembolsos atómicos, y el checkout funciona con Polar, con precios en liras y en dólares.
Decisiones importantes
Un paso que falla se reembolsa solo
El pipeline está formado por jobs duraderos con un handler de fallo para cada paso. Un paso que muere devuelve sus créditos exactamente una vez y libera la ejecución en lugar de dejarla atascada.
Los archivos multimedia son privados por defecto
Los renders en bruto viven en un bucket privado, el SELECT a nivel de columna está revocado y los enlaces a los archivos multimedia son URLs firmadas de corta duración. No se puede adivinar ninguna dirección de un archivo multimedia, plan gratuito incluido.
Cada anuncio se puntúa antes de que nadie pague por él
Ningún anuncio sale del estudio sin puntuación. El modelo ordena las variantes antes de que nadie invierta presupuesto en ninguna.
Código del repositorio
async function safeRefund(
opts: Parameters<typeof refundCredits>[0],
): Promise<void> {
try {
await refundCredits(opts);
} catch (err) {
console.error(
`[billing][RECONCILE-NEEDED] refund failed user=${opts.userId} ` +1
`amount=${opts.amount} ref=${opts.refType}:${opts.refId} ` +
`key=${opts.idempotencyKey ?? "-"}`,
err,
);
}
}
// …
await safeRefund({
supa,
userId: campaign.user_id,
amount: campaignCreditCost({
videoCount: brief.videoCount ?? 1,
videoModel: brief.videoModel ?? "seedance",
durationSec: brief.durationSec,
}),
refType: "campaign",
refId: campaignId,
idempotencyKey: `refund:campaign-all:${campaignId}`,2
description: "Kampanya başlatılamadı — otomatik kredi iadesi",3
});
Línea 8. Un reembolso fallido registra un marcador RECONCILE-NEEDED en lugar de lanzar una excepción, así que ningún crédito se pierde en silencio y el pipeline libera igualmente la ejecución.
Línea 27. Una clave de idempotencia por evento: el handler de fallo puede volver a ejecutarse, pero el ledger registra el reembolso una sola vez.
Línea 28. El motivo del reembolso está en turco porque el producto es bilingüe de principio a fin.
En qué punto está
Pasó de ser una demo de hackathon a un producto bilingüe con puntuación en GPU. Ahora es de código cerrado y se está preparando el lanzamiento de pago.