---
title: Búsqueda y analítica
description: Encuentra entradas por significado en cualquier idioma, muestra entradas relacionadas y deja que un agente vea qué leen los lectores, desde tu analítica.
section: guides
order: 5
---

# Búsqueda y analítica

La búsqueda por palabras funciona en todas partes, desde el primer `npm create hollware`. La búsqueda por significado es opcional, porque usa servicios que cuestan dinero pasado su uso gratuito. Lo que leen los lectores viene de la analítica que tu sitio ya tiene, una vez que la conectas.

## Búsqueda

La herramienta `search` y el método `search` de un sitio encuentran entradas publicadas de tres formas:

| `mode`     | Encuentra                                                                 |
| ---------- | --------------------------------------------------------------------- |
| `fulltext` | las entradas que contienen cada palabra de la consulta                         |
| `semantic` | las entradas más cercanas en significado, en cualquier idioma                       |
| `hybrid`   | ambas, combinadas; es el modo por defecto                                             |

La respuesta dice qué modo se ejecutó. Sin búsqueda por significado, cada modo se ejecuta como `fulltext`, y la respuesta lo indica.

La búsqueda por palabras ignora los acentos, así que `migracion` encuentra `migración`. La búsqueda por significado encuentra un post en español para una pregunta en inglés.

## Responde preguntas con tu contenido

`search` con `passages: true` responde además, para cada resultado, el **pasaje** que coincidió: la sección completa de la entrada, con su `heading`, su `text` tal como lo lee un lector, y la `url` de la entrada cuando el sitio tiene una. Eso es lo que lee un modelo para responder una pregunta con tu contenido y citarlo (retrieval-augmented generation). `minScore`, de 0 a 1, deja fuera lo que coincide por significado menos que eso, así una pregunta que tu contenido no responde no encuentra nada, en vez de la entrada menos mala:

```ts
const found = await env.HOLLWARE.search('my-site', '¿cómo cacheo las páginas?', {
  passages: true,
  minScore: 0.5,
  limit: 3,
})
const context = found.results.map((r) => `## ${r.passage?.heading}\n${r.passage?.text}\n(${r.passage?.url})`)
```

Hollware encuentra los pasajes; el modelo que escribe la respuesta lo eliges tú. Por HTTP, agrega `passages=true&minScore=0.5`.

## Activa la búsqueda por significado

Necesita dos bindings: Workers AI, que convierte el texto en vectores, y un índice de Vectorize, que encuentra los más cercanos.

1. Crea el índice, con las dimensiones del modelo, y los índices de metadatos por los que filtra:

```sh
npx wrangler vectorize create my-cms-bge-m3 --dimensions=1024 --metric=cosine
npx wrangler vectorize create-metadata-index my-cms-bge-m3 --property-name=site --type=string
npx wrangler vectorize create-metadata-index my-cms-bge-m3 --property-name=collection --type=string
npx wrangler vectorize create-metadata-index my-cms-bge-m3 --property-name=locale --type=string
npx wrangler vectorize create-metadata-index my-cms-bge-m3 --property-name=piece --type=number
```

2. Enlaza ambos en `wrangler.jsonc` y despliega:

```jsonc
"ai": { "binding": "AI" },
"vectorize": [{ "binding": "VECTORIZE", "index_name": "my-cms-bge-m3" }]
```

En unos minutos Hollware indexa lo que publicaste: cada entrada se convierte en un vector para su resumen y uno por cada sección `##`. Las publicaciones nuevas se indexan a medida que ocurren, y una entrada despublicada sale del índice.

El modelo es `@cf/baai/bge-m3` salvo que la variable `HOLLWARE_EMBEDDING_MODEL` indique otro. Un índice sirve a un solo modelo, ya que sus dimensiones son fijas: para cambiar de modelo, crea un índice nuevo, apunta ambos a él y despliega. Hollware vuelve a indexar todo por su cuenta.

::callout{type="tip"}
La búsqueda por significado hace algunas llamadas a D1 por cada consulta. Si tu base de datos está lejos de tus lectores, agrega `"placement": { "mode": "smart" }` a `wrangler.jsonc`, para que el Worker se ejecute cerca de ella.
::

## Entradas relacionadas

Con la búsqueda por significado activa, cada entrada publicada conoce las cinco entradas de su colección y locale más cercanas a ella. Pídelas con `get_entry`, o con `getEntry` y `related: true`:

```ts
const answer = await env.HOLLWARE.getEntry('my-site', 'blog', 'hello-world', 'en', { related: true })
answer.related // entradas publicadas, las más cercanas primero
```

## Mira lo que leen los lectores

Hollware no cuenta visitas por su cuenta. Las lee de la analítica que tu sitio ya tiene, con una clave de solo lectura, y no guarda nada de los lectores. Lee cuatro proveedores:

| Proveedor                | La clave de solo lectura                                                                     |
| ------------------------ | -------------------------------------------------------------------------------------------- |
| Cloudflare Web Analytics | Un API token con **Account Analytics: Read**, sobre la cuenta del sitio                     |
| PostHog                  | Una personal API key con **Query: Read** y **Project: Read**, limitada al proyecto del sitio |
| Plausible                | Una clave de tipo **Stats API**                                                              |
| Google Analytics 4       | La clave JSON de una cuenta de servicio que es **Viewer** de la propiedad                    |

Conecta uno en el admin, en **Sitio → Analíticas**: elige el proveedor, pega la clave y presiona **Conectar**. Hollware primero le pide al proveedor los últimos 7 días, y guarda la clave solo cuando responde:

```txt
Conectadas: 139 visitas en 21 páginas en los últimos 7 días.
```

Una clave sin el permiso correcto se rechaza con el permiso que falta. La clave se sella con `HOLLWARE_SECRET`, que crea `hollware deploy`, y no se vuelve a mostrar.

Algunos proveedores necesitan un dato más:

- **Cloudflare**: el id de la cuenta, los 32 caracteres que siguen a `dash.cloudflare.com/` en la dirección del dashboard.
- **GA4**: el id de la propiedad, los dígitos en **Admin → Property details**.
- **PostHog y Plausible**, autoalojados: la dirección de la instancia.
- **Otros hosts**: el host de la URL del sitio siempre cuenta. Agrega los demás en los que responde el sitio, como `www.example.com`.

La CLI conecta igual, con un token de un dueño. Lee la clave de un archivo, nunca de la línea de comandos:

```sh
npx hollware analytics connect posthog --credential-file posthog.key --hosts www.example.com \
  --url https://cms.example.com --token-file owner.token
npx hollware analytics status --url https://cms.example.com --token-file owner.token
```

`hollware analytics disconnect` olvida la conexión y su clave. Una conexión nueva reemplaza a la anterior.

## Deja que los agentes lean los conteos

`content_stats` entrega las rutas más leídas de los últimos días (`days`, de 1 a 90, 30 por defecto), cada una con la entrada publicada que muestra, y `source`, el proveedor que leyó. La portada del admin muestra la misma lista en **Lo más leído**.

```json
{ "outcome": "ok", "source": "posthog", "pages": [{ "path": "/blog/hello-world", "views": 116, "entry": { "slug": "hello-world" } }] }
```

Hollware se queda solo con los hosts del sitio, quita la query y el fragmento, y cuenta `/a` y `/a/` como una sola ruta. Los conteos de Cloudflare son muestreados, en pasos de diez; igual ordenan bien las páginas, que es para lo que sirven.

Una respuesta se guarda por diez minutos. Un proveedor tiene 25 s para responder: la API de consultas de PostHog tarda de 5 a 15 s, así que la primera lectura del día puede ser lenta. Sin una conexión, `content_stats` responde `unavailable`, y su arreglo dice cómo conectar una.

Conecta la clave en el admin o con la CLI, no a través de un agente: así la clave nunca pasa por el contexto del agente.
