Skip to content / Saltar al contenido

SaaS multi-tenant que se indexa11 min de lectura

1.400 páginas indexables sin contenido basura: cómo diseñé el directorio de Agendamelo

Por Publicado el

Diseñé el directorio de Agendamelo —cerca de 1.400 páginas, 19 rubros por 69 comunas— para que cada URL sea útil sola: texto del oficio y de la comuna, fichas de negocios reales y un recuento que puede ser cero. Sin esa utilidad, la combinación no se publica o no se indexa.

El volumen no es un logro. El directorio existe para que un cliente encuentre un oficio en un lugar, y para que cada negocio reciba esa visita sin pagarle comisión a la plataforma. La evidencia está en el caso de Agendamelo. Aquí defiendo la arquitectura.

¿Por qué rubro × comuna es la unidad correcta en Chile?

En Chile el cliente no busca “un servicio en una ciudad”. Busca un oficio en un lugar al que puede llegar. “Barbería en Providencia”. “Psicólogo en Ñuñoa”. “Peluquería en Las Condes”. La comuna es la unidad de movimiento: Metro, tiempo de viaje, permiso municipal. Santiago como blob no sirve. Es demasiado grande para decidir. La región es peor: mezcla comunas que no se visitan entre sí.

El rubro es la otra mitad. Agendamelo es agenda para profesionales independientes. Barberías, peluquerías, psicólogos, manicuristas, estética, tatuadores, dentistas, veterinarias, spas, masajes: oficios que se reservan. Un supermercado no entra. Si el producto es reserva, la taxonomía tiene que ser de reserva.

Diecinueve rubros por sesenta y nueve comunas es la apuesta de cobertura, no un scrape de todo Chile. Santiago más ciudades donde ya hay demanda tipable —Viña del Mar, Concepción, Valparaíso, Temuco, Antofagasta, La Serena— y el resto de la matriz generada. Cerca de 1.400 páginas públicas. Cada celda es un oficio en un lugar, no un token en una plantilla.

Ciudad × categoría miente. Providencia y Puente Alto no compiten por el mismo cliente a pie. “Categoría” en un CMS extranjero llega como Beauty. En Chile el cliente escribe el oficio. El slug coincide con esa lengua: /barberias, /peluquerias, /psicologos. Title Case en la comuna (/barberias/Providencia). Ñuñoa se serializa Nunoa; Peñalolén, Penalolen. Esa es la clave pública de la celda.

La prueba: si quitas el nombre de la comuna y el oficio, ¿queda una página que todavía explica algo? Si no, el corte está mal. Rubro × comuna pasa porque cada eje tiene semántica propia. Reduce until it becomes obvious: el directorio es el índice público del mismo esquema que ya exige el onboarding. No inventa otra taxonomía.

¿Qué hace única a cada página?

Un template “Mejores [rubro] en [comuna]” repetido 1.400 veces es contenido basura. El cliente ve un H1 con el nombre cambiado y fichas vacías, y se va. El tenant no aparece. El volumen trabaja contra el producto.

La unicidad sale de bloques con fuentes distintas, no de un sinónimo.

BloqueDónde viveQué lo hace único
Copy de oficioPágina de rubroEl trabajo que se reserva
FAQ de preciosPágina de rubroRangos del oficio en Chile
Intro geográficaPágina de comunaHitos, Metro, tráfico de esa comuna
FAQ con recuentoPágina de comunaUn número vivo, incluido el cero
Fichas de tenantsLas dosServicios y precios del onboarding
Navegación, CTA, schemaTodasBoilerplate. No cubre el cuerpo

/barberias tiene que explicar el oficio y mostrar negocios vivos con precios. Santiago Centro nombra Plaza de Armas, La Moneda, el Metro; esa intro no se reutiliza en La Florida. Si el query devuelve tres barberías en Providencia, las fichas son la prueba. Si devuelve cero, el recuento es el dato. Mentir un listado es peor que admitir el hueco.

Las fichas traen lo que el tenant ya publicó: nombre, servicios, precios en CLP, comuna. Cinco minutos de onboarding son la restricción de esquema. Con eso hay H1, precios y calendario en agendamelo.cl/tu-negocio. El directorio reutiliza esas filas. No pide un segundo CMS para “rellenar SEO”.

El producto en vivo está en agendamelo.cl. El recorte de arquitectura está en el caso. Este texto lo usa como prueba, no como repetición.

¿Cuándo no publico una combinación?

La matriz 19 × 69 no obliga a indexar celdas huecas. Una combinación tiene tres estados honestos:

  1. Existe e indexa. Hay copy geográfico o de oficio que no es un H1 intercambiable, y un recuento honesto. El cero es un dato si el resto enseña el oficio en ese lugar.
  2. Existe y no indexa. La URL sirve a un humano —un tenant que acaba de elegir comuna, una preview— pero el cuerpo todavía es el esqueleto. noindex no es un castigo. Es no mentir al índice.
  3. No existe. generateStaticParams no la emite. El sitemap no la lista. Quien pega la URL recibe un 404. Correcto fuera de la apuesta de cobertura, y dentro de ella cuando no hay página útil que escribir.

Si el único token distinto es el nombre de la comuna en el H1, la combinación no se publica como índice. Plantilla vacía no es “pronto habrá negocios”. Es contenido basura con fecha.

Sin nombre, servicios y horarios el tenant no entra a las fichas. Galería y reseñas mejoran la tarjeta; no la habilitan. Fuera de las 69 comunas no se fabrica la URL por si acaso. Hasta que hay pedido —o un tenant real en esa celda— la ruta no existe.

El Perfil Gratis después del trial es un cuarto caso. La página del tenant sigue publicada. El motor de reservas se apaga. Si el directorio cuenta esa ficha como agenda viva, trabaja contra quien busca un turno. Separar “publicado” de “reserva activa” es la corrección que todavía haría.

No publicar es una decisión de producto. Cada URL del contrato con el índice tiene que servir sola.

¿El contenido nace del tenant o de una plantilla?

De las filas. El tenant es el CMS. La plantilla solo ensambla.

Postgres en Supabase. Cada negocio, servicio, horario, reserva y reseña lleva tenancy. El path /tu-negocio es la clave pública. El directorio lista slug, comuna, rubro, servicios y precios —no ids internos. El panel escribe. El sitio lee acotado a ese tenant. El directorio lee acotado a la celda. Si el aislamiento falla, el incidente es una agenda ajena en la ficha de otro.

Nadie redacta 1.400 artículos. El onboarding escribe las entidades mínimas. El render público ya tiene título, precios y calendario. El directorio proyecta esas entidades en dos ejes. El copy de oficio y el de comuna son los únicos bloques editoriales: decenas, no miles. No escalan con el cartesian product.

generateStaticParams materializa el set. Next.js es el lugar correcto para ~1.400 rutas que tienen que existir como HTML. El recuento en vivo no puede esperar un deploy cada vez que un barbero publica en Providencia. La inferencia razonable es regenerar o revalidar. ISR frente a SSG puro no lo publico como hecho cerrado.

La tentación opuesta es un CMS de “páginas SEO” al lado del producto. Divide la verdad: el panel dice tres servicios; el directorio dice un párrafo genérico. Sistemas sobre pantallas: una sola fuente, varias proyecciones. Si los precios son públicos en el tenant, son públicos en la ficha.

Cuando diseño un producto digital con superficie pública por tenant, la pregunta de Think es: ¿qué fila, al escribirse, produce una URL que alguien puede abrir sin nosotros en el medio? Si la respuesta es “un redactor más tarde”, el directorio ya nació hueco.

Sitemap, canonical y enlaces internos: ¿qué decide el producto?

Tres mecanismos. Ninguno repara una página que no sirve sola.

El sitemap lista las URLs que el producto considera objetos. No lista el excel de 19 × 69 si una celda no existe, ni un noindex, ni el panel, ni un tenant bajo el esquema mínimo. lastmod es veraz: la página de comuna se mueve cuando entra o sale un tenant, no “ahora” en cada deploy.

La canonical de cada celda es ella misma. Colapsar /barberias/Providencia hacia /barberias destruye la unidad. Providencia no es un filtro de la página de Chile. Es otra pregunta. Hreflang no aplica dentro de Agendamelo: el producto es español, para Chile. La paridad EN/ES de jorgeco.tech es otro problema.

Los enlaces internos impiden orfanato y canibalización. El rubro enlaza comunas con recuento mayor que cero, no las 69 por checklist. La comuna enlaza los oficios que realmente oferta. Cada ficha enlaza el mini-sitio: esa es la conversión. El mini-sitio enlaza de vuelta al rubro y a la comuna. Rubros afines se enlazan con criterio de oficio —barbería y peluquería, no barbería y veterinaria.

Ese grafo se genera de los mismos filtros que las fichas. Si se escribe a mano, se pudre. Schema.org describe lo que la página es: un ItemList de tenants reales, un FAQPage que incluye el recuento, también cuando es cero. Mentir en el JSON es la misma basura, en otro content-type.

Esto no es un servicio de posicionamiento. Es el mínimo para que ~1.400 objetos públicos no se tropiecen. Quien busca SEO, AEO y GEO como método va a pgas.online.

¿Cómo el directorio devuelve tráfico a cada negocio con cero comisión?

El resultado publicado: cerca de 1.400 páginas indexables llevan tráfico de búsqueda gratis a cada negocio, con cero comisión por reserva. Es una decisión de arquitectura comercial, no un eslogan.

La plataforma cobra suscripción. El cliente reserva en línea y paga en el local. No hay pasarela al visitante. No hay leads que el negocio recompra. Un directorio que subasta clics infla el recuento y hace pagar por aparecer. Eso es otro producto. Yo no lo construí.

Cero comisión alinea incentivos con la tesis. Más tenants reales hacen la página más útil. La página útil manda visitas al mini-sitio. El mini-sitio convierte en reserva. La plataforma cobra el software, no el match. El lugar destacado existe como feature de plan, no como subasta opaca. Si mintiera ofertas, rompería la unicidad igual que una plantilla vacía.

El tráfico tiene que aterrizar en una URL que reserva. Por eso el onboarding publica el mini-sitio el mismo día. Una ficha a Instagram no cierra el loop. Una ficha a agendamelo.cl/tu-negocio, con precios y calendario, sí.

Gratis para el tenant significa: no se le cobra el clic ni la reserva. El trial es de siete días, sin tarjeta. Después hay plan de pago. Quien construye un índice para extraer take-rate rellena celdas. Quien lo construye para devolver visitas cuida cada página.

Cómo lo aplico

Checklist de Think antes de escribir un generador de rutas. En Agendamelo ya está aplicado.

  1. 01/
    Nombro la unidad de demanda con lengua de cliente. En Chile, para agenda, fue rubro × comuna. Si no se puede decir en una query que alguien escribiría, no es unidad.
  2. 02/
    Hago del modelo de datos el CMS. La fila que el onboarding escribe es la que el directorio lee. Un segundo CMS para “contenido SEO” significa que todavía no hay objeto público.
  3. 03/
    Parto cada URL en bloques con fuente de unicidad distinta. Editorial donde hay pocas entidades. Query donde hay muchas. Boilerplate solo en navegación, CTA y schema.
  4. 04/
    Defino tres estados —indexa, noindex, no existe— antes de generateStaticParams. No se publica una combinación cuyo único token único es el H1.
  5. 05/
    Sitemap, canonical y enlaces se derivan de esa tabla. Canonical es la propia celda. El grafo enlaza oferta real y manda el clic al tenant.
  6. 06/
    Abro la página sola, sin las otras 1.399. Si no sirve a un cliente en esa query, no entra al índice.

Si el brief pide “miles de landing de ciudad” y no puede decir qué bloque es único en cada una, el trabajo empieza por rechazar el corte. A veces lo obvio es no fabricar la página.

La tesis, otra vez

Un directorio programático de 1.400 páginas solo funciona si cada página es útil sola. En Agendamelo esa utilidad se diseña: rubro × comuna como unidad, tenants como origen del texto, celdas que no se publican cuando no hay nada que decir, un grafo que devuelve la visita al negocio, cero comisión para no pudrir el índice. El número grande es un efecto. La unidad útil es el producto.

Proyectos relacionados