Introducción
La mayoría de los fundadores tratan el SEO como una caja negra. Se quedan mirando los dashboards de Google Search Console llenos de impresiones y clics, y les cuesta responder la única pregunta que realmente importa: ¿qué debería hacer a continuación?
Viví este problema durante años construyendo sitios para clientes y mis propios productos SaaS. Los datos estaban ahí - miles de filas de rendimiento de keywords, rankings de páginas, tendencias de CTR - pero extraer insights accionables requería o bien experiencia profunda en SEO o horas de manipulación de hojas de cálculo. Decidí cerrar esa brecha construyendo HeySeo: una plataforma de analítica SEO potenciada por IA que le permite tener una conversación con sus datos de búsqueda.
Esta es la historia completa de la construcción. Decisiones de arquitectura, ingeniería de prompts para LLMs, integración con la API de Google, diseño del servidor MCP, estrategia de precios, y lo que aprendí al lanzarlo. Compartiré el código real y los errores reales.
El Problema: Los Datos de SEO Son Difíciles de Usar
Google Search Console es gratuito, completo y casi completamente impráctico para la mayoría de los dueños de sitios web.
La interfaz le da:
- Una tabla plana de consultas ordenadas por volumen de impresiones
- Rendimiento a nivel de página desglosado por dispositivo
- Reportes de cobertura llenos de códigos de error confusos
- Core Web Vitals y datos de PageSpeed enterrados detrás de múltiples clics
Lo que no le da son respuestas. Usted aún necesita saber suficiente sobre SEO para mirar un CTR de 0,8% en un keyword en posición 4 y razonar: "Ese title tag está bajo rendimiento para este intent. Hay que reescribirlo."
Las herramientas profesionales de SEO, Ahrefs, Semrush, Moz, resuelven parte del problema agregando análisis de backlinks, investigación de competidores y puntajes de dificultad de keywords. Pero cuestan $99-$500 por mes, tienen curvas de aprendizaje pronunciadas y aún requieren una interpretación manual significativa.
Hay una brecha entre "datos crudos" y "siguiente acción clara" que ninguna herramienta estaba llenando bien para desarrolladores independientes, pequeños equipos de marketing y creadores de contenido. Esa es la brecha para la que HeySeo fue construido.
La Idea: Conversar con Sus Datos
El insight que inició todo fue simple. Los LLMs son genuinamente buenos razonando sobre datos estructurados cuando se les da el contexto correcto. ¿Qué pasaría si en lugar de construir otro dashboard de SEO, dejara que los usuarios simplemente hicieran preguntas?
- "¿Qué páginas perdieron más posiciones en los últimos 30 días?"
- "¿Qué keywords estoy posicionando entre el 6to y 15to lugar que debería priorizar?"
- "¿Por qué mi CTR de la página principal es tan bajo comparado con mis posts del blog?"
- "Dame una lista de tareas SEO que debería abordar esta semana, ordenadas por impacto esperado."
Esa última es el caso de uso definitivo. No solo análisis - recomendaciones priorizadas. El LLM puede razonar sobre mejores prácticas de SEO, examinar los datos y producir una lista concreta de tareas. Sin necesidad de experiencia del lado del usuario.
A partir de esa idea central, el conjunto de funcionalidades se expandió:
- Interfaz de chat en lenguaje natural sobre datos de Search Console
- Reportes SEO semanales automatizados entregados por email o Slack
- Monitoreo de PageSpeed con seguimiento de tendencias y comentarios generados por LLM
- Gestión de indexación - verificar qué páginas ha indexado Google, enviar URLs para rastreo
- Tablero Kanban para tareas SEO, pre-poblado por análisis de IA
- Servidor MCP para conectar HeySeo a Claude, Cursor y Windsurf
Visión General de la Arquitectura
Arquitectura del Sistema HeySeo
Flujo de solicitudes desde el navegador al LLM y de vuelta
Elegí Nuxt 3 como framework full-stack porque lo conozco bien, maneja SSR y rutas de API en un solo codebase de forma limpia, y la capa de servidor Nitro (H3) es genuinamente rápida. No se necesita un backend Express separado.
La base de datos es Supabase. Los datos de Google Search Console se cachean agresivamente en Redis porque la API de GSC tiene rate limits y es relativamente lenta - no se quiere que la latencia de respuesta del LLM incluya una consulta fría a GSC en cada mensaje.
Construcción de la Interfaz de Chat
La interfaz de chat fue lo primero que construí porque era el diferenciador central. Acertar en el modelo de interacción temprano definió todo lo demás.
Las decisiones de diseño clave:
Respuestas en streaming. Esperar 5-10 segundos por una respuesta completa del LLM mata la sensación conversacional. Transmito tokens desde la ruta de API directamente al navegador usando Server-Sent Events.
// server/api/chat.post.ts
import { streamText } from 'ai'
import { anthropic } from '@ai-sdk/anthropic'
export default defineEventHandler(async (event) => {
const { messages, siteId } = await readBody(event)
const user = await requireAuth(event)
const context = await buildSeoContext(siteId, user.id)
const result = streamText({
model: anthropic('claude-3-5-sonnet-20241022'),
system: buildSystemPrompt(context),
messages,
maxTokens: 2048,
})
return result.toDataStreamResponse()
})Construcción composable de contexto. Antes de cada mensaje de chat, construyo un objeto de contexto SEO estructurado que contiene los datos recientes del sitio. Esto se serializa en el system prompt. Cubriré la ingeniería de prompts en detalle más adelante.
Historial de mensajes. Persisto la conversación en Supabase para que los usuarios puedan regresar a un hilo y continuar. Cada sitio tiene su propio historial de conversación indexado por site_id + user_id.
Del lado de Vue, el componente de chat usa el composable useChat del Vercel AI SDK:
<!-- components/SeoChat.vue -->
<script setup lang="ts">
import { useChat } from '@ai-sdk/vue'
const props = defineProps<{ siteId: string }>()
const { messages, input, handleSubmit, isLoading } = useChat({
api: '/api/chat',
body: { siteId: props.siteId },
initialMessages: await loadConversationHistory(props.siteId),
})
</script>
<template>
<div class="flex h-full flex-col">
<ChatMessageList :messages="messages" :is-loading="isLoading" />
<ChatInput
v-model="input"
:disabled="isLoading"
@submit="handleSubmit"
/>
</div>
</template>El componente ChatMessageList renderiza markdown del asistente - importante porque el LLM naturalmente formatea las recomendaciones SEO como viñetas, tablas y encabezados. Usé @nuxtjs/mdc para el renderizado de markdown dentro del stream.
Conexión con Google Search Console
OAuth 2.0 con Google es sencillo en teoría y doloroso en la práctica. El punto de dolor específico con GSC es que los access tokens expiran después de una hora y necesita manejar los refresh tokens correctamente a lo largo de sesiones de larga duración.
La API de Google Search Console devuelve datos como filas de series temporales. Para un sitio y rango de fechas dado, puede consultar por:
query(el término de búsqueda)page(la URL que se posicionó)countrydevice
Cada fila devuelve clicks, impressions, ctr y position. Esa es la materia prima.
// server/utils/gsc.ts
import { google } from 'googleapis'
export async function fetchSearchAnalytics(
accessToken: string,
siteUrl: string,
dateRange: { startDate: string; endDate: string },
dimensions: string[],
): Promise<SearchAnalyticsRow[]> {
const auth = new google.auth.OAuth2()
auth.setCredentials({ access_token: accessToken })
const searchconsole = google.searchconsole({ version: 'v1', auth })
const response = await searchconsole.searchanalytics.query({
siteUrl,
requestBody: {
startDate: dateRange.startDate,
endDate: dateRange.endDate,
dimensions,
rowLimit: 1000,
},
})
return response.data.rows ?? []
}La estrategia de caching importa mucho aquí. Los datos de GSC para fechas pasadas nunca cambian, así que cacheo a nivel de fecha con un TTL de 24 horas para el día actual e indefinidamente para todos los días anteriores. Esto significa que la mayoría de los mensajes de chat resuelven el contexto desde Redis en menos de 50ms en lugar de hacer llamadas en vivo a la API.
// server/utils/gsc-cache.ts
export async function getCachedAnalytics(
siteId: string,
dateRange: DateRange,
): Promise<SearchAnalyticsRow[]> {
const cacheKey = `gsc:${siteId}:${dateRange.startDate}:${dateRange.endDate}`
const cached = await redis.get(cacheKey)
if (cached) return JSON.parse(cached)
const rows = await fetchFromGSC(siteId, dateRange)
const ttl = isCurrentDay(dateRange.endDate) ? 3600 : 0
await redis.set(cacheKey, JSON.stringify(rows), ttl > 0 ? { ex: ttl } : undefined)
return rows
}Un problema que descubrí: la API de GSC devuelve datos con un retraso de 3-4 días. Aprendí esto por las malas cuando los usuarios preguntaron "¿qué pasó con mi tráfico ayer?" y el LLM dijo con confianza que nada cambió - porque los datos literalmente no estaban ahí todavía. Ahora incluyo una nota sobre el retraso en el system prompt y lo muestro en la interfaz.
Ingeniería de Prompts del LLM para Datos SEO
Aquí es donde ocurrió la mayor parte de la iteración. La calidad de las respuestas de HeySeo depende completamente de darle al LLM el contexto correcto en el formato correcto.
El objeto de contexto. Antes de cada turno de conversación, construyo un snapshot de contexto que contiene:
interface SeoContext {
site: {
url: string
verified: boolean
addedAt: string
}
summary: {
last28Days: PerformanceSummary
previous28Days: PerformanceSummary
deltaClicksPct: number
deltaImpressionsPct: number
}
topQueries: SearchAnalyticsRow[] // top 50 por clics
decliningQueries: SearchAnalyticsRow[] // mayores caídas de clics YoY
topPages: SearchAnalyticsRow[] // top 20 páginas por clics
quickWins: SearchAnalyticsRow[] // pos 4-15, CTR por debajo del promedio
pagespeed: PagespeedSummary | null
pendingTasks: SeoTask[]
recentInsights: Insight[]
}Deliberadamente limito los datos pasados al LLM. Enviar las 1000 filas de GSC excedería los límites de contexto y agregaría ruido. El paso de pre-procesamiento - calcular deltas, identificar quick wins, marcar caídas - hace el trabajo pesado para que el LLM pueda enfocarse en razonamiento y recomendaciones.
El system prompt. Pasé semanas iterando sobre esto. La versión final tiene cuatro secciones:
- Rol y encuadre - qué es HeySeo, cuál es el trabajo del asistente
- Base de conocimiento SEO - principios fundamentales que el LLM debería aplicar (benchmarks de CTR por posición, lógica de identificación de quick wins, mejores prácticas de indexación)
- Contexto de datos actual - el objeto
SeoContextserializado - Lineamientos de respuesta - cómo formatear respuestas, cuándo sugerir tareas, tono
function buildSystemPrompt(context: SeoContext): string {
return `You are an expert SEO analyst assistant for HeySeo. Your job is to help the user understand their search performance and take concrete actions to improve it.
## SEO Principles You Apply
- Position 1-3: expected CTR 15-30%. Below 10% suggests a title/meta issue.
- Position 4-15: high-opportunity zone. These keywords rank but don't convert to clicks.
- Quick wins: keywords in positions 4-15 with above-average impressions and below-average CTR.
- A click decline with stable impressions usually means a CTR problem (title, meta description).
- A click decline with impression decline usually means a ranking drop (content, backlinks, technical).
## Current Site Data
Site: ${context.site.url}
Last 28 days: ${context.summary.last28Days.clicks} clicks, ${context.summary.last28Days.impressions} impressions
Change vs prior period: ${context.summary.deltaClicksPct > 0 ? '+' : ''}${context.summary.deltaClicksPct.toFixed(1)}% clicks
Top queries by clicks:
${formatQueryTable(context.topQueries)}
Quick win opportunities (pos 4-15, low CTR):
${formatQueryTable(context.quickWins)}
Pages with biggest click declines:
${formatPageTable(context.decliningQueries)}
## Response Guidelines
- Be specific. Reference actual keywords, URLs, and numbers from the data.
- When suggesting tasks, format them as a numbered list with estimated impact (High / Medium / Low).
- If the user asks something you cannot answer from the available data, say so clearly.
- Keep answers concise. Use markdown tables for data-heavy responses.
- Current data lag: GSC reports with a 3-4 day delay. Mention this when relevant.`
}El helper formatQueryTable convierte filas a tablas markdown compactas que el LLM maneja bien sin desperdiciar tokens en JSON verboso.
Salida estructurada para generación de tareas. Cuando el usuario le pide al asistente generar una lista de tareas, cambio a una llamada de salida estructurada que devuelve objetos tipados SeoTask[] que se escriben directamente en el tablero Kanban:
import { generateObject } from 'ai'
import { z } from 'zod'
const SeoTaskSchema = z.object({
tasks: z.array(z.object({
title: z.string(),
description: z.string(),
category: z.enum(['content', 'technical', 'onpage', 'indexing']),
impact: z.enum(['high', 'medium', 'low']),
effort: z.enum(['high', 'medium', 'low']),
targetUrl: z.string().optional(),
targetKeyword: z.string().optional(),
})),
})
export async function generateSeoTasks(
context: SeoContext,
): Promise<SeoTask[]> {
const { object } = await generateObject({
model: anthropic('claude-3-5-sonnet-20241022'),
schema: SeoTaskSchema,
prompt: buildTaskGenerationPrompt(context),
})
return object.tasks.map((task) => ({
...task,
id: crypto.randomUUID(),
status: 'todo' as const,
createdAt: new Date().toISOString(),
}))
}El Tablero Kanban para Tareas SEO
Una de las funcionalidades que los usuarios adoptaron inmediatamente fue el tablero Kanban. La interfaz de chat es excelente para exploración y preguntas/respuestas, pero se necesita un lugar para capturar y seguir el trabajo real.
El tablero tiene cuatro columnas: Por Hacer, En Progreso, Hecho y Descartado. Las tareas pueden ser generadas por la IA, agregadas manualmente o creadas desde dentro de una conversación de chat (el asistente puede decir "He agregado esto a su tablero de tareas" y realmente hacerlo a través de un tool call).
<!-- components/SeoKanban.vue -->
<script setup lang="ts">
import { useSeoTasks } from '~/composables/useSeoTasks'
const { tasks, moveTask, updateTask, deleteTask } = useSeoTasks()
const columns = [
{ id: 'todo', label: 'To Do' },
{ id: 'in_progress', label: 'In Progress' },
{ id: 'done', label: 'Done' },
{ id: 'dismissed', label: 'Dismissed' },
] as const
function onDrop(taskId: string, targetStatus: SeoTask['status']) {
moveTask(taskId, targetStatus)
}
</script>La función moveTask en el composable llama a un RPC de Supabase que actualiza el estado de la tarea y registra una entrada de historial con timestamp. Este historial se pasa de vuelta al LLM como contexto para que pueda ver qué tareas el usuario ya completó al generar recomendaciones futuras.
Reportes Automatizados e Integración con Slack
No todos los usuarios quieren abrir la aplicación para mantenerse informados. Los reportes automatizados entregan un resumen semanal directamente a su bandeja de entrada o canal de Slack.
La generación de reportes se ejecuta como un trabajo de BullMQ cada lunes por la mañana:
// server/jobs/weekly-report.ts
export async function generateWeeklyReport(siteId: string): Promise<void> {
const [context, user, reportSettings] = await Promise.all([
buildSeoContext(siteId, { days: 28 }),
getUserBySiteId(siteId),
getReportSettings(siteId),
])
const { text: reportMarkdown } = await generateText({
model: anthropic('claude-3-5-sonnet-20241022'),
system: REPORT_SYSTEM_PROMPT,
prompt: buildReportPrompt(context),
})
const reportRecord = await saveReport(siteId, reportMarkdown)
if (reportSettings.emailEnabled) {
await sendReportEmail(user.email, reportMarkdown, reportRecord.id)
}
if (reportSettings.slackWebhookUrl) {
await sendSlackReport(reportSettings.slackWebhookUrl, reportMarkdown)
}
}La integración con Slack usa incoming webhooks, lo que mantiene la fricción de configuración baja - los usuarios pegan una URL de webhook en la página de configuración y simplemente funciona. Consideré construir una app de Slack completa con OAuth, pero para el caso de uso (entrega unidireccional de reportes) los webhooks son más simples y confiables.
El prompt del reporte le pide al LLM estructurar la salida como:
- Resumen de rendimiento (cambio de tráfico, principales movimientos)
- Top 3 logros de la última semana
- Top 3 preocupaciones o caídas
- Áreas de enfoque recomendadas para la próxima semana
Corto, opinado y accionable. Los usuarios me dijeron en feedback temprano que los reportes de herramientas anteriores que recibían eran demasiado largos y llenos de datos. Querían un briefing estilo CFO, no una hoja de cálculo.
Monitoreo de PageSpeed
PageSpeed es la métrica SEO que la mayoría de los dueños de sitios ignoran hasta que Google los penaliza por ella. HeySeo consulta la API de PageSpeed Insights semanalmente para cada URL registrada y almacena los datos de series temporales en Supabase.
El dashboard de monitoreo muestra tendencias de Core Web Vitals (LCP, CLS, FID/INP) con un gráfico spark-line y una insignia de estado codificada por colores. Cuando un puntaje cae por debajo de un umbral, el sistema genera una explicación del LLM sobre qué probablemente lo causó y qué arreglar.
// server/utils/pagespeed.ts
export async function fetchPagespeedScore(
url: string,
strategy: 'mobile' | 'desktop' = 'mobile',
): Promise<PagespeedResult> {
const apiUrl = new URL('https://www.googleapis.com/pagespeedonline/v5/runPagespeed')
apiUrl.searchParams.set('url', url)
apiUrl.searchParams.set('strategy', strategy)
apiUrl.searchParams.set('key', process.env.GOOGLE_PAGESPEED_API_KEY!)
const response = await $fetch<PagespeedApiResponse>(apiUrl.toString())
return {
url,
strategy,
score: Math.round((response.lighthouseResult.categories.performance.score ?? 0) * 100),
lcp: response.lighthouseResult.audits['largest-contentful-paint'].numericValue,
cls: response.lighthouseResult.audits['cumulative-layout-shift'].numericValue,
fid: response.lighthouseResult.audits['total-blocking-time'].numericValue,
fetchedAt: new Date().toISOString(),
}
}Algo que aprendí: la API de PageSpeed tiene sus propios rate limits y a veces devuelve errores 500 para URLs completamente válidas. Agregué backoff exponencial con 3 reintentos y omito silenciosamente los fallos en lugar de mostrar errores ruidosos a los usuarios.
Gestión de Indexación
La sección de indexación integra tanto el reporte de Cobertura de Google Search Console como la API de Indexación de Google.
Los usuarios pueden ver qué páginas están indexadas, cuáles tienen errores (404s, cadenas de redirecciones, bloqueadas por robots.txt), y enviar URLs directamente para re-rastreo. Para la mayoría de los sitios pequeños, el flujo de envío manual en GSC es engorroso - tiene que navegar a Inspección de URL, pegar la URL, hacer clic en Solicitar Indexación, y repetir para cada página.
El gestor de indexación de HeySeo permite a los usuarios pegar o importar una lista de URLs, ver su estado actual de indexación de forma masiva, y programar envíos con un solo clic.
// server/api/indexing/submit.post.ts
export default defineEventHandler(async (event) => {
const { urls, siteId } = await readBody<{ urls: string[]; siteId: string }>(event)
const user = await requireAuth(event)
const accessToken = await getGoogleAccessToken(user.id)
const results = await Promise.allSettled(
urls.map((url) => submitUrlForIndexing(accessToken, url)),
)
const succeeded = results
.filter((r): r is PromiseFulfilledResult<IndexingResult> => r.status === 'fulfilled')
.map((r) => r.value)
const failed = results
.filter((r): r is PromiseRejectedResult => r.status === 'rejected')
.map((_, i) => urls[i])
await recordIndexingSubmissions(siteId, succeeded)
return { succeeded: succeeded.length, failed: failed.length, failedUrls: failed }
})La API de Indexación tiene una cuota de 200 envíos de URL por día por propiedad. Muestro este límite en la interfaz y hago seguimiento de los envíos contra él para que los usuarios no encuentren errores inesperados de la API.
Integración del Servidor MCP
Esta es la funcionalidad que más me entusiasma. Model Context Protocol (MCP) permite que asistentes de codificación con IA como Claude Desktop, Cursor y Windsurf llamen a herramientas externas. Construí un servidor MCP de HeySeo que expone toda la funcionalidad de la plataforma como un conjunto de herramientas.
El resultado práctico: los desarrolladores pueden quedarse en su editor, preguntar a Claude "¿qué problemas de SEO tiene mi sitio hoy?" y obtener una respuesta en vivo extraída de Google Search Console - sin abrir un navegador.
El servidor MCP es un proceso Node.js independiente que envuelve la API de HeySeo:
// mcp/index.ts
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
import { z } from 'zod'
const server = new McpServer({
name: 'heyseo',
version: '1.0.0',
})
server.tool(
'get_search_performance',
'Fetch search performance data for a site from Google Search Console',
{
siteUrl: z.string().describe('The site URL registered in HeySeo'),
days: z.number().default(28).describe('Number of days to look back'),
},
async ({ siteUrl, days }) => {
const data = await heyseoClient.getSearchPerformance(siteUrl, { days })
return {
content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],
}
},
)
server.tool(
'get_seo_recommendations',
'Get AI-generated SEO recommendations for a site',
{ siteUrl: z.string() },
async ({ siteUrl }) => {
const recommendations = await heyseoClient.getRecommendations(siteUrl)
return {
content: [{ type: 'text', text: recommendations.markdown }],
}
},
)
server.tool(
'create_seo_task',
'Add a task to the HeySeo Kanban board',
{
siteUrl: z.string(),
title: z.string(),
description: z.string(),
impact: z.enum(['high', 'medium', 'low']),
},
async (params) => {
const task = await heyseoClient.createTask(params)
return {
content: [{ type: 'text', text: `Task created: ${task.id}` }],
}
},
)
const transport = new StdioServerTransport()
await server.connect(transport)Los usuarios instalan el servidor MCP mediante npx heyseo-mcp y lo agregan a su configuración de Claude/Cursor con su API key. El onboarding toma aproximadamente 2 minutos.
Esta fue la funcionalidad de mayor retención que lancé. Los usuarios que configuran la integración MCP tienen una tasa de abandono 40% menor que los que solo usan la app web. La fricción de "abrir navegador, navegar a la herramienta, hacer una pregunta" es real, y eliminarla cambia la frecuencia con la que las personas realmente interactúan con sus datos SEO.
Estrategia de Precios
Pasé más tiempo en los precios que en cualquier funcionalidad técnica individual. Esto es lo que definí después de probar múltiples modelos.
Tier gratuito: Un sitio, 30 días de historial, 20 mensajes de chat por mes, solo resumen del reporte semanal. Suficiente para demostrar valor sin regalar el producto.
Starter ($19/mes): Tres sitios, 12 meses de historial, chat ilimitado, reportes semanales automatizados, monitoreo de PageSpeed. Este es el tier para proyectos personales / pequeños negocios.
Growth ($49/mes): Diez sitios, historial completo, reportes automatizados en cualquier horario, integración con Slack, acceso al servidor MCP, gestión de indexación. Este es el tier objetivo para equipos de marketing y agencias.
Agency ($149/mes): Sitios ilimitados, reportes white-label, puestos de equipo, soporte prioritario.
El insight clave fue que el servidor MCP y la integración con Slack necesitaban ser funcionalidades de pago. Son las funcionalidades de mayor engagement y sirven a usuarios que claramente obtienen valor profesional recurrente de la herramienta. Ponerlas detrás de un paywall también crea un momento claro de upgrade: cuando un usuario está activamente usando el producto en su entorno de codificación, entiende la propuesta de valor concretamente.
También ofrezco un descuento anual del 20%, lo que mejora el flujo de caja y reduce el churn simultáneamente. Aproximadamente el 35% de los usuarios de pago eligen facturación anual.
Lanzamiento y Tracción Temprana
Lancé HeySeo en Product Hunt en enero de 2026 después de aproximadamente 3 meses de construcción. Los resultados fueron mejores de lo esperado para una herramienta B2B de nicho:
- 847 upvotes, producto #3 del día
- 312 registros en las primeras 48 horas
- 23 conversiones de pago en la primera semana
- $1.100 MRR al final del mes de lanzamiento
El lanzamiento en Product Hunt generó conciencia inicial, pero el crecimiento sostenido ha venido de dos fuentes:
Marketing de contenido. Publiqué guías detalladas sobre el uso de IA para análisis SEO, integración de Search Console con herramientas de IA, y comprensión de Core Web Vitals. Estas se posicionan para keywords long-tail de SEO y generan registros de personas que buscan activamente el problema exacto que HeySeo resuelve.
Visibilidad en el ecosistema MCP. Cuando envié el servidor MCP de HeySeo al registro MCP de Claude.ai y la página de plugins de Cursor, el descubrimiento orgánico aumentó significativamente. Los desarrolladores que buscan herramientas MCP encuentran HeySeo sin ninguna promoción pagada.
Lo que no funcionó: outreach en frío a agencias de marketing. El ciclo de compra es demasiado largo y quien toma la decisión raramente es la persona que evalúa herramientas técnicas. Archivé el outreach a agencias en favor del contenido inbound.
Lecciones Aprendidas
Los costos de LLM son predecibles con el caching correcto. Me preocupaban los costos desbordados de API antes del lanzamiento. En la práctica, la combinación de caching agresivo de datos GSC y objetos de contexto pre-calculados significa que la llamada al LLM es el único paso costoso, y solo se dispara con mensajes reales de usuarios. Mi costo actual de LLM es aproximadamente $0,08 por usuario activo por mes a niveles típicos de uso.
Los rate limits de las APIs de Google requieren codificación defensiva desde el día uno. No solo backoff exponencial - también necesita circuit breakers, seguimiento de rate limits por usuario, y degradación elegante cuando la API no está disponible. Fui demasiado optimista sobre la confiabilidad de la API al principio y tuve varios incidentes antes de agregar resiliencia adecuada.
El tablero Kanban me sorprendió. Lo construí como una funcionalidad secundaria para dar a las salidas del chat un lugar donde aterrizar. Resultó ser la razón principal por la que muchos usuarios permanecen suscritos - el tablero les da una lista de tareas persistente que mantiene estado entre sesiones y se vuelve más valioso cuanto más lo usan.
El streaming importa más que la calidad cruda de la respuesta. Los usuarios que ven tokens apareciendo dentro de 500ms perciben el producto como rápido y responsivo incluso cuando la respuesta completa toma 8 segundos. Los usuarios que esperan una respuesta completa perciben incluso respuestas de 3 segundos como lentas. Invierta en streaming antes de invertir en optimización de latencia.
Construya para sus mejores usuarios primero. La integración MCP requirió un esfuerzo de ingeniería significativo y sirve a una minoría de usuarios. Pero esos usuarios son power users que recomiendan el producto en voz alta, tienen bajo churn y suben a tiers superiores. El ROI es mucho mejor que funcionalidades que mejoran la experiencia promedio ligeramente.
Lo Que Viene
HeySeo está en $3.800 MRR a marzo de 2026 y creciendo de manera estable. El roadmap para el próximo trimestre:
- Seguimiento de competidores (comparar sus rankings vs. competidores identificados)
- Sugerencias de meta title y description generadas por IA con soporte para pruebas A/B
- Alertas automatizadas cuando los rankings caen significativamente
- Integración con GA4 para correlacionar tráfico de búsqueda con datos de conversión
- Reportes white-label para el tier Agency
El insight central, que la mayoría de los dueños de sitios web tienen datos que no pueden usar, y los LLMs pueden cerrar esa brecha, se mantiene en cada etapa del producto. La plataforma se vuelve más valiosa a medida que Google agrega más datos a Search Console y a medida que los LLMs mejoran en el razonamiento sobre datos estructurados.
Si está construyendo algo similar o quiere hablar sobre la arquitectura, me encantaría escucharlo.
¿Quiere trabajar juntos en un proyecto como este? Construyo productos SaaS potenciados por IA y herramientas basadas en datos para fundadores y equipos de desarrollo. Si tiene un problema que vale la pena resolver, hablemos.