diff --git a/src/app/api/admin/superhuman/sync/route.ts b/src/app/api/admin/superhuman/sync/route.ts index 6655a3d..630d69f 100644 --- a/src/app/api/admin/superhuman/sync/route.ts +++ b/src/app/api/admin/superhuman/sync/route.ts @@ -99,7 +99,7 @@ export async function POST(req: NextRequest) { ? String(body.tenantId) : undefined; const exportBodies = body.exportBodies !== false; - const maxEntries = Math.min(Number(body.maxEntries) || 24, 40); + const maxEntries = Math.min(Number(body.maxEntries) || 80, 150); try { const docs = await listDocs(); @@ -175,6 +175,7 @@ export async function POST(req: NextRequest) { try { const rows = await buildSeguimientoRowsForDoc({ tenantId: tenant.id, + tenantName: tenant.name, docId, maxEntries, exportBodies, diff --git a/src/app/api/seguimiento/route.ts b/src/app/api/seguimiento/route.ts index 60eb984..205c863 100644 --- a/src/app/api/seguimiento/route.ts +++ b/src/app/api/seguimiento/route.ts @@ -57,7 +57,7 @@ export async function GET() { if (agency && ctx.viaView) { const tid = encodeURIComponent(tenantId); const e = await restGet( - `/rest/v1/seguimiento_entries?tenant_id=eq.${tid}&select=*&order=occurred_at.desc.nullslast&limit=200` + `/rest/v1/seguimiento_entries?tenant_id=eq.${tid}&select=*&order=occurred_at.desc.nullslast&limit=300` ); entries = e.data || []; } else { @@ -65,7 +65,7 @@ export async function GET() { .from('seguimiento_entries') .select('*') .order('occurred_at', { ascending: false, nullsFirst: false }) - .limit(200); + .limit(300); entries = (e as SeguimientoEntry[]) || []; } diff --git a/src/app/api/social/route.ts b/src/app/api/social/route.ts index e51a7e4..401afaf 100644 --- a/src/app/api/social/route.ts +++ b/src/app/api/social/route.ts @@ -23,6 +23,8 @@ type SocialPost = { platform: 'facebook' | 'instagram'; message?: string; mediaUrl?: string | null; + /** Poster / still for VIDEO / REELS when mediaUrl is a video. */ + thumbnailUrl?: string | null; permalink?: string | null; createdAt?: string | null; likes: number; @@ -33,6 +35,17 @@ type SocialPost = { mediaType?: string | null; }; +function isVideoType(t?: string | null): boolean { + const s = (t || '').toUpperCase(); + return ( + s === 'VIDEO' || + s === 'REELS' || + s === 'REEL' || + s.includes('VIDEO') || + s === 'ANIMATED_IMAGE_VIDEO' + ); +} + function insightValue( data: { name?: string; values?: { value?: number }[] }[] | undefined, name: string @@ -70,30 +83,75 @@ async function fetchPageBundle(token: string, pageId: string) { } const postsRes = await fetch( - `${META}/${encodeURIComponent(pageId)}/posts?fields=id,message,created_time,full_picture,permalink_url,shares,likes.summary(true),comments.summary(true)&limit=12&access_token=${encodeURIComponent(token)}`, + `${META}/${encodeURIComponent(pageId)}/posts?fields=id,message,created_time,full_picture,permalink_url,status_type,shares,likes.summary(true),comments.summary(true),attachments{media_type,type,url,media,subattachments}&limit=16&access_token=${encodeURIComponent(token)}`, { cache: 'no-store' } ); const posts: SocialPost[] = []; + const seen = new Set(); if (postsRes.ok) { const j = await postsRes.json(); for (const p of j.data || []) { + const att = p.attachments?.data?.[0]; + const attType = String(att?.media_type || att?.type || '').toLowerCase(); + const videoSrc = + att?.media?.source || + att?.url || + att?.subattachments?.data?.[0]?.media?.source || + null; + const isVideo = + attType.includes('video') || + String(p.status_type || '').includes('video') || + Boolean(videoSrc && /\.mp4|video/i.test(String(videoSrc))); + const thumb = + p.full_picture || + att?.media?.image?.src || + att?.subattachments?.data?.[0]?.media?.image?.src || + null; posts.push({ id: p.id, platform: 'facebook', message: p.message || '', - mediaUrl: p.full_picture || null, + mediaUrl: isVideo ? videoSrc || thumb : thumb, + thumbnailUrl: thumb, permalink: p.permalink_url || null, createdAt: p.created_time || null, likes: Number(p.likes?.summary?.total_count || 0), comments: Number(p.comments?.summary?.total_count || 0), shares: Number(p.shares?.count || 0), - mediaType: p.full_picture ? 'image' : 'status', + mediaType: isVideo ? 'VIDEO' : thumb ? 'IMAGE' : 'status', }); + seen.add(p.id); } } else { profile.postsError = (await postsRes.text()).slice(0, 160); } + // Native page videos / reels often missing from /posts + const videosRes = await fetch( + `${META}/${encodeURIComponent(pageId)}/videos?fields=id,description,created_time,source,permalink_url,picture,length,title&limit=10&access_token=${encodeURIComponent(token)}`, + { cache: 'no-store' } + ); + if (videosRes.ok) { + const j = await videosRes.json(); + for (const v of j.data || []) { + if (!v?.id || seen.has(v.id)) continue; + posts.push({ + id: v.id, + platform: 'facebook', + message: v.description || v.title || '', + mediaUrl: v.source || v.picture || null, + thumbnailUrl: v.picture || null, + permalink: v.permalink_url || null, + createdAt: v.created_time || null, + likes: 0, + comments: 0, + shares: 0, + mediaType: 'VIDEO', + }); + seen.add(v.id); + } + } + return { ...profile, pageInsights, posts }; } @@ -137,18 +195,29 @@ async function fetchIgBundle(token: string, igId: string) { } const mediaRes = await fetch( - `${META}/${encodeURIComponent(igId)}/media?fields=id,caption,media_type,media_url,thumbnail_url,permalink,timestamp,like_count,comments_count&limit=12&access_token=${encodeURIComponent(token)}`, + `${META}/${encodeURIComponent(igId)}/media?fields=id,caption,media_type,media_url,thumbnail_url,permalink,timestamp,like_count,comments_count&limit=16&access_token=${encodeURIComponent(token)}`, { cache: 'no-store' } ); const posts: SocialPost[] = []; if (mediaRes.ok) { const j = await mediaRes.json(); for (const m of j.data || []) { + const mediaType = m.media_type || null; + const video = isVideoType(mediaType); + // VIDEO/REELS: keep media_url for