streamlit-reportes
Estado: en producción. Dos apps Streamlit independientes, un repo, un
docker-compose.yml, un mismo dominio con path-routing: reportes.frento.com.mx.
Dos apps consumiendo directo de bases en vivo (sin pasar por el
warehouse Postgres trivasa_dw para lo operativo) — split 2026-09-02 por
área de negocio, cada una en su propio contenedor:
| App | Path | Puerto | Contra |
|---|---|---|---|
| Recursos Materiales | /recursos-materiales |
8501 | TRIVASADB (SQL Server, .207, producción) |
| Contabilidad | /contabilidad |
8502 | TRIVASADB3 (.205, no productiva) + TRIVASADB (.207, producción) + Postgres raw_sat |
| Landing (raíz) | / |
8503 | -- (estático, nginx:alpine, sin BD) |
Landing agregado 2026-09-04:
/(sin path) no tenía dueño — cada app corre con--server.baseUrlPathfijo (/recursos-materialeso/contabilidad), así que ninguna respondía en la raíz pelada (404 desde que existe el split, nadie lo había notado hasta que alguien probóreportes.frento.com.mx/directo).landing/index.htmles HTML estático sin build propio, servido pornginx:alpinemontando el archivo — solo enlaza a las dos apps reales. La regla de fallback de la raíz en/etc/cloudflared/config.yml(antes apuntaba a8501por default, causa real del 404) ahora apunta a8503.
Antes del split (hasta 2026-09-02) era un solo app con todo bajo un mismo proceso, sin path — la sección de Contabilidad se agregó primero ahí como página 4, después se separó a su propio contenedor+path el mismo día al ver que necesitaba una segunda fuente de datos (Postgres) que la app de logística no necesita.
Cada app es multipágina nativo: app.py es el home con tarjetas ->
st.switch_page a cada reporte en pages/ (Streamlit arma el sidebar de
navegación solo a partir de ahí).
Infraestructura
| Repo | github.com/ehalso/streamlit-reportes |
| Host | ctunlinux, ruta ~/ehalso/streamlit-reportes |
| Deploy | docker compose up -d --build (levanta los cuatro servicios) |
| Binding de puertos | usuarios-sm (8501), contabilidad (8502) y landing (8503) en 127.0.0.1 — solo alcanzables desde el propio host o vía el túnel. contabilidad-auth (8600, agregado 2026-09-05) en 0.0.0.0 — ver nota abajo |
| Expuesto vía | túnel cloudflared existente en ctunlinux (mismo túnel de metabase/dash/bot), path-routing sobre reportes.frento.com.mx — ver abajo. Contabilidad además accesible por IP de la LAN/NetBird detrás de HTTP Basic Auth (contabilidad-auth, ver nota) |
| Conexión BD — Recursos Materiales | TRIVASADB_207_* (_HOST, _PORT, _DATABASE, _USER, _PASSWORD) en .env local, no trackeado — mismo prefijo que usa varela-bot |
| Conexión BD — Contabilidad | TRIVASADB_205_* (mismo shape, apunta a .205, no productiva) + TRIVASADB_207_* (mismo shape que Recursos Materiales, apunta a .207 producción — usado por CONT-3) + PG_* (_HOST, _PORT, _DATABASE, _USER, _PASSWORD) — mismo .env, compartido entre ambos servicios |
Prefijos renombrados 2026-09-03 (antes
TRIVASADB_*/TRIVASADB_TEST_*, ambiguo): Contabilidad terminó necesitando dos conexiones SQL Server distintas en el mismo.envcompartido (Layout de Gastos por CECO se queda en.205a propósito, Cruce XML se movió a.207real) — los prefijos ahora llevan el número de instancia explícito en el nombre para no repetir la confusión.Antes de agosto 2026 este mismo app vivía como
~/stack/streamlit/usuarios-smenctunlinux(código montado:rosobre undocker-compose.ymlajeno, sin repo propio). Se migró a repo propio el 2026-08-29 — cualquier referencia a la ruta vieja está obsoleta.
contabilidad accesible por LAN/NetBird sin túnel, detrás de un proxy con HTTP Basic Auth (2026-09-05)
Los tres servicios nacieron bindeados a 127.0.0.1:PUERTO:PUERTO —
alcanzables solo desde el propio ctunlinux (localhost puro) o vía
cloudflared, que corre en la misma máquina y por eso sí puede llegar a
127.0.0.1 aunque el tráfico haya entrado por internet: desde el punto de
vista del kernel, cloudflared → 127.0.0.1:8502 es loopback, no importa
que cloudflared mismo haya recibido la conexión desde afuera. Una
máquina externa (LAN física 192.168.117.0/24 o NetBird) que intente
<ip-de-ctunlinux>:8502 directo nunca llega — el socket de Docker jamás
escuchó en esa interfaz.
Primer intento (descartado): publicar contabilidad directo en 0.0.0.0
("127.0.0.1:8502:8502" → "8502:8502") — funcionaba, pero dejaba el
reporte de contabilidad (toca TRIVASADB .207 producción, incluye
GASTO_REGISTRO_NOMINA) alcanzable sin ninguna autenticación desde
cualquier dispositivo con acceso a la LAN/NetBird de Trivasa.
Solución final: contabilidad volvió a "127.0.0.1:8502:8502" (el
túnel de Cloudflare sigue exactamente igual que siempre, sin tocar). Se
agregó un cuarto servicio, contabilidad-auth (nginx:alpine, puerto
8600 publicado en 0.0.0.0), que hace de proxy con auth_basic hacia
http://contabilidad:8502 (nombre de servicio, red interna de
docker-compose — no pasa por el host). Credenciales en
contabilidad-auth/.htpasswd (solo el hash apr1, generado con openssl
passwd -apr1, nunca la contraseña en claro — pedir la contraseña real a
quien administra el proyecto, no vive en este repo). URL de acceso:
http://192.168.117.14:8600/contabilidad (IP LAN de ctunlinux).
⚠️ Gotcha real, encontrado al primer intento: el proxy usaba
proxy_set_header Host $host; — $host en nginx no incluye el
puerto. Streamlit recibía Host: 192.168.117.14 (sin :8600) y
construía su redirect interno (307, agrega la barra final a
/contabilidad) como http://192.168.117.14/contabilidad/ — puerto 80
implícito, nunca llega a nada. Corregido con proxy_set_header Host
$http_host; (preserva el puerto tal como llegó la petición). Cualquier
proxy delante de una app que genere redirects propios (Streamlit,
Django, etc.) necesita $http_host, no $host, o el síntoma es "carga
la página pero el redirect se rompe" — fácil de confundir con un
problema de la app misma.
usuarios-sm/landing no se tocaron, siguen solo en 127.0.0.1, sin
proxy ni auth adicional.
Versión de Streamlit
streamlit==1.63.0 en ambos servicios desde 2026-09-03 (antes 1.39.0,
un downgrade que ningún commit ni doc explicaba). El bump reveló dos bugs
reales que estaban ocultos: width="stretch" (API nueva) no existe en
1.39.0 — TypeError: 'str' object cannot be interpreted as an integer
en CONT-1/CONT-2 al promoverlos —, y openpyxl faltaba en
requirements.txt (enmascarado hasta entonces por el bug anterior, que
fallaba primero en la ejecución del script). Ninguno de los dos se
detectó con curl/chequeos HTTP — Streamlit solo ejecuta el script real
por WebSocket, un 200 de HTTP solo confirma que el shell estático
responde. Se encontraron y confirmaron corriendo streamlit.testing.v1.
AppTest vía docker exec dentro del contenedor real (no en un venv
local aparte con versiones distintas) — esa es ahora la metodología de
validación antes de cualquier deploy que toque una dependencia
compartida, documentada en el skill trivasa-streamlit-reportes.
streamlit-aggrid subió de paso (1.0.5 → 1.2.1.post2: la vieja fija
altair<5, incompatible con streamlit>=1.63). Bump hecho en rama
(bump-streamlit-1.63), imagen real construida y validada con AppTest
en cada página antes de mergear a main. Detalle en
PROGRESS.md de layout-gastos (entrada
2026-09-03 "Bump de Streamlit en producción + venv de exploración
pinneado").
Path-routing en Cloudflare Tunnel
/etc/cloudflared/config.yml en ctunlinux (fuera de este repo, no
versionado ahí). Reglas de path para reportes.frento.com.mx, evaluadas en
orden — antes del catch-all del propio host, y ese antes del catch-all
global:
- hostname: reportes.frento.com.mx
path: ^/recursos-materiales.*
service: http://127.0.0.1:8501
- hostname: reportes.frento.com.mx
path: ^/contabilidad.*
service: http://127.0.0.1:8502
- hostname: reportes.frento.com.mx # fallback, root sin path -> recursos-materiales
service: http://127.0.0.1:8501
- service: http_status:404
Validado con cloudflared tunnel ingress validate y ingress rule
<url> antes de aplicar (las 4 rutas: ambos paths nuevos + root +
metabase.frento.com.mx para confirmar que las reglas viejas no se
rompieron). Cada Dockerfile fija su propio --server.baseUrlPath
(/recursos-materiales o /contabilidad) en el CMD de streamlit run
— necesario para que los assets/links internos de Streamlit funcionen
detrás del path-routing (si no, Streamlit sirve todo como si viviera en
/, y las peticiones internas no matchean ninguna regla de ingress).
Estructura del repo
app.py, db.py, pages/, queries/, Dockerfile -- app Recursos Materiales (8501)
contabilidad/
app.py, db.py, pages/, queries/, Dockerfile -- app Contabilidad (8502)
docker-compose.yml -- ambos servicios, un .env compartido
contabilidad/db.py es copia de db.py — ninguna app importa del otro
directorio (mismo patrón ya documentado: "copiado, no symlink"). db.py
centraliza la conexión dentro de cada app: antes cada página definía su
propio get_engine() idéntico (3 copias, 3 pools de conexión separados en
vez de uno compartido).
Reportes — Recursos Materiales (/recursos-materiales)
| Página | Qué muestra | Detalle |
|---|---|---|
| 1 — Análisis de Solicitud de Material | Totales y evolución mensual de solicitudes por operador (conteo, sin detalle de línea) | reportes/analisis-solicitud-material.md |
| 2 — Transferencias sin recepción | Envíos entre sucursales ya salidos pero sin recepción confirmada en destino | reportes/transferencias-sin-recepcion.md |
| 3 — Reporte de Solicitud de Material | Detalle a nivel línea: solicitado/apartado/existencia/surtido/saldo + requisición | reportes/solicitud-material-detalle.md |
Reportes — Contabilidad (/contabilidad)
Convención de nombre de página desde 2026-09-03:
<orden>_<Nombre_Del_Reporte>_<CLAVE>.py — el label del sidebar (derivado
del nombre de archivo por Streamlit) queda "Nombre Del Reporte CLAVE",
nombre primero y clave (CONT-N) al final.
| Página | Qué muestra | Detalle |
|---|---|---|
| CONT-1 — Layout de Gastos por CECO | Gasto_Registro_Control vs. Poliza_Detalle a nivel centro de costo, 6 orígenes de gasto — diagnóstico de calidad de dato, no el layout final |
reportes/layout-gastos-ceco-cont-1.md |
| CONT-2 — Layout de Gastos por CECO con Nómina | Igual que CONT-1 + GASTO_REGISTRO_NOMINA (7mo origen, emparejado por texto de concepto, no rank-pairing) |
reportes/layout-gastos-ceco-cont-2.md |
| CONT-3 — Cruce XML Recibidos vs Comprobante Digital | Qué XML CFDI recibidos (Postgres raw_sat) no tienen registro ligado en MPRO (Comprobante_Digital, match por UUID) |
reportes/cruce-xml-recibidos-vs-comprobante-digital.md |
CONT-1/CONT-2 reemplazan, el 2026-09-03, al reporte original "Layout de Gastos — Reconciliación CECO" (mismo path de página, rediseño completo del mecanismo de reconciliación — ver el
.mdde CONT-1, sección "Rediseño"). CONT-3 es el reporte de Cruce XML renombrado a la misma numeración, sin cambio de contenido — solo el nombre de archivo/label y la conexión (pasó de.205temporal a.207producción real).
Cada reporte documenta grano, fuente, de dónde sale cada columna, la query
base y los filtros del Streamlit — ver el .md correspondiente. La lógica
de negocio de cada query se desarrolla y valida primero en su propia carpeta
de exploración en trivasa-bi/proyectos-bi/ (link desde cada .md) antes
de pasar a queries/*.sql en este repo.
Pendiente / roadmap
- Más reportes de auditoría contable (
trivasa-bi/proyectos-bi/auditoria-dash) se agregarían como nuevas páginas del app Contabilidad, numeradasCONT-4,CONT-5... Al agregar uno: nueva fila en la tabla de arriba + nuevo.mdenreportes/.