diag: revelar o motivo de a customização por posicionamento falhar

As 3 imagens sobem, mas a Meta recusa montar o criativo com elas e o app
cai pra 1 imagem silenciosamente. Agora o erro real da Meta é logado
([Placement]) e devolvido na resposta (placement_error), aparecendo na
tela final — pra diagnosticar e corrigir a estrutura do asset_feed_spec.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Marcio Bevervanso 2026-06-16 14:21:28 -03:00
parent adc6580848
commit 8ebbe079ec
2 changed files with 35 additions and 2 deletions

View file

@ -77,6 +77,9 @@ export default function CreateCampaignPage() {
// Subir a campanha já ativa (no ar) em vez de pausada para revisão. // Subir a campanha já ativa (no ar) em vez de pausada para revisão.
const [startActive, setStartActive] = useState(false); const [startActive, setStartActive] = useState(false);
// Diagnóstico: motivo de a customização por posicionamento ter caído pra 1 imagem.
const [placementNote, setPlacementNote] = useState<string | null>(null);
const [uploadedImageBytes, setUploadedImageBytes] = const [uploadedImageBytes, setUploadedImageBytes] =
useState<string | null>(null); useState<string | null>(null);
@ -608,6 +611,12 @@ export default function CreateCampaignPage() {
approved_at: new Date().toISOString(), approved_at: new Date().toISOString(),
}); });
setPlacementNote(
data.data?.placements_dropped
? data.data?.placement_error || 'A Meta recusou a customização por posicionamento; subiu com 1 imagem.'
: null
);
setStep('done'); setStep('done');
} catch (err) { } catch (err) {
console.error('[Debug] Publish error:', err); console.error('[Debug] Publish error:', err);
@ -1537,6 +1546,15 @@ export default function CreateCampaignPage() {
? 'Tudo enviado para o seu Gerenciador de Anúncios. A campanha subiu ATIVA e entra em análise da Meta agora — começa a entregar assim que for aprovada.' ? 'Tudo enviado para o seu Gerenciador de Anúncios. A campanha subiu ATIVA e entra em análise da Meta agora — começa a entregar assim que for aprovada.'
: 'Tudo enviado para o seu Gerenciador de Anúncios. A campanha subiu PAUSADA — revise no Gerenciador e dê play quando quiser colocar no ar.'} : 'Tudo enviado para o seu Gerenciador de Anúncios. A campanha subiu PAUSADA — revise no Gerenciador e dê play quando quiser colocar no ar.'}
</p> </p>
{placementNote && (
<div className="max-w-md mb-8 -mt-4 text-left rounded-lg border border-amber-500/30 bg-amber-500/10 p-3">
<p className="text-xs text-amber-300/90 leading-relaxed">
<span className="font-semibold">Subiu com 1 imagem</span> (a Meta recusou usar as 3 por posicionamento). Motivo retornado pela Meta:
</p>
<p className="mt-1 text-[11px] text-white/60 break-words font-mono">{placementNote}</p>
</div>
)}
<div className="flex gap-4"> <div className="flex gap-4">
<button <button
onClick={() => { onClick={() => {

View file

@ -713,13 +713,18 @@ export async function addAdToAdSet(client: Client, params: AddAdToAdSetParams) {
} }
}; };
let placementError: string | undefined;
if (wantPlacements) { if (wantPlacements) {
try { try {
// 1) Melhor caso: imagem específica por posicionamento, com IG. // 1) Melhor caso: imagem específica por posicionamento, com IG.
creative = await buildCreative(params.instagram_actor_id, true); creative = await buildCreative(params.instagram_actor_id, true);
} catch { } catch (err) {
// 2) Customização por posicionamento falhou (ex: conflito com a // 2) Customização por posicionamento falhou (ex: conflito com a
// identidade de IG) → cai pra imagem única mantendo o Instagram. // identidade de IG) → cai pra imagem única mantendo o Instagram.
// Guardamos o motivo pra diagnóstico (sai no log e na resposta).
placementError = err instanceof Error ? err.message : String(err);
console.warn('[Placement] Customização por posicionamento falhou, usando imagem única:', placementError);
placementsDropped = true; placementsDropped = true;
creative = await buildSingleWithIgFallback(); creative = await buildSingleWithIgFallback();
} }
@ -751,6 +756,7 @@ export async function addAdToAdSet(client: Client, params: AddAdToAdSetParams) {
ad_id: adId, ad_id: adId,
instagram_dropped: instagramDropped, instagram_dropped: instagramDropped,
placements_dropped: placementsDropped, placements_dropped: placementsDropped,
placement_error: placementError,
}; };
} }
@ -850,7 +856,13 @@ export async function createFullCampaign(client: Client, params: FullCampaignPar
} }
// Steps 3-5: Image Upload + Ad Creative + Ad (pipeline compartilhado) // Steps 3-5: Image Upload + Ad Creative + Ad (pipeline compartilhado)
const { creative_id: creativeId, ad_id: adId } = await addAdToAdSet(client, { const {
creative_id: creativeId,
ad_id: adId,
instagram_dropped: instagramDropped,
placements_dropped: placementsDropped,
placement_error: placementError,
} = await addAdToAdSet(client, {
adset_id: adSetId, adset_id: adSetId,
ad_name: params.campaign_name, ad_name: params.campaign_name,
page_id: params.page_id, page_id: params.page_id,
@ -880,5 +892,8 @@ export async function createFullCampaign(client: Client, params: FullCampaignPar
bid_strategy: params.bid_strategy, bid_strategy: params.bid_strategy,
bid_amount: params.bid_amount, bid_amount: params.bid_amount,
is_whatsapp: params.is_whatsapp || false, is_whatsapp: params.is_whatsapp || false,
instagram_dropped: instagramDropped,
placements_dropped: placementsDropped,
placement_error: placementError,
}; };
} }