- OcrExtractionService: registra cada llamada en ai_usage_logs (servicio=ocr,
costo=5 COP/foto) y ahora tambien loggea cuando SI funciona, no solo
cuando falla (antes era imposible confirmar por log que el OCR corrio).
- GeminiVisionService: el log decia siempre "[Gemini Vision]" sin importar
si la llamada fue de imagen o de texto post-OCR, haciendo imposible
distinguir cual camino se uso realmente. Ahora usa el servicio real.
- GeminiVisionService: agrega thinkingConfig.thinkingBudget=0 (igual que
GeminiIntentService) para que el modelo no gaste tokens narrando su
razonamiento en texto plano antes del JSON final -- se estaba colando
ese razonamiento en la respuesta y comiendose el presupuesto de tokens.
- GeminiIntentService: resultado ya no queda forzado a 'ok'; ahora filtra
bloques 'thought' igual que vision, y guarda tokens/raw en el detalle
para poder diagnosticar sin acceso al servidor.
- ShowAiLogs: card y filtro de costo OCR en el dashboard de consumo.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Si en el panel se pega la URL completa (https://host/extract) en vez del
dominio base, el servicio duplicaba la ruta (.../extract/extract) y daba
404. normalizarUrl() ahora le quita el /extract final si ya viene, tanto
en el flujo real como en el boton "Probar conexion".
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Antes, si Gemini Vision fallaba (cuota/API), el 100% de los comprobantes
del chat web quedaban sin poder leerse. Ahora el job intenta primero un
servicio OCR externo configurable (imagen -> texto) y le pasa el texto
a Gemini para que solo lo estructure en JSON, más barato y sin la cuota
de visión. Si el OCR no está habilitado o falla, cae al método anterior
(Gemini leyendo la imagen directamente) sin romper nada.
Agrega configuración en el panel (URL/token/habilitado + probar conexión)
y la spec del servicio OCR a desplegar en docs/ocr-service-spec.md.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>