From 6c6c563fd4dfc3e6f9b028b14107f4002b4b7d6d Mon Sep 17 00:00:00 2001 From: Administrator Date: Fri, 11 Sep 2026 15:37:21 -0500 Subject: [PATCH] Actualizar backend/services.py --- backend/services.py | 124 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) diff --git a/backend/services.py b/backend/services.py index 502b104..6c82868 100644 --- a/backend/services.py +++ b/backend/services.py @@ -1033,12 +1033,66 @@ def usuarios_actualizar_rol(user_id, rol): sb.table("perfiles").update({"rol": rol}).eq("id", user_id).execute() return {"status": "ok"} +def usuarios_actualizar_email(user_id, email): + """Cambia el correo de un usuario existente (Supabase Auth).""" + sb = _sb() + sb.auth.admin.update_user_by_id(user_id, {"email": email, "email_confirm": True}) + return {"status": "ok"} + +def usuarios_actualizar_password(user_id, password): + """Cambia la contraseña de un usuario existente (Supabase Auth).""" + sb = _sb() + sb.auth.admin.update_user_by_id(user_id, {"password": password}) + return {"status": "ok"} + def usuarios_eliminar(user_id): sb = _sb() sb.auth.admin.delete_user(user_id) return {"status": "ok"} +# ── ROLES Y PERMISOS (tabla 'permisos': rol, modulo, puede_ver) ──────────── +def roles_modulos_disponibles(): + """Lista de módulos existentes (todos los que aparecen en la tabla permisos).""" + sb = _sb() + res = sb.table("permisos").select("modulo").execute() + mods = sorted({str(r.get("modulo", "")).strip() for r in (res.data or []) if r.get("modulo")}) + return {"modulos": mods} + +def roles_listar(): + """Devuelve todos los roles con la lista de módulos que puede ver cada uno.""" + sb = _sb() + res = sb.table("permisos").select("rol,modulo,puede_ver").execute() + por_rol = {} + for r in (res.data or []): + rol = str(r.get("rol", "")).strip() + if not rol: + continue + por_rol.setdefault(rol, []) + if r.get("puede_ver"): + por_rol[rol].append(str(r.get("modulo", "")).strip()) + return {"roles": [{"rol": k, "modulos": sorted(v)} for k, v in sorted(por_rol.items())]} + +def rol_guardar(rol, modulos): + """Crea o actualiza un rol: borra sus filas y reinserta una por módulo marcado.""" + sb = _sb() + rol = str(rol or "").strip().upper() + if not rol: + return {"status": "error", "msg": "Nombre de rol vacío"} + sb.table("permisos").delete().eq("rol", rol).execute() + registros = [{"rol": rol, "modulo": str(m).strip(), "puede_ver": True} + for m in (modulos or []) if str(m).strip()] + if registros: + sb.table("permisos").insert(registros).execute() + return {"status": "ok", "rol": rol, "modulos": len(registros)} + +def rol_eliminar(rol): + """Borra un rol y todos sus permisos.""" + sb = _sb() + sb.table("permisos").delete().eq("rol", str(rol or "").strip().upper()).execute() + return {"status": "ok"} + + # ── VENDEDORES MANUALES (Comisiones, en Supabase) ────────────────────────── # ── CONFIG MENSUAL COMISIONES (IMP_TC, META, COMISIÓN por tipo de programa) ── def comisiones_config_listar(ano, mes): @@ -1158,3 +1212,73 @@ def comisiones_override_restaurar(num_matricula): cache_invalidate("ventas") cache_invalidate("comisiones_det_todos") return {"status": "ok"} + + +# ── HISTORIAL DE ESTADO DE ASESORES (Chatwoot -> historial_estado_asesor) ── +def registrar_estado_asesores(): + """Lee el estado actual de cada agente en Chatwoot (account_users) y, si cambió + respecto a lo último guardado, inserta una fila en historial_estado_asesor. + availability: 0 = ON (conectado), 1 = OFF (desconectado).""" + dm = get_dm() + conn = dm.get_pg_connection() + if conn is None: + return {"status": "error", "msg": "sin conexion postgre"} + try: + cur = conn.cursor() + cur.execute(""" + SELECT u.name, u.uid, au.availability, au.updated_at + FROM account_users au + JOIN users u ON au.user_id = u.id + WHERE u.uid LIKE '%@escueladerefrigeracion.edu.pe' + """) + actuales = cur.fetchall() + + cur.execute(""" + SELECT DISTINCT ON (email) email, accion + FROM historial_estado_asesor + ORDER BY email, fecha_hora DESC + """) + ultimo = {r[0]: r[1] for r in cur.fetchall()} + + nuevos = 0 + for (name, email, avail, updated_at) in actuales: + accion = "ON" if int(avail) == 0 else "OFF" + if ultimo.get(email) == accion: + continue + cur.execute(""" + INSERT INTO historial_estado_asesor (asesor, email, accion, origen, quien, fecha_hora) + VALUES (%s, %s, %s, %s, %s, %s) + """, (name, email, accion, "CHATWOOT", name, updated_at)) + nuevos += 1 + conn.commit() + return {"status": "ok", "nuevos": nuevos} + except Exception as e: + print(f"[historial asesores] {e}") + return {"status": "error", "msg": str(e)} + finally: + conn.close() + + +def historial_asesor(email, limite=100): + """Devuelve el historial de estados de un asesor (por email), más reciente primero.""" + dm = get_dm() + conn = dm.get_pg_connection() + if conn is None: + return {"historial": []} + try: + cur = conn.cursor() + cur.execute(""" + SELECT accion, origen, quien, fecha_hora + FROM historial_estado_asesor + WHERE email = %s + ORDER BY fecha_hora DESC + LIMIT %s + """, (email, int(limite))) + filas = [{"accion": r[0], "origen": r[1], "quien": r[2], + "fecha_hora": r[3].isoformat() if r[3] else None} for r in cur.fetchall()] + return {"historial": filas} + except Exception as e: + print(f"[historial_asesor] {e}") + return {"historial": []} + finally: + conn.close()