'use client'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import Link from 'next/link'; import { GEO_ENGINES, ORGANICO_SECTIONS, GeoSnapshot, IntegrationLite, OrganicDailyRow, OrganicoPillar, OrganicoSection, OrganicoSectionId, OrganicoWorkspace, SeoAuditRow, formatNum, formatPct, groupsForPillar, sumOrganic, DEFAULT_EXPERT_PRIORITIES, } from '@/lib/organic'; import { isoYesterday } from '@/lib/ad-tracker-range'; import { AlertTriangle, ArrowRight, Bot, Check, ExternalLink, Leaf, Loader2, Plus, Search, ShieldAlert, Sparkles, } from 'lucide-react'; import { PageHeader, SegmentedControl } from '@/components/ui'; import SyncNowButton from '@/components/SyncNowButton'; type Props = { label: string; portfolioWide: boolean; isAgency: boolean; tenantId: string | null; }; function connBadge(status?: string) { if (status === 'connected') return 'bg-emerald-50 text-emerald-800 border-emerald-200'; if (status === 'error') return 'bg-red-50 text-red-800 border-red-200'; if (status === 'pending') return 'bg-amber-50 text-amber-900 border-amber-200'; return 'bg-gray-50 text-gray-600 border-gray-200'; } function Kpi({ label, value, hint, accent, }: { label: string; value: string; hint?: string; accent?: boolean; }) { return (
{label}
{value}
{hint ?{hint}
: null}Selecciona un cliente para abrir su suite SEO / GEO.
{msg || warning}
)} {/* Connections */}{c.title}
{c.row?.status === 'connected' ? 'ON' : 'OFF'}{c.row?.external_account_id || 'Sin vincular'}
{isAgency && tenantId && c.title !== 'GEO / IA' ? ( FichaClics GSC · 28d
Sesiones orgánicas GA4 · 28d
organic_daily (acumulamos
nosotros). Las queries actuales vienen live de GSC (ventana ~28d).
No es un rank tracker de 10k KW tipo Semrush.
Posición media GSC · histórico diario
Barras más altas ≈ mejor posición (inverso)
Clics · evolución
Sesiones orgánicas · evolución
Cruce: qué URLs rankean (GSC) vs qué landings reciben sesión orgánica (GA4).
Ideas a partir de queries con impresiones altas y CTR bajo (oportunidad de mejorar snippet / contenido).
{eng.label}
{Number.isFinite(v) ? formatPct(v) : '—'}
Dashboard GEO listo — falta la primera medición
A diferencia de SEO (GSC/GA4 por API), Share of Model se captura con un estudio de prompts × motores. Usa las prioridades expertas y registra el snapshot para rellenar estas tarjetas.
{isAgency && ( )}Una por línea. Lo que hay que mejorar primero.
Hallazgos, prompts, fuentes o gaps de esta sección.
{isAgency ? ( <>Requiere herramienta externa
Competencia de dominio, keyword gap y backlinks no salen de GSC/GA4. Hay que conectar Ahrefs/Semrush (o importar CSV) para alimentar este panel. Mientras tanto usa las secciones con datos reales: visión general, rankings, keywords y páginas.
Acciones de esta sección
Sin queries. Verifica Search Console conectado y permisos.
) : (| Query | Clics | Imp. | CTR | Pos. |
|---|---|---|---|---|
| {r.key} | {formatNum(r.clicks)} | {formatNum(r.impressions)} | {formatPct(r.ctr)} | {formatNum(r.position, 1)} |
Sin páginas GSC.
) : (| URL | Clics | Imp. | Pos. |
|---|---|---|---|
| {r.key} | {formatNum(r.clicks)} | {formatNum(r.impressions)} | {formatNum(r.position, 1)} |
Sin landings orgánicas GA4.
) : (| Landing | Sesiones | Usuarios |
|---|---|---|
| {r.page} | {formatNum(r.sessions)} | {formatNum(r.users)} |
{section.group}
{section.blurb}
Auditoría técnica real: HTTPS, robots, sitemap, title/H1/meta, indexación y top páginas GSC. Cada ejecución queda en el histórico.
GSC:{' '} {gsc?.status === 'connected' ? 'conectado' : 'sin conectar'} {gsc?.external_account_id ? ` · ${gsc.external_account_id}` : ''}{' '} · GA4:{' '} {ga4?.status === 'connected' ? 'conectado' : 'sin conectar'}
{Number(selected.critical_count) > 0 ? `${selected.critical_count} problema(s) crítico(s)` : `${selected.warning_count} aviso(s)`}
{selected.summary || 'Revisa los checks marcados abajo antes del siguiente sprint SEO.'}
Histórico ({audits.length})
Checks ({selected.checks?.length || 0}) {criticals.length || warns.length ? ` · ${criticals.length} críticos · ${warns.length} avisos` : ''}
{c.title} {auditStatusLabel(c.status)}
{c.category}{c.detail}
{text}
); } function CompetitorPanel({ latest }: { latest: GeoSnapshot | null }) { if (!latest?.competitors?.length) { return (