Aplicación web para rendición de gastos empresariales: registro de gastos, análisis de boletas con IA (OCR), creación de rendiciones, flujos de aprobación, notificaciones y branding por empresa.
- Gestión de gastos con adjunto de comprobante (imagen/PDF).
- Los comprobantes subidos desde web/API se guardan con nombre único para evitar colisiones y sobreescritura entre gastos distintos.
- OCR con IA vía OpenRouter al subir la boleta (autocompletado de monto, comercio, fecha y categoría), incluyendo PDF convertido internamente a imagen para análisis.
- Vista previa embebida del comprobante en el formulario, con apertura ampliada para imágenes y PDF.
- En
Mis Gastos, los comprobantes PDF se muestran con acceso directo correcto al visor ampliado. - Normalización de montos para formato local (CLP): separador de miles y decimales.
- Soporte de gastos en CLP y USD con conversión a CLP para reportes, políticas, dashboard y aprobación.
- Tipo de gasto
Vehículo particularcon cálculo automático por tramo y boleta opcional de combustible como respaldo. Mis Gastosmuestra más contexto por gasto (motivo, cliente/partner y datos de kilometraje cuando aplica).- Los gastos en borrador pueden editarse mientras no hayan sido enviados a aprobación; si están dentro de una rendición borrador, también pueden quitarse de ella.
- Detección de duplicados por hash de imagen y por monto/fecha.
- GPS obligatorio al crear gastos (captura de coordenadas + dirección aproximada).
- Validación antifraude con score combinado (
match,partial,mismatch): comercio↔ubicación + fecha boleta↔rendición + hora boleta↔rendición (margen 20 min). Incluye además regla horaria habitual: L-V entre 09:00 y 19:00. - Rendiciones con múltiples gastos.
- Tipo de rendición: solicitud de devolución o tarjeta corporativa.
- En
Rendiciones, los usuarios pueden filtrar sus propias rendiciones porPendientes de devolución,PagadasyTarjeta corporativa. - En
Rendiciones -> Finanzas, los usuarios con permisos financieros pueden subfiltrar porPagadasyTarjeta corporativa. - Flujo de aprobación configurable por pasos (
rol,usuario,manager). - Si un paso del flujo usa
managery el solicitante no tiene manager asignado, el sistema omite ese paso y avanza automáticamente al siguiente aprobador válido. - Solicitud de antecedentes adicionales durante la aprobación, con reenvío al mismo paso del flujo.
- Notificaciones in-app y envío de correos para aprobaciones/rechazos.
- Panel administrativo: usuarios, centros de costo, flujos, auditoría y branding.
- Branding por empresa: nombre de app, ícono, logo y dominio por defecto para emails de usuarios.
- Selector de temas visuales (Executive / Paper / Midnight / Rose).
- Guía funcional de uso integrada para usuarios desde
Mi Perfil.
- Backend: Flask 3, SQLAlchemy, Flask-Login, Flask-Migrate, Flask-Mail, Flask-Limiter.
- Base de datos: PostgreSQL.
- Frontend: Jinja2 + Alpine.js + UnoCSS.
- Geocodificación: OpenStreetMap Nominatim (reverse geocoding de coordenadas).
- OCR IA: OpenRouter (SDK
openai). - Exportación: ReportLab (PDF).
- Procesamiento de primera página PDF para OCR/hash:
pypdfium2. - Infra: Docker + Docker Compose.
- Docker + Docker Compose (recomendado).
- Opcional para desarrollo frontend: Node.js 18+ (
npm). - Opcional para ejecución local sin Docker: Python 3.12 + PostgreSQL.
- Copiar variables de entorno:
cp .env.example .env- Ajustar al menos:
OPENROUTER_API_KEY(si quieres OCR IA).SECRET_KEYy credenciales de BD para tu entorno.
- Levantar servicios:
docker compose up -d --build- Abrir la app:
La imagen ejecuta flask db upgrade al iniciar.
El script seed.py recrea la base completa (borra datos actuales) y crea usuarios demo:
docker compose exec web python seed.pyCredenciales demo:
admin@demo.com / admin123user@demo.com / user123
- Crear y activar entorno virtual:
python3 -m venv .venv
source .venv/bin/activate- Instalar dependencias:
pip install -r requirements.txt
npm install- Variables de entorno:
cp .env.example .envDefine DATABASE_URL apuntando a PostgreSQL local.
- Migraciones:
flask db upgrade- Generar CSS:
npm run css-buildModo watch:
npm run css-watch- Ejecutar:
python run_dev.pyRuta: Admin -> Branding (/admin/branding).
Permite configurar:
- Nombre visible de la app (
brand_app_name). - Ícono de la app (
brand_icon_url) para pestaña/navegación. - Logo corporativo (PNG/JPG/WEBP/SVG).
- Dominio de correo por defecto para creación de usuarios (
brand_user_default_domain).
Valor por defecto global: Rinde Fácil.
Ruta: Mi Perfil (/auth/profile).
Cada usuario puede crear sus propias API keys para integraciones con agentes IA:
- Generación de API key personal (se muestra solo una vez al crearla).
- Revocación manual de keys activas.
- Registro de último uso (
last_used_at). - Herencia de permisos: la key opera con el mismo rol/permisos del usuario creador.
Para consumir la API con una key:
Authorization: Bearer rfk_...Ruta en la app: Mi Perfil -> Guía de Uso Completa (/auth/user-guide).
Incluye:
- Flujo completo de rendición paso a paso.
- Roles/perfiles y alcance por tipo de usuario.
- Operación del panel administrativo.
- Configuración y uso de flujos de aprobación.
- Reglas y señales del score antifraude.
- Buenas prácticas para evitar rechazos.
- Uso de API Keys para agentes IA.
La aplicación separa tres conceptos:
Gasto: comprobante individual. Se crea desdeGastos -> Nuevo Gasto.Rendición: agrupación de uno o más gastos. Internamente el modelo se llamaReport, pero en la UI corresponde a una rendición.Flujo de aprobación: se ejecuta sobre la rendición, no sobre el gasto individual.
Tipos de gasto soportados:
receipt: gasto normal con monto manual o autocompletado por OCR.mileage: tramo en vehículo particular. Cada tramo se registra como un gasto independiente y luego se agrupa normalmente en una rendición.
Cada rendición además tiene un tipo:
employee_reimbursement: solicitar devolución al empleado.corporate_card: rendición de tarjeta corporativa, sin devolución de dinero.
Secuencia normal de uso:
- Crear uno o varios gastos.
- Ir a
Rendicionesy agrupar esos gastos. - Crear la rendición en estado borrador.
- Enviar la rendición al flujo de aprobación.
Comportamiento en Mis Gastos:
- Un gasto
draftsin rendición puedeEditaroEliminar. - Un gasto
draftdentro de una rendicióndraftpuedeEditaroQuitar de Rendición. - Si editas un gasto que ya está dentro de una rendición borrador, el total de la rendición se recalcula automáticamente.
- Una vez que la rendición fue enviada, el gasto deja de ser editable.
Durante la aprobación puede ocurrir además este ciclo:
- El aprobador solicita antecedentes adicionales con comentario obligatorio.
- La rendición pasa a estado
needs_info. - El solicitante ve el motivo en la rendición, responde y la reenvía.
- La revisión vuelve al mismo paso del flujo, no al inicio.
Importante:
- Si no existe un flujo activo con pasos configurados para la empresa, la rendición no se envía y se mantiene en borrador.
- Los gastos individuales no se aprueban por separado; el resultado final se refleja en la rendición y en los gastos asociados.
needs_infono es rechazo: significa que faltan antecedentes o contexto para decidir.
Roles disponibles:
employee: crea gastos y rendiciones propias.manager: revisa/decide rendiciones según flujo y jerarquía.approver/reviewer: participan en pasos definidos por flujo.admin/superadmin: acceso a administración completa.
Permisos adicionales sobre el usuario:
can_view_approved_reports: permite ver rendicionesapprovedypaidde toda la empresa.can_mark_reimbursements_paid: permite marcar comopaidlas rendiciones aprobadas de tipoemployee_reimbursement.
Estos permisos son acumulativos y no reemplazan el rol principal. Un mismo usuario puede, por ejemplo, seguir siendo manager y además operar como Finanzas.
Vista operativa para Finanzas:
Finanzas -> Todas: rendiciones visibles para el perfil financiero.Finanzas -> Pagadas: rendiciones de devolución ya marcadas como pagadas.Finanzas -> Tarjeta corporativa: rendiciones sin devolución al empleado.
Acceso administrativo (/admin) requiere rol admin o superadmin.
Ruta: Admin -> Flujos (/admin/flows).
Cada flujo permite:
- Definir regla de activación (ej.
min_amount). - Configurar pasos secuenciales de aprobación.
- Asignar aprobadores por:
role(rol específico),user(usuario específico),manager(jefe directo del solicitante).
Notas operativas:
- El flujo se evalúa al enviar la rendición.
- Si existen varios flujos aplicables, el sistema selecciona el de mayor
monto mínimoy, en empate, el de más pasos. - Si el flujo no tiene pasos o no existe uno aplicable, la rendición permanece en borrador.
- La aprobación afecta a la rendición y actualiza el estado de los gastos que contiene.
- Cada aprobador ve y gestiona solo el paso actual que realmente le corresponde.
- Un usuario
adminno interviene automáticamente en pasos previos de otros aprobadores mientras el flujo siga activo. - El detalle de la rendición muestra
Paso X de Ypara reflejar el avance real del flujo. - Un aprobador puede pedir antecedentes adicionales sin rechazar la rendición.
- Cuando el solicitante responde, la rendición vuelve al mismo paso pendiente.
- Si un paso configurado como
managerno tiene destinatario porque el solicitante no posee manager asignado, el sistema lo omite y continúa con el siguiente paso del flujo.
Ruta: /admin.
Módulos principales:
Usuarios: crear/editar/eliminar, rol, manager, centro de costo.Permisos de Finanzas: visibilidad corporativa de rendiciones aprobadas y cierre de devoluciones pagadas.Centros de costo: código y presupuesto mensual.Flujos: diseño de pipeline de aprobación.Branding: nombre app, ícono, logo y dominio por defecto de usuarios.Auditoría: historial de acciones.
- El análisis se dispara automáticamente al seleccionar un archivo en “Nuevo Gasto”.
- Si OCR no detecta campos, el formulario sigue disponible para carga manual.
- El endpoint de extracción es
POST /expenses/extract-data. - Archivos se guardan localmente en
app/static/uploads. - Los nombres almacenados incluyen un identificador único por carga, para evitar que dos archivos con el mismo nombre original se sobrescriban.
- OCR intenta extraer fecha en formato regional
DD/MM/YYYYy horaHH:MMcuando exista. - Si el comprobante es PDF, la app convierte la primera página a imagen para OCR y cálculo de hash.
- El formulario muestra vista previa embebida del comprobante y permite abrirlo en modal ampliado.
- La moneda contable base es
CLP. - Los gastos pueden registrarse en
CLPoUSD. - En gastos USD se guarda:
- monto original en USD,
- tipo de cambio usado,
- monto equivalente en CLP (
amount_clp).
- La app intenta completar el tipo de cambio automáticamente con:
- fuente primaria:
mindicador.cl - fallback:
CMFsi existeCMF_API_KEY
- fuente primaria:
- Si no hay respuesta de fuente externa, el usuario puede completar el tipo de cambio manualmente.
- Se registra desde
Nuevo Gastocomo tipoVehículo particular. - Cada tramo corresponde a un gasto independiente para mantener intacta la lógica actual de gastos y rendiciones.
- Campos operativos por tramo:
- fecha,
- descripción del trayecto,
- kilómetros,
- precio litro,
- rendimiento km/l,
- factor de corrección,
- GPS,
- boleta opcional de combustible.
- Fórmula aplicada:
km_ajustados = kilometros + (kilometros * factor_correccion)
monto = (km_ajustados / rendimiento_km_l) * precio_litro
- El resultado se guarda como gasto normal y puede entrar en rendición, aprobación, PDF y API igual que cualquier otro gasto.
Nuevo Gastoexige geolocalización activa (latitud/longitud obligatorias).- Se guarda precisión GPS, timestamp de captura y dirección aproximada.
- La app calcula coherencia para alertar posibles fraudes:
- Comercio vs dirección GPS.
- Fecha de boleta vs fecha de rendición.
- Hora de boleta vs hora de rendición (20 min de margen).
- Horario habitual de operación: L-V 09:00 a 19:00 (fin de semana/fuera de horario suma riesgo).
- Si cae en fin de semana o fuera de horario, la validación nunca queda en
match(baja al menos apartial).
match: alta coherencia.partial: coherencia parcial.mismatch: potencial riesgo.- Nota: en navegador, geolocalización requiere contexto seguro (HTTPS) o
localhost.
Base URL:
http://localhost:5001/api/v1
Autenticación:
- Bearer JWT vía
Authorization: Bearer <token>. - API Key personal (generada en
Mi Perfil):Authorization: Bearer rfk_.... - Las API keys heredan el rol/permisos del usuario que las crea y pueden revocarse desde
Mi Perfil.
POST /auth/token: login API (email/password) y entrega token JWT.GET /me: datos del usuario autenticado.GET /categories: categorías activas de la empresa.POST /expenses/analyze: analiza una boleta (multipartreceipt) y devuelve campos OCR.GET /expenses: lista gastos (paginable porlimityoffset).POST /expenses: crea gasto; acepta imagen/PDF, puede autocompletar con IA y exigegps_latitude/gps_longitude. También aceptareceipt_time(HH:MMoHH:MM:SS). Soporta ademáscurrency,exchange_rate,expense_type,distance_km,fuel_price_per_liter,vehicle_efficiency_km_lycorrection_factor.GET /reports: lista rendiciones.POST /reports: crea rendición a partir deexpense_ids. Aceptasettlement_typecon valoresemployee_reimbursementocorporate_card.GET /reports/{id}: detalle completo (gastos + decisiones).POST /reports/{id}/submit: envía rendición al flujo de aprobación.POST /reports/{id}/approve: aprueba un paso o aprobación final.POST /reports/{id}/reject: rechaza rendición con motivo.GET /reports/pending-approvals: mejora recomendada, lista rendiciones pendientes que el usuario actual puede aprobar.
- Obtener token:
curl -X POST http://localhost:5001/api/v1/auth/token \
-H "Content-Type: application/json" \
-d '{"email":"admin@demo.com","password":"admin123"}'1.1. Consumir API con API key de usuario:
curl -X GET http://localhost:5001/api/v1/me \
-H "Authorization: Bearer rfk_..."- Analizar boleta con IA:
curl -X POST http://localhost:5001/api/v1/expenses/analyze \
-H "Authorization: Bearer <TOKEN>" \
-F "receipt=@/ruta/boleta.jpg"- Crear gasto con imagen (OCR aplicado):
curl -X POST http://localhost:5001/api/v1/expenses \
-H "Authorization: Bearer <TOKEN>" \
-F "description=Traslado cliente Santiago centro" \
-F "date=2026-03-08" \
-F "receipt_time=13:25" \
-F "gps_latitude=-33.4489" \
-F "gps_longitude=-70.6693" \
-F "gps_accuracy_m=25.0" \
-F "receipt=@/ruta/boleta.jpg"date soporta YYYY-MM-DD, DD/MM/YYYY y DD-MM-YYYY.
- Crear rendición:
curl -X POST http://localhost:5001/api/v1/reports \
-H "Authorization: Bearer <TOKEN>" \
-H "Content-Type: application/json" \
-d '{"title":"Rendicion Marzo","description":"Semana 1","expense_ids":["<EXPENSE_ID_1>","<EXPENSE_ID_2>"]}'- Aprobar rendición:
curl -X POST http://localhost:5001/api/v1/reports/<REPORT_ID>/approve \
-H "Authorization: Bearer <TOKEN>" \
-H "Content-Type: application/json" \
-d '{"comment":"Aprobado por jefatura"}'app/
blueprints/ # auth, dashboard, expenses, reports, admin, api
models/ # entidades SQLAlchemy
services/ # OCR, notificaciones, correo, auditoría, exportación
templates/ # vistas Jinja
static/ # css/js/img/uploads
migrations/ # alembic/flask-migrate
docker-compose.yml
Dockerfile
run_dev.py
seed.py
- Ejecuta
npm run css-build. - Verifica que exista
app/static/css/uno.css. - Recarga dura del navegador (
Cmd+Shift+R/Ctrl+F5).
- Revisa
OPENROUTER_API_KEYen.env. - Verifica conectividad saliente a
https://openrouter.ai. - Si falla, la carga manual sigue disponible.
- Revisa conectividad saliente hacia
mindicador.cl. - Si usarás fallback institucional, configura
CMF_API_KEY. - Mientras no exista respuesta de fuente externa, el tipo de cambio puede completarse manualmente.
- Verifica que el archivo tenga al menos una página legible.
- La app analiza la primera página del PDF para OCR.
- La vista previa depende del visor PDF del navegador. Si falla en un navegador, prueba
Ver grandeo cambia de navegador.
- Confirma
DATABASE_URL. - Ejecuta
flask db upgrade.
ssh-add ~/.ssh/id_rsa.envestá excluido del repo.- No subas credenciales reales en archivos versionados.
- Cambia secretos por defecto antes de producción.