From 809149850890c2bc222d81532141b952bcd53e32 Mon Sep 17 00:00:00 2001 From: 4Ventures <4ventures@MacBook-Air-de-4Ventures.local> Date: Wed, 23 Sep 2026 18:38:02 +0200 Subject: [PATCH] feat: zoom de creatividades con assets verticales Meta y PMax completo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Preview 4:5 + textos en card; modal con todas las imágenes, vídeos y copy. Co-authored-by: Cursor --- src/components/AdCreativesGallery.tsx | 594 ++++++++++++++++++++++++++ src/components/AdTrackerClient.tsx | 212 +-------- src/lib/ad-creatives-google.ts | 151 +++++-- src/lib/ad-creatives-meta.ts | 302 ++++++++++--- 4 files changed, 951 insertions(+), 308 deletions(-) create mode 100644 src/components/AdCreativesGallery.tsx diff --git a/src/components/AdCreativesGallery.tsx b/src/components/AdCreativesGallery.tsx new file mode 100644 index 0000000..7ca5367 --- /dev/null +++ b/src/components/AdCreativesGallery.tsx @@ -0,0 +1,594 @@ +'use client'; + +import React, { useEffect } from 'react'; +import type { AdCreativeCard, CreativeAsset } from '@/lib/ad-creatives-meta'; +import { formatEur, formatNum } from '@/lib/metrics'; +import { ImageIcon, Play, Maximize2, X, ExternalLink } from 'lucide-react'; + +export function proxiedMediaUrl(raw: string): string { + if (!raw) return ''; + if (raw.startsWith('/api/')) return raw; + return `/api/ad-tracker/media?url=${encodeURIComponent(raw)}`; +} + +function adHeadlines(ad: AdCreativeCard): string[] { + if (ad.headlines?.length) return ad.headlines; + return (ad.headline || '') + .split('\n') + .map((s) => s.trim()) + .filter(Boolean); +} + +function adDescriptions(ad: AdCreativeCard): string[] { + if (ad.descriptions?.length) return ad.descriptions; + return (ad.primaryText || '') + .split('\n') + .map((s) => s.trim()) + .filter(Boolean); +} + +function adAssets(ad: AdCreativeCard): CreativeAsset[] { + if (ad.assets?.length) return ad.assets; + const out: CreativeAsset[] = []; + const u = ad.imageUrl || ad.thumbnailUrl; + if (u) out.push({ kind: 'image', url: u, label: 'preview' }); + if (ad.youtubeId) { + out.push({ + kind: 'youtube', + url: `https://i.ytimg.com/vi/${ad.youtubeId}/hqdefault.jpg`, + youtubeId: ad.youtubeId, + label: 'YOUTUBE', + }); + } + return out; +} + +function previewUrlFor(ad: AdCreativeCard): string { + const assets = adAssets(ad); + const portrait = assets.find( + (a) => + a.kind === 'image' && + /portrait|vertical|tall|TALL|PORTRAIT|story|4.?5|9.?16/i.test( + a.label || '' + ) + ); + const raw = + portrait?.url || + ad.imageUrl || + ad.thumbnailUrl || + assets.find((a) => a.kind === 'image')?.url || + (ad.youtubeId + ? `https://i.ytimg.com/vi/${ad.youtubeId}/hqdefault.jpg` + : '') || + assets.find((a) => a.youtubeId)?.url || + ''; + return raw ? proxiedMediaUrl(raw) : ''; +} + +function MiniKpi({ label, value }: { label: string; value: string }) { + return ( +
+

{label}

+

+ {value} +

+
+ ); +} + +export function CreativesGallery({ + platform, + ads, + loading, + warning, + onZoom, +}: { + platform: 'meta' | 'google'; + ads: AdCreativeCard[]; + loading: boolean; + warning?: string | null; + onZoom: (ad: AdCreativeCard) => void; +}) { + const filtered = ads.filter((a) => a.platform === platform); + const platformWarning = + warning + ?.split(' · ') + .find((w) => + platform === 'meta' + ? /meta|token meta|sin ads meta/i.test(w) + : /google|pmax|rsa|sin ads google|sin assets/i.test(w) + ) || null; + const shownWarning = + filtered.length === 0 && warning + ? platformWarning || (ads.length === 0 ? warning : null) + : platformWarning && filtered.length === 0 + ? platformWarning + : null; + + return ( +
+
+

+ Creatividades · preview por anuncio +

+

+ {platform === 'meta' ? 'Meta Ads' : 'Google Ads'} · creatividades +

+

+ Preview vertical + textos. Pulsa Zoom para ver todas las piezas. +

+
+ {shownWarning ? ( +

+ {shownWarning} +

+ ) : null} + {loading ? ( +

+ Cargando creatividades… +

+ ) : filtered.length === 0 ? ( +

+ Sin creatividades para este periodo. +

+ ) : ( +
+ {filtered.map((ad) => ( + onZoom(ad)} + /> + ))} +
+ )} +
+ ); +} + +function CreativeCard({ + ad, + onZoom, +}: { + ad: AdCreativeCard; + onZoom: () => void; +}) { + const preview = previewUrlFor(ad); + const isVideo = ad.mediaType === 'video' || Boolean(ad.youtubeId); + const headlines = adHeadlines(ad); + const bodies = adDescriptions(ad); + const assets = adAssets(ad); + const extraCount = Math.max(0, assets.length - 1); + + return ( +
+ +
+
+
+

+ {ad.adName || 'Sin nombre'} +

+
+ + {ad.status || ad.platform} + + +
+
+

{ad.campaignName || '—'}

+ {ad.adsetName ? ( +

Conjunto: {ad.adsetName}

+ ) : null} +
+ + {ad.platform === 'meta' ? 'Meta' : 'Google'} + + {ad.mediaType && ad.mediaType !== 'unknown' ? ( + + {ad.mediaType} + + ) : null} + {assets.length > 0 ? ( + + {assets.length} piezas + + ) : null} +
+
+ {(headlines.length > 0 || bodies.length > 0) && ( +
+ {headlines.length > 0 ? ( +
+

+ Titulares +

+
    + {headlines.slice(0, 4).map((h) => ( +
  • + {h} +
  • + ))} + {headlines.length > 4 ? ( +
  • +{headlines.length - 4} más
  • + ) : null} +
+
+ ) : null} + {bodies.length > 0 ? ( +
+

+ Textos / descripciones +

+
    + {bodies.slice(0, 3).map((b) => ( +
  • + {b} +
  • + ))} + {bodies.length > 3 ? ( +
  • +{bodies.length - 3} más
  • + ) : null} +
+
+ ) : null} + {ad.cta ? ( +

+ CTA · {ad.cta} +

+ ) : null} +
+ )} +
+ + + + +
+
+
+ ); +} + +export function CreativeZoomModal({ + ad, + onClose, +}: { + ad: AdCreativeCard; + onClose: () => void; +}) { + const headlines = adHeadlines(ad); + const bodies = adDescriptions(ad); + const longH = ad.longHeadlines || []; + const assets = adAssets(ad); + const images = assets.filter((a) => a.kind === 'image' || a.kind === 'logo'); + const videos = assets.filter((a) => a.kind === 'youtube' || a.kind === 'video'); + const main = previewUrlFor(ad); + + useEffect(() => { + const onKey = (e: KeyboardEvent) => { + if (e.key === 'Escape') onClose(); + }; + window.addEventListener('keydown', onKey); + const prev = document.body.style.overflow; + document.body.style.overflow = 'hidden'; + return () => { + window.removeEventListener('keydown', onKey); + document.body.style.overflow = prev; + }; + }, [onClose]); + + return ( +
+
e.stopPropagation()} + > +
+
+

+ Zoom creatividades ·{' '} + {ad.platform === 'meta' ? 'Meta Ads' : 'Google Ads'} +

+

+ {ad.adName || 'Anuncio'} +

+

+ {ad.campaignName} + {ad.adsetName ? ` · ${ad.adsetName}` : ''} +

+
+ +
+ +
+
+
+
+ {main ? ( + // eslint-disable-next-line @next/next/no-img-element + {ad.adName} + ) : ( +
+ Sin preview +
+ )} +
+
+ + + + +
+
+

+ Impresiones:{' '} + + {formatNum(ad.impressions)} + +

+

+ Formato:{' '} + {ad.mediaType || '—'} +

+

+ Estado:{' '} + {ad.status || '—'} +

+ {ad.cta ? ( +

+ CTA: {ad.cta} +

+ ) : null} + {ad.businessName ? ( +

+ Negocio:{' '} + {ad.businessName} +

+ ) : null} + {ad.linkUrl ? ( + + + {ad.linkUrl} + + ) : null} +
+
+ +
+ {images.length > 0 ? ( +
+

+ Imágenes y logos ({images.length}) +

+
+ {images.map((asset, i) => ( +
+
+ {/* eslint-disable-next-line @next/next/no-img-element */} + {asset.label +
+ {asset.label ? ( +
+ {asset.label} + {asset.kind === 'logo' ? ' · logo' : ''} +
+ ) : null} +
+ ))} +
+
+ ) : null} + + {videos.length > 0 ? ( +
+

+ Vídeos ({videos.length}) +

+
+ {videos.map((v, i) => ( +
+ {v.youtubeId ? ( +
+