From ab64ead6c37e98c3305a4cbd2446e91276ed7df1 Mon Sep 17 00:00:00 2001 From: Marcos Fargas Date: Mon, 12 Oct 2026 21:15:43 +0200 Subject: [PATCH] feat(brief): add product and landing_url to brief, map from campaign_products and inherit from source creative --- src/app/api/generation-requests/route.ts | 50 +++++++++++++++++-- .../creatives/[id]/context/route.ts | 6 ++- .../api/integrations/fatigue/mark/route.ts | 26 ++++++++-- 3 files changed, 72 insertions(+), 10 deletions(-) diff --git a/src/app/api/generation-requests/route.ts b/src/app/api/generation-requests/route.ts index e231ea1..0d3d793 100644 --- a/src/app/api/generation-requests/route.ts +++ b/src/app/api/generation-requests/route.ts @@ -81,14 +81,37 @@ export async function POST(req: NextRequest) { const admin = getAdmin(); - // Get tenant slug + // Get tenant slug and maps const { data: tenant } = await admin .from('tenants') - .select('slug, name, campaign_audience') + .select('slug, name, campaign_audience, campaign_products') .eq('id', tenantId) .maybeSingle(); - const campaignName = body.campaign_name || null; + // Si viene source_creative_id, obtener datos de la creatividad fatigada + let creativeCampaignName: string | null = null; + let creativeAdsetId: string | null = null; + let creativeAngle: string | null = null; + + if (body.source_creative_id) { + const { data: srcCreative } = await admin + .from('creatives') + .select('campaign_name, adset_id, angle') + .eq('id', body.source_creative_id) + .eq('tenant_id', tenantId) + .maybeSingle(); + + if (srcCreative) { + creativeCampaignName = srcCreative.campaign_name || null; + creativeAdsetId = srcCreative.adset_id || null; + creativeAngle = srcCreative.angle || null; + } + } + + // Priorizar campaign_name y adset_id de la creatividad de origen si existe + const campaignName = creativeCampaignName || body.campaign_name || null; + const adsetId = creativeAdsetId || body.adset_id || null; + const campaignAudienceMap = (tenant as any)?.campaign_audience || {}; let audience: 'b2b' | 'b2c' | null = body.audience || null; if (!audience && campaignName && campaignAudienceMap && typeof campaignAudienceMap === 'object' && campaignAudienceMap[campaignName]) { @@ -98,6 +121,22 @@ export async function POST(req: NextRequest) { } } + const campaignProductsMap = (tenant as any)?.campaign_products || {}; + let product: string | null = body.product || null; + let landingUrl: string | null = body.landing_url || null; + + if (campaignName && campaignProductsMap && typeof campaignProductsMap === 'object' && campaignProductsMap[campaignName]) { + const pVal = campaignProductsMap[campaignName]; + if (typeof pVal === 'string') { + if (!product) product = pVal; + } else if (typeof pVal === 'object' && pVal !== null) { + if (!product) product = pVal.product ? String(pVal.product) : null; + if (!landingUrl) landingUrl = pVal.landing_url ? String(pVal.landing_url) : null; + } + } + + const avoidAngles = body.avoid_angles || (creativeAngle ? [creativeAngle] : []); + const brief = { tenant_id: tenantId, client_id: tenantId, @@ -107,8 +146,11 @@ export async function POST(req: NextRequest) { format: body.format || 'estatico', variants_requested: Number(body.variants_requested || 3), campaign_name: campaignName, - adset_id: body.adset_id || null, + adset_id: adsetId, audience, + product, + landing_url: landingUrl, + avoid_angles: avoidAngles, notes: body.notes || null, }; diff --git a/src/app/api/integrations/creatives/[id]/context/route.ts b/src/app/api/integrations/creatives/[id]/context/route.ts index 75064f6..d9ca313 100644 --- a/src/app/api/integrations/creatives/[id]/context/route.ts +++ b/src/app/api/integrations/creatives/[id]/context/route.ts @@ -108,10 +108,10 @@ export async function GET( topCreatives = fallbackRows || []; } - // 5. Obtener datos del cliente (drive_folder_id, slug, nombre) + // 5. Obtener datos del cliente (drive_folder_id, slug, nombre, mapas) const { data: tenant } = await adminClient .from('tenants') - .select('id, name, slug, drive_folder_id, meta_ad_account_id') + .select('id, name, slug, drive_folder_id, meta_ad_account_id, campaign_audience, campaign_products') .eq('id', tenantId) .maybeSingle(); @@ -127,6 +127,8 @@ export async function GET( name: tenant?.name || null, slug: tenant?.slug || null, drive_folder_id: tenant?.drive_folder_id || null, + campaign_audience: (tenant as any)?.campaign_audience || {}, + campaign_products: (tenant as any)?.campaign_products || {}, }, }; diff --git a/src/app/api/integrations/fatigue/mark/route.ts b/src/app/api/integrations/fatigue/mark/route.ts index 60e824f..76879d3 100644 --- a/src/app/api/integrations/fatigue/mark/route.ts +++ b/src/app/api/integrations/fatigue/mark/route.ts @@ -30,7 +30,7 @@ export async function POST(req: NextRequest) { // 1. Obtener la creatividad a marcar const { data: creative, error: getErr } = await adminClient .from('creatives') - .select('*, tenant:tenants(slug, name, campaign_audience)') + .select('*, tenant:tenants(slug, name, campaign_audience, campaign_products)') .eq('id', creativeId) .eq('tenant_id', tenantId) .maybeSingle(); @@ -79,7 +79,11 @@ export async function POST(req: NextRequest) { const avoidAngles = creative.angle ? [creative.angle] : []; const clientSlug = (creative.tenant as any)?.slug || 'bps'; const campaignAudienceMap = (creative.tenant as any)?.campaign_audience || {}; - const campaignName = creative.campaign_name || ''; + const campaignProductsMap = (creative.tenant as any)?.campaign_products || {}; + + // campaign_name y adset_id tomados directamente de la creatividad fatigada + const campaignName = creative.campaign_name || null; + const adsetId = creative.adset_id || null; let audience: 'b2b' | 'b2c' | null = null; if (campaignName && campaignAudienceMap && typeof campaignAudienceMap === 'object' && campaignAudienceMap[campaignName]) { @@ -89,6 +93,18 @@ export async function POST(req: NextRequest) { } } + let product: string | null = null; + let landingUrl: string | null = null; + if (campaignName && campaignProductsMap && typeof campaignProductsMap === 'object' && campaignProductsMap[campaignName]) { + const pVal = campaignProductsMap[campaignName]; + if (typeof pVal === 'string') { + product = pVal; + } else if (typeof pVal === 'object' && pVal !== null) { + product = pVal.product ? String(pVal.product) : null; + landingUrl = pVal.landing_url ? String(pVal.landing_url) : null; + } + } + const deadline = new Date(); deadline.setDate(deadline.getDate() + 1); // 24 horas después @@ -102,9 +118,11 @@ export async function POST(req: NextRequest) { aspect_ratio: '4:5', variants_requested: 2, avoid_angles: avoidAngles, - campaign_name: creative.campaign_name || 'General', - adset_id: creative.adset_id || null, + campaign_name: campaignName, + adset_id: adsetId, audience, + product, + landing_url: landingUrl, language: 'es', deadline: deadline.toISOString(), };