# SiReTT Online — Alimentar la Base de Conocimiento con su propia IA

**Edición de octubre de 2026.**

> **Este archivo es para su IA.** Está escrito para que lo lea una máquina y lo siga al pie de la letra:
> entrégueselo entero a su asistente (Claude Code, Cursor, ChatGPT con herramientas, un agente propio…).
>
> **Para usted, el operador, hay una guía aparte en PDF**, con las pantallas del sistema y los beneficios
> explicados: `SiReTT-Guia-Base-de-Conocimiento-con-su-IA.pdf`, en https://sirettonline.net/descargas.html

Tiene dos partes:

- **Parte A — resumen para el administrador:** qué hace falta, dónde se crea la llave y cómo se entrega. Es lo
  mismo que explica el PDF, sin capturas, para que la IA también sepa qué pedirle a usted.
- **Parte B — para la IA:** el contrato completo de la API y las reglas de trabajo.

La **Base de Conocimiento** es de donde el Asistente de IA de su SiReTT saca lo que responde a su personal y a
sus clientes. Todo lo que aquí se explica sirve para que una IA de su confianza redacte, suba, corrija, publique
y pruebe esos artículos por usted, con su visto bueno en cada paso.

---

# Parte A — Para el administrador

## A1. Qué necesita

| Qué | Detalle |
|---|---|
| Licencia **Asistente IA** | Sin ella la API responde que la Base de Conocimiento no está disponible. |
| Acceso a **Opciones del Sistema** | Es el permiso que deja crear llaves. |
| La dirección de su sistema | La que aparece en el navegador, por ejemplo `https://miempresa.sirettonline.net`. |
| Una IA que pueda hacer llamadas web | Tiene que poder ejecutar comandos o código en su equipo (`curl`, Python…). Un chat sin herramientas no puede subir nada: solo redactar. |

## A2. Crear la llave

**Cómo llegar:** Menú Sistema → Llaves de API.

Llene el formulario de arriba y presione **Generar llave**:

| Campo | Qué poner |
|---|---|
| **Para qué es** | Un nombre que la identifique: «Carga de artículos con IA». |
| **Descripción** | Quién la usa y desde dónde: «Claude Code en el equipo de María». |
| **Qué puede hacer** | Marque **Base de Conocimiento: consultar** y **Base de Conocimiento: alimentar**. Marque además **Asistente de IA: preguntar** si quiere que su IA pruebe los artículos preguntándole al asistente; esa casilla **consume saldo** del sistema en cada pregunta. |
| **Actúa como** | Déjelo en «Ningún usuario». Solo lo usan otras integraciones. |
| **Desde dónde se puede usar** | Vea A3. |
| **Vence** | Opcional. Para un trabajo puntual conviene ponerle fecha. |

Al guardar, el sistema muestra la llave **una sola vez**. Empieza con `sirett_sys_`. Cópiela en ese momento: el
sistema no la conserva, y si se pierde hay que renovarla.

Una llave por uso. Si mañana conecta otra herramienta, créele la suya: así puede apagar una sin tocar la otra.

## A3. Desde dónde se puede usar

| Opción | Cuándo escogerla |
|---|---|
| **Solo desde las redes autorizadas del sistema** | La recomendada. La llave solo funciona desde las direcciones registradas en **Menú Sistema → Seguridad por IP y Redes**. Si su equipo no está ahí, agréguelo. |
| **Solo desde una lista propia de direcciones** | Cuando la IA trabaja desde un lugar que no quiere agregar a las redes del sistema. Escriba las direcciones en **Direcciones permitidas**, separadas por punto y coma. |
| **Desde cualquier lugar** | Solo si no hay otra salida. La llave queda sin esa segunda barrera. |

¿No sabe con qué dirección sale su equipo? Pídale a su IA que ejecute la prueba de conexión (B4): si la llave
está bien pero el lugar no está autorizado, la respuesta es el código **35**, y entonces sabe que hay que
autorizar esa red. La dirección pública de su equipo la puede ver en cualquier página de «cuál es mi IP».

## A4. Entregarle la llave a su IA

La llave es una contraseña. Tres reglas:

1. **No la pegue en un archivo del proyecto** ni en un repositorio.
2. **Désela como variable de entorno**, o dígasela en la conversación y pídale que no la guarde.
3. **Renuévela o apáguela** cuando termine el trabajo, desde la misma pantalla de Llaves de API.

En una terminal, antes de abrir su IA:

```bash
# Windows (PowerShell)
$env:SIRETT_URL = "https://miempresa.sirettonline.net"
$env:SIRETT_KB_KEY = "sirett_sys_...la llave completa..."

# macOS / Linux
export SIRETT_URL="https://miempresa.sirettonline.net"
export SIRETT_KB_KEY="sirett_sys_...la llave completa..."
```

Y el encargo, con este archivo adjunto:

> Lee `SiReTT-KB-para-su-IA.md` completo y sigue la Parte B. La dirección del sistema está en la variable
> `SIRETT_URL` y la llave en `SIRETT_KB_KEY`; no las escribas en ningún archivo ni las muestres. Empieza por la
> prueba de conexión y dime qué artículos hay. Después te doy el tema del primer artículo.

## A5. Dónde ve usted los artículos

**Cómo llegar:** Menú Clientes → Asistente IA → Base de Conocimiento.

Los artículos que sube la IA llevan la marca **Vía API**. Los que usted escribe a mano en esa pantalla no la
llevan, y la IA **no puede modificarlos ni borrarlos**: solo leerlos. Al revés sí hay que tener cuidado: si usted
edita a mano un artículo marcado Vía API, la próxima carga de la IA lo reemplaza.

## A5b. Artículos públicos y privados

Cada artículo dice **a quién responde**:

- **Público** (como nacen todos): el asistente lo usa con todos, clientes, visitantes y personal.
- **Privado**: solo con **el personal de su empresa**, por el Chat interno del sistema o desde el WhatsApp
  registrado en la ficha de cada usuario. Nunca con un cliente ni con alguien que no se identificó.

Los privados sirven para **capacitar al personal**: procedimientos internos, políticas, cómo se resuelve un caso
en el sistema. Su IA puede marcarlos así al subirlos; dígale qué temas son internos. En la Base de Conocimiento
se reconocen por la insignia **Privado**, y el filtro **Responde a** los separa.

## A6. Borrador primero; publicar sola, más adelante

Al principio, su IA sube cada artículo como **borrador** y usted lo aprueba antes de publicarlo. Es la forma de
conocer cómo redacta y de corregirle el rumbo.

Cuando los resultados vayan siendo satisfactorios, puede soltarle la mano: dígale que **publique directamente**
los artículos de cierto tipo, o todos. Ella lo hará y se lo informará. Puede volver al modo de borrador cuando
quiera, con solo decírselo.

## A7. Medir si los artículos sirven

Publicar un artículo no garantiza que el asistente lo use. Hay dos formas de comprobarlo:

- **Que su IA le pregunte al asistente.** Con la casilla **Asistente de IA: preguntar** marcada en la llave, su
  IA puede hacerle al asistente la misma pregunta que haría un empleado y ver, junto con la respuesta, **en qué
  artículos se apoyó**. Así se sabe si el artículo nuevo se encuentra, si otro lo tapa o si el asistente contestó
  por su cuenta porque no halló nada. Cada pregunta llama al modelo y **consume saldo**; el tope es de **200
  preguntas por día**.
- **La lista «Sin respaldo».** En Menú Clientes → Asistente IA → Base de Conocimiento → **Sin respaldo** están las
  preguntas reales que el asistente no pudo contestar con sus artículos. Es la mejor fuente de temas para el
  siguiente artículo: désela a su IA.

---

# Parte B — Para la IA

Eres un asistente que mantiene la Base de Conocimiento de un sistema SiReTT Online en nombre de su
administrador. Lee esta parte completa antes de hacer la primera llamada.

## B1. Reglas de trabajo

1. **Borrador primero.** Todo artículo nuevo se sube con `estado: 0`. Se publica (`estado: 1`) solo cuando el
   administrador lo aprueba de forma explícita. **Ese es el modo inicial, no un límite permanente:** cuando el
   administrador esté conforme con los resultados puede autorizarte a publicar directamente —todo, o cierto tipo
   de artículos—. Desde ese momento publicas sin esperar, sigues informando qué publicaste, y vuelves al modo de
   borrador en cuanto te lo pida. No te concedas esa autorización tú: tiene que dártela él con sus palabras.
2. **Nada se elimina sin que te lo pidan.** `kb_articulo_eliminar` es irreversible. Para retirar un artículo sin
   perderlo, archívalo (`estado: 2`).
3. **La llave no se escribe ni se muestra.** Léela de la variable de entorno. No la pongas en archivos, en
   mensajes, en registros ni en el historial de comandos si puedes evitarlo.
4. **No inventes el sistema.** Un artículo describe menús, botones y comportamientos reales. Si no tienes la
   fuente (una guía, capturas, el dictado del administrador), pregunta. Un artículo equivocado es peor que
   ninguno: el asistente lo va a repetir con seguridad.
5. **Informa lo que hiciste con exactitud:** qué se creó, qué se actualizó, qué quedó en borrador y qué falló,
   con el `ws_ref` de cada uno.
6. **`kb_preguntar` gasta dinero.** Cada pregunta llama a un modelo y consume saldo del sistema. Úsala para
   comprobar, no para explorar: pocas preguntas, bien escogidas.
7. **Público o privado, en cada artículo.** Un artículo **privado** (`visibilidad: "privado"`) solo se usa para
   responderle al personal de la empresa; uno **público**, a todos. Marca privado lo que es de uso interno
   —capacitación, procedimientos internos, políticas que no se le dicen a un cliente— y público lo que sirve a
   clientes. Si un tema no es claro, **pregunta**: un artículo interno marcado público le llega a cualquier
   cliente, y uno para clientes marcado privado nunca les llega.

## B2. Cómo se llama a la API

- **Dirección:** `{SIRETT_URL}/api.php`
- **Método:** `POST`, con `Content-Type: application/json` y el cuerpo en **UTF-8**.
- **Credencial:** cabecera `Authorization: Bearer {SIRETT_KB_KEY}`.
- **Cuerpo:** `{"action": "<acción>", "params": { ... }}`

Toda respuesta tiene la misma forma:

```json
{ "result": 0, "data": { }, "message": "", "version": 1 }
```

`result` en `0` es éxito. Cualquier otro valor es un código de B6; en ese caso `data` viene vacío o nulo.
**Guíate siempre por `result`, no por el código HTTP**: un artículo que no existe llega con HTTP 200 y
`result: 31`, y una llave inválida llega con HTTP 401 y `result: 3`. En los dos casos el cuerpo trae el mismo
sobre; si tu cliente lanza una excepción con los códigos HTTP de error, lee el cuerpo igualmente.

## B3. Programa de apoyo

Guárdalo como `sirett_kb.py`. Solo usa la biblioteca estándar de Python.

```python
# -*- coding: utf-8 -*-
"""Cliente mínimo de la Base de Conocimiento de SiReTT Online.
   Lee SIRETT_URL y SIRETT_KB_KEY del entorno; no guarda la llave en ningún lado."""
import os, sys, json, urllib.request, urllib.error

def api(action, params=None):
    url = os.environ["SIRETT_URL"].rstrip("/") + "/api.php"
    body = json.dumps({"action": action, "params": params or {}}, ensure_ascii=False).encode("utf-8")
    req = urllib.request.Request(url, data=body, method="POST", headers={
        "Authorization": "Bearer " + os.environ["SIRETT_KB_KEY"],
        "Content-Type": "application/json"})
    try:
        with urllib.request.urlopen(req, timeout=120) as r:
            return json.loads(r.read().decode("utf-8", "replace"))
    except urllib.error.HTTPError as e:
        # Los rechazos llegan con un código HTTP de error (401 con una llave mala), pero traen el mismo sobre.
        try: return json.loads(e.read().decode("utf-8", "replace"))
        except ValueError: return {"result": -1, "data": None, "message": "HTTP %d" % e.code}

def todos(estado=-1):
    """Índice completo, paginado de 200 en 200. No trae el contenido."""
    out, off = [], 0
    while True:
        r = api("kb_articulos", {"estado": estado, "limit": 200, "offset": off})
        if r["result"] != 0: raise SystemExit("kb_articulos: result %s" % r["result"])
        lote = r["data"]["articulos"]; out += lote; off += len(lote)
        if len(lote) < 200: return out

if __name__ == "__main__":
    sys.stdout.reconfigure(encoding="utf-8")
    print(json.dumps(api(sys.argv[1], json.loads(sys.argv[2]) if len(sys.argv) > 2 else {}), ensure_ascii=False, indent=1))
```

Uso desde la terminal: `python sirett_kb.py sys_ping` · `python sirett_kb.py kb_articulo "{\"ws_ref\": \"apoyo/ventas/descuentos\"}"`

Con `curl`, si no hay Python:

```bash
curl -sS "$SIRETT_URL/api.php" \
  -H "Authorization: Bearer $SIRETT_KB_KEY" -H "Content-Type: application/json" \
  -d '{"action":"sys_ping","params":{}}'
```

## B4. Primer paso: la prueba de conexión

```json
{"action": "sys_ping", "params": {}}
```

```json
{"result": 0, "data": {"llave": "Carga de artículos con IA", "hint": "sirett_sys_...d02a",
  "ambitos": ["kb.read", "kb.write", "ia.ask"], "ip": "190.0.0.1", "fecha": "2026-10-13 14:39:12"}}
```

Comprueba tres cosas antes de seguir:

- `result` es `0`. Si es `3`, la llave está mal copiada, apagada o vencida. Si es `35`, el lugar desde donde
  llamas no está autorizado: dile al administrador que autorice esa red (Parte A, A3).
- `ambitos` trae `kb.read` y `kb.write`. Sin `kb.write` solo puedes leer.
- Si no trae `ia.ask`, no puedes usar `kb_preguntar`: dilo en vez de intentarlo.

## B5. Las operaciones

### Leer — ámbito `kb.read`

| Acción | Parámetros | Devuelve en `data` |
|---|---|---|
| `kb_categorias` | — | `categorias[]`: `categoria_id`, `nombre`, `descripcion`, `pos`, `activo` |
| `kb_articulos` | `categoria_id`, `estado`, `visibilidad`, `desde`, `offset`, `limit` (todos opcionales) | `total`, `offset`, `limit` y `articulos[]` **sin contenido**: `articulo_id`, `ws_ref`, `titulo`, `categoria_id`, `estado`, `visibilidad`, `vigencia_desde`, `vigencia_hasta`, `updated_at`, `hash`, `bytes`, `chunks`, `origen` |
| `kb_articulo` | `ws_ref` **o** `articulo_id` | El artículo completo, con `contenido` |

- `estado`: `-1` todos (por defecto) · `0` borrador · `1` publicado · `2` archivado.
- `visibilidad`: `publico` o `privado`; sin él, todos. Cada artículo trae la suya.
- `desde`: `AAAA-MM-DD`; trae solo lo modificado desde esa fecha.
- `limit`: 100 por defecto, **200** como máximo. Pagina con `offset`.
- `origen`: `api` si lo subió una llave; `manual` si lo escribió una persona en el sistema.
- `chunks`: en cuántos fragmentos está indexado. Un borrador tiene `0`: no participa en las respuestas.

### Escribir — ámbito `kb.write`

| Acción | Parámetros | Devuelve en `data` |
|---|---|---|
| `kb_articulo_guardar` | `articulo` (objeto) | `articulo_id`, `modo`, `chunks` |
| `kb_articulos_guardar` | `articulos[]` (máximo **25**) | `reply[]`: una fila por artículo |
| `kb_articulo_estado` | `ws_ref` o `articulo_id`, `estado` | `articulo_id`, `estado`, `chunks` |
| `kb_articulo_eliminar` | `ws_ref` o `articulo_id` | `articulo_id` |
| `kb_categoria_guardar` | `categoria_id` o `nombre`; `descripcion`, `icono`, `pos`, `activo` | `categoria_id`, `modo` |
| `kb_reindexar` | — | `articulos`, `chunks` |

**El objeto `articulo`:**

| Campo | Obligatorio | Notas |
|---|---|---|
| `ws_ref` | Recomendado siempre | Tu identificador estable del artículo, hasta 120 caracteres. Es la llave para actualizarlo. Vea B7. |
| `articulo_id` | No | Alternativa a `ws_ref` para apuntar a uno que ya existe. |
| `titulo` | Sí, al crear | Hasta 200 caracteres. |
| `contenido` | Sí, al crear | Texto plano o markdown sencillo. Hasta 200 KB. |
| `categoria_id` | No | Identificador de `kb_categorias`. **Prefiérelo** al nombre. |
| `categoria` | No | Nombre de la categoría; si no existe, **la crea**. Úsalo solo cuando quieras una categoría nueva. |
| `estado` | No | `0` borrador (por defecto al crear) · `1` publicado · `2` archivado. |
| `visibilidad` | No | `publico` (por defecto al crear) · `privado`: solo para el personal de la empresa. Vea la regla 7 de B1. Al actualizar, si no lo mandas no se toca. |
| `vigencia_desde`, `vigencia_hasta` | No | `AAAA-MM-DD` o vacío. |

**Reglas del guardado:**

- Con un `ws_ref` que no existe, **crea**. Con uno que existe, **actualiza**. `modo` lo dice: `creado`,
  `actualizado` o `sin_cambios`.
- Al actualizar, **los campos que no envías no se tocan**. Puedes mandar solo el `contenido`, o solo el `titulo`.
- Si reenvías lo mismo, responde `sin_cambios` y no hace nada. Reintentar es seguro.
- Sin `ws_ref` y sin `articulo_id`, **crea siempre**: reenviar duplica. No lo hagas.

**Ejemplos verificados:**

```json
{"action": "kb_articulo_guardar", "params": {"articulo": {
  "ws_ref": "apoyo/ventas/como-anular-una-factura",
  "titulo": "Cómo anular una factura",
  "categoria_id": 7,
  "contenido": "Cómo llegar\n\nMenú Ventas > ...",
  "estado": 0 }}}
→ {"result": 0, "data": {"articulo_id": 419, "modo": "creado", "chunks": 0}}
```

```json
{"action": "kb_articulo_estado", "params": {"ws_ref": "apoyo/ventas/como-anular-una-factura", "estado": 1}}
→ {"result": 0, "data": {"articulo_id": 419, "estado": 1, "chunks": 3}}
```

```json
{"action": "kb_articulos_guardar", "params": {"articulos": [ {…artículo 1…}, {…artículo 2…} ]}}
→ {"result": 17, "data": {"reply": [
    {"i": 0, "ws_ref": "apoyo/…", "result": 0,  "articulo_id": 419, "modo": "actualizado", "chunks": 0},
    {"i": 1, "ws_ref": "",        "result": 13, "articulo_id": 0,   "modo": "",            "chunks": 0} ]}}
```

En el lote, `result` general en `17` significa «alguno falló»: los demás **sí se guardaron**. Recorre `reply`
y revisa el `result` de cada elemento.

```json
{"action": "kb_articulo_eliminar", "params": {"ws_ref": "apoyo/ventas/como-anular-una-factura"}}
→ {"result": 0, "data": {"articulo_id": 419}}
```

`kb_reindexar` regenera el índice de todos los artículos publicados que subió la API. No hace falta en el
trabajo normal: guardar y publicar ya indexan. Úsalo solo si el administrador sospecha de un índice dañado.

### Preguntar — ámbito `ia.ask`

```json
{"action": "kb_preguntar", "params": {"pregunta": "¿Cómo anulo una factura?"}}
```

| En `data` | Qué es |
|---|---|
| `como` | Con qué artículos se respondió: `personal` o `cliente` (ver abajo). |
| `texto` | La respuesta del asistente, tal como la vería un usuario. |
| `grounding` | `1` si se apoyó en la Base de Conocimiento; `0` si contestó de su conocimiento general. |
| `escalado` | `1` si decidió pasar el caso a una persona. No es un error. |
| `fuentes[]` | Los fragmentos que leyó: `articulo_id`, `titulo`, `visibilidad`, `score`, `extracto`. |
| `modelo`, `llamadas`, `costo` | Con qué modelo respondió, cuántas veces se llamó y cuánto costó. |

`como` es opcional: `personal` (por defecto) responde con los artículos públicos **y** los privados, como le
respondería a un empleado; `cliente` responde **solo con los públicos**, como le respondería a un cliente. Úsalo
para comprobar que un artículo privado no le llega a un cliente.

`historial[]` es opcional, para probar una conversación de varios turnos: hasta 10 elementos con la forma
`{"role": "user" | "assistant", "content": "..."}`, en orden. Los límites y cómo interpretar el resultado
están en B11.

## B6. Códigos de resultado

| `result` | Significado | Qué hacer |
|---|---|---|
| `0` | Correcto | — |
| `3` | Llave inválida, apagada o vencida | Detente. Pide al administrador que la revise o la renueve. |
| `4` | La llave no tiene el permiso para esta acción | Dile cuál casilla falta (consultar, alimentar o preguntar). |
| `13` | Falta el `titulo` o el `contenido` al crear, o el lote va vacío | Corrige y reenvía. |
| `17` | En un lote, algunos fallaron | Revisa `reply` elemento por elemento. |
| `30` | La Base de Conocimiento no está disponible | El sistema no tiene la licencia Asistente IA; o, en `kb_preguntar`, el asistente está apagado. Avisa al administrador. |
| `31` | El artículo no existe, o es **manual** y no se puede modificar | Si lo escribió una persona, no insistas: propón el cambio al administrador. |
| `32` | El contenido o la pregunta es demasiado largo | Parte el artículo en dos. |
| `33` | Se alcanzó un tope: artículos, o preguntas del día | Detente e informa. |
| `34` | Un parámetro está mal: estado fuera de rango, `visibilidad` que no es `publico` ni `privado`, `ws_ref` muy largo, fecha o `limit` mal formados | Corrige el parámetro. |
| `35` | El lugar desde donde llamas no está autorizado para esta llave | Detente. El administrador debe autorizar la red. |

## B7. El identificador del artículo: `ws_ref`

`ws_ref` es tuyo: tú lo defines y con él vuelves a encontrar el artículo. Elígelo bien, porque cambiarlo después
crea un artículo nuevo y deja el viejo.

- Forma: `apoyo/<área>/<tema>`, en minúsculas, sin espacios ni tildes, con guiones:
  `apoyo/ventas/como-anular-una-factura`, `apoyo/taller/garantia-de-reparacion`.
- **Antes de crear, mira lo que ya hay.** Baja el índice con `kb_articulos` y revisa si el tema ya existe. Dos
  artículos sobre lo mismo se estorban: el asistente puede citar el equivocado.
- Respeta las convenciones que ya use esa Base: si los artículos existentes siguen otro patrón de `ws_ref`,
  sigue el de ellos.

## B8. Lo que le pasa al texto al guardarse

El sistema guarda en una codificación que no admite todos los símbolos. Al guardar, **normaliza** el texto:

| Se conserva | Se transforma | Se pierde |
|---|---|---|
| á é í ó ú ñ Ñ ü ¿ ¡ | `—` y `–` → `-` · comillas curvas → rectas · `…` → `...` · `→` → `->` · `•` → `-` · `₡` → `CRC` · `€` → `EUR` | Emojis y cualquier otro símbolo fuera de ese juego |

Consecuencias prácticas:

- **Escribe con caracteres simples desde el principio:** guion corto, comillas rectas, `>` o `->` para las rutas
  de menú, guiones para las listas. Sin emojis.
- Para montos, escribe «1.000 colones» en vez de `₡1.000`, que quedaría `CRC1.000`.
- El `hash` del índice es el MD5 del contenido **ya normalizado**. Si lo comparas con el MD5 de tu archivo local,
  no coincidirá aunque el artículo esté al día. Para saber si algo cambió, reenvíalo: `sin_cambios` es la
  respuesta fiable, y no cuesta nada.
- Tras subir un artículo, **léelo de vuelta** con `kb_articulo` la primera vez, para ver cómo quedó.

## B9. El flujo de trabajo

1. **Conexión.** `sys_ping` (B4).
2. **Reconocimiento.** `kb_categorias` y el índice completo con `kb_articulos`. Resume al administrador qué hay:
   cuántos artículos, en qué categorías, cuántos en borrador.
3. **Tema.** El administrador indica qué documentar y entrega la fuente.
4. **Redacción.** Escribes el artículo según B10 y se lo muestras **antes de subirlo**.
5. **Borrador.** Con su visto bueno, `kb_articulo_guardar` con `estado: 0`.
6. **Publicación.** Cuando lo apruebe, `kb_articulo_estado` con `estado: 1`.
7. **Comprobación.** Si tienes `ia.ask`, una o dos preguntas con `kb_preguntar` (B11).
8. **Informe.** Lista de `ws_ref` con lo que pasó con cada uno.

Para una tanda grande, sube en lotes de hasta 25 con `kb_articulos_guardar`, todos en borrador, y publica al
final con una llamada a `kb_articulo_estado` por artículo.

**Corregir** un artículo es reenviar el mismo `ws_ref` con el contenido nuevo. Si está publicado, sigue
publicado y se reindexa solo.

**Al documentar un cambio del sistema, busca los artículos que describen cómo era antes y corrígelos también.**
Agregar el artículo nuevo no basta: quedan dos versiones y el asistente puede citar la vieja.

## B10. Cómo se escribe un artículo que el asistente encuentre

El asistente no busca artículos: busca **fragmentos de texto parecidos a la pregunta**, y luego cita el título
del artículo que los contiene. Eso decide casi todo.

**Un tema por artículo.** De una a tres páginas. No subas un manual entero como un solo artículo: se recupera
mal y se cita peor.

**El título es la pregunta, no el nombre del módulo.** «Cómo anular una factura», no «Módulo de anulaciones».

**Estructura, en este orden:**

1. **Cómo llegar** — la ruta completa del menú, con los nombres tal como se ven en pantalla:
   `Menú Ventas > Buscar Facturas > (abrir la factura) > Anular`. Y de qué licencia o permiso depende, si aplica.
   Es lo que más se olvida y lo primero que el usuario necesita.
2. **Para qué sirve** — en una o dos frases, el problema que resuelve.
3. **Pasos** — numerados, uno por acción.
4. **Detalles a tener en cuenta** — las trampas: lo que frena la operación, lo que depende de la configuración.
5. **Las mismas dudas, como las pregunta la gente** — de cuatro a ocho preguntas reales, cada una con su
   respuesta corta. Escríbelas como las haría un empleado con prisa, con sus palabras y no con las del manual:
   «¿por qué no me deja anular?», «¿se puede borrar una factura?». **Esta sección es la que hace que el artículo
   se encuentre.**

**Repite el dato clave.** Si el artículo tiene un enlace o un dato esencial, ponlo arriba y otra vez en la sección
de preguntas. El texto se parte en fragmentos por longitud: un enlace que está solo al principio no viaja con el
fragmento del final.

**Lenguaje de quien usa el sistema.** Menús, botones y pasos. Nunca nombres de archivos, de tablas ni términos
de programación.

**¿Artículo nuevo o una nota dentro de otro?** Artículo nuevo, si alguien podría hacer esa pregunta sin tener el
otro tema enfrente. Nota dentro, si solo tiene sentido en ese contexto. Cuando dos artículos se tocan, enlázalos
por su título en una línea («Sobre los descuentos, vea: Cómo aplicar un descuento»); no los fusiones.

## B11. Medir la eficacia de lo publicado: preguntarle al asistente

Publicar no garantiza que el asistente lo use. `kb_preguntar` (ámbito `ia.ask`) es la forma de medirlo: le haces
al asistente la pregunta que haría un usuario y recibes la respuesta **junto con los artículos en que se apoyó**.
Es el mismo motor que atiende al personal y a los clientes, así que lo que ves es lo que ellos verían.

Por defecto preguntas **como el personal** (con los artículos privados incluidos). Para saber qué le respondería
a un **cliente**, agrega `"como": "cliente"`. Cuando subas un artículo privado, haz una pregunta de cada forma:
como personal debe aparecer en `fuentes`; como cliente, no.

```json
{"action": "kb_preguntar", "params": {"pregunta": "¿Por qué no me deja anular una factura?"}}
```

Lee el resultado así:

| Lo que ves | Qué significa | Qué hacer |
|---|---|---|
| `grounding: 1` y tu artículo arriba en `fuentes` | Funciona. | Nada. |
| `grounding: 1`, pero cita otro artículo | Otro artículo tapa al tuyo. | Agrega a tu artículo preguntas con las palabras de la consulta; revisa si el otro dice algo que contradice. |
| `grounding: 0` | No encontró nada y contestó por su cuenta. | El artículo no está publicado, o no usa las palabras de la pregunta. Agrega la sección de dudas. |
| `escalado: 1` | Decidió pasar el caso a una persona. | Lee `texto`: suele indicar que le faltó información. |
| La respuesta es correcta pero le falta el enlace | Recuperó un fragmento que no lo trae. | Repite el enlace en la sección de preguntas. |

Prueba con dos o tres formulaciones distintas, no con veinte. Cada una cuesta.

**Los límites de esta operación:**

| Límite | Valor |
|---|---|
| Preguntas por día | **200** (al alcanzarlo responde `33`) |
| Longitud de la pregunta, y de cada turno del historial | 4 000 caracteres |
| Turnos de `historial` | 10 |
| Fragmentos devueltos en `fuentes` | hasta 6, los mismos que leyó el modelo |
| Costo | Cada pregunta llama al modelo; `costo`, `llamadas` y los tokens vienen en la respuesta. Si el asistente escala a su modelo de segunda opinión, son dos llamadas. |

Las preguntas hechas por la API quedan registradas aparte de las del personal y los clientes, para no falsear
el consumo real del asistente. La API no permite leer las conversaciones de otros.

**Un banco de preguntas para medir en serio.** Si el administrador quiere saber qué tan bien cubierta está un
área, no improvises: acuerda con él de diez a veinte preguntas representativas, guárdalas en un archivo, y
córrelas cuando termines una tanda. Informa tres números: cuántas respondió con respaldo (`grounding: 1`),
cuántas citaron el artículo esperado, y cuántas escaló. Repite el mismo banco después de corregir: es la única
comparación honesta. Con 200 preguntas al día, un banco de veinte se puede correr varias veces.

**De dónde salen las preguntas reales.** El administrador tiene en el sistema la lista **Sin respaldo** (Menú
Clientes > Asistente IA > Base de Conocimiento > Sin respaldo), con lo que la gente preguntó y el asistente no
pudo contestar con sus artículos. Pídesela: es el mejor punto de partida para decidir qué escribir.

## B12. Límites

| Límite | Valor |
|---|---|
| Tamaño de un artículo | 200 KB |
| Artículos subidos por la API, en total | 2 000 |
| Artículos por lote | 25 |
| Artículos por página de lectura | 200 |
| Longitud de `ws_ref` | 120 |
| Longitud de `titulo` | 200 |
| Preguntas con `kb_preguntar` por día | 200 |
| Longitud de una pregunta | 4 000 caracteres |

## B13. Lo que la API no hace

- **No modifica ni borra artículos escritos a mano** en el sistema (responde `31`). Puede leerlos.
- **No borra categorías.**
- **No da acceso a las conversaciones** de usuarios y clientes con el asistente.
- **No cambia la configuración del asistente**: modelo, personalidad, saldo.
- **No toca nada más del sistema.** Una llave con estos permisos no ve ventas, clientes ni inventario.

---

*SiReTT Online · Zephysoft. Este documento describe la API tal como funciona en octubre de 2026 (con los
artículos públicos y privados, agregados el 14 de octubre). Los ejemplos
de respuesta están tomados de llamadas reales.*
