fix(pipeline): write-back RRSS al crear creatividades
Al terminar Claude/Freepik, el cover vuelve al pipeline sin esperar solo a Canva; plantillas Canva usan canal rrss. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -34,23 +34,39 @@ export async function POST(req: NextRequest) {
|
||||
return NextResponse.json({ error: 'No creatives provided' }, { status: 400 });
|
||||
}
|
||||
|
||||
// Si viene generation_request_id, averiguar parent_id (source_creative_id del request)
|
||||
// Si viene generation_request_id, averiguar parent_id + brief RRSS
|
||||
let defaultParentId: string | null = null;
|
||||
let rrssBrief: {
|
||||
channel?: string;
|
||||
source_pipeline_id?: string;
|
||||
write_back?: { pipeline_id?: string };
|
||||
format?: string;
|
||||
} | null = null;
|
||||
const genRequestId = body.generation_request_id || items[0]?.generation_request_id;
|
||||
|
||||
if (genRequestId) {
|
||||
const { data: genReq } = await adminClient
|
||||
.from('generation_requests')
|
||||
.select('source_creative_id')
|
||||
.select('source_creative_id, brief')
|
||||
.eq('id', genRequestId)
|
||||
.eq('tenant_id', tenantId)
|
||||
.maybeSingle();
|
||||
|
||||
if (genReq) {
|
||||
defaultParentId = genReq.source_creative_id;
|
||||
rrssBrief =
|
||||
genReq.brief && typeof genReq.brief === 'object'
|
||||
? (genReq.brief as typeof rrssBrief)
|
||||
: null;
|
||||
}
|
||||
}
|
||||
|
||||
const isRrss = rrssBrief?.channel === 'rrss';
|
||||
const pipelineId =
|
||||
rrssBrief?.source_pipeline_id ||
|
||||
rrssBrief?.write_back?.pipeline_id ||
|
||||
null;
|
||||
|
||||
const toInsert = items.map((c: any) => {
|
||||
const copyNorm = normalizeMetaAdCopyInput({
|
||||
primary_text: c.primary_text,
|
||||
@@ -117,7 +133,7 @@ export async function POST(req: NextRequest) {
|
||||
const { resolveTenantCanvaTemplates } = await import('@/lib/canva-templates');
|
||||
const canvaTemplates = resolveTenantCanvaTemplates(tenant?.canva_templates, {
|
||||
slug: tenant?.slug,
|
||||
channel: 'ads',
|
||||
channel: isRrss ? 'rrss' : 'ads',
|
||||
});
|
||||
|
||||
const clientInfo = {
|
||||
@@ -141,6 +157,8 @@ export async function POST(req: NextRequest) {
|
||||
format: c.format,
|
||||
generation_request_id: c.generation_request_id,
|
||||
parent_id: c.parent_id,
|
||||
channel: isRrss ? 'rrss' : 'ads',
|
||||
pipeline_id: pipelineId,
|
||||
},
|
||||
visibility: 'agency',
|
||||
});
|
||||
@@ -153,15 +171,69 @@ export async function POST(req: NextRequest) {
|
||||
meta: c.meta || {},
|
||||
client: clientInfo,
|
||||
},
|
||||
...staticAdWebhookAspectFields(c.format),
|
||||
channel: isRrss ? 'rrss' : 'ads',
|
||||
pipeline_id: pipelineId,
|
||||
generation_request_id: genRequestId || c.generation_request_id || null,
|
||||
canva_templates: canvaTemplates,
|
||||
...(isRrss
|
||||
? {}
|
||||
: staticAdWebhookAspectFields(c.format)),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// RRSS: escribir cover provisional en pipeline ya (Freepik), no esperar solo a Canva
|
||||
if (isRrss && pipelineId && inserted?.length) {
|
||||
const primary = inserted[0];
|
||||
const meta = (primary.meta || {}) as Record<string, unknown>;
|
||||
const assetUrl =
|
||||
(typeof primary.asset_url === 'string' && primary.asset_url) ||
|
||||
(typeof meta.raw_image_url === 'string' && meta.raw_image_url) ||
|
||||
(typeof meta.stories_asset_url === 'string' && meta.stories_asset_url) ||
|
||||
null;
|
||||
if (assetUrl) {
|
||||
const { data: pipeItem } = await adminClient
|
||||
.from('creative_pipeline')
|
||||
.select('id, notes, cover_path, caption')
|
||||
.eq('id', pipelineId)
|
||||
.eq('tenant_id', tenantId)
|
||||
.maybeSingle();
|
||||
if (pipeItem && !pipeItem.cover_path) {
|
||||
const caption =
|
||||
[primary.hook, primary.primary_text, primary.headline]
|
||||
.filter((x) => typeof x === 'string' && String(x).trim())
|
||||
.join('\n\n') || pipeItem.caption;
|
||||
const notes = [
|
||||
pipeItem.notes || '',
|
||||
genRequestId ? `[[generation_request:${genRequestId}]]` : null,
|
||||
'Generado: Claude + Freepik (Canva pendiente)',
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join('\n');
|
||||
await adminClient
|
||||
.from('creative_pipeline')
|
||||
.update({
|
||||
cover_path: assetUrl,
|
||||
cover_mime: String(assetUrl).includes('.png')
|
||||
? 'image/png'
|
||||
: 'image/jpeg',
|
||||
caption,
|
||||
status: 'revision_agencia',
|
||||
notes,
|
||||
updated_at: new Date().toISOString(),
|
||||
})
|
||||
.eq('id', pipelineId)
|
||||
.eq('tenant_id', tenantId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
ok: true,
|
||||
count: inserted?.length || 0,
|
||||
creatives: inserted,
|
||||
pipeline_id: pipelineId,
|
||||
channel: isRrss ? 'rrss' : 'ads',
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -80,6 +80,75 @@ export async function PATCH(
|
||||
visibility: 'agency',
|
||||
});
|
||||
|
||||
// RRSS: si Claude marca completada y el pipeline sigue sin cover, volcar asset del result
|
||||
if (updated.status === 'completada') {
|
||||
const brief = (updated.brief || {}) as {
|
||||
channel?: string;
|
||||
source_pipeline_id?: string;
|
||||
write_back?: { pipeline_id?: string };
|
||||
};
|
||||
const pipelineId =
|
||||
brief.source_pipeline_id || brief.write_back?.pipeline_id || null;
|
||||
if (brief.channel === 'rrss' && pipelineId) {
|
||||
const { data: pipeItem } = await adminClient
|
||||
.from('creative_pipeline')
|
||||
.select('id, cover_path, notes, caption')
|
||||
.eq('id', pipelineId)
|
||||
.eq('tenant_id', tenantId)
|
||||
.maybeSingle();
|
||||
if (pipeItem && !pipeItem.cover_path) {
|
||||
const result = (updated.result || {}) as {
|
||||
creatives?: Array<{
|
||||
asset_url?: string;
|
||||
meta?: { raw_image_url?: string };
|
||||
}>;
|
||||
asset_url?: string;
|
||||
};
|
||||
const fromResult =
|
||||
result.asset_url ||
|
||||
result.creatives?.[0]?.asset_url ||
|
||||
result.creatives?.[0]?.meta?.raw_image_url ||
|
||||
null;
|
||||
let assetUrl = fromResult;
|
||||
if (!assetUrl) {
|
||||
const { data: cr } = await adminClient
|
||||
.from('creatives')
|
||||
.select('asset_url, meta')
|
||||
.eq('generation_request_id', id)
|
||||
.eq('tenant_id', tenantId)
|
||||
.order('created_at', { ascending: true })
|
||||
.limit(1)
|
||||
.maybeSingle();
|
||||
assetUrl =
|
||||
cr?.asset_url ||
|
||||
(cr?.meta as { raw_image_url?: string } | null)?.raw_image_url ||
|
||||
null;
|
||||
}
|
||||
if (assetUrl) {
|
||||
await adminClient
|
||||
.from('creative_pipeline')
|
||||
.update({
|
||||
cover_path: assetUrl,
|
||||
cover_mime: String(assetUrl).includes('.png')
|
||||
? 'image/png'
|
||||
: 'image/jpeg',
|
||||
status: 'revision_agencia',
|
||||
notes: [
|
||||
pipeItem.notes || '',
|
||||
`[[generation_request:${id}]]`,
|
||||
'Generado: Claude + Freepik (write-back al completar)',
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join('\n'),
|
||||
updated_at: new Date().toISOString(),
|
||||
})
|
||||
.eq('id', pipelineId)
|
||||
.eq('tenant_id', tenantId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
...updated,
|
||||
client_id: updated.tenant_id,
|
||||
|
||||
Reference in New Issue
Block a user