feat: al conectar Google, auto-emparejar Drive/GA4/GSC/Ads en fichas
Bootstrap unificado post-OAuth + botón Emparejar todo. Meta discover también intenta FB/IG pages con el token system user (API 4V2030). Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -86,7 +86,11 @@ export default function AdsDiscoverPanel() {
|
||||
const parts = [];
|
||||
if (data.meta) {
|
||||
parts.push(
|
||||
`Meta: ${data.meta.linked}/${data.meta.accounts} enlazadas` +
|
||||
`Meta Ads: ${data.meta.linked}/${data.meta.accounts}` +
|
||||
(data.meta.pagesLinked
|
||||
? ` · FB ${data.meta.pagesLinked}`
|
||||
: '') +
|
||||
(data.meta.igLinked ? ` · IG ${data.meta.igLinked}` : '') +
|
||||
(data.meta.error ? ` (${data.meta.error})` : '')
|
||||
);
|
||||
}
|
||||
@@ -179,9 +183,9 @@ export default function AdsDiscoverPanel() {
|
||||
Meta Ads + Google Ads (agencia)
|
||||
</h3>
|
||||
<p className="text-xs text-gray-400 mt-0.5 max-w-xl">
|
||||
Lista cuentas accesibles y las enlaza a cada cliente por nombre.
|
||||
Google Ads ya puede emparejarse; Meta cuando tengas el token de
|
||||
App Review.
|
||||
Lista cuentas accesibles y las enlaza a cada ficha por nombre.
|
||||
Meta Ads + páginas FB/IG usan el token system user (API 4V2030 /
|
||||
App Review). Google Ads con el MCC de agencia.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -37,11 +37,14 @@ export default function DriveConnectPanel() {
|
||||
useEffect(() => {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
if (params.get('drive') === 'ok') {
|
||||
const boot = params.get('boot');
|
||||
setMsg(
|
||||
`Google conectado. Enlazadas ${params.get('linked') || 0} carpetas Drive` +
|
||||
(params.get('ambiguous') && params.get('ambiguous') !== '0'
|
||||
? ` · ${params.get('ambiguous')} ambiguas (revisa abajo)`
|
||||
: '')
|
||||
boot
|
||||
? `Google conectado · ${boot}`
|
||||
: `Google conectado. Enlazadas ${params.get('linked') || 0} carpetas Drive` +
|
||||
(params.get('ambiguous') && params.get('ambiguous') !== '0'
|
||||
? ` · ${params.get('ambiguous')} ambiguas`
|
||||
: '')
|
||||
);
|
||||
}
|
||||
if (params.get('drive') === 'error') {
|
||||
@@ -101,7 +104,8 @@ export default function DriveConnectPanel() {
|
||||
Una sola conexión con{' '}
|
||||
<strong className="text-gray-800">marketing@4ventures.es</strong>.
|
||||
En el consentimiento acepta Drive, Analytics y Search Console.
|
||||
Sirve para carpetas Drive y para Orgánico (panel de abajo).
|
||||
Sirve para carpetas Drive, GA4, GSC y (con MCC) Google Ads.
|
||||
Tras el consentimiento se emparejan solos a las fichas.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -54,40 +54,36 @@ export default function GoogleOrganicPanel() {
|
||||
useEffect(() => {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
if (params.get('drive') === 'ok') {
|
||||
const boot = params.get('boot');
|
||||
setMsg(
|
||||
'Google reconectado. Pulsa «Descubrir y emparejar» para GA4/GSC.'
|
||||
boot
|
||||
? `Google reconectado · ${boot}`
|
||||
: 'Google reconectado. Emparejando fichas…'
|
||||
);
|
||||
}
|
||||
void loadStatus();
|
||||
}, []);
|
||||
|
||||
async function discover(apply: boolean) {
|
||||
async function bootstrapAll() {
|
||||
setBusy('match');
|
||||
setErr(null);
|
||||
const res = await fetch('/api/admin/google/organic', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ action: 'match', apply }),
|
||||
body: JSON.stringify({ action: 'bootstrap', days: 28 }),
|
||||
});
|
||||
const data = await res.json();
|
||||
if (!res.ok) {
|
||||
setBusy(null);
|
||||
setErr(data.error || 'Discover falló');
|
||||
setBusy(null);
|
||||
if (!res.ok && !data.message) {
|
||||
setErr(data.error || 'Emparejado falló');
|
||||
return;
|
||||
}
|
||||
setMatches(data.matches || []);
|
||||
setGa4Count(data.ga4Count || 0);
|
||||
setGscCount(data.gscCount || 0);
|
||||
setMsg(
|
||||
apply
|
||||
? `Emparejado · GA4 ${data.applied?.ga4 || 0} · GSC ${data.applied?.gsc || 0}. Actualizando datos…`
|
||||
: `Vistas ${data.ga4Count} props GA4 · ${data.gscCount} sites GSC (sin escribir)`
|
||||
);
|
||||
if (apply) {
|
||||
await syncAll();
|
||||
} else {
|
||||
setBusy(null);
|
||||
setMsg(data.message || 'Emparejado listo');
|
||||
if (data.organic) {
|
||||
setGa4Count(data.organic.ga4Visible || 0);
|
||||
setGscCount(data.organic.gscVisible || 0);
|
||||
}
|
||||
await loadStatus();
|
||||
}
|
||||
|
||||
async function syncAll() {
|
||||
@@ -109,7 +105,7 @@ export default function GoogleOrganicPanel() {
|
||||
0
|
||||
);
|
||||
setMsg(
|
||||
`Datos al día · ${data.synced || 0} clientes · ${wrote} días en Orgánico (se actualiza solo al abrir la vista y cada noche)`
|
||||
`Datos al día · ${data.synced || 0} clientes · ${wrote} días en Orgánico`
|
||||
);
|
||||
}
|
||||
|
||||
@@ -128,12 +124,12 @@ export default function GoogleOrganicPanel() {
|
||||
<Leaf className="w-5 h-5 text-lime-600 mt-0.5 shrink-0" />
|
||||
<div>
|
||||
<h3 className="text-sm font-semibold text-gray-900">
|
||||
2 · Orgánico (GA4 + Search Console)
|
||||
2 · Orgánico + emparejado Google
|
||||
</h3>
|
||||
<p className="text-xs text-gray-600 mt-0.5 max-w-xl">
|
||||
No es otra cuenta: usa la conexión del panel 1. Aquí solo
|
||||
emparejas propiedades/sites a cada cliente y actualizas datos.
|
||||
Luego se mantienen al día solos.
|
||||
Al conectar Google (panel 1) se rellenan solos Drive, GA4, GSC y
|
||||
Google Ads en cada ficha. Aquí puedes repetir el emparejado y
|
||||
actualizar Orgánico.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -141,7 +137,7 @@ export default function GoogleOrganicPanel() {
|
||||
<button
|
||||
type="button"
|
||||
disabled={!connected || busy !== null}
|
||||
onClick={() => void discover(true)}
|
||||
onClick={() => void bootstrapAll()}
|
||||
className="inline-flex items-center gap-1.5 text-xs font-semibold px-3 py-1.5 rounded-lg border border-gray-200 text-gray-800 hover:bg-gray-50 disabled:opacity-40"
|
||||
>
|
||||
{busy === 'match' ? (
|
||||
@@ -149,7 +145,7 @@ export default function GoogleOrganicPanel() {
|
||||
) : (
|
||||
<Search className="w-3.5 h-3.5" />
|
||||
)}
|
||||
Descubrir y emparejar
|
||||
Emparejar todo Google
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
|
||||
Reference in New Issue
Block a user