\chapter{Arquitectura bajo prueba} \label{ch:arquitectura} \section{C\'omo est\'a dise\~nado el sistema} IPSExtractor sigue una arquitectura de \textbf{microservicios} detrĂ¡s de un \textbf{API Gateway}. El host solo expone el puerto \textbf{8189}; el resto vive en la red Docker. Los agents Selenium corren en PCs externas (HTTP + PutObject a MinIO). \noindent\textbf{Nombres:} \texttt{bot-descarga} = orquestador (API); \textbf{agent Selenium} = PC que descarga. ST-BOT-* mide el orquestador; $\sim$422 fact/h es del agent. \input{diagrams/arquitectura} \section{Patrones de dise\~no} \begin{description}[style=nextline,leftmargin=0.8cm,labelwidth=3.2cm] \item[API Gateway] \'Unica puerta p\'ublica. Proxy a extraction, cleaning, count-api y bot-descarga. Centraliza CORS, health y timeouts (JSON 30\,s; uploads del orquestador 600\,s). \emph{Implicaci\'on de prueba:} ST-GW-* miden el gateway, no el OCR ni el LLM. \item[Bounded contexts] Cada servicio tiene un dominio claro: OCR (count-api), LLM (extraction+workers), orquestaci\'on de descarga (\texttt{bot-descarga}), limpieza CSV (cleaning). \emph{Implicaci\'on:} fallos y capacidad se prueban por servicio, no solo E2E. \item[Cola as\'incrona (ARQ)] Con \texttt{ASYNC\_QUEUE=true}, el ingest encola en Redis y los workers drenan. Con \texttt{false} (default), el API procesa en el mismo request (sync). \emph{Implicaci\'on:} ST-EXT-01 exige async + workers; ST-EXT-02 puede correr sync. \item[Claim / lease] Un job \texttt{pending} solo lo toma un agent Selenium (claim at\'omico v\'ia \texttt{bot-descarga}). Heartbeat mantiene al worker online; sin heartbeat, no se asignan jobs. \emph{Implicaci\'on:} ST-BOT-01/02 miden el orquestador (heartbeats y contenci\'on de claim). \end{description} \input{diagrams/patrones} \section{Pipeline de negocio} \input{diagrams/pipeline_negocio} \begin{enumerate} \item \textbf{Descarga:} agent Selenium obtiene PDFs $\rightarrow$ MinIO. \item \textbf{Conteo:} OCR/ONNX valida el documento (filtro de calidad). \item \textbf{Extracci\'on:} LLM rellena campos $\rightarrow$ PostgreSQL. \item \textbf{Limpieza:} reglas/CSV sobre el lote. \end{enumerate} \section{Inventario de componentes} \begin{table}[ht] \centering \begin{tabular}{@{}llp{5.8cm}@{}} \toprule \textbf{Componente} & \textbf{Puerto} & \textbf{Responsabilidad} \\ \midrule \texttt{api\_gateway} & Host 8189 & Proxy HTTP, health agregado, CORS \\ \texttt{extraction} & 8001 & Tasks, LLM, ingest PDFs \\ \texttt{worker} (ARQ) & --- & Procesamiento async v\'ia Redis \\ \texttt{cleaning} & 8002 & Limpieza CSV / reglas \\ \texttt{count-api} & 8080 & OCR + ONNX, stateless \\ \texttt{bot-descarga} & 8200 & Orquestador: jobs, claim, heartbeats, events \\ PostgreSQL 16 & 5432 & Persistencia compartida \\ Redis 7 & 6379 & Cola ARQ + rate limiter \\ Desktop & local & Cliente Electron/React \\ Agent Selenium & local PC & Descarga PDFs (Selenium/Imaging) + MinIO upload \\ MinIO/S3 & externo & Excel y PDFs del pipeline de descarga \\ \bottomrule \end{tabular} \caption{Componentes del sistema bajo prueba.} \end{table} \section{Flujo cr\'itico: descarga (orquestador + agent Selenium)} \input{diagrams/flujo_bot} Secuencia resumida: \begin{enumerate} \item Agent Selenium: \texttt{register} + loop \texttt{heartbeat} / \texttt{claim\_pending} ($\sim$8\,s). \item Desktop: \texttt{POST /bot-descarga/jobs} (Excel, \texttt{worker\_id}, \texttt{job\_id}). \item API: valida job padre y worker online; sube Excel a MinIO; estado \texttt{pending}. \item Agent Selenium: claim $\rightarrow$ \texttt{RUNNING}; descarga Excel; Selenium/Imaging; PutObject PDFs. \item Eventos \texttt{POST /events}; cierre \texttt{PATCH /jobs/\{id\}} (\texttt{completed}/\texttt{failed}). \end{enumerate} Layout MinIO: \begin{lstlisting}[language={},caption={Prefijos MinIO del pipeline de descarga}] uploads/{download_job_id}/input.xlsx downloads/{download_job_id}/{nit}/{radicado}.pdf downloads/{download_job_id}/_manifest.json \end{lstlisting} \section{Otros flujos de negocio} \begin{description} \item[Conteo] Desktop $\rightarrow$ \texttt{POST /count/v1/count} $\rightarrow$ \texttt{POST /count-jobs} $\rightarrow$ validaci\'on humana. \item[Extracci\'on] \texttt{from-count} o ingest de PDFs $\rightarrow$ (ARQ) workers $\rightarrow$ filas en BD. \item[Limpieza] Job cleaning desde \texttt{extraction\_batch\_id} o CSV upload. \end{description}