BuildNexion

Importar facturas a Quipu

Cada factura de compra que entra en BuildNexion se crea como gasto en Quipu. Un servidor en Python (FastAPI) recibe el webhook y llama a la API de Quipu.

Qué necesitas

  • Una API key de BuildNexion con el permiso write:webhooks.
  • El App ID y el App Secret de la API de Quipu.
  • Un servidor con una URL pública HTTPS.

1. Registrar el webhook

bash
curl -X POST https://app.buildnexion.com/api/v1/webhooks \
  -H "Authorization: Bearer $BUILDNEXION_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url":    "https://tu-servidor.es/buildnexion/quipu",
    "events": ["invoice.created"]
  }'

Guarda el secret de la respuesta para verificar la firma.

2. Token de Quipu

Quipu usa OAuth2 con credenciales de cliente; guarda el token mientras no caduque:

python
import os, time, requests

_token = {"valor": None, "caduca": 0}

def token_quipu() -> str:
    if _token["valor"] and _token["caduca"] > time.time():
        return _token["valor"]
    res = requests.post(
        "https://getquipu.com/oauth/token",
        auth=(os.environ["QUIPU_APP_ID"], os.environ["QUIPU_APP_SECRET"]),
        data={"grant_type": "client_credentials"},
    )
    res.raise_for_status()
    cuerpo = res.json()
    _token["valor"] = cuerpo["access_token"]
    _token["caduca"] = time.time() + cuerpo["expires_in"] - 30
    return _token["valor"]

3. Recibir el aviso

python
import json, os
from fastapi import BackgroundTasks, FastAPI, HTTPException, Request
from firma import firma_valida   # ver "Verificar la firma"

app = FastAPI()

@app.post("/buildnexion/quipu")
async def webhook(request: Request, tareas: BackgroundTasks):
    cuerpo = await request.body()
    cabecera = request.headers.get("x-buildnexion-signature", "")
    if not firma_valida(cuerpo, cabecera, os.environ["BUILDNEXION_WEBHOOK_SECRET"]):
        raise HTTPException(401, "firma no válida")
    evento = json.loads(cuerpo)
    if evento["event"] == "invoice.created":
        tareas.add_task(crear_gasto, evento["data"], request.headers.get("x-buildnexion-delivery"))
    return {"ok": True}   # responde enseguida; el gasto se crea después

4. Crear el gasto

invoice.created trae número, fecha, proveedor con su NIF, base, IVA y total (en euros), así que no hace falta pedir nada más a BuildNexion:

python
import requests

def crear_gasto(factura: dict, entrega: str):
    if ya_procesada(entrega):        # tu tabla de entregas procesadas
        return
    base = factura["base_imponible"] if factura["base_imponible"] is not None else factura["amount"]
    payload = {
        "data": {
            "type": "expenses",
            "attributes": {
                "issue_date": factura["date"],              # YYYY-MM-DD
                "number": factura["number"],
                "subtotal": f"{base:.2f}",                  # euros, como texto
                "total": f"{factura['amount']:.2f}",        # con IVA
                "supplier_name": factura["supplier"]["name"],
                "supplier_tin": factura["supplier"]["cif"],
            },
        }
    }
    res = requests.post(
        "https://getquipu.com/api/expenses",
        headers={
            "Authorization": f"Bearer {token_quipu()}",
            "Content-Type": "application/vnd.api+json",
            "Accept": "application/vnd.api+json",
        },
        json=payload,
    )
    res.raise_for_status()
    marcar_procesada(entrega)
Los campos de Quipu son orientativos: compruébalos en su documentación. Como tu endpoint ya respondió 200, si Quipu falla BuildNexion no reintentará: guarda el error y reinténtalo tú.

Errores típicos

  • Quipu responde 422: suele ser una factura duplicada para el mismo proveedor. Decide si la actualizas o la ignoras.
  • 401 de Quipu: el token ha caducado; bórralo de la caché y pide otro.
  • base_imponible a null: la lectura de la factura no la encontró. Usa el total o deja el gasto pendiente de revisar.