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)
|
# Instancia única del DataManager (como @st.cache_resource)
|
||||||
_DM = None
|
_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:
|
def get_dm() -> DataManager:
|
||||||
global _DM
|
global _DM
|
||||||
if _DM is None:
|
if _DM is None:
|
||||||
@@ -356,6 +368,9 @@ def precargar_todo():
|
|||||||
t()
|
t()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[precarga] error: {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
|
_refresh_count = 0
|
||||||
|
|
||||||
@@ -369,6 +384,8 @@ def _recalcular_clave(key):
|
|||||||
return ocupabilidad(args[0], args[1], args[2], args[3])
|
return ocupabilidad(args[0], args[1], args[2], args[3])
|
||||||
if prefijo == "ventas" and len(args) == 2:
|
if prefijo == "ventas" and len(args) == 2:
|
||||||
return ventas(args[0], args[1])
|
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:
|
if prefijo == "cobranza" and len(args) == 4:
|
||||||
return cobranza(args[0], args[1], args[2], args[3])
|
return cobranza(args[0], args[1], args[2], args[3])
|
||||||
if prefijo == "rentabilidad" and len(args) == 4:
|
if prefijo == "rentabilidad" and len(args) == 4:
|
||||||
@@ -394,7 +411,187 @@ def refrescar_todo():
|
|||||||
if not cache_keys():
|
if not cache_keys():
|
||||||
precargar_todo()
|
precargar_todo()
|
||||||
else:
|
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)
|
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 ──────────────────────────────────────────────────────
|
# ── OPERACIONES EXTRA ──────────────────────────────────────────────────────
|
||||||
def guardar_costos(num_indice, costos_inicial, costos_actual):
|
def guardar_costos(num_indice, costos_inicial, costos_actual):
|
||||||
|
|||||||
Reference in New Issue
Block a user