# SDK

> Referencia del objeto eltick para el código de tus herramientas: red con credenciales, contacto, conversación, cuenta, conocimiento, almacén y logs.

El código de una herramienta es un módulo JavaScript que exporta una función. eltick la llama con las **entradas** y el **SDK**:

```js
export default async function run(input, eltick) {
  // input: las entradas de la herramienta
  // eltick: el SDK
  return { ok: true }
}
```

- `input` trae las [entradas](/avanzados/entradas) por su nombre. Las de credenciales llegan como un token opaco.
- Lo que devuelves tiene que poder convertirse a JSON. Es lo que recibe el agente (dentro de `resultado`).
- Si la función tira un error, el agente recibe `ok: false` con el mensaje.
- Puedes exportarla como `export default` o como `export async function run`.

## Dónde corre

Cada ejecución corre en un **entorno aislado**, armado de cero para esa llamada:

- Sin acceso a la base de datos de eltick, a otras herramientas ni a otros clientes.
- Sin red, salvo `eltick.fetch` hacia los **dominios que declaraste** y que aprobamos.
- Sin `eval`, `new Function`, `import` de otros módulos ni WebAssembly.
- Con JavaScript moderno y las APIs web estándar: `URL`, `URLSearchParams`, `TextEncoder`, `crypto.subtle`, `Date`, `Intl`, `JSON`.

| Límite | Valor |
| --- | --- |
| Tiempo total de una ejecución | 10 segundos |
| Tiempo de cada `fetch` | 8 segundos |
| CPU | 1 segundo |
| Pedidos de red por ejecución | 25 |
| Tamaño del código | 64 KB |
| Dominios declarados | 10 |
| Logs guardados | 100 líneas |
| Cambios al contacto | 20 por ejecución |

## `eltick.fetch(url, opciones)`

Hace un pedido HTTP, igual que `fetch`, con dos agregados:

```js
const res = await eltick.fetch("https://api.tuempresa.com/clientes/123", {
  method: "POST",
  auth: input.credencial,     // una entrada de credencial
  json: { motivo: input.motivo }, // se manda como JSON
  headers: { "x-origen": "eltick" },
})
if (!res.ok) throw new Error(`La API respondió ${res.status}`)
const data = await res.json()
```

| Opción | Qué hace |
| --- | --- |
| `auth` | Una entrada de credencial. eltick agrega la autenticación según su tipo (Bearer, usuario y contraseña, API key, headers u OAuth 2.0). |
| `json` | Un objeto que se manda como cuerpo JSON, con su `content-type`. |
| `method`, `headers`, `body` | Como en `fetch`. Un objeto en `body` también se manda como JSON. |

- Solo `https://` y solo hacia los **dominios declarados** de la herramienta. Un pedido a otro dominio devuelve `403` con el motivo.
- Las **redirecciones no se siguen**: recibes la respuesta `3xx` y decides.
- Una credencial solo sale hacia **sus propios dominios** (los de Ajustes › Credenciales), aunque la herramienta tenga otros.

### Credenciales dentro de headers, URL o cuerpo

Si tu API espera la credencial en otro lugar, pon la entrada directamente donde va. eltick reemplaza el token por el valor real al salir el pedido:

```js
await eltick.fetch(`https://api.tuempresa.com/v1/estado?key=${input.clave}`)

await eltick.fetch("https://api.tuempresa.com/v1/estado", {
  headers: { "X-Token": input.clave },
})
```

El código nunca ve el valor: si haces `eltick.log(input.clave)` aparece el token, no la clave.

## `eltick.contact`

Requiere el permiso **Leer el contacto** (`contact.read`) para leer y **Guardar campos y etiquetas del contacto** (`contact.write`) para escribir.

```js
const c = eltick.contact.get()
// { id, name, phone, instagram, tags, fields: { numero_usuario: "48213" } }

const numero = eltick.contact.field("numero_usuario")

await eltick.contact.setField("numero_usuario", "48213")
await eltick.contact.addTag("moroso")
```

`setField` y `addTag` **se aplican al terminar el turno**, después de que el agente responde, y solo si el turno sigue vigente (si alguien del equipo tomó la conversación mientras tanto, no se aplican). En las pruebas del editor se muestran pero no se guardan.

## `eltick.conversation`

```js
eltick.conversation.id       // ID de la conversación (o null)
eltick.conversation.channel  // "whatsapp" o "instagram"

// Con el permiso "Leer la conversación" (conversation.read):
const ultimos = eltick.conversation.messages(20)
// [{ role: "user" | "assistant", text: "…" }, …]  hasta 50
const anuncio = eltick.conversation.ad
// { id, headline, body, url } del anuncio de origen, o null
```

## `eltick.account`

```js
eltick.account.name      // nombre de tu negocio
eltick.account.timezone  // "America/Argentina/Buenos_Aires"

// Con el permiso "Leer datos de la cuenta" (account.read):
eltick.account.vars      // las variables del agente: { telefono_guardia: "153-060280", … }
```

## `eltick.knowledge.search(consulta)`

Requiere **Buscar en el conocimiento** (`knowledge.read`). Busca en las fuentes del agente (en la prueba del editor, en todas las de tu negocio) y devuelve hasta 5 resultados:

```js
const hits = await eltick.knowledge.search("plan 600 megas precio")
// [{ title, url, text }, …]
```

## `eltick.store`

Un almacén clave-valor propio de la herramienta, para guardar algo entre ejecuciones (por ejemplo, un token o una respuesta que cambia poco):

```js
const cache = await eltick.store.get("tarifas")
if (!cache) {
  const tarifas = await (await eltick.fetch("https://api.tuempresa.com/tarifas")).json()
  await eltick.store.set("tarifas", tarifas, 3600) // segundos: de 60 a 30 días; por defecto 1 día
}
```

Cada valor puede pesar hasta 64 KB. No lo uses para guardar credenciales: para eso están [las credenciales](/avanzados/credenciales).

## `eltick.log`, `eltick.warn`, `eltick.error`

Escriben en los logs de la ejecución, que ves en la pestaña **Probar**. `console.log`, `console.warn` y `console.error` también funcionan.

```js
eltick.log("respuesta", data.estado)
```

## Permisos

Declaras los permisos en la pestaña **Código** y se aprueban con la versión. Sin el permiso, el método correspondiente tira un error y esos datos ni siquiera llegan al entorno aislado.

| Permiso | Habilita |
| --- | --- |
| Leer el contacto (`contact.read`) | `contact.get()`, `contact.field()` |
| Guardar campos y etiquetas (`contact.write`) | `contact.setField()`, `contact.addTag()` |
| Leer la conversación (`conversation.read`) | `conversation.messages()`, `conversation.ad` |
| Leer datos de la cuenta (`account.read`) | `account.vars` |
| Buscar en el conocimiento (`knowledge.read`) | `knowledge.search()` |

Pide solo los que usas: cada permiso nuevo es algo más que revisamos.

## Ejemplo completo

Diagnóstico de un servicio de internet con una credencial de usuario y contraseña, que guarda el número de usuario en el contacto:

```js
export default async function run(input, eltick) {
  const base = "https://sopnet.tuisp.com.ar/api/v1"
  const res = await eltick.fetch(`${base}/clienteplan/obtenerPorNumeroPlan?numero=${encodeURIComponent(input.numero_usuario)}`, {
    auth: input.credenciales,
  })
  if (res.status === 404) return { encontrado: false }
  if (!res.ok) throw new Error(`SopNet respondió ${res.status}`)

  const plan = await res.json()
  await eltick.contact.setField("numero_usuario", input.numero_usuario)
  eltick.log("estado", plan.ESTADO, "bloqueado", plan.BLOQUEADO)

  return {
    encontrado: true,
    bloqueado: plan.BLOQUEADO === "1",
    conectado: plan.ESTADO === "1",
    plan: plan.DESCRIPCION,
  }
}
```

Con estas [reglas de lectura](/avanzados/herramientas#cómo-leer-la-respuesta): *bloqueado es verdadero → manda la deuda*, *conectado es verdadero → ofrece reiniciar*, *conectado es falso → corre el diagnóstico*.
