fix: Meta creatividades — 5 titulares, 5 textos y descripción
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -16,19 +16,27 @@ export function proxiedMediaUrl(raw: string): string {
|
||||
}
|
||||
|
||||
function adHeadlines(ad: AdCreativeCard): string[] {
|
||||
if (ad.headlines?.length) return ad.headlines;
|
||||
if (ad.headlines?.length) return ad.headlines.slice(0, 5);
|
||||
return (ad.headline || '')
|
||||
.split('\n')
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean);
|
||||
.filter(Boolean)
|
||||
.slice(0, 5);
|
||||
}
|
||||
|
||||
function adDescriptions(ad: AdCreativeCard): string[] {
|
||||
if (ad.descriptions?.length) return ad.descriptions;
|
||||
function adBodies(ad: AdCreativeCard): string[] {
|
||||
if (ad.bodies?.length) return ad.bodies.slice(0, 5);
|
||||
// Legacy: primaryText used to hold bodies
|
||||
return (ad.primaryText || '')
|
||||
.split('\n')
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean);
|
||||
.filter(Boolean)
|
||||
.slice(0, 5);
|
||||
}
|
||||
|
||||
function adLinkDescriptions(ad: AdCreativeCard): string[] {
|
||||
if (ad.descriptions?.length) return ad.descriptions.slice(0, 5);
|
||||
return [];
|
||||
}
|
||||
|
||||
function adAssets(ad: AdCreativeCard): CreativeAsset[] {
|
||||
@@ -186,8 +194,9 @@ function CreativeCard({
|
||||
}) {
|
||||
const preview = previewUrlFor(ad);
|
||||
const isVideo = ad.mediaType === 'video' || Boolean(ad.youtubeId);
|
||||
const headline = adHeadlines(ad)[0] || '';
|
||||
const body = adDescriptions(ad)[0] || '';
|
||||
const headlines = adHeadlines(ad);
|
||||
const bodies = adBodies(ad);
|
||||
const linkDescs = adLinkDescriptions(ad);
|
||||
const assets = adAssets(ad);
|
||||
const extraCount = Math.max(0, assets.length - 1);
|
||||
|
||||
@@ -206,14 +215,19 @@ function CreativeCard({
|
||||
className="w-full h-full object-cover"
|
||||
loading="lazy"
|
||||
/>
|
||||
) : ad.platform === 'google' && headline ? (
|
||||
) : ad.platform === 'google' && headlines.length ? (
|
||||
<div className="w-full h-full p-4 space-y-2 text-left bg-gradient-to-b from-[#1a2332] to-[#0d121f] flex flex-col justify-center">
|
||||
<p className="text-[10px] uppercase tracking-wider text-sky-400">
|
||||
Google · {ad.mediaType || 'rsa'}
|
||||
</p>
|
||||
<p className="text-sm font-semibold text-blue-300 leading-snug line-clamp-4">
|
||||
{headline}
|
||||
</p>
|
||||
{headlines.slice(0, 3).map((h) => (
|
||||
<p
|
||||
key={h}
|
||||
className="text-sm font-semibold text-blue-300 leading-snug line-clamp-2"
|
||||
>
|
||||
{h}
|
||||
</p>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center text-gray-500 text-xs px-4">
|
||||
@@ -259,18 +273,55 @@ function CreativeCard({
|
||||
<p className="text-xs text-gray-400">Conjunto: {ad.adsetName}</p>
|
||||
) : null}
|
||||
</div>
|
||||
{(headline || body) && (
|
||||
<div className="space-y-1.5 text-xs border border-gray-200 rounded-xl p-3 bg-gray-50">
|
||||
{headline ? (
|
||||
<p className="text-gray-900 font-medium leading-snug line-clamp-2">
|
||||
{headline}
|
||||
</p>
|
||||
{(headlines.length > 0 || bodies.length > 0 || linkDescs.length > 0) && (
|
||||
<div className="space-y-2.5 text-xs border border-gray-200 rounded-xl p-3 bg-gray-50">
|
||||
{headlines.length > 0 ? (
|
||||
<div>
|
||||
<p className="text-[9px] uppercase tracking-wider text-gray-500 mb-1">
|
||||
Titulares ({headlines.length})
|
||||
</p>
|
||||
<ul className="space-y-1">
|
||||
{headlines.map((h) => (
|
||||
<li
|
||||
key={h}
|
||||
className="text-gray-900 font-medium leading-snug"
|
||||
>
|
||||
{h}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
) : null}
|
||||
{body ? (
|
||||
<p className="text-gray-500 leading-relaxed line-clamp-3">{body}</p>
|
||||
{bodies.length > 0 ? (
|
||||
<div>
|
||||
<p className="text-[9px] uppercase tracking-wider text-gray-500 mb-1">
|
||||
Textos ({bodies.length})
|
||||
</p>
|
||||
<ul className="space-y-1.5">
|
||||
{bodies.map((b) => (
|
||||
<li key={b} className="text-gray-600 leading-relaxed">
|
||||
{b}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
) : null}
|
||||
{linkDescs.length > 0 ? (
|
||||
<div>
|
||||
<p className="text-[9px] uppercase tracking-wider text-gray-500 mb-1">
|
||||
Descripción
|
||||
</p>
|
||||
<ul className="space-y-1">
|
||||
{linkDescs.map((d) => (
|
||||
<li key={d} className="text-gray-500 leading-relaxed">
|
||||
{d}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
) : null}
|
||||
{ad.cta ? (
|
||||
<p className="text-[10px] text-brand-700 uppercase tracking-wide pt-1">
|
||||
<p className="text-[10px] text-brand-700 uppercase tracking-wide pt-0.5">
|
||||
CTA · {ad.cta}
|
||||
</p>
|
||||
) : null}
|
||||
@@ -298,7 +349,8 @@ export function CreativeZoomModal({
|
||||
onClose: () => void;
|
||||
}) {
|
||||
const headlines = adHeadlines(ad);
|
||||
const bodies = adDescriptions(ad);
|
||||
const bodies = adBodies(ad);
|
||||
const linkDescs = adLinkDescriptions(ad);
|
||||
const longH = ad.longHeadlines || [];
|
||||
const assets = adAssets(ad);
|
||||
const images = assets.filter((a) => a.kind === 'image' || a.kind === 'logo');
|
||||
@@ -494,10 +546,10 @@ export function CreativeZoomModal({
|
||||
{headlines.length > 0 ? (
|
||||
<section>
|
||||
<h3 className="text-sm font-semibold text-gray-900 mb-2">
|
||||
Titular
|
||||
Titulares ({headlines.length})
|
||||
</h3>
|
||||
<ul className="space-y-1.5 text-sm text-gray-800">
|
||||
{headlines.slice(0, 2).map((h) => (
|
||||
{headlines.map((h) => (
|
||||
<li
|
||||
key={h}
|
||||
className="rounded-lg border border-gray-200 bg-gray-50 px-3 py-2"
|
||||
@@ -512,10 +564,10 @@ export function CreativeZoomModal({
|
||||
{longH.length > 0 ? (
|
||||
<section>
|
||||
<h3 className="text-sm font-semibold text-gray-900 mb-2">
|
||||
Titulares largos
|
||||
Titulares largos ({longH.length})
|
||||
</h3>
|
||||
<ul className="space-y-1.5 text-sm text-gray-800">
|
||||
{longH.slice(0, 2).map((h) => (
|
||||
{longH.slice(0, 5).map((h) => (
|
||||
<li
|
||||
key={h}
|
||||
className="rounded-lg border border-gray-200 bg-gray-50 px-3 py-2"
|
||||
@@ -530,10 +582,10 @@ export function CreativeZoomModal({
|
||||
{bodies.length > 0 ? (
|
||||
<section>
|
||||
<h3 className="text-sm font-semibold text-gray-900 mb-2">
|
||||
Texto
|
||||
Textos ({bodies.length})
|
||||
</h3>
|
||||
<ul className="space-y-2 text-sm text-gray-600">
|
||||
{bodies.slice(0, 2).map((b) => (
|
||||
{bodies.map((b) => (
|
||||
<li
|
||||
key={b}
|
||||
className="rounded-lg border border-gray-200 bg-gray-50 px-3 py-2 leading-relaxed whitespace-pre-wrap"
|
||||
@@ -544,6 +596,24 @@ export function CreativeZoomModal({
|
||||
</ul>
|
||||
</section>
|
||||
) : null}
|
||||
|
||||
{linkDescs.length > 0 ? (
|
||||
<section>
|
||||
<h3 className="text-sm font-semibold text-gray-900 mb-2">
|
||||
Descripción
|
||||
</h3>
|
||||
<ul className="space-y-2 text-sm text-gray-600">
|
||||
{linkDescs.map((d) => (
|
||||
<li
|
||||
key={d}
|
||||
className="rounded-lg border border-gray-200 bg-gray-50 px-3 py-2 leading-relaxed whitespace-pre-wrap"
|
||||
>
|
||||
{d}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</section>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -107,6 +107,9 @@ export default function DriveConnectPanel() {
|
||||
<strong className="text-gray-800">Google Ads</strong>.
|
||||
Sirve para carpetas Drive, GA4, GSC y creatividades Google Ads.
|
||||
Si ves error de token caducado en Ads, pulsa Reconectar Google.
|
||||
En Google Cloud el consent screen debe estar en{' '}
|
||||
<strong className="text-gray-800">In production</strong> (si está
|
||||
en Testing el refresh token caduca a los 7 días).
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user