Mark GEO as coming soon and slim the SEO menu to live data.
Hide duplicate GSC/GA4 views and the manual day-load CTA so Orgánico matches Search Console, Analytics, and Métricas. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -171,7 +171,9 @@ export default function OrganicoDashboard({
|
|||||||
}, [load]);
|
}, [load]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const first = ORGANICO_SECTIONS.find((s) => s.pillar === pillar);
|
const first = ORGANICO_SECTIONS.find(
|
||||||
|
(s) => s.pillar === pillar && !s.soon
|
||||||
|
);
|
||||||
if (first) setSectionId(first.id);
|
if (first) setSectionId(first.id);
|
||||||
}, [pillar]);
|
}, [pillar]);
|
||||||
|
|
||||||
@@ -214,7 +216,9 @@ export default function OrganicoDashboard({
|
|||||||
const maxSessions = Math.max(1, ...seoDaily.map((d) => d.sessions));
|
const maxSessions = Math.max(1, ...seoDaily.map((d) => d.sessions));
|
||||||
|
|
||||||
const section = useMemo(
|
const section = useMemo(
|
||||||
() => ORGANICO_SECTIONS.find((s) => s.id === sectionId)!,
|
() =>
|
||||||
|
ORGANICO_SECTIONS.find((s) => s.id === sectionId) ||
|
||||||
|
ORGANICO_SECTIONS.find((s) => s.pillar === 'seo' && !s.soon)!,
|
||||||
[sectionId]
|
[sectionId]
|
||||||
);
|
);
|
||||||
const navGroups = useMemo(() => groupsForPillar(pillar), [pillar]);
|
const navGroups = useMemo(() => groupsForPillar(pillar), [pillar]);
|
||||||
@@ -406,11 +410,11 @@ export default function OrganicoDashboard({
|
|||||||
<PageHeader
|
<PageHeader
|
||||||
eyebrow="Crecimiento"
|
eyebrow="Crecimiento"
|
||||||
title="Orgánico"
|
title="Orgánico"
|
||||||
description="SEO + GEO (visibilidad en IA). Elige un cliente en Contexto."
|
description="SEO con Search Console y GA4. Elige un cliente en Contexto."
|
||||||
actions={<SyncNowButton forceModule="organico" />}
|
actions={<SyncNowButton forceModule="organico" />}
|
||||||
/>
|
/>
|
||||||
<p className="text-sm text-gray-600 border border-gray-200 rounded-xl px-4 py-8 text-center bg-gray-50">
|
<p className="text-sm text-gray-600 border border-gray-200 rounded-xl px-4 py-8 text-center bg-gray-50">
|
||||||
Selecciona un cliente para abrir su suite SEO / GEO.
|
Selecciona un cliente para ver su SEO.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -429,7 +433,7 @@ export default function OrganicoDashboard({
|
|||||||
<PageHeader
|
<PageHeader
|
||||||
eyebrow="Crecimiento · Suite"
|
eyebrow="Crecimiento · Suite"
|
||||||
title="Orgánico"
|
title="Orgánico"
|
||||||
description={`${label} · Observa posicionamiento en búsqueda e IA, competencia y oportunidades de contenido.`}
|
description={`${label} · Search Console y Analytics. GEO (visibilidad en IA) llega después.`}
|
||||||
actions={
|
actions={
|
||||||
<div className="flex flex-wrap items-center gap-2">
|
<div className="flex flex-wrap items-center gap-2">
|
||||||
<SyncNowButton forceModule="organico" />
|
<SyncNowButton forceModule="organico" />
|
||||||
@@ -438,20 +442,10 @@ export default function OrganicoDashboard({
|
|||||||
onChange={(p) => setPillar(p)}
|
onChange={(p) => setPillar(p)}
|
||||||
size="sm"
|
size="sm"
|
||||||
options={[
|
options={[
|
||||||
{ id: 'geo', label: 'GEO · IA' },
|
|
||||||
{ id: 'seo', label: 'SEO' },
|
{ id: 'seo', label: 'SEO' },
|
||||||
|
{ id: 'geo', label: 'GEO · IA' },
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
{isAgency ? (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => setShowCapture((v) => !v)}
|
|
||||||
className="inline-flex items-center gap-1.5 text-xs font-semibold px-3 py-1.5 rounded-lg bg-brand-500 text-white hover:bg-brand-600"
|
|
||||||
>
|
|
||||||
<Plus className="w-3.5 h-3.5" />
|
|
||||||
{pillar === 'geo' ? 'Medición GEO' : 'Cargar día SEO'}
|
|
||||||
</button>
|
|
||||||
) : null}
|
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@@ -473,15 +467,6 @@ export default function OrganicoDashboard({
|
|||||||
{[
|
{[
|
||||||
{ title: 'Search Console', row: gsc },
|
{ title: 'Search Console', row: gsc },
|
||||||
{ title: 'GA4', row: ga4 },
|
{ title: 'GA4', row: ga4 },
|
||||||
{
|
|
||||||
title: 'GEO / IA',
|
|
||||||
row: latestGeo
|
|
||||||
? {
|
|
||||||
status: 'connected',
|
|
||||||
external_account_id: latestGeo.label || 'Medición activa',
|
|
||||||
}
|
|
||||||
: { status: 'disconnected', external_account_id: null },
|
|
||||||
},
|
|
||||||
].map((c) => (
|
].map((c) => (
|
||||||
<div
|
<div
|
||||||
key={c.title}
|
key={c.title}
|
||||||
@@ -498,7 +483,7 @@ export default function OrganicoDashboard({
|
|||||||
<p className="text-sm font-medium text-gray-900 mt-1 truncate">
|
<p className="text-sm font-medium text-gray-900 mt-1 truncate">
|
||||||
{c.row?.external_account_id || 'Sin vincular'}
|
{c.row?.external_account_id || 'Sin vincular'}
|
||||||
</p>
|
</p>
|
||||||
{isAgency && tenantId && c.title !== 'GEO / IA' ? (
|
{isAgency && tenantId ? (
|
||||||
<Link
|
<Link
|
||||||
href={`/dashboard/agencia/cliente/${tenantId}`}
|
href={`/dashboard/agencia/cliente/${tenantId}`}
|
||||||
className="inline-flex items-center gap-1 text-[11px] font-semibold text-brand-700 mt-1.5 hover:underline"
|
className="inline-flex items-center gap-1 text-[11px] font-semibold text-brand-700 mt-1.5 hover:underline"
|
||||||
@@ -508,10 +493,35 @@ export default function OrganicoDashboard({
|
|||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
<div className="rounded-xl border border-dashed border-gray-300 bg-gray-50 px-4 py-3">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<p className="text-xs font-semibold text-gray-700">GEO / IA</p>
|
||||||
|
<span className="text-[10px] font-bold uppercase px-2 py-0.5 rounded-full border border-amber-200 bg-amber-50 text-amber-800">
|
||||||
|
Próximamente
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<p className="text-sm text-gray-600 mt-1">
|
||||||
|
Visibilidad de marca en ChatGPT, Gemini y AI Overviews.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Capture forms */}
|
{pillar === 'geo' ? (
|
||||||
{isAgency && showCapture && pillar === 'seo' && (
|
<div className="rounded-2xl border border-gray-200 bg-white px-6 py-14 text-center shadow-sm">
|
||||||
|
<p className="text-[11px] font-bold uppercase tracking-wider text-amber-700">
|
||||||
|
Próximamente disponible
|
||||||
|
</p>
|
||||||
|
<h2 className="text-xl font-bold text-gray-900 mt-2">GEO · visibilidad en IA</h2>
|
||||||
|
<p className="text-sm text-gray-600 mt-2 max-w-lg mx-auto">
|
||||||
|
Aquí mediremos cómo aparece la marca en respuestas de ChatGPT,
|
||||||
|
Gemini y AI Overviews (Share of Model, citas, competidores). SEO
|
||||||
|
(Google) sigue en la pestaña SEO, con datos de Search Console y
|
||||||
|
Analytics.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{isAgency && showCapture && (
|
||||||
<CaptureSeo
|
<CaptureSeo
|
||||||
form={seoForm}
|
form={seoForm}
|
||||||
setForm={setSeoForm}
|
setForm={setSeoForm}
|
||||||
@@ -519,28 +529,12 @@ export default function OrganicoDashboard({
|
|||||||
onSave={() => void saveOrganicDay()}
|
onSave={() => void saveOrganicDay()}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{isAgency && showCapture && pillar === 'geo' && (
|
|
||||||
<CaptureGeo
|
|
||||||
form={geoForm}
|
|
||||||
setForm={setGeoForm}
|
|
||||||
saving={saving}
|
|
||||||
onSave={() => void saveGeo()}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Shell: nav + content */}
|
{/* Shell: nav + content */}
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-[240px_minmax(0,1fr)] gap-4 items-start">
|
<div className="grid grid-cols-1 lg:grid-cols-[240px_minmax(0,1fr)] gap-4 items-start">
|
||||||
<aside className="rounded-2xl border border-gray-200 bg-white p-3 shadow-sm lg:sticky lg:top-20 max-h-[calc(100vh-7rem)] overflow-y-auto">
|
<aside className="rounded-2xl border border-gray-200 bg-white p-3 shadow-sm lg:sticky lg:top-20 max-h-[calc(100vh-7rem)] overflow-y-auto">
|
||||||
<p className="px-2 pb-2 text-[10px] font-bold uppercase tracking-wider text-gray-500 flex items-center gap-1.5">
|
<p className="px-2 pb-2 text-[10px] font-bold uppercase tracking-wider text-gray-500 flex items-center gap-1.5">
|
||||||
{pillar === 'geo' ? (
|
<Leaf className="w-3.5 h-3.5 text-lime-600" /> Suite SEO
|
||||||
<>
|
|
||||||
<Bot className="w-3.5 h-3.5 text-brand-600" /> Análisis de IA
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<Leaf className="w-3.5 h-3.5 text-lime-600" /> Suite SEO
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</p>
|
</p>
|
||||||
{navGroups.map((g) => (
|
{navGroups.map((g) => (
|
||||||
<div key={g.group} className="mb-3">
|
<div key={g.group} className="mb-3">
|
||||||
@@ -948,7 +942,7 @@ export default function OrganicoDashboard({
|
|||||||
<section className="rounded-xl border border-gray-200 bg-white p-4 shadow-sm space-y-2">
|
<section className="rounded-xl border border-gray-200 bg-white p-4 shadow-sm space-y-2">
|
||||||
<h3 className="text-sm font-semibold text-gray-900 flex items-center gap-2">
|
<h3 className="text-sm font-semibold text-gray-900 flex items-center gap-2">
|
||||||
<Sparkles className="w-4 h-4 text-brand-600" />
|
<Sparkles className="w-4 h-4 text-brand-600" />
|
||||||
Prioridades claras · {pillar === 'geo' ? 'GEO' : 'SEO'}
|
Prioridades claras · SEO
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-[11px] text-gray-600">
|
<p className="text-[11px] text-gray-600">
|
||||||
Una por línea. Lo que hay que mejorar primero.
|
Una por línea. Lo que hay que mejorar primero.
|
||||||
@@ -989,10 +983,7 @@ export default function OrganicoDashboard({
|
|||||||
type="button"
|
type="button"
|
||||||
disabled={saving}
|
disabled={saving}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const def =
|
const def = DEFAULT_EXPERT_PRIORITIES.seo;
|
||||||
pillar === 'geo'
|
|
||||||
? DEFAULT_EXPERT_PRIORITIES.geo
|
|
||||||
: DEFAULT_EXPERT_PRIORITIES.seo;
|
|
||||||
setPrioritiesText(def.join('\n'));
|
setPrioritiesText(def.join('\n'));
|
||||||
}}
|
}}
|
||||||
className="ml-2 inline-flex items-center gap-1.5 text-xs font-semibold px-3 py-1.5 rounded-lg border border-gray-200 text-gray-700 hover:bg-gray-50"
|
className="ml-2 inline-flex items-center gap-1.5 text-xs font-semibold px-3 py-1.5 rounded-lg border border-gray-200 text-gray-700 hover:bg-gray-50"
|
||||||
@@ -1059,9 +1050,9 @@ export default function OrganicoDashboard({
|
|||||||
</p>
|
</p>
|
||||||
<p className="text-xs text-amber-800">
|
<p className="text-xs text-amber-800">
|
||||||
Competencia de dominio, keyword gap y backlinks no salen de
|
Competencia de dominio, keyword gap y backlinks no salen de
|
||||||
GSC/GA4. Hay que conectar Ahrefs/Semrush (o importar CSV) para
|
Search Console ni GA4. Cuando conectemos Ahrefs o Semrush, esta
|
||||||
alimentar este panel. Mientras tanto usa las secciones con datos
|
vista se activará. El SEO con datos reales está en Visión
|
||||||
reales: visión general, rankings, keywords y páginas.
|
general, Keywords y Páginas.
|
||||||
</p>
|
</p>
|
||||||
<ul className="space-y-1.5 pt-1">
|
<ul className="space-y-1.5 pt-1">
|
||||||
{section.actions.map((a) => (
|
{section.actions.map((a) => (
|
||||||
@@ -1100,6 +1091,8 @@ export default function OrganicoDashboard({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -439,13 +439,10 @@ export const ORGANICO_SECTIONS: OrganicoSection[] = [
|
|||||||
group: 'Rendimiento del sitio',
|
group: 'Rendimiento del sitio',
|
||||||
label: 'Rastreo de posición',
|
label: 'Rastreo de posición',
|
||||||
blurb:
|
blurb:
|
||||||
'Sí: posición media diaria en organic_daily (histórico nuestro) + ranking actual de queries GSC. No es un rank tracker de 10k KW tipo Semrush; es lo que GSC reporta para tus impresiones reales.',
|
'Cubierto en Palabras clave (posición GSC) y Visión general (tendencia diaria).',
|
||||||
actions: [
|
actions: [],
|
||||||
'Mirar tendencia de posición media',
|
|
||||||
'Atacar queries en pos. 4–15 (quick wins)',
|
|
||||||
'Ampliar cartera vía contenido',
|
|
||||||
],
|
|
||||||
live: 'seo-rankings',
|
live: 'seo-rankings',
|
||||||
|
soon: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'seo-organic',
|
id: 'seo-organic',
|
||||||
@@ -453,13 +450,10 @@ export const ORGANICO_SECTIONS: OrganicoSection[] = [
|
|||||||
group: 'Tráfico y páginas',
|
group: 'Tráfico y páginas',
|
||||||
label: 'Posicionamiento orgánico',
|
label: 'Posicionamiento orgánico',
|
||||||
blurb:
|
blurb:
|
||||||
'Evolución de clics e impresiones (visibilidad). Distinto de «Páginas principales».',
|
'Misma serie de clics/impresiones que Visión general. Se unifica ahí.',
|
||||||
actions: [
|
actions: [],
|
||||||
'Leer tendencia MoM de clics',
|
|
||||||
'Cruzar con campañas de contenido',
|
|
||||||
'Alertar caídas bruscas',
|
|
||||||
],
|
|
||||||
live: 'seo-trend',
|
live: 'seo-trend',
|
||||||
|
soon: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'seo-pages',
|
id: 'seo-pages',
|
||||||
@@ -495,13 +489,10 @@ export const ORGANICO_SECTIONS: OrganicoSection[] = [
|
|||||||
group: 'Keywords',
|
group: 'Keywords',
|
||||||
label: 'Organic Traffic Insights',
|
label: 'Organic Traffic Insights',
|
||||||
blurb:
|
blurb:
|
||||||
'Cruce GSC (páginas) + GA4 (sesiones orgánicas / landings) para ver qué rankea y qué convierte.',
|
'El cruce GSC + GA4 ya está en Visión general (top queries, páginas y landings).',
|
||||||
actions: [
|
actions: [],
|
||||||
'Comparar top pages GSC vs landings GA4',
|
|
||||||
'Detectar páginas con clics y poca conversión',
|
|
||||||
'Informar ROI orgánico',
|
|
||||||
],
|
|
||||||
live: 'seo-cross',
|
live: 'seo-cross',
|
||||||
|
soon: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'seo-competition',
|
id: 'seo-competition',
|
||||||
@@ -540,7 +531,10 @@ export function sectionsForPillar(pillar: OrganicoPillar): OrganicoSection[] {
|
|||||||
export function groupsForPillar(
|
export function groupsForPillar(
|
||||||
pillar: OrganicoPillar
|
pillar: OrganicoPillar
|
||||||
): { group: string; items: OrganicoSection[] }[] {
|
): { group: string; items: OrganicoSection[] }[] {
|
||||||
const list = sectionsForPillar(pillar);
|
if (pillar === 'geo') return [];
|
||||||
|
const list = sectionsForPillar('seo').filter(
|
||||||
|
(s) => !s.soon || s.id === 'seo-competition'
|
||||||
|
);
|
||||||
const order: string[] = [];
|
const order: string[] = [];
|
||||||
const map = new Map<string, OrganicoSection[]>();
|
const map = new Map<string, OrganicoSection[]>();
|
||||||
for (const s of list) {
|
for (const s of list) {
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ export const DASHBOARD_MODULES: DashboardModuleDef[] = [
|
|||||||
href: '/dashboard/organico',
|
href: '/dashboard/organico',
|
||||||
label: 'Orgánico',
|
label: 'Orgánico',
|
||||||
providers: ['ga4', 'search_console'],
|
providers: ['ga4', 'search_console'],
|
||||||
hint: 'SEO (GSC/GA4) + GEO (visibilidad IA)',
|
hint: 'SEO (Search Console y GA4)',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'activos',
|
id: 'activos',
|
||||||
|
|||||||
Reference in New Issue
Block a user