chore: initial commit del deploy de utilerias-recon-2
Incluye: - app.ipynb con las 10 pestanas (Conexion, Descargas, Analisis, NLP, KGS, CTM, Saldos Vencidos, DataStage, Estructuras SCAII, Valores). - launcher.py + run_dev.bat para lanzar Voila localmente. - docker-compose.yml para levantar Postgres 16 con healthcheck. - schema_registro.sql con las 27 tablas (Registro501..Registro701, RegistroInci/Resumen/Sel, base_numpartes). - requirements.txt con pandas, pyodbc, psycopg2-binary, sqlalchemy, scikit-learn, openpyxl, voila, ipywidgets, python-docx. - 6 manuales de usuario (CTM, SaldosVencidos, DataStage, EstructurasSCAII, Valores, GENPACT_V2 general) en .docx. - 6 generadores de manuales para regenerar los .docx tras editar. - .gitignore que excluye .env, .venv, outputs generados y caches. - README.md con instrucciones de setup en server. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
121
app/launcher.py
Normal file
121
app/launcher.py
Normal file
@@ -0,0 +1,121 @@
|
||||
"""
|
||||
Launcher de la app GENPACT V2.
|
||||
|
||||
Arranca un servidor Voila local en background y abre el browser apuntando al app.ipynb.
|
||||
Cuando se cierra la consola, el servidor de Voila se mata.
|
||||
|
||||
Funciona tanto desde Python directo como empaquetado con PyInstaller.
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import socket
|
||||
import threading
|
||||
import webbrowser
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Localizar app.ipynb y .env
|
||||
# ------------------------------------------------------------------
|
||||
def base_dir() -> Path:
|
||||
"""Carpeta donde estan los recursos de la app.
|
||||
- En modo PyInstaller (--onedir), sys._MEIPASS apunta a la carpeta temporal.
|
||||
- En modo dev, es la carpeta del script.
|
||||
"""
|
||||
if getattr(sys, 'frozen', False):
|
||||
# PyInstaller --onedir: los datos quedan al lado del .exe
|
||||
return Path(sys.executable).parent
|
||||
return Path(__file__).resolve().parent
|
||||
|
||||
|
||||
BASE = base_dir()
|
||||
APP_NB = BASE / 'app.ipynb'
|
||||
|
||||
# Si el .env esta un nivel arriba (proyecto), copialo o setea el cwd
|
||||
if not (BASE / '.env').exists() and (BASE.parent / '.env').exists():
|
||||
os.chdir(BASE.parent)
|
||||
else:
|
||||
os.chdir(BASE)
|
||||
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Buscar puerto libre
|
||||
# ------------------------------------------------------------------
|
||||
def find_free_port(preferido: int = 8866) -> int:
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
try:
|
||||
sock.bind(('127.0.0.1', preferido))
|
||||
sock.close()
|
||||
return preferido
|
||||
except OSError:
|
||||
sock.close()
|
||||
# Si el preferido esta ocupado, deja que el SO asigne uno
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
s.bind(('127.0.0.1', 0))
|
||||
p = s.getsockname()[1]
|
||||
s.close()
|
||||
return p
|
||||
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Arrancar Voila
|
||||
# ------------------------------------------------------------------
|
||||
def main():
|
||||
if not APP_NB.exists():
|
||||
print(f'ERROR: no se encuentra {APP_NB}')
|
||||
sys.exit(1)
|
||||
|
||||
port = find_free_port(8866)
|
||||
# Cuando Voila apunta a un notebook especifico, lo sirve en la raiz "/"
|
||||
url = f'http://127.0.0.1:{port}/'
|
||||
|
||||
print('=' * 60)
|
||||
print(' GENPACT V2 - Sistema de Descargas SCAII')
|
||||
print('=' * 60)
|
||||
print(f' Iniciando servidor Voila en puerto {port}...')
|
||||
print(f' URL: {url}')
|
||||
print(' Cierra esta ventana para detener la app.')
|
||||
print('=' * 60)
|
||||
|
||||
# Construir comando de Voila
|
||||
if getattr(sys, 'frozen', False):
|
||||
# En modo empaquetado, llamamos al binario de python integrado
|
||||
cmd = [sys.executable, '-m', 'voila',
|
||||
str(APP_NB),
|
||||
f'--port={port}',
|
||||
'--no-browser',
|
||||
'--Voila.ip=127.0.0.1',
|
||||
'--strip_sources=True']
|
||||
else:
|
||||
cmd = [sys.executable, '-m', 'voila',
|
||||
str(APP_NB),
|
||||
f'--port={port}',
|
||||
'--no-browser',
|
||||
'--Voila.ip=127.0.0.1',
|
||||
'--strip_sources=True']
|
||||
|
||||
# Lanzar proceso
|
||||
proc = subprocess.Popen(cmd, stdout=sys.stdout, stderr=sys.stderr)
|
||||
|
||||
# Abrir browser cuando el server este listo
|
||||
def _open():
|
||||
for _ in range(60):
|
||||
try:
|
||||
with socket.create_connection(('127.0.0.1', port), timeout=0.5):
|
||||
break
|
||||
except OSError:
|
||||
time.sleep(0.5)
|
||||
webbrowser.open(url)
|
||||
|
||||
threading.Thread(target=_open, daemon=True).start()
|
||||
|
||||
try:
|
||||
proc.wait()
|
||||
except KeyboardInterrupt:
|
||||
print('\nDeteniendo Voila...')
|
||||
proc.terminate()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user