feat: menú Ads + Social; Drive por defecto en activos

Renombra Ad Tracker a Ads, añade módulo Social (perfiles RRSS)
y abre Biblioteca en pestaña Drive. Aclara histórico GSC en Orgánico.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
4Ventures
2026-09-24 13:44:56 +02:00
parent 7ed6e641cb
commit ba05626fed
9 changed files with 280 additions and 10 deletions

View File

@@ -0,0 +1,51 @@
import { NextResponse } from 'next/server';
import { createClient } from '@/lib/supabase/server';
import { resolveActiveTenant, restGet } from '@/lib/tenant-context';
import { isAgencyRole, userRole } from '@/lib/agency';
export const dynamic = 'force-dynamic';
/** GET — social channel integrations for active tenant. */
export async function GET() {
const supabase = await createClient();
const {
data: { user },
} = await supabase.auth.getUser();
if (!user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
const ctx = await resolveActiveTenant(user);
if (!ctx.tenantId || ctx.portfolioWide) {
return NextResponse.json({
integrations: [],
warning: 'Selecciona un cliente en Contexto',
});
}
const agency = isAgencyRole(userRole(user));
const providers = 'instagram,facebook,tiktok,linkedin';
if (agency && ctx.viaView) {
const tid = encodeURIComponent(ctx.tenantId);
const r = await restGet(
`/rest/v1/integrations?tenant_id=eq.${tid}&provider=in.(${providers})&select=provider,status,external_account_id,label,last_sync_at,meta,error_message`
);
return NextResponse.json({
integrations: r.data || [],
warning: r.error || null,
});
}
const { data, error } = await supabase
.from('integrations')
.select(
'provider,status,external_account_id,label,last_sync_at,meta,error_message'
)
.in('provider', ['instagram', 'facebook', 'tiktok', 'linkedin']);
return NextResponse.json({
integrations: data || [],
warning: error?.message || null,
});
}

View File

@@ -34,7 +34,7 @@ export default async function AdTrackerPage() {
if (ctx.portfolioWide) {
return (
<p className="text-sm text-gray-500 border border-gray-200 rounded-xl p-6 bg-white">
Selecciona un cliente en Contexto para abrir su Ad Tracker (privado por
Selecciona un cliente en Contexto para abrir Ads (Meta / Google Ads por
tenant).
</p>
);

View File

@@ -0,0 +1,32 @@
import React from 'react';
import { createClient } from '@/lib/supabase/server';
import { redirect } from 'next/navigation';
import { resolveActiveTenant } from '@/lib/tenant-context';
import { isAgencyRole, userRole } from '@/lib/agency';
import SocialDashboard from '@/components/SocialDashboard';
export const dynamic = 'force-dynamic';
export default async function SocialPage() {
const supabase = await createClient();
const {
data: { user },
} = await supabase.auth.getUser();
if (!user) redirect('/login');
const ctx = await resolveActiveTenant(user);
const agency = isAgencyRole(userRole(user));
if (ctx.portfolioWide) {
return (
<p className="text-sm text-gray-500 border border-gray-200 rounded-xl p-6 bg-white">
Selecciona un cliente en Contexto para abrir Social (perfiles RRSS por
tenant).
</p>
);
}
return (
<SocialDashboard tenantLabel={ctx.label} isAgency={agency} />
);
}