import { NextRequest, NextResponse } from 'next/server'; import { assertCronAuth } from '@/lib/cron-auth'; import { syncAllConnectedOrganic } from '@/lib/google-organic'; import { getAgencyOAuthStatus } from '@/lib/google-drive'; import { syncAdsForTenants } from '@/lib/ads-sync'; import { syncAllSuperhumanTenants, superhumanConfigured, } from '@/lib/superhuman-docs'; import { syncAllClientifyTenants } from '@/lib/clientify-sync'; import { syncAllGhlTenants } from '@/lib/gohighlevel-sync'; import { syncAllShopifyTenants } from '@/lib/shopify-sync'; import { runAgencyHealthAlerts } from '@/lib/agency-health'; import { processDuePipelinePublishes } from '@/lib/pipeline-publish-worker'; export const dynamic = 'force-dynamic'; export const maxDuration = 300; /** * Master daily job: organic + ads + superhuman. * Dokploy (daily ~06:00 UTC): * curl -sS -H "Authorization: Bearer $CRON_SECRET" \ * "https://app.4ventures.es/api/cron/daily-sync" */ export async function GET(req: NextRequest) { const denied = assertCronAuth(req); if (denied) return denied; const days = Math.min( 28, Math.max(3, Number(req.nextUrl.searchParams.get('days')) || 7) ); const out: Record = { ok: true, days, at: new Date().toISOString(), }; // 1) Organic try { const oauth = await getAgencyOAuthStatus(); if (!oauth.connected) { out.organic = { ok: false, warning: 'Google agencia no conectado' }; } else { out.organic = { ok: true, ...(await syncAllConnectedOrganic({ days })), }; } } catch (e) { out.organic = { ok: false, error: e instanceof Error ? e.message : 'organic failed', }; } // 2) Ads try { const ads = await syncAdsForTenants({ days }); out.ads = { ok: true, synced: ads.results.filter((r) => r.metaRows + r.googleRows > 0).length, withErrors: ads.results.filter((r) => r.errors.length).length, results: ads.results, }; } catch (e) { out.ads = { ok: false, error: e instanceof Error ? e.message : 'ads failed', }; } // 3) Superhuman try { if (!superhumanConfigured()) { out.superhuman = { ok: false, warning: 'SUPERHUMAN_DOCS_API_KEY no configurado', }; } else { out.superhuman = { ok: true, ...(await syncAllSuperhumanTenants({ maxEntries: 80, exportBodies: true, })), }; } } catch (e) { out.superhuman = { ok: false, error: e instanceof Error ? e.message : 'superhuman failed', }; } // 4) CRM + Shopify (prefer dedicated crons; safety net here) try { const lookback = Math.max(days * 2, 30); const [clientify, gohighlevel, shopify] = await Promise.all([ syncAllClientifyTenants({ lookbackDays: lookback }), syncAllGhlTenants({ lookbackDays: lookback }), syncAllShopifyTenants({ lookbackDays: lookback }), ]); const results = [ ...clientify.results, ...gohighlevel.results, ...shopify.results.map((r) => ({ tenantId: r.tenantId, tenantSlug: r.tenantSlug, leads: 0, deals: r.orders, error: r.error, })), ]; out.crm = { ok: true, synced: results.filter((r) => !r.error).length, withErrors: results.filter((r) => r.error).length, results, }; } catch (e) { out.crm = { ok: false, error: e instanceof Error ? e.message : 'crm failed', }; } // 5) Pipeline Meta publish (safety net; prefer dedicated */10 cron) try { out.pipelinePublish = await processDuePipelinePublishes({ limit: 5 }); } catch (e) { out.pipelinePublish = { ok: false, error: e instanceof Error ? e.message : 'pipeline-publish failed', }; } // 6) Health alerts (stale sync / ecommerce drop) try { out.healthAlerts = await runAgencyHealthAlerts(); } catch (e) { out.healthAlerts = { ok: false, error: e instanceof Error ? e.message : 'health-alerts failed', }; } const failed = ['organic', 'ads', 'superhuman', 'crm'].some( (k) => (out[k] as { ok?: boolean } | undefined)?.ok === false ); return NextResponse.json(out, { status: failed ? 207 : 200 }); } export async function POST(req: NextRequest) { return GET(req); }