Actualizar backend/services.py
This commit is contained in:
@@ -13,6 +13,18 @@ from cache_manager import cache_get_or_set
|
||||
# Instancia única del DataManager (como @st.cache_resource)
|
||||
_DM = None
|
||||
|
||||
# Hora de la última actualización de DATOS (se setea al precargar/refrescar)
|
||||
_ultima_actualizacion = None
|
||||
|
||||
def _marcar_actualizacion():
|
||||
global _ultima_actualizacion
|
||||
from datetime import datetime
|
||||
_ultima_actualizacion = datetime.now()
|
||||
|
||||
def ultima_actualizacion():
|
||||
"""Devuelve la hora (ISO) del último refresco de datos, o None."""
|
||||
return {"hora": _ultima_actualizacion.isoformat() if _ultima_actualizacion else None}
|
||||
|
||||
def get_dm() -> DataManager:
|
||||
global _DM
|
||||
if _DM is None:
|
||||
@@ -356,6 +368,9 @@ def precargar_todo():
|
||||
t()
|
||||
except Exception as e:
|
||||
print(f"[precarga] error: {e}")
|
||||
_marcar_actualizacion()
|
||||
# Precargar mes actual + anterior de TODOS los módulos (sin entrar a ellos)
|
||||
precargar_todos_modulos()
|
||||
|
||||
_refresh_count = 0
|
||||
|
||||
@@ -369,6 +384,8 @@ def _recalcular_clave(key):
|
||||
return ocupabilidad(args[0], args[1], args[2], args[3])
|
||||
if prefijo == "ventas" and len(args) == 2:
|
||||
return ventas(args[0], args[1])
|
||||
if prefijo == "ventas" and len(args) == 5:
|
||||
return ventas(args[0], args[1], args[2], args[3], args[4] == "True")
|
||||
if prefijo == "cobranza" and len(args) == 4:
|
||||
return cobranza(args[0], args[1], args[2], args[3])
|
||||
if prefijo == "rentabilidad" and len(args) == 4:
|
||||
@@ -394,7 +411,187 @@ def refrescar_todo():
|
||||
if not cache_keys():
|
||||
precargar_todo()
|
||||
else:
|
||||
# Limpiar el caché anual de cobranza para que el recálculo lea SQL fresco
|
||||
try:
|
||||
get_dm().limpiar_cache_cobranza()
|
||||
except Exception:
|
||||
pass
|
||||
cache_refresh_existing(_recalcular_clave)
|
||||
_marcar_actualizacion()
|
||||
# Reprecargar mes actual + anterior de todos los módulos (incluye combos nuevos)
|
||||
precargar_todos_modulos()
|
||||
|
||||
|
||||
def refrescar_manual(clave_actual=None):
|
||||
"""Refresco a demanda (botón 'Actualizar'):
|
||||
1) VACÍA el caché y RE-LEE las bases para la vista que el usuario ve AHORA.
|
||||
2) El resto de vistas cacheadas se recalculan en SEGUNDO PLANO.
|
||||
NO afecta al ciclo automático de 15 min."""
|
||||
import threading
|
||||
from cache_manager import cache_keys, cache_invalidate
|
||||
|
||||
claves_previas = cache_keys()
|
||||
cache_invalidate()
|
||||
try:
|
||||
get_dm().limpiar_cache_cobranza()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if clave_actual:
|
||||
try:
|
||||
_recalcular_clave(clave_actual)
|
||||
except Exception as e:
|
||||
print(f"[refresh manual] vista actual {clave_actual}: {e}")
|
||||
|
||||
resto = [k for k in claves_previas if k != clave_actual]
|
||||
|
||||
def _fondo():
|
||||
for k in resto:
|
||||
try:
|
||||
_recalcular_clave(k)
|
||||
except Exception as e:
|
||||
print(f"[refresh manual bg] {k}: {e}")
|
||||
_marcar_actualizacion()
|
||||
print(f"[refresh manual] segundo plano completado ({len(resto)} vistas)")
|
||||
|
||||
threading.Thread(target=_fondo, daemon=True).start()
|
||||
precargar_todos_modulos()
|
||||
_marcar_actualizacion()
|
||||
return ultima_actualizacion()
|
||||
|
||||
|
||||
# ── PRECARGA EN SEGUNDO PLANO (cache warming por módulo) ────────────────────
|
||||
import threading as _threading
|
||||
_precarga_gen = {}
|
||||
_precarga_estado = {}
|
||||
_precarga_lock = _threading.Lock()
|
||||
_PRECARGA_PAUSA = 0.15
|
||||
|
||||
|
||||
def precarga_estado(modulo):
|
||||
"""Progreso de la precarga de un módulo."""
|
||||
modulo = (modulo or "").lower()
|
||||
with _precarga_lock:
|
||||
e = _precarga_estado.get(modulo)
|
||||
return dict(e) if e else {"total": 0, "hechas": 0, "fallidas": 0, "completo": False}
|
||||
|
||||
|
||||
def _periodos_act_ant():
|
||||
"""(año, mes) del mes actual y el anterior (maneja el cambio de año en enero)."""
|
||||
from datetime import datetime
|
||||
hoy = datetime.now()
|
||||
prev = (hoy.year - 1, 12) if hoy.month == 1 else (hoy.year, hoy.month - 1)
|
||||
return [(hoy.year, hoy.month), prev]
|
||||
|
||||
|
||||
def _combos_cobranza(ano):
|
||||
"""(ano, mes, sectorista, agrupacion) a calentar: SOLO mes actual y anterior."""
|
||||
periodos = _periodos_act_ant()
|
||||
agrupaciones = ["SEDE", "PROGRAMA", "ASESOR"]
|
||||
combos = []
|
||||
for (yy, mm) in periodos:
|
||||
try:
|
||||
logic = CobranzaLogic(get_dm())
|
||||
secs = logic.obtener_lista_sectoristas(str(yy), str(mm)) or ["TODOS"]
|
||||
except Exception:
|
||||
secs = ["TODOS"]
|
||||
secs = list(dict.fromkeys(["TODOS"] + [s for s in secs if s and s != "TODOS"]))
|
||||
for ag in agrupaciones:
|
||||
for sec in secs:
|
||||
combos.append((yy, mm, sec, ag))
|
||||
return combos
|
||||
|
||||
|
||||
def precargar_modulo(modulo, ano, en_hilo=True):
|
||||
"""Precarga la tabla principal del módulo (mes actual + anterior)."""
|
||||
modulo = (modulo or "").lower()
|
||||
with _precarga_lock:
|
||||
gen = _precarga_gen.get(modulo, 0) + 1
|
||||
_precarga_gen[modulo] = gen
|
||||
_precarga_estado[modulo] = {"total": 0, "hechas": 0, "fallidas": 0, "completo": False, "ano": ano}
|
||||
|
||||
def _vigente():
|
||||
with _precarga_lock:
|
||||
return _precarga_gen.get(modulo) == gen
|
||||
|
||||
def _sumar(campo):
|
||||
with _precarga_lock:
|
||||
if _precarga_gen.get(modulo) == gen:
|
||||
_precarga_estado[modulo][campo] += 1
|
||||
|
||||
def _set_total(n):
|
||||
with _precarga_lock:
|
||||
if _precarga_gen.get(modulo) == gen:
|
||||
_precarga_estado[modulo]["total"] = n
|
||||
|
||||
def _marcar_completo():
|
||||
with _precarga_lock:
|
||||
if _precarga_gen.get(modulo) == gen:
|
||||
_precarga_estado[modulo]["completo"] = True
|
||||
|
||||
def _fondo():
|
||||
import time
|
||||
if modulo == "cobranza":
|
||||
combos = _combos_cobranza(ano)
|
||||
_set_total(len(combos))
|
||||
for (yy, mm, sec, ag) in combos:
|
||||
if not _vigente():
|
||||
return
|
||||
try:
|
||||
cobranza(str(yy), str(mm), sec, ag)
|
||||
_sumar("hechas")
|
||||
except Exception as e:
|
||||
print(f"[precarga cobranza] {yy}-{mm} {ag}/{sec}: {e}")
|
||||
_sumar("fallidas")
|
||||
time.sleep(_PRECARGA_PAUSA)
|
||||
_marcar_completo()
|
||||
print(f"[precarga cobranza] {ano} completado ({len(combos)} vistas)")
|
||||
elif modulo in ("ocupabilidad", "ventas", "rentabilidad"):
|
||||
periodos = _periodos_act_ant()
|
||||
_set_total(len(periodos))
|
||||
for (yy, mm) in periodos:
|
||||
if not _vigente():
|
||||
return
|
||||
try:
|
||||
if modulo == "ocupabilidad":
|
||||
ocupabilidad(str(yy), str(mm), "TODOS", "TODOS")
|
||||
elif modulo == "ventas":
|
||||
ventas(str(yy), str(mm), "TODOS", "TODOS")
|
||||
else:
|
||||
rentabilidad(str(yy), str(mm), "TODOS", "TODOS")
|
||||
_sumar("hechas")
|
||||
except Exception as e:
|
||||
print(f"[precarga {modulo}] {yy}-{mm}: {e}")
|
||||
_sumar("fallidas")
|
||||
time.sleep(_PRECARGA_PAUSA)
|
||||
_marcar_completo()
|
||||
print(f"[precarga {modulo}] {ano} completado ({len(periodos)} vistas)")
|
||||
else:
|
||||
print(f"[precarga] módulo no soportado aún: {modulo}")
|
||||
|
||||
if en_hilo:
|
||||
_threading.Thread(target=_fondo, daemon=True).start()
|
||||
else:
|
||||
_fondo()
|
||||
return {"status": "precarga iniciada", "modulo": modulo, "ano": ano}
|
||||
|
||||
|
||||
def precargar_todos_modulos():
|
||||
"""Precarga (en 2º plano) el mes actual y anterior de TODOS los módulos, de forma
|
||||
SECUENCIAL en un solo hilo. Se llama al arrancar, cada 15 min y tras el refresco manual."""
|
||||
from datetime import datetime
|
||||
ano = datetime.now().year
|
||||
|
||||
def _todo():
|
||||
for m in ("cobranza", "ocupabilidad", "ventas", "rentabilidad"):
|
||||
try:
|
||||
precargar_modulo(m, ano, en_hilo=False)
|
||||
except Exception as e:
|
||||
print(f"[precarga todos] {m}: {e}")
|
||||
print("[precarga todos] completado (todos los módulos)")
|
||||
|
||||
_threading.Thread(target=_todo, daemon=True).start()
|
||||
|
||||
|
||||
# ── OPERACIONES EXTRA ──────────────────────────────────────────────────────
|
||||
def guardar_costos(num_indice, costos_inicial, costos_actual):
|
||||
|
||||
Reference in New Issue
Block a user