feat: Update docker-compose configuration to use environment variables for sensitive data, enhance testing documentation with new LaTeX files for capacity planning, and refine README for clarity on compilation and testing processes.

This commit is contained in:
erik
2026-07-17 08:32:00 -05:00
parent e47ef4f8c6
commit dc3fcc2280
89 changed files with 5331 additions and 1210 deletions
@@ -0,0 +1,45 @@
\chapter{Objetivo: 100\,000 facturas al mes}
\label{ch:objetivo}
\section{Qu\'e pregunta responde este documento}
\textquestiondown Puede IPSExtractor procesar del orden de \textbf{100\,000 facturas m\'edicas al mes}?
\callout{\textbf{Anticipo del veredicto (detalle en cap.~\ref{ch:veredicto})}}{%
\textbf{En el host compartido actual: no garantizado} (pipeline $\approx$ OCR $\sim$100--325/h; piso $\sim$100 $<$ 139).\\
\textbf{Con host dedicado y OCR dimensionado: s\'i plausible para 24$\times$7;} jornada 8\,h pide m\'as escalado.
}
La respuesta depende de dos cosas:
\begin{enumerate}
\item \textbf{Horario:} 24$\times$7 vs solo jornada laboral.
\item \textbf{Infra:} host compartido (hoy) vs host dedicado + OCR, agent Selenium y LLM dimensionados (orquestador \texttt{bot-descarga} en Docker).
\end{enumerate}
\section{Traducci\'on a ritmo (facturas/hora)}
\begin{table}[ht]
\centering
\begin{tabular}{@{}lrr@{}}
\toprule
\textbf{Escenario operativo} & \textbf{Facturas/d\'ia} & \textbf{Facturas/hora} \\
\midrule
100\,000 / mes, 24$\times$7 & $\approx$3\,333 & $\approx$\textbf{139} \\
100\,000 / mes, 8\,h $\times$ 22\,d & $\approx$4\,545 & $\approx$\textbf{570} \\
\bottomrule
\end{tabular}
\caption{Ritmo m\'inimo para cumplir 100k/mes.}
\label{tab:objetivos-h}
\end{table}
\callout{\large Lectura r\'apida}{%
24$\times$7 $\Rightarrow$ $\approx$\textbf{139 fact/h} punta a punta.\\
Jornada 8\,h $\Rightarrow$ $\approx$\textbf{570 fact/h}.\\
Gana la etapa m\'as lenta; el resto sobra si esa falla.
}
\section{C\'omo usar este documento}
\begin{enumerate}
\item Mire el veredicto (cap.~\ref{ch:veredicto}): s\'i / no / qu\'e falta.
\item Compare etapas vs 139 y 570 (tabla~\ref{tab:capacidad}).
\item Revise evidencias medidas (cap.~\ref{ch:evidencias}) y coste LLM (cap.~\ref{ch:coste}).
\item Use el checklist de requisitos (cap.~\ref{ch:requisitos}) como plan de acci\'on.
\end{enumerate}
@@ -0,0 +1,44 @@
\chapter{Arquitectura del pipeline}
\label{ch:arq-cap}
\callout{\textbf{Nombres (importante)}}{%
\textbf{bot-descarga} = servicio de orquestaci\'on (API Docker): jobs, claim, heartbeats.\\
\textbf{Agent Selenium} = programa en el PC que descarga PDFs (Imaging $\rightarrow$ MinIO).\\
Las pruebas \textbf{ST-BOT-*} miden el orquestador. La capacidad $\sim$422 fact/h es del agent.
}
\section{Patrones de dise\~no relevantes}
IPSExtractor no es un monolito: el volumen mensualmente viable depende de c\'omo
escalan \textbf{por separado} cada bounded context.
\begin{description}[style=nextline,leftmargin=0.5cm]
\item[API Gateway]
\'Unica puerta HTTP en el host (\texttt{:8189}). Orquesta, no procesa OCR ni LLM.
Escala de control; no es el cuello de 100k.
\item[Microservicios por dominio]
\texttt{count-api} (OCR), \texttt{extraction}+workers (LLM), \texttt{bot-descarga} (orquestador)
+ agent Selenium (PC), \texttt{cleaning} (CSV). Cada uno tiene su propio l\'imite de capacidad.
\item[Cola as\'incrona (ARQ/Redis)]
La extracci\'on puede encolar PDFs y drenar con $N$ workers. Throughput LLM
$\approx N \times (3600/\mathrm{p50})$ si no hay cuota del proveedor.
\item[Claim/lease del orquestador (\texttt{bot-descarga})]
Varios agents Selenium en PCs distintos reclaman jobs v\'ia la API; la capacidad de descarga
crece con $N$ agents, no con un solo proceso Selenium.
\end{description}
\section{Flujo de negocio (punta a punta)}
\input{../diagrams/pipeline_negocio}
Orden t\'ipico:
\begin{enumerate}
\item \textbf{Descarga:} agent Selenium $\rightarrow$ PDFs en MinIO.
\item \textbf{Conteo:} OCR/ONNX valida p\'aginas/palabras (filtro).
\item \textbf{Extracci\'on:} LLM rellena campos $\rightarrow$ filas en PostgreSQL.
\item \textbf{Limpieza:} reglas/CSV sobre el lote extra\'ido.
\end{enumerate}
El pipeline completo solo es tan r\'apido como la etapa m\'as lenta bajo la misma
contenci\'on de CPU/RAM.
\section{Vista de componentes}
\input{../diagrams/arquitectura}
@@ -0,0 +1,237 @@
\chapter{Modelo de capacidad por etapa}
\label{ch:modelo}
\callout{\textbf{Idea en una frase}}{%
Cada etapa tiene un ritmo (facturas/hora).\\
El \textbf{pipeline completo} solo va tan r\'apido como la etapa m\'as lenta
(hoy: OCR en host compartido).\\
El agent Selenium y el LLM pueden ir bien por separado y el sistema entero igual no llega a 100k/mes.
}
\section{C\'omo leer los n\'umeros}
\begin{itemize}
\item \textbf{Host actual:} servidor compartido de las pruebas (8\,GiB + MLflow y otros).
\item \textbf{Host dedicado:} estimaci\'on si IPSExtractor va solo (sin competir por CPU/RAM).
\item \textbf{139/h:} m\'inimo para 100k/mes en 24$\times$7.
\item \textbf{570/h:} m\'inimo para 100k/mes en jornada 8\,h $\times$ 22\,d.
\end{itemize}
\noindent Sem\'aforo:
\textcolor{passgreen}{\textbf{S\'I}} = llega;
\textcolor{warnorange}{\textbf{JUSTO}} = cerca / hace falta margen;
\textcolor{failred}{\textbf{NO}} = no llega.
\section{Tabla de capacidad (datos julio 2026)}
\begin{itemize}
\item LLM ST-EXT-02: p50 7{,}1\,s $\Rightarrow$ $\approx$506 fact/h (1 worker).
\item OCR ST-COUNT-01: p50 35{,}8\,s @3 concurrentes $\Rightarrow$ $\approx$100--325 fact/h
(en serie era $\sim$2\,s/PDF).
\item Agent Selenium: 633 PDFs / 90\,min $\Rightarrow$ $\approx$422 fact/h (1 agent PC).
\item Gateway ST-GW-01: p95 41\,ms, 0\,\% fallos --- no limita.
\end{itemize}
\begin{table}[ht]
\centering
\small
\begin{tabular}{@{}lrrcc@{}}
\toprule
\textbf{Etapa} & \textbf{Host actual} & \textbf{Dedicado (est.)} & \textbf{\textquestiondown 139/h?} & \textbf{\textquestiondown 570/h?} \\
& fact/h & fact/h & & \\
\midrule
Gateway & $>$10\,000 & $>$20\,000 & \textcolor{passgreen}{\textbf{S\'I}} & \textcolor{passgreen}{\textbf{S\'I}} \\
OCR (conteo) & $\sim$100--325 & $\sim$400 & \textcolor{warnorange}{\textbf{JUSTO}} & \textcolor{failred}{\textbf{NO}} \\
LLM (extracci\'on) & $\sim$506 & $\sim$1\,000 (2\,w) & \textcolor{passgreen}{\textbf{S\'I}} & \textcolor{warnorange}{\textbf{JUSTO}} \\
Descarga (agent) & $\sim$422 & $\sim$500+ & \textcolor{passgreen}{\textbf{S\'I}} & \textcolor{failred}{\textbf{NO}} \\
Limpieza CSV & $\sim$800 & $\sim$3\,000 & \textcolor{passgreen}{\textbf{S\'I}} & \textcolor{warnorange}{\textbf{JUSTO}} \\
\midrule
\textbf{Pipeline entero} & $\mathbf{\sim}$\textbf{100--325} & $\mathbf{\sim}$\textbf{400} & \textcolor{warnorange}{\textbf{JUSTO}}$^{\mathrm{P}}$ & \textcolor{failred}{\textbf{NO}} \\
\bottomrule
\end{tabular}
\caption{Ritmo por etapa. Pipeline $\approx\min$ de etapas (cuello OCR). $^{\mathrm{P}}$Conservador
$\sim$100 $<$ 139; techo observado OCR $\sim$325 puede cubrir 139 si el host no degrada m\'as.}
\label{tab:capacidad}
\end{table}
\section{C\'omo se obtiene el ritmo del pipeline}
\subsection*{Primero: qu\'e \emph{no} es ese n\'umero}
No corrimos 100\,000 PDFs de punta a punta midiendo el reloj.
El ritmo del ``pipeline entero'' es un \textbf{modelo}: se calcula a partir de las
etapas que s\'i medimos (agent, OCR, LLM, limpieza).
\subsection*{Una factura es secuencial; muchas facturas no}
Para \textbf{una} factura el orden es obligatorio:
\begin{center}
descarga $\rightarrow$ conteo OCR $\rightarrow$ extracci\'on LLM $\rightarrow$ limpieza.
\end{center}
No puede empezar el LLM de esa factura si a\'un no termin\'o el OCR.
Pero en el d\'ia a d\'ia no hay una sola factura: hay un \textbf{lote}.
Mientras la factura A est\'a en el LLM, la B puede estar en OCR y la C en descarga.
Es como una cadena de montaje: cada puesto trabaja en paralelo sobre piezas distintas.
\subsection*{Analog\'ia del embudo}
El ritmo del sistema completo lo marca el \textbf{puesto m\'as lento}
(el cuello / embudo), no el m\'as r\'apido.
\begin{center}
\begin{tabular}{@{}lrl@{}}
\toprule
\textbf{Puesto (etapa)} & \textbf{Ritmo medido} & \textbf{Lectura} \\
\midrule
Descarga (agent Selenium) & $\approx$422 fact/h & r\'apido \\
Conteo (OCR) & $\approx$100--325 fact/h & \textbf{el m\'as lento} \\
Extracci\'on (LLM) & $\approx$506 fact/h & r\'apido \\
Limpieza & $\approx$800 fact/h & r\'apido \\
\midrule
\textbf{Pipeline entero} & $\approx$\textbf{100--325 fact/h} & $=\min$ = OCR \\
\bottomrule
\end{tabular}
\end{center}
Por eso da igual que el agent haga 422/h: si el OCR solo deja pasar 100--325/h,
el lote completo no puede ir m\'as r\'apido que eso.
\subsection*{F\'ormula (r\'egimen normal / cadena de montaje)}
\[
\text{ritmo del pipeline} \;\approx\; \min(\text{descarga},\,\text{OCR},\,\text{LLM},\,\text{limpieza}).
\]
Con los n\'umeros de arriba: $\min(422,\,100\text{--}325,\,506,\,800) = \mathbf{100\text{--}325}$.
\subsection*{Y si proces\'aramos de una en una, sin solapar?}
Entonces s\'i se \textbf{suman} los tiempos de cada etapa por factura:
\[
\text{fact/h} \approx 3600\big/\big(t_{\mathrm{descarga}}+t_{\mathrm{OCR}}+t_{\mathrm{LLM}}+t_{\mathrm{limpieza}}\big).
\]
Ejemplo con OCR lento ($\sim$36\,s): $\sim$70 fact/h.
Con OCR r\'apido en serie ($\sim$2\,s): $\sim$200 fact/h.
Eso ser\'ia un laboratorio artificial (una factura y la siguiente solo cuando acaba).
En producci\'on real usamos el modelo del embudo ($\min$), que es el que importa.
\subsection*{Qu\'e ponemos en tablas y gr\'aficos}
\begin{itemize}
\item Host actual: pipeline = $\sim$100--325 (cuello OCR).
\item Para el veredicto 24$\times$7 usamos el \textbf{piso} $\sim$100 (conservador; queda bajo 139).
\item Host dedicado (est.): $\sim$400 (OCR estimado $\sim$400).
\end{itemize}
\section{Gr\'afico 1 --- etapas vs objetivos}
\begin{figure}[ht]
\centering
\begin{tikzpicture}
\begin{axis}[
ybar,
width=\textwidth,
height=6.8cm,
ylabel={Facturas / hora},
symbolic x coords={OCR,LLM,Agent,Limpieza,Pipeline},
xtick=data,
xticklabels={OCR,LLM,{Agent Selenium},Limpieza,Pipeline},
x tick label style={font=\small},
legend style={at={(0.02,0.98)},anchor=north west,font=\small,draw=ipsgrey!40},
bar width=11pt,
ymin=0, ymax=1100,
enlarge x limits=0.12,
grid=major,
major grid style={ipsgrey!20},
]
\addplot[fill=ipsblue!75, draw=ipsblue] coordinates {
(OCR,200) (LLM,506) (Agent,422) (Limpieza,800) (Pipeline,200)
};
\addlegendentry{Host actual}
\addplot[fill=passgreen!60, draw=passgreen] coordinates {
(OCR,400) (LLM,1012) (Agent,500) (Limpieza,1000) (Pipeline,400)
};
\addlegendentry{Host dedicado (est.)}
\draw[failred, ultra thick] (axis cs:OCR,139) -- (axis cs:Pipeline,139)
node[pos=0, left, font=\scriptsize\bfseries, text=failred] {139};
\draw[warnorange, ultra thick] (axis cs:OCR,570) -- (axis cs:Pipeline,570)
node[pos=0, left, font=\scriptsize\bfseries, text=warnorange] {570};
\end{axis}
\end{tikzpicture}
\caption{Azul = host compartido (medido/estimado). Verde = host dedicado (estimado).
L\'ineas = m\'inimos para 100k/mes.}
\label{fig:capacidad}
\end{figure}
\section{Gr\'afico 2 --- \textquestiondown llegamos a 100k/mes?}
\subsection*{C\'omo leer este gr\'afico}
Solo hay \textbf{dos barras} = cu\'anto puede el pipeline (modelo):
\begin{itemize}
\item Rojo = host \textbf{actual} (piso $\sim$100 fact/h).
\item Verde = host \textbf{dedicado} estimado ($\sim$400 fact/h).
\end{itemize}
Las \textbf{l\'ineas verticales} no son barras: son las metas que hay que superar:
\begin{itemize}
\item Azul punteada = 139/h (100k/mes trabajando 24$\times$7).
\item Naranja punteada = 570/h (100k/mes en jornada 8\,h).
\end{itemize}
Si la barra \textbf{pasa} la l\'inea $\Rightarrow$ esa meta es alcanzable. Si se queda corta $\Rightarrow$ no.
\begin{figure}[H]
\centering
\begin{tikzpicture}
\begin{axis}[
xbar,
width=0.78\textwidth,
height=4.8cm,
xlabel={Facturas / hora que aguanta el pipeline},
symbolic y coords={dedicado,actual},
ytick=data,
yticklabels={\empty,\empty},
xmin=0, xmax=650,
enlarge y limits=0.45,
bar width=22pt,
bar shift=0pt,
grid=major,
major grid style={ipsgrey!25},
axis y line*=left,
axis x line*=bottom,
clip=false,
]
\addplot[fill=failred!75, draw=failred] coordinates {(100,actual)};
\addplot[fill=passgreen!75, draw=passgreen] coordinates {(400,dedicado)};
% Etiquetas izquierda
\node[anchor=east, font=\small\bfseries, align=right]
at (axis cs:-5,actual) {Host actual\\(hoy)};
\node[anchor=east, font=\small\bfseries, align=right]
at (axis cs:-5,dedicado) {Host dedicado\\(estimado)};
% Valores
\node[anchor=west, font=\normalsize\bfseries] at (axis cs:110,actual) {$\sim$100 fact/h};
\node[anchor=west, font=\normalsize\bfseries] at (axis cs:410,dedicado) {$\sim$400 fact/h};
% Metas como l\'ineas (no barras)
\draw[ipsblue, ultra thick, dashed]
(axis cs:139,actual) -- (axis cs:139,dedicado);
\node[font=\scriptsize\bfseries, text=ipsblue, anchor=south, align=center]
at (axis cs:139,dedicado) {meta 139\\(24$\times$7)};
\draw[warnorange, ultra thick, dashed]
(axis cs:570,actual) -- (axis cs:570,dedicado);
\node[font=\scriptsize\bfseries, text=warnorange, anchor=south, align=center]
at (axis cs:570,dedicado) {meta 570\\(jornada)};
\end{axis}
\end{tikzpicture}
\caption{Solo capacidad (barras) vs metas (l\'ineas). Modelo por cuello OCR; no es E2E de 100k.}
\label{fig:pipeline-metas}
\end{figure}
\begin{center}
\renewcommand{\arraystretch}{1.25}
\begin{tabular}{@{}lcc@{}}
\toprule
& \textbf{\textquestiondown Llega a 139/h?} & \textbf{\textquestiondown Llega a 570/h?} \\
& (100k en 24$\times$7) & (100k en 8\,h) \\
\midrule
Host actual $\sim$100 & \textcolor{failred}{\textbf{NO}} (falta margen) & \textcolor{failred}{\textbf{NO}} \\
Host dedicado $\sim$400 & \textcolor{passgreen}{\textbf{S\'I}} & \textcolor{failred}{\textbf{NO}} (falta escalar) \\
\bottomrule
\end{tabular}
\end{center}
\section{Qu\'e concluir aqu\'i}
\begin{enumerate}
\item Gateway, agent Selenium y LLM \textbf{no} son el cuello para 139/h (por etapa).
\item El pipeline sigue al OCR: $\sim$100--325/h $\Rightarrow$ 139/h \textbf{no garantizado}
(el piso conservador $\sim$100 queda bajo).
\item Host dedicado ($\sim$400/h): \textbf{s\'i} a 139/h; \textbf{no} a 570/h sin m\'as OCR/agents.
\end{enumerate}
@@ -0,0 +1,40 @@
\chapter{Evidencias que alimentan el modelo}
\label{ch:evidencias}
\section{Resumen}
\begin{table}[ht]
\centering
\small
\begin{tabular}{@{}llp{5.4cm}@{}}
\toprule
\textbf{Etapa} & \textbf{Cifra} & \textbf{Origen} \\
\midrule
Gateway & no es cuello (p95 41\,ms) & ST-GW-01 (13-jul), 0\,\% fallos \\
Agent Selenium (1 agent PC) & $\approx$422 fact/h & 633 PDFs / 90\,min \\
LLM (1 worker) & $\approx$506 fact/h & ST-EXT-02 (17-jul): p50 7{,}114\,s \\
OCR count-api & $\sim$100--325 fact/h & ST-COUNT-01 (17-jul): p50 35{,}8\,s @3 \\
Cola ARQ (mock) & $\approx$4\,500 docs/h cola & ST-EXT-01: 30/30 en $\approx$24\,s \\
Pipeline host actual & $\sim$100--325 fact/h & $=\min$ etapas; cuello OCR (no E2E) \\
Pipeline dedicado (est.) & $\sim$400 fact/h & $\min$ con OCR $\sim$400 \\
\bottomrule
\end{tabular}
\caption{Medido vs estimado (campa\~na julio 2026).}
\end{table}
\section{Detalle por prueba}
\begin{description}[style=nextline,leftmargin=0.4cm]
\item[ST-EXT-02 (LLM)]
gpt-5.4-mini; 23/23 ok; p50 7{,}114\,s $\Rightarrow$ 506 fact/h (1\,w); 0$\times$429.
\item[ST-EXT-01 (ARQ mock)]
30/30 drenados en 22{,}5\,s. Valida cola async; \textbf{no} es capacidad LLM real.
\item[ST-COUNT-01 (OCR)]
9/9 ok; p50 35{,}8\,s con 3 concurrentes (vs $\sim$2\,s en serie). Degrada fuerte en shared8g.
\item[Agent Selenium]
422 fact/h con 1 agent PC: supera 139/h; no alcanza 570/h sin m\'as agents.
\item[ST-GW-01 / ST-BOT-01]
Gateway OK. ST-BOT-01 mide el orquestador \texttt{bot-descarga} (heartbeats): host contendido malo (p95 2{,}28\,s); host m\'as libre OK (86\,ms).
\end{description}
\section{A\'un no medido a escala}
ST-COUNT-01 lab, ST-EXT-01 lab (100--500), E2E punta a punta a cientos/miles de PDFs,
ni 100k reales.
@@ -0,0 +1,32 @@
\chapter{Coste LLM (100k que pasan conteo)}
\label{ch:coste}
Hip\'otesis: el filtro de conteo deja pasar \textbf{100\,000} PDFs a extracci\'on
con modelo \textbf{gpt-5.4 mini} a USD\,0{,}0257 por documento.
\begin{table}[ht]
\centering
\begin{tabular}{@{}lr@{}}
\toprule
\textbf{Concepto} & \textbf{Valor} \\
\midrule
Modelo de extracci\'on & gpt-5.4 mini \\
Coste unitario por documento & USD\,0{,}0257 \\
Volumen (pasan filtro de conteo) & 100\,000 \\
\textbf{Coste estimado extracci\'on / mes} & \textbf{USD\,2\,570} \\
\bottomrule
\end{tabular}
\caption{Presupuesto LLM: $100\,000 \times 0{,}0257 = 2\,570$ USD.
No incluye agent Selenium, orquestaci\'on \texttt{bot-descarga}, OCR, infra ni reintentos.}
\label{tab:coste-llm-100k}
\end{table}
\begin{itemize}
\item Si pasan $n < 100\,000$, el coste es lineal: $n \times 0{,}0257$.
\item Ejemplo: 60\,000 v\'alidos $\Rightarrow$ $\approx$ USD\,1\,542.
\item El unitario 0{,}0257 se us\'o como estimaci\'on en ST-EXT-02; contrastar con tokens reales si se instrumenta \texttt{usage}.
\end{itemize}
\callout{\textbf{Coste de referencia}}{%
\textbf{USD\,2\,570}/mes si 100k PDFs llegan a extracci\'on con gpt-5.4 mini.
}
@@ -0,0 +1,91 @@
\chapter{Veredicto}
\label{ch:veredicto}
\section{La respuesta corta}
\begin{center}
\renewcommand{\arraystretch}{1.35}
\begin{tabular}{@{}p{4.2cm}p{2.2cm}p{6.5cm}@{}}
\toprule
\textbf{Escenario} & \textbf{\textquestiondown 100k?} & \textbf{Por qu\'e} \\
\midrule
Host \textbf{actual} (compartido)
& \parcial\ / \fail
& Pipeline $\approx$ OCR $\sim$100--325/h.
Piso $\sim$100 $<$ 139 (no garantizado). \\
Host \textbf{dedicado} + dimensionado, 24$\times$7
& \pass\ \emph{si}
& Estimado $\sim$400/h $>$ 139/h si OCR/agents/LLM OK. \\
Misma infra, jornada 8\,h (570/h)
& \fail
& 400 $<$ 570: falta m\'as OCR, m\'as agents Selenium y cuota LLM. \\
\bottomrule
\end{tabular}
\end{center}
\callout{\large Resumen para decidir}{%
\textbf{Hoy (host compartido): 100k/mes 24$\times$7 no est\'a garantizado}
(pipeline $\approx$ cuello OCR; piso $\sim$100 $<$ 139).\\
\textbf{Con host dedicado + OCR dimensionado: s\'i plausible a 24$\times$7.}\\
\textbf{Jornada 8\,h: no} sin escalar m\'as.\\[0.35em]
\footnotesize El pipeline \textbf{no} se midi\'o E2E a 100k. Ritmo = $\min$ de etapas medidas
(cap.~\ref{ch:modelo}).
}
\begin{figure}[H]
\centering
\begin{tikzpicture}
\begin{axis}[
xbar,
width=0.78\textwidth,
height=4.6cm,
xlabel={Facturas / hora que aguanta el pipeline},
symbolic y coords={dedicado,actual},
ytick=data,
yticklabels={\empty,\empty},
xmin=0, xmax=650,
enlarge y limits=0.45,
bar width=22pt,
bar shift=0pt,
grid=major,
major grid style={ipsgrey!25},
clip=false,
]
\addplot[fill=failred!75, draw=failred] coordinates {(100,actual)};
\addplot[fill=passgreen!75, draw=passgreen] coordinates {(400,dedicado)};
\node[anchor=east, font=\small\bfseries, align=right]
at (axis cs:-5,actual) {Host actual\\(hoy)};
\node[anchor=east, font=\small\bfseries, align=right]
at (axis cs:-5,dedicado) {Host dedicado\\(estimado)};
\node[anchor=west, font=\normalsize\bfseries] at (axis cs:110,actual) {$\sim$100};
\node[anchor=west, font=\normalsize\bfseries] at (axis cs:410,dedicado) {$\sim$400};
\draw[ipsblue, ultra thick, dashed]
(axis cs:139,actual) -- (axis cs:139,dedicado);
\node[font=\scriptsize\bfseries, text=ipsblue, anchor=south, align=center]
at (axis cs:139,dedicado) {meta 139};
\draw[warnorange, ultra thick, dashed]
(axis cs:570,actual) -- (axis cs:570,dedicado);
\node[font=\scriptsize\bfseries, text=warnorange, anchor=south, align=center]
at (axis cs:570,dedicado) {meta 570};
\end{axis}
\end{tikzpicture}
\caption{Barras = capacidad. L\'ineas = metas. Rojo no llega a 139; verde llega a 139 pero no a 570.}
\label{fig:veredicto-brecha}
\end{figure}
\section{Si hoy no est\'a garantizado: qu\'e hace falta}
\begin{enumerate}
\item \textbf{Host dedicado} (sin MLflow/Airflow/n8n); RAM $\geq$16--32\,GiB.
\item \textbf{OCR con margen} hasta sostener $\geq$139 fact/h bajo carga real.
\item \textbf{LLM async} (\texttt{ASYNC\_QUEUE=true}, 1--2 workers; ya $\approx$506/h medidos).
\item \textbf{Agent Selenium:} 1 PC basta para 139/h; $N$ PCs para 570/h.
\item \textbf{Presupuesto LLM} $\approx$ USD\,2\,570/mes si pasan 100k a extracci\'on.
\item \textbf{Validar E2E} de cientos de PDFs punta a punta (a\'un pendiente).
\end{enumerate}
\section{Por meta operativa}
\begin{description}
\item[100k/mes en 24$\times$7 ($\geq$139 fact/h)]
Plausible en host dedicado. En compartido depende de que el OCR no se quede en el piso $\sim$100.
\item[100k/mes en jornada 8\,h ($\geq$570 fact/h)]
No con el estimado dedicado ($\sim$400). Escalar OCR, agents Selenium y LLM, y re-medir.
\end{description}
@@ -0,0 +1,36 @@
\chapter{Requisitos sugeridos y pr\'oximos pasos}
\label{ch:requisitos}
\section{Para lograr 100k/mes (lista simple)}
Si hoy es \fail\ en el host compartido, esto es lo m\'inimo para apuntar a \pass\ en 24$\times$7:
\begin{center}
\renewcommand{\arraystretch}{1.3}
\begin{tabular}{@{}clp{9.2cm}@{}}
\toprule
\textbf{\#} & \textbf{Estado} & \textbf{Qu\'e} \\
\midrule
1 & Obligatorio & Host \textbf{dedicado} IPS (sin MLflow/Airflow/n8n); RAM $\geq$16--32\,GiB \\
2 & Obligatorio & OCR con margen (CPU/RAM o r\'eplicas) hasta $\geq$139 fact/h bajo carga \\
3 & Obligatorio & \texttt{ASYNC\_QUEUE=true} + 1--2 workers LLM \\
4 & Obligatorio & Presupuesto LLM $\approx$ USD\,2\,570/mes (si pasan 100k a extracci\'on) \\
5 & Seg\'un meta & 1 agent Selenium basta para 139/h; $N$ agents PC para 570/h \\
6 & Validaci\'on & Repetir ST-COUNT-01 / ST-EXT-01 lab + E2E de cientos de PDFs \\
\bottomrule
\end{tabular}
\end{center}
\section{Pr\'oximas mediciones}
\begin{enumerate}
\item ST-COUNT-01 perfil lab ($\geq$3\,GiB libres).
\item ST-EXT-01 perfil lab (100--500 docs, 2 workers).
\item E2E controlado (200--500 PDFs punta a punta).
\item Tokens reales del LLM para validar USD\,0{,}0257/doc.
\end{enumerate}
\section{Documentos relacionados}
\begin{itemize}
\item \textbf{Plan de pruebas} --- estrategia e IDs.
\item \textbf{Informe de resultados} --- m\'etricas de la campa\~na.
\item Modelo Markdown: \texttt{docs/plan\_pruebas/scripts/estimate\_capacity.md}.
\end{itemize}