ctunlinux — servidor Docker de Trivasa BI

Ubuntu 26.04, 2 vCPU / 5.2GB RAM (+4GB swap) / 48GB disco. Es la VM que sostiene todo el BI on-premise de Trivasa — Metabase, Lightdash, trivasa_dw en Postgres, los pipelines dlt — además de una instancia de Glances personal de Esteban. El hub Streamlit corrió acá hasta decomisionarse el 2026-08-26. Es la misma VM que documenta Warehouse Postgres en la narrativa de movimiento (“VM de 5.2GB con swap ya al límite” al intentar el backfill completo con sql_table) — la RAM ajustada no es anecdótica, es una restricción real de este host a tener en cuenta para cualquier carga nueva. El disco (48GB) tiene el mismo perfil: el despliegue de Lightdash lo dejó rutinariamente por debajo de 5GB libres (>90% uso) solo por el peso de una imagen Docker — ver el cierre técnico de ese despliegue en sources/trivasa/lightdash-dashboards-demo/despliegue-cierre.md.

Tiene NetBird instalado desde 2026-08-09 (FQDN: ctunlinux.netbird.cloud, IP 100.71.141.100/16) — se agregó a la malla que ya usan las VMs de Conkafecito/GCP/Oracle, vía instalador oficial (pkgs.netbird.io/install.sh, paquete apt, deja netbird.service como servicio systemd enabled+active de fábrica) y netbird up --setup-key <key>. Antes de esa fecha el acceso remoto era solo SSH directo (puerto 22, 0.0.0.0) o los hostnames públicos detrás del túnel de Cloudflare — ambas vías siguen funcionando igual, NetBird es una tercera ruta adicional, no un reemplazo.

Reboot del 2026-08-08 — causa raíz de dos incidentes que parecían separados

uptime -s confirma que el host se reinició el 2026-08-08 01:06:26. Esa fecha/hora no se investigó en su momento, pero conecta dos síntomas que hasta el 2026-08-09 se habían tratado como incidentes sueltos:

  • El directorio de datos de postgres-dw apareció vacío exactamente en ese momento (ver Warehouse Postgres para el detalle de la migración que reparó el daño) — consistente con que, al bootear, Docker haya encontrado el path del bind mount todavía no listo y creado un directorio vacío en su lugar en vez de fallar (comportamiento normal de Docker cuando el host path de un bind mount no existe).
  • Metabase y Superset quedaron Exited y nunca se reiniciaron solos — sus docker-compose.yml no tenían restart: unless-stopped (a diferencia de postgres-dw, Lightdash, dvt-checks y soda-perses/soda-loki, que sí lo tienen y por eso sobrevivieron el reboot sin intervención). Quedaron caídos 44 horas sin que nadie lo notara hasta una revisión de disco el 2026-08-09.

Fix aplicado: restart: unless-stopped agregado al docker-compose.yml de Metabase (ambos servicios) — sobrevive el próximo reboot solo. Superset se decomisionó en la misma revisión (ver tabla de Docker abajo), así que no aplica. Sigue sin confirmarse por qué rebooteó el host en primer lugar (¿mantenimiento del proveedor, kernel update automático, algo distinto?) — pendiente si vuelve a pasar.

Túnel de Cloudflare: un solo cloudflared.service

A diferencia de vm-main (dos túneles, uno por cuenta/zona — ver Túneles de Cloudflare), ctunlinux corre un único túnel para la zona frento.com.mx (dominio de negocio de Trivasa): nombre ctunlinux, id 0bf84666-69f3-4f53-b595-a36ab8956667, config en /etc/cloudflared/config.yml, credenciales/cert en ~/.cloudflared/.

HostnameServicio localQué es
metabase.frento.com.mxlocalhost:3000Metabase (docker)
monitor.frento.com.mx127.0.0.1:61208Glances (systemd, ver abajo)
data.frento.com.mx127.0.0.1:8080Perses — 3 dashboards: dummy de Soda + 2 con datos reales de DVT (ver abajo)
dash.frento.com.mx127.0.0.1:8090Lightdash (docker)
bot.frento.com.mx127.0.0.1:8091varela-bot, bot de Telegram (docker) — documentado el 2026-08-26, corría desde el 2026-08-12 sin mención previa en la wiki
reportes.frento.com.mx127.0.0.1:8501/8502/8503Streamlit reportes, ruteo por path (/recursos-materiales*, /contabilidad*, resto) — hostname ya existía en el ingress, quedó sin documentar aquí hasta ahora
reportesweb.frento.com.mx127.0.0.1:8765Agregado 2026-09-07: API REST mínima (stdlib, sin dependencias) para probar conectividad ad-hoc desde otra máquina/cowork. El CNAME ya existía pero apuntaba a un túnel muerto (error 1033); se sobreescribió con route dns -f

DNS: CNAME por hostname, creados con cloudflared tunnel route dns ctunlinux <hostname> usando el cert.pem ya logueado en el host — no hace falta un token de API de Cloudflare separado para altas nuevas en esta zona.

Gotcha confirmado 2026-09-07: un quick tunnel (cloudflared tunnel --url ..., sin --config) en este host siempre devuelve 404, aunque el servicio local sí responda bien. Con --loglevel debug se ve ingressRule=9 originService=http_status:404 — la petición sí llega al proceso, pero cloudflared de todas formas carga el config.yml de este mismo túnel nombrado (ruta default /etc/cloudflared/config.yml) y, como el hostname aleatorio de trycloudflare.com no matchea ninguna regla de ingress, cae en el catch-all http_status:404. El síntoma engaña (headers server: cloudflare, cf-ray válido, parece un 404 real del borde) — se descartó firewall/DPI de red (handshake TLS/QUIC exitoso, iptables/nft sin reglas de bloqueo). Ya estaba documentado en Claude Code remoto — visualizar outputs (Rich) desde el cel; esta vuelta lo confirma con el log de debug como evidencia adicional. Fix: forzar un config vacío (cloudflared tunnel --config /dev/null --url http://localhost:PUERTO), o mejor, sumar la entrada al ingress de este config.yml + route dns -f (como se hizo arriba para reportesweb.frento.com.mx) en vez de pelear con el quick tunnel.

Inconsistencia conocida, no urgente: Metabase publica su puerto en docker-compose.yml como "PUERTO:PUERTO" (bindea 0.0.0.0, alcanzable en la red local además de por el túnel), en vez de 127.0.0.1:PUERTO:PUERTO como sí hace el patrón nuevo (data.frento.com.mx/Perses, dash.frento.com.mx/Lightdash). No es la convención a seguir para servicios nuevos — bindear siempre a 127.0.0.1 y dejar que el túnel sea la única vía de entrada.

Docker

Todos los stacks corren en contenedores separados, sin red compartida entre ellos salvo soda-loki/soda-perses (mismo docker-compose.yml, se resuelven por nombre de servicio).

ProyectoContenedoresPuerto hostRepo/carpeta
Trivasa DWpostgres-dw5433trivasa-bi-dev/postgres-warehouse/ — ver Warehouse Postgres
Lightdashlightdash, lightdash-db, lightdash-minio (+ lightdash-minio-init, un solo uso)127.0.0.1:8090trivasa-bi-dev/lightdash/
Metabasemetabase-metabase-1, metabase-metabase-db-13000trivasa-bi-dev/metabase/
Loki + Persessoda-loki, soda-perses3100, 8080 (ambos solo 127.0.0.1)~/soda-observability/
DVTdvt-checks (Python 3.11, no accesible por red — solo docker exec)—trivasa-bi-dev/dvt-checks/ — ver DVT
varela-botvarela-bot, varela-bot-postgres8091~/ehalso/varela-bot/

Authentik se decomisionó el 2026-08-09 (docker compose down en ~/authentik/, entrada de authentik.frento.com.mx quitada del túnel) — no llegó a protegerse ningún servicio con él, se liberó su RAM/imagen para poder completar el despliegue de Lightdash (detalle en sources/trivasa/lightdash-dashboards-demo/despliegue-cierre.md). Datos y docker-compose.yml siguen en ~/authentik/ por si se retoma; imagen ghcr.io/goauthentik/server y postgres:16-alpine (su DB), además del volumen authentik_database, se borraron del host el 2026-08-09.

Superset se decomisionó por completo el 2026-08-09 — a diferencia de Authentik, sí tenía contenido real (un dashboard “Compras” conectado a MPRO Ealcocer 204/TRIVASADB vía SQL Server, no solo datos de ejemplo), pero seguía siendo dev sin nada en producción — se eliminó sin backup por decisión explícita, junto con la carpeta completa trivasa-bi-dev/superset/ (463MB, clon del repo oficial apache/superset), sus imágenes Docker (superset-superset:latest custom build, redis:7) y la entrada superset.frento.com.mx del túnel. Ver Stack de BI explorado, no adoptado § actualización 2026-08-09 para el historial completo de esta instancia (fue scaffold sin usar, después un deployment real, y ahora decomisionada).

Hub Streamlit (explore.frento.com.mx) se decomisionó — confirmado por Esteban el 2026-08-26. A diferencia de Superset/Authentik, la baja real (carpeta del venv borrada) había pasado sin detener streamlit-hub.service ni quitar la entrada del túnel: el servicio quedó en crash-loop indefinido, 30,344 reinicios acumulados (status=203/EXEC, venv inexistente) sirviendo 502 en vez del 404 limpio de un hostname sin ingress. Se detectó primero desde vm-playground sin SSH directo a este host (~/.ssh/config de esa VM no tenía el alias TRV_ctunlinux — resuelto el mismo día sumando esa VM a la malla Syncthing de .ssh, ver Syncthing); con acceso ya confirmado, se cerró bien: systemctl stop+disable streamlit-hub.service, línea de explore.frento.com.mx quitada de /etc/cloudflared/config.yml, cloudflared.service reiniciado. Ver Hub Streamlit § decomiso para el detalle completo.

Loki + Perses — data.frento.com.mx

Sirve 3 dashboards: el dummy de Soda (abajo, datos ficticios) y dos con datos reales de reconciliación fuente/destino vía DVT — ver DVT para el detalle completo (por qué reemplazó a Soda Core, el bloqueo de Python 3.14 y su solución vía Docker, las 15 tablas, cron).

Dummy — datos ficticios

Loki + Perses, dashboard replicado del look real de Soda Cloud (2 stat panels + 1 gauge de health score + barras apiladas pass/warn/fail por día, mismos colores verde/amarillo/ rojo) poblado con ~60 días de checks 100% ficticios sobre datasets de ejemplo (ventas, clientes, inventario) — no hay integración real con Soda Core todavía. Loki con storage de filesystem simple (sin S3), Perses con storage de archivos (sin Postgres/SQLite) — ambos el modo más simple soportado nativamente. El datasource Loki en el dashboard usa proxy (no directUrl): el browser del usuario no puede resolver el hostname interno de Docker loki, así que las queries pasan por el server de Perses. Dashboard aplicado como código (percli apply), no a mano en la UI — YAML en ~/soda-observability/dashboards/soda-data-quality.yaml.

Dos gotchas de Loki para backfill histórico (no aplican a ingesta en tiempo real, solo si se vuelve a poblar con timestamps pasados): ingester.max_chunk_age tiene que cubrir más del doble del rango a cargar (el rechazo real de “entry too far behind” es la mitad de este valor respecto al wall-clock, no reject_old_samples como sugiere el nombre del error), y limits_config.max_query_length (default 30d) tiene que ser mayor que la ventana del dashboard o las queries del panel de barras fallan.

Otros servicios en el host (no docker)

Servicio (systemd)PuertoQué es
consulta-xmls-gastos.service8600 (0.0.0.0, no detrás del túnel)Consulta XMLs vs Gastos/Compras, Flask/waitress
streamlit-hub.service8501Hub Streamlit — decomisionado el 2026-08-26 (systemctl stop+disable), ver nota arriba junto a Superset
glances.service61208Panel de monitoreo, expuesto en monitor.frento.com.mx. Instancia standalone — no es parte del hub multi-host que documenta Glances (ese hub vive en vm-playground y monitorea vm-main/vm-backup/vm-playground/vm-personal vía NetBird; ctunlinux tiene NetBird desde 2026-08-09 pero no está agregado a ese hub — sigue siendo una instancia aparte, no una limitación técnica)
smbd.service139, 445Share Samba ctunlinux-ealcocer — uso personal de Esteban, no relacionado a Trivasa
lightdash-dashboard-pipeline.service9100 (red local, 192.168.117.7)Dashboard como imagen en la red local — webhook receiver + servidor HTTP
beszel-agent.service45876 (saliente hacia el hub, sin puerto público)Agente de Beszel (TRV:ctunlinux), agregado 2026-08-09 — primer host de Trivasa en ese hub, instalado por API sin SSH a vm-playground. HUB_URL apunta a https://monitor.ehas.uk desde 2026-08-16 (antes dashboard.estebanalcocer.cloud). El sistema se perdió y recuperó dos veces por el mismo motivo — un reinicio del contenedor del hub sincroniza config.yml y borra cualquier sistema no listado ahí, y ctunlinux se agregó siempre por API, nunca por archivo, hasta la segunda vez (2026-08-16, ver Beszel § incidente) — a partir de ahí sí queda declarado en config.yml
cloudflared.service—El túnel de arriba
tmux-ctunlinux.service—Deshabilitado el 2026-08-25 (mismo día que se creó, ver actualización abajo) — el unit file sigue en disco por si se retoma, pero no arranca nada

consulta-xmls-gastos.service corriendo en 0.0.0.0:8600 sin pasar por el túnel es una brecha real (accesible a cualquiera en la misma red que el host) — pendiente decidir si se agrega al ingress de cloudflared (bindeando a 127.0.0.1 primero) o se deja interno a propósito.

tmux-ctunlinux.service + cron — Claude Code arranca solo y se auto-relanza

Servicio systemd --user (~/.config/systemd/user/tmux-ctunlinux.service, Type=oneshot + RemainAfterExit=yes, WantedBy=default.target) agregado el 2026-08-25, replicando en este host un patrón que ya corría en pop-os (t640-pop): ExecStart es idempotente — si la sesión de tmux ctunlinux ya existe, no hace nada; si no, la crea desatachada en ~/claude-scratch (tmux new-session -d -s ctunlinux -c /home/ealcocer/claude-scratch, decisión explícita de Esteban — no $HOME) y le manda las teclas /home/ealcocer/.npm-global/bin/claude --name ctunlinux + Enter, lanzando Claude Code ya nombrado dentro. ExecStop mata la sesión de tmux si el servicio se detiene. No hizo falta loginctl enable-linger — este host ya lo tenía activo (Linger=yes) desde antes, a diferencia de vm-personal/vm-playground (ver claude-skills — repo de skills propios de Claude Code para ese otro caso).

Resuelto 2026-09-02: se comparó el unit file real de t640-pop (pop-os) contra el de acá — eran equivalentes, difiriendo solo en rutas propias de cada host (/bin/sh vs /bin/bash, workdir ~/claude-scratch explícito acá vs %h en pop-os, nombre de sesión, ruta del binario claude). El watchdog de cron, en cambio, no estaba replicado en pop-os — solo corría el systemd oneshot, sin nada que relanzara la sesión si moría en caliente. Motivo del descubrimiento: Esteban cerró a mano la tmux t640-pop (tmux kill-session) desde una conversación de Claude Code corriendo ahí mismo, y notó que no volvía sola — el oneshot ya estaba active (exited) y no iba a disparar de nuevo hasta el próximo login. Se instaló tmux-claude-watchdog.sh también en pop-os (mismo script, un solo target t640-pop:/home/esteban:t640-pop) vía crontab de usuario (* * * * * ... >> ~/.local/state/tmux-claude-watchdog.log 2>&1), y se verificó end-to-end (matando la sesión a mano, viendo el siguiente tick de cron recrearla).

Veredicto, mismo día: con el watchdog probado, tmux-t640-pop.service quedó redundante — se eliminó (stop + disable + borrar el unit file), no solo se deshabilitó. Razón de fondo, no solo prolijidad: Restart= de systemd no puede cubrir nunca este caso — en cuanto tmux new-session -d crea la sesión, el proceso que systemd rastrea (el cliente tmux) termina y el control pasa al demonio de tmux, ajeno al árbol de systemd; si claude truena adentro después, systemd no se entera. La única solución real siempre fue un script de polling externo, y ese script ya cubre por sí solo tanto la creación inicial (primer tick tras un reboot) como la vigilancia en caliente — el oneshot no aportaba nada que el cron no hiciera ya. Estado final en pop-os: un solo mecanismo, cron únicamente, verificado matando la sesión otra vez después de borrar la unit y viendo que el siguiente tick la recreó sin ayuda de systemd. tmux-ctunlinux.service (acá) se dejó como está — deshabilitado pero en disco — sin extender esta misma decisión a este host a menos que se pida explícitamente.

Limitación de tmux-ctunlinux.service por sí solo: al ser Type=oneshot + RemainAfterExit=yes sin Restart= ni timer, solo garantiza que la sesión exista al arrancar la sesión de usuario — no vigila nada después. Si Claude Code se cierra o truena dentro de la sesión (el pane vuelve a un shell normal) o si alguien mata la sesión de tmux completa, el servicio se queda marcado “activo” sin enterarse y no relanza nada hasta el próximo reboot o un systemctl --user start manual.

tmux-claude-watchdog.sh (cron, cada minuto) cubre ese caso: agregado el 2026-08-25 en ~/.local/bin/tmux-claude-watchdog.sh, corrido vía crontab de usuario (* * * * * ... >> ~/.local/state/tmux-claude-watchdog.log 2>&1) — no depende de systemd --user ni de Linger, el daemon de cron del sistema es independiente de que haya una sesión de usuario logueada. Por cada sesión de una lista (sesión:directorio: --name), revisa tmux has-session: si no existe, la crea igual que el servicio; si existe, chequea tmux list-panes -F '#{pane_current_command}' — si no es literalmente claude (se cerró, truena, o quedó en un shell suelto), manda de nuevo las teclas para relanzarlo. Complementa al servicio de systemd en vez de reemplazarlo: el servicio cubre el arranque del host, el cron cubre que alguien cierre Claude por accidente durante el día. Verificado end-to-end matando el proceso claude a mano — el pane volvió a bash y el siguiente tick de cron (dentro del minuto) lo relanzó solo.

Generalizado el mismo día a una segunda sesión, claude-scratch: se descubrió que esa sesión (manual, preexistente desde antes de este mecanismo — ver Herramientas CLI de escritorio para el comando manual equivalente) corría un Claude Code nombrado --name ctunlinux por coincidencia de flag, sin relación real con la sesión automática — confusión real que motivó renombrarlo a ctunlinux_claude-scratch y sumar esa sesión a la misma lista del watchdog (antes tmux-ctunlinux-watchdog.sh, un solo objetivo; renombrado a tmux-claude-watchdog.sh, lista de objetivos). Cada relanzamiento de este mecanismo arranca una conversación nueva y vacía, nunca retoma la anterior — decisión explícita al generalizarlo (la opción de relanzar con --resume <session-id> se evaluó y se descartó).

Por qué cron y no un timer de systemd --user (evaluado explícitamente, no por default): ninguno de los dos puede usar Restart= de systemd para esto — en cuanto tmux new-session -d crea la sesión, el proceso que systemd rastrea (el cliente tmux) termina y el control pasa al demonio de tmux, que ya no es hijo de systemd; si claude truena adentro después, systemd nunca se entera. Por eso la única solución real es un script de polling que revisa y relanza, sin importar si lo dispara cron o un .timer — la diferencia entre ambos es solo operativa: un timer sería más consistente con claude-skills-pull.timer (mismo patrón, un mecanismo menos que aprender) y no depende de un archivo de log a mano, pero cron no depende de Linger en absoluto y esto se implementó primero con cron y ya está probado — se decidió no migrarlo a systemd solo por prolijidad, sin ganancia funcional real. El intervalo se dejó en 1 min tal cual (no cada hora) — a esa cadencia da igual para el argumento de “qué tan rápido se entera”, pero se prefirió mantenerlo así por ser lo ya validado en producción.

Actualización 2026-08-25 (tarde) — se dio de baja el target ctunlinux, deja solo claude-scratch

El mismo día en que se generalizó el watchdog a dos targets, Esteban notó que ambas sesiones “revivían solas” al cerrarlas y sospechó un choque entre cron y systemd --user. Diagnóstico (desde una sesión de Claude Code corriendo dentro de la propia sesión claude-scratch, en este mismo host): no había tal choque — no es un conflicto de dos mecanismos peleando, es que ambos hacían justo lo que estaban diseñados para hacer, y el watchdog de cron es más agresivo de lo que la memoria inmediata de Esteban esperaba:

  • tmux-ctunlinux.service (Type=oneshot + RemainAfterExit=yes) solo dispara After=default.target — corrió una vez a las 03:40:28 UTC y quedó active (exited) sin volver a activarse solo. No es el que revive nada en caliente.
  • tmux-claude-watchdog.sh (cron, cada minuto) sí es el responsable: el log (~/.local/state/tmux-claude-watchdog.log) mostraba relanzamientos de ambos targets a lo largo del día (16:11:02 para ctunlinux, 16:24:01 para claude-scratch), cada uno ≤60s después de que su pane dejara de tener claude como comando activo — exactamente el comportamiento ya documentado arriba, funcionando como se probó.

Con el diagnóstico claro, la decisión de Esteban fue que solo claude-scratch debía seguir vigilada — la sesión ctunlinux (la que usaba el flag --name ctunlinux por la coincidencia de nombre descrita arriba) quedaba de más, sin uso real distinto de claude-scratch que justificara mantenerla en paralelo. Acciones tomadas, en orden:

  1. systemctl --user disable --now tmux-ctunlinux.service — el ExecStop del propio servicio mató la sesión de tmux ctunlinux como efecto colateral (no hizo falta un tmux kill-session aparte). El unit file se dejó en disco, solo disabled, por si se retoma más adelante.
  2. Se quitó la línea "ctunlinux:/home/ealcocer/claude-scratch:ctunlinux" del array TARGETS en tmux-claude-watchdog.sh, dejando un único target: "claude-scratch:/home/ealcocer/claude-scratch:ctunlinux_claude-scratch". El nombre ctunlinux_claude-scratch (herencia del renombre que motivó la generalización) no se tocó — sigue siendo el --name real de esa sesión, pendiente si algún día se limpia.

Estado actual: el único mecanismo vivo es el cron, con un solo target (claude-scratch). El comportamiento para esa sesión no cambió — sigue reconstruyéndose sola (sesión completa o solo el proceso claude) dentro de un minuto si se cierra, igual que se documentó y probó arriba —, simplemente ya no compite por atención con una segunda sesión que nadie usaba a propósito.

Salida HTTPS pasa por un Fortinet que hace inspección SSL — rompe cualquier cliente que valide certificados a mano

Descubierto el 2026-09-03 al instalar Syncthing en este host (ver Syncthing para el caso completo): el tráfico HTTPS saliente de ctunlinux pasa por un firewall Fortinet que re-firma los certificados de terceros con su propia CA (FGT50GTK24000421, emitida por Fortinet). Un curl -v a cualquier host externo (relays.syncthing.net, discovery-lookup.syncthing.net probados) muestra el certificado servido con issuer: O=Fortinet ... CN=FGT50GTK24000421 en vez del emisor real, y SSL certificate OpenSSL verify result: unable to get local issuer certificate porque esa CA de Fortinet no está instalada en /etc/ssl/certs/ca-certificates.crt de este host.

Por qué no rompe todo lo demás que sí funciona en este host: paquetes vía apt (archive.ubuntu.com, download.docker.com, deb.nodesource.com, etc.) usan HTTP plano o repos con signed-by= explícito verificado aparte del TLS de transporte, y las llamadas a APIs con las que ya se trabaja aquí (Cloudflare, NetBird) parecen tolerar la inspección o pinning menos estricto. El síntoma solo apareció con Syncthing porque su cliente Go valida la cadena de certificados de forma estricta contra el trust store del sistema, sin excepción — cualquier herramienta nueva que dependa de validar TLS contra una cadena de confianza pública (no solo alcanzar el host) puede pegar con el mismo error.

No se instaló la CA de Fortinet en el trust store — sería aceptar que este firewall puede hacer MITM de cualquier TLS saliente del host validado por Claude Code sin que medie una decisión explícita de Esteban/IT sobre eso. El rodeo para Syncthing (direcciones directas en vez de depender de discovery/relay públicos) está documentado en Syncthing; para cualquier otra herramienta nueva que falle igual contra un host externo, diagnosticar primero con curl -v al endpoint que falla — el patrón (issuer: ... Fortinet ...) es el mismo.

Montajes CIFS (fuentes de datos externas)

Montajes de solo lectura en /etc/fstab, hacia hosts de SQL Server de MPRO — ver Conexiones a TRIVASADB para el rol de cada uno:

  • //192.168.117.200/C_readonly y .../D_readonly → /mnt/win200_c, /mnt/win200_d (usuario bi_readonly)
  • //192.168.117.211/SincronizarXml → trivasa-bi-dev/exploracion/consulta_xmls_gastos/mnt_xml/ — XMLs CFDI que ingesta Consulta XMLs vs Gastos/Compras

Véase también