feat: periodo global, métricas negocio, seguimiento notas, Drive/Social/Orgánico

Selector de fechas en header (con Personalizado) compartido en el dashboard; Métricas con Ventas/CAC/ROAS CRM, cruce canal y gráficas diarias; Seguimiento solo notas + sync Superhuman; Activos con navegación Drive; Social con métricas IG/FB; Orgánico con prioridades SEO/GEO expertas.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
4Ventures
2026-09-24 15:45:30 +02:00
parent eb1622c88c
commit 3425364b07
15 changed files with 1067 additions and 1011 deletions

View File

@@ -10,7 +10,9 @@ import {
export const dynamic = 'force-dynamic';
/** GET — list Drive files for active tenant folder (or ?download=fileId). */
const FOLDER_MIME = 'application/vnd.google-apps.folder';
/** GET — list Drive files for tenant folder (browse with ?folderId=). */
export async function GET(req: NextRequest) {
const supabase = await createClient();
const {
@@ -28,11 +30,7 @@ export async function GET(req: NextRequest) {
);
}
// Clients only see own tenant; agency via view cookie already in ctx
const agency = isAgencyRole(userRole(user));
if (!agency) {
// resolveActiveTenant already scopes to home tenant
}
void isAgencyRole(userRole(user));
const downloadId = req.nextUrl.searchParams.get('download')?.trim();
if (downloadId) {
@@ -53,30 +51,47 @@ export async function GET(req: NextRequest) {
files: [],
linked: false,
warning:
'Sin carpeta Drive enlazada. Conecta marketing@ en Agencia y ejecuta auto-match.',
'Sin carpeta Drive enlazada. Conecta marketing@ en Agencia y ejecuta auto-match bajo «Clientes».',
});
}
const requested = req.nextUrl.searchParams.get('folderId')?.trim();
const folderId = requested || link.folderId;
try {
const files = await listFilesInFolder(link.folderId);
return NextResponse.json({
linked: true,
folderId: link.folderId,
folderName: link.folderName,
files: files.map((f) => ({
const files = await listFilesInFolder(folderId);
const mapped = files
.map((f) => ({
id: f.id,
name: f.name,
mimeType: f.mimeType,
bytes: f.size ? Number(f.size) : null,
modifiedTime: f.modifiedTime,
webViewLink: f.webViewLink,
isFolder: f.mimeType === FOLDER_MIME,
source: 'drive' as const,
})),
}))
.sort((a, b) => {
if (a.isFolder !== b.isFolder) return a.isFolder ? -1 : 1;
return a.name.localeCompare(b.name, 'es');
});
return NextResponse.json({
linked: true,
rootFolderId: link.folderId,
folderId,
folderName: link.folderName,
driveOpenUrl: `https://drive.google.com/drive/folders/${encodeURIComponent(folderId)}`,
embedPossible: false,
embedHint:
'Réplica vía API (misma carpeta Clientes del cliente). Embed iframe de Drive exige carpeta pública; usamos listado nativo + Abrir en Drive.',
files: mapped,
});
} catch (e) {
return NextResponse.json({
linked: true,
folderId: link.folderId,
rootFolderId: link.folderId,
folderId,
folderName: link.folderName,
files: [],
warning: e instanceof Error ? e.message : 'Drive error',