Saltar al contenido
Agents Finance
Caso de usoEvidencia: Caso real

Un fundador de una SaaS deja que Hermes Agent lleve su Zoho Books junto a un contable y enumera los siete errores que corrigió

Resumen: Chintan Prajapati, consejero delegado de Satva Solutions, conectó Hermes Agent a Gmail, a su banco, a Stripe y a una segunda empresa de Zoho Books para su SaaS de Singapur, y lo compara cada mes con los libros de su contable. Relato de primera mano con estimaciones suyas sin auditar y siete errores corregidos; su empresa vende estos montajes.

Publicada el 14 min de lectura3043 palabras

Diagrama: las facturas de Gmail y los datos de Aspire y Stripe pasan por Hermes Agent hasta la empresa de Zoho Books del agente, con excepciones para el fundador y comparación mensual.
Diagrama propio del montaje que describe Chintan Prajapati.Imagen: Agents Finance · Obra propia

Qué hacen y cómo

Chintan Prajapati tiene una empresa de software por suscripción (SaaS) en Singapur cuya contabilidad hacía una vez al año el servicio con el que la constituyó. Contrató a un contable interno para llevarla cada mes y, en paralelo, abrió una segunda empresa en Zoho Books que lleva Hermes Agent, el agente de código abierto de Nous Research. El agente lee las facturas y extractos reenviados a una dirección de Gmail, extrae sus datos, registra las facturas de proveedor en su divisa original, importa a Zoho los movimientos del banco y las tarjetas de Aspire, propone emparejamientos, pide los tiques que faltan con un solo correo semanal por titular de tarjeta y prepara para contabilizar los cobros, devoluciones y liquidaciones de Stripe. Cuando duda, le pregunta por Telegram. No hay servidor MCP: el agente escribió sus propios scripts de Python contra las API oficiales y los guardó como skills que ahora se ejecutan con calendario.

Resultado declarado

Según Chintan Prajapati: unas 6-8 horas de trabajo efectivo de puesta en marcha en uno o dos días; 89 movimientos extraídos de dos meses de extractos; 145 movimientos de saldo y 36 liquidaciones de Stripe recuperados y convertidos en 357 líneas de la cuenta puente; y una bajada estimada de 7-13 horas al mes de contabilidad rutinaria a 45-65 minutos de revisión de excepciones (un 85-90 %), con la puesta en marcha amortizada en menos de un mes. Son estimaciones suyas para una empresa de ese tamaño, sin medir ni auditar, y la comparación con los libros del contable sigue en curso. Enumera siete errores del agente; el más grave, registrar facturas por la base en lugar de por lo que realmente se cargó.

Cifras declaradas. Una persona u organización identificada declara que lo ha usado. Los resultados son declarados por la fuente; no los hemos auditado.

Qué se ha probado de verdad

El 25 y el 26 de octubre de 2026 leímos el artículo de Chintan Prajapati (2 de junio de 2026) y la publicación de LinkedIn de su compañero Kathaan Shah que lo resume, ambos de Satva Solutions, así que no hay una segunda fuente independiente. Comprobamos la licencia, el formato de las skills, las tareas programadas y los ajustes de aprobación de Hermes Agent en su documentación y su repositorio oficiales (commit ff29484), el flujo OAuth, los ámbitos y los endpoints bancarios de Zoho Books en su documentación de la API, y los precios de Nous Portal en la web oficial. No hemos visto sus scripts, sus skills ni sus libros, y el artículo no da el nombre de la SaaS. Los dos scripts de Python que trabajan con archivos solo se han ejecutado con datos ficticios; el de sincronización con Zoho, solo contra un servidor falso local; el resto son plantillas sin probar.

Cómo replicarlo

Abre una segunda empresa en Zoho Books (o un entorno de pruebas) para que el agente nunca toque la contabilidad que firma tu contable. Dale a Hermes Agent una dirección de Gmail para los documentos, claves de solo lectura del banco y de Stripe, y una conexión con Zoho cuyo primer intercambio de tokens hagas tú. Escribe tus reglas contables en una skill antes de que registre nada: facturas por el importe total a pagar, plan de cuentas, divisas e impuestos leídos en directo al empezar cada ejecución, movimientos importados en Zoho antes de emparejar, una regla de emparejamiento estricta y una cola de excepciones en lenguaje contable. Deja todo en borrador hasta que lo apruebes, programa las tareas y compara los dos juegos de libros cada cierre de mes.

En dos frases

El fundador de una SaaS de Singapur conectó Hermes Agent, el agente de código abierto de Nous Research, a Gmail, a su banco, a Stripe y a una segunda empresa de Zoho Books, y le deja las facturas de proveedor, la importación bancaria, la conciliación y la reclamación de tiques mientras un contable lleva en paralelo otra contabilidad para comparar. Es un relato de primera mano, con estimaciones suyas y una lista honesta de siete errores: en todos, el agente «razonaba como programador, no como contable».

Por qué este caso y por qué ahora

Hermes Agent es uno de los agentes de los que más se habla este otoño. Su repositorio de GitHub supera las 250.000 estrellas, X contaba cerca de 7.000 publicaciones que lo mencionaban en los siete días hasta el 25 de octubre de 2026, y la comunidad de Nous Research recopila historias de gente que lo usa para trabajar. Casi todas esas historias caben en una línea. Este es más largo e incluye los fallos. Lo escribió Chintan Prajapati en el blog de su empresa el 2 de junio de 2026, y un compañero lo resumió en LinkedIn en agosto. Interesa porque el agente es generalista, sin skills contables; porque no hay servidor MCP (el agente escribió sus propios scripts), y porque trabaja junto a un contable humano, en otra empresa de Zoho Books.

Quién lo hace

Chintan Prajapati es consejero delegado de Satva Solutions, una empresa que desarrolla integraciones contables y ahora vende flujos de trabajo contables con agentes. También es dueño de una SaaS constituida en Singapur. El artículo no da su nombre, así que nosotros tampoco. Al principio, el servicio con el que la constituyó le hacía la contabilidad una vez al año, sin cierre mensual. Al crecer necesitaba cifras mensuales, y encontraba cargos duplicados, suscripciones canceladas que seguían cobrando y sobrecargos de proveedores que nadie veía en meses.

Contrató a un contable interno para llevar la contabilidad mensual en Zoho Books. Y abrió una segunda empresa en Zoho Books con los datos de la misma sociedad para dársela a un agente, para comparar ambas a fin de mes.

Ten en cuenta su interés comercial: el artículo termina ofreciendo montar lo mismo a los lectores, y las cifras son estimaciones suyas.

Herramientas y arquitectura

Diagrama de los siete errores de Hermes Agent en Zoho Books y la regla que le dio el fundador para cada uno.
Las siete correcciones según Chintan Prajapati · Diagrama de Agents Finance, obra propia

La idea es que cada sistema siga haciendo su trabajo y el agente mueva los datos entre ellos:

  • Zoho Books, la segunda empresa, es el registro contable del lado del agente, que contabiliza en borrador hasta que él lo confirma.
  • Gmail es la bandeja de documentos. Reenvió un trimestre de facturas de proveedor y de extractos de banco y tarjeta a una dirección propia, con etiquetas para bandeja, proveedores, banco, tarjetas, procesado y «preguntar al dueño».
  • Aspire aporta la cuenta de empresa y dos tarjetas corporativas, la suya y la de una compañera, con extractos en PDF.
  • Stripe aporta los ingresos: suscripciones, cobros, devoluciones y liquidaciones. El agente lo lee con una clave restringida de solo lectura.
  • Telegram es donde el agente le pregunta.
  • Hermes Agent está en el medio. De serie traía 91 skills generales y unas diez servían aquí: Gmail, Google Workspace, OCR y lectura de documentos, automatización del navegador y programación de tareas. No había ninguna skill contable. Él no escribió código. El agente escribió scripts de Python contra las API de Zoho Books, Gmail, Stripe y Telegram, los guardó en una carpeta de trabajo y convirtió los patrones reutilizables en skills de Hermes, de modo que la ejecución del mes siguiente no necesita volver a enseñarle nada.

Paso a paso, con lo que salió mal

Describe nueve fases, y en casi todas la primera versión falló de una forma que enseña algo.

  1. Entrada del correo. El primer clasificador solo miraba el asunto y el nombre del adjunto, y su confianza no pasaba del 60-70 %. Le pidió que leyera la primera o las dos primeras páginas de cada PDF y que le enviara a él todo lo que no llegara al 70 %. La cola de «preguntar al dueño» se quedó «casi en nada».
  2. Extracción. Proveedor, fecha, divisa, líneas y totales pasaron a JSONL y CSV. Sacó 89 movimientos de dos meses de extractos de Aspire y las líneas de cuatro facturas de proveedor sin errores, y funcionó a la primera.
  3. Plan de cuentas y conexión con Zoho. Fue la fase más dolorosa. Le dio al agente lo que creía un token de refresco y era un código de concesión de un solo uso, y la autenticación fallaba una y otra vez. Después las facturas fallaban con «Account does not exist», porque el CSV de 72 cuentas que había subido llevaba identificadores de otra empresa de pruebas. La solución fue un script que lee de Zoho, al empezar cada ejecución, el plan de cuentas, las divisas y los impuestos vigentes.
  4. Varias divisas. Los proveedores facturan en USD, GBP e INR y la moneda funcional es el SGD. El agente comprobó que esas divisas estaban activas y registra cada factura en su divisa original.
  5. Facturas de proveedor. Aquí estuvo el error grave. Como no había ningún código de impuesto asignado, el agente registró dos facturas de alojamiento por 110,00 USD en lugar de los 119,90 USD que se iban a cargar en la tarjeta, porque «sin código de impuesto, no hay impuesto» le parecía limpio. La conciliación se habría descuadrado 9,90 USD por factura. Su regla: la factura se registra por el importe total que se paga al proveedor, y el desglose del impuesto se guarda aparte.
  6. Importación bancaria. El agente había extraído los 89 movimientos a un archivo local «de preparación» y nunca los envió a Zoho, porque su bucle de contabilización solo cubría proveedores y facturas. Ahora importa primero las líneas del extracto a la cuenta bancaria de Zoho, sin categorizar, y empareja allí. Además le pidió que las repeticiones no duplicaran nada, y en la segunda ejecución no hubo ningún duplicado.
  7. Conciliación. El agente llamaba «candidatos» a los emparejamientos propuestos, así que le hizo usar las palabras de un contable: propuesto, aprobado, excepción, sin categorizar. Peor: emparejó una comisión de plataforma con un cargo de tarjeta de 30 días de distancia porque coincidía el importe, cuando la tarjeta que se usó de verdad aún no tenía el extracto cargado. La regla nueva exige importe exacto, fechas próximas, coincidencia aproximada del nombre del proveedor y el extracto correcto; si falta algo, se queda como excepción. El resultado pasó a ser «dos emparejamientos seguros y una excepción honesta».
  8. Tiques. El agente quería mandar 43 correos sueltos por 43 tiques que faltaban. Ahora cada titular de tarjeta recibe un único resumen semanal con una tabla de pendientes y un identificador de solicitud que vincula cada respuesta con su movimiento. Sale cada viernes a las 08:00 (hora de la India).
  9. Stripe. Con la clave de solo lectura recuperó 145 movimientos de saldo y 36 liquidaciones y generó 357 líneas de la cuenta puente listas para contabilizar. Para entonces «apenas tuvo que intervenir».

Todo funciona ahora como tareas programadas. La entrada de Gmail se ejecuta cada dos horas; la sincronización de Stripe y de la Shopify App Store, cada seis; las peticiones de liquidaciones de Shopify Partner, cada pocos días; y el resumen de tiques, cada viernes. Él solo trabaja la cola de excepciones.

Cópialo y adáptalo

Satva no ha publicado sus scripts, skills ni prompts, así que nada de lo que sigue es suyo. Hermes Agent tiene licencia MIT. Todo esto es una plantilla de Agents Finance basada en las reglas que describe Chintan Prajapati, escrita con los formatos documentados de Hermes Agent (SKILL.md, AGENTS.md, ~/.hermes/config.yaml, hermes cron). Los tres scripts de Python se publican con licencia MIT-0. Usa un entorno de pruebas o una segunda empresa, nunca tus libros reales, hasta que te fíes.

Requisitos previos

  • Hermes Agent instalado (curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash o la app de escritorio) y un proveedor de modelos al que puedas enviar datos financieros.
  • Una empresa de Zoho Books para el agente y un «Self Client» en la consola de API de Zoho. El primer intercambio del código de concesión por un token de refresco hazlo tú: el código caduca en minutos y solo sirve una vez.
  • Tokens separados. Uno de solo lectura (ZohoBooks.accountants.READ, ZohoBooks.settings.READ, ZohoBooks.banking.READ, ZohoBooks.bills.READ) para las tareas diarias, y otro de escritura de facturas y banca solo cuando vayas a aprobar borradores.
  • Una dirección de Gmail propia, una clave restringida de Stripe de solo lectura y un bot de Telegram conectado a Hermes.

1. AGENTS.md de la carpeta de contabilidad (plantilla de Agents Finance)

Hermes carga el AGENTS.md de la carpeta de trabajo al empezar cada sesión.

# Contabilidad de <Empresa> (empresa del agente en Zoho Books)

## Alcance
- Llevas la SEGUNDA empresa de Zoho Books (id en ZOHO_ORG_ID). Nunca uses credenciales de la empresa que lleva el contable.
- Moneda funcional: <SGD>. Los proveedores pueden facturar en <USD, GBP, INR>: registra en la divisa original.
- Fuentes: etiqueta de Gmail Contabilidad/Entrada, extractos de banco y tarjeta (PDF), Stripe (clave de solo lectura).

## Reglas innegociables
1. Antes de contabilizar nada, ejecuta scripts/sync_zoho_settings.py y usa solo identificadores de cuentas, divisas e impuestos de zoho_settings.json.
2. Una factura de proveedor se registra por el TOTAL que se le pagará. Nunca quites una línea de impuesto porque falte el código: registra el total, anota el desglose en las notas y añade la factura a la cola de excepciones.
3. Los movimientos de banco y tarjeta se importan primero a la cuenta bancaria de Zoho (sin categorizar). El emparejamiento se hace en Zoho, nunca solo en un archivo local.
4. Para emparejar hacen falta importe exacto, fechas a 3 días como máximo, coincidencia del proveedor y el extracto de la tarjeta que se usó de verdad. Si falta algo, es una excepción.
5. Clasifica los documentos leyendo el PDF, no el nombre del archivo. Con menos del 70 % de confianza -> etiqueta «Preguntar al dueño» y pregúntame por Telegram.
6. Todo lo que crees se queda en borrador hasta que yo lo apruebe. Repetir una ejecución no puede crear duplicados.

## Lenguaje
Escribe para contables: emparejamiento propuesto, emparejamiento aprobado, excepción, sin categorizar. No «candidato», «registro» ni «payload».

## Personas
Un correo semanal por titular de tarjeta con una tabla de tiques pendientes y un identificador de solicitud por línea. Nunca un correo por tique.

2. Skill: rutina de Zoho Books (plantilla de Agents Finance)

Guárdala como ~/.hermes/skills/zoho-books-bookkeeping/SKILL.md, con los scripts en scripts/ al lado.

---
name: zoho-books-bookkeeping
description: Contabilidad semanal y de cierre de mes de una empresa de Zoho Books - sincronizar ajustes, importar movimientos, proponer emparejamientos, poner en cola las excepciones
version: 1.0.0
author: Plantilla de Agents Finance
license: MIT-0
metadata:
  hermes:
    tags: [Contabilidad, Zoho Books, Conciliación]
required_environment_variables:
  - name: ZOHO_ORG_ID
    prompt: "organization_id de Zoho Books de la empresa del agente"
  - name: ZOHO_CLIENT_ID
    prompt: "ID del Self Client"
  - name: ZOHO_CLIENT_SECRET
    prompt: "Secreto del Self Client"
  - name: ZOHO_REFRESH_TOKEN
    prompt: "Token de refresco (ámbitos de solo lectura para las tareas programadas)"
---

# Contabilidad en Zoho Books

## Cuándo usarla
Conciliación semanal, resumen de tiques de los viernes y comparación de cierre de mes con los libros del contable.

## Procedimiento
1. Ejecuta `python scripts/sync_zoho_settings.py zoho_settings.json`. Si falla, para.
2. Exporta los movimientos de banco y tarjeta del periodo, las facturas y gastos contabilizados y la lista de extractos ya cargados a bank.csv, docs.csv y statements.csv.
3. Ejecuta `python scripts/match_bank_lines.py bank.csv docs.csv statements.csv proposals.csv`.
4. Envíame por Telegram el recuento por estado y cada excepción con su motivo. No apruebes nada tú.
5. Las líneas «uncategorized» van a la tabla semanal de tiques de su titular.
6. A fin de mes, pídeme los dos balances de sumas y saldos y ejecuta `python scripts/compare_books.py accountant_tb.csv agent_tb.csv`. Explica cada diferencia en una línea.

## Errores conocidos
- Un código de concesión no es un token de refresco. Si la autenticación falla con invalid_code, pídeme que repita el intercambio; nunca entres en bucle.
- Que falte un código de impuesto es una excepción, no un motivo para cambiar el importe.
- «Procesado en local» no es «contabilizado»: comprueba que la línea existe en Zoho.

## Comprobación
El número de líneas sin categorizar en Zoho del periodo coincide con el de líneas de las tablas de tiques.

3. Sincronización en directo de los ajustes (plantilla de Agents Finance, MIT-0)

Es de solo lectura. Renueva el token de acceso y descarga el plan de cuentas, las divisas y los impuestos, para no contabilizar nunca contra una exportación caducada. Usa los endpoints documentados de Zoho. Si tu cuenta está en otro centro de datos (UE, India…), cambia las dos URL; en España lo normal es accounts.zoho.eu y www.zohoapis.eu.

#!/usr/bin/env python3
# sync_zoho_settings.py: Agents Finance template based on the "sync settings
# from live Zoho" step Chintan Prajapati describes (Satva Solutions, June 2026).
# Licence: MIT-0. Read-only: it refreshes the access token and downloads the
# chart of accounts, currencies and taxes, so the agent never posts against a
# stale CSV. Scopes it needs: ZohoBooks.accountants.READ, ZohoBooks.settings.READ
import json
import os
import sys
import urllib.parse
import urllib.request
from datetime import datetime, timezone

ACCOUNTS_URL = os.environ.get("ZOHO_ACCOUNTS_URL", "https://accounts.zoho.com")
API_URL = os.environ.get("ZOHO_API_URL", "https://www.zohoapis.com/books/v3")


def access_token():
    # The refresh token is long-lived; the one-time grant code (valid for minutes)
    # is exchanged only once, by a person, when the connection is set up.
    params = urllib.parse.urlencode({
        "refresh_token": os.environ["ZOHO_REFRESH_TOKEN"],
        "client_id": os.environ["ZOHO_CLIENT_ID"],
        "client_secret": os.environ["ZOHO_CLIENT_SECRET"],
        "grant_type": "refresh_token",
    })
    req = urllib.request.Request(f"{ACCOUNTS_URL}/oauth/v2/token?{params}", method="POST")
    with urllib.request.urlopen(req, timeout=30) as r:
        body = json.load(r)
    if "access_token" not in body:
        sys.exit(f"token refresh failed: {body.get('error', 'unknown error')}")
    return body["access_token"]


def get(token, path, key):
    org = os.environ["ZOHO_ORG_ID"]
    items, page = [], 1
    while True:
        url = f"{API_URL}/{path}?organization_id={org}&page={page}&per_page=200"
        req = urllib.request.Request(url, headers={"Authorization": f"Zoho-oauthtoken {token}"})
        with urllib.request.urlopen(req, timeout=30) as r:
            body = json.load(r)
        if body.get("code") != 0:
            sys.exit(f"{path}: {body.get('message')}")
        items += body.get(key, [])
        if not body.get("page_context", {}).get("has_more_page"):
            return items
        page += 1


def main(out_path):
    token = access_token()
    snapshot = {
        "taken_at": datetime.now(timezone.utc).isoformat(timespec="seconds"),
        "organization_id": os.environ["ZOHO_ORG_ID"],
        "accounts": [{"account_id": a["account_id"], "account_name": a["account_name"],
                      "account_type": a.get("account_type"), "is_active": a.get("is_active")}
                     for a in get(token, "chartofaccounts", "chartofaccounts")],
        "currencies": [{"currency_id": c["currency_id"], "currency_code": c["currency_code"]}
                       for c in get(token, "settings/currencies", "currencies")],
        "taxes": [{"tax_id": t["tax_id"], "tax_name": t["tax_name"],
                   "tax_percentage": t.get("tax_percentage")}
                  for t in get(token, "settings/taxes", "taxes")],
    }
    with open(out_path, "w", encoding="utf-8") as f:
        json.dump(snapshot, f, indent=2, ensure_ascii=False)
    print(f"{len(snapshot['accounts'])} accounts, {len(snapshot['currencies'])} currencies, "
          f"{len(snapshot['taxes'])} taxes -> {out_path}")


if __name__ == "__main__":
    main(sys.argv[1] if len(sys.argv) > 1 else "zoho_settings.json")

4. La regla de emparejamiento en código (plantilla de Agents Finance, MIT-0)

Es la regla de la fase 7, escrita para poder probarla: importe exacto, margen de fechas, coincidencia del proveedor y extracto de la tarjeta correcta. Lo dudoso se queda como excepción, y el script nunca escribe en Zoho.

#!/usr/bin/env python3
# match_bank_lines.py: Agents Finance template based on the matching rule
# Chintan Prajapati describes (Satva Solutions, June 2026). Licence: MIT-0.
# Proposes matches between bank/card lines and posted bills or expenses.
# It never writes to Zoho Books: it only produces a CSV for a human to approve.
import csv
import sys
from datetime import date
from difflib import SequenceMatcher

DATE_WINDOW_DAYS = 3        # maximum distance between bill date and charge date
VENDOR_MIN_SIMILARITY = 0.6  # fuzzy match on the vendor name (0 to 1)


def norm(text):
    return " ".join("".join(c.lower() if c.isalnum() else " " for c in text).split())


def vendor_score(vendor, description):
    v, d = norm(vendor), norm(description)
    if v and v in d:
        return 1.0
    return SequenceMatcher(None, v, d).ratio()


def load(path):
    with open(path, newline="", encoding="utf-8") as f:
        return list(csv.DictReader(f))


def main(bank_csv, docs_csv, statements_csv, out_csv):
    bank = load(bank_csv)            # line_id,date,amount,currency,description,card
    docs = load(docs_csv)            # doc_id,date,total,currency,vendor,paid_with
    ingested = {r["card"] for r in load(statements_csv)}  # card,period
    used_lines = set()
    rows = []
    for doc in docs:
        result = {"doc_id": doc["doc_id"], "vendor": doc["vendor"],
                  "total": doc["total"], "line_id": "", "status": "", "reason": ""}
        if doc["paid_with"] not in ingested:
            result.update(status="exception",
                          reason=f"statement for {doc['paid_with']} not ingested yet")
            rows.append(result)
            continue
        candidates = []
        for line in bank:
            if line["line_id"] in used_lines or line["card"] != doc["paid_with"]:
                continue
            if line["currency"] != doc["currency"]:
                continue
            if abs(float(line["amount"]) - float(doc["total"])) > 0.005:
                continue
            gap = abs((date.fromisoformat(line["date"]) - date.fromisoformat(doc["date"])).days)
            if gap > DATE_WINDOW_DAYS:
                continue
            score = vendor_score(doc["vendor"], line["description"])
            if score < VENDOR_MIN_SIMILARITY:
                continue
            candidates.append((gap, -score, line["line_id"]))
        if len(candidates) == 1:
            line_id = candidates[0][2]
            used_lines.add(line_id)
            result.update(line_id=line_id, status="proposed match",
                          reason="amount, date and vendor agree")
        elif len(candidates) > 1:
            result.update(status="exception",
                          reason=f"{len(candidates)} possible lines: a person must choose")
        else:
            result.update(status="exception", reason="no line meets all the rules")
        rows.append(result)
    for line in bank:
        if line["line_id"] not in used_lines and line["card"] in ingested:
            rows.append({"doc_id": "", "vendor": "", "total": line["amount"],
                         "line_id": line["line_id"], "status": "uncategorized",
                         "reason": "no receipt or bill: request it from the cardholder"})
    with open(out_csv, "w", newline="", encoding="utf-8") as f:
        writer = csv.DictWriter(f, fieldnames=list(rows[0].keys()))
        writer.writeheader()
        writer.writerows(rows)
    counts = {}
    for r in rows:
        counts[r["status"]] = counts.get(r["status"], 0) + 1
    print(", ".join(f"{k}: {v}" for k, v in sorted(counts.items())))


if __name__ == "__main__":
    if len(sys.argv) != 5:
        sys.exit("usage: match_bank_lines.py bank.csv docs.csv statements.csv out.csv")
    main(*sys.argv[1:])

5. Comparación de cierre de mes (plantilla de Agents Finance, MIT-0)

Este script compara cuenta a cuenta el balance de sumas y saldos del contable con el del agente, de mayor a menor diferencia.

#!/usr/bin/env python3
# compare_books.py: Agents Finance template for the month-end parallel run
# (accountant's Zoho Books organisation vs the agent's). Licence: MIT-0.
# Input: two trial balance exports as CSV with columns account,debit,credit.
import csv
import sys

TOLERANCE = 0.01


def balances(path):
    out = {}
    with open(path, newline="", encoding="utf-8") as f:
        for row in csv.DictReader(f):
            net = float(row["debit"] or 0) - float(row["credit"] or 0)
            key = row["account"].strip().lower()
            out[key] = out.get(key, 0.0) + net
    return out


def main(human_csv, agent_csv):
    human, agent = balances(human_csv), balances(agent_csv)
    for name, book in (("accountant", human), ("agent", agent)):
        total = round(sum(book.values()), 2)
        if abs(total) > TOLERANCE:
            print(f"WARNING: the {name} trial balance does not balance ({total:+.2f})")
    diffs = []
    for account in sorted(set(human) | set(agent)):
        h, a = human.get(account, 0.0), agent.get(account, 0.0)
        if abs(h - a) > TOLERANCE:
            diffs.append((abs(h - a), account, h, a))
    if not diffs:
        print("Both sets of books agree account by account.")
        return
    print(f"{len(diffs)} account(s) differ (largest first):")
    for gap, account, h, a in sorted(diffs, reverse=True):
        print(f"  {account}: accountant {h:,.2f} | agent {a:,.2f} | gap {gap:,.2f}")


if __name__ == "__main__":
    if len(sys.argv) != 3:
        sys.exit("usage: compare_books.py accountant_tb.csv agent_tb.csv")
    main(*sys.argv[1:])

6. Aprobaciones, credenciales y calendario (plantilla de Agents Finance)

En ~/.hermes/config.yaml, deja los comandos peligrosos detrás de una aprobación. Haz que las tareas programadas los rechacen en lugar de aprobarlos solas, y pasa las variables de Zoho solo a los scripts que las necesitan:

approvals:
  mode: manual        # always ask before dangerous shell commands
  cron_mode: deny     # a scheduled run that hits one stops instead of approving itself
terminal:
  env_passthrough:
    - ZOHO_ORG_ID
    - ZOHO_CLIENT_ID
    - ZOHO_CLIENT_SECRET
    - ZOHO_REFRESH_TOKEN

Después crea las tareas. Usamos los horarios que describe Satva; ajústalos a los tuyos:

hermes cron create "every 2h" "Procesa los documentos nuevos de la etiqueta de Gmail Contabilidad/Entrada" \
  --skill zoho-books-bookkeeping --name "gmail-intake"
hermes cron create "0 8 * * 5" "Envía a cada titular de tarjeta la tabla semanal de tiques pendientes" \
  --skill zoho-books-bookkeeping --name "receipt-summary"
hermes cron create "0 9 3 * *" "Cierre de mes: sincroniza ajustes, propone emparejamientos y compara los dos balances" \
  --skill zoho-books-bookkeeping --name "month-end-check"

Un ejemplo concreto (ficticio)

Ejecutamos el script de emparejamiento con datos inventados que reproducen los casos del artículo. Había dos facturas de IONOS de 119,90 USD con un mes de diferencia, una comisión de Shopify de 42,51 SGD fechada 30 días antes del cargo del mismo importe, otra comisión de Shopify pagada con una tarjeta cuyo extracto no se había cargado y un cargo de 15,00 USD sin justificante. Salieron dos emparejamientos propuestos, dos excepciones («no line meets all the rules» y «statement for card-sales not ingested yet») y dos líneas sin categorizar para el correo de tiques. El script de comparación, con dos balances en los que el agente había separado 19,80 USD de impuesto del gasto de alojamiento, señaló las dos cuentas con su diferencia.

Resultados que declara la fuente

Todas las cifras son de Chintan Prajapati:

  • Puesta en marcha: unas 6-8 horas de trabajo efectivo en uno o dos días. Lo que más tiempo llevó fueron los problemas de OAuth y del plan de cuentas (cerca de 1,5 horas) y la lógica de conciliación (cerca de 1,5 horas).
  • Volumen: 89 movimientos de banco y tarjeta de dos meses; 145 movimientos de saldo y 36 liquidaciones de Stripe convertidos en 357 líneas de la cuenta puente.
  • Tiempo: de unas 7-13 horas al mes de trabajo manual (extractos, facturas, conciliación, Stripe, tiques) a 45-65 minutos de revisión de excepciones, según su estimación. Es un 85-90 % menos, con la inversión recuperada en menos de un mes. Lo calcula para una empresa con dos o tres cuentas bancarias, una pasarela de pago y 10-20 facturas de proveedor al mes.
  • Calidad: siete correcciones lo convirtieron, en sus palabras, de una «demo que se cae en producción» en un «contable júnior que necesita que un sénior revise su trabajo».

No publica tasa de errores, tiempo de cierre ni el resultado de la comparación de fin de mes.

Limitaciones y riesgos

  • Una sola fuente, con interés. Los dos relatos son de Satva Solutions, que vende este servicio. La empresa no tiene nombre y nadie de fuera ha revisado los libros.
  • Poco volumen. Cuatro facturas de proveedor y 89 movimientos bancarios bastan para encontrar los fallos. No bastan para conocer la tasa de errores a gran escala.
  • El criterio sigue siendo humano. Lo dice él mismo. El agente no sabe decidir entre coste de ventas, gasto anticipado o pérdida, interpretar el IVA soportado o un convenio de doble imposición, tratar operaciones intragrupo o tipos de cambio raros, ni fijar la materialidad o cuánta justificación basta.
  • Scripts escritos por el agente. Son tus controles: alguien tiene que leerlos, versionarlos y volver a probarlos.
  • Los datos salen de casa. Hermes envía los resultados de las herramientas al proveedor de modelos que elijas. Los extractos bancarios y las facturas son confidenciales, así que elige proveedor y contrato en consecuencia, o usa un modelo local. En la UE, revisa además el encargo de tratamiento y las transferencias internacionales.
  • Credenciales. Los tokens de refresco de Zoho no caducan hasta que se revocan. Deja las tareas programadas con ámbitos de solo lectura y revoca los tokens que ya no uses.

Errores comunes

  • Dejar que el agente trabaje en la misma empresa que firma tu contable.
  • Subir el plan de cuentas una vez y fiarse de él para siempre.
  • Permitir que la falta de un código de impuesto cambie el importe de una factura.
  • Tratar un archivo local como si estuviera contabilizado.
  • Emparejar solo por importe, o emparejar cuando aún no está cargado el extracto correcto.
  • Mandar a la gente un mensaje por cada tique que falta.
  • Desactivar las aprobaciones (approvals.mode: off, modo YOLO) para que las tareas programadas «funcionen sin más».

Conclusiones

Lo más útil de este caso no es la estimación del 85-90 %. Es la lista de siete errores, porque cualquier empresa que pruebe un agente generalista con su contabilidad se va a encontrar los mismos. El agente hizo bien lo mecánico; cada vez que se equivocó, le faltaba una regla contable que nadie había escrito. Escribe esas reglas antes, en un AGENTS.md y en una skill. Pon al agente a trabajar en una segunda empresa y deja que la comparación de fin de mes con tu contable decida cuándo se ha ganado más confianza. Así, para un controller o una asesoría, «¿probamos Hermes Agent?» pasa a ser un proyecto con una prueba de salida clara.

Fuentes

Hemos redactado esta ficha con nuestras palabras a partir de las fuentes anteriores y comprobamos que los enlaces funcionaban el 26 de octubre de 2026. ¿Algún dato es incorrecto? Avísanos.

Comparte esta entrada

Esta entrada forma parte de la guía: Agentes de IA para contabilidad

Newsletter

El boletín semanal de Agents Finance

Una vez por semana, los casos de uso, prompts y tutoriales nuevos de IA en finanzas y ERPs. Sin spam; te das de baja con un clic.

Responsable: Agents Finance. Finalidad: enviarte el boletín semanal. Base: tu consentimiento, que puedes retirar en cualquier momento con el enlace de baja de cada email. Encargado: Mailrelay (WEBLEGAL SL). Derechos: acceso, rectificación, supresión y demás, escribiendo a info@agentsfinance.io.

Entradas relacionadas

Seguir explorando