# Banco de Chile Empresas

> Saldos, movimientos de cuentas CLP/USD, movimientos de tarjetas y cartolas mensuales del Portal Empresas de Banco de Chile, sincronizados a la caché de Connect.



Con la clave del portal, `bch_empresas` sincroniza saldos, movimientos de cuentas, movimientos de tarjetas de crédito y cartolas mensuales del Portal Empresas de Banco de Chile hacia el plano persistido de Connect. Cuatro tools de consulta los leen de esa caché sin esperar al banco. El `conn_…` de la conexión dice qué empresa lees y viaja en toda llamada (en REST, el header `X-Connect-Connection`).

A diferencia de BICE Empresas, el login de Banco de Chile no exige resolver ningún desafío de navegador: todo el camino, desde el login hasta el logout, corre por HTTP puro. Es el conector bancario más simple de los cuatro que ofrece Connect, no el más caro.

## Qué necesitas para conectar [#qué-necesitas-para-conectar]

En el [enlace de conexión](/docs/empezar/conectar) la persona entrega la **Clave de banca en línea** del portal, en tres campos:

| Campo             | Qué es                                               |
| ----------------- | ---------------------------------------------------- |
| RUT de acceso     | El RUT de la persona que inicia sesión en el portal. |
| Clave             | Su clave de banca en línea de Banco de Chile.        |
| RUT de la empresa | La empresa que esta conexión va a leer.              |

El acceso solo lee, y desde el dashboard se revoca en el acto. La clave no pasa por tu código: la entrega quien la tiene, en un enlace que sirve una sola vez.

**Si la clave ve varias empresas en el portal, crea una conexión por empresa.** Cada una declara su propio «RUT de la empresa», y Connect deja el portal en esa empresa antes de leer nada, en cada sincronización. Es la misma regla de siempre: la conexión ES la empresa. Eso es lo que hace que los movimientos de una nunca terminen contados en la otra. Si la clave no tiene acceso a la empresa que declaraste, la conexión falla al crearse y te dice por qué.

## Los alcances [#los-alcances]

| Alcance                | Qué trae                                                                                                | Tool que lo lee                               |
| ---------------------- | ------------------------------------------------------------------------------------------------------- | --------------------------------------------- |
| `saldos`               | El saldo disponible y la línea de crédito de cada cuenta, como una foto por día                         | `bch_empresas.saldos.consultar`               |
| `movimientos`          | Los movimientos de cuentas CLP y USD del período, con moneda, descripción, canal y saldo arrastrado     | `bch_empresas.movimientos.consultar`          |
| `movimientos_tarjetas` | Movimientos pendientes y facturados de tarjetas, en CLP y USD                                           | `bch_empresas.movimientos_tarjetas.consultar` |
| `cartolas`             | El extracto mensual ya emitido por el banco, con su número de cartola y los saldos de apertura y cierre | `bch_empresas.cartolas.consultar`             |

Qué alcances quedan habilitados se decide por conexión, desde el dashboard. El gate de alcances rechaza la llamada **entera** si uno solo de los alcances pedidos no está habilitado: pedir `["saldos","movimientos","movimientos_tarjetas"]` sobre una conexión que no habilitó `movimientos_tarjetas` no sincroniza los otros dos, sino que devuelve `403 alcance_not_enabled` completo. Las conexiones anteriores a un alcance nuevo no lo reciben de forma implícita: se habilita desde `/ajustes` como cualquier otro alcance.

## Sincronizar [#sincronizar]

```bash
curl -X POST https://connect.emisso.ai/api/v1/tools/bch_empresas.conexion.sincronizar/execute \
  -H "Authorization: Bearer connect_sk_..." \
  -H "X-Connect-Connection: conn_9tKfR2mQx4Vb" \
  -H "Content-Type: application/json" \
  -d '{"input":{"periodo":"2026-08","alcances":["saldos","movimientos","movimientos_tarjetas"]}}'
```

Respuesta (recortada):

```json
{
  "data": {
    "periodo": "2026-08",
    "results": [
      { "alcance": "saldos", "status": "ok", "recordsSynced": 2 },
      { "alcance": "movimientos", "status": "ok", "recordsSynced": 41 },
      { "alcance": "movimientos_tarjetas", "status": "ok", "recordsSynced": 18 }
    ]
  },
  "meta": { "request_id": "req_...", "tool_id": "bch_empresas.conexion.sincronizar", "plane": "read" }
}
```

Un solo login del Portal Empresas cubre los alcances pedidos, y el logout corre al terminar. Como en los demás bancos, `saldos` es una foto del momento: en un período que no es el corriente devuelve cero con su explicación en `detalle`. Y el login no exige esperarlo en línea: el trabajo se encola por la [API de control](/docs/api-control). Habilita cada alcance antes de incluirlo; pedir uno deshabilitado tumba la sincronización entera, no solo ese alcance.

### Cómo se leen las tarjetas [#cómo-se-leen-las-tarjetas]

Las tarjetas no aparecen en el mismo catálogo que las cuentas. Connect usa el catálogo de tarjetas del portal y, por cada tarjeta, consulta la foto de movimientos no facturados, las fechas de facturación y los estados de cuenta nacional e internacional. El estado nacional produce filas CLP y el internacional filas USD. La consulta no entrega PAN ni el identificador crudo del banco: expone solo los últimos cuatro dígitos cuando la atribución es inequívoca y referencias opacas estables.

Los pendientes solo se consultan para el mes corriente, porque son una foto mutable. Los estados facturados sí se consultan por las fechas del período solicitado. Cuando una operación pasa de pendiente a facturada, la versión facturada prevalece y una sincronización posterior no puede degradarla de nuevo a pendiente.

## Verdades operativas [#verdades-operativas]

### La ventana es de 45 días por consulta, y el rechazo es HTTP 300 [#la-ventana-es-de-45-días-por-consulta-y-el-rechazo-es-http-300]

Cada consulta de movimientos al banco acepta como máximo 45 días de rango. Una ventana más ancha no vuelve con un código 4xx habitual: vuelve con **HTTP 300**, un estado de redirección que un cliente HTTP común seguiría en silencio como si fuera un enlace válido. Connect intercepta ese 300 antes de seguirlo y lo traduce a `upstream_unexpected_response` (502, no reintentable) con un marcador que identifica el rechazo de la ventana. Ese código también cubre otras respuestas deterministas que no cumplen el contrato esperado del portal, como una forma JSON desconocida, una moneda o discriminante no reconocido, o una respuesta que excede los límites de tamaño. Por eso el código por sí solo no demuestra que la ventana sea la causa: revisa el `marcador` seguro del alcance y conserva el `request_id` para reportarlo. `bch_empresas.conexion.sincronizar` pide siempre un mes calendario (máximo 31 días), así que tu sync nunca choca con el límite de 45 días por sí solo.

### La retención es de \~6 años [#la-retención-es-de-6-años]

El banco acepta consultas de hasta aproximadamente 6 años atrás: una ventana de 30 días ubicada 1, 3 o 5 años atrás responde HTTP 200, aunque en la medición volvió sin movimientos, así que ese 200 vacío no distingue "no hubo movimientos en esa ventana" de "la cuenta no existía todavía". La misma ventana ubicada 7 años atrás el banco la rechaza con el mismo HTTP 300 que usa para una ventana demasiado ancha: recién ahí queda claro que la fecha cayó fuera de lo que el banco conserva. Si necesitas historia más allá de esos \~6 años, no hay forma de conseguirla: el corte lo pone el banco, no Connect.

### El `x-xsrf-token` rota a mitad de sesión [#el-x-xsrf-token-rota-a-mitad-de-sesión]

El header `x-xsrf-token` que protege cada POST del portal no queda fijo durante todo el login: el banco lo reemite en ciertas respuestas, y el valor cambia. Connect relee la cookie del jar inmediatamente antes de cada POST, nunca cachea el valor del login. Un cliente que lo cachee del primer login funciona en las primeras llamadas y después empieza a fallar de forma intermitente, justo cuando el banco rota el token, un patrón difícil de reproducir porque depende del momento exacto de la rotación.

### El flag de paginación es `"Y"`/`"N"`, no `"S"` [#el-flag-de-paginación-es-yn-no-s]

El indicador de "hay más páginas" que devuelve el banco (`pagina[].masPaginas`) usa las letras en inglés, `"Y"` o `"N"`, nunca la `"S"` de "sí" que uno esperaría en un portal en castellano. Un cliente que compare contra `"S"` nunca la encuentra, corta después de la primera página y trunca en silencio: parece que la cuenta tiene pocos movimientos cuando en realidad hay más sin traer. Connect no usa ese flag para decidir si sigue paginando: el corte real es que el índice de término llegue al total de registros, el mismo criterio sin importar qué letra mande el banco.

### La cartola emitida es mensual y llega en MT940 [#la-cartola-emitida-es-mensual-y-llega-en-mt940]

El extracto que el banco emite una vez al mes (la cartola) no es el mismo feed que `movimientos`: es un objeto propio, con su propio saldo de apertura y de cierre. El único formato en que el banco la entrega con esos dos datos es **MT940**, el estándar SWIFT de mensajería bancaria: trae el número de cartola (tag 28C) y los saldos de apertura y cierre (tags 60/62), que ningún otro formato del portal declara en su encabezado. El único formato alternativo medido, el XLS, no tiene esos dos campos y sus columnas para identificar cada línea (número de documento, transacción, caja) vienen en cero en toda la muestra medida; el PDF y el TXT no se llegaron a inspeccionar. `bch_empresas.cartolas.consultar` ya devuelve el número de cartola y los saldos parseados desde MT940: no hace falta interpretarlo por tu cuenta.

### El logout no invalida la sesión del lado del banco [#el-logout-no-invalida-la-sesión-del-lado-del-banco]

Está medido, más de una vez: guardando la cookie de sesión antes de que Connect cierre la conexión con el banco y reinyectándola después, el banco sigue respondiendo autenticado con HTTP 200. Connect igual llama al logout en cada sincronización (son tres peticiones baratas y es lo que hace el propio portal), pero eso limpia el estado del lado de Connect, no el del banco: ninguna integración debería asumir que, tras un logout, la sesión anterior quedó inutilizable. Como Connect nunca persiste esa sesión entre sincronizaciones, el residual queda enteramente del lado del banco, que la expira sola pasado un tiempo de inactividad.

## Errores que vas a ver [#errores-que-vas-a-ver]

| Código                               | Qué significa                                                                                                                                                                                                | Qué hacer                                                                                                                                           |
| ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| `428 connection_credential_required` | La conexión no tiene una credencial viva: nunca se vinculó, venció o fue revocada.                                                                                                                           | Emite un enlace de reconexión y pide la clave de nuevo. No reintentes con la credencial anterior.                                                   |
| `409 connection_busy`                | La conexión está en uso o el servicio alcanzó temporalmente su capacidad.                                                                                                                                    | Espera unos segundos y reintenta. No reconectes ni vuelvas a ingresar credenciales.                                                                 |
| `409 connection_sync_in_progress`    | Ya corre una sincronización de esa conexión para ese período.                                                                                                                                                | Espera a que termine, o consulta directamente: puede que ya haya datos.                                                                             |
| `403 alcance_not_enabled`            | La conexión no tiene habilitado el alcance que la tool pide.                                                                                                                                                 | Habilítalo en la conexión desde el dashboard, o quítalo del input de la sincronización.                                                             |
| `409 connection_identity_mismatch`   | La empresa que ve el banco no coincide con la que declara la conexión: la clave no tiene acceso a esa empresa en el portal, o una fila sincronizada trajo el RUT de otra.                                    | Revisa que el «RUT de la empresa» sea el correcto y que esa clave pueda operarla en el portal. No reintentes sin revisar: reintentar no lo arregla. |
| `502 upstream_unexpected_response`   | El portal devolvió una respuesta determinista fuera del contrato: puede ser un rechazo HTTP 300 de ventana, una forma o discriminante desconocido, una moneda no soportada o una respuesta demasiado grande. | No reintentes a ciegas. Revisa el `marcador` seguro del alcance y repórtalo junto con el `request_id`; no copies el payload crudo del banco.        |
| `502 upstream_error`                 | El banco (o el camino hasta él) falló de forma transitoria.                                                                                                                                                  | Reintenta más tarde. Si persiste, el problema está del lado del banco.                                                                              |

El envelope de cada código está en el [catálogo de errores](/docs/operar/errores).

## Próximos pasos [#próximos-pasos]

* Las seis tools, con contrato completo: [referencia de `bch_empresas`](/docs/referencia/bch_empresas).
* La separación entre escribir la caché y leerla: [Sincronizar y consultar](/docs/conceptos/sincronizar-consultar).
* Saber de cada sync sin preguntar: [Webhooks](/docs/operar/webhooks).
