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>
6.7 KiB
Servicio OCR para lectura de comprobantes — Especificación
Este documento describe el microservicio que hay que desplegar (en Coolify u otro host con Docker) para que SirPremium deje de depender únicamente de Gemini Vision para leer comprobantes de pago. El flujo pasa a ser:
imagen del comprobante → [ESTE SERVICIO OCR] → texto plano → Gemini (solo texto) → JSON estructurado
Gemini deja de "ver" la imagen; solo recibe texto y lo ordena en JSON. Esto evita el punto único de falla que tenías hoy (si Gemini Vision falla, cae el 100% de las lecturas) y reduce el consumo de cuota, porque los modelos de texto son más baratos y menos limitados que los de visión.
1. Qué tiene que exponer el servicio
Un único endpoint HTTP:
POST /extract
Request
Content-Type: application/json
Authorization: Bearer <OCR_TOKEN>
{
"image_base64": "<imagen en base64, SIN el prefijo data:image/...;base64,>",
"mime_type": "image/jpeg"
}
mime_typepuede serimage/jpeg,image/png,image/webpoimage/heic(son los formatos que ya acepta la app hoy).- El token se valida por header
Authorization: Bearer. Si no coincide, responder401.
Response — éxito (HTTP 200)
{
"success": true,
"text": "Transferencia exitosa\nBre-B\nComprobante 24152758409697903...\nValor de la transferencia\n$1.000,00\nEnviaste LEDYS LEON QUINTERO\nA la llave @LEON8909\nEntidad BANCOLOMBIA\n..."
}
text: todo el texto detectado en la imagen, tal cual lo lee el OCR (no hace falta que estructure nada — de eso se encarga Gemini después). Si detecta texto en varias líneas/bloques, mejor mantener saltos de línea, ayuda a Gemini a interpretar el layout.
Response — error (HTTP 4xx/5xx)
{
"success": false,
"error": "descripción corta del error"
}
Casos que deben devolver error controlado (no un 500 crudo):
- Imagen corrupta / no decodificable.
- Imagen sin texto detectable (
"error": "sin_texto_detectado"). mime_typeno soportado.
Timeouts
Laravel llama con un timeout de 20 segundos. El servicio debe responder bien antes de eso — el OCR no debería tardar más de 2-5 segundos por imagen en CPU.
2. Motor de OCR recomendado
Para desplegar rápido en Coolify, la opción más simple es un contenedor Docker con:
- Tesseract OCR (
tesseract-ocr+ paquete de idiomaspa) envuelto en una API pequeña (Python + FastAPI, o Node + Express). Es la opción de menor esfuerzo. - Si la precisión con las capturas de pantalla de apps bancarias (fondos oscuros, fuentes pequeñas) resulta insuficiente, migrar el mismo contrato de API a PaddleOCR (más preciso, pero imagen Docker más pesada — necesita Python + PaddlePaddle).
El contrato HTTP (POST /extract) es el mismo sin importar cuál motor uses por
dentro — así que se puede empezar con Tesseract y cambiar el motor después sin
tocar el lado de Laravel.
Preprocesamiento recomendado antes de correr OCR
Mejora mucho la precisión con capturas de pantalla de apps:
- Convertir a escala de grises.
- Aumentar contraste / binarizar (umbral adaptativo).
- Escalar la imagen si es muy pequeña (mínimo ~1000px de ancho).
3. Variables de entorno del servicio (sugeridas)
OCR_TOKEN=<token secreto que Laravel debe enviar en el Authorization header>
OCR_LANG=spa # idioma para Tesseract
PORT=8000
4. Ejemplo mínimo de referencia (Python + FastAPI + Tesseract)
Esto es solo un punto de partida — no es la implementación final, es para no arrancar de cero.
Dockerfile
FROM python:3.11-slim
RUN apt-get update && apt-get install -y --no-install-recommends \
tesseract-ocr tesseract-ocr-spa libgl1 \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app.py .
EXPOSE 8000
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]
requirements.txt
fastapi
uvicorn[standard]
pytesseract
pillow
python-multipart
app.py
import base64, io, os
from fastapi import FastAPI, Header, HTTPException
from pydantic import BaseModel
from PIL import Image, ImageOps
import pytesseract
app = FastAPI()
OCR_TOKEN = os.environ.get("OCR_TOKEN", "")
LANG = os.environ.get("OCR_LANG", "spa")
class ExtractRequest(BaseModel):
image_base64: str
mime_type: str = "image/jpeg"
def preprocesar(img: Image.Image) -> Image.Image:
img = img.convert("L") # escala de grises
img = ImageOps.autocontrast(img)
if img.width < 1000:
ratio = 1000 / img.width
img = img.resize((1000, int(img.height * ratio)))
return img
@app.post("/extract")
def extract(req: ExtractRequest, authorization: str = Header(default="")):
if OCR_TOKEN and authorization != f"Bearer {OCR_TOKEN}":
raise HTTPException(status_code=401, detail="token invalido")
try:
raw = base64.b64decode(req.image_base64)
img = Image.open(io.BytesIO(raw))
except Exception:
return {"success": False, "error": "imagen invalida o corrupta"}
img = preprocesar(img)
texto = pytesseract.image_to_string(img, lang=LANG).strip()
if not texto:
return {"success": False, "error": "sin_texto_detectado"}
return {"success": True, "text": texto}
5. Despliegue en Coolify
- Crear un nuevo recurso tipo "Application" apuntando a un repo/carpeta con el
Dockerfilede arriba (o el que termines usando). - Configurar la variable de entorno
OCR_TOKENcon un valor secreto largo (ej: generado conopenssl rand -hex 32). - Publicar el servicio con dominio propio (ej:
https://ocr.tu-dominio.com) o IP interna si Coolify y el hosting de Laravel comparten red — cualquiera de las dos formas sirve, Laravel solo necesita una URL alcanzable por HTTPS. - Probar con:
curl -X POST https://ocr.tu-dominio.com/extract \ -H "Authorization: Bearer <OCR_TOKEN>" \ -H "Content-Type: application/json" \ -d '{"image_base64":"'"$(base64 -w0 comprobante.jpg)"'","mime_type":"image/jpeg"}'
6. Qué necesito de ti para conectar Laravel
Una vez el servicio esté arriba, en el panel de SirPremium (Configuración del Chat → sección "OCR de comprobantes", ya agregada) solo tienes que llenar:
- URL del servicio OCR → ej:
https://ocr.tu-dominio.com - Token → el mismo valor que pusiste en
OCR_TOKEN - Activar el checkbox "Habilitar OCR antes de enviar a la IA"
Laravel hace el resto: llama a {URL}/extract, si responde con texto se lo pasa a
Gemini en modo texto (estructurarTexto), y si el OCR falla o no está habilitado,
cae automáticamente al método anterior (Gemini leyendo la imagen directamente) —
no se rompe nada si el servicio OCR está caído o aún no lo has desplegado.