Saltar al contenido principal

Automatiza la contabilidad con bea

Scriptea tus libros de Beancount con bea: resuelve el libro mayor explícitamente, analiza el sobre JSON con jq, decide según los códigos de salida, ejecuta sin supervisión y programa una verificación nocturna.

Un script maneja bea con cuatro decisiones: qué libro mayor lee, --json para salida legible por máquina, jq para el valor que necesita, y el código de salida en el que se bifurca. Esta guía recorre esas cuatro decisiones de principio a fin, y luego las programa.

Necesitas bea en la máquina que ejecuta el trabajo y un libro mayor al que pueda acceder. Si estás comenzando nuevos libros, sigue primero el inicio rápido CLI. Cada dato sobre flags, claves de sobre y códigos de salida se consulta en la referencia CLI de Beancount, no se repite aquí.

Elige el libro mayor explícitamente​

Nombra el archivo. Un comando local resuelve su destino desde --file, luego $BEA_FILE, luego ./main.bean en el directorio de trabajo, y un trabajo programado rara vez se ejecuta donde crees.

bea --file ~/books/main.bean check
BEA_FILE=~/books/main.bean bea check
cd ~/books && bea check

Las opciones globales van antes del comando, como en bea --file main.bean check. Si el archivo resuelto no existe, el comando sale con 2 y nombra las tres fuentes, para que un error tipográfico en una entrada cron falle con claridad en lugar de validar los libros incorrectos. La selección hospedada mediante una bandera --ledger aún no existe; bea nunca sube un archivo local implícitamente.

Lee el sobre JSON​

Agrega --json global y cada comando compatible responde con el mismo sobre: bea, target, data, truncated y limit en listas acotadas. Los montos son cadenas decimales y las fechas son ISO YYYY-MM-DD, así que un valor es seguro de comparar sin que un float entre jamás en la tubería. Las claves del sobre se tabulan en la referencia JSON y código de salida.

bea --json --file main.bean report income-statement | jq .data.net_profit
bea --json --file main.bean list transaction --limit 2 | jq '.data[0].postings[0].units'
bea --json --file main.bean import statement.csv --csv date=Date,amount=Amount,payee=Payee \
  --account Assets:Checking --apply --duplicates skip | jq '.data | {written, ready, duplicates}'

Esos tres comandos — report, list y import — mantienen su forma de resultado, así que una ruta jq escrita contra ellos permanece válida. bea --json check y bea --json query también emiten el sobre hoy, pero son los comandos entregados a los ejecutables nativos de Beancount, por lo que un script debería basarse en el estado de salida de check en lugar de su forma de salida. Elige tu política de duplicados deliberadamente: --duplicates sigue siendo necesario cuando una importación necesita una decisión, como explica el recorrido de importación.

Detente en el código de salida correcto​

Bifurca según el estado, y lee el objeto de error antes de reintentar cualquier cosa que escriba. En modo --json un fallo no escribe nada en stdout y exactamente un objeto en stderr, cuyo error.category nombra la clase: validation (1), usage (2), auth (3), conflict (4).

#!/usr/bin/env bash
set -euo pipefail
 
out=$(mktemp)
err=$(mktemp)
status=0
 
bea --json --file main.bean report income-statement >"$out" 2>"$err" || status=$?
 
case "$status" in
  0) jq -r '.data.net_profit | to_entries[] | "net profit: \(.value) \(.key)"' "$out" ;;
  4) echo "conflict — inspect the ledger before retrying" >&2
     jq -r '.error.message' "$err" >&2
     exit 4 ;;
  *) jq -r '.error | "\(.category) (exit \(.exit_code)): \(.message)"' "$err" >&2
     exit "$status" ;;
esac

Salir con 4 es algo que un script nunca debe reintentar a ciegas: significa que el resultado es un conflicto o es desconocido, como una edición externa que llega a mitad de escritura o un objetivo init que ya existe. Inspecciona el libro mayor y luego reintenta desde una lectura fresca. Salir con 1 cubre fallos de validación y cualquier otro error en tiempo de ejecución; error.details lleva los errores individuales del libro mayor, y error.result lleva lo que realmente hizo una escritura parcial. Un código de salida distinto de cero nunca garantiza que no haya cambiado nada.

Ejecutar sin terminal​

bea deja de solicitar por sí mismo. --no-input se implica siempre que stdin no sea un terminal, siempre que --json esté configurado y siempre que CI sea verdadero — 1, true, yes o on. En ese modo, una confirmación faltante falla con salida 2 en lugar de esperar para siempre.

CI=true BEA_NO_UPDATE_NOTIFIER=1 bea --json --file main.bean report balance-sheet
bea --json --file main.bean list transaction --limit 100 --sort oldest

Las lecturas son permisivas en un terminal y estrictas en cualquier otro lugar. Cuando el libro mayor tiene errores de carga, query, list y report salen con 1 bajo --json, bajo una salida stdout canalizada, bajo un CI verdadero, o con --strict; pasa el propio --allow-errors del comando para aceptar la respuesta parcial en su lugar, que también establece ledger_valid: false y llena ledger_errors en el JSON. --strict es la imagen espejo: rechaza respuestas parciales incluso en un terminal, que es lo que quieres cuando un humano ejecuta el mismo script a mano. bea check no tiene --allow-errors — reportar errores es toda su tarea — y siempre sale con 1 cuando encuentra alguno. Configura BEA_NO_UPDATE_NOTIFIER=1 para silenciar el aviso pasivo de actualización; un CI verdadero ya lo hace.

Programar una revisión​

Ejecuta una validación cada noche y deja que el código de salida sea la alerta. Ambos bloques a continuación son plantillas — las rutas, el horario y el ejecutor son tuyos.

# crontab -e — 07:15 daily; cron mails you only when bea exits nonzero
15 7 * * * BEA_NO_UPDATE_NOTIFIER=1 /opt/homebrew/bin/bea --file /home/alice/books/main.bean check
name: ledger
on:
  schedule:
    - cron: "15 7 * * *"
  push:
jobs:
  check:
    runs-on: ubuntu-latest
    env:
      BEA_NO_UPDATE_NOTIFIER: "1"
    steps:
      - uses: actions/checkout@v4
      - uses: astral-sh/setup-uv@v5
      - run: uv tool install beancount-io==0.2.0
      - run: bea --file main.bean check
      - run: bea --json --file main.bean report balance-sheet > balance-sheet.json

El primer comando respaldado por motor descarga el motor gestionado, por lo que el ejecutor necesita acceso a la red. Para reutilizarlo entre trabajos, cachea ~/.local/share/bea/engine con una clave que contenga el SO del ejecutor, arquitectura, versión de Python y la versión fijada de bea. Fija la versión cuando el trabajo deba ser reproducible, y suelta el fijado cuando prefieras seguir las versiones. CI ya es verdadero en GitHub Actions, por lo que las solicitudes están desactivadas y el aviso de actualización está silenciado antes de que configures algo. Aquí no hay paso de formato a propósito. Un trabajo programado no debe reescribir archivos que no tuvo que modificar, así que usa bea format main.bean --check en un hook pre-commit, que no toca nada y sale con 1 cuando un archivo necesita formato.

Usar una credencial alojada en un trabajo​

Establezca BEA_TOKEN, desde el almacén secreto de su proveedor de CI, y omita completamente el inicio de sesión en el navegador. El token se lee desde el entorno y nunca se escribe en disco, por lo que nada queda en el directorio de inicio del runner para que el siguiente trabajo lo encuentre.

export BEA_TOKEN="$YOUR_CI_SECRET"
bea --json cloud status

Salir 0 significa que las credenciales se resolvieron y el sobre nombra la cuenta a la que pertenece; salir 3 con error.category de auth significa que no, y el mensaje distingue una credencial no establecida de una rechazada. bea cloud logout no hace nada a un token suministrado de esta forma — ni lo revoca ni lo desestablece, ya que otro trabajo podría compartirlo — así que revoque un token filtrado desde el panel en su lugar. Los comandos locales no necesitan credenciales en absoluto; solo bea cloud y bea ask alcanzan el servicio alojado. La lista completa de variables está en la referencia de configuración.

No todo responde en JSON. bea ask rechaza el modo JSON de inmediato, bea cloud login necesita un humano, y un bea cloud logout o bea cloud ledger clone exitoso no devuelve un objeto de éxito en JSON — lea su estado de salida en su lugar. La ayuda, la versión y la salida de autocompletado en shell permanecen en texto.

Próximos pasos​

Fuente: https://beancount.io/es/docs/Solutions/automate-bookkeeping-with-bea