Skip to content

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.baseUrlPath fijo (/recursos-materiales o /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.html es HTML estático sin build propio, servido por nginx:alpine montando el archivo — solo enlaza a las dos apps reales. La regla de fallback de la raíz en /etc/cloudflared/config.yml (antes apuntaba a 8501 por default, causa real del 404) ahora apunta a 8503.

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 .env compartido (Layout de Gastos por CECO se queda en .205 a propósito, Cruce XML se movió a .207 real) — 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-sm en ctunlinux (código montado :ro sobre un docker-compose.yml ajeno, 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 .md de 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 .205 temporal a .207 producció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, numeradas CONT-4, CONT-5... Al agregar uno: nueva fila en la tabla de arriba + nuevo .md en reportes/.