Files
mve-micro-docs/README.md
Ernesto Herrera fcc516c9b3 feat: Agregar soporte OCR con Tesseract para PDFs escaneados
- Integrar Tesseract OCR para leer PDFs escaneados automáticamente
- Detectar automáticamente si el PDF tiene texto o requiere OCR
- Agregar servicio ocr_service.py con funciones de OCR
- Actualizar Dockerfile con tesseract-ocr, tesseract-ocr-spa y poppler-utils
- Agregar variables de configuración OCR (OCR_ENABLED, OCR_LANGUAGE, OCR_DPI, OCR_TIMEOUT)
- Crear endpoint de debug para ver texto extraído (/api/v1/debug/extract-text)
- Agregar scripts de instalación y prueba (install_ocr.ps1, test_ocr.py, debug_pdf.ps1)
- Documentación completa (OCR_SETUP.md, DOCKER_OCR.md, COMO_PROBAR.md)
- Actualizar docker-compose.yml con variables de entorno OCR
- Modificar pdf_text.py para usar OCR cuando sea necesario
- Actualizar requirements.txt con pytesseract, Pillow, pdf2image
2026-03-04 08:21:41 -07:00

553 lines
15 KiB
Markdown

# MVE Incrementables Parser
Microservicio para extraer y parsear la sección de **Incrementables** de documentos PDF tipo "CARTA INS". Construido con **FastAPI** y **Python 3.12**.
## 🚀 Características
-**Autenticación JWT** con bcrypt
-**Extracción de texto** con PyMuPDF y pdfplumber (fallback)
-**OCR con Tesseract** para PDFs escaneados (detección automática)
-**Parsing robusto** de incrementables (fletes, seguros, almacenaje, regalías)
-**Validación de archivos** (tamaño, tipo MIME, PDF cifrado)
-**Logging estructurado** con correlation ID
-**Rate limiting** en login
-**Procesamiento asíncrono** con Celery y Redis
-**Cola de tareas** para procesamiento en background
-**Tests con pytest**
-**Docker & Docker Compose**
## 📋 Requisitos
- Python 3.12+
- Docker & Docker Compose (opcional)
- **Tesseract OCR** (para PDFs escaneados) - [Ver guía de instalación](OCR_SETUP.md)
- **Poppler** (para conversión PDF a imagen) - Requerido solo para OCR
### 🤖 OCR para PDFs Escaneados
El sistema puede leer **PDFs escaneados** (imágenes) usando Tesseract OCR. La detección es automática:
- Si el PDF tiene texto seleccionable → Extracción normal (rápido)
- Si el PDF está escaneado → OCR automático (más lento)
**Con Docker (Recomendado):**
- ✅ Tesseract ya incluido en la imagen
- ✅ Sin instalación adicional
- ✅ Ver [DOCKER_OCR.md](DOCKER_OCR.md) para guía completa
**Sin Docker (Instalación local):**
```powershell
# Windows - Ejecutar script de instalación
.\install_ocr.ps1
# O instalar manualmente - Ver OCR_SETUP.md para guía completa
```
**Verificar instalación local:**
```bash
python test_ocr.py
```
Ver [OCR_SETUP.md](OCR_SETUP.md) para instrucciones detalladas de instalación local.
## 🛠️ Instalación
### Opción 1: Con Docker (Recomendado)
1. **Clonar el repositorio**
```bash
cd mve-incrementables-parser
```
2. **Configurar variables de entorno**
```bash
cp .env.example .env
```
Edita `.env` y configura:
```env
AUTH_USERNAME=admin
AUTH_PASSWORD_HASH=$2b$12$LQv3c1yqBWVHxkd0LHAkCOYz6TtxMQJqhN8/LewY5GyYqwMzYXhHO
JWT_SECRET=your-secret-key-change-this-in-production
JWT_EXPIRES_MINUTES=60
MAX_FILE_MB=10
LOG_LEVEL=INFO
# OCR Settings (ya configurado por defecto)
OCR_ENABLED=true
OCR_LANGUAGE=spa
OCR_DPI=300
OCR_TIMEOUT=300
```
**Generar hash de contraseña:**
```bash
python -c "from passlib.hash import bcrypt; print(bcrypt.hash('tu_contraseña'))"
```
3. **Levantar el servicio**
```bash
docker-compose up --build
```
El servicio estará disponible en `http://localhost:9876`
**Servicios incluidos:**
- API FastAPI: puerto 9876
- Redis: puerto 16379
- Celery Worker: procesamiento en background
- **Tesseract OCR**: Incluido en el contenedor (sin instalación adicional)
### Opción 2: Sin Docker
1. **Instalar dependencias del sistema**
```bash
# macOS
brew install mupdf-tools tesseract tesseract-lang
# Ubuntu/Debian
sudo apt-get install libmupdf-dev mupdf-tools tesseract-ocr tesseract-ocr-spa poppler-utils
# Windows - Ver OCR_SETUP.md para instrucciones detalladas
# Descargar e instalar:
# - Tesseract: https://github.com/UB-Mannheim/tesseract/wiki
# - Poppler: https://github.com/oschwartz10612/poppler-windows/releases
```
2. **Crear entorno virtual**
```bash
python3.12 -m venv venv
source venv/bin/activate # En Windows: venv\Scripts\activate
```
3. **Instalar dependencias Python**
```bash
pip install -r requirements.txt
```
4. **Configurar .env** (ver Opción 1, paso 2)
5. **Ejecutar el servicio**
```bash
uvicorn app.main:app --host 0.0.0.0 --port 9876 --reload
```
## 📚 Uso
### 1. Health Check
```bash
curl http://localhost:9876/health
```
**Respuesta:**
```json
{
"status": "ok",
"service": "mve-incrementables-parser",
"version": "1.0.0"
}
```
### 2. Autenticación (Login)
```bash
curl -X POST http://localhost:9876/auth/login \
-H "Content-Type: application/json" \
-d '{
"username": "admin",
"password": "tu_contraseña"
}'
```
**Respuesta:**
```json
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "bearer",
"expires_in": 3600
}
```
⚠️ **Nota:** Guarda el `access_token` para usarlo en las siguientes peticiones.
### 3. Parsear PDF (Síncrono)
```bash
curl -X POST http://localhost:9876/v1/incrementables/parse \
-H "Authorization: Bearer <TU_TOKEN_AQUI>" \
-F "file=@CARTA_INS_228718.pdf" \
-F "document_ref=INS-228718"
```
**Con Correlation ID personalizado:**
```bash
curl -X POST http://localhost:9876/v1/incrementables/parse \
-H "Authorization: Bearer <TU_TOKEN_AQUI>" \
-H "X-Correlation-Id: custom-id-12345" \
-F "file=@CARTA_INS_228718.pdf"
```
**Respuesta exitosa (200):**
```json
{
"correlation_id": "550e8400-e29b-41d4-a716-446655440000",
"document": {
"filename": "CARTA INS 228718.pdf",
"pages": 1,
"sha256": "a3d5f..."
},
"incrementables": {
"currency": "USD",
"fletes": 1591.20,
"seguros": null,
"almacenaje_consolidacion": 0.00,
"regalias": null
},
"extraction": {
"method": "text",
"anchors_found": ["AJUSTE DE INCREMENTABLES EN:"],
"warnings": []
}
}
```
### 4. Parsear PDF (Asíncrono con Cola)
Para archivos grandes o cuando no quieres esperar, usa el endpoint asíncrono:
```bash
curl -X POST http://localhost:9876/v1/incrementables/parse/async \
-H "Authorization: Bearer <TU_TOKEN_AQUI>" \
-F "file=@CARTA_INS_228718.pdf" \
-F "document_ref=INS-228718"
```
**Respuesta (202):**
```json
{
"task_id": "a8f2e9c1-5b3d-4e7f-9a1c-2d3e4f5a6b7c",
"correlation_id": "550e8400-e29b-41d4-a716-446655440000",
"status": "queued",
"message": "Task queued for processing. Use /v1/incrementables/status/{task_id} to check progress."
}
```
### 5. Consultar Estado de Tarea
```bash
curl -X GET http://localhost:9876/v1/incrementables/status/a8f2e9c1-5b3d-4e7f-9a1c-2d3e4f5a6b7c \
-H "Authorization: Bearer <TU_TOKEN_AQUI>"
```
**Respuesta (en proceso):**
```json
{
"task_id": "a8f2e9c1-5b3d-4e7f-9a1c-2d3e4f5a6b7c",
"status": "started",
"result": null,
"error": null
}
```
**Respuesta (completada):**
```json
{
"task_id": "a8f2e9c1-5b3d-4e7f-9a1c-2d3e4f5a6b7c",
"status": "completed",
"result": {
"status": "completed",
"task_id": "a8f2e9c1-5b3d-4e7f-9a1c-2d3e4f5a6b7c",
"document": {
"filename": "CARTA INS 228718.pdf",
"pages": 1,
"sha256": "a3d5f...",
"document_ref": "INS-228718"
},
"incrementables": {
"currency": "USD",
"fletes": 1591.20,
"seguros": null,
"almacenaje_consolidacion": 0.00,
"regalias": null
},
"extraction": {
"method": "text",
"anchors_found": ["AJUSTE DE INCREMENTABLES EN:"],
"warnings": []
}
},
"error": null
}
```
**Errores comunes:**
- **400**: Archivo no válido, no es PDF, excede tamaño máximo
- **401**: Token ausente, inválido o expirado
- **422**: No se encontró sección de incrementables o parsing falló
- **500**: Error interno del servidor
## 🔄 Procesamiento Asíncrono
El servicio soporta dos modos de procesamiento:
### Modo Síncrono (`/parse`)
- Respuesta inmediata
- Ideal para PDFs pequeños
- Timeout en request HTTP
### Modo Asíncrono (`/parse/async`)
- Respuesta inmediata con task_id
- Procesamiento en background con Celery
- Ideal para PDFs grandes o lotes
- Sin timeout (límite: 5 minutos por tarea)
- Consulta estado con `/status/{task_id}`
### Arquitectura
```
Cliente → FastAPI → Redis (Cola) → Celery Worker → Procesa PDF → Redis (Resultado)
Cliente ← FastAPI ← Redis (Consulta) ←────────────────────────────────
```
## 🧪 Tests
```bash
# Ejecutar todos los tests
pytest
# Con cobertura
pytest --cov=app --cov-report=html
# Test específico
pytest tests/test_parser.py -v
# Ver logs detallados
pytest -v -s
```
## 🏗️ Estructura del Proyecto
```
mve-incrementables-parser/
├── app/
│ ├── __init__.py
│ ├── main.py # FastAPI app principal
│ ├── schemas.py # Modelos Pydantic
│ ├── core/
│ │ ├── config.py # Configuración (ENV)
│ │ ├── security.py # JWT, bcrypt, rate limiting
│ │ └── celery_app.py # Configuración de Celery
│ ├── api/
│ │ ├── auth.py # Endpoint de login
│ │ └── v1/
│ │ └── incrementables.py # Endpoints de parsing (sync/async)
│ ├── tasks/
│ │ └── parse_tasks.py # Tareas de Celery
│ └── services/
│ ├── pdf_text.py # Extracción de texto (PyMuPDF/pdfplumber)
│ └── parser.py # Parsing de incrementables
├── tests/
│ ├── conftest.py # Fixtures de pytest
│ ├── test_parser.py # Tests del parser
│ └── test_api.py # Tests de endpoints
├── .env.example # Ejemplo de variables de entorno
├── .gitignore
├── Dockerfile
├── docker-compose.yml # API + Redis + Celery Worker
├── pytest.ini
├── requirements.txt
└── README.md
```
│ │ └── v1/
│ │ └── incrementables.py # Endpoint de parsing
│ └── services/
│ ├── pdf_text.py # Extracción de texto (PyMuPDF/pdfplumber)
│ └── parser.py # Parsing de incrementables
├── tests/
│ ├── conftest.py # Fixtures de pytest
│ ├── test_parser.py # Tests del parser
│ └── test_api.py # Tests de endpoints
├── .env.example # Ejemplo de variables de entorno
├── .gitignore
├── Dockerfile
├── docker-compose.yml
├── pytest.ini
├── requirements.txt
└── README.md
```
## 🔒 Seguridad
### Autenticación JWT
- **Login** con username/password → devuelve JWT
- **Protección** de endpoints `/v1/*` con Bearer token
- **Expiración** configurable (default: 60 minutos)
- **Rate limiting** básico en login (5 intentos / 5 minutos por IP)
### Contraseñas
- **Nunca** se loguean contraseñas en texto plano
- Uso de **bcrypt** para hashing
- Respuestas **uniformes** (401) en error de autenticación
### Archivos
- Validación de **tipo MIME**
- Validación de **extensión** `.pdf`
- **Tamaño máximo** configurable (default: 10 MB)
- Rechazo de **PDFs cifrados**
- Cálculo de **SHA256** para integridad
## ⚙️ Configuración (Variables de Entorno)
| Variable | Descripción | Default |
|----------|-------------|---------|
| `AUTH_USERNAME` | Usuario para login | `admin` |
| `AUTH_PASSWORD_HASH` | Hash bcrypt de contraseña | *requerido* |
| `JWT_SECRET` | Secret para firmar JWT | *requerido* |
| `JWT_EXPIRES_MINUTES` | Duración del token (minutos) | `60` |
| `MAX_FILE_MB` | Tamaño máximo de PDF (MB) | `10` |
| `LOG_LEVEL` | Nivel de logging | `INFO` |
| `REDIS_URL` | URL de conexión a Redis | `redis://redis:6379/0` |
| `CELERY_BROKER_URL` | URL del broker de Celery | `redis://redis:6379/0` |
| `CELERY_RESULT_BACKEND` | URL del backend de resultados | `redis://redis:6379/0` |
| `SERVICE_NAME` | Nombre del servicio | `mve-incrementables-parser` |
| `SERVICE_VERSION` | Versión del servicio | `1.0.0` |
## 📖 API Documentation
Una vez levantado el servicio, accede a la documentación interactiva:
- **Swagger UI**: http://localhost:9876/docs
- **ReDoc**: http://localhost:9876/redoc
## 🔍 Logging
El servicio utiliza **logging estructurado en JSON**:
```json
{
"time": "2026-03-02T10:30:45",
"level": "INFO",
"name": "app.api.v1.incrementables",
"message": "Successfully parsed incrementables",
"correlation_id": "550e8400-e29b-41d4-a716-446655440000",
"currency": "USD",
"fletes": 1591.20
}
```
### Correlation ID
- **Automático**: Se genera UUID si no se proporciona
- **Manual**: Enviar header `X-Correlation-Id`
- **Propagación**: Aparece en logs y respuesta
- **Utilidad**: Tracking de requests en sistemas distribuidos
## 🐛 Troubleshooting
### Error: "PyMuPDF extraction failed"
**Causa**: PDF corrupto o cifrado
**Solución**: El servicio intentará con pdfplumber automáticamente. Si ambos fallan:
- Verificar que el PDF no esté cifrado
- Intentar con otro PDF
- Revisar logs para detalles
### Error: "Incrementables section not found"
**Causa**: El PDF no contiene la sección esperada o el formato es diferente
**Solución**:
- Verificar que el PDF tenga el texto "AJUSTE DE INCREMENTABLES EN:"
- Revisar el formato del documento
- Si es un formato nuevo, actualizar los patrones en `app/services/parser.py`
### Error: "Rate limit exceeded"
**Causa**: Demasiados intentos de login desde la misma IP
**Solución**: Esperar 5 minutos o reiniciar el servicio
## 🚀 Despliegue en Producción
### Recomendaciones
1. **Cambiar credenciales**: Generar nueva contraseña y JWT_SECRET
2. **HTTPS**: Usar reverse proxy (nginx, traefik)
3. **Límites**: Ajustar `MAX_FILE_MB` según necesidad
4. **Logging**: Integrar con sistema de logs centralizado
5. **Monitoring**: Configurar health checks y alertas
6. **Recursos**: Ajustar memoria/CPU del contenedor
### Ejemplo con nginx
```nginx
server {
listen 443 ssl;
server_name api.mve.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
location / {
proxy_pass http://localhost:9876;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Aumentar límites para PDFs grandes
client_max_body_size 20M;
}
}
```
## 📝 Ejemplos de Formato Esperado
El servicio espera PDFs con formato similar a:
```
CARTA INSTRUCTIVO DE EMBARQUE No. 228718
AJUSTE DE INCREMENTABLES EN:
Fletes: $1,591.20 USD
Seguros: USD
Almacenaje/Consolidación: $0.00 USD
Regalías: USD
Observaciones...
```
### Campos Reconocidos
- **Fletes**: Requerido, con o sin signo `$`, con comas
- **Seguros**: Opcional, puede estar vacío (solo "USD" → `null`)
- **Almacenaje/Consolidación**: Requerido, acepta variaciones de escritura
- **Regalías/Regalias**: Opcional, con o sin acento
## 🤝 Contribuir
1. Fork el proyecto
2. Crear rama feature (`git checkout -b feature/amazing-feature`)
3. Commit cambios (`git commit -m 'Add amazing feature'`)
4. Push a la rama (`git push origin feature/amazing-feature`)
5. Abrir Pull Request
## 📄 Licencia
Este proyecto es parte del sistema MVE y es de uso interno.
## 📧 Contacto
Para soporte o dudas, contactar al equipo de desarrollo MVE.
---
**Versión:** 1.0.0
**Última actualización:** Marzo 2026