@@ -0,0 +1,428 @@
# 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.builder.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`. *