feat: Social con gráficas diarias por periodo y token Meta 4V2030

Series IG/FB (alcance, impresiones, clics, engagement, fans) según el rango del header; Meta y Google quedan como tokens separados.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
4Ventures
2026-09-24 17:30:44 +02:00
parent 224f060e9d
commit 5e5fc87710
4 changed files with 707 additions and 257 deletions

View File

@@ -31,21 +31,35 @@ export function googleAdsConfigured(): boolean {
);
}
/** Prefer token saved in agency_oauth (pasted after App Review), else env. */
/** Prefer env META_ACCESS_TOKEN (Dokploy / 4V2030), then agency_oauth paste. */
export async function resolveMetaAccessToken(): Promise<string | null> {
const tokens = await resolveMetaAccessTokens();
return tokens[0] || null;
}
/** All candidate Meta tokens (env first — production 4V2030 system user). */
export async function resolveMetaAccessTokens(): Promise<string[]> {
const out: string[] = [];
const env = process.env.META_ACCESS_TOKEN?.trim();
if (env) out.push(env);
const headers = serviceHeaders();
if (headers) {
const r = await fetch(
`${supabaseUrl()}/rest/v1/agency_oauth?provider=eq.${META_OAUTH_PROVIDER}&select=access_token`,
{ headers, cache: 'no-store' }
);
if (r.ok) {
const rows = (await r.json()) as { access_token?: string }[];
const t = rows[0]?.access_token?.trim();
if (t) return t;
try {
const r = await fetch(
`${supabaseUrl()}/rest/v1/agency_oauth?provider=eq.${META_OAUTH_PROVIDER}&select=access_token`,
{ headers, cache: 'no-store' }
);
if (r.ok) {
const rows = (await r.json()) as { access_token?: string }[];
const t = rows[0]?.access_token?.trim();
if (t && !out.includes(t)) out.push(t);
}
} catch {
/* ignore */
}
}
return process.env.META_ACCESS_TOKEN?.trim() || null;
return out;
}
export async function metaConfigured(): Promise<boolean> {