new-term-radar
신규 AI 용어를 매일 추적, X·Hugging Face·GitHub 등에서 모델·제품·레포 이름을 빠르게 수집
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
Dashboard unificado de monitoreo de campañas digitales y offline. Consolida Google Ads, Meta Ads, GA4 y canales offline, compara performance real vs planning, visualiza el funnel completo (impresiones → clicks → sesiones → conversiones) e incorpora inteligencia competitiva (web + redes sociales). Estado: app desplegada en Vercel con datos reales. Tres orquestadores:README 전문 보기 ↗
Dashboard unificado de monitoreo de campañas digitales y offline. Consolida Google Ads, Meta Ads, GA4 y canales offline, compara performance real vs planning, visualiza el funnel completo (impresiones → clicks → sesiones → conversiones) e incorpora inteligencia competitiva (web + redes sociales).
Estado: app desplegada en Vercel con datos reales. Tres orquestadores:
- GitHub Actions dispara los endpoints
/api/cron/*del Next app para GA4, Meta (orgánico FB/IG), paid creatives, sentiment, organic insights. Verdocs/crons-github-actions.md.- Apps Script "Sync Drive Tablero CB" lee folders de Drive (CSVs de Cuadros Básicos, Floor Share y Planning de Pauta) y los upsertea a Supabase. Sin OAuth tokens — corre con la cuenta Google del dueño del script. Ver
docs/planning-media-sync.md.- N8N queda solo para Apify (scraping de competidores web/social).
flowchart LR
subgraph Sources["Fuentes de datos"]
META[Meta Ads / Graph API]
GA4[Google Analytics 4]
DRIVE[Google Drive<br/>CSVs CB / FS / Pauta]
end
subgraph Scrape["Scraping"]
APIFY[Apify<br/>SimilarWeb + RRSS]
end
subgraph OrchA["Orquestación A: GH Actions"]
GHA[GitHub Actions<br/>workflows .yml<br/>cada 6-12h]
end
subgraph OrchB["Orquestación B: Apps Script"]
GAS[Apps Script<br/>Sync Drive Tablero CB<br/>trigger 1h]
end
subgraph OrchC["Orquestación C: N8N"]
N8N[N8N workflows<br/>Apify only]
end
subgraph App["Frontend + APIs"]
NEXT[Next.js 14 en Vercel<br/>/api/cron/* endpoints]
end
subgraph Storage["Storage"]
SUPA[(Supabase<br/>Postgres)]
end
META --> NEXT
GA4 --> NEXT
GHA -->|curl /api/cron/*| NEXT
NEXT --> SUPA
DRIVE --> GAS
GAS -->|REST upsert| SUPA
APIFY --> N8N
N8N --> SUPA
SUPA --> NEXT
Los crons de APIs (Meta FB/IG/paid, GA4, sentiment, organic-insights) viven
en .github/workflows/ y disparan endpoints /api/cron/* — detalle en
docs/crons-github-actions.md.
Los syncs de archivos de Drive (Cuadros Básicos, Floor Share, Planning de
Pauta) viven en el Apps Script "Sync Drive Tablero CB" que se ejecuta cada
hora — detalle en docs/planning-media-sync.md.
Stack
dashboard-mkt/
├── apps/
│ └── web/ # Next.js (frontend)
├── packages/
│ ├── db/ # Cliente Supabase + tipos
│ └── shared/ # Tipos y utils compartidos
├── supabase/
│ ├── migrations/ # SQL versionado
│ └── seed/ # Data de prueba
├── n8n-workflows/ # JSON exports versionados (fase 2)
└── docs/ # Documentación adicional
npm i -g pnpm)git clone https://github.com/<tu-usuario>/dashboard-mkt.git
cd dashboard-mkt
pnpm install
Ver guía paso a paso en docs/supabase-setup.md. Resumen:
supabase/migrations/0001_initial_schema.sqlsupabase/migrations/0002_views.sqlsupabase/seed/seed.sql (opcional, data de prueba)cp .env.example apps/web/.env.local
# editar apps/web/.env.local con tus valores reales
Variables requeridas en fase 1:
| Variable | Dónde se usa |
|---|---|
NEXT_PUBLIC_SUPABASE_URL | Frontend + scripts |
NEXT_PUBLIC_SUPABASE_ANON_KEY | Frontend (lectura RLS) |
SUPABASE_SERVICE_ROLE_KEY | Scripts / N8N (server) |
NEXT_PUBLIC_APP_URL | Frontend (links) |
pnpm dev
# → http://localhost:3000
El funnel depende de que las UTMs sean consistentes. Toda la convención
está en docs/utm-conventions.md. Resumen:
utm_source, utm_medium, utm_campaign → obligatoriosutm_source = plataforma (google, facebook, instagram, tiktok, ...)utm_medium = tipo de medio (cpc, paid-social, email, display, ...)utm_campaign = nombre interno de la campaña (q2-search, lanzamiento-mayo, ...)Toda modificación al schema se versiona como un archivo SQL nuevo:
supabase/migrations/000N_descripcion.sql. Nunca editar una migración ya aplicada.
Cuando crees un workflow, exportalo como JSON y guardalo en
n8n-workflows/<nombre>.json. Así queda versionado y se puede importar en otra
instancia de N8N. Ver n8n-workflows/README.md.
docs/architecture.md — detalle técnico de cada capadocs/calendario-publicacion-meta.md — Generador de Contenido: calendario, adaptación de piezas y publicación orgánica IG/FB (+ estado de permisos Meta)docs/utm-conventions.md — convención de UTMs (crítico)docs/supabase-setup.md — paso a paso para crear y aplicar el schemadocs/planning-sheet-template.md — estructura del Sheet de planningdocs/n8n-planning-setup.md — setup del workflow planning N8Ndocs/n8n-ga4-setup.md — setup del workflow GA4 → web_trafficdocs/n8n-ga4-demographics-setup.md — setup del workflow GA4 → tablas demográficas (device, geo, interest)docs/n8n-social-setup.md — setup del workflow Social Sheet → social_competitor + social_metricsdocs/n8n-scraper-drean-setup.md — setup del scraper adaptado de Tombaio para RRSS de Drean/contenido)Genera piezas orgánicas para redes (imagen + copy) por pilar, con la
estética premium fija de Drean. Fuente de verdad completa (qué funciona,
qué NO, y el próximo paso): docs/generador-contenido.md.
Conexiones:
fal-ai/ideogram/v3 — ÚNICO modelo de imagen (respeta la estética premium). FAL_KEY (fal.ai/dashboard/keys, prepago).gpt-4o-mini — brief: prompt de imagen (EN), caption (ES), hashtags, mensaje clave = título + bajada para la placa. OPENAI_API_KEY.meta_posts — top posts por pilar como insumo del brief.producto-catalog.ts con descripcion + medidas.Flujo (todo con Ideogram):
/api/generar-contenido (pilar/categoría/modelo/formato/aspecto/cantidad).BRAND_LOOK), proporción por categoría (heladera alta, cocina/lavarropas altura mesada). Con modelo, recrea ese electrodoméstico (descripción visual, no packshot pixel-exacto). En pilar Experiencia uso, con personas usando el producto.Trade-off clave (ver doc): no se puede producto pixel-exacto + estética premium + buena integración a la vez. Se eligió estética (Ideogram) → producto recreado, no exacto.
Qué NO funciona — no re-iterar: Bria product-shot / 2-etapas (escenas claras o producto mal integrado),
product-photography(passthrough), referencias de estilo por posteos (se sacó por decisión),portrait_4_5(inválido), packshots del1000x1000(son fichas; los limpios están en "Alta"), y la carpeta "MABE BRAND CENTER" no la indexa el Drive tool.Próximo paso: una herramienta de compositing/inpainting mejor que Bria para meter el packshot real bien colocado en la escena de Ideogram (inpainting con máscara / relight). Video (Kling/Veo) pendiente.
| Comando | Acción |
|---|---|
pnpm dev | Levanta el frontend en localhost:3000 |
pnpm build | Build de producción de todos los packages |
pnpm lint | Lint en todo el monorepo |
pnpm typecheck | Type-check estricto en todo el monorepo |
pnpm db:types | Regenera tipos TS desde Supabase (requiere CLI) |
Lo que YA funciona:
dashboard-mkt-seven.vercel.app)/api/cron/meta-paid-sync) — operativo, a la espera de acceso a la Ad Account (ver Operación)/contenido) — imágenes vía fal.ai (Ideogram v3, estética premium fija) + copy con OpenAI. Con modelo, recrea el electrodoméstico (no packshot exacto). Placa título+bajada editable. Ver docs/generador-contenido.md. Requiere FAL_KEY en Vercel.Lo que falta (fase 2+):
Ver docs/next-phases.md para el roadmap detallado.
Los syncs corren como GitHub Actions (detalle en
docs/crons-github-actions.md). Para ver si
uno falló: repo → Actions → workflow → run rojo → logs con el JSON del
endpoint. Dos gotchas que ya nos mordieron:
El scraper de SimilarWeb (Apify, vía N8N) corre semanal y guarda un snapshot
nuevo en competitor_web por cada corrida. Cada snapshot trae en el campo
raw los últimos ~3 meses de visitas estimadas por SimilarWeb.
Decisión clave: getCompetitorMonthlyHistory() mergea los meses de
todos los snapshots (no solo el último). Razón: si una corrida nueva
solo trae Mar/Abr/May, el código viejo "perdía" los meses anteriores que
estaban en snapshots viejos (Feb, Ene...). El merge preserva esos meses,
y cuando dos snapshots tienen el mismo mes, gana el más reciente (estimación
más fresca de SimilarWeb).
Como fetchAll() ordena por fecha DESC, el orden de iteración garantiza
que el snapshot más reciente entre primero al Map<fecha, visitas> y los
viejos solo agreguen meses que no estaban.
La página /web tiene una sección "Competencia Web" que compara Drean contra
competidores (Whirlpool, Philco, Electrolux, Gafa, Newsan).
Decisión: Drean usa SimilarWeb (igual que los competidores), NO GA4. Razón: la comparación tiene que ser apples-to-apples — SimilarWeb estima visitas con un panel + modelos, GA4 mide sesiones reales first-party. Mezclar fuentes hace que Drean parezca artificialmente más grande (o más chico) que los competidores.
GA4 sigue usándose en el resto de la página /web (KPIs de Drean,
canales, landings, demografía) que son métricas propias, no de benchmark.
Si alguna vez se quiere volver al modelo viejo (Drean=GA4 en la tabla y chart
de competencia), revertir el commit que removió getDreanWebMetrics() de la
sección.
Síntoma: el sync GA4 falla con invalid_grant: Token has been expired or revoked.
Causa: la app OAuth (Google Cloud → Google Auth Platform → Público) está en
estado "Prueba/Testing", y en ese modo Google caduca los refresh tokens a
los 7 días. Fix permanente: Publicar app (pasar a "En producción"); ahí el
token deja de expirar. Si hay que regenerarlo, usar
OAuth Playground con el client
propio ("Use your own OAuth credentials", el cliente Dashboard Vercel) y scope
https://www.googleapis.com/auth/analytics.readonly; pegar el refresh token nuevo
en la env var GOOGLE_REFRESH_TOKEN de Vercel y redeploy.
La pauta de Drean en Meta corre dentro de la cuenta publicitaria Mabe Argentina
(act_1428795852368328), propiedad del BM de OMD (796317087092108). El cron
meta-paid-sync lee esa cuenta con el token del system user
(META_SYSTEM_USER_TOKEN) que vive en nuestro BM (122350585916257).
Pendiente operativo: para que el cron corra solo todos los días, OMD tiene
que compartir la cuenta Mabe con nuestro BM como partner (acceso "Ver
rendimiento" o superior). Mientras eso no esté, el schedule queda
deshabilitado y el endpoint devuelve No encontré cuenta… Disponibles: (ninguna).
Backfill puntual sin esperar a OMD: si alguien con acceso personal a la
cuenta genera un token de usuario en el Graph API Explorer
(scopes: ads_read, read_insights, business_management; al autorizar, tildar
OMD Argentina en la pantalla de negocios), se puede correr el sync una vez
seteando estas envs en Vercel:
| Env var | Valor |
|---|---|
META_PAID_TOKEN_OVERRIDE | el token EAA... de usuario (temporal, ~1-2 h) |
META_AD_ACCOUNT_ID | act_1428795852368328 (fija la cuenta sin depender del nombre) |
Y disparar el workflow desde Actions → Meta paid creatives sync → Run workflow
con el input mes (YYYY-MM). El workflow también acepta debug=true (lista
cuentas y permisos que ve el token, sin sincronizar) y act_id (override por
corrida). Después de cada backfill borrar META_PAID_TOKEN_OVERRIDE por
higiene.
Imágenes permanentes (mirror al bucket): las URLs de imágenes de Meta están
firmadas y caducan en 1-2 días. El endpoint espeja cada thumbnail al bucket
meta-thumbs de Supabase (migración 0039) — la URL pública del bucket no
caduca. Para piezas de video, Meta solo expone un thumbnail_url de 64×64
desde el ad; la versión en alta vive en el post original y se trae vía
effective_object_story_id → full_picture, leído con el token del system user
(no con el override de usuario: el usuario típicamente no administra la Página).
Tres folders de Drive se sincronizan a Supabase con un único Apps Script que corre cada hora.
| Folder Drive | Property | Tabla Supabase | Proyecto Supabase | Filename pattern |
|---|---|---|---|---|
| Cuadros Básicos | CB_FOLDER_ID | cuadro_basico_semanal + contactos | CB (fsvdcpqzchrezkxflyfi) | NN.csv (semana) o tienda-promotor-supervisor*.csv |
| Floor Share | FS_FOLDER_ID | floor_share | CB (fsvdcpqzchrezkxflyfi) | YYYY-MM_Categoria.csv o NN_Categoria.csv |
| Planning de Pauta | PLANNING_FOLDER_ID | planning_media | Principal (vtcrhyyirqexczycuwhe) | Mes-Pauta.xlsx (planilla viva de OMD con 12 meses adentro) |
⚠️ Arquitectura multi-proyecto: el dashboard usa 2 proyectos de Supabase:
vtcrhyyirqexczycuwhe) — donde viven planning_media,
meta_posts, ga4_*, pauta_performance, insights_log, etc. Las
dashboards de Next leen de acá.fsvdcpqzchrezkxflyfi) — donde viven cuadro_basico_semanal,
floor_share, contactos, sync_status.El Apps Script escribe CB y Floor Share al proyecto CB (configurado en
SUPABASE_URL), pero planning_media vive en el principal. Por eso el
script usa un par de properties separado (PLANNING_SUPABASE_URL y
PLANNING_SUPABASE_SECRET_KEY) y una rama condicional en syncPlanning que
arma un planningCtx distinto al ctx general antes de upsertear.
Por qué Apps Script y no GitHub Actions / Vercel:
syncAll cada 1h) sin secrets.sync_status (proyecto CB) trackea cada archivo por drive_modified
→ solo se reprocesa lo que cambió (idempotente y barato). Detalle clave: la
comparación es contra file.getLastUpdated() en Drive, así que basta con
editar el archivo para forzar re-sync. Si querés re-procesar a la fuerza
sin tocar el archivo, borrar la fila de sync_status correspondiente.Prefer: resolution=merge-duplicates por unique key compuesta
→ la data histórica nunca se pisa con ceros aunque borres archivos del folder.Script Properties requeridas (7 en total):
| Property | Para qué |
|---|---|
SUPABASE_URL | URL del proyecto CB (CB / FS / contactos / sync_status) |
SUPABASE_SECRET_KEY | service_role del proyecto CB |
CB_FOLDER_ID | ID del folder con CSVs de Cuadros Básicos |
FS_FOLDER_ID | ID del folder con CSVs de Floor Share |
PLANNING_FOLDER_ID | ID del folder con Mes-Pauta.xlsx (Pauta-omd) |
PLANNING_SUPABASE_URL | URL del proyecto Principal (vtcrhyyirqexczycuwhe) |
PLANNING_SUPABASE_SECRET_KEY | service_role del proyecto principal |
Mes-Pauta.xlsx (planning de pauta)OMD mantiene una planilla viva tipo Junio-Pauta.xlsx que contiene todo el
año (12 columnas mensuales) con todas las categorías interleavadas. Cada
mes te manda una versión nueva renombrada al mes vigente
(Julio-Pauta.xlsx, Agosto-Pauta.xlsx, etc.). El script parsea las 12
columnas mensuales de cada archivo, así jul-dic se mantienen actualizados con
el forecast más reciente y los meses pasados se refrescan si OMD ajustó algo.
Estructura del Excel:
| Campaña | Rol Of Comms | Sistema | Formato & Channel | Enero | Febrero | ... | Diciembre |
|---|---|---|---|---|---|---|---|
| (vacío) | Build | Total OFF | (vacío) | $-, $-, ... | (← fila skipeada por subtotal) | ||
| Brand | Build | TVC | Tanda | $- | $- | ... | $- |
| Brand | Build | OOH | Cartel Unicenter | $- | $- | ... | $9.800.000 |
| Heladera | Build | YouTube | Bumper | ... | (Heladera se mappea a "Refrigeración") | ||
| (vacío) | Build | Total ON | (vacío) | (← otra fila skipeada) | |||
| (vacío) | (vacío) | (vacío) | PERCEPCIÓN IIBB | $295.835 | ... | (cost row, campania='General', tipo='costo') |
Reglas del parser syncPlanning:
Mes-Pauta.{csv,xlsx} (mes en español, sin acentos). Para
.xlsx, el script convierte a Google Sheets en memoria via Advanced Drive
Service y borra la copia temporal al terminar.Formato + (Sistema o Rol) + (Campaña o categoría inferible del
filename). Las filas vacías arriba del header (logo, "2026", "Q1/Q2/Q3/Q4")
se ignoran.normalizePlanningCategoria): "heladera" →
Refrigeración, "refriger*" → Refrigeración, "cocina*" → Cocina,
"lavado" → Lavado, "brand" → Brand, "promo*" → Promoción,
"ugc" → UGC.esPlanningSkip): salta filas donde
sistema o formato arrancan con Total o Subtotal (ej. "Total
OFF", "Total ON", "TOTAL", "Total Campaña"). Sin este filtro la inversión
se cuenta 2.2× (subtotales + detalle).esPlanningCosto): filas cuyo formato matchea
/iibb|percep|cheque|impuesto|tech\s*fee|comisi/i se marcan como
tipo='costo'. Si la fila no tiene categoría asignada (típico de costos
generales del mes), se le asigna campania='General'. Sin este fallback los
costos no aparecen en Costos adicionales del dashboard.parseLocaleNumber): strippea $, espacios, %;
convierte formato AR (65.253.780,00) a número JS. Sin el strip del $,
todos los valores quedan en null y no se emite nada.Enero..Diciembre como
headers y emite una fila de planning_media por cada (data row × mes con
inversión > 0). La fecha resultante usa el año de PLANNING_YEAR (default:
año actual) y el mes correspondiente.Convenciones y troubleshooting completo en
docs/planning-media-sync.md.
Subir un mes nuevo (cada vez que OMD te mande pauta nueva):
Mes-Pauta.xlsx (ej. Julio-Pauta.xlsx).PLANNING_FOLDER_ID.syncAll → Run./planning que el mes nuevo aparezca con los totales correctos.Forzar re-procesamiento de un archivo ya sincronizado (debugging):
-- En el proyecto CB
delete from sync_status where filename = 'Junio-Pauta.xlsx';
Luego correr syncAll. El archivo se reprocesará desde cero.
Verificación de integridad post-sync (los totales del DB tienen que
matchear el Excel — sumar Total OFF + Total ON del mes):
-- En el proyecto principal
select fecha,
sum(inversion) filter (where tipo='media') as media,
sum(inversion) filter (where tipo='costo') as costos
from planning_media
group by fecha
order by fecha;
Para que el sync funcione hay que tener:
Drive, Version v2). Sin esto,
los .xlsx no se pueden convertir a Sheets para parseo.syncAll, Google pide
autorización para Drive (read/write para conversión temporal) y
UrlFetchApp (para hablar con Supabase). Aceptar todo.sync_status existente en el proyecto CB con columnas
filename, drive_id, drive_modified, synced_at, rows_processed, status, error_msg.planning_media existente en el proyecto principal (creada por
supabase/migrations/0004_planning_media.sql) con la unique key compuesta
(fecha, campania, rol, sistema, formato, tipo).La grilla "Piezas pautadas" del dashboard tiene 4 secciones (Meta, TikTok,
Programmatic, YouTube) que comparten la misma tabla meta_paid_creatives,
discriminadas por la columna plataforma. Hoy solo Meta tiene cron de API;
las otras 3 entran por carga manual (source = 'looker_export').
| Plataforma | API necesaria | Estado |
|---|---|---|
| TikTok | TikTok Marketing API — requiere app de desarrollador aprobada (App ID + Secret + OAuth con el anunciante). El acceso al Business Center solo no alcanza: no hay API keys estáticas, todo es OAuth contra una app registrada. Pendiente: pedir a DDB/OMD si ya tienen una app aprobada para Mabe (advertiser_id 7601141532934225927). | Manual (export desde Business Center → Custom reports). Loader manual o INSERT armado a mano contra meta_paid_creatives con plataforma='tiktok', source='looker_export'. |
| YouTube | Google Ads API (YouTube vive ahí, no en GA4). Proyecto aparte: OAuth + Developer Token de Google Ads + autorización del MCC que opera la cuenta. | Manual. Limitación conocida: el export de Looker no trae inversión por anuncio en YouTube. |
| Programmatic | DSP que pautó (DV360 / The Trade Desk / etc.). Cada DSP tiene su API. Decidir cuál primero según volumen. | Manual. |
El grid de "Piezas pautadas" (/performance → Por Medio) renderiza
image_url ?? thumbnail_url de meta_paid_creatives. No hay nada que tocar en
el front: todo es origen de la imagen en cada plataforma.
Para piezas de video, el ad solo expone un thumbnail_url de 64×64; la
imagen en alta vive en el video/post. /api/cron/meta-paid-sync resuelve en
cascada (todo con el token del system user META_SYSTEM_USER_TOKEN, que
administra la Página Drean — el override de usuario NO sirve para esto):
video_id del creative, buscado en orden: inline (video_id,
object_story_spec.video_data.video_id), asset_feed_spec.videos (creatives
dinámicos / Advantage+ — la mayoría de Drean), attachments del post
(effective_object_story_id), y por último un fetch directo del creative
(/{creative_id}?fields=..., que devuelve el object_story_spec/asset_feed_spec
completo cuando la llamada masiva los trae incompletos).video_id → /{video_id}/thumbnails → se elige el de mayor width
(el cover full-size, típicamente 1080).full_picture del post.image_url / cover / thumbnail 64×64 (se ve pixelado).Mirror al bucket meta-thumbs (key paid/{ad_id}-hdN.jpg): el helper
mirrorMetaImage saltea la descarga si la key ya existe, así que cuando se
mejora la fuente hay que bumpear el sufijo (-hd6 → -hd7 …) para forzar el
re-espejado. Key actual: -hd7. Después de deployar un cambio de resolución,
re-correr meta-paid-sync para que re-espeje.
Diagnóstico (en la respuesta del cron):
img_stats: {video_thumb, full_picture, fallback, max_thumb_w} — cuántas
piezas resolvieron por cada vía y el ancho máx. de thumbnail (1080 = OK).fallback_sample: lista las piezas que caen en fallback con qué tienen
(has_image_url, has_thumbnail, has_story, afs_videos, afs_images, src).Modo repair (?repair=1, o input repair del workflow): re-procesa por
creative_id directo las piezas Meta con imagen vieja (ads eliminados que
/ads ya no devuelve). En batches, en loop hasta restantes=0.
Page size limit=8 en la llamada a /ads: la request (insights + creative +
asset_feed_spec) es pesada y pega contra el límite de datos de Meta. Páginas
chicas = respuestas completas y confiables.
⚠️ Rate-limiting de Meta — NO correr el sync muchas veces seguidas. Tras ~20 runs en una hora, Meta empieza a degradar las respuestas: devuelve
asset_feed_specsinvideo_ido tira500 "reduce the amount of data", y todo cae a fallback. Si pasa: no bumpees la key (sobrescribirías las buenas con thumbnails chicos), dejá la key como está y esperá ~1h; el mirror reusa las imágenes en alta que siguen en el bucket bajo la key vigente.
Limpieza hecha: había 65 filas duplicadas source='looker_export' con
ad_id/creative_id tipo looker_xxx (no existen en la API) que se veían
pixeladas (copias viejas de piezas que la API ya trae nítidas). Se borraron:
delete from meta_paid_creatives
where plataforma = 'meta' and source = 'looker_export' and ad_id like 'looker%';
Límite real conocido: quedan ~9 piezas CPC de video por mes que no
exponen el video_id por ninguna API (probado: inline, post, asset_feed_spec,
fetch directo del creative). Su única fuente nítida es el archivo original de la
agencia. No es un bug — es el techo de la Graph API para esos creatives.
Estas plataformas no se cargan por la API de Meta (TikTok es carga manual;
DV360 viene de sus reportes). Sus piezas no tienen image_url. Y el reporte de
DV360 NO puede traer la imagen: sus dimensiones de creative son solo metadata
(Creative, Creative ID, Creative Source/Type/Size/Status/Width) — ninguna
es una URL de imagen. O sea, cargar la imagen vía el reporte de DV360 por email
está descartado por diseño.
Solución elegida: planilla de la agencia. El grid ya muestra image_url, así
que solo falta la fuente de esos links. Se le pidió a la agencia una planilla
de Google (Creativo · Plataforma · Link) con el link por pieza:
i.ytimg.com).Pendiente: cuando llegue la planilla, sincronizarla a Supabase + cron que baje las
miniaturas al bucket meta-thumbs y complete image_url. La YOUTUBE_API_KEY ya
está cargada como secret para la parte de YouTube. (Las mismas ~9 CPC de Meta sin
video_id también se resolverían por esta vía si la agencia las incluye.)
docs/n8n-competitor-web-setup.mddocs/planning-media-sync.md — sync de Planning de Pauta (Drive CSV → planning_media) vía Apps Scriptdocs/generador-contenido.md — Generador de contenido orgánico (/contenido): flujo actual (Ideogram + OpenAI, estética fija), trade-off, qué NO funciona y próximo pasodocs/next-phases.md — roadmap fases 2/3/4