Files
mve-micro-docs/README.md

13 KiB

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)
  • 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)

🛠️ Instalación

Opción 1: Con Docker (Recomendado)

  1. Clonar el repositorio
cd mve-incrementables-parser
  1. Configurar variables de entorno
cp .env.example .env

Edita .env y configura:

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

Generar hash de contraseña:

python -c "from passlib.hash import bcrypt; print(bcrypt.hash('tu_contraseña'))"
  1. Levantar el servicio
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

Opción 2: Sin Docker

  1. Instalar dependencias del sistema (para PyMuPDF)
# macOS
brew install mupdf-tools

# Ubuntu/Debian
sudo apt-get install libmupdf-dev mupdf-tools
  1. Crear entorno virtual
python3.12 -m venv venv
source venv/bin/activate  # En Windows: venv\Scripts\activate
  1. Instalar dependencias Python
pip install -r requirements.txt
  1. Configurar .env (ver Opción 1, paso 2)

  2. Ejecutar el servicio

uvicorn app.main:app --host 0.0.0.0 --port 9876 --reload

📚 Uso

1. Health Check

curl http://localhost:9876/health

Respuesta:

{
  "status": "ok",
  "service": "mve-incrementables-parser",
  "version": "1.0.0"
}

2. Autenticación (Login)

curl -X POST http://localhost:9876/auth/login \
  -H "Content-Type: application/json" \
  -d '{
    "username": "admin",
    "password": "tu_contraseña"
  }'

Respuesta:

{
  "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)

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:

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):

{
  "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:

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):

{
  "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

curl -X GET http://localhost:9876/v1/incrementables/status/a8f2e9c1-5b3d-4e7f-9a1c-2d3e4f5a6b7c \
  -H "Authorization: Bearer <TU_TOKEN_AQUI>"

Respuesta (en proceso):

{
  "task_id": "a8f2e9c1-5b3d-4e7f-9a1c-2d3e4f5a6b7c",
  "status": "started",
  "result": null,
  "error": null
}

Respuesta (completada):

{
  "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

# 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

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