Files
IPSExtractor/docs/MIGRACION_MICROSERVICIOS.md
T

429 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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`.*