fix: vídeos en Social y notas Superhuman por día desde Drive

Los docs de seguimiento son Google Docs embebidos: se exportan, se parten por fecha y se acumulan. Social renderiza VIDEO/REELS y trae vídeos de página.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
4Ventures
2026-09-24 17:09:49 +02:00
parent 06024cf473
commit 751673c341
6 changed files with 419 additions and 107 deletions

View File

@@ -14,7 +14,6 @@ import {
Send,
} from 'lucide-react';
import { PageHeader } from '@/components/ui';
import { useDateRange } from '@/components/DateRangeContext';
type Entry = {
id: string;
@@ -49,14 +48,7 @@ function typeTone(t: string) {
return 'bg-violet-50 text-violet-800 border-violet-200';
}
function inRange(isoDate: string | undefined | null, since: string, until: string) {
if (!isoDate) return true; // keep undated notes visible
const d = isoDate.slice(0, 10);
return d >= since && d <= until;
}
export default function SeguimientoBoard() {
const { since, until, label: rangeLabel } = useDateRange();
const [data, setData] = useState<Payload | null>(null);
const [loading, setLoading] = useState(true);
const [err, setErr] = useState<string | null>(null);
@@ -90,13 +82,14 @@ export default function SeguimientoBoard() {
const filteredEntries = useMemo(() => {
const entries = data?.entries || [];
const q = query.trim().toLowerCase();
// Histórico completo (más reciente → más antiguo). El periodo del header
// no oculta notas: el doc Superhuman/Drive acumula todos los días.
return entries.filter((e) => {
if (!inRange(e.occurred_at, since, until)) return false;
if (!q) return true;
const blob = `${e.title} ${e.summary || ''} ${e.body_md || ''}`.toLowerCase();
return blob.includes(q);
});
}, [data, query, since, until]);
}, [data, query]);
async function saveNote() {
if (!noteBody.trim()) return;
@@ -136,7 +129,7 @@ export default function SeguimientoBoard() {
body: JSON.stringify({
tenantId: data.tenantId,
exportBodies: true,
maxEntries: 80,
maxEntries: 120,
}),
});
const json = await res.json().catch(() => ({}));
@@ -195,7 +188,7 @@ export default function SeguimientoBoard() {
<PageHeader
eyebrow="Operación"
title="Seguimiento"
description={`${data.label} · notas e histórico del equipo (periodo ${rangeLabel}). Ads y KPIs están en Métricas.`}
description={`${data.label} · histórico de notas (más reciente primero). El periodo del header no filtra este timeline.`}
/>
<section className="rounded-2xl border border-gray-200 bg-white p-5 space-y-3">

View File

@@ -63,6 +63,7 @@ type SocialPost = {
platform: 'facebook' | 'instagram';
message?: string;
mediaUrl?: string | null;
thumbnailUrl?: string | null;
permalink?: string | null;
createdAt?: string | null;
likes: number;
@@ -73,6 +74,13 @@ type SocialPost = {
mediaType?: string | null;
};
function isVideoPost(p: SocialPost): boolean {
const t = (p.mediaType || '').toUpperCase();
if (t.includes('VIDEO') || t === 'REELS' || t === 'REEL') return true;
const url = p.mediaUrl || '';
return /\.mp4($|\?)/i.test(url) || /video/i.test(url);
}
const CHANNELS: {
id: string;
label: string;
@@ -307,10 +315,19 @@ export default function SocialDashboard({
className="rounded-2xl border border-gray-200 bg-white overflow-hidden flex flex-col"
>
<div className="relative aspect-square bg-gray-100">
{p.mediaUrl ? (
{p.mediaUrl && isVideoPost(p) ? (
<video
src={p.mediaUrl}
poster={p.thumbnailUrl || undefined}
className="absolute inset-0 w-full h-full object-cover"
controls
playsInline
preload="metadata"
/>
) : p.mediaUrl || p.thumbnailUrl ? (
// eslint-disable-next-line @next/next/no-img-element
<img
src={p.mediaUrl}
src={p.thumbnailUrl || p.mediaUrl || ''}
alt=""
className="absolute inset-0 w-full h-full object-cover"
/>