Sistema de decisión para las facturas de Alberto: lee PDFs, extrae campos y decide
si cada factura se puede pagar — PAGAR, NO_PAGAR o ESCALAR — con evidencia
y trazabilidad en cada paso.
HIGHLY RECOMMENDED: Using the desktop client is strongly recommended. Run:
./run.sh desktop
PDF ──▶ EXTRACCIÓN ──▶ FEATURES ──▶ PARSER ──▶ CAMPOS ──▶ MOTOR DE REGLAS ──▶ RESULTADO
│
evidencia + config ──▶ STORE ──▶ UI
python start.py # lanzador: prepara el entorno y arranca (cliente por defecto)
uv sync # entorno (Python 3.13, user-space)
uv run -- npm ci --prefix src/filemaid/store/pouchdb # motor PouchDB; requiere Node >=20
uv run filemaid run --lote caja-de-alberto/facturas --out outcomes.jsonl
uv run filemaid emit # re-emite outcomes desde el store
uv run filemaid serve # API de revisión
uv run filemaid server # servicio VLM remoto dedicado, puerto 8001
uv run pytest # tests
uv run ruff check src tests # lint
uv run -- npx pnpm --dir frontend install
uv run -- npx pnpm --dir frontend build # UI real, servida por FastAPI
uv run -- npx pnpm --dir frontend dev_syncth # demo sintética opcional
uv sync --extra desktop # pywebview para la ventana nativa
uv run filemaid-desktop # ventana nativa con la misma UI
uv run filemaid-desktop --headless # misma UI y API sin ventana (sin display)Tres superficies, un solo motor:
| Superficie | Comando | Qué es |
|---|---|---|
| Web UI | uv run filemaid serve |
API + frontend/dist en 127.0.0.1:8000 (FILEMAID_PORT) |
| Desktop | uv run filemaid-desktop · --headless |
ventana nativa (pywebview) sobre el mismo FastAPI, puerto loopback del SO; --headless sirve el mismo servicio sin ventana y sin dependencia de pywebview ni display, con FILEMAID_PORT o --port |
| Servidor VLM | uv run filemaid server |
servidor VLM local dedicado, puerto 8001 |
Punto de entrada único que prepara el entorno y arranca la app. Requiere
Python 3.10+ (probado con 3.14) para ejecutar el propio start.py, más uv y
Node >= 20 en el PATH (el Python 3.13 del proyecto lo gestiona uv); el resto
de dependencias se instalan solas y no se reconstruyen si ya están al día.
python start.py # cliente: ventana nativa (por defecto)
python start.py client --headless --port 8000 # misma UI/API sin ventana
python start.py client --standalone # persiste standalone y aprovisiona el VLM local
python start.py client --ui-url http://127.0.0.1:5173 # UI en desarrollo
python start.py server --host 127.0.0.1 --port 8001 # servidor VLM local (solo Linux)- Cliente (Windows/Linux): sincroniza el entorno uv, instala las deps Node
de PouchDB y construye la UI solo si falta o está desactualizada, y abre la
app. La primera vez, la UI pregunta Standalone o Servidor antes de
descargar el VLM; el modo se guarda en PouchDB y se restaura en cada arranque.
--standalonepersiste el modo y deja que el proceso de la app aprovisione el VLM local (el sidecar vive en ese proceso, no en el lanzador). - Servidor (solo Linux): rechaza otras plataformas antes de cualquier
efecto; exige
FILEMAID_SERVER_TOKENsi--hostno es loopback. Delega enfilemaid server, que aprovisiona el VLM local al arrancar y no sirve hasta que está listo.
En Linux, si la sesión define QT_QPA_PLATFORM como lista de respaldo
(wayland;xcb), la ventana QT puede cerrarse al instante; lanza con una sola
plataforma: QT_QPA_PLATFORM=wayland python start.py client.
La ventana nativa sirve el front construido (frontend/dist) mediante el mismo
FastAPI local que la versión web, en un puerto loopback asignado por el SO.
El puente histórico extraer/decidir no participa en esta conexión.
En desarrollo: FILEMAID_UI_URL=http://127.0.0.1:5173 uv run filemaid-desktop.
El lanzador fuerza el backend QT cuando está disponible (sin sondeo GTK). Las sondas del sistema que escriben en stderr durante el arranque (Vulkan «Failed to detect any valid GPUs», libva) se capturan y se descartan; solo se muestran como diagnóstico si la ventana no llega a abrirse. En máquinas con ICD de Vulkan instalados (p. ej. mesa-vulkan-drivers, paquete del sistema) la sonda ni siquiera se produce.
En Linux, si la sesión define QT_QPA_PLATFORM como una lista de respaldo
(p. ej. wayland;xcb), la ventana QT puede cargar y cerrarse de inmediato
(salida 0, sin error). Es un comportamiento del entorno, no del código: lanza
con una sola plataforma, p. ej.
QT_QPA_PLATFORM=wayland uv run --extra desktop filemaid-desktop
(o QT_QPA_PLATFORM=xcb en sesiones X11).
La UI real consulta facturas, decisiones y logs persistidos en PouchDB. El botón
«logs» aplica un filtro por basename exacto; los detalles conservan todos los
candidatos. uv run filemaid serve sirve la API y frontend/dist.
Los targets *_syncth siguen disponibles para la demo sin datos reales.
La UI pregunta Standalone o Servidor solo la primera vez: el modo confirmado se guarda en PouchDB y se restaura en cada arranque; Configuración permite cambiarlo después.
- Autónomo (Standalone): sin conexión remota; no se muestran campos de red. El modelo VLM local es obligatorio y se descarga/instala la primera vez.
- Servidor: requiere la URL completa de CouchDB remoto (ej.
http://couchdb:5984/facturas) y el endpoint VLM remoto (base OpenAI-compatible/v1, independiente de CouchDB); el modelo local es un respaldo opcional (checkbox).
Los valores se guardan únicamente en PouchDB local; no se replican. La UI muestra el estado real del VLM local (descargando/iniciando/listo/error) y no lo declara listo hasta que el modelo está en ejecución. En modo servidor, la confirmación realiza una sincronización PouchDB<->CouchDB nativa y los cambios se sincronizan periódicamente y tras los scans. El cliente usa PouchDB LevelDB local sin instalar ni requerir CouchDB localmente. Un fallo mantiene los datos locales y muestra el error; no sustituye el resultado de las reglas.
La sincronización entre dispositivos se realiza mediante replicación nativa PouchDB a una base de datos remota Apache CouchDB existente (proporcionada por el usuario o infraestructura central, ej. http://couchdb:5984/facturas).
El cliente no requiere instalar CouchDB localmente: PouchDB (LevelDB) gestiona la persistencia local y replica bidireccionalmente contra CouchDB.
Para autenticación con CouchDB:
- Variables de entorno de credenciales básicas:
FILEMAID_COUCHDB_USERyFILEMAID_COUCHDB_PASSWORD. - O alternativamente token Bearer:
FILEMAID_SYNC_TOKEN.
Para el servicio VLM (server.py), el comando filemaid server aloja el
escalador VLM local: al arrancar aprovisiona (instala el binario llama.cpp +
pesos PaddleOCR-VL 1.6 full Q8 con verificación sha256) y arranca el sidecar; si
no queda listo, el servidor no sirve. No hay modo remoto/upstream en el servidor.
FILEMAID_DATA=data/servidor uv run filemaid server --host 127.0.0.1 --port 8001Configure FILEMAID_SERVER_TOKEN para proteger el acceso (obligatorio fuera de
loopback). El endpoint VLM es independiente de CouchDB (nunca se deduce de
sync_url). El cliente usa FILEMAID_VLM_KEY solo para su VLM remoto.
Estado y aprovisionamiento (API local):
GET /api/vlm/status:downloaded(ficheros verificados) vsrunning/ready(sidecar sano sirviendo el modelo esperado).readyno se declara hasta que el modelo está en ejecución.POST /api/vlm/provision: dispara el aprovisionamiento idempotente.GET /api/sync/status:{ok, state, error, pending, last_sync}constateenidle|syncing|pending|synced|error|standalone. En standalone nunca hay sincronización remota.
La configuración de ejecución vive en _local/runtime-settings (no replicada):
PUT /api/config persiste primero en local y luego sincroniza, de modo que una
caída remota no descarta la configuración confirmada; el error de sincronización
se reporta aparte y se reintenta.
PouchDB es el único almacén runtime, en FILEMAID_DATA/pouchdb; no hay fallback
ni persistencia relacional. Los documentos tienen esquema dinámico; decisiones y
artefactos históricos son inmutables. Esquema, sincronización y límites:
docs/db-mig.md.
uv run filemaid clean elimina únicamente copias temporales y páginas de trabajo,
con confirmación salvo -y. No borra PouchDB, caché, configuración ni evidencias.
Los HTML escritos por report y los JSONL de run/emit son exports explícitos.
Regla de oro (docs/normas.md): ante duda razonable, escalar antes que pagar. El motor es puro: mismos inputs + misma config ⇒ misma salida, byte a byte.
