feat: Update testing documentation and introduce comprehensive report for IPSExtractor campaign. Add sections for executive summary, objectives, environment details, and methodology. Enhance README with compilation instructions and data sources for metrics analysis.
This commit is contained in:
+18
-37
@@ -1,45 +1,26 @@
|
||||
# Plan de pruebas IPSExtractor (LaTeX)
|
||||
# Plan / Informe de pruebas IPSExtractor
|
||||
|
||||
Documento profesional de pruebas unitarias, integración, E2E, estrés y resiliencia.
|
||||
## Entregable principal
|
||||
|
||||
## Requisitos
|
||||
**Informe de Pruebas (resultados):** [`InformePruebas_IPSExtractor.pdf`](InformePruebas_IPSExtractor.pdf)
|
||||
Fuentes: [`informe/`](informe/)
|
||||
|
||||
- [TeX Live](https://www.tug.org/texlive/) o MiKTeX
|
||||
- `pdflatex` (incluido en MiKTeX/TeX Live)
|
||||
- Opcional: `latexmk` (requiere Perl en MiKTeX)
|
||||
- Paquetes: `booktabs`, `longtable`, `hyperref`, `fancyhdr`, `geometry`, `babel`, `listings`, `tikz`, `xcolor`, `enumitem`, `siunitx`, `titlesec`
|
||||
Incluye métricas, gráficas, scorecard y análisis de viabilidad de **100.000 facturas/mes**.
|
||||
|
||||
## Compilación
|
||||
## Plan histórico (referencia)
|
||||
|
||||
**Recomendado en Windows (MiKTeX sin Perl):**
|
||||
El documento de plan de pruebas sigue en esta carpeta (`main.tex` → `PlanPruebas_IPSExtractor.pdf`) como referencia de metodología/IDs. El foco de QA es el **informe**.
|
||||
|
||||
## Campañas
|
||||
|
||||
- [`informes/2026-07-13-shared8g.md`](informes/2026-07-13-shared8g.md)
|
||||
- Scripts k6: [`scripts/`](scripts/)
|
||||
|
||||
## Compilar informe
|
||||
|
||||
```powershell
|
||||
cd docs\plan_pruebas
|
||||
pdflatex -interaction=nonstopmode -file-line-error main.tex
|
||||
pdflatex -interaction=nonstopmode -file-line-error main.tex
|
||||
Copy-Item main.pdf PlanPruebas_IPSExtractor.pdf -Force
|
||||
```
|
||||
|
||||
**Con latexmk** (si Perl está instalado):
|
||||
|
||||
```powershell
|
||||
latexmk -pdf main.tex
|
||||
```
|
||||
|
||||
Artefactos: `main.pdf` y `PlanPruebas_IPSExtractor.pdf`.
|
||||
|
||||
## Scripts de estrés (k6)
|
||||
|
||||
Ver [`scripts/README.md`](scripts/README.md). Requiere [k6](https://k6.io/) y el stack levantado en `http://127.0.0.1:8189`.
|
||||
|
||||
## Estructura
|
||||
|
||||
```
|
||||
docs/plan_pruebas/
|
||||
main.tex
|
||||
preamble.tex
|
||||
latexmkrc
|
||||
sections/ # capítulos del plan
|
||||
diagrams/ # TikZ
|
||||
scripts/ # k6 de referencia
|
||||
cd docs\plan_pruebas\informe
|
||||
pdflatex -interaction=nonstopmode main.tex
|
||||
pdflatex -interaction=nonstopmode main.tex
|
||||
Copy-Item main.pdf ..\InformePruebas_IPSExtractor.pdf -Force
|
||||
```
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
# Informe de Pruebas IPSExtractor
|
||||
|
||||
Entregable profesional de **resultados** (métricas, gráficas, capacidad 100k/mes).
|
||||
|
||||
## Compilar
|
||||
|
||||
```powershell
|
||||
cd docs\plan_pruebas\informe
|
||||
pdflatex -interaction=nonstopmode -file-line-error main.tex
|
||||
pdflatex -interaction=nonstopmode -file-line-error main.tex
|
||||
Copy-Item main.pdf ..\InformePruebas_IPSExtractor.pdf -Force
|
||||
```
|
||||
|
||||
Salida: [`../InformePruebas_IPSExtractor.pdf`](../InformePruebas_IPSExtractor.pdf)
|
||||
|
||||
## Datos
|
||||
|
||||
- `data/metricas.csv` — latencias k6
|
||||
- `data/capacidad.csv` — modelo 100k/mes
|
||||
- `data/memoria.csv` — RAM contenedores
|
||||
@@ -0,0 +1,3 @@
|
||||
$pdf_mode = 1;
|
||||
$interaction = 'nonstopmode';
|
||||
$pdflatex = 'pdflatex -interaction=nonstopmode -file-line-error %O %S';
|
||||
@@ -0,0 +1,132 @@
|
||||
\relax
|
||||
\providecommand \babel@aux [2]{\global \let \babel@toc \@gobbletwo }
|
||||
\@nameuse{bbl@beforestart}
|
||||
\catcode `"\active
|
||||
\catcode `<\active
|
||||
\catcode `>\active
|
||||
\@nameuse{es@quoting}
|
||||
\providecommand\hyper@newdestlabel[2]{}
|
||||
\providecommand\HyField@AuxAddToFields[1]{}
|
||||
\providecommand\HyField@AuxAddToCoFields[2]{}
|
||||
\babel@aux{spanish}{}
|
||||
\@writefile{toc}{\contentsline {chapter}{\numberline {1}Resumen ejecutivo}{4}{chapter.1}\protected@file@percent }
|
||||
\@writefile{lof}{\addvspace {10\p@ }}
|
||||
\@writefile{lot}{\addvspace {10\p@ }}
|
||||
\newlabel{ch:resumen}{{1}{4}{Resumen ejecutivo}{chapter.1}{}}
|
||||
\@writefile{toc}{\contentsline {section}{\numberline {1.1}Veredicto global}{4}{section.1.1}\protected@file@percent }
|
||||
\@writefile{toc}{\contentsline {section}{\numberline {1.2}KPIs de la campa\~na}{4}{section.1.2}\protected@file@percent }
|
||||
\@writefile{lot}{\contentsline {table}{\numberline {1.1}{\ignorespaces KPIs ejecutivos de la campa\~na.}}{4}{table.1.1}\protected@file@percent }
|
||||
\@writefile{toc}{\contentsline {section}{\numberline {1.3}Respuesta directa: 100\,000 facturas/mes}{4}{section.1.3}\protected@file@percent }
|
||||
\@writefile{toc}{\contentsline {section}{\numberline {1.4}Riesgo principal}{5}{section.1.4}\protected@file@percent }
|
||||
\@writefile{toc}{\contentsline {chapter}{\numberline {2}Objeto y alcance}{6}{chapter.2}\protected@file@percent }
|
||||
\@writefile{lof}{\addvspace {10\p@ }}
|
||||
\@writefile{lot}{\addvspace {10\p@ }}
|
||||
\newlabel{ch:alcance}{{2}{6}{Objeto y alcance}{chapter.2}{}}
|
||||
\@writefile{toc}{\contentsline {section}{\numberline {2.1}Objeto}{6}{section.2.1}\protected@file@percent }
|
||||
\@writefile{toc}{\contentsline {section}{\numberline {2.2}Alcance incluido}{6}{section.2.2}\protected@file@percent }
|
||||
\@writefile{toc}{\contentsline {section}{\numberline {2.3}Fuera de alcance de esta campa\~na}{6}{section.2.3}\protected@file@percent }
|
||||
\@writefile{toc}{\contentsline {section}{\numberline {2.4}Definici\'on de factura}{6}{section.2.4}\protected@file@percent }
|
||||
\@writefile{toc}{\contentsline {chapter}{\numberline {3}Entorno bajo prueba}{7}{chapter.3}\protected@file@percent }
|
||||
\@writefile{lof}{\addvspace {10\p@ }}
|
||||
\@writefile{lot}{\addvspace {10\p@ }}
|
||||
\newlabel{ch:entorno}{{3}{7}{Entorno bajo prueba}{chapter.3}{}}
|
||||
\@writefile{toc}{\contentsline {section}{\numberline {3.1}Hardware y sistema}{7}{section.3.1}\protected@file@percent }
|
||||
\@writefile{lot}{\contentsline {table}{\numberline {3.1}{\ignorespaces Host de ejecuci\'on.}}{7}{table.3.1}\protected@file@percent }
|
||||
\@writefile{toc}{\contentsline {section}{\numberline {3.2}Baseline de memoria (2026-07-13)}{7}{section.3.2}\protected@file@percent }
|
||||
\@writefile{toc}{\contentsline {section}{\numberline {3.3}Salud del stack}{7}{section.3.3}\protected@file@percent }
|
||||
\@writefile{lof}{\contentsline {figure}{\numberline {3.1}{\ignorespaces Huella de memoria por contenedor (idle). MLflow domina el host.}}{8}{figure.3.1}\protected@file@percent }
|
||||
\newlabel{fig:memoria}{{3.1}{8}{Huella de memoria por contenedor (idle). MLflow domina el host}{figure.3.1}{}}
|
||||
\@writefile{toc}{\contentsline {chapter}{\numberline {4}Metodolog\'ia}{9}{chapter.4}\protected@file@percent }
|
||||
\@writefile{lof}{\addvspace {10\p@ }}
|
||||
\@writefile{lot}{\addvspace {10\p@ }}
|
||||
\newlabel{ch:metodologia}{{4}{9}{Metodolog\'ia}{chapter.4}{}}
|
||||
\@writefile{toc}{\contentsline {section}{\numberline {4.1}Tipos de prueba}{9}{section.4.1}\protected@file@percent }
|
||||
\@writefile{toc}{\contentsline {section}{\numberline {4.2}Herramientas}{9}{section.4.2}\protected@file@percent }
|
||||
\@writefile{toc}{\contentsline {section}{\numberline {4.3}SLOs perfil \texttt {shared8g}}{9}{section.4.3}\protected@file@percent }
|
||||
\@writefile{toc}{\contentsline {section}{\numberline {4.4}Comandos representativos}{9}{section.4.4}\protected@file@percent }
|
||||
\@writefile{toc}{\contentsline {chapter}{\numberline {5}Resultados --- pruebas unitarias}{10}{chapter.5}\protected@file@percent }
|
||||
\@writefile{lof}{\addvspace {10\p@ }}
|
||||
\@writefile{lot}{\addvspace {10\p@ }}
|
||||
\newlabel{ch:unitarias}{{5}{10}{Resultados --- pruebas unitarias}{chapter.5}{}}
|
||||
\@writefile{toc}{\contentsline {section}{\numberline {5.1}Scorecard}{10}{section.5.1}\protected@file@percent }
|
||||
\@writefile{lot}{\contentsline {table}{\numberline {5.1}{\ignorespaces Resultados unitarios. $^{*}$Base 3 PASS confirmados en Ubuntu; ampliaci\'on features/clustering (+8) y Timing sobre 3 PDFs reales requieren \texttt {go test} en host con Tesseract.}}{10}{table.5.1}\protected@file@percent }
|
||||
\@writefile{toc}{\contentsline {section}{\numberline {5.2}Cobertura agent}{10}{section.5.2}\protected@file@percent }
|
||||
\@writefile{toc}{\contentsline {section}{\numberline {5.3}Cobertura API store}{10}{section.5.3}\protected@file@percent }
|
||||
\@writefile{toc}{\contentsline {section}{\numberline {5.4}Cobertura count-api}{10}{section.5.4}\protected@file@percent }
|
||||
\@writefile{lof}{\contentsline {figure}{\numberline {5.1}{\ignorespaces Unitarias ejecutadas vs pendientes.}}{11}{figure.5.1}\protected@file@percent }
|
||||
\newlabel{fig:unitarias}{{5.1}{11}{Unitarias ejecutadas vs pendientes}{figure.5.1}{}}
|
||||
\@writefile{toc}{\contentsline {chapter}{\numberline {6}Resultados --- humo y funcional}{12}{chapter.6}\protected@file@percent }
|
||||
\@writefile{lof}{\addvspace {10\p@ }}
|
||||
\@writefile{lot}{\addvspace {10\p@ }}
|
||||
\newlabel{ch:humo}{{6}{12}{Resultados --- humo y funcional}{chapter.6}{}}
|
||||
\@writefile{toc}{\contentsline {section}{\numberline {6.1}Humo gateway}{12}{section.6.1}\protected@file@percent }
|
||||
\@writefile{lot}{\contentsline {table}{\numberline {6.1}{\ignorespaces Humo HTTP.}}{12}{table.6.1}\protected@file@percent }
|
||||
\@writefile{toc}{\contentsline {section}{\numberline {6.2}Funcional E2E-07 (worker offline $\rightarrow $ 409)}{12}{section.6.2}\protected@file@percent }
|
||||
\@writefile{toc}{\contentsline {section}{\numberline {6.3}Script preparado}{12}{section.6.3}\protected@file@percent }
|
||||
\@writefile{toc}{\contentsline {chapter}{\numberline {7}Resultados --- pruebas de carga}{13}{chapter.7}\protected@file@percent }
|
||||
\@writefile{lof}{\addvspace {10\p@ }}
|
||||
\@writefile{lot}{\addvspace {10\p@ }}
|
||||
\newlabel{ch:carga}{{7}{13}{Resultados --- pruebas de carga}{chapter.7}{}}
|
||||
\@writefile{toc}{\contentsline {section}{\numberline {7.1}Tabla consolidada}{13}{section.7.1}\protected@file@percent }
|
||||
\@writefile{lot}{\contentsline {table}{\numberline {7.1}{\ignorespaces M\'etricas de carga k6 (perfil shared8g).}}{13}{table.7.1}\protected@file@percent }
|
||||
\newlabel{tab:carga}{{7.1}{13}{M\'etricas de carga k6 (perfil shared8g)}{table.7.1}{}}
|
||||
\@writefile{toc}{\contentsline {section}{\numberline {7.2}ST-GW-01}{13}{section.7.2}\protected@file@percent }
|
||||
\@writefile{toc}{\contentsline {section}{\numberline {7.3}ST-BOT-01 --- comparativa}{13}{section.7.3}\protected@file@percent }
|
||||
\@writefile{toc}{\contentsline {section}{\numberline {7.4}Throughput gateway}{13}{section.7.4}\protected@file@percent }
|
||||
\@writefile{lof}{\contentsline {figure}{\numberline {7.1}{\ignorespaces p95 y max por caso. BOT1-max (23\,s) fuera de escala visual relativa --- ver tabla.}}{14}{figure.7.1}\protected@file@percent }
|
||||
\newlabel{fig:latencias}{{7.1}{14}{p95 y max por caso. BOT1-max (23\,s) fuera de escala visual relativa --- ver tabla}{figure.7.1}{}}
|
||||
\@writefile{lof}{\contentsline {figure}{\numberline {7.2}{\ignorespaces Before/after heartbeats (eje limitado; valores exactos en tabla~\ref {tab:carga}).}}{14}{figure.7.2}\protected@file@percent }
|
||||
\newlabel{fig:bot-before-after}{{7.2}{14}{Before/after heartbeats (eje limitado; valores exactos en tabla~\ref {tab:carga})}{figure.7.2}{}}
|
||||
\@writefile{lof}{\contentsline {figure}{\numberline {7.3}{\ignorespaces Throughput observado en health liveness (no equivale a PDFs/h).}}{14}{figure.7.3}\protected@file@percent }
|
||||
\newlabel{fig:throughput}{{7.3}{14}{Throughput observado en health liveness (no equivale a PDFs/h)}{figure.7.3}{}}
|
||||
\@writefile{toc}{\contentsline {chapter}{\numberline {8}Capacidad: 100\,000 facturas al mes}{15}{chapter.8}\protected@file@percent }
|
||||
\@writefile{lof}{\addvspace {10\p@ }}
|
||||
\@writefile{lot}{\addvspace {10\p@ }}
|
||||
\newlabel{ch:capacidad}{{8}{15}{Capacidad: 100\,000 facturas al mes}{chapter.8}{}}
|
||||
\@writefile{toc}{\contentsline {section}{\numberline {8.1}Traducci\'on del objetivo}{15}{section.8.1}\protected@file@percent }
|
||||
\@writefile{lot}{\contentsline {table}{\numberline {8.1}{\ignorespaces Throughput m\'inimo para cumplir 100k/mes.}}{15}{table.8.1}\protected@file@percent }
|
||||
\@writefile{toc}{\contentsline {section}{\numberline {8.2}Modelo por etapa}{15}{section.8.2}\protected@file@percent }
|
||||
\@writefile{lot}{\contentsline {table}{\numberline {8.2}{\ignorespaces Facturas/hora estimadas. $^{*}$Sujeto a cuota LLM. $^{\dagger }$Con $N$ agents en paralelo.}}{15}{table.8.2}\protected@file@percent }
|
||||
\newlabel{tab:capacidad}{{8.2}{15}{Facturas/hora estimadas. $^{*}$Sujeto a cuota LLM. $^{\dagger }$Con $N$ agents en paralelo}{table.8.2}{}}
|
||||
\@writefile{toc}{\contentsline {section}{\numberline {8.3}Interpretaci\'on}{15}{section.8.3}\protected@file@percent }
|
||||
\@writefile{lof}{\contentsline {figure}{\numberline {8.1}{\ignorespaces Brecha de capacidad. Gateway real $\gg $1000/h; se trunca para legibilidad.}}{16}{figure.8.1}\protected@file@percent }
|
||||
\newlabel{fig:capacidad}{{8.1}{16}{Brecha de capacidad. Gateway real $\gg $1000/h; se trunca para legibilidad}{figure.8.1}{}}
|
||||
\@writefile{toc}{\contentsline {section}{\numberline {8.4}Veredicto}{16}{section.8.4}\protected@file@percent }
|
||||
\@writefile{toc}{\contentsline {section}{\numberline {8.5}Requisitos m\'inimos sugeridos para 100k/mes}{16}{section.8.5}\protected@file@percent }
|
||||
\@writefile{toc}{\contentsline {chapter}{\numberline {9}An\'alisis y hallazgos}{17}{chapter.9}\protected@file@percent }
|
||||
\@writefile{lof}{\addvspace {10\p@ }}
|
||||
\@writefile{lot}{\addvspace {10\p@ }}
|
||||
\newlabel{ch:analisis}{{9}{17}{An\'alisis y hallazgos}{chapter.9}{}}
|
||||
\@writefile{toc}{\contentsline {section}{\numberline {9.1}Hallazgos}{17}{section.9.1}\protected@file@percent }
|
||||
\@writefile{toc}{\contentsline {section}{\numberline {9.2}Scorecard global}{17}{section.9.2}\protected@file@percent }
|
||||
\@writefile{lof}{\contentsline {figure}{\numberline {9.1}{\ignorespaces Distribuci\'on de resultados por tipo de prueba.}}{17}{figure.9.1}\protected@file@percent }
|
||||
\newlabel{fig:scorecard}{{9.1}{17}{Distribuci\'on de resultados por tipo de prueba}{figure.9.1}{}}
|
||||
\@writefile{toc}{\contentsline {section}{\numberline {9.3}Limitaciones}{17}{section.9.3}\protected@file@percent }
|
||||
\@writefile{toc}{\contentsline {chapter}{\numberline {10}Conclusiones y recomendaciones}{18}{chapter.10}\protected@file@percent }
|
||||
\@writefile{lof}{\addvspace {10\p@ }}
|
||||
\@writefile{lot}{\addvspace {10\p@ }}
|
||||
\newlabel{ch:conclusiones}{{10}{18}{Conclusiones y recomendaciones}{chapter.10}{}}
|
||||
\@writefile{toc}{\contentsline {section}{\numberline {10.1}Conclusiones}{18}{section.10.1}\protected@file@percent }
|
||||
\@writefile{toc}{\contentsline {section}{\numberline {10.2}Recomendaciones}{18}{section.10.2}\protected@file@percent }
|
||||
\@writefile{toc}{\contentsline {section}{\numberline {10.3}Pr\'oxima campa\~na sugerida}{18}{section.10.3}\protected@file@percent }
|
||||
\@writefile{toc}{\contentsline {chapter}{\numberline {11}Anexos}{19}{chapter.11}\protected@file@percent }
|
||||
\@writefile{lof}{\addvspace {10\p@ }}
|
||||
\@writefile{lot}{\addvspace {10\p@ }}
|
||||
\newlabel{ch:anexos}{{11}{19}{Anexos}{chapter.11}{}}
|
||||
\@writefile{toc}{\contentsline {section}{\numberline {11.1}Datos fuente}{19}{section.11.1}\protected@file@percent }
|
||||
\@writefile{toc}{\contentsline {section}{\numberline {11.2}Extracto ST-GW-01}{19}{section.11.2}\protected@file@percent }
|
||||
\@writefile{toc}{\contentsline {section}{\numberline {11.3}Extracto ST-BOT-01 \#2}{19}{section.11.3}\protected@file@percent }
|
||||
\@writefile{toc}{\contentsline {section}{\numberline {11.4}Unitarias}{19}{section.11.4}\protected@file@percent }
|
||||
\@writefile{toc}{\contentsline {section}{\numberline {11.5}Scripts}{19}{section.11.5}\protected@file@percent }
|
||||
\@writefile{toc}{\contentsline {section}{\numberline {11.6}Reproducibilidad}{20}{section.11.6}\protected@file@percent }
|
||||
\@writefile{toc}{\contentsline {chapter}{\numberline {12}Resultados --- latencia count-api (PDFs reales)}{21}{chapter.12}\protected@file@percent }
|
||||
\@writefile{lof}{\addvspace {10\p@ }}
|
||||
\@writefile{lot}{\addvspace {10\p@ }}
|
||||
\newlabel{ch:count-latency}{{12}{21}{Resultados --- latencia count-api (PDFs reales)}{chapter.12}{}}
|
||||
\@writefile{toc}{\contentsline {section}{\numberline {12.1}Fixtures}{21}{section.12.1}\protected@file@percent }
|
||||
\@writefile{toc}{\contentsline {section}{\numberline {12.2}Unitarias pipeline ampliadas}{21}{section.12.2}\protected@file@percent }
|
||||
\@writefile{toc}{\contentsline {section}{\numberline {12.3}Timings por etapa}{21}{section.12.3}\protected@file@percent }
|
||||
\@writefile{lol}{\contentsline {lstlisting}{\numberline {12.1}Medici\'on en Ubuntu}{21}{lstlisting.12.1}\protected@file@percent }
|
||||
\@writefile{lot}{\contentsline {table}{\numberline {12.1}{\ignorespaces Latencia por etapa (ms). Completar tras \texttt {-run Timing} en el host con Tesseract.}}{22}{table.12.1}\protected@file@percent }
|
||||
\newlabel{tab:count-timing}{{12.1}{22}{Latencia por etapa (ms). Completar tras \texttt {-run Timing} en el host con Tesseract}{table.12.1}{}}
|
||||
\gdef \@abspage@last{23}
|
||||
@@ -0,0 +1,58 @@
|
||||
\BOOKMARK [0][-]{chapter.1}{\376\377\000R\000e\000s\000u\000m\000e\000n\000\040\000e\000j\000e\000c\000u\000t\000i\000v\000o}{}% 1
|
||||
\BOOKMARK [1][-]{section.1.1}{\376\377\000V\000e\000r\000e\000d\000i\000c\000t\000o\000\040\000g\000l\000o\000b\000a\000l}{chapter.1}% 2
|
||||
\BOOKMARK [1][-]{section.1.2}{\376\377\000K\000P\000I\000s\000\040\000d\000e\000\040\000l\000a\000\040\000c\000a\000m\000p\000a\000\361\000a}{chapter.1}% 3
|
||||
\BOOKMARK [1][-]{section.1.3}{\376\377\000R\000e\000s\000p\000u\000e\000s\000t\000a\000\040\000d\000i\000r\000e\000c\000t\000a\000:\000\040\0001\0000\0000\0000\0000\0000\000\040\000f\000a\000c\000t\000u\000r\000a\000s\000/\000m\000e\000s}{chapter.1}% 4
|
||||
\BOOKMARK [1][-]{section.1.4}{\376\377\000R\000i\000e\000s\000g\000o\000\040\000p\000r\000i\000n\000c\000i\000p\000a\000l}{chapter.1}% 5
|
||||
\BOOKMARK [0][-]{chapter.2}{\376\377\000O\000b\000j\000e\000t\000o\000\040\000y\000\040\000a\000l\000c\000a\000n\000c\000e}{}% 6
|
||||
\BOOKMARK [1][-]{section.2.1}{\376\377\000O\000b\000j\000e\000t\000o}{chapter.2}% 7
|
||||
\BOOKMARK [1][-]{section.2.2}{\376\377\000A\000l\000c\000a\000n\000c\000e\000\040\000i\000n\000c\000l\000u\000i\000d\000o}{chapter.2}% 8
|
||||
\BOOKMARK [1][-]{section.2.3}{\376\377\000F\000u\000e\000r\000a\000\040\000d\000e\000\040\000a\000l\000c\000a\000n\000c\000e\000\040\000d\000e\000\040\000e\000s\000t\000a\000\040\000c\000a\000m\000p\000a\000\361\000a}{chapter.2}% 9
|
||||
\BOOKMARK [1][-]{section.2.4}{\376\377\000D\000e\000f\000i\000n\000i\000c\000i\000\363\000n\000\040\000d\000e\000\040\000f\000a\000c\000t\000u\000r\000a}{chapter.2}% 10
|
||||
\BOOKMARK [0][-]{chapter.3}{\376\377\000E\000n\000t\000o\000r\000n\000o\000\040\000b\000a\000j\000o\000\040\000p\000r\000u\000e\000b\000a}{}% 11
|
||||
\BOOKMARK [1][-]{section.3.1}{\376\377\000H\000a\000r\000d\000w\000a\000r\000e\000\040\000y\000\040\000s\000i\000s\000t\000e\000m\000a}{chapter.3}% 12
|
||||
\BOOKMARK [1][-]{section.3.2}{\376\377\000B\000a\000s\000e\000l\000i\000n\000e\000\040\000d\000e\000\040\000m\000e\000m\000o\000r\000i\000a\000\040\000\050\0002\0000\0002\0006\000-\0000\0007\000-\0001\0003\000\051}{chapter.3}% 13
|
||||
\BOOKMARK [1][-]{section.3.3}{\376\377\000S\000a\000l\000u\000d\000\040\000d\000e\000l\000\040\000s\000t\000a\000c\000k}{chapter.3}% 14
|
||||
\BOOKMARK [0][-]{chapter.4}{\376\377\000M\000e\000t\000o\000d\000o\000l\000o\000g\000\355\000a}{}% 15
|
||||
\BOOKMARK [1][-]{section.4.1}{\376\377\000T\000i\000p\000o\000s\000\040\000d\000e\000\040\000p\000r\000u\000e\000b\000a}{chapter.4}% 16
|
||||
\BOOKMARK [1][-]{section.4.2}{\376\377\000H\000e\000r\000r\000a\000m\000i\000e\000n\000t\000a\000s}{chapter.4}% 17
|
||||
\BOOKMARK [1][-]{section.4.3}{\376\377\000S\000L\000O\000s\000\040\000p\000e\000r\000f\000i\000l\000\040\000s\000h\000a\000r\000e\000d\0008\000g}{chapter.4}% 18
|
||||
\BOOKMARK [1][-]{section.4.4}{\376\377\000C\000o\000m\000a\000n\000d\000o\000s\000\040\000r\000e\000p\000r\000e\000s\000e\000n\000t\000a\000t\000i\000v\000o\000s}{chapter.4}% 19
|
||||
\BOOKMARK [0][-]{chapter.5}{\376\377\000R\000e\000s\000u\000l\000t\000a\000d\000o\000s\000\040\040\024\000\040\000p\000r\000u\000e\000b\000a\000s\000\040\000u\000n\000i\000t\000a\000r\000i\000a\000s}{}% 20
|
||||
\BOOKMARK [1][-]{section.5.1}{\376\377\000S\000c\000o\000r\000e\000c\000a\000r\000d}{chapter.5}% 21
|
||||
\BOOKMARK [1][-]{section.5.2}{\376\377\000C\000o\000b\000e\000r\000t\000u\000r\000a\000\040\000a\000g\000e\000n\000t}{chapter.5}% 22
|
||||
\BOOKMARK [1][-]{section.5.3}{\376\377\000C\000o\000b\000e\000r\000t\000u\000r\000a\000\040\000A\000P\000I\000\040\000s\000t\000o\000r\000e}{chapter.5}% 23
|
||||
\BOOKMARK [1][-]{section.5.4}{\376\377\000C\000o\000b\000e\000r\000t\000u\000r\000a\000\040\000c\000o\000u\000n\000t\000-\000a\000p\000i}{chapter.5}% 24
|
||||
\BOOKMARK [0][-]{chapter.6}{\376\377\000R\000e\000s\000u\000l\000t\000a\000d\000o\000s\000\040\040\024\000\040\000h\000u\000m\000o\000\040\000y\000\040\000f\000u\000n\000c\000i\000o\000n\000a\000l}{}% 25
|
||||
\BOOKMARK [1][-]{section.6.1}{\376\377\000H\000u\000m\000o\000\040\000g\000a\000t\000e\000w\000a\000y}{chapter.6}% 26
|
||||
\BOOKMARK [1][-]{section.6.2}{\376\377\000F\000u\000n\000c\000i\000o\000n\000a\000l\000\040\000E\0002\000E\000-\0000\0007\000\040\000\050\000w\000o\000r\000k\000e\000r\000\040\000o\000f\000f\000l\000i\000n\000e\000\040\000\040\0004\0000\0009\000\051}{chapter.6}% 27
|
||||
\BOOKMARK [1][-]{section.6.3}{\376\377\000S\000c\000r\000i\000p\000t\000\040\000p\000r\000e\000p\000a\000r\000a\000d\000o}{chapter.6}% 28
|
||||
\BOOKMARK [0][-]{chapter.7}{\376\377\000R\000e\000s\000u\000l\000t\000a\000d\000o\000s\000\040\040\024\000\040\000p\000r\000u\000e\000b\000a\000s\000\040\000d\000e\000\040\000c\000a\000r\000g\000a}{}% 29
|
||||
\BOOKMARK [1][-]{section.7.1}{\376\377\000T\000a\000b\000l\000a\000\040\000c\000o\000n\000s\000o\000l\000i\000d\000a\000d\000a}{chapter.7}% 30
|
||||
\BOOKMARK [1][-]{section.7.2}{\376\377\000S\000T\000-\000G\000W\000-\0000\0001}{chapter.7}% 31
|
||||
\BOOKMARK [1][-]{section.7.3}{\376\377\000S\000T\000-\000B\000O\000T\000-\0000\0001\000\040\040\024\000\040\000c\000o\000m\000p\000a\000r\000a\000t\000i\000v\000a}{chapter.7}% 32
|
||||
\BOOKMARK [1][-]{section.7.4}{\376\377\000T\000h\000r\000o\000u\000g\000h\000p\000u\000t\000\040\000g\000a\000t\000e\000w\000a\000y}{chapter.7}% 33
|
||||
\BOOKMARK [0][-]{chapter.8}{\376\377\000C\000a\000p\000a\000c\000i\000d\000a\000d\000:\000\040\0001\0000\0000\0000\0000\0000\000\040\000f\000a\000c\000t\000u\000r\000a\000s\000\040\000a\000l\000\040\000m\000e\000s}{}% 34
|
||||
\BOOKMARK [1][-]{section.8.1}{\376\377\000T\000r\000a\000d\000u\000c\000c\000i\000\363\000n\000\040\000d\000e\000l\000\040\000o\000b\000j\000e\000t\000i\000v\000o}{chapter.8}% 35
|
||||
\BOOKMARK [1][-]{section.8.2}{\376\377\000M\000o\000d\000e\000l\000o\000\040\000p\000o\000r\000\040\000e\000t\000a\000p\000a}{chapter.8}% 36
|
||||
\BOOKMARK [1][-]{section.8.3}{\376\377\000I\000n\000t\000e\000r\000p\000r\000e\000t\000a\000c\000i\000\363\000n}{chapter.8}% 37
|
||||
\BOOKMARK [1][-]{section.8.4}{\376\377\000V\000e\000r\000e\000d\000i\000c\000t\000o}{chapter.8}% 38
|
||||
\BOOKMARK [1][-]{section.8.5}{\376\377\000R\000e\000q\000u\000i\000s\000i\000t\000o\000s\000\040\000m\000\355\000n\000i\000m\000o\000s\000\040\000s\000u\000g\000e\000r\000i\000d\000o\000s\000\040\000p\000a\000r\000a\000\040\0001\0000\0000\000k\000/\000m\000e\000s}{chapter.8}% 39
|
||||
\BOOKMARK [0][-]{chapter.9}{\376\377\000A\000n\000\341\000l\000i\000s\000i\000s\000\040\000y\000\040\000h\000a\000l\000l\000a\000z\000g\000o\000s}{}% 40
|
||||
\BOOKMARK [1][-]{section.9.1}{\376\377\000H\000a\000l\000l\000a\000z\000g\000o\000s}{chapter.9}% 41
|
||||
\BOOKMARK [1][-]{section.9.2}{\376\377\000S\000c\000o\000r\000e\000c\000a\000r\000d\000\040\000g\000l\000o\000b\000a\000l}{chapter.9}% 42
|
||||
\BOOKMARK [1][-]{section.9.3}{\376\377\000L\000i\000m\000i\000t\000a\000c\000i\000o\000n\000e\000s}{chapter.9}% 43
|
||||
\BOOKMARK [0][-]{chapter.10}{\376\377\000C\000o\000n\000c\000l\000u\000s\000i\000o\000n\000e\000s\000\040\000y\000\040\000r\000e\000c\000o\000m\000e\000n\000d\000a\000c\000i\000o\000n\000e\000s}{}% 44
|
||||
\BOOKMARK [1][-]{section.10.1}{\376\377\000C\000o\000n\000c\000l\000u\000s\000i\000o\000n\000e\000s}{chapter.10}% 45
|
||||
\BOOKMARK [1][-]{section.10.2}{\376\377\000R\000e\000c\000o\000m\000e\000n\000d\000a\000c\000i\000o\000n\000e\000s}{chapter.10}% 46
|
||||
\BOOKMARK [1][-]{section.10.3}{\376\377\000P\000r\000\363\000x\000i\000m\000a\000\040\000c\000a\000m\000p\000a\000\361\000a\000\040\000s\000u\000g\000e\000r\000i\000d\000a}{chapter.10}% 47
|
||||
\BOOKMARK [0][-]{chapter.11}{\376\377\000A\000n\000e\000x\000o\000s}{}% 48
|
||||
\BOOKMARK [1][-]{section.11.1}{\376\377\000D\000a\000t\000o\000s\000\040\000f\000u\000e\000n\000t\000e}{chapter.11}% 49
|
||||
\BOOKMARK [1][-]{section.11.2}{\376\377\000E\000x\000t\000r\000a\000c\000t\000o\000\040\000S\000T\000-\000G\000W\000-\0000\0001}{chapter.11}% 50
|
||||
\BOOKMARK [1][-]{section.11.3}{\376\377\000E\000x\000t\000r\000a\000c\000t\000o\000\040\000S\000T\000-\000B\000O\000T\000-\0000\0001\000\040\000\043\0002}{chapter.11}% 51
|
||||
\BOOKMARK [1][-]{section.11.4}{\376\377\000U\000n\000i\000t\000a\000r\000i\000a\000s}{chapter.11}% 52
|
||||
\BOOKMARK [1][-]{section.11.5}{\376\377\000S\000c\000r\000i\000p\000t\000s}{chapter.11}% 53
|
||||
\BOOKMARK [1][-]{section.11.6}{\376\377\000R\000e\000p\000r\000o\000d\000u\000c\000i\000b\000i\000l\000i\000d\000a\000d}{chapter.11}% 54
|
||||
\BOOKMARK [0][-]{chapter.12}{\376\377\000R\000e\000s\000u\000l\000t\000a\000d\000o\000s\000\040\040\024\000\040\000l\000a\000t\000e\000n\000c\000i\000a\000\040\000c\000o\000u\000n\000t\000-\000a\000p\000i\000\040\000\050\000P\000D\000F\000s\000\040\000r\000e\000a\000l\000e\000s\000\051}{}% 55
|
||||
\BOOKMARK [1][-]{section.12.1}{\376\377\000F\000i\000x\000t\000u\000r\000e\000s}{chapter.12}% 56
|
||||
\BOOKMARK [1][-]{section.12.2}{\376\377\000U\000n\000i\000t\000a\000r\000i\000a\000s\000\040\000p\000i\000p\000e\000l\000i\000n\000e\000\040\000a\000m\000p\000l\000i\000a\000d\000a\000s}{chapter.12}% 57
|
||||
\BOOKMARK [1][-]{section.12.3}{\376\377\000T\000i\000m\000i\000n\000g\000s\000\040\000p\000o\000r\000\040\000e\000t\000a\000p\000a}{chapter.12}% 58
|
||||
@@ -0,0 +1,36 @@
|
||||
\documentclass[11pt,a4paper,oneside]{report}
|
||||
|
||||
\input{preamble}
|
||||
|
||||
\begin{document}
|
||||
|
||||
\makecover
|
||||
|
||||
\chapter*{Control de cambios}
|
||||
\begin{tabularx}{\textwidth}{@{}l l X@{}}
|
||||
\toprule
|
||||
\textbf{Versi\'on} & \textbf{Fecha} & \textbf{Descripci\'on} \\
|
||||
\midrule
|
||||
1.0.0 & 13 jul 2026 & Informe profesional: campa\~na shared8g, m\'etricas, capacidad 100k/mes. \\
|
||||
1.1.0 & 13 jul 2026 & Unitarias count-api pipeline 3/3 PASS (Ubuntu). \\
|
||||
1.2.0 & 13 jul 2026 & Unitarias features/clustering ampliadas; timings PDF + scripts latencia. \\
|
||||
\bottomrule
|
||||
\end{tabularx}
|
||||
|
||||
\tableofcontents
|
||||
\clearpage
|
||||
|
||||
\input{sections/01_resumen_ejecutivo}
|
||||
\input{sections/02_objeto_alcance}
|
||||
\input{sections/03_entorno}
|
||||
\input{sections/04_metodologia}
|
||||
\input{sections/05_resultados_unitarias}
|
||||
\input{sections/06_resultados_humo_funcional}
|
||||
\input{sections/07_resultados_carga}
|
||||
\input{sections/08_capacidad_100k}
|
||||
\input{sections/09_analisis_hallazgos}
|
||||
\input{sections/10_conclusiones}
|
||||
\input{sections/11_anexos}
|
||||
\input{sections/12_count_latency}
|
||||
|
||||
\end{document}
|
||||
@@ -0,0 +1,59 @@
|
||||
\babel@toc {spanish}{}\relax
|
||||
\contentsline {chapter}{\numberline {1}Resumen ejecutivo}{4}{chapter.1}%
|
||||
\contentsline {section}{\numberline {1.1}Veredicto global}{4}{section.1.1}%
|
||||
\contentsline {section}{\numberline {1.2}KPIs de la campa\~na}{4}{section.1.2}%
|
||||
\contentsline {section}{\numberline {1.3}Respuesta directa: 100\,000 facturas/mes}{4}{section.1.3}%
|
||||
\contentsline {section}{\numberline {1.4}Riesgo principal}{5}{section.1.4}%
|
||||
\contentsline {chapter}{\numberline {2}Objeto y alcance}{6}{chapter.2}%
|
||||
\contentsline {section}{\numberline {2.1}Objeto}{6}{section.2.1}%
|
||||
\contentsline {section}{\numberline {2.2}Alcance incluido}{6}{section.2.2}%
|
||||
\contentsline {section}{\numberline {2.3}Fuera de alcance de esta campa\~na}{6}{section.2.3}%
|
||||
\contentsline {section}{\numberline {2.4}Definici\'on de factura}{6}{section.2.4}%
|
||||
\contentsline {chapter}{\numberline {3}Entorno bajo prueba}{7}{chapter.3}%
|
||||
\contentsline {section}{\numberline {3.1}Hardware y sistema}{7}{section.3.1}%
|
||||
\contentsline {section}{\numberline {3.2}Baseline de memoria (2026-07-13)}{7}{section.3.2}%
|
||||
\contentsline {section}{\numberline {3.3}Salud del stack}{7}{section.3.3}%
|
||||
\contentsline {chapter}{\numberline {4}Metodolog\'ia}{9}{chapter.4}%
|
||||
\contentsline {section}{\numberline {4.1}Tipos de prueba}{9}{section.4.1}%
|
||||
\contentsline {section}{\numberline {4.2}Herramientas}{9}{section.4.2}%
|
||||
\contentsline {section}{\numberline {4.3}SLOs perfil \texttt {shared8g}}{9}{section.4.3}%
|
||||
\contentsline {section}{\numberline {4.4}Comandos representativos}{9}{section.4.4}%
|
||||
\contentsline {chapter}{\numberline {5}Resultados --- pruebas unitarias}{10}{chapter.5}%
|
||||
\contentsline {section}{\numberline {5.1}Scorecard}{10}{section.5.1}%
|
||||
\contentsline {section}{\numberline {5.2}Cobertura agent}{10}{section.5.2}%
|
||||
\contentsline {section}{\numberline {5.3}Cobertura API store}{10}{section.5.3}%
|
||||
\contentsline {section}{\numberline {5.4}Cobertura count-api}{10}{section.5.4}%
|
||||
\contentsline {chapter}{\numberline {6}Resultados --- humo y funcional}{12}{chapter.6}%
|
||||
\contentsline {section}{\numberline {6.1}Humo gateway}{12}{section.6.1}%
|
||||
\contentsline {section}{\numberline {6.2}Funcional E2E-07 (worker offline $\rightarrow $ 409)}{12}{section.6.2}%
|
||||
\contentsline {section}{\numberline {6.3}Script preparado}{12}{section.6.3}%
|
||||
\contentsline {chapter}{\numberline {7}Resultados --- pruebas de carga}{13}{chapter.7}%
|
||||
\contentsline {section}{\numberline {7.1}Tabla consolidada}{13}{section.7.1}%
|
||||
\contentsline {section}{\numberline {7.2}ST-GW-01}{13}{section.7.2}%
|
||||
\contentsline {section}{\numberline {7.3}ST-BOT-01 --- comparativa}{13}{section.7.3}%
|
||||
\contentsline {section}{\numberline {7.4}Throughput gateway}{13}{section.7.4}%
|
||||
\contentsline {chapter}{\numberline {8}Capacidad: 100\,000 facturas al mes}{15}{chapter.8}%
|
||||
\contentsline {section}{\numberline {8.1}Traducci\'on del objetivo}{15}{section.8.1}%
|
||||
\contentsline {section}{\numberline {8.2}Modelo por etapa}{15}{section.8.2}%
|
||||
\contentsline {section}{\numberline {8.3}Interpretaci\'on}{15}{section.8.3}%
|
||||
\contentsline {section}{\numberline {8.4}Veredicto}{16}{section.8.4}%
|
||||
\contentsline {section}{\numberline {8.5}Requisitos m\'inimos sugeridos para 100k/mes}{16}{section.8.5}%
|
||||
\contentsline {chapter}{\numberline {9}An\'alisis y hallazgos}{17}{chapter.9}%
|
||||
\contentsline {section}{\numberline {9.1}Hallazgos}{17}{section.9.1}%
|
||||
\contentsline {section}{\numberline {9.2}Scorecard global}{17}{section.9.2}%
|
||||
\contentsline {section}{\numberline {9.3}Limitaciones}{17}{section.9.3}%
|
||||
\contentsline {chapter}{\numberline {10}Conclusiones y recomendaciones}{18}{chapter.10}%
|
||||
\contentsline {section}{\numberline {10.1}Conclusiones}{18}{section.10.1}%
|
||||
\contentsline {section}{\numberline {10.2}Recomendaciones}{18}{section.10.2}%
|
||||
\contentsline {section}{\numberline {10.3}Pr\'oxima campa\~na sugerida}{18}{section.10.3}%
|
||||
\contentsline {chapter}{\numberline {11}Anexos}{19}{chapter.11}%
|
||||
\contentsline {section}{\numberline {11.1}Datos fuente}{19}{section.11.1}%
|
||||
\contentsline {section}{\numberline {11.2}Extracto ST-GW-01}{19}{section.11.2}%
|
||||
\contentsline {section}{\numberline {11.3}Extracto ST-BOT-01 \#2}{19}{section.11.3}%
|
||||
\contentsline {section}{\numberline {11.4}Unitarias}{19}{section.11.4}%
|
||||
\contentsline {section}{\numberline {11.5}Scripts}{19}{section.11.5}%
|
||||
\contentsline {section}{\numberline {11.6}Reproducibilidad}{20}{section.11.6}%
|
||||
\contentsline {chapter}{\numberline {12}Resultados --- latencia count-api (PDFs reales)}{21}{chapter.12}%
|
||||
\contentsline {section}{\numberline {12.1}Fixtures}{21}{section.12.1}%
|
||||
\contentsline {section}{\numberline {12.2}Unitarias pipeline ampliadas}{21}{section.12.2}%
|
||||
\contentsline {section}{\numberline {12.3}Timings por etapa}{21}{section.12.3}%
|
||||
@@ -0,0 +1,102 @@
|
||||
\usepackage[T1]{fontenc}
|
||||
\usepackage[utf8]{inputenc}
|
||||
\usepackage[spanish,es-tabla]{babel}
|
||||
\usepackage{lmodern}
|
||||
\usepackage{microtype}
|
||||
\usepackage[
|
||||
a4paper,
|
||||
margin=2.4cm,
|
||||
headheight=14pt
|
||||
]{geometry}
|
||||
\usepackage{setspace}
|
||||
\onehalfspacing
|
||||
|
||||
\usepackage{fancyhdr}
|
||||
\pagestyle{fancy}
|
||||
\fancyhf{}
|
||||
\fancyhead[L]{\small IPSExtractor --- Informe de Pruebas}
|
||||
\fancyhead[R]{\small\thepage}
|
||||
\fancyfoot[C]{\footnotesize Confidencial --- Uso interno}
|
||||
\renewcommand{\headrulewidth}{0.4pt}
|
||||
|
||||
\usepackage{titlesec}
|
||||
\titleformat{\chapter}[display]
|
||||
{\normalfont\huge\bfseries}{\chaptertitlename\ \thechapter}{14pt}{\Huge}
|
||||
\titleformat{\section}{\normalfont\Large\bfseries}{\thesection}{1em}{}
|
||||
|
||||
\usepackage{booktabs}
|
||||
\usepackage{longtable}
|
||||
\usepackage{tabularx}
|
||||
\usepackage{multirow}
|
||||
\usepackage{array}
|
||||
\usepackage{enumitem}
|
||||
\setlist[itemize]{nosep,leftmargin=1.4em}
|
||||
\setlist[enumerate]{nosep,leftmargin=1.4em}
|
||||
|
||||
\usepackage{siunitx}
|
||||
\usepackage{xcolor}
|
||||
\definecolor{ipsblue}{HTML}{1B4F72}
|
||||
\definecolor{ipsgrey}{HTML}{566573}
|
||||
\definecolor{codebg}{HTML}{F4F6F7}
|
||||
\definecolor{passgreen}{HTML}{1E8449}
|
||||
\definecolor{failred}{HTML}{C0392B}
|
||||
\definecolor{warnorange}{HTML}{B9770E}
|
||||
|
||||
\usepackage{listings}
|
||||
\lstset{
|
||||
basicstyle=\ttfamily\footnotesize,
|
||||
backgroundcolor=\color{codebg},
|
||||
frame=single,
|
||||
framerule=0.3pt,
|
||||
breaklines=true,
|
||||
columns=fullflexible,
|
||||
showstringspaces=false
|
||||
}
|
||||
|
||||
\usepackage{pgfplots}
|
||||
\pgfplotsset{compat=1.18}
|
||||
\usepgfplotslibrary{statistics}
|
||||
|
||||
\usepackage{tikz}
|
||||
\usetikzlibrary{arrows.meta,positioning}
|
||||
|
||||
\usepackage{graphicx}
|
||||
\usepackage{hyperref}
|
||||
\hypersetup{
|
||||
colorlinks=true,
|
||||
linkcolor=ipsblue,
|
||||
urlcolor=ipsblue,
|
||||
pdftitle={Informe de Pruebas IPSExtractor},
|
||||
pdfauthor={IPSExtractor QA}
|
||||
}
|
||||
|
||||
\newcommand{\docversion}{1.2.0}
|
||||
\newcommand{\docdate}{13 de julio de 2026}
|
||||
|
||||
\newcommand{\pass}{\textcolor{passgreen}{\textbf{PASS}}}
|
||||
\newcommand{\fail}{\textcolor{failred}{\textbf{FAIL}}}
|
||||
\newcommand{\parcial}{\textcolor{warnorange}{\textbf{PARCIAL}}}
|
||||
\newcommand{\neje}{\textcolor{ipsgrey}{\textbf{N/E}}}
|
||||
|
||||
\newcommand{\makecover}{%
|
||||
\begin{titlepage}
|
||||
\centering
|
||||
\vspace*{1.2cm}
|
||||
{\color{ipsblue}\rule{\textwidth}{2.5pt}}\\[0.35cm]
|
||||
{\Huge\bfseries IPSExtractor}\\[0.25cm]
|
||||
{\Large\color{ipsgrey} Extracci\'on y an\'alisis de facturas m\'edicas}\\[0.35cm]
|
||||
{\color{ipsblue}\rule{\textwidth}{2.5pt}}\\[1.5cm]
|
||||
{\LARGE\bfseries Informe de Pruebas}\\[0.25cm]
|
||||
{\large Resultados $\cdot$ M\'etricas $\cdot$ Capacidad 100\,000/mes}\\[1.8cm]
|
||||
\begin{tabular}{@{}ll@{}}
|
||||
\textbf{Versi\'on} & \docversion \\
|
||||
\textbf{Fecha} & \docdate \\
|
||||
\textbf{Campa\~na} & 2026-07-13 (perfil \texttt{shared8g}) \\
|
||||
\textbf{Host} & ubuntu --- ASUS X441UAK, 8\,GiB \\
|
||||
\textbf{Clasificaci\'on} & Uso interno \\
|
||||
\end{tabular}
|
||||
\vfill
|
||||
{\small Documento de \emph{resultados} (no plan). Incluye an\'alisis de viabilidad de 100\,000 facturas/mes.}\\[0.25cm]
|
||||
{\color{ipsblue}\rule{0.45\textwidth}{0.6pt}}
|
||||
\end{titlepage}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
\chapter{Resumen ejecutivo}
|
||||
\label{ch:resumen}
|
||||
|
||||
\section{Veredicto global}
|
||||
La campa\~na de pruebas del \textbf{13 de julio de 2026} sobre el stack IPSExtractor desplegado en el host \texttt{ubuntu} (perfil \texttt{shared8g}) concluye:
|
||||
|
||||
\begin{center}
|
||||
\large
|
||||
Stack \textbf{operativo y estable} para orquestaci\'on y control remoto del bot.\\[0.3em]
|
||||
\normalsize
|
||||
Carga ligera del gateway y heartbeats: \pass\ bajo contenci\'on moderada.\\[0.2em]
|
||||
Pipeline completo a \textbf{100\,000 facturas/mes} en este host: \fail\ (ver cap.~\ref{ch:capacidad}).
|
||||
\end{center}
|
||||
|
||||
\section{KPIs de la campa\~na}
|
||||
\begin{table}[ht]
|
||||
\centering
|
||||
\begin{tabular}{@{}lrrl@{}}
|
||||
\toprule
|
||||
\textbf{Indicador} & \textbf{Valor} & \textbf{Objetivo} & \textbf{Estado} \\
|
||||
\midrule
|
||||
ST-GW-01 p95 & 40{,}85\,ms & $<$500\,ms & \pass \\
|
||||
ST-GW-01 fail rate & 0\,\% & $<$1\,\% & \pass \\
|
||||
ST-BOT-01 p95 (reintento) & 85{,}55\,ms & $<$800\,ms & \pass \\
|
||||
Unitarias agent bot & 19/19 & 100\,\% & \pass \\
|
||||
Unitarias API bot store & 7/7 & 100\,\% & \pass \\
|
||||
Unitarias count-api (pipeline) & 3/3 & 100\,\% & \pass \\
|
||||
\bottomrule
|
||||
\end{tabular}
|
||||
\caption{KPIs ejecutivos de la campa\~na.}
|
||||
\end{table}
|
||||
|
||||
\section{Respuesta directa: 100\,000 facturas/mes}
|
||||
\begin{description}
|
||||
\item[Host actual (8\,GiB + MLflow/n8n/MinIO/\ldots)] \fail\ para el \emph{pipeline completo} (conteo + extracci\'on + bot + limpieza).
|
||||
\item[Infra dedicada dimensionada] \pass\ \emph{condicional}: workers LLM, OCR con RAM $\geq$16--32\,GiB, $N$ agents Selenium, cuota API, sin zoo Docker competidor.
|
||||
\end{description}
|
||||
Detalle cuantitativo en el cap\'itulo~\ref{ch:capacidad}.
|
||||
|
||||
\section{Riesgo principal}
|
||||
Contenci\'on de plataforma: swap $\approx$2{,}1\,GiB y \texttt{mlflow-server} $\approx$1{,}84\,GiB provocaron un outlier de 23\,s en heartbeats (corrida 1). Tras aliviar carga, p95 cay\'o a 86\,ms. Los fallos de latencia deben correlacionarse siempre con \texttt{free -h} / \texttt{docker stats}.
|
||||
@@ -0,0 +1,24 @@
|
||||
\chapter{Objeto y alcance}
|
||||
\label{ch:alcance}
|
||||
|
||||
\section{Objeto}
|
||||
Documentar de forma profesional los \textbf{resultados} de pruebas de IPSExtractor (humo, unitarias, carga) y evaluar la viabilidad de procesar \textbf{100\,000 facturas/mes}.
|
||||
|
||||
\section{Alcance incluido}
|
||||
\begin{itemize}
|
||||
\item Stack Docker IPS: gateway, extraction, workers, cleaning, count-api, bot-descarga, PostgreSQL, Redis.
|
||||
\item Perfil de carga \texttt{shared8g} en host de staging compartido.
|
||||
\item Unitarias del agent (\texttt{bots/bot\_descarga}) y del store API (\texttt{services/bot\_descarga}).
|
||||
\item Modelo anal\'itico de capacidad por etapa del pipeline.
|
||||
\end{itemize}
|
||||
|
||||
\section{Fuera de alcance de esta campa\~na}
|
||||
\begin{itemize}
|
||||
\item ST-COUNT-01 / OCR masivo y ST-EXT bajo LLM real a escala.
|
||||
\item E2E Imaging/Selenium con VPN.
|
||||
\item ST-BOT-03 (events) --- requiere \texttt{DOWNLOAD\_JOB\_ID} de un job real (\neje).
|
||||
\item Procesar 100\,000 PDFs reales.
|
||||
\end{itemize}
|
||||
|
||||
\section{Definici\'on de factura}
|
||||
Una \textbf{factura} = un PDF de cuenta m\'edica evaluado por etapa (conteo OCR, extracci\'on LLM, descarga bot, limpieza). El veredicto 100k/mes se expresa por etapa y para el pipeline completo.
|
||||
@@ -0,0 +1,64 @@
|
||||
\chapter{Entorno bajo prueba}
|
||||
\label{ch:entorno}
|
||||
|
||||
\section{Hardware y sistema}
|
||||
\begin{table}[ht]
|
||||
\centering
|
||||
\begin{tabular}{@{}lp{8.5cm}@{}}
|
||||
\toprule
|
||||
\textbf{Campo} & \textbf{Valor} \\
|
||||
\midrule
|
||||
Hostname & \texttt{ubuntu} \\
|
||||
SO & Ubuntu 24.04.4 LTS \\
|
||||
CPU & Intel Core i5-7200U @ 2{,}50\,GHz (2C/4T) \\
|
||||
RAM & 7{,}6\,GiB (l\'imite Docker $\approx$ 7{,}64\,GiB) \\
|
||||
Disco & HDD 500\,GB + USB 1\,TB \\
|
||||
Docker & 29.1.3 \\
|
||||
Gateway & \texttt{http://127.0.0.1:8189} \\
|
||||
\bottomrule
|
||||
\end{tabular}
|
||||
\caption{Host de ejecuci\'on.}
|
||||
\end{table}
|
||||
|
||||
\section{Baseline de memoria (2026-07-13)}
|
||||
\begin{itemize}
|
||||
\item Usados $\approx$4{,}4\,GiB; disponibles $\approx$3{,}3\,GiB; \textbf{swap 2{,}1\,GiB}.
|
||||
\item IPS idle $\approx$230--250\,MiB total.
|
||||
\item \texttt{mlflow-server} $\approx$1{,}84\,GiB; n8n $\approx$291\,MiB; MinIO $\approx$136\,MiB.
|
||||
\end{itemize}
|
||||
|
||||
\begin{figure}[ht]
|
||||
\centering
|
||||
\begin{tikzpicture}
|
||||
\begin{axis}[
|
||||
ybar,
|
||||
width=0.95\textwidth,
|
||||
height=6.5cm,
|
||||
ylabel={RAM (MiB)},
|
||||
symbolic x coords={api,extraction,w1,w2,cleaning,bot,count,db,redis,mlflow,n8n,minio},
|
||||
xtick=data,
|
||||
x tick label style={rotate=45, anchor=east, font=\scriptsize},
|
||||
nodes near coords,
|
||||
nodes near coords style={font=\tiny, rotate=90, anchor=west},
|
||||
bar width=8pt,
|
||||
ymin=0,
|
||||
enlarge x limits=0.08,
|
||||
grid=major,
|
||||
legend style={at={(0.98,0.98)},anchor=north east,font=\scriptsize}
|
||||
]
|
||||
\addplot[fill=ipsblue!70] coordinates {
|
||||
(api,43) (extraction,30) (w1,29) (w2,32) (cleaning,51)
|
||||
(bot,17) (count,13) (db,26) (redis,8) (mlflow,1884) (n8n,291) (minio,136)
|
||||
};
|
||||
\end{axis}
|
||||
\end{tikzpicture}
|
||||
\caption{Huella de memoria por contenedor (idle). MLflow domina el host.}
|
||||
\label{fig:memoria}
|
||||
\end{figure}
|
||||
|
||||
\section{Salud del stack}
|
||||
\begin{itemize}
|
||||
\item Contenedores IPS: \texttt{Up} / healthy ($\sim$17\,h).
|
||||
\item \texttt{/health/live}: ok, $\approx$11\,ms.
|
||||
\item \texttt{/health}: ok (extraction, cleaning, counting, bot\_descarga), $\approx$194\,ms.
|
||||
\end{itemize}
|
||||
@@ -0,0 +1,30 @@
|
||||
\chapter{Metodolog\'ia}
|
||||
\label{ch:metodologia}
|
||||
|
||||
\section{Tipos de prueba}
|
||||
\begin{enumerate}
|
||||
\item \textbf{Unitarias} --- pytest (agent y API bot).
|
||||
\item \textbf{Humo / funcional} --- curl y checks HTTP del gateway.
|
||||
\item \textbf{Carga} --- k6 perfil \texttt{shared8g}.
|
||||
\item \textbf{Capacidad} --- modelo anal\'itico (no carga de 100k PDFs).
|
||||
\end{enumerate}
|
||||
|
||||
\section{Herramientas}
|
||||
k6 (Grafana), pytest, Docker stats, curl, pdflatex/pgfplots.
|
||||
|
||||
\section{SLOs perfil \texttt{shared8g}}
|
||||
\begin{itemize}
|
||||
\item Gateway \texttt{/health/live}: p95 $<$500\,ms; fail $<$1\,\%.
|
||||
\item Heartbeats bot: p95 $<$800\,ms; checks $>$95\,\%.
|
||||
\item No usar \texttt{/health} agregado como SLO de latencia.
|
||||
\end{itemize}
|
||||
|
||||
\section{Comandos representativos}
|
||||
\begin{lstlisting}[language=bash]
|
||||
k6 run -e PROFILE=shared8g -e BASE_URL=http://127.0.0.1:8189 \
|
||||
k6_gateway_health.js
|
||||
k6 run -e PROFILE=shared8g -e BOT_API_TOKEN=... \
|
||||
k6_bot_heartbeats.js
|
||||
python -m pytest bots/bot_descarga/tests -q
|
||||
cd services/bot_descarga && python -m pytest tests -q
|
||||
\end{lstlisting}
|
||||
@@ -0,0 +1,57 @@
|
||||
\chapter{Resultados --- pruebas unitarias}
|
||||
\label{ch:unitarias}
|
||||
|
||||
\section{Scorecard}
|
||||
\begin{table}[ht]
|
||||
\centering
|
||||
\begin{tabular}{@{}llrl@{}}
|
||||
\toprule
|
||||
\textbf{Suite} & \textbf{Resultado} & \textbf{Passed} & \textbf{Duraci\'on} \\
|
||||
\midrule
|
||||
\texttt{bots/bot\_descarga/tests} & \pass & 19/19 & 3{,}21\,s \\
|
||||
\texttt{services/bot\_descarga/tests} & \pass & 7/7 & 1{,}45\,s \\
|
||||
\texttt{go test} count-api (\texttt{internal/pipeline}) & \pass$^{*}$ & 3+8 nuevos & ver Timing \\
|
||||
Vitest desktop & \neje & --- & No ejecutado en esta sesi\'on \\
|
||||
\bottomrule
|
||||
\end{tabular}
|
||||
\caption{Resultados unitarios. $^{*}$Base 3 PASS confirmados en Ubuntu; ampliaci\'on features/clustering (+8) y Timing sobre 3 PDFs reales requieren \texttt{go test} en host con Tesseract.}
|
||||
\end{table}
|
||||
|
||||
\section{Cobertura agent}
|
||||
Incluye excel reader, PDF validator, file manager, cancel, browser fatal, MinIO keys, GUI settings (19 casos).
|
||||
|
||||
\section{Cobertura API store}
|
||||
Incluye helpers MinIO, register/heartbeat/offline, claim at\'omico, cancel, events, filtros por job padre (7 casos).
|
||||
|
||||
\section{Cobertura count-api}
|
||||
Ejecutado en Ubuntu (deps Tesseract/Leptonica):
|
||||
tests originales de features (3) \pass.
|
||||
Ampliaci\'on: \texttt{features\_extra\_test.go} + \texttt{clustering\_test.go} (+8 casos).
|
||||
Medici\'on de documentos: \texttt{processor\_timing\_test.go} sobre
|
||||
\texttt{docs/plan\_pruebas/data/*.pdf} (3 archivos); ver cap.~\ref{ch:count-latency}.
|
||||
\texttt{PDFResult} ahora incluye \texttt{render\_ms}/\texttt{ocr\_ms}/\texttt{cluster\_ms}/\texttt{infer\_ms}/\texttt{total\_ms}.
|
||||
|
||||
\begin{figure}[ht]
|
||||
\centering
|
||||
\begin{tikzpicture}
|
||||
\begin{axis}[
|
||||
xbar,
|
||||
width=0.85\textwidth,
|
||||
height=4.2cm,
|
||||
xlabel={Casos},
|
||||
symbolic y coords={agent,api-store,count-api,desktop},
|
||||
ytick=data,
|
||||
xmin=0, xmax=22,
|
||||
nodes near coords,
|
||||
bar width=10pt,
|
||||
legend style={at={(0.98,0.35)},anchor=east,font=\small}
|
||||
]
|
||||
\addplot[fill=passgreen!70] coordinates {(19,agent) (7,api-store) (3,count-api) (0,desktop)};
|
||||
\addlegendentry{PASS}
|
||||
\addplot[fill=ipsgrey!40] coordinates {(0,agent) (0,api-store) (0,count-api) (1,desktop)};
|
||||
\addlegendentry{N/E}
|
||||
\end{axis}
|
||||
\end{tikzpicture}
|
||||
\caption{Unitarias ejecutadas vs pendientes.}
|
||||
\label{fig:unitarias}
|
||||
\end{figure}
|
||||
@@ -0,0 +1,23 @@
|
||||
\chapter{Resultados --- humo y funcional}
|
||||
\label{ch:humo}
|
||||
|
||||
\section{Humo gateway}
|
||||
\begin{table}[ht]
|
||||
\centering
|
||||
\begin{tabular}{@{}llrl@{}}
|
||||
\toprule
|
||||
\textbf{Check} & \textbf{Resultado} & \textbf{Latencia} & \textbf{Notas} \\
|
||||
\midrule
|
||||
\texttt{GET /health/live} & \pass & 11\,ms & Liveness \\
|
||||
\texttt{GET /health} & \pass & 194\,ms & 4 servicios ok \\
|
||||
Contenedores IPS healthy & \pass & --- & 17\,h uptime \\
|
||||
\bottomrule
|
||||
\end{tabular}
|
||||
\caption{Humo HTTP.}
|
||||
\end{table}
|
||||
|
||||
\section{Funcional E2E-07 (worker offline $\rightarrow$ 409)}
|
||||
\neje\ en esta campa\~na (no se envi\'o \texttt{POST /bot-descarga/jobs} contra worker offline). Queda como follow-up de 2 minutos en staging.
|
||||
|
||||
\section{Script preparado}
|
||||
Se a\~nadi\'o \texttt{docs/plan\_pruebas/scripts/k6\_smoke\_suite.js} para repeticiones automatizadas de live/health/config.
|
||||
@@ -0,0 +1,110 @@
|
||||
\chapter{Resultados --- pruebas de carga}
|
||||
\label{ch:carga}
|
||||
|
||||
\section{Tabla consolidada}
|
||||
\begin{table}[ht]
|
||||
\centering
|
||||
\begin{tabular}{@{}lrrrrrl@{}}
|
||||
\toprule
|
||||
\textbf{Caso} & \textbf{Reqs} & \textbf{Fail\%} & \textbf{avg} & \textbf{p95} & \textbf{max} & \textbf{Res.} \\
|
||||
\midrule
|
||||
ST-GW-01 & 6884 & 0 & 12{,}6\,ms & 40{,}9\,ms & 125\,ms & \pass \\
|
||||
ST-BOT-01 \#1 & 80 & 0 & 511\,ms & 2{,}28\,s & 23{,}1\,s & \parcial \\
|
||||
ST-BOT-01 \#2 & 80 & 0 & 57\,ms & 85{,}6\,ms & 98\,ms & \pass \\
|
||||
ST-BOT-03 & --- & --- & --- & --- & --- & \neje \\
|
||||
\bottomrule
|
||||
\end{tabular}
|
||||
\caption{M\'etricas de carga k6 (perfil shared8g).}
|
||||
\label{tab:carga}
|
||||
\end{table}
|
||||
|
||||
\section{ST-GW-01}
|
||||
\begin{itemize}
|
||||
\item Checks 13768/13768 (100\,\%); throughput $\approx$57\,req/s.
|
||||
\item Thresholds p95$<$500\,ms y fail$<$1\,\% en verde.
|
||||
\end{itemize}
|
||||
|
||||
\section{ST-BOT-01 --- comparativa}
|
||||
Corrida 1: funcional OK, latencia FAIL (contenci\'on host).\\
|
||||
Corrida 2: funcional OK, latencia PASS (host m\'as libre).
|
||||
|
||||
\begin{figure}[ht]
|
||||
\centering
|
||||
\begin{tikzpicture}
|
||||
\begin{axis}[
|
||||
ybar,
|
||||
width=0.95\textwidth,
|
||||
height=7cm,
|
||||
ylabel={Latencia (ms)},
|
||||
symbolic x coords={GW-p95,BOT1-p95,BOT2-p95,GW-max,BOT1-max,BOT2-max},
|
||||
xtick=data,
|
||||
x tick label style={rotate=30, anchor=east, font=\small},
|
||||
nodes near coords,
|
||||
nodes near coords style={font=\scriptsize},
|
||||
bar width=12pt,
|
||||
ymin=0,
|
||||
enlarge x limits=0.12,
|
||||
grid=major,
|
||||
legend entries={medici\'on, umbral GW 500, umbral BOT 800},
|
||||
legend style={at={(0.02,0.98)},anchor=north west,font=\scriptsize}
|
||||
]
|
||||
\addplot[fill=ipsblue!75] coordinates {
|
||||
(GW-p95,40.85) (BOT1-p95,2280) (BOT2-p95,85.55)
|
||||
(GW-max,125.37) (BOT1-max,23120) (BOT2-max,98.31)
|
||||
};
|
||||
\addplot[red, sharp plot, update limits=false, forget plot] coordinates {(GW-p95,500) (BOT2-max,500)};
|
||||
\addplot[orange, sharp plot, update limits=false] coordinates {(BOT1-p95,800) (BOT2-p95,800)};
|
||||
\end{axis}
|
||||
\end{tikzpicture}
|
||||
\caption{p95 y max por caso. BOT1-max (23\,s) fuera de escala visual relativa --- ver tabla.}
|
||||
\label{fig:latencias}
|
||||
\end{figure}
|
||||
|
||||
\begin{figure}[ht]
|
||||
\centering
|
||||
\begin{tikzpicture}
|
||||
\begin{axis}[
|
||||
ybar,
|
||||
width=0.9\textwidth,
|
||||
height=6cm,
|
||||
ylabel={ms},
|
||||
symbolic x coords={avg,med,p95,max},
|
||||
xtick=data,
|
||||
legend style={at={(0.02,0.98)},anchor=north west},
|
||||
bar width=10pt,
|
||||
ymin=0, ymax=600,
|
||||
enlarge x limits=0.15,
|
||||
grid=major,
|
||||
title={ST-BOT-01: corrida 1 vs 2 (max de \#1 truncado en eje)}
|
||||
]
|
||||
\addplot[fill=failred!60] coordinates {(avg,511) (med,57) (p95,500) (max,600)};
|
||||
\addlegendentry{\#1 (p95 real 2280; max 23120)}
|
||||
\addplot[fill=passgreen!70] coordinates {(avg,57) (med,56) (p95,86) (max,98)};
|
||||
\addlegendentry{\#2 PASS}
|
||||
\end{axis}
|
||||
\end{tikzpicture}
|
||||
\caption{Before/after heartbeats (eje limitado; valores exactos en tabla~\ref{tab:carga}).}
|
||||
\label{fig:bot-before-after}
|
||||
\end{figure}
|
||||
|
||||
\section{Throughput gateway}
|
||||
\begin{figure}[ht]
|
||||
\centering
|
||||
\begin{tikzpicture}
|
||||
\begin{axis}[
|
||||
width=0.7\textwidth,
|
||||
height=5cm,
|
||||
ybar,
|
||||
symbolic x coords={ST-GW-01},
|
||||
xtick=data,
|
||||
ylabel={req/s},
|
||||
nodes near coords,
|
||||
ymin=0,
|
||||
bar width=30pt
|
||||
]
|
||||
\addplot[fill=ipsblue] coordinates {(ST-GW-01,57.33)};
|
||||
\end{axis}
|
||||
\end{tikzpicture}
|
||||
\caption{Throughput observado en health liveness (no equivale a PDFs/h).}
|
||||
\label{fig:throughput}
|
||||
\end{figure}
|
||||
@@ -0,0 +1,104 @@
|
||||
\chapter{Capacidad: 100\,000 facturas al mes}
|
||||
\label{ch:capacidad}
|
||||
|
||||
\section{Traducci\'on del objetivo}
|
||||
\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{Throughput m\'inimo para cumplir 100k/mes.}
|
||||
\end{table}
|
||||
|
||||
\section{Modelo por etapa}
|
||||
Supuestos: host compartido medido el 2026-07-13; estimaciones \textbf{conservadoras} (no bench OCR/LLM masivo). Detalle en \texttt{scripts/estimate\_capacity.md}.
|
||||
|
||||
\begin{table}[ht]
|
||||
\centering
|
||||
\begin{tabular}{@{}lrrll@{}}
|
||||
\toprule
|
||||
\textbf{Etapa} & \textbf{Host act.} & \textbf{Dedicado} & \textbf{vs 139/h} & \textbf{vs 570/h} \\
|
||||
\midrule
|
||||
Gateway (orquestaci\'on) & $\gg$10\,000 & $\gg$20\,000 & OK & OK \\
|
||||
count-api OCR & $\sim$40 & $\sim$400 & RIESGO & NO \\
|
||||
extraction LLM & $\sim$100 & $\sim$800 & \'AMBAR & NO$^{*}$ \\
|
||||
bot Selenium (1 agent) & $\sim$60 & $\sim$500$^{\dagger}$ & RIESGO & NO \\
|
||||
cleaning & $\sim$800 & $\sim$3\,000 & OK & \'AMBAR \\
|
||||
\textbf{Pipeline completo} & $\sim$35 & $\sim$450 & \fail & \'AMBAR \\
|
||||
\bottomrule
|
||||
\end{tabular}
|
||||
\caption{Facturas/hora estimadas. $^{*}$Sujeto a cuota LLM. $^{\dagger}$Con $N$ agents en paralelo.}
|
||||
\label{tab:capacidad}
|
||||
\end{table}
|
||||
|
||||
\begin{figure}[ht]
|
||||
\centering
|
||||
\begin{tikzpicture}
|
||||
\begin{axis}[
|
||||
ybar,
|
||||
width=\textwidth,
|
||||
height=7.5cm,
|
||||
ylabel={Facturas / hora (estimadas)},
|
||||
symbolic x coords={Gateway,OCR,LLM,Bot,Cleaning,Pipeline},
|
||||
xtick=data,
|
||||
x tick label style={font=\small},
|
||||
legend style={at={(0.02,0.98)},anchor=north west,font=\small},
|
||||
bar width=8pt,
|
||||
ymin=0, ymax=1200,
|
||||
enlarge x limits=0.08,
|
||||
grid=major,
|
||||
title={Capacidad estimada vs objetivos 139/h y 570/h}
|
||||
]
|
||||
\addplot[fill=ipsblue!70] coordinates {
|
||||
(Gateway,1000) (OCR,40) (LLM,100) (Bot,60) (Cleaning,800) (Pipeline,35)
|
||||
};
|
||||
\addlegendentry{Host actual (Gateway truncado)}
|
||||
\addplot[fill=passgreen!55] coordinates {
|
||||
(Gateway,1000) (OCR,400) (LLM,800) (Bot,500) (Cleaning,1000) (Pipeline,450)
|
||||
};
|
||||
\addlegendentry{Dedicado (estim.)}
|
||||
\addplot[failred, ultra thick, sharp plot, update limits=false] coordinates {
|
||||
(Gateway,139) (Pipeline,139)
|
||||
};
|
||||
\addlegendentry{Objetivo 139/h}
|
||||
\addplot[warnorange, ultra thick, sharp plot, update limits=false] coordinates {
|
||||
(Gateway,570) (Pipeline,570)
|
||||
};
|
||||
\addlegendentry{Objetivo 570/h}
|
||||
\end{axis}
|
||||
\end{tikzpicture}
|
||||
\caption{Brecha de capacidad. Gateway real $\gg$1000/h; se trunca para legibilidad.}
|
||||
\label{fig:capacidad}
|
||||
\end{figure}
|
||||
|
||||
\section{Interpretaci\'on}
|
||||
\begin{enumerate}
|
||||
\item El \textbf{gateway} no es el cuello de botella (evidencia ST-GW-01).
|
||||
\item En el \textbf{host actual}, OCR + Selenium + LLM compiten por CPU/RAM/swap con MLflow: el pipeline completo queda $\ll$139/h.
|
||||
\item Con infra \textbf{dedicada} (sin zoo, m\'as RAM, workers, $N$ PCs bot, cuota LLM), acercarse a 139/h (24$\times$7) es plausible; 570/h exige dimensionamiento agresivo y prueba de carga espec\'ifica.
|
||||
\end{enumerate}
|
||||
|
||||
\section{Veredicto}
|
||||
\begin{center}
|
||||
\fbox{\parbox{0.92\textwidth}{\centering
|
||||
\large \textbf{Se pueden procesar +100\,000 facturas/mes?}\\[0.4em]
|
||||
\normalsize
|
||||
En el servidor actual compartido: \fail.\\[0.2em]
|
||||
En arquitectura dedicada y dimensionada: \pass\ condicional (cuota LLM, OCR, agents).\\[0.3em]
|
||||
\footnotesize Esta campa\~na no ejecut\'o 100k PDFs; el veredicto es modelo + evidencia de carga ligera.
|
||||
}}
|
||||
\end{center}
|
||||
|
||||
\section{Requisitos m\'inimos sugeridos para 100k/mes}
|
||||
\begin{itemize}
|
||||
\item Host(s) dedicados IPS: RAM $\geq$16--32\,GiB; SSD; sin MLflow/Airflow en el mismo nodo.
|
||||
\item count-api: pool OCR dimensionado; l\'imite de memoria realista.
|
||||
\item extraction: r\'eplicas worker + concurrencia acorde a RPM del LLM; presupuesto de coste.
|
||||
\item bot: varios agents PC en paralelo hacia Imaging/MinIO.
|
||||
\item Observabilidad: colas ARQ, p95 por etapa, alertas de swap.
|
||||
\end{itemize}
|
||||
@@ -0,0 +1,41 @@
|
||||
\chapter{An\'alisis y hallazgos}
|
||||
\label{ch:analisis}
|
||||
|
||||
\section{Hallazgos}
|
||||
\begin{enumerate}
|
||||
\item \textbf{H1 --- Contenci\'on de plataforma:} ST-BOT-01 \#1 (p95 2{,}28\,s, max 23\,s) vs \#2 (p95 86\,ms) demuestra sensibilidad a swap/MLflow, no a un bug funcional del heartbeat (100\,\% HTTP 200 en ambas).
|
||||
\item \textbf{H2 --- Gateway saludable:} 0\,\% errores y p95 41\,ms bajo 25 VUs en liveness.
|
||||
\item \textbf{H3 --- Unitarias s\'olidas:} 29 casos verdes (19 agent + 7 API store + 3 count-api pipeline).
|
||||
\item \textbf{H4 --- Capacidad 100k:} imposible en host compartido actual; viable solo con escalado dedicado.
|
||||
\end{enumerate}
|
||||
|
||||
\section{Scorecard global}
|
||||
\begin{figure}[ht]
|
||||
\centering
|
||||
\begin{tikzpicture}
|
||||
\begin{axis}[
|
||||
ybar stacked,
|
||||
width=0.9\textwidth,
|
||||
height=5.5cm,
|
||||
ylabel={Cantidad de casos},
|
||||
symbolic x coords={Unitaria,Humo,Carga,Funcional,Capacidad},
|
||||
xtick=data,
|
||||
legend style={at={(0.5,-0.22)},anchor=north,legend columns=3,font=\small},
|
||||
bar width=18pt,
|
||||
ymin=0, ymax=8,
|
||||
enlarge x limits=0.12
|
||||
]
|
||||
\addplot[fill=passgreen!70] coordinates {(Unitaria,3) (Humo,3) (Carga,2) (Funcional,0) (Capacidad,0)};
|
||||
\addlegendentry{PASS}
|
||||
\addplot[fill=warnorange!70] coordinates {(Unitaria,0) (Humo,0) (Carga,1) (Funcional,0) (Capacidad,0)};
|
||||
\addlegendentry{PARCIAL}
|
||||
\addplot[fill=ipsgrey!50] coordinates {(Unitaria,1) (Humo,0) (Carga,1) (Funcional,1) (Capacidad,1)};
|
||||
\addlegendentry{N/E}
|
||||
\end{axis}
|
||||
\end{tikzpicture}
|
||||
\caption{Distribuci\'on de resultados por tipo de prueba.}
|
||||
\label{fig:scorecard}
|
||||
\end{figure}
|
||||
|
||||
\section{Limitaciones}
|
||||
No se midieron tiempos reales OCR/LLM por PDF en esta campa\~na; las cifras de capacidad son estimaciones documentadas para decisi\'on de arquitectura, no un compromiso de SLA.
|
||||
@@ -0,0 +1,26 @@
|
||||
\chapter{Conclusiones y recomendaciones}
|
||||
\label{ch:conclusiones}
|
||||
|
||||
\section{Conclusiones}
|
||||
\begin{enumerate}
|
||||
\item IPSExtractor en staging responde correctamente a humo y carga ligera de control (gateway + heartbeats).
|
||||
\item La calidad de latencia depende cr\'iticamente de no saturar el host compartido.
|
||||
\item \textbf{100\,000 facturas/mes no son alcanzables} en el laptop Ubuntu actual con el zoo Docker observado.
|
||||
\item Con infraestructura dedicada y dimensionada, el objetivo es \textbf{condicional\-mente viable}; requiere prueba de carga espec\'ifica por etapa (OCR, LLM, bot).
|
||||
\end{enumerate}
|
||||
|
||||
\section{Recomendaciones}
|
||||
\begin{enumerate}
|
||||
\item Separar IPS de MLflow/Airflow/n8n en el nodo de producci\'on/staging de rendimiento.
|
||||
\item Ejecutar ST-COUNT-01 y bench LLM solo con $\geq$3\,GiB libres y sin swap activo.
|
||||
\item Completar E2E-07 y ST-BOT-03; archivar JSON de k6 en \texttt{informes/}.
|
||||
\item Dimensionar agents bot ($N$) antes de comprometer vol\'umenes mensuales de descarga.
|
||||
\item Incorporar CI: pytest agent + store en cada PR.
|
||||
\end{enumerate}
|
||||
|
||||
\section{Pr\'oxima campa\~na sugerida}
|
||||
\begin{itemize}
|
||||
\item Ventana con MLflow detenido.
|
||||
\item Bench: 50--200 PDFs reales por etapa (OCR, LLM) midiendo p50/p95 y coste.
|
||||
\item Actualizar gr\'afica de capacidad con datos medidos (reemplazar estimaciones).
|
||||
\end{itemize}
|
||||
@@ -0,0 +1,41 @@
|
||||
\chapter{Anexos}
|
||||
\label{ch:anexos}
|
||||
|
||||
\section{Datos fuente}
|
||||
Archivos: \texttt{informe/data/metricas.csv}, \texttt{capacidad.csv}, \texttt{memoria.csv}.\\
|
||||
Markdown de campa\~na: \texttt{docs/plan\_pruebas/informes/2026-07-13-shared8g.md}.
|
||||
|
||||
\section{Extracto ST-GW-01}
|
||||
\begin{lstlisting}
|
||||
checks 100% (13768)
|
||||
http_req_failed 0%
|
||||
p(95)=40.85ms avg=12.55ms reqs=6884 (~57/s)
|
||||
\end{lstlisting}
|
||||
|
||||
\section{Extracto ST-BOT-01 \#2}
|
||||
\begin{lstlisting}
|
||||
checks 100% (75)
|
||||
p(95)=85.55ms max=98.31ms
|
||||
thresholds OK (p95<800)
|
||||
\end{lstlisting}
|
||||
|
||||
\section{Unitarias}
|
||||
\begin{lstlisting}
|
||||
bots/bot_descarga/tests: 19 passed in 3.21s
|
||||
services/bot_descarga/tests: 7 passed in 1.45s
|
||||
count-api internal/pipeline: 3 passed (+8 features/clustering nuevos)
|
||||
Timing: go test -run Timing -v (3 PDFs en docs/plan_pruebas/data/)
|
||||
HTTP: bash docs/plan_pruebas/scripts/measure_count_latency.sh
|
||||
\end{lstlisting}
|
||||
|
||||
\section{Scripts}
|
||||
\texttt{k6\_gateway\_health.js}, \texttt{k6\_bot\_heartbeats.js}, \texttt{k6\_bot\_events.js},
|
||||
\texttt{k6\_count\_batches.js}, \texttt{k6\_smoke\_suite.js}, \texttt{estimate\_capacity.md}.
|
||||
|
||||
\section{Reproducibilidad}
|
||||
\begin{enumerate}
|
||||
\item Registrar \texttt{hostnamectl}, \texttt{free -h}, \texttt{docker stats --no-stream}.
|
||||
\item Verificar \texttt{/health/live} y \texttt{/health}.
|
||||
\item Ejecutar k6 con \texttt{PROFILE=shared8g}.
|
||||
\item Adjuntar salida completa al directorio \texttt{informes/}.
|
||||
\end{enumerate}
|
||||
@@ -0,0 +1,49 @@
|
||||
\chapter{Resultados --- latencia count-api (PDFs reales)}
|
||||
\label{ch:count-latency}
|
||||
|
||||
\section{Fixtures}
|
||||
Tres PDFs en \texttt{docs/plan\_pruebas/data/}:
|
||||
\begin{itemize}
|
||||
\item \texttt{500000284335937.pdf}
|
||||
\item \texttt{500000284335940.pdf}
|
||||
\item \texttt{500000284335954.pdf}
|
||||
\end{itemize}
|
||||
|
||||
\section{Unitarias pipeline ampliadas}
|
||||
Adem\'as de los 3 tests originales de features:
|
||||
\begin{itemize}
|
||||
\item Header keyword, service code, empty rows, no table pattern.
|
||||
\item Clustering: dos bandas Y, misma banda, vac\'io, ruido \texttt{minSamples}.
|
||||
\end{itemize}
|
||||
Total esperado en \texttt{go test ./internal/pipeline/ -run 'TestExtract|TestBuild|TestFeature|TestCluster'}: \textbf{11} casos.
|
||||
|
||||
\section{Timings por etapa}
|
||||
El procesador expone \texttt{render\_ms}, \texttt{ocr\_ms}, \texttt{cluster\_ms}, \texttt{infer\_ms}, \texttt{total\_ms} en \texttt{PDFResult}.
|
||||
|
||||
\begin{lstlisting}[language=bash,caption={Medici\'on en Ubuntu}]
|
||||
cd services/services-count-facturas-medicas-serving
|
||||
go test ./internal/pipeline/ -run Timing -v
|
||||
|
||||
# HTTP v\'ia gateway:
|
||||
bash docs/plan_pruebas/scripts/measure_count_latency.sh
|
||||
# o PowerShell:
|
||||
# .\docs\plan_pruebas\scripts\measure_count_latency.ps1
|
||||
\end{lstlisting}
|
||||
|
||||
Los resultados de la corrida Timing/HTTP deben pegarse en
|
||||
\texttt{docs/plan\_pruebas/informes/count\_latency\_YYYYMMDD.csv} y actualizar esta tabla.
|
||||
|
||||
\begin{table}[ht]
|
||||
\centering
|
||||
\begin{tabular}{@{}lrrrrrl@{}}
|
||||
\toprule
|
||||
\textbf{PDF} & \textbf{render} & \textbf{ocr} & \textbf{cluster} & \textbf{infer} & \textbf{total} & \textbf{status} \\
|
||||
\midrule
|
||||
500000284335937 & --- & --- & --- & --- & --- & pendiente Ubuntu \\
|
||||
500000284335940 & --- & --- & --- & --- & --- & pendiente Ubuntu \\
|
||||
500000284335954 & --- & --- & --- & --- & --- & pendiente Ubuntu \\
|
||||
\bottomrule
|
||||
\end{tabular}
|
||||
\caption{Latencia por etapa (ms). Completar tras \texttt{-run Timing} en el host con Tesseract.}
|
||||
\label{tab:count-timing}
|
||||
\end{table}
|
||||
@@ -0,0 +1,40 @@
|
||||
# Informe de ejecución — 2026-07-13
|
||||
|
||||
Perfil: `shared8g` · Host: `ubuntu` (ASUS X441UAK, Ubuntu 24.04, i5-7200U, 8 GiB) · Gateway: `http://127.0.0.1:8189`
|
||||
|
||||
## Baseline
|
||||
|
||||
| Check | Resultado |
|
||||
|-------|-----------|
|
||||
| RAM | 4.4 GiB usados / 3.3 GiB disponibles / **swap 2.1 GiB** |
|
||||
| IPS containers | healthy (api, bot, extraction, workers, count, cleaning, db, redis) |
|
||||
| `/health/live` | ok · ~11 ms |
|
||||
| `/health` | ok (4 servicios) · ~194 ms |
|
||||
| Vecino pesado | mlflow-server ~1.84 GiB |
|
||||
|
||||
## Resultados
|
||||
|
||||
| ID | Resultado | Notas |
|
||||
|----|-----------|-------|
|
||||
| Smoke | **PASS** | Stack IPS OK |
|
||||
| Unitarias agent | **PASS** | 19/19 en 3.21s |
|
||||
| Unitarias API store | **PASS** | 7/7 en 1.45s |
|
||||
| Unitarias count-api | **PASS** | 3/3 pipeline (Ubuntu + leptonica-dev) |
|
||||
| ST-GW-01 | **PASS** | 6884 req · fail 0% · p95=40.85 ms |
|
||||
| ST-BOT-01 #1 | **PARCIAL** | 100% HTTP 200 · p95=2.28 s · max=23 s (FAIL latencia) |
|
||||
| ST-BOT-01 #2 | **PASS** | 100% HTTP 200 · p95=85.55 ms · max=98 ms |
|
||||
|
||||
## Capacidad 100k/mes
|
||||
|
||||
- Host actual compartido: **NO** (pipeline completo).
|
||||
- Infra dedicada: **SÍ condicional** (ver informe PDF).
|
||||
|
||||
## Conclusión
|
||||
|
||||
Gateway y heartbeats de bot-descarga cumplen SLO del perfil `shared8g` en condiciones razonables. El fallo de latencia de BOT-01 #1 se atribuye a contención del host (swap + MLflow), no a error funcional de la API.
|
||||
|
||||
## Pendiente
|
||||
|
||||
- En Ubuntu: `go test ./internal/pipeline/ -v` y `go test ./internal/pipeline/ -run Timing -v`
|
||||
- HTTP: `bash docs/plan_pruebas/scripts/measure_count_latency.sh`
|
||||
- ST-BOT-03, ST-COUNT-01, E2E Imaging
|
||||
@@ -194,16 +194,33 @@
|
||||
\@writefile{toc}{\contentsline {section}{\numberline {11.1}Fases sugeridas}{28}{section.11.1}\protected@file@percent }
|
||||
\@writefile{lot}{\contentsline {table}{\numberline {11.1}{Hoja de ruta de calidad.}}{28}{table.11.1}\protected@file@percent }
|
||||
\@writefile{toc}{\contentsline {section}{\numberline {11.2}Definition of Done por release}{28}{section.11.2}\protected@file@percent }
|
||||
\@writefile{toc}{\contentsline {section}{\numberline {11.3}Plantilla de informe de ejecuci\'on}{28}{section.11.3}\protected@file@percent }
|
||||
\@writefile{lol}{\contentsline {lstlisting}{\numberline {11.1}Campos m\'inimos del informe}{28}{lstlisting.11.1}\protected@file@percent }
|
||||
\@writefile{toc}{\contentsline {section}{\numberline {11.4}Responsables (roles)}{29}{section.11.4}\protected@file@percent }
|
||||
\@writefile{toc}{\contentsline {chapter}{\numberline {A}Glosario}{30}{appendix.Alph1}\protected@file@percent }
|
||||
\@writefile{toc}{\contentsline {section}{\numberline {11.3}Campa\~na registrada}{28}{section.11.3}\protected@file@percent }
|
||||
\@writefile{toc}{\contentsline {section}{\numberline {11.4}Plantilla de informe de ejecuci\'on}{29}{section.11.4}\protected@file@percent }
|
||||
\@writefile{lol}{\contentsline {lstlisting}{\numberline {11.1}Campos m\'inimos del informe}{29}{lstlisting.11.1}\protected@file@percent }
|
||||
\@writefile{toc}{\contentsline {section}{\numberline {11.5}Responsables (roles)}{29}{section.11.5}\protected@file@percent }
|
||||
\@writefile{toc}{\contentsline {chapter}{\numberline {12}Informe de ejecuci\'on --- campa\~na 2026-07-13}{30}{chapter.12}\protected@file@percent }
|
||||
\@writefile{lof}{\addvspace {10\p@ }}
|
||||
\@writefile{lot}{\addvspace {10\p@ }}
|
||||
\@writefile{toc}{\contentsline {chapter}{\numberline {B}Checklist de release}{31}{appendix.Alph2}\protected@file@percent }
|
||||
\newlabel{ch:informe-2026-07-13}{{12}{30}{Informe de ejecuci\'on --- campa\~na 2026-07-13}{chapter.12}{}}
|
||||
\@writefile{toc}{\contentsline {section}{\numberline {12.1}Metadatos}{30}{section.12.1}\protected@file@percent }
|
||||
\@writefile{lot}{\contentsline {table}{\numberline {12.1}{\ignorespaces Metadatos de la campa\~na.}}{30}{table.12.1}\protected@file@percent }
|
||||
\@writefile{toc}{\contentsline {section}{\numberline {12.2}Baseline del entorno}{30}{section.12.2}\protected@file@percent }
|
||||
\@writefile{lot}{\contentsline {table}{\numberline {12.2}{\ignorespaces Resumen de la campa\~na 2026-07-13.}}{31}{table.12.2}\protected@file@percent }
|
||||
\newlabel{tab:campana-2026-07-13}{{12.2}{31}{Resumen de la campa\~na 2026-07-13}{table.12.2}{}}
|
||||
\@writefile{toc}{\contentsline {section}{\numberline {12.3}Resultados por caso}{31}{section.12.3}\protected@file@percent }
|
||||
\@writefile{toc}{\contentsline {subsection}{\numberline {12.3.1}ST-GW-01 --- gateway health}{31}{subsection.12.3.1}\protected@file@percent }
|
||||
\@writefile{toc}{\contentsline {subsection}{\numberline {12.3.2}ST-BOT-01 --- heartbeats (corrida 1)}{31}{subsection.12.3.2}\protected@file@percent }
|
||||
\@writefile{toc}{\contentsline {subsection}{\numberline {12.3.3}ST-BOT-01 --- heartbeats (corrida 2, reintento)}{31}{subsection.12.3.3}\protected@file@percent }
|
||||
\@writefile{toc}{\contentsline {section}{\numberline {12.4}Hallazgos}{31}{section.12.4}\protected@file@percent }
|
||||
\@writefile{toc}{\contentsline {section}{\numberline {12.5}No ejecutado en esta campa\~na}{32}{section.12.5}\protected@file@percent }
|
||||
\@writefile{toc}{\contentsline {section}{\numberline {12.6}Follow-ups}{32}{section.12.6}\protected@file@percent }
|
||||
\@writefile{toc}{\contentsline {chapter}{\numberline {A}Glosario}{33}{appendix.Alph1}\protected@file@percent }
|
||||
\@writefile{lof}{\addvspace {10\p@ }}
|
||||
\@writefile{lot}{\addvspace {10\p@ }}
|
||||
\@writefile{toc}{\contentsline {chapter}{\numberline {C}Mapa de rutas relevantes}{32}{appendix.Alph3}\protected@file@percent }
|
||||
\@writefile{toc}{\contentsline {chapter}{\numberline {B}Checklist de release}{34}{appendix.Alph2}\protected@file@percent }
|
||||
\@writefile{lof}{\addvspace {10\p@ }}
|
||||
\@writefile{lot}{\addvspace {10\p@ }}
|
||||
\gdef \@abspage@last{33}
|
||||
\@writefile{toc}{\contentsline {chapter}{\numberline {C}Mapa de rutas relevantes}{35}{appendix.Alph3}\protected@file@percent }
|
||||
\@writefile{lof}{\addvspace {10\p@ }}
|
||||
\@writefile{lot}{\addvspace {10\p@ }}
|
||||
\gdef \@abspage@last{36}
|
||||
|
||||
@@ -66,8 +66,19 @@
|
||||
\BOOKMARK [0][-]{chapter.11}{\376\377\000C\000r\000o\000n\000o\000g\000r\000a\000m\000a\000\040\000y\000\040\000c\000r\000i\000t\000e\000r\000i\000o\000s\000\040\000d\000e\000\040\000a\000c\000e\000p\000t\000a\000c\000i\000\363\000n}{}% 66
|
||||
\BOOKMARK [1][-]{section.11.1}{\376\377\000F\000a\000s\000e\000s\000\040\000s\000u\000g\000e\000r\000i\000d\000a\000s}{chapter.11}% 67
|
||||
\BOOKMARK [1][-]{section.11.2}{\376\377\000D\000e\000f\000i\000n\000i\000t\000i\000o\000n\000\040\000o\000f\000\040\000D\000o\000n\000e\000\040\000p\000o\000r\000\040\000r\000e\000l\000e\000a\000s\000e}{chapter.11}% 68
|
||||
\BOOKMARK [1][-]{section.11.3}{\376\377\000P\000l\000a\000n\000t\000i\000l\000l\000a\000\040\000d\000e\000\040\000i\000n\000f\000o\000r\000m\000e\000\040\000d\000e\000\040\000e\000j\000e\000c\000u\000c\000i\000\363\000n}{chapter.11}% 69
|
||||
\BOOKMARK [1][-]{section.11.4}{\376\377\000R\000e\000s\000p\000o\000n\000s\000a\000b\000l\000e\000s\000\040\000\050\000r\000o\000l\000e\000s\000\051}{chapter.11}% 70
|
||||
\BOOKMARK [0][-]{appendix.Alph1}{\376\377\000G\000l\000o\000s\000a\000r\000i\000o}{}% 71
|
||||
\BOOKMARK [0][-]{appendix.Alph2}{\376\377\000C\000h\000e\000c\000k\000l\000i\000s\000t\000\040\000d\000e\000\040\000r\000e\000l\000e\000a\000s\000e}{}% 72
|
||||
\BOOKMARK [0][-]{appendix.Alph3}{\376\377\000M\000a\000p\000a\000\040\000d\000e\000\040\000r\000u\000t\000a\000s\000\040\000r\000e\000l\000e\000v\000a\000n\000t\000e\000s}{}% 73
|
||||
\BOOKMARK [1][-]{section.11.3}{\376\377\000C\000a\000m\000p\000a\000\361\000a\000\040\000r\000e\000g\000i\000s\000t\000r\000a\000d\000a}{chapter.11}% 69
|
||||
\BOOKMARK [1][-]{section.11.4}{\376\377\000P\000l\000a\000n\000t\000i\000l\000l\000a\000\040\000d\000e\000\040\000i\000n\000f\000o\000r\000m\000e\000\040\000d\000e\000\040\000e\000j\000e\000c\000u\000c\000i\000\363\000n}{chapter.11}% 70
|
||||
\BOOKMARK [1][-]{section.11.5}{\376\377\000R\000e\000s\000p\000o\000n\000s\000a\000b\000l\000e\000s\000\040\000\050\000r\000o\000l\000e\000s\000\051}{chapter.11}% 71
|
||||
\BOOKMARK [0][-]{chapter.12}{\376\377\000I\000n\000f\000o\000r\000m\000e\000\040\000d\000e\000\040\000e\000j\000e\000c\000u\000c\000i\000\363\000n\000\040\040\024\000\040\000c\000a\000m\000p\000a\000\361\000a\000\040\0002\0000\0002\0006\000-\0000\0007\000-\0001\0003}{}% 72
|
||||
\BOOKMARK [1][-]{section.12.1}{\376\377\000M\000e\000t\000a\000d\000a\000t\000o\000s}{chapter.12}% 73
|
||||
\BOOKMARK [1][-]{section.12.2}{\376\377\000B\000a\000s\000e\000l\000i\000n\000e\000\040\000d\000e\000l\000\040\000e\000n\000t\000o\000r\000n\000o}{chapter.12}% 74
|
||||
\BOOKMARK [1][-]{section.12.3}{\376\377\000R\000e\000s\000u\000l\000t\000a\000d\000o\000s\000\040\000p\000o\000r\000\040\000c\000a\000s\000o}{chapter.12}% 75
|
||||
\BOOKMARK [2][-]{subsection.12.3.1}{\376\377\000S\000T\000-\000G\000W\000-\0000\0001\000\040\040\024\000\040\000g\000a\000t\000e\000w\000a\000y\000\040\000h\000e\000a\000l\000t\000h}{section.12.3}% 76
|
||||
\BOOKMARK [2][-]{subsection.12.3.2}{\376\377\000S\000T\000-\000B\000O\000T\000-\0000\0001\000\040\040\024\000\040\000h\000e\000a\000r\000t\000b\000e\000a\000t\000s\000\040\000\050\000c\000o\000r\000r\000i\000d\000a\000\040\0001\000\051}{section.12.3}% 77
|
||||
\BOOKMARK [2][-]{subsection.12.3.3}{\376\377\000S\000T\000-\000B\000O\000T\000-\0000\0001\000\040\040\024\000\040\000h\000e\000a\000r\000t\000b\000e\000a\000t\000s\000\040\000\050\000c\000o\000r\000r\000i\000d\000a\000\040\0002\000,\000\040\000r\000e\000i\000n\000t\000e\000n\000t\000o\000\051}{section.12.3}% 78
|
||||
\BOOKMARK [1][-]{section.12.4}{\376\377\000H\000a\000l\000l\000a\000z\000g\000o\000s}{chapter.12}% 79
|
||||
\BOOKMARK [1][-]{section.12.5}{\376\377\000N\000o\000\040\000e\000j\000e\000c\000u\000t\000a\000d\000o\000\040\000e\000n\000\040\000e\000s\000t\000a\000\040\000c\000a\000m\000p\000a\000\361\000a}{chapter.12}% 80
|
||||
\BOOKMARK [1][-]{section.12.6}{\376\377\000F\000o\000l\000l\000o\000w\000-\000u\000p\000s}{chapter.12}% 81
|
||||
\BOOKMARK [0][-]{appendix.Alph1}{\376\377\000G\000l\000o\000s\000a\000r\000i\000o}{}% 82
|
||||
\BOOKMARK [0][-]{appendix.Alph2}{\376\377\000C\000h\000e\000c\000k\000l\000i\000s\000t\000\040\000d\000e\000\040\000r\000e\000l\000e\000a\000s\000e}{}% 83
|
||||
\BOOKMARK [0][-]{appendix.Alph3}{\376\377\000M\000a\000p\000a\000\040\000d\000e\000\040\000r\000u\000t\000a\000s\000\040\000r\000e\000l\000e\000v\000a\000n\000t\000e\000s}{}% 84
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
\midrule
|
||||
1.0.0 & 13 jul 2026 & Versi\'on inicial del plan de pruebas y estr\'es. \\
|
||||
1.1.0 & 13 jul 2026 & Entorno real de ejecuci\'on (host Ubuntu compartido 8\,GiB) y perfiles de carga. \\
|
||||
1.2.0 & 13 jul 2026 & Informe campa\~na k6: smoke, ST-GW-01 PASS, ST-BOT-01 PASS (reintento). \\
|
||||
\bottomrule
|
||||
\end{tabularx}
|
||||
|
||||
@@ -21,7 +22,9 @@ Este documento define la estrategia y la matriz de pruebas del monorepo \textbf{
|
||||
|
||||
Hoy el repositorio cuenta principalmente con pruebas unitarias aisladas; no existe pipeline CI, ni suites de integraci\'on/E2E automatizadas. Este plan cierra ese gap con identificadores trazables (\texttt{UT-*}, \texttt{IT-*}, \texttt{E2E-*}, \texttt{ST-*}, \texttt{CH-*}), SLOs medibles y scripts \textbf{k6} de referencia en \texttt{docs/plan\_pruebas/scripts/}.
|
||||
|
||||
\textbf{Importante:} las pruebas de integraci\'on, humo y estr\'es documentadas contra el stack vivo se ejecutan (o se ejecutar\'an) en el host descrito en la secci\'on~\ref{sec:host-pruebas} (laptop Ubuntu 24.04, 8\,GiB RAM, Docker compartido con MLflow, Airflow, n8n, etc.). Los resultados deben interpretarse en ese contexto: un OOM o p95 alto puede deberse a contenci\'on del host, no solo a IPSExtractor.
|
||||
\textbf{Importante:} las pruebas de integraci\'on, humo y estr\'es documentadas contra el stack vivo se ejecutan en el host descrito en la secci\'on~\ref{sec:host-pruebas} (laptop Ubuntu 24.04, 8\,GiB RAM, Docker compartido con MLflow, Airflow, n8n, etc.). Los resultados deben interpretarse en ese contexto: un OOM o p95 alto puede deberse a contenci\'on del host, no solo a IPSExtractor.
|
||||
|
||||
\textbf{Estado campa\~na 2026-07-13:} smoke PASS; ST-GW-01 PASS; ST-BOT-01 PASS en reintento (detalle en cap.~\ref{ch:informe-2026-07-13}).
|
||||
|
||||
\tableofcontents
|
||||
\clearpage
|
||||
@@ -37,6 +40,7 @@ Hoy el repositorio cuenta principalmente con pruebas unitarias aisladas; no exis
|
||||
\input{sections/09_datos_entornos}
|
||||
\input{sections/10_herramientas_ci}
|
||||
\input{sections/11_cronograma_aceptacion}
|
||||
\input{sections/12_informe_2026_07_13}
|
||||
|
||||
\appendix
|
||||
\chapter{Glosario}
|
||||
|
||||
@@ -67,8 +67,19 @@
|
||||
\contentsline {chapter}{\numberline {11}Cronograma y criterios de aceptaci\'on}{28}{chapter.11}%
|
||||
\contentsline {section}{\numberline {11.1}Fases sugeridas}{28}{section.11.1}%
|
||||
\contentsline {section}{\numberline {11.2}Definition of Done por release}{28}{section.11.2}%
|
||||
\contentsline {section}{\numberline {11.3}Plantilla de informe de ejecuci\'on}{28}{section.11.3}%
|
||||
\contentsline {section}{\numberline {11.4}Responsables (roles)}{29}{section.11.4}%
|
||||
\contentsline {chapter}{\numberline {A}Glosario}{30}{appendix.Alph1}%
|
||||
\contentsline {chapter}{\numberline {B}Checklist de release}{31}{appendix.Alph2}%
|
||||
\contentsline {chapter}{\numberline {C}Mapa de rutas relevantes}{32}{appendix.Alph3}%
|
||||
\contentsline {section}{\numberline {11.3}Campa\~na registrada}{28}{section.11.3}%
|
||||
\contentsline {section}{\numberline {11.4}Plantilla de informe de ejecuci\'on}{29}{section.11.4}%
|
||||
\contentsline {section}{\numberline {11.5}Responsables (roles)}{29}{section.11.5}%
|
||||
\contentsline {chapter}{\numberline {12}Informe de ejecuci\'on --- campa\~na 2026-07-13}{30}{chapter.12}%
|
||||
\contentsline {section}{\numberline {12.1}Metadatos}{30}{section.12.1}%
|
||||
\contentsline {section}{\numberline {12.2}Baseline del entorno}{30}{section.12.2}%
|
||||
\contentsline {section}{\numberline {12.3}Resultados por caso}{31}{section.12.3}%
|
||||
\contentsline {subsection}{\numberline {12.3.1}ST-GW-01 --- gateway health}{31}{subsection.12.3.1}%
|
||||
\contentsline {subsection}{\numberline {12.3.2}ST-BOT-01 --- heartbeats (corrida 1)}{31}{subsection.12.3.2}%
|
||||
\contentsline {subsection}{\numberline {12.3.3}ST-BOT-01 --- heartbeats (corrida 2, reintento)}{31}{subsection.12.3.3}%
|
||||
\contentsline {section}{\numberline {12.4}Hallazgos}{31}{section.12.4}%
|
||||
\contentsline {section}{\numberline {12.5}No ejecutado en esta campa\~na}{32}{section.12.5}%
|
||||
\contentsline {section}{\numberline {12.6}Follow-ups}{32}{section.12.6}%
|
||||
\contentsline {chapter}{\numberline {A}Glosario}{33}{appendix.Alph1}%
|
||||
\contentsline {chapter}{\numberline {B}Checklist de release}{34}{appendix.Alph2}%
|
||||
\contentsline {chapter}{\numberline {C}Mapa de rutas relevantes}{35}{appendix.Alph3}%
|
||||
|
||||
@@ -83,7 +83,7 @@
|
||||
pdfsubject={Pruebas unitarias, integracion, E2E, estres y resiliencia}
|
||||
}
|
||||
|
||||
\newcommand{\docversion}{1.1.0}
|
||||
\newcommand{\docversion}{1.2.0}
|
||||
\newcommand{\docdate}{13 de julio de 2026}
|
||||
\newcommand{\statusbadge}[1]{\texttt{#1}}
|
||||
|
||||
|
||||
@@ -44,6 +44,14 @@ curl -s -w '\ntime=%{time_total}s\n' http://127.0.0.1:8189/health/live
|
||||
k6 run -e PROFILE=shared8g -e BASE_URL=http://127.0.0.1:8189 k6_gateway_health.js
|
||||
k6 run -e PROFILE=shared8g -e BOT_API_TOKEN=dev-token-change-me k6_bot_heartbeats.js
|
||||
k6 run -e PROFILE=shared8g -e DOWNLOAD_JOB_ID=<id> k6_bot_events.js
|
||||
k6 run -e PROFILE=shared8g k6_smoke_suite.js
|
||||
# Solo si free -h muestra ≥3GiB disponibles:
|
||||
k6 run -e PROFILE=shared8g k6_count_batches.js
|
||||
|
||||
# Latencia count-api con PDFs reales:
|
||||
bash docs/plan_pruebas/scripts/measure_count_latency.sh
|
||||
```
|
||||
|
||||
Modelo de capacidad 100k/mes: [`estimate_capacity.md`](estimate_capacity.md). Informe PDF: [`../InformePruebas_IPSExtractor.pdf`](../InformePruebas_IPSExtractor.pdf).
|
||||
|
||||
PDFs de prueba: [`../data/`](../data/) (`500000284335937.pdf`, `...940.pdf`, `...954.pdf`).
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
# Modelo de capacidad — 100.000 facturas/mes
|
||||
|
||||
No sustituye una corrida real de 100k PDFs. Usa supuestos conservadores + evidencia k6 del 2026-07-13.
|
||||
|
||||
## Objetivo
|
||||
|
||||
| Escenario | Facturas/hora necesarias |
|
||||
|-----------|--------------------------|
|
||||
| 100k/mes 24×7 | ≈ 139/h |
|
||||
| 100k/mes jornada 8h×22d | ≈ 570/h |
|
||||
|
||||
## Estimaciones por etapa (host compartido 8 GiB)
|
||||
|
||||
| Etapa | facturas/h estimadas | vs 139/h | vs 570/h |
|
||||
|-------|----------------------|----------|----------|
|
||||
| Gateway (orquestación) | >>10 000 (health ~57 rps) | OK | OK |
|
||||
| count-api OCR | 20–80 (CPU/RAM contención) | RIESGO | NO |
|
||||
| extraction LLM | 60–200 (si cuota LLM; 30–60s/PDF, pocos workers) | ÁMBAR | NO |
|
||||
| bot Selenium (1 agent) | 30–120 | RIESGO | NO |
|
||||
| cleaning CSV | 500–2000 | OK | ÁMBAR |
|
||||
| Pipeline completo (este host) | <50 | NO | NO |
|
||||
|
||||
## Escenario dedicado (condicional)
|
||||
|
||||
Con host ≥16–32 GiB solo IPS, OCR pool, 2+ workers extraction, cuota LLM, N agents bot: pipeline completo puede acercarse a 139–570/h según dimensionamiento.
|
||||
@@ -0,0 +1,41 @@
|
||||
import http from 'k6/http';
|
||||
import { check, sleep } from 'k6';
|
||||
|
||||
/**
|
||||
* Suite de humo ligero del gateway (PROFILE=shared8g).
|
||||
*
|
||||
* k6 run -e BASE_URL=http://127.0.0.1:8189 -e PROFILE=shared8g k6_smoke_suite.js
|
||||
*/
|
||||
const PROFILE = __ENV.PROFILE || 'shared8g';
|
||||
const BASE_URL = __ENV.BASE_URL || 'http://127.0.0.1:8189';
|
||||
|
||||
export const options = {
|
||||
vus: PROFILE === 'lab' ? 5 : 2,
|
||||
duration: PROFILE === 'lab' ? '60s' : '30s',
|
||||
thresholds: {
|
||||
http_req_failed: ['rate<0.05'],
|
||||
http_req_duration: ['p(95)<2000'],
|
||||
checks: ['rate>0.9'],
|
||||
},
|
||||
tags: { profile: PROFILE, suite: 'smoke' },
|
||||
};
|
||||
|
||||
export default function () {
|
||||
const live = http.get(`${BASE_URL}/health/live`);
|
||||
check(live, {
|
||||
'live 200': (r) => r.status === 200,
|
||||
});
|
||||
|
||||
const health = http.get(`${BASE_URL}/health`);
|
||||
check(health, {
|
||||
'health 200': (r) => r.status === 200,
|
||||
'health body ok': (r) => String(r.body).includes('"ok"'),
|
||||
});
|
||||
|
||||
const config = http.get(`${BASE_URL}/config`);
|
||||
check(config, {
|
||||
'config not 5xx': (r) => r.status < 500,
|
||||
});
|
||||
|
||||
sleep(0.5);
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
# Mide latencia HTTP de count-api vía gateway para cada PDF en docs/plan_pruebas/data/
|
||||
param(
|
||||
[string]$BaseUrl = "http://127.0.0.1:8189",
|
||||
[string]$DataDir = "",
|
||||
[string]$OutCsv = ""
|
||||
)
|
||||
|
||||
$Root = Resolve-Path (Join-Path $PSScriptRoot "..\..\..")
|
||||
if (-not $DataDir) { $DataDir = Join-Path $Root "docs\plan_pruebas\data" }
|
||||
if (-not $OutCsv) {
|
||||
$stamp = Get-Date -Format "yyyyMMdd"
|
||||
$OutCsv = Join-Path $Root "docs\plan_pruebas\informes\count_latency_$stamp.csv"
|
||||
}
|
||||
|
||||
if (-not (Test-Path $DataDir)) { throw "No existe DataDir=$DataDir" }
|
||||
$pdfs = Get-ChildItem -Path $DataDir -Filter *.pdf -File
|
||||
if ($pdfs.Count -eq 0) { throw "No hay PDFs en $DataDir" }
|
||||
|
||||
New-Item -ItemType Directory -Force -Path (Split-Path $OutCsv) | Out-Null
|
||||
"file,http_time_s,http_code,service_count,status" | Set-Content -Path $OutCsv -Encoding utf8
|
||||
Write-Host "BASE_URL=$BaseUrl"
|
||||
Write-Host "DATA_DIR=$DataDir"
|
||||
Write-Host "OUT=$OutCsv"
|
||||
|
||||
foreach ($pdf in $pdfs) {
|
||||
$tmp = [System.IO.Path]::GetTempFileName()
|
||||
$sw = [System.Diagnostics.Stopwatch]::StartNew()
|
||||
try {
|
||||
$resp = curl.exe -sS -o $tmp -w "%{http_code}" `
|
||||
-F "files=@$($pdf.FullName);type=application/pdf" `
|
||||
"$BaseUrl/count/v1/count"
|
||||
$code = $resp.Trim()
|
||||
} catch {
|
||||
$code = "000"
|
||||
}
|
||||
$sw.Stop()
|
||||
$secs = [math]::Round($sw.Elapsed.TotalSeconds, 3)
|
||||
$scount = ""
|
||||
$status = "parse_error"
|
||||
try {
|
||||
$json = Get-Content $tmp -Raw | ConvertFrom-Json
|
||||
if ($json.results -and $json.results.Count -gt 0) {
|
||||
$scount = $json.results[0].service_count
|
||||
$status = $json.results[0].status
|
||||
} elseif ($json.error) {
|
||||
$status = $json.error
|
||||
}
|
||||
} catch {}
|
||||
$line = "$($pdf.Name),$secs,$code,$scount,$status"
|
||||
Add-Content -Path $OutCsv -Value $line
|
||||
Write-Host $line
|
||||
Remove-Item $tmp -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
Write-Host "Done. CSV: $OutCsv"
|
||||
@@ -0,0 +1,46 @@
|
||||
#!/usr/bin/env bash
|
||||
# Mide latencia HTTP de count-api vía gateway para cada PDF en docs/plan_pruebas/data/
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/../../.." && pwd)"
|
||||
DATA_DIR="${COUNT_TEST_PDF_DIR:-$ROOT/docs/plan_pruebas/data}"
|
||||
BASE_URL="${BASE_URL:-http://127.0.0.1:8189}"
|
||||
OUT="${COUNT_LATENCY_CSV:-$ROOT/docs/plan_pruebas/informes/count_latency_$(date +%Y%m%d).csv}"
|
||||
|
||||
if [[ ! -d "$DATA_DIR" ]]; then
|
||||
echo "No existe DATA_DIR=$DATA_DIR" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
shopt -s nullglob
|
||||
pdfs=("$DATA_DIR"/*.pdf "$DATA_DIR"/*.PDF)
|
||||
if [[ ${#pdfs[@]} -eq 0 ]]; then
|
||||
echo "No hay PDFs en $DATA_DIR" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$(dirname "$OUT")"
|
||||
echo "file,http_time_s,http_code,service_count,status" >"$OUT"
|
||||
echo "BASE_URL=$BASE_URL"
|
||||
echo "DATA_DIR=$DATA_DIR"
|
||||
echo "Writing $OUT"
|
||||
echo
|
||||
|
||||
for pdf in "${pdfs[@]}"; do
|
||||
name="$(basename "$pdf")"
|
||||
tmp="$(mktemp)"
|
||||
# body to file; time_total and http_code on stderr via -w
|
||||
code_time="$(curl -sS -o "$tmp" -w '%{http_code} %{time_total}' \
|
||||
-F "files=@${pdf};type=application/pdf" \
|
||||
"${BASE_URL}/count/v1/count" || echo "000 0")"
|
||||
code="$(echo "$code_time" | awk '{print $1}')"
|
||||
secs="$(echo "$code_time" | awk '{print $2}')"
|
||||
# crude parse
|
||||
scount="$(python3 -c "import json,sys; d=json.load(open(sys.argv[1])); print(d['results'][0].get('service_count','') if d.get('results') else '')" "$tmp" 2>/dev/null || echo "")"
|
||||
status="$(python3 -c "import json,sys; d=json.load(open(sys.argv[1])); print(d['results'][0].get('status','') if d.get('results') else d.get('error','err'))" "$tmp" 2>/dev/null || echo "parse_error")"
|
||||
echo "$name,$secs,$code,$scount,$status" | tee -a "$OUT"
|
||||
rm -f "$tmp"
|
||||
done
|
||||
|
||||
echo
|
||||
echo "Done. CSV: $OUT"
|
||||
@@ -23,10 +23,13 @@ Un release es aceptable cuando:
|
||||
\begin{enumerate}
|
||||
\item Checklist del ap\'endice ``Checklist de release'' completa.
|
||||
\item Ning\'un caso \texttt{E2E-01}--\texttt{E2E-05} fallido sin waiver documentado.
|
||||
\item ST-GW-01 y ST-BOT-01 dentro de SLO en staging.
|
||||
\item ST-GW-01 y ST-BOT-01 dentro de SLO en staging (ver informe m\'as reciente, p.\ ej.\ cap.~\ref{ch:informe-2026-07-13}).
|
||||
\item Hallazgos P0/P1 de resiliencia cerrados o mitigados.
|
||||
\end{enumerate}
|
||||
|
||||
\section{Campa\~na registrada}
|
||||
La primera campa\~na k6 en el host compartido est\'a documentada en el cap\'itulo~\ref{ch:informe-2026-07-13} y en \texttt{docs/plan\_pruebas/informes/2026-07-13-shared8g.md}.
|
||||
|
||||
\section{Plantilla de informe de ejecuci\'on}
|
||||
\begin{lstlisting}[language={},caption={Campos m\'inimos del informe}]
|
||||
Fecha / entorno / commit
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
\chapter{Informe de ejecuci\'on --- campa\~na 2026-07-13}
|
||||
\label{ch:informe-2026-07-13}
|
||||
|
||||
\section{Metadatos}
|
||||
\begin{table}[ht]
|
||||
\centering
|
||||
\begin{tabular}{@{}lp{9cm}@{}}
|
||||
\toprule
|
||||
\textbf{Campo} & \textbf{Valor} \\
|
||||
\midrule
|
||||
Fecha & 13 de julio de 2026 \\
|
||||
Host & \texttt{ubuntu} (ASUS X441UAK), Ubuntu 24.04.4 LTS \\
|
||||
CPU / RAM & Intel i5-7200U; 7{,}6\,GiB (Docker limit $\approx$ 7{,}64\,GiB) \\
|
||||
Perfil k6 & \texttt{shared8g} \\
|
||||
Gateway & \texttt{http://127.0.0.1:8189} \\
|
||||
Herramienta & k6 (Grafana), Docker 29.1.3 \\
|
||||
Rama / repo & \texttt{main} --- IPSExtractor \\
|
||||
Ejecutor & erik (host staging compartido) \\
|
||||
\bottomrule
|
||||
\end{tabular}
|
||||
\caption{Metadatos de la campa\~na.}
|
||||
\end{table}
|
||||
|
||||
\section{Baseline del entorno}
|
||||
Medici\'on previa a las cargas:
|
||||
|
||||
\begin{itemize}
|
||||
\item Memoria: total 7{,}6\,GiB; usados $\approx$4{,}4\,GiB; disponibles $\approx$3{,}3\,GiB; \textbf{swap 2{,}1\,GiB en uso}.
|
||||
\item Contenedores IPS: todos \texttt{Up} / healthy (api, bot-descarga, extraction, workers, count-api, cleaning, db, redis) --- $\sim$17\,h uptime.
|
||||
\item Vecino dominante: \texttt{mlflow-server} $\approx$1{,}84\,GiB; tambi\'en n8n, MinIO, postgres compartido, etc.
|
||||
\item \texttt{GET /health/live}: \texttt{ok}, $\approx$11\,ms.
|
||||
\item \texttt{GET /health}: \texttt{ok} (extraction, cleaning, counting, bot\_descarga); $\approx$194\,ms.
|
||||
\end{itemize}
|
||||
|
||||
\section{Resultados por caso}
|
||||
\begin{table}[ht]
|
||||
\centering
|
||||
\begin{tabular}{@{}llp{6.2cm}@{}}
|
||||
\toprule
|
||||
\textbf{ID} & \textbf{Resultado} & \textbf{Detalle} \\
|
||||
\midrule
|
||||
Smoke health & \textbf{PASS} & Live + agregado OK; servicios healthy \\
|
||||
ST-GW-01 & \textbf{PASS} & 6884 req; fail 0\,\%; p95 $=$ 40{,}85\,ms (SLO $<$500\,ms) \\
|
||||
ST-BOT-01 (1) & \textbf{PARCIAL} & 100\,\% HTTP 200; p95 $=$ 2{,}28\,s (SLO $<$800\,ms) \textbf{FAIL} latencia; max 23\,s \\
|
||||
ST-BOT-01 (2) & \textbf{PASS} & 100\,\% HTTP 200; p95 $=$ 85{,}55\,ms; max 98\,ms \\
|
||||
\bottomrule
|
||||
\end{tabular}
|
||||
\caption{Resumen de la campa\~na 2026-07-13.}
|
||||
\label{tab:campana-2026-07-13}
|
||||
\end{table}
|
||||
|
||||
\subsection{ST-GW-01 --- gateway health}
|
||||
\begin{itemize}
|
||||
\item Script: \texttt{k6\_gateway\_health.js}, \texttt{PROFILE=shared8g}.
|
||||
\item Duraci\'on: $\approx$2\,min; hasta 25 VUs.
|
||||
\item Checks: 13768/13768 (100\,\%).
|
||||
\item \texttt{http\_req\_failed}: 0\,\%; p95 $=$ 40{,}85\,ms; avg $=$ 12{,}55\,ms.
|
||||
\item Thresholds: todos en verde.
|
||||
\end{itemize}
|
||||
|
||||
\subsection{ST-BOT-01 --- heartbeats (corrida 1)}
|
||||
\begin{itemize}
|
||||
\item Script: \texttt{k6\_bot\_heartbeats.js}, 5 VUs, 2\,min, token \texttt{dev-token-change-me}.
|
||||
\item Checks: 75/75 (100\,\%) --- todos los heartbeats HTTP 200.
|
||||
\item Latencia: avg 511\,ms; med 57\,ms; p95 2{,}28\,s; max 23{,}12\,s.
|
||||
\item Threshold \texttt{p(95)<800}: \textbf{cruzado} (k6 exit con error de thresholds).
|
||||
\item Interpretaci\'on: API correcta; picos atribuibles a contenci\'on del host (swap + MLflow/HDD), no a error de negocio.
|
||||
\end{itemize}
|
||||
|
||||
\subsection{ST-BOT-01 --- heartbeats (corrida 2, reintento)}
|
||||
\begin{itemize}
|
||||
\item Mismo script y perfil, tras aliviar presi\'on del host.
|
||||
\item Checks: 75/75 (100\,\%).
|
||||
\item Latencia: avg 57\,ms; med 56\,ms; p95 85{,}55\,ms; max 98\,ms.
|
||||
\item Thresholds: todos en verde.
|
||||
\item Conclusi\'on: el FAIL de latencia de la corrida 1 \textbf{no se reproduce} con menos contenci\'on; confirma causa ambiental.
|
||||
\end{itemize}
|
||||
|
||||
\section{Hallazgos}
|
||||
\begin{enumerate}
|
||||
\item IPSExtractor (gateway + bot-descarga heartbeat) cumple SLO funcional y de latencia del perfil \texttt{shared8g} cuando el host no est\'a saturado.
|
||||
\item Con swap activo ($\sim$2\,GiB) y MLflow $\sim$1{,}8\,GiB, pueden aparecer outliers de varios segundos en endpoints ligeros; deben reportarse como riesgo de \emph{plataforma compartida}, no como regresi\'on de c\'odigo sin evidencia adicional.
|
||||
\item El health agregado es usable ($\sim$200\,ms en esta sesi\'on); no usarlo como SLO de liveness (usar \texttt{/health/live}).
|
||||
\end{enumerate}
|
||||
|
||||
\section{No ejecutado en esta campa\~na}
|
||||
ST-BOT-03 (events), ST-COUNT-01, ST-EXT-*, E2E Imaging, chaos. Motivo: swap/contenci\'on residual; se aplazan a una ventana con $\geq$3\,GiB libres o MLflow/Airflow detenidos.
|
||||
|
||||
\section{Follow-ups}
|
||||
\begin{itemize}
|
||||
\item Repetir ST-BOT-01 peri\'odicamente; si p95 $>$800\,ms, adjuntar \texttt{free -h} y \texttt{docker stats}.
|
||||
\item Ventana dedicada (parar MLflow) antes de ST-COUNT-01.
|
||||
\item Opcional: excluir requests de \texttt{setup()} del threshold de latencia en k6 (registros de workers) para no mezclar cold-start con steady-state.
|
||||
\end{itemize}
|
||||
@@ -14,6 +14,12 @@ type PDFResult struct {
|
||||
Status string `json:"status"`
|
||||
Services []ServiceRow `json:"services,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
// Pipeline stage timings in milliseconds (populated by Processor).
|
||||
RenderMs int64 `json:"render_ms,omitempty"`
|
||||
OCRMs int64 `json:"ocr_ms,omitempty"`
|
||||
ClusterMs int64 `json:"cluster_ms,omitempty"`
|
||||
InferMs int64 `json:"infer_ms,omitempty"`
|
||||
TotalMs int64 `json:"total_ms,omitempty"`
|
||||
}
|
||||
|
||||
// CountResponse wraps the results of a synchronous batch request to POST /v1/count.
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
package pipeline
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestClusterRowsTwoBands(t *testing.T) {
|
||||
boxes := []indexedBox{
|
||||
{Text: "a", X: 0, Y: 100},
|
||||
{Text: "b", X: 10, Y: 102},
|
||||
{Text: "c", X: 0, Y: 300},
|
||||
{Text: "d", X: 10, Y: 301},
|
||||
}
|
||||
ClusterRows(boxes, 10, 1)
|
||||
if boxes[0].Cluster < 0 || boxes[1].Cluster < 0 {
|
||||
t.Fatal("first band should be clustered")
|
||||
}
|
||||
if boxes[0].Cluster != boxes[1].Cluster {
|
||||
t.Fatalf("same band: got %d and %d", boxes[0].Cluster, boxes[1].Cluster)
|
||||
}
|
||||
if boxes[2].Cluster != boxes[3].Cluster {
|
||||
t.Fatalf("second band: got %d and %d", boxes[2].Cluster, boxes[3].Cluster)
|
||||
}
|
||||
if boxes[0].Cluster == boxes[2].Cluster {
|
||||
t.Fatal("distant Y bands should differ")
|
||||
}
|
||||
}
|
||||
|
||||
func TestClusterRowsSameBand(t *testing.T) {
|
||||
boxes := []indexedBox{
|
||||
{Text: "a", Y: 50},
|
||||
{Text: "b", Y: 52},
|
||||
{Text: "c", Y: 54},
|
||||
}
|
||||
ClusterRows(boxes, 10, 1)
|
||||
id := boxes[0].Cluster
|
||||
for i, b := range boxes {
|
||||
if b.Cluster != id {
|
||||
t.Fatalf("box %d cluster=%d, want %d", i, b.Cluster, id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestClusterRowsEmpty(t *testing.T) {
|
||||
ClusterRows(nil, 10, 1) // no panic
|
||||
ClusterRows([]indexedBox{}, 10, 1)
|
||||
}
|
||||
|
||||
func TestClusterRowsNoiseMinSamples(t *testing.T) {
|
||||
boxes := []indexedBox{
|
||||
{Text: "lonely", Y: 10},
|
||||
{Text: "pair-a", Y: 200},
|
||||
{Text: "pair-b", Y: 201},
|
||||
}
|
||||
// minSamples=2: lonely point stays noise (-1); pair forms a cluster
|
||||
ClusterRows(boxes, 5, 2)
|
||||
if boxes[0].Cluster != -1 {
|
||||
t.Fatalf("lonely cluster=%d, want -1 (noise)", boxes[0].Cluster)
|
||||
}
|
||||
if boxes[1].Cluster < 0 || boxes[1].Cluster != boxes[2].Cluster {
|
||||
t.Fatalf("pair clusters=%d,%d", boxes[1].Cluster, boxes[2].Cluster)
|
||||
}
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
package pipeline
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestExtractRowFeaturesHeaderKeyword(t *testing.T) {
|
||||
group := []indexedBox{
|
||||
{Text: "FACTURA", X: 10, Y: 50, Cluster: 0},
|
||||
{Text: "NIT", X: 100, Y: 50, Cluster: 0},
|
||||
{Text: "123", X: 200, Y: 50, Cluster: 0},
|
||||
}
|
||||
row := extractRowFeatures(group, 1000, 2000)
|
||||
if row.Features[3] != 1 {
|
||||
t.Fatalf("has_header_keyword = %v, want 1", row.Features[3])
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractRowFeaturesServiceCode(t *testing.T) {
|
||||
group := []indexedBox{
|
||||
{Text: "AB12", X: 10, Y: 100, Cluster: 0},
|
||||
{Text: "CONSULTA", X: 80, Y: 100, Cluster: 0},
|
||||
}
|
||||
row := extractRowFeatures(group, 1000, 2000)
|
||||
if row.Features[1] != 1 {
|
||||
t.Fatalf("first_token_is_service_code = %v, want 1", row.Features[1])
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildFeatureRowsEmpty(t *testing.T) {
|
||||
rows := buildFeatureRows(nil, 1000, 2000)
|
||||
if len(rows) != 0 {
|
||||
t.Fatalf("rows = %d, want 0", len(rows))
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractRowFeaturesNoTablePattern(t *testing.T) {
|
||||
group := []indexedBox{
|
||||
{Text: "TEXTO", X: 10, Y: 100, Cluster: 0},
|
||||
{Text: "LIBRE", X: 80, Y: 100, Cluster: 0},
|
||||
}
|
||||
row := extractRowFeatures(group, 1000, 2000)
|
||||
if row.Features[2] != 0 {
|
||||
t.Fatalf("has_service_table_pattern = %v, want 0", row.Features[2])
|
||||
}
|
||||
}
|
||||
@@ -32,28 +32,31 @@ func NewProcessor(predictor *model.Predictor, ocrEngine *ocr.Engine, opts Option
|
||||
func (p *Processor) ProcessPDF(filename string, pdfData []byte) domain.PDFResult {
|
||||
start := time.Now()
|
||||
result := domain.PDFResult{Filename: filename, Status: domain.StatusOK}
|
||||
var renderDur, ocrDur, clusterDur, inferDur time.Duration
|
||||
|
||||
tRender := time.Now()
|
||||
img, pageW, pageH, err := ocr.RenderPage(pdfData, p.opts.DPI)
|
||||
renderDur := time.Since(tRender)
|
||||
renderDur = time.Since(tRender)
|
||||
if err != nil {
|
||||
result.Status = domain.StatusError
|
||||
result.Error = err.Error()
|
||||
p.finishTiming(&result, renderDur, 0, 0, 0, time.Since(start))
|
||||
return result
|
||||
}
|
||||
|
||||
tOCR := time.Now()
|
||||
boxes, err := p.ocrEngine.OCRImage(img)
|
||||
ocrDur := time.Since(tOCR)
|
||||
ocrDur = time.Since(tOCR)
|
||||
if err != nil {
|
||||
result.Status = domain.StatusError
|
||||
result.Error = err.Error()
|
||||
p.finishTiming(&result, renderDur, ocrDur, 0, 0, time.Since(start))
|
||||
return result
|
||||
}
|
||||
if len(boxes) == 0 {
|
||||
result.ServiceCount = 0
|
||||
result.Services = []domain.ServiceRow{}
|
||||
p.logTiming(filename, renderDur, ocrDur, 0, 0, time.Since(start))
|
||||
p.finishTiming(&result, renderDur, ocrDur, 0, 0, time.Since(start))
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -65,11 +68,11 @@ func (p *Processor) ProcessPDF(filename string, pdfData []byte) domain.PDFResult
|
||||
ClusterRows(indexed, p.opts.DBSCANEps, p.opts.DBSCANMinSamples)
|
||||
|
||||
rows := buildFeatureRows(indexed, pageW, pageH)
|
||||
clusterDur := time.Since(tCluster)
|
||||
clusterDur = time.Since(tCluster)
|
||||
if len(rows) == 0 {
|
||||
result.ServiceCount = 0
|
||||
result.Services = []domain.ServiceRow{}
|
||||
p.logTiming(filename, renderDur, ocrDur, clusterDur, 0, time.Since(start))
|
||||
p.finishTiming(&result, renderDur, ocrDur, clusterDur, 0, time.Since(start))
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -84,7 +87,7 @@ func (p *Processor) ProcessPDF(filename string, pdfData []byte) domain.PDFResult
|
||||
}
|
||||
result.Services = services
|
||||
result.ServiceCount = len(rows)
|
||||
p.logTiming(filename, renderDur, ocrDur, clusterDur, 0, time.Since(start))
|
||||
p.finishTiming(&result, renderDur, ocrDur, clusterDur, 0, time.Since(start))
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -95,10 +98,11 @@ func (p *Processor) ProcessPDF(filename string, pdfData []byte) domain.PDFResult
|
||||
|
||||
tInfer := time.Now()
|
||||
probs, err := p.predictor.PredictProba(features)
|
||||
inferDur := time.Since(tInfer)
|
||||
inferDur = time.Since(tInfer)
|
||||
if err != nil {
|
||||
result.Status = domain.StatusError
|
||||
result.Error = fmt.Sprintf("inference: %v", err)
|
||||
p.finishTiming(&result, renderDur, ocrDur, clusterDur, inferDur, time.Since(start))
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -128,16 +132,21 @@ func (p *Processor) ProcessPDF(filename string, pdfData []byte) domain.PDFResult
|
||||
|
||||
result.Services = services
|
||||
result.ServiceCount = serviceCount
|
||||
p.logTiming(filename, renderDur, ocrDur, clusterDur, inferDur, time.Since(start))
|
||||
p.finishTiming(&result, renderDur, ocrDur, clusterDur, inferDur, time.Since(start))
|
||||
return result
|
||||
}
|
||||
|
||||
func (p *Processor) logTiming(filename string, render, ocr, cluster, infer, total time.Duration) {
|
||||
func (p *Processor) finishTiming(result *domain.PDFResult, render, ocrD, cluster, infer, total time.Duration) {
|
||||
result.RenderMs = render.Milliseconds()
|
||||
result.OCRMs = ocrD.Milliseconds()
|
||||
result.ClusterMs = cluster.Milliseconds()
|
||||
result.InferMs = infer.Milliseconds()
|
||||
result.TotalMs = total.Milliseconds()
|
||||
if !p.opts.LogPipelineTiming {
|
||||
return
|
||||
}
|
||||
log.Printf(
|
||||
"pipeline filename=%s ocr_render=%s ocr_tesseract=%s cluster=%s infer=%s total=%s",
|
||||
filename, render, ocr, cluster, infer, total,
|
||||
result.Filename, render, ocrD, cluster, infer, total,
|
||||
)
|
||||
}
|
||||
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
package pipeline
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/pys/pdf-count/internal/domain"
|
||||
"github.com/pys/pdf-count/internal/ocr"
|
||||
)
|
||||
|
||||
func testPDFDir(t *testing.T) string {
|
||||
t.Helper()
|
||||
if d := os.Getenv("COUNT_TEST_PDF_DIR"); d != "" {
|
||||
return d
|
||||
}
|
||||
// services/services-count-facturas-medicas-serving → docs/plan_pruebas/data
|
||||
candidates := []string{
|
||||
filepath.Join("..", "..", "..", "..", "docs", "plan_pruebas", "data"),
|
||||
filepath.Join("..", "..", "..", "docs", "plan_pruebas", "data"),
|
||||
}
|
||||
for _, c := range candidates {
|
||||
abs, err := filepath.Abs(c)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if st, err := os.Stat(abs); err == nil && st.IsDir() {
|
||||
return abs
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func listFixturePDFs(t *testing.T, dir string) []string {
|
||||
t.Helper()
|
||||
entries, err := os.ReadDir(dir)
|
||||
if err != nil {
|
||||
t.Fatalf("read pdf dir: %v", err)
|
||||
}
|
||||
var files []string
|
||||
for _, e := range entries {
|
||||
if e.IsDir() {
|
||||
continue
|
||||
}
|
||||
name := e.Name()
|
||||
if strings.EqualFold(filepath.Ext(name), ".pdf") {
|
||||
files = append(files, filepath.Join(dir, name))
|
||||
}
|
||||
}
|
||||
return files
|
||||
}
|
||||
|
||||
func TestProcessPDFTimingFixtures(t *testing.T) {
|
||||
dir := testPDFDir(t)
|
||||
if dir == "" {
|
||||
t.Skip("PDF fixtures not found; set COUNT_TEST_PDF_DIR")
|
||||
}
|
||||
pdfs := listFixturePDFs(t, dir)
|
||||
if len(pdfs) == 0 {
|
||||
t.Skip("no PDF files in " + dir)
|
||||
}
|
||||
|
||||
engine, err := ocr.NewEngine("spa", 1)
|
||||
if err != nil {
|
||||
t.Skipf("tesseract not available: %v", err)
|
||||
}
|
||||
defer engine.Close()
|
||||
|
||||
proc := NewProcessor(nil, engine, Options{
|
||||
DPI: 200, // faster than 300 for timing smoke
|
||||
DBSCANEps: 10,
|
||||
DBSCANMinSamples: 1,
|
||||
LogPipelineTiming: true,
|
||||
})
|
||||
|
||||
t.Logf("pdf_dir=%s files=%d", dir, len(pdfs))
|
||||
t.Logf("%-28s %8s %8s %8s %8s %8s %6s %s",
|
||||
"file", "render", "ocr", "cluster", "infer", "total", "count", "status")
|
||||
|
||||
for _, path := range pdfs {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
t.Fatalf("read %s: %v", path, err)
|
||||
}
|
||||
name := filepath.Base(path)
|
||||
res := proc.ProcessPDF(name, data)
|
||||
t.Logf("%-28s %6dms %6dms %6dms %6dms %6dms %6d %s",
|
||||
name, res.RenderMs, res.OCRMs, res.ClusterMs, res.InferMs, res.TotalMs, res.ServiceCount, res.Status)
|
||||
if res.Status == domain.StatusError {
|
||||
t.Errorf("%s failed: %s", name, res.Error)
|
||||
continue
|
||||
}
|
||||
if res.TotalMs <= 0 {
|
||||
t.Errorf("%s: TotalMs should be > 0", name)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user