feat: previews de creatividades en Ad Tracker (estilo Brava)
Meta Graph en vivo + ficha Google; galería por anuncio en resumen/Meta/Google. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
96
src/app/api/ad-tracker/creatives/route.ts
Normal file
96
src/app/api/ad-tracker/creatives/route.ts
Normal file
@@ -0,0 +1,96 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { createClient } from '@/lib/supabase/server';
|
||||
import { resolveActiveTenant, restGet } from '@/lib/tenant-context';
|
||||
import { isAgencyRole, userRole } from '@/lib/agency';
|
||||
import { AdDailyRow } from '@/lib/metrics';
|
||||
import {
|
||||
AdCreativeCard,
|
||||
buildGoogleCardsFromDaily,
|
||||
fetchMetaCreativeCards,
|
||||
} from '@/lib/ad-creatives-meta';
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
/**
|
||||
* GET /api/ad-tracker/creatives?since=YYYY-MM-DD&until=YYYY-MM-DD&platform=meta|google|all
|
||||
* Returns ad cards with creative preview URLs (Meta live; Google text fallback).
|
||||
*/
|
||||
export async function GET(req: NextRequest) {
|
||||
const supabase = await createClient();
|
||||
const {
|
||||
data: { user },
|
||||
} = await supabase.auth.getUser();
|
||||
if (!user) {
|
||||
return NextResponse.json({ error: 'unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
const ctx = await resolveActiveTenant(user);
|
||||
if (!ctx.tenantId || ctx.portfolioWide) {
|
||||
return NextResponse.json(
|
||||
{ error: 'select_tenant', hint: 'Elige un cliente en Contexto' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
const since = req.nextUrl.searchParams.get('since') || '';
|
||||
const until = req.nextUrl.searchParams.get('until') || '';
|
||||
const platform = (req.nextUrl.searchParams.get('platform') || 'all').toLowerCase();
|
||||
if (!/^\d{4}-\d{2}-\d{2}$/.test(since) || !/^\d{4}-\d{2}-\d{2}$/.test(until)) {
|
||||
return NextResponse.json(
|
||||
{ error: 'bad_range', hint: 'since/until YYYY-MM-DD' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
const agency = isAgencyRole(userRole(user));
|
||||
let daily: AdDailyRow[] = [];
|
||||
if (agency && ctx.viaView) {
|
||||
const tid = encodeURIComponent(ctx.tenantId);
|
||||
const d = await restGet<AdDailyRow[]>(
|
||||
`/rest/v1/ad_daily?tenant_id=eq.${tid}&date=gte.${since}&date=lte.${until}&select=date,platform,campaign_name,ad_id,ad_name,impressions,clicks,spend,platform_leads&order=date.desc&limit=5000`
|
||||
);
|
||||
daily = d.data || [];
|
||||
} else {
|
||||
const { data } = await supabase
|
||||
.from('ad_daily')
|
||||
.select(
|
||||
'date,platform,campaign_name,ad_id,ad_name,impressions,clicks,spend,platform_leads'
|
||||
)
|
||||
.gte('date', since)
|
||||
.lte('date', until)
|
||||
.order('date', { ascending: false })
|
||||
.limit(5000);
|
||||
daily = (data as AdDailyRow[]) || [];
|
||||
}
|
||||
|
||||
const warnings: string[] = [];
|
||||
let metaAds: AdCreativeCard[] = [];
|
||||
let googleAds: AdCreativeCard[] = [];
|
||||
|
||||
if (platform === 'meta' || platform === 'all') {
|
||||
const meta = await fetchMetaCreativeCards({
|
||||
tenantId: ctx.tenantId,
|
||||
dailyRows: daily,
|
||||
since,
|
||||
until,
|
||||
limit: 40,
|
||||
});
|
||||
metaAds = meta.ads;
|
||||
if (meta.warning) warnings.push(meta.warning);
|
||||
}
|
||||
|
||||
if (platform === 'google' || platform === 'all') {
|
||||
googleAds = buildGoogleCardsFromDaily(daily, since, until, 40);
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
ok: true,
|
||||
tenantId: ctx.tenantId,
|
||||
since,
|
||||
until,
|
||||
ads: [...metaAds, ...googleAds],
|
||||
metaCount: metaAds.length,
|
||||
googleCount: googleAds.length,
|
||||
warnings,
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user