429 lines
17 KiB
Markdown
429 lines
17 KiB
Markdown
# Migración a microservicios y arquitectura IPSExtractor
|
||
|
||
Documento de referencia para la rama `migration-services` respecto a `main`.
|
||
Describe qué hay de nuevo, cómo funciona la infraestructura, los patrones de diseño, cómo escalar el sistema y cómo incorporar servicios futuros.
|
||
|
||
> **Nota:** el repositorio no tiene rama `master`; la línea base de comparación es **`main`**.
|
||
|
||
---
|
||
|
||
## 1. Resumen ejecutivo
|
||
|
||
IPSExtractor evolucionó de un **monolito Python** (interfaz gráfica de escritorio + API embebida) a una **arquitectura de microservicios** orquestada con Docker Compose, con una **aplicación de escritorio Electron + React** como cliente principal.
|
||
|
||
| Aspecto | Estado actual |
|
||
|---------|---------------|
|
||
| Despliegue | `docker compose up` — stack completo en contenedores |
|
||
| Puerta de entrada | **API Gateway** en `0.0.0.0:8189` (único puerto expuesto al host) |
|
||
| Persistencia | PostgreSQL 16 + migraciones Alembic |
|
||
| Cola async | Redis 7 + workers ARQ para extracción LLM |
|
||
| Conteo | `count-api` (Go, Tesseract + ONNX), stateless |
|
||
| Cliente | IPSExtractor Desktop **v1.0.0** (2026-07-06), instalador NSIS Windows |
|
||
|
||
**Alcance de la migración (rama `migration-services`):** ~170 archivos modificados, +27 000 / −8 500 líneas, 5 commits sobre `main`.
|
||
|
||
---
|
||
|
||
## 2. Qué cambió respecto a `main`
|
||
|
||
### Antes y después
|
||
|
||
| Antes (`main`) | Ahora (`migration-services`) |
|
||
|----------------|------------------------------|
|
||
| `gui/` + `gui_main.py` — UI desktop en Python | `services/desktop_app/` — Electron + React + TypeScript |
|
||
| `server/app/` — API monolítica | `api_gateway` + `extraction` + `cleaning` + `count-api` |
|
||
| `application/` — lógica de dominio acoplada al monolito | Dominio repartido bajo `services/<nombre>/` |
|
||
| `installer.iss` / `build_installer.bat` — Inno Setup | `electron-builder` → `IPSExtractor-Desktop-1.0.0-Setup.exe` |
|
||
| Sin orquestación unificada | [`docker-compose.yml`](../docker-compose.yml) — stack completo |
|
||
| Sin modelo de trabajos en BD | PostgreSQL: `jobs`, `tasks`, conteo, extracción, limpieza |
|
||
| Procesamiento síncrono en el mismo proceso | Redis + workers ARQ (`WORKER_REPLICAS`) |
|
||
|
||
### Eliminado
|
||
|
||
- `gui/` — pestañas de extracción, limpieza, métricas, preclasificación, etc.
|
||
- `application/` en la raíz del repo
|
||
- `server/` — API FastAPI monolítica
|
||
- `gui_main.py`, `requirements.txt` raíz, `installer.iss`
|
||
- Tests legacy en `tests/` (sustituidos por tests por servicio / desktop)
|
||
|
||
### Añadido (highlights)
|
||
|
||
| Ruta | Rol |
|
||
|------|-----|
|
||
| `services/api_gateway/` | Proxy HTTP único hacia el resto de servicios |
|
||
| `services/extraction/` | Jobs, tasks, conteo en BD, extracción LLM, API REST |
|
||
| `services/extraction/worker/` | Workers ARQ para procesamiento asíncrono |
|
||
| `services/cleaning/` | Limpieza y transformación de datos CSV |
|
||
| `services/services-count-facturas-medicas-serving/` | `count-api` — OCR + clasificación ONNX (Go) |
|
||
| `services/db/` | Migraciones Alembic (PostgreSQL) |
|
||
| `services/desktop_app/` | App Electron + React |
|
||
| `docs/database.dbml` | Esquema completo de la base de datos |
|
||
| `.env.example` | Variables de entorno de referencia |
|
||
|
||
### Commits en `migration-services` (sobre `main`)
|
||
|
||
1. Eliminación del monolito legacy + introducción de Docker Compose microservicios
|
||
2. App desktop Electron + wizards de extracción
|
||
3. Redis, `.env.example`, indexación de carpetas
|
||
4. Servicio `db-migrations`, jobs y conteo en PostgreSQL
|
||
5. Healthchecks, puerto 8189, OCR tuning, instalador desktop v1.0.0
|
||
|
||
---
|
||
|
||
## 3. Infraestructura nueva
|
||
|
||
### 3.1 Despliegue Docker
|
||
|
||
Solo el **API Gateway** publica un puerto al host. El resto de servicios se comunican por la red interna de Docker.
|
||
|
||
| Servicio | Puerto host | Puerto interno | Tecnología |
|
||
|----------|-------------|----------------|------------|
|
||
| `api_gateway` | **8189** | 8000 | Python / FastAPI |
|
||
| `extraction` | — | 8001 | Python / FastAPI |
|
||
| `cleaning` | — | 8002 | Python / FastAPI |
|
||
| `count-api` | — | 8080 | Go |
|
||
| `database` | — | 5432 | PostgreSQL 16 |
|
||
| `redis` | — | 6379 | Redis 7 |
|
||
| `worker` | — | — | Python ARQ (N réplicas) |
|
||
| `db-migrations` | — | — | Alembic (one-shot) |
|
||
|
||
```mermaid
|
||
flowchart TB
|
||
subgraph host [Host]
|
||
Desktop[DesktopApp Electron]
|
||
Port8189["0.0.0.0:8189"]
|
||
end
|
||
subgraph docker [Red Docker interna]
|
||
GW[api_gateway :8000]
|
||
EXT[extraction :8001]
|
||
CLN[cleaning :8002]
|
||
CNT[count-api :8080]
|
||
WRK[worker ARQ x N]
|
||
PG[(PostgreSQL)]
|
||
RD[(Redis)]
|
||
end
|
||
Desktop -->|"HTTPS prod o 127.0.0.1:8189 dev"| Port8189
|
||
Port8189 --> GW
|
||
GW --> EXT
|
||
GW --> CLN
|
||
GW --> CNT
|
||
EXT --> PG
|
||
EXT --> RD
|
||
WRK --> PG
|
||
WRK --> RD
|
||
CLN --> PG
|
||
```
|
||
|
||
### 3.2 Arranque y dependencias
|
||
|
||
El orden de arranque está controlado por healthchecks y `depends_on`:
|
||
|
||
1. `database` y `redis` deben estar **healthy**
|
||
2. `db-migrations` ejecuta Alembic y termina con éxito
|
||
3. `extraction`, `cleaning`, `count-api` y `worker` arrancan
|
||
4. `api_gateway` espera a `extraction` healthy y `count-api` healthy
|
||
|
||
```bash
|
||
cp .env.example .env
|
||
docker compose up --build
|
||
curl http://127.0.0.1:8189/health/live
|
||
```
|
||
|
||
> En Windows use `127.0.0.1` en lugar de `localhost` para evitar timeouts por resolución IPv6 (`::1`).
|
||
|
||
### 3.3 Flujo de negocio (centrado en jobs)
|
||
|
||
Un **job** agrupa el trabajo del usuario (carpeta de documentos). Dentro del job se ejecutan **tasks** por módulo (`counting`, `extraction`, `cleaning`).
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant App as DesktopApp
|
||
participant GW as ApiGateway
|
||
participant CNT as CountApi
|
||
participant EXT as Extraction
|
||
participant DB as PostgreSQL
|
||
|
||
App->>GW: POST /count/v1/count
|
||
GW->>CNT: proxy OCR+ONNX
|
||
CNT-->>App: resultados por PDF
|
||
App->>GW: POST /count-jobs
|
||
GW->>EXT: persistir conteo
|
||
EXT->>DB: count_job_files
|
||
App->>GW: POST /tasks + ingest-many
|
||
GW->>EXT: encolar extracción
|
||
EXT->>DB: extraction_rows
|
||
```
|
||
|
||
### 3.4 Rutas del API Gateway
|
||
|
||
El gateway en [`services/api_gateway/api/main.py`](../services/api_gateway/api/main.py) actúa como reverse proxy:
|
||
|
||
| Prefijo público | Servicio upstream |
|
||
|-----------------|-------------------|
|
||
| `/jobs`, `/tasks`, `/count-jobs` | `extraction:8001` |
|
||
| `/extraction/*` | `extraction:8001` |
|
||
| `/cleaning/*` | `cleaning:8002` |
|
||
| `/count/*` | `count-api:8080` |
|
||
| `/health`, `/health/live`, `/config`, `/models` | Agregación / proxy |
|
||
|
||
Timeouts diferenciados: peticiones JSON (~30 s) vs uploads OCR/conteo (hasta 600 s).
|
||
|
||
---
|
||
|
||
## 4. Patrones de diseño
|
||
|
||
| Patrón | Ubicación | Propósito |
|
||
|--------|-----------|-----------|
|
||
| **API Gateway / Reverse Proxy** | [`services/api_gateway/api/main.py`](../services/api_gateway/api/main.py) | Un solo endpoint público; enrutamiento por prefijo |
|
||
| **Microservicios sin paquetes compartidos** | `services/*` | Cada servicio con Dockerfile, dominio y dependencias propias |
|
||
| **Cola de trabajos async** | [`services/extraction/worker/`](../services/extraction/worker/), Redis | Extracción LLM fuera del ciclo request/response HTTP |
|
||
| **Registry / Plugin (desktop)** | [`services/desktop_app/src/renderer/modules/registry.ts`](../services/desktop_app/src/renderer/modules/registry.ts) | Módulos `counting` / `extraction` se registran sin acoplar `App.tsx` |
|
||
| **Job + Task (agregado)** | Tablas `jobs` → `tasks` en PostgreSQL | Un trabajo del usuario agrupa módulos ejecutados |
|
||
| **Healthchecks en cadena** | [`docker-compose.yml`](../docker-compose.yml) | `depends_on: service_healthy` evita arranques prematuros |
|
||
| **BFF con timeouts por ruta** | Gateway `_is_upload_path()` | JSON ligero vs uploads pesados |
|
||
| **Stateless vs stateful** | `count-api` (stateless) vs `extraction` (stateful + BD) | Conteo horizontalmente escalable; extracción con persistencia |
|
||
|
||
### Registry de módulos (desktop)
|
||
|
||
Cada módulo implementa `ModuleDescriptor` y se auto-registra al importarse:
|
||
|
||
```typescript
|
||
// services/desktop_app/src/renderer/modules/counting/CountingModule.ts
|
||
registerModule({
|
||
type: "counting",
|
||
label: "Conteo",
|
||
Wizard: CountingWizardModule,
|
||
Results: CountingResults,
|
||
hasTask: (tasks) => tasks.some((t) => t.type === "counting"),
|
||
});
|
||
```
|
||
|
||
`App.tsx` solo importa los módulos; no necesita conocer su implementación interna.
|
||
|
||
---
|
||
|
||
## 5. Componentes principales
|
||
|
||
### 5.1 Backend
|
||
|
||
| Servicio | Responsabilidad |
|
||
|----------|-----------------|
|
||
| **api_gateway** | Proxy HTTP, `/health/live`, CORS, logging, errores 503/504 hacia upstream caído |
|
||
| **extraction** | CRUD de jobs/tasks, count-jobs, ingest de PDFs, estimación de costos, API LLM |
|
||
| **worker** | Procesamiento ARQ en background; escala con `WORKER_REPLICAS` |
|
||
| **count-api** | OCR (Tesseract) + inferencia ONNX por lote; sin estado ni BD |
|
||
| **cleaning** | Reglas de limpieza CSV; expuesto vía `/cleaning/*` |
|
||
| **db-migrations** | Alembic one-shot antes de que arranquen los servicios de aplicación |
|
||
|
||
### 5.2 Base de datos (resumen)
|
||
|
||
```
|
||
jobs — contenedor de un trabajo del usuario
|
||
tasks — módulo ejecutado (counting / extraction / cleaning)
|
||
count_job_files — resultado de conteo por PDF
|
||
extraction_batches — lote de extracción
|
||
extraction_rows — fila extraída por documento
|
||
cleaning_jobs — job de limpieza
|
||
cleaned_rows — fila transformada
|
||
```
|
||
|
||
Esquema completo: [`database.dbml`](database.dbml)
|
||
|
||
### 5.3 Desktop (`services/desktop_app/`)
|
||
|
||
| Componente | Función |
|
||
|------------|---------|
|
||
| `App.tsx` | Navegación Home / Job / Settings; sidebar con submenú por job |
|
||
| `HomeView.tsx` | Listado y creación de jobs |
|
||
| `JobView.tsx` | Paneles de conteo y extracción (progreso en segundo plano) |
|
||
| `JobResultsView.tsx` | Resultados unificados, export CSV, lanzar extracción desde válidos |
|
||
| `CountingWizard.tsx` | Lotes paralelos hacia `POST /count/v1/count` |
|
||
| `JobExtractionWizard.tsx` | Extracción con upload de PDFs desde el escritorio |
|
||
| `apiClient.ts` | Cliente HTTP único hacia el gateway (timeouts, mensajes de error) |
|
||
| `electron/folderIndex.cjs` | Indexación local: modo `general` o `radicados` |
|
||
| `electron/preload.cjs` | Bridge seguro main ↔ renderer (`contextBridge`) |
|
||
|
||
---
|
||
|
||
## 6. Cómo escalar para mayor velocidad
|
||
|
||
### 6.1 Conteo (`count-api`)
|
||
|
||
El conteo es el cuello de botella más frecuente en lotes grandes de PDFs.
|
||
|
||
| Variable / ajuste | Efecto | Dónde |
|
||
|-------------------|--------|-------|
|
||
| `OCR_DPI=150` | ~4× menos RAM que 300 DPI | `docker-compose.yml` |
|
||
| `OCR_POOL_SIZE` | Goroutines OCR por request | `docker-compose.yml` |
|
||
| `SYNC_PROCESS_CONCURRENCY` | Paralelismo interno por request | `docker-compose.yml` |
|
||
| `COUNT_API_MEMORY` | Límite RAM del contenedor (evitar OOM) | `docker-compose.yml` |
|
||
| `restart: unless-stopped` | Recuperación automática tras OOM | `docker-compose.yml` |
|
||
| `BATCH_SIZE=10` | PDFs por petición HTTP | `CountingWizard.tsx` |
|
||
| `BATCH_CONCURRENCY=2` | Lotes HTTP simultáneos desde desktop | `CountingWizard.tsx` |
|
||
|
||
**Futuro:** réplicas de `count-api` detrás del gateway con balanceo round-robin en el proxy.
|
||
|
||
### 6.2 Extracción LLM
|
||
|
||
| Variable / comando | Efecto |
|
||
|--------------------|--------|
|
||
| `WORKER_REPLICAS=4` | Más workers ARQ procesando en paralelo |
|
||
| `docker compose up --scale worker=4` | Escala horizontal de workers |
|
||
| `WORKER_CONCURRENCY` | Tareas concurrentes por worker |
|
||
| `CONCURRENCY_LIMIT` | Límite global en el servicio extraction |
|
||
| `PDF_THREAD_COUNT` | Hilos para renderizado de PDF |
|
||
| `PDF_DPI` | Resolución de páginas enviadas al LLM |
|
||
|
||
### 6.3 Infraestructura y build
|
||
|
||
| Problema | Solución |
|
||
|----------|----------|
|
||
| BuildKit satura al construir `count-api` | `docker compose build count-api` antes del stack completo |
|
||
| Build paralelo falla en Docker Desktop | `COMPOSE_BAKE=false COMPOSE_PARALLEL_LIMIT=2` (ver `.env.example`) |
|
||
| Conexiones BD colgadas | Pool SQLAlchemy + `connect_timeout` en [`database.py`](../services/extraction/infrastructure/database.py) |
|
||
| Gateway sin respuesta en Windows | Usar `127.0.0.1:8189`, no `localhost` |
|
||
|
||
---
|
||
|
||
## 7. Cómo añadir un servicio futuro
|
||
|
||
Checklist para incorporar un nuevo microservicio sin romper la arquitectura:
|
||
|
||
### Paso 1 — Crear el servicio
|
||
|
||
```
|
||
services/mi-servicio/
|
||
Dockerfile
|
||
api/main.py # API propia (FastAPI, Go, etc.)
|
||
domain/ # lógica de negocio
|
||
requirements.txt
|
||
README.md
|
||
```
|
||
|
||
### Paso 2 — Registrar en Docker Compose
|
||
|
||
En [`docker-compose.yml`](../docker-compose.yml):
|
||
|
||
- Añadir el servicio **sin** `ports` al host (solo red interna)
|
||
- Definir `healthcheck` si expone `/health`
|
||
- Configurar `depends_on` según dependencias (BD, migraciones, etc.)
|
||
|
||
### Paso 3 — Exponer vía API Gateway
|
||
|
||
En [`services/api_gateway/api/main.py`](../services/api_gateway/api/main.py):
|
||
|
||
```python
|
||
MI_SERVICIO_URL = os.getenv("MI_SERVICIO_URL", "http://mi-servicio:8003").rstrip("/")
|
||
|
||
@app.api_route("/mi-servicio/{path:path}", methods=["GET", "POST", "PUT", "PATCH", "DELETE"])
|
||
async def mi_servicio_proxy(path: str, request: Request) -> Response:
|
||
return await _proxy_request(request, f"{MI_SERVICIO_URL}/{path}")
|
||
```
|
||
|
||
Añadir `MI_SERVICIO_URL` en el bloque `environment` de `api_gateway` en compose.
|
||
|
||
### Paso 4 — Base de datos (si aplica)
|
||
|
||
1. Crear migración Alembic en `services/db/alembic/versions/`
|
||
2. El servicio `db-migrations` la aplicará en el próximo `docker compose up`
|
||
|
||
### Paso 5 — Módulo en la app desktop (si aplica)
|
||
|
||
1. Crear `services/desktop_app/src/renderer/modules/mi-modulo/`
|
||
2. Implementar `ModuleDescriptor` y llamar `registerModule()`
|
||
3. Importar el módulo en `App.tsx` (una línea, igual que counting/extraction)
|
||
4. Añadir entrada en el sidebar de `JobView` si requiere sección propia
|
||
|
||
### Paso 6 — Documentación
|
||
|
||
- README del servicio
|
||
- Actualizar tabla de servicios en [`README.md`](../README.md)
|
||
- Actualizar este documento si el patrón de integración cambia
|
||
|
||
```mermaid
|
||
flowchart LR
|
||
NewSvc[nuevo_servicio]
|
||
Compose[docker-compose]
|
||
GW[api_gateway nueva ruta]
|
||
Reg[registerModule desktop]
|
||
NewSvc --> Compose
|
||
Compose --> GW
|
||
GW --> Desktop
|
||
Reg --> Desktop
|
||
```
|
||
|
||
---
|
||
|
||
## 8. App de escritorio — versión y funcionalidades
|
||
|
||
### 8.1 Versión e instalador
|
||
|
||
| Campo | Valor |
|
||
|-------|-------|
|
||
| **Versión** | 1.0.0 |
|
||
| **Fecha de release** | 2026-07-06 |
|
||
| **Instalador** | `services/desktop_app/release/IPSExtractor-Desktop-1.0.0-Setup.exe` |
|
||
| **Formato** | NSIS, Windows x64 |
|
||
| **Fuente de versión** | `services/desktop_app/package.json` + `src/shared/version.json` |
|
||
|
||
Generar instalador:
|
||
|
||
```bash
|
||
cd services/desktop_app
|
||
npm install
|
||
npm run dist
|
||
```
|
||
|
||
### 8.2 URL del API Gateway
|
||
|
||
| Entorno | URL por defecto |
|
||
|---------|-----------------|
|
||
| Desarrollo (`npm run dev`) | `http://127.0.0.1:8189` |
|
||
| Instalador (build producción) | `https://pys1.builker.xyz` |
|
||
|
||
La URL es configurable en **Configuración** de la app y se persiste en `localStorage`.
|
||
|
||
### 8.3 Funcionalidades incluidas
|
||
|
||
- **Gestión de trabajos (jobs):** crear, abrir y organizar trabajos con carpeta local de documentos
|
||
- **Conteo:** OCR + clasificación ML en lotes con barra de progreso en segundo plano
|
||
- **Validación humana:** marcar archivos del conteo como válidos/inválidos
|
||
- **Extracción LLM:** wizard con descubrimiento de campos, estimación de costo y ejecución
|
||
- **Extracción desde conteo:** lanzar extracción solo sobre PDFs válidos del conteo
|
||
- **Resultados unificados:** vista de conteo y extracción con exportación CSV
|
||
- **Configuración:** URL del gateway, verificación de conexión, vista de config/modelos del backend
|
||
- **Versión visible:** sidebar (menú) y esquina superior derecha (`v1.0.0 · 6 jul 2026`)
|
||
|
||
### 8.4 Arquitectura de la UI
|
||
|
||
```
|
||
Home (lista de jobs)
|
||
└── Job activo
|
||
├── Conteo → CountingWizardModule
|
||
├── Extracción → JobExtractionWizard
|
||
└── Resultados → JobResultsView
|
||
Configuración (global)
|
||
```
|
||
|
||
La navegación usa sidebar lateral con submenú contextual por job (sin tabs en el área principal).
|
||
|
||
---
|
||
|
||
## 9. Enlaces relacionados
|
||
|
||
| Documento | Descripción |
|
||
|-----------|-------------|
|
||
| [`README.md`](../README.md) | Guía rápida del stack completo |
|
||
| [`services/api_gateway/README.md`](../services/api_gateway/README.md) | API Gateway |
|
||
| [`services/extraction/README.md`](../services/extraction/README.md) | Extracción LLM y jobs |
|
||
| [`services/cleaning/README.md`](../services/cleaning/README.md) | Limpieza de datos |
|
||
| [`services/services-count-facturas-medicas-serving/README.md`](../services/services-count-facturas-medicas-serving/README.md) | Count API (Go) |
|
||
| [`services/db/README.md`](../services/db/README.md) | Migraciones Alembic |
|
||
| [`services/desktop_app/README.md`](../services/desktop_app/README.md) | App Electron |
|
||
| [`database.dbml`](database.dbml) | Esquema completo de PostgreSQL |
|
||
| [`.env.example`](../.env.example) | Variables de entorno |
|
||
|
||
---
|
||
|
||
*Última actualización: 2026-07-06 — rama `migration-services`.*
|