```
feat(vista): Integra carrera camellos (main) Integra T2: simulación de carrera de camellos con hilos. * Añade interfaz gráfica en pestaña "Resultados". * Implementa lógica de inicio, progreso y resultado. * Utiliza locks para evitar deadlocks en tramos críticos. * Guarda el resultado final de la carrera. * Refactoriza panel central y lateral para integrar la app. * Agrega botón en panel lateral para iniciar la carrera. * Usa un número aleatorio de camellos entre 10 y 20. * Corrige error de cuota insuficiente. ```
This commit is contained in:
parent
11fae2b8ff
commit
813b2b7d65
|
|
@ -0,0 +1,189 @@
|
||||||
|
# Módulo: logica/T2/carreraCamellos.py
|
||||||
|
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
import random
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
# --- RECURSOS Y ESTADO GLOBAL PERSISTENTE ---
|
||||||
|
|
||||||
|
# Locks para simular tramos críticos y prevenir DEADLOCKS (Resource Ordering)
|
||||||
|
lock_tramo_1 = threading.Lock()
|
||||||
|
lock_tramo_2 = threading.Lock()
|
||||||
|
|
||||||
|
# Evento para controlar el inicio de la carrera
|
||||||
|
CARRERA_EN_CURSO = threading.Event()
|
||||||
|
|
||||||
|
# Variable GLOBAL y PERSISTENTE para guardar el ÚLTIMO resultado completo de la carrera.
|
||||||
|
# Se inicializa a None y se actualiza al finalizar CADA carrera.
|
||||||
|
# Estructura: {'id': 'uuid', 'activa': bool, 'meta': int, 'camellos': [...], 'ganador': str}
|
||||||
|
RESULTADO_ULTIMO = None
|
||||||
|
|
||||||
|
|
||||||
|
# --- CLASE DEL HILO CAMELLO ---
|
||||||
|
|
||||||
|
class Camello(threading.Thread):
|
||||||
|
"""Representa un camello que avanza y necesita adquirir recursos (Locks)."""
|
||||||
|
|
||||||
|
def __init__(self, nombre, carrera_id, distancia_meta=50):
|
||||||
|
super().__init__()
|
||||||
|
self.nombre = nombre
|
||||||
|
self.carrera_id = carrera_id
|
||||||
|
self.progreso = 0
|
||||||
|
self.distancia_meta = distancia_meta
|
||||||
|
self.posicion_final = None
|
||||||
|
self.estado = "Esperando"
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
self.estado = "Listo"
|
||||||
|
CARRERA_EN_CURSO.wait()
|
||||||
|
self.estado = "Corriendo"
|
||||||
|
|
||||||
|
# Lista local para guardar el progreso para la actualización final
|
||||||
|
progreso_local = []
|
||||||
|
|
||||||
|
while self.progreso < self.distancia_meta:
|
||||||
|
if not CARRERA_EN_CURSO.is_set(): # Salir si la carrera se detiene manualmente
|
||||||
|
self.estado = "Abortado"
|
||||||
|
break
|
||||||
|
|
||||||
|
avance = random.randint(1, 5)
|
||||||
|
self.progreso += avance
|
||||||
|
if self.progreso >= self.distancia_meta:
|
||||||
|
self.progreso = self.distancia_meta
|
||||||
|
break
|
||||||
|
|
||||||
|
# 1. TRAMO 1: Adquirir Lock 1
|
||||||
|
if self.progreso >= 15 and self.progreso < 30:
|
||||||
|
self.intentar_avanzar_tramo(lock_tramo_1, "TRAMO 1")
|
||||||
|
|
||||||
|
# 2. TRAMO 2: Adquirir Lock 2 (Respetando la jerarquía para evitar deadlock)
|
||||||
|
elif self.progreso >= 30 and self.progreso < 45:
|
||||||
|
# Simulación de que necesita el Lock 1 y luego el Lock 2 (Jerarquía 1 -> 2)
|
||||||
|
self.intentar_avanzar_tramo_jerarquico()
|
||||||
|
|
||||||
|
time.sleep(0.1)
|
||||||
|
progreso_local.append(self.progreso)
|
||||||
|
|
||||||
|
if self.progreso >= self.distancia_meta:
|
||||||
|
self.finalizar_carrera()
|
||||||
|
|
||||||
|
def intentar_avanzar_tramo(self, lock_actual, nombre_tramo):
|
||||||
|
"""Intenta adquirir un Lock simple para un tramo crítico."""
|
||||||
|
self.estado = f"Esperando {nombre_tramo}"
|
||||||
|
with lock_actual:
|
||||||
|
self.estado = f"En {nombre_tramo} 🔒"
|
||||||
|
time.sleep(random.uniform(0.5, 1.0))
|
||||||
|
self.estado = "Corriendo"
|
||||||
|
|
||||||
|
def intentar_avanzar_tramo_jerarquico(self):
|
||||||
|
"""Simula la necesidad de adquirir Locks en ORDEN (1 luego 2) para prevenir interbloqueo."""
|
||||||
|
self.estado = "Esperando TRAMO 2 (Lock 1 y 2)"
|
||||||
|
|
||||||
|
# ⚠️ Clave de la prevención de Deadlock: Adquirir siempre en el mismo orden (Lock 1, luego Lock 2)
|
||||||
|
with lock_tramo_1:
|
||||||
|
with lock_tramo_2:
|
||||||
|
self.estado = "En TRAMO 2 (Lock 1+2) 🔒🔒"
|
||||||
|
time.sleep(random.uniform(1.0, 2.0))
|
||||||
|
self.estado = "Corriendo"
|
||||||
|
|
||||||
|
def finalizar_carrera(self):
|
||||||
|
"""Registra el resultado final de la carrera y lo guarda en RESULTADO_ULTIMO."""
|
||||||
|
global RESULTADO_ULTIMO
|
||||||
|
|
||||||
|
# Creamos una copia local del estado para que sea seguro leer desde el hilo principal
|
||||||
|
estado_camello = {
|
||||||
|
'nombre': self.nombre,
|
||||||
|
'progreso': self.progreso,
|
||||||
|
'estado': "Meta",
|
||||||
|
'posicion': None, # Se asigna después
|
||||||
|
'carrera_id': self.carrera_id
|
||||||
|
}
|
||||||
|
|
||||||
|
# Utilizamos lock_tramo_1 para serializar la escritura en la variable global compartida
|
||||||
|
with lock_tramo_1:
|
||||||
|
if RESULTADO_ULTIMO and RESULTADO_ULTIMO['id'] == self.carrera_id:
|
||||||
|
RESULTADO_ULTIMO['camellos'].append(estado_camello)
|
||||||
|
|
||||||
|
# Asignar posición: len actual de la lista es la posición
|
||||||
|
self.posicion_final = len(RESULTADO_ULTIMO['camellos'])
|
||||||
|
RESULTADO_ULTIMO['camellos'][-1]['posicion'] = self.posicion_final
|
||||||
|
|
||||||
|
if self.posicion_final == 1:
|
||||||
|
RESULTADO_ULTIMO['ganador'] = self.nombre
|
||||||
|
|
||||||
|
self.estado = "Meta"
|
||||||
|
print(f"🐫 {self.nombre} ha llegado en la posición {self.posicion_final}.")
|
||||||
|
|
||||||
|
|
||||||
|
# --- FUNCIONES DE CONTROL DE LA CARRERA ---
|
||||||
|
|
||||||
|
def iniciar_carrera(nombres_camellos):
|
||||||
|
"""Inicializa y comienza los hilos de la carrera, limpiando el resultado anterior."""
|
||||||
|
global RESULTADO_ULTIMO
|
||||||
|
|
||||||
|
carrera_id = str(uuid.uuid4())
|
||||||
|
|
||||||
|
# 1. Limpiar el estado global y preparar el nuevo resultado PERSISTENTE
|
||||||
|
RESULTADO_ULTIMO = {
|
||||||
|
'id': carrera_id,
|
||||||
|
'activa': True,
|
||||||
|
'meta': 50,
|
||||||
|
'camellos': [], # Aquí se acumularán los resultados finales
|
||||||
|
'ganador': 'Nadie'
|
||||||
|
}
|
||||||
|
|
||||||
|
CARRERA_EN_CURSO.clear()
|
||||||
|
|
||||||
|
camellos = [Camello(nombre, carrera_id) for nombre in nombres_camellos]
|
||||||
|
|
||||||
|
for camello in camellos:
|
||||||
|
camello.start()
|
||||||
|
|
||||||
|
CARRERA_EN_CURSO.set()
|
||||||
|
|
||||||
|
return camellos
|
||||||
|
|
||||||
|
|
||||||
|
def obtener_estado_carrera(camellos):
|
||||||
|
"""
|
||||||
|
Devuelve el estado actual de los camellos (mientras corren) O el último resultado guardado
|
||||||
|
(si ya han terminado).
|
||||||
|
"""
|
||||||
|
global RESULTADO_ULTIMO
|
||||||
|
|
||||||
|
if RESULTADO_ULTIMO and not CARRERA_EN_CURSO.is_set():
|
||||||
|
# Si la carrera ya terminó, devolvemos el resultado guardado
|
||||||
|
return {
|
||||||
|
'tipo': 'final',
|
||||||
|
'datos': RESULTADO_ULTIMO
|
||||||
|
}
|
||||||
|
|
||||||
|
# Si la carrera está activa o no ha comenzado, devolvemos el progreso en tiempo real
|
||||||
|
estado_tiempo_real = [
|
||||||
|
{
|
||||||
|
'nombre': c.nombre,
|
||||||
|
'progreso': c.progreso,
|
||||||
|
'estado': c.estado,
|
||||||
|
'posicion': c.posicion_final
|
||||||
|
} for c in camellos if c.is_alive() or c.progreso == c.distancia_meta
|
||||||
|
]
|
||||||
|
|
||||||
|
# Si todos los hilos han terminado, marcamos la carrera como inactiva
|
||||||
|
if estado_tiempo_real and all(
|
||||||
|
e['progreso'] >= RESULTADO_ULTIMO['meta'] for e in estado_tiempo_real) and RESULTADO_ULTIMO:
|
||||||
|
with lock_tramo_1:
|
||||||
|
RESULTADO_ULTIMO['activa'] = False
|
||||||
|
|
||||||
|
return {
|
||||||
|
'tipo': 'activo',
|
||||||
|
'datos': {
|
||||||
|
'activa': CARRERA_EN_CURSO.is_set(),
|
||||||
|
'camellos': estado_tiempo_real
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def detener_carrera():
|
||||||
|
"""Detiene la señal de la carrera."""
|
||||||
|
CARRERA_EN_CURSO.clear()
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
# Módulo: vista/panel_central.py
|
# Módulo: vista/panel_central.py
|
||||||
|
|
||||||
|
import random
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
from logica.T1.trafficMeter import iniciar_monitor_red
|
from logica.T1.trafficMeter import iniciar_monitor_red
|
||||||
|
|
@ -7,21 +8,37 @@ from logica.T1.graficos import crear_grafico_recursos, actualizar_historial_dato
|
||||||
from matplotlib.figure import Figure
|
from matplotlib.figure import Figure
|
||||||
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
|
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
|
||||||
|
|
||||||
# --- IMPORTACIÓN DE CONSTANTES DESDE vista/config.py ---
|
# --- IMPORTACIÓN DE LA LÓGICA DE T2 (CARRERA DE CAMELLOS) ---
|
||||||
|
from logica.T2.carreraCamellos import (
|
||||||
|
iniciar_carrera,
|
||||||
|
obtener_estado_carrera,
|
||||||
|
detener_carrera,
|
||||||
|
RESULTADO_ULTIMO # Variable de estado persistente
|
||||||
|
)
|
||||||
|
|
||||||
|
# --- IMPORTACIÓN UNIVERSAL DE CONSTANTES ---
|
||||||
|
# Asume que todas las constantes de estilo y colores necesarias están definidas en vista.config
|
||||||
from vista.config import *
|
from vista.config import *
|
||||||
|
|
||||||
class PanelCentral(ttk.Frame):
|
|
||||||
"""Contiene el Notebook (subpestañas de T1), el panel de Notas y el panel de Chat."""
|
|
||||||
|
|
||||||
# Usamos la constante importada para el intervalo de actualización
|
class PanelCentral(ttk.Frame):
|
||||||
|
"""Contiene el Notebook (subpestañas de T1, T2 en Resultados), el panel de Notas y el panel de Chat."""
|
||||||
|
|
||||||
INTERVALO_ACTUALIZACION_MS = INTERVALO_RECURSOS_MS
|
INTERVALO_ACTUALIZACION_MS = INTERVALO_RECURSOS_MS
|
||||||
|
INTERVALO_CARRERA_MS = 200
|
||||||
|
|
||||||
def __init__(self, parent, *args, **kwargs):
|
def __init__(self, parent, *args, **kwargs):
|
||||||
super().__init__(parent, *args, **kwargs)
|
super().__init__(parent, *args, **kwargs)
|
||||||
self.after_id = None
|
self.after_id = None
|
||||||
self.parent_root = parent.winfo_toplevel()
|
self.after_carrera_id = None
|
||||||
|
self.camellos = []
|
||||||
|
self.progreso_labels = {}
|
||||||
|
self.frame_carrera_controles = None
|
||||||
|
self.frame_progreso = None
|
||||||
|
self.carrera_info_label = None
|
||||||
|
self.carrera_estado_label = None
|
||||||
|
|
||||||
# 1. INICIALIZACIÓN DE VARIABLES
|
# 1. INICIALIZACIÓN DE VARIABLES (T1)
|
||||||
self.net_monitor = iniciar_monitor_red()
|
self.net_monitor = iniciar_monitor_red()
|
||||||
self.figure = Figure(figsize=(5, 4), dpi=100)
|
self.figure = Figure(figsize=(5, 4), dpi=100)
|
||||||
self.canvas = None
|
self.canvas = None
|
||||||
|
|
@ -35,9 +52,14 @@ class PanelCentral(ttk.Frame):
|
||||||
self.crear_panel_chat_y_alumnos()
|
self.crear_panel_chat_y_alumnos()
|
||||||
|
|
||||||
self.iniciar_actualizacion_automatica()
|
self.iniciar_actualizacion_automatica()
|
||||||
|
self.iniciar_actualizacion_carrera()
|
||||||
|
|
||||||
|
# -------------------------------------------------------------
|
||||||
|
# 📦 ESTRUCTURA PRINCIPAL DEL PANEL
|
||||||
|
# -------------------------------------------------------------
|
||||||
|
|
||||||
def crear_area_principal_y_notas(self):
|
def crear_area_principal_y_notas(self):
|
||||||
"""Crea el contenedor de las subpestañas de T1 y el panel de notas (inferior izquierda)."""
|
"""Crea el contenedor de las subpestañas y el panel de notas."""
|
||||||
frame_izquierdo = ttk.Frame(self, style='TFrame')
|
frame_izquierdo = ttk.Frame(self, style='TFrame')
|
||||||
frame_izquierdo.grid(row=0, column=0, sticky="nsew")
|
frame_izquierdo.grid(row=0, column=0, sticky="nsew")
|
||||||
|
|
||||||
|
|
@ -45,19 +67,17 @@ class PanelCentral(ttk.Frame):
|
||||||
frame_izquierdo.grid_rowconfigure(1, weight=1)
|
frame_izquierdo.grid_rowconfigure(1, weight=1)
|
||||||
frame_izquierdo.grid_columnconfigure(0, weight=1)
|
frame_izquierdo.grid_columnconfigure(0, weight=1)
|
||||||
|
|
||||||
self.crear_sub_pestañas_t1(frame_izquierdo) # Llamada al método que crea las pestañas
|
self.crear_notebook_pestañas(frame_izquierdo)
|
||||||
|
|
||||||
# 2. El Panel de Notas
|
|
||||||
panel_notas = ttk.Frame(frame_izquierdo, style='Note.TFrame')
|
panel_notas = ttk.Frame(frame_izquierdo, style='Note.TFrame')
|
||||||
panel_notas.grid(row=1, column=0, sticky="nsew", pady=(5, 0))
|
panel_notas.grid(row=1, column=0, sticky="nsew", pady=(5, 0))
|
||||||
|
|
||||||
# Usando FUENTE_NOTA
|
|
||||||
ttk.Label(panel_notas, text="Panel para notas informativas y mensajes sobre la ejecución de los hilos.",
|
ttk.Label(panel_notas, text="Panel para notas informativas y mensajes sobre la ejecución de los hilos.",
|
||||||
style='Note.TLabel', anchor="nw", justify=tk.LEFT, padding=10, font=FUENTE_NOTA).pack(
|
style='Note.TLabel', anchor="nw", justify=tk.LEFT, padding=10, font=FUENTE_NOTA).pack(
|
||||||
expand=True, fill="both")
|
expand=True, fill="both")
|
||||||
|
|
||||||
def crear_sub_pestañas_t1(self, parent_frame):
|
def crear_notebook_pestañas(self, parent_frame):
|
||||||
"""Crea las pestañas internas para la tarea T1 y las empaqueta en la rejilla."""
|
"""Crea las pestañas internas para las tareas (T1, Carrera en Resultados)."""
|
||||||
sub_notebook = ttk.Notebook(parent_frame)
|
sub_notebook = ttk.Notebook(parent_frame)
|
||||||
sub_notebook.grid(row=0, column=0, sticky="nsew")
|
sub_notebook.grid(row=0, column=0, sticky="nsew")
|
||||||
|
|
||||||
|
|
@ -69,37 +89,191 @@ class PanelCentral(ttk.Frame):
|
||||||
sub_notebook.add(frame, text=sub_tab_text)
|
sub_notebook.add(frame, text=sub_tab_text)
|
||||||
self.tabs[sub_tab_text] = frame
|
self.tabs[sub_tab_text] = frame
|
||||||
|
|
||||||
# LÓGICA DE LA PESTAÑA DE RECURSOS
|
|
||||||
if sub_tab_text == "Recursos":
|
if sub_tab_text == "Recursos":
|
||||||
|
# Lógica de T1 - Gráfico de recursos
|
||||||
self.grafico_frame = ttk.Frame(frame, style='TFrame')
|
self.grafico_frame = ttk.Frame(frame, style='TFrame')
|
||||||
self.grafico_frame.pack(expand=True, fill="both", padx=10, pady=10)
|
self.grafico_frame.pack(expand=True, fill="both", padx=10, pady=10)
|
||||||
|
|
||||||
# Inicialización del Canvas de Matplotlib
|
|
||||||
self.canvas = FigureCanvasTkAgg(self.figure, master=self.grafico_frame)
|
self.canvas = FigureCanvasTkAgg(self.figure, master=self.grafico_frame)
|
||||||
self.canvas_widget = self.canvas.get_tk_widget()
|
self.canvas_widget = self.canvas.get_tk_widget()
|
||||||
self.canvas_widget.pack(expand=True, fill="both")
|
self.canvas_widget.pack(expand=True, fill="both")
|
||||||
|
|
||||||
sub_notebook.select(i)
|
# --- INTEGRACIÓN DE LA CARRERA EN LA PESTAÑA "Resultados" ---
|
||||||
|
elif sub_tab_text == "Resultados":
|
||||||
|
self.crear_interfaz_carrera(frame)
|
||||||
|
|
||||||
# Contenido de otras pestañas
|
# -------------------------------------------------------------
|
||||||
elif sub_tab_text == "Navegador":
|
# 🐪 LÓGICA DE T2 (CARRERA DE CAMELLOS)
|
||||||
# Usando COLOR_BLANCO, FUENTE_MONO y COLOR_TEXTO
|
# -------------------------------------------------------------
|
||||||
contenido_area = tk.Text(frame, wrap="word", padx=15, pady=15, bg=COLOR_BLANCO, relief="groove",
|
|
||||||
borderwidth=1, font=FUENTE_MONO, foreground=COLOR_TEXTO)
|
def crear_interfaz_carrera(self, parent_frame):
|
||||||
contenido_area.insert(tk.END,
|
"""Crea los controles y la visualización de la Carrera de Camellos."""
|
||||||
">>> ÁREA DE CONTENIDO / VISOR DE NAVEGADOR (Para mostrar resultados o web scraping)\n\n""Este es el espacio dedicado a la visualización de datos o interfaces específicas de cada tarea.")
|
|
||||||
contenido_area.pack(expand=True, fill="both", padx=5, pady=5)
|
# 1. Título y Controles (Solo título, el botón de inicio va en el lateral)
|
||||||
|
frame_controles = ttk.Frame(parent_frame, style='TFrame', padding=10)
|
||||||
|
frame_controles.pack(fill="x")
|
||||||
|
self.frame_carrera_controles = frame_controles
|
||||||
|
|
||||||
|
ttk.Label(frame_controles, text="Resultado de Carrera de Camellos (T2 Sincronización)",
|
||||||
|
style='TLabel', font=FUENTE_NEGOCIOS).pack(side="left", padx=5)
|
||||||
|
|
||||||
|
self.carrera_estado_label = ttk.Label(frame_controles, text="Estado.", style='TLabel', font=FUENTE_NEGOCIOS)
|
||||||
|
self.carrera_estado_label.pack(side="right", padx=10)
|
||||||
|
|
||||||
|
# 2. Marco de visualización de progreso
|
||||||
|
self.frame_progreso = ttk.Frame(parent_frame, style='TFrame', padding=10)
|
||||||
|
self.frame_progreso.pack(fill="both", expand=True)
|
||||||
|
|
||||||
|
ttk.Label(self.frame_progreso,
|
||||||
|
text="Presiona el botón 'App2 (T2-Carrera 🏁)' en el panel lateral para iniciar la simulación de hilos.",
|
||||||
|
style='TLabel').pack(pady=20)
|
||||||
|
|
||||||
|
def manejar_inicio_carrera(self):
|
||||||
|
"""Inicia una nueva carrera de camellos con un número aleatorio de participantes."""
|
||||||
|
if self.camellos and any(c.is_alive() for c in self.camellos):
|
||||||
|
self.carrera_estado_label.config(text="⚠️ Ya hay una carrera en curso.")
|
||||||
|
return
|
||||||
|
|
||||||
|
print("Iniciando Carrera de Camellos con número variable de participantes...")
|
||||||
|
|
||||||
|
# Genera un número aleatorio de camellos entre 10 y 20
|
||||||
|
num_camellos = random.randint(10, 20)
|
||||||
|
nombres = [f"Camello {i + 1}" for i in range(num_camellos)]
|
||||||
|
|
||||||
|
# Limpiar la visualización anterior
|
||||||
|
for widget in self.frame_progreso.winfo_children():
|
||||||
|
widget.destroy()
|
||||||
|
self.progreso_labels = {}
|
||||||
|
|
||||||
|
self.camellos = iniciar_carrera(nombres)
|
||||||
|
|
||||||
|
# --- MENSAJE SIMPLIFICADO ---
|
||||||
|
self.carrera_estado_label.config(text=f"¡Carrera en marcha! 💨 ({num_camellos} participantes)")
|
||||||
|
|
||||||
|
self.crear_visualizacion_carrera(nombres)
|
||||||
|
self.iniciar_actualizacion_carrera()
|
||||||
|
|
||||||
|
def crear_visualizacion_carrera(self, nombres):
|
||||||
|
"""Prepara el layout de la carrera."""
|
||||||
|
for widget in self.frame_progreso.winfo_children():
|
||||||
|
widget.destroy()
|
||||||
|
|
||||||
|
for i, nombre in enumerate(nombres):
|
||||||
|
ttk.Label(self.frame_progreso, text=f"{nombre}: ", style='TLabel', font=FUENTE_NEGOCIOS).grid(row=i,
|
||||||
|
column=0,
|
||||||
|
sticky="w")
|
||||||
|
|
||||||
|
label_progreso = ttk.Label(self.frame_progreso, text="[Esperando...]", style='TLabel',
|
||||||
|
foreground=COLOR_TEXTO)
|
||||||
|
label_progreso.grid(row=i, column=1, sticky="w", padx=10)
|
||||||
|
self.progreso_labels[nombre] = label_progreso
|
||||||
|
|
||||||
|
label_posicion = ttk.Label(self.frame_progreso, text="", style='TLabel')
|
||||||
|
label_posicion.grid(row=i, column=2, sticky="w")
|
||||||
|
self.progreso_labels[f'{nombre}_pos'] = label_posicion
|
||||||
|
|
||||||
|
self.carrera_info_label = ttk.Label(self.frame_progreso, text="", style='TLabel', font=FUENTE_NEGOCIOS)
|
||||||
|
self.carrera_info_label.grid(row=len(nombres), column=0, columnspan=3, sticky="w", pady=(10, 0))
|
||||||
|
|
||||||
|
def mostrar_progreso_activo(self, datos_activos):
|
||||||
|
"""Actualiza la visualización de la carrera mientras los hilos están corriendo."""
|
||||||
|
|
||||||
|
if not datos_activos['camellos']:
|
||||||
|
return
|
||||||
|
|
||||||
|
nombres_activos = [c['nombre'] for c in datos_activos['camellos']]
|
||||||
|
if not self.progreso_labels or any(n not in self.progreso_labels for n in nombres_activos):
|
||||||
|
self.crear_visualizacion_carrera(nombres_activos)
|
||||||
|
|
||||||
|
for estado in datos_activos['camellos']:
|
||||||
|
nombre = estado['nombre']
|
||||||
|
progreso = estado['progreso']
|
||||||
|
etiqueta_progreso = self.progreso_labels.get(nombre)
|
||||||
|
etiqueta_posicion = self.progreso_labels.get(f'{nombre}_pos')
|
||||||
|
|
||||||
|
if etiqueta_progreso:
|
||||||
|
barra = "█" * (progreso // 2)
|
||||||
|
texto_progreso = f"[{barra}{' ' * (25 - len(barra))}] ({progreso}/50) Estado: {estado['estado']}"
|
||||||
|
etiqueta_progreso.config(text=texto_progreso)
|
||||||
|
|
||||||
|
if etiqueta_posicion and estado['posicion']:
|
||||||
|
etiqueta_posicion.config(text=f"POS: {estado['posicion']} 🏆", foreground=COLOR_ACCION)
|
||||||
|
|
||||||
|
# --- MENSAJE SIMPLIFICADO ---
|
||||||
|
self.carrera_estado_label.config(text="Carrera en curso...")
|
||||||
|
self.carrera_info_label.config(text="")
|
||||||
|
|
||||||
|
def mostrar_resultado_final(self, resultado_final):
|
||||||
|
"""Muestra el resultado final persistente de la carrera."""
|
||||||
|
|
||||||
|
nombres_finales = [c['nombre'] for c in resultado_final['camellos']]
|
||||||
|
if not self.progreso_labels or any(n not in self.progreso_labels for n in nombres_finales):
|
||||||
|
self.crear_visualizacion_carrera(nombres_finales)
|
||||||
|
|
||||||
|
camellos_ordenados = sorted(resultado_final['camellos'], key=lambda x: x['posicion'])
|
||||||
|
|
||||||
|
for estado in camellos_ordenados:
|
||||||
|
nombre = estado['nombre']
|
||||||
|
etiqueta_progreso = self.progreso_labels.get(nombre)
|
||||||
|
etiqueta_posicion = self.progreso_labels.get(f'{nombre}_pos')
|
||||||
|
|
||||||
|
if etiqueta_progreso:
|
||||||
|
barra = "█" * (estado['progreso'] // 2)
|
||||||
|
texto_progreso = f"[{barra}{' ' * (25 - len(barra))}] (50/50) Estado: Meta"
|
||||||
|
etiqueta_progreso.config(text=texto_progreso)
|
||||||
|
|
||||||
|
if etiqueta_posicion:
|
||||||
|
etiqueta_posicion.config(text=f"POS: {estado['posicion']} {'🏆' if estado['posicion'] == 1 else ''}",
|
||||||
|
foreground=COLOR_ACCION)
|
||||||
|
|
||||||
|
# --- MENSAJE SIMPLIFICADO ---
|
||||||
|
self.carrera_estado_label.config(text="✅ Carrera Terminada.")
|
||||||
|
self.carrera_info_label.config(text=f"¡El ganador es: {resultado_final['ganador']}!",
|
||||||
|
font=FUENTE_TITULO, foreground=COLOR_EXITO)
|
||||||
|
|
||||||
|
def actualizar_carrera(self):
|
||||||
|
"""Ciclo de actualización visual: lee el estado activo o el resultado final persistente."""
|
||||||
|
|
||||||
|
estado = obtener_estado_carrera(self.camellos)
|
||||||
|
|
||||||
|
if estado['tipo'] == 'final':
|
||||||
|
self.mostrar_resultado_final(estado['datos'])
|
||||||
|
self.detener_actualizacion_carrera()
|
||||||
|
return
|
||||||
|
|
||||||
|
elif estado['tipo'] == 'activo':
|
||||||
|
self.mostrar_progreso_activo(estado['datos'])
|
||||||
|
|
||||||
|
self.after_carrera_id = self.after(self.INTERVALO_CARRERA_MS, self.actualizar_carrera)
|
||||||
|
|
||||||
|
def iniciar_actualizacion_carrera(self):
|
||||||
|
"""Inicia el ciclo de actualización visual (o carga el resultado guardado al inicio)."""
|
||||||
|
self.detener_actualizacion_carrera()
|
||||||
|
|
||||||
|
if RESULTADO_ULTIMO and not RESULTADO_ULTIMO.get('activa', True) and self.frame_progreso:
|
||||||
|
self.mostrar_resultado_final(RESULTADO_ULTIMO)
|
||||||
|
else:
|
||||||
|
# --- MENSAJE SIMPLIFICADO AL CARGAR ---
|
||||||
|
self.carrera_estado_label.config(text="Carrera lista para empezar.")
|
||||||
|
self.after_carrera_id = self.after(0, self.actualizar_carrera)
|
||||||
|
|
||||||
|
def detener_actualizacion_carrera(self):
|
||||||
|
"""Detiene el ciclo de actualización visual de la carrera."""
|
||||||
|
if self.after_carrera_id:
|
||||||
|
self.after_cancel(self.after_carrera_id)
|
||||||
|
self.after_carrera_id = None
|
||||||
|
print("Ciclo de actualización de carrera detenido.")
|
||||||
|
|
||||||
|
# -------------------------------------------------------------
|
||||||
|
# 📈 LÓGICA DE T1 (MONITOR DE RECURSOS)
|
||||||
|
# -------------------------------------------------------------
|
||||||
|
|
||||||
def actualizar_recursos(self):
|
def actualizar_recursos(self):
|
||||||
"""Obtiene los datos del sistema (incluyendo Red) y dibuja/redibuja el gráfico."""
|
"""Obtiene los datos del sistema (incluyendo Red) y dibuja/redibuja el gráfico."""
|
||||||
try:
|
try:
|
||||||
# 1. Obtener datos de red del hilo (en KB/s)
|
|
||||||
net_in, net_out = self.net_monitor.get_io_data_kb()
|
net_in, net_out = self.net_monitor.get_io_data_kb()
|
||||||
|
|
||||||
# 2. Actualizar el historial global (CPU y RAM se obtienen dentro de esta función)
|
|
||||||
actualizar_historial_datos(net_in, net_out)
|
actualizar_historial_datos(net_in, net_out)
|
||||||
|
|
||||||
# 3. Redibujar el gráfico
|
|
||||||
if self.canvas:
|
if self.canvas:
|
||||||
crear_grafico_recursos(self.figure)
|
crear_grafico_recursos(self.figure)
|
||||||
self.canvas.draw()
|
self.canvas.draw()
|
||||||
|
|
@ -107,24 +281,21 @@ class PanelCentral(ttk.Frame):
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
error_msg = f"Error al generar el gráfico de recursos: {e}"
|
error_msg = f"Error al generar el gráfico de recursos: {e}"
|
||||||
print(error_msg)
|
print(error_msg)
|
||||||
if self.canvas_widget.winfo_exists():
|
if self.canvas_widget and self.canvas_widget.winfo_exists():
|
||||||
self.canvas_widget.pack_forget()
|
self.canvas_widget.pack_forget()
|
||||||
error_label = ttk.Label(self.grafico_frame, text=error_msg, foreground='red', style='TLabel')
|
error_label = ttk.Label(self.grafico_frame, text=error_msg, foreground='red', style='TLabel')
|
||||||
error_label.pack(pady=20)
|
error_label.pack(pady=20)
|
||||||
self.detener_actualizacion_automatica()
|
self.detener_actualizacion_automatica()
|
||||||
|
|
||||||
# 4. Programar la siguiente llamada
|
|
||||||
self.after_id = self.after(self.INTERVALO_ACTUALIZACION_MS, self.actualizar_recursos)
|
self.after_id = self.after(self.INTERVALO_ACTUALIZACION_MS, self.actualizar_recursos)
|
||||||
|
|
||||||
# --- Control del Ciclo de Vida (Actualizar los nombres de las llamadas) ---
|
|
||||||
|
|
||||||
def iniciar_actualizacion_automatica(self):
|
def iniciar_actualizacion_automatica(self):
|
||||||
"""Inicia el ciclo de actualización del gráfico de recursos."""
|
"""Inicia el ciclo de actualización del gráfico de recursos."""
|
||||||
print("Iniciando actualización automática de recursos.")
|
print("Iniciando actualización automática de recursos.")
|
||||||
self.after_id = self.after(0, self.actualizar_recursos)
|
self.after_id = self.after(0, self.actualizar_recursos)
|
||||||
|
|
||||||
def detener_actualizacion_automatica(self):
|
def detener_actualizacion_automatica(self):
|
||||||
"""Detiene el ciclo de actualización periódica y el hilo de red."""
|
"""Detiene el ciclo de actualización periódica y el hilo de red (T1) y la carrera (T2)."""
|
||||||
if self.after_id:
|
if self.after_id:
|
||||||
self.after_cancel(self.after_id)
|
self.after_cancel(self.after_id)
|
||||||
self.after_id = None
|
self.after_id = None
|
||||||
|
|
@ -135,52 +306,72 @@ class PanelCentral(ttk.Frame):
|
||||||
self.net_monitor.join()
|
self.net_monitor.join()
|
||||||
print("Hilo de TrafficMeter detenido.")
|
print("Hilo de TrafficMeter detenido.")
|
||||||
|
|
||||||
|
self.detener_actualizacion_carrera()
|
||||||
|
|
||||||
|
# -------------------------------------------------------------
|
||||||
|
# 💬 PANEL CHAT, ALUMNOS Y APPS (PANEL LATERAL)
|
||||||
|
# -------------------------------------------------------------
|
||||||
|
|
||||||
def crear_panel_chat_y_alumnos(self, ):
|
def crear_panel_chat_y_alumnos(self, ):
|
||||||
"""Crea el panel de chat, lista de Alumnos y Reproductor de Música (columna derecha)."""
|
"""Crea el panel de chat y lista de Alumnos (columna derecha)."""
|
||||||
panel_chat = ttk.Frame(self, style='TFrame', padding="10")
|
panel_chat = ttk.Frame(self, style='TFrame', padding="10")
|
||||||
panel_chat.grid(row=0, column=1, sticky="nsew")
|
panel_chat.grid(row=0, column=1, sticky="nsew")
|
||||||
|
|
||||||
|
# Configuración de expansión (La fila 5, que contiene los alumnos, es la que se expande)
|
||||||
panel_chat.grid_rowconfigure(5, weight=1)
|
panel_chat.grid_rowconfigure(5, weight=1)
|
||||||
panel_chat.grid_rowconfigure(7, weight=0)
|
|
||||||
panel_chat.grid_columnconfigure(0, weight=1)
|
panel_chat.grid_columnconfigure(0, weight=1)
|
||||||
|
|
||||||
# 1. Título "Chat" - Usando COLOR_ACCION y FUENTE_TITULO
|
# --- FILA 0: Título ---
|
||||||
ttk.Label(panel_chat, text="Chat", foreground=COLOR_ACCION, font=FUENTE_TITULO, style='TLabel').grid(row=0,
|
ttk.Label(panel_chat, text="Chat", foreground=COLOR_ACCION, font=FUENTE_TITULO, style='TLabel').grid(row=0,
|
||||||
column=0,
|
column=0,
|
||||||
pady=(
|
pady=(
|
||||||
0,
|
0,
|
||||||
10),
|
10),
|
||||||
sticky="w")
|
sticky="w")
|
||||||
|
|
||||||
# 2. Área de Mensaje
|
# --- FILAS 1-3: Chat Input ---
|
||||||
ttk.Label(panel_chat, text="Mensaje", style='TLabel').grid(row=1, column=0, sticky="w")
|
ttk.Label(panel_chat, text="Mensaje", style='TLabel').grid(row=1, column=0, sticky="w")
|
||||||
|
|
||||||
# Usando color de fondo simulado para el chat
|
# Usando COLOR_BLANCO para el fondo del Text
|
||||||
chat_text = tk.Text(panel_chat, height=6, width=30, bg='#fff8e1', relief="solid", borderwidth=1,
|
chat_text = tk.Text(panel_chat, height=6, width=30, bg=COLOR_BLANCO, relief="solid", borderwidth=1,
|
||||||
font=('Arial', 10))
|
font=('Arial', 10))
|
||||||
chat_text.grid(row=2, column=0, sticky="ew", pady=(0, 5))
|
chat_text.grid(row=2, column=0, sticky="ew", pady=(0, 5))
|
||||||
|
|
||||||
ttk.Button(panel_chat, text="Enviar", style='Action.TButton').grid(row=3, column=0, pady=(0, 15), sticky="e")
|
ttk.Button(panel_chat, text="Enviar", style='Action.TButton').grid(row=3, column=0, pady=(0, 15), sticky="e")
|
||||||
|
|
||||||
# 3. Lista de Alumnos (Simulación)
|
# --- FILAS 4-7: Alumnos (Se expanden) ---
|
||||||
for i in range(1, 4):
|
for i in range(1, 4):
|
||||||
frame_alumno = ttk.Frame(panel_chat, style='Alumno.TFrame', padding=8)
|
frame_alumno = ttk.Frame(panel_chat, style='Alumno.TFrame', padding=8)
|
||||||
frame_alumno.grid(row=3 + i, column=0, sticky="ew", pady=5)
|
frame_alumno.grid(row=3 + i, column=0, sticky="ew", pady=5)
|
||||||
frame_alumno.grid_columnconfigure(0, weight=1)
|
frame_alumno.grid_columnconfigure(0, weight=1)
|
||||||
|
|
||||||
# El font de Alumno está en los estilos de VentanaPrincipal
|
|
||||||
ttk.Label(frame_alumno, text=f"Alumno {i}", font=('Arial', 11, 'bold'), style='Alumno.TLabel').grid(row=0,
|
ttk.Label(frame_alumno, text=f"Alumno {i}", font=('Arial', 11, 'bold'), style='Alumno.TLabel').grid(row=0,
|
||||||
column=0,
|
column=0,
|
||||||
sticky="w")
|
sticky="w")
|
||||||
|
|
||||||
ttk.Label(frame_alumno, text="Lorem ipsum dolor sit amet, consectetur adipiscing elit.", wraplength=250,
|
ttk.Label(frame_alumno, text="Lorem ipsum dolor sit amet, consectetur adipiscing elit.", wraplength=250,
|
||||||
justify=tk.LEFT, style='Alumno.TLabel').grid(row=1, column=0, sticky="w")
|
justify=tk.LEFT, style='Alumno.TLabel').grid(row=1, column=0, sticky="w")
|
||||||
|
|
||||||
ttk.Button(frame_alumno, text="↻", width=3, style='Action.TButton').grid(row=0, column=1, rowspan=2, padx=5,
|
ttk.Button(frame_alumno, text="↻", width=3, style='Action.TButton').grid(row=0, column=1, rowspan=2, padx=5,
|
||||||
sticky="ne")
|
sticky="ne")
|
||||||
|
|
||||||
# 4. Reproductor de Música (Simulado)
|
# --- FILA 8: Música ---
|
||||||
musica_frame = ttk.LabelFrame(panel_chat, text="Reproductor Música", padding=10, style='TFrame')
|
musica_frame = ttk.LabelFrame(panel_chat, text="Reproductor Música", padding=10, style='TFrame')
|
||||||
musica_frame.grid(row=8, column=0, sticky="ew", pady=(15, 0))
|
musica_frame.grid(row=8, column=0, sticky="ew", pady=(15, 0))
|
||||||
ttk.Label(musica_frame, text="[ Botones de Play/Stop y Control de Volumen ]", anchor="center",
|
ttk.Label(musica_frame, text="[ Botones de Play/Stop y Control de Volumen ]", anchor="center",
|
||||||
style='TLabel').pack(fill="x", padx=5, pady=5)
|
style='TLabel').pack(fill="x", padx=5, pady=5)
|
||||||
|
|
||||||
|
# --- FILA 9: Botones de Aplicación (Minijuegos) ---
|
||||||
|
frame_apps = ttk.LabelFrame(panel_chat, text="Minijuegos / Apps", padding=10, style='TFrame')
|
||||||
|
frame_apps.grid(row=9, column=0, sticky="ew", pady=(15, 0))
|
||||||
|
|
||||||
|
# Botón 1: Placeholder
|
||||||
|
ttk.Button(frame_apps, text="App1 (T3-Red)", width=15, style='Action.TButton').pack(side="left", padx=5)
|
||||||
|
|
||||||
|
# Botón 2: Inicia la Carrera de Camellos (T2 Sincronización)
|
||||||
|
ttk.Button(frame_apps,
|
||||||
|
text="App2 (T2-Carrera 🏁)",
|
||||||
|
command=self.manejar_inicio_carrera,
|
||||||
|
width=15,
|
||||||
|
style='Action.TButton').pack(side="left", padx=5)
|
||||||
|
|
||||||
|
# Asegurar que las filas de abajo no se expandan
|
||||||
|
panel_chat.grid_rowconfigure(8, weight=0)
|
||||||
|
panel_chat.grid_rowconfigure(9, weight=0)
|
||||||
|
|
@ -21,6 +21,7 @@ class PanelLateral(ttk.Frame):
|
||||||
|
|
||||||
def __init__(self, parent, central_panel=None, *args, **kwargs):
|
def __init__(self, parent, central_panel=None, *args, **kwargs):
|
||||||
super().__init__(parent, *args, **kwargs)
|
super().__init__(parent, *args, **kwargs)
|
||||||
|
# La referencia al PanelCentral es esencial para iniciar la carrera
|
||||||
self.central_panel = central_panel
|
self.central_panel = central_panel
|
||||||
|
|
||||||
self.configurar_estilos_locales(parent)
|
self.configurar_estilos_locales(parent)
|
||||||
|
|
@ -32,7 +33,6 @@ class PanelLateral(ttk.Frame):
|
||||||
|
|
||||||
# 2. Área de Extracción/Navegación
|
# 2. Área de Extracción/Navegación
|
||||||
acciones_extraccion = [
|
acciones_extraccion = [
|
||||||
# NOTA: Cambiamos el nombre de este comando si maneja una acción manual
|
|
||||||
("Actualizar Recursos", self.manejar_extraccion_datos),
|
("Actualizar Recursos", self.manejar_extraccion_datos),
|
||||||
("Navegar", self.manejar_navegacion),
|
("Navegar", self.manejar_navegacion),
|
||||||
("Buscar API Google", lambda: accion_placeholder("Buscar API Google"))
|
("Buscar API Google", lambda: accion_placeholder("Buscar API Google"))
|
||||||
|
|
@ -40,9 +40,15 @@ class PanelLateral(ttk.Frame):
|
||||||
self.crear_seccion(self, titulo="", acciones=acciones_extraccion)
|
self.crear_seccion(self, titulo="", acciones=acciones_extraccion)
|
||||||
|
|
||||||
# 3. Área de Aplicaciones
|
# 3. Área de Aplicaciones
|
||||||
|
|
||||||
|
# --- CAMBIO CLAVE: CONEXIÓN DE APP2 ---
|
||||||
|
|
||||||
|
# Definimos el comando para App2 usando el método que llama a PanelCentral
|
||||||
|
app2_comando = self.manejar_inicio_carrera_t2
|
||||||
|
|
||||||
acciones_aplicaciones = [
|
acciones_aplicaciones = [
|
||||||
("Visual Code", abrir_vscode),
|
("Visual Code", abrir_vscode),
|
||||||
("App2", lambda: accion_placeholder("App2")),
|
("App2 (Carrera 🏁)", app2_comando), # <--- CONEXIÓN REALIZADA
|
||||||
("App3", lambda: accion_placeholder("App3"))
|
("App3", lambda: accion_placeholder("App3"))
|
||||||
]
|
]
|
||||||
self.crear_seccion(self, titulo="Aplicaciones", acciones=acciones_aplicaciones)
|
self.crear_seccion(self, titulo="Aplicaciones", acciones=acciones_aplicaciones)
|
||||||
|
|
@ -59,29 +65,44 @@ class PanelLateral(ttk.Frame):
|
||||||
# 6. Panel de Notas
|
# 6. Panel de Notas
|
||||||
self.crear_editor_res_notes()
|
self.crear_editor_res_notes()
|
||||||
|
|
||||||
# --- NUEVO MÉTODO PARA MANEJAR LA NAVEGACIÓN ---
|
# --- NUEVO MÉTODO PARA MANEJAR LA CARRERA (App2) ---
|
||||||
|
def manejar_inicio_carrera_t2(self):
|
||||||
|
"""
|
||||||
|
Llama al método 'manejar_inicio_carrera' del Panel Central.
|
||||||
|
"""
|
||||||
|
if self.central_panel:
|
||||||
|
print("Botón App2 presionado. Iniciando Carrera de Camellos en Panel Central...")
|
||||||
|
# Aquí es donde se llama a la función expuesta por PanelCentral
|
||||||
|
self.central_panel.manejar_inicio_carrera()
|
||||||
|
|
||||||
|
# Opcional: Cambiar automáticamente a la pestaña Resultados
|
||||||
|
if "Resultados" in self.central_panel.tabs:
|
||||||
|
notebook = self.central_panel.tabs["Resultados"].winfo_toplevel().winfo_children()[0]
|
||||||
|
if isinstance(notebook, ttk.Notebook):
|
||||||
|
# Asume que el Notebook es el primer widget hijo del frame principal
|
||||||
|
notebook.select(self.central_panel.tabs["Resultados"])
|
||||||
|
else:
|
||||||
|
messagebox.showerror("Error", "El Panel Central no está inicializado.")
|
||||||
|
|
||||||
|
# --- MÉTODOS EXISTENTES ---
|
||||||
def manejar_navegacion(self, event=None):
|
def manejar_navegacion(self, event=None):
|
||||||
"""
|
"""
|
||||||
Obtiene el texto de la entrada superior y llama a la función de navegación.
|
Obtiene el texto de la entrada superior y llama a la función de navegación.
|
||||||
"""
|
"""
|
||||||
url = self.entrada_superior.get()
|
url = self.entrada_superior.get()
|
||||||
if navegar_a_url(url):
|
if navegar_a_url(url):
|
||||||
# Limpiar la casilla si la navegación fue exitosa
|
|
||||||
self.entrada_superior.delete(0, tk.END)
|
self.entrada_superior.delete(0, tk.END)
|
||||||
|
|
||||||
# --- LÓGICA DEL EDITOR res/notes ---
|
|
||||||
|
|
||||||
def crear_editor_res_notes(self):
|
def crear_editor_res_notes(self):
|
||||||
"""Crea el editor de texto simple para el archivo res/notes."""
|
"""Crea el editor de texto simple para el archivo res/notes."""
|
||||||
|
|
||||||
# Usando FUENTE_NEGOCIOS
|
|
||||||
ttk.Label(self, text="Editor Simple (res/notes)", font=FUENTE_NEGOCIOS).pack(fill="x", pady=(10, 0),
|
ttk.Label(self, text="Editor Simple (res/notes)", font=FUENTE_NEGOCIOS).pack(fill="x", pady=(10, 0),
|
||||||
padx=5)
|
padx=5)
|
||||||
|
|
||||||
frame_editor = ttk.Frame(self, padding=5)
|
frame_editor = ttk.Frame(self, padding=5)
|
||||||
frame_editor.pack(fill="x", padx=5, pady=(0, 10))
|
frame_editor.pack(fill="x", padx=5, pady=(0, 10))
|
||||||
|
|
||||||
# 1. Widget de texto - Aplicamos el ancho fijo, COLOR_BLANCO y FUENTE_MONO
|
# 1. Widget de texto
|
||||||
self.notes_text_editor = tk.Text(
|
self.notes_text_editor = tk.Text(
|
||||||
frame_editor,
|
frame_editor,
|
||||||
height=8,
|
height=8,
|
||||||
|
|
@ -90,7 +111,7 @@ class PanelLateral(ttk.Frame):
|
||||||
bg=COLOR_BLANCO,
|
bg=COLOR_BLANCO,
|
||||||
relief="solid",
|
relief="solid",
|
||||||
borderwidth=1,
|
borderwidth=1,
|
||||||
font=FUENTE_MONO # Fuente tipo terminal
|
font=FUENTE_MONO
|
||||||
)
|
)
|
||||||
self.notes_text_editor.pack(fill="x", expand=False)
|
self.notes_text_editor.pack(fill="x", expand=False)
|
||||||
|
|
||||||
|
|
@ -134,12 +155,10 @@ class PanelLateral(ttk.Frame):
|
||||||
messagebox.showerror("❌ Error al Guardar", message)
|
messagebox.showerror("❌ Error al Guardar", message)
|
||||||
print(f"FALLO AL GUARDAR: {message}")
|
print(f"FALLO AL GUARDAR: {message}")
|
||||||
|
|
||||||
# --- MÉTODOS EXISTENTES ---
|
|
||||||
def manejar_extraccion_datos(self):
|
def manejar_extraccion_datos(self):
|
||||||
"""
|
"""
|
||||||
Llama a la lógica de actualización del gráfico de recursos en el panel central (actualización manual).
|
Llama a la lógica de actualización del gráfico de recursos en el panel central (actualización manual).
|
||||||
"""
|
"""
|
||||||
# NOTA: Renombramos a 'actualizar_recursos' para ser consistente con panel_central.py
|
|
||||||
if self.central_panel:
|
if self.central_panel:
|
||||||
print("Activando actualización del gráfico de Recursos (Manual)...")
|
print("Activando actualización del gráfico de Recursos (Manual)...")
|
||||||
self.central_panel.actualizar_recursos()
|
self.central_panel.actualizar_recursos()
|
||||||
|
|
@ -160,31 +179,26 @@ class PanelLateral(ttk.Frame):
|
||||||
"""Configura estilos para los widgets del panel lateral, usando constantes importadas."""
|
"""Configura estilos para los widgets del panel lateral, usando constantes importadas."""
|
||||||
style = ttk.Style(parent)
|
style = ttk.Style(parent)
|
||||||
|
|
||||||
# Estilos existentes (Usando constantes importadas)
|
|
||||||
style.configure('Yellow.TEntry', fieldbackground='#fff8e1', foreground=COLOR_TEXTO, padding=[5, 5],
|
style.configure('Yellow.TEntry', fieldbackground='#fff8e1', foreground=COLOR_TEXTO, padding=[5, 5],
|
||||||
relief='solid', borderwidth=1)
|
relief='solid', borderwidth=1)
|
||||||
|
|
||||||
# Botones de Acción (Green.TButton)
|
|
||||||
style.configure('Green.TButton', background=COLOR_EXITO, foreground=COLOR_BLANCO, font=FUENTE_NEGOCIOS,
|
style.configure('Green.TButton', background=COLOR_EXITO, foreground=COLOR_BLANCO, font=FUENTE_NEGOCIOS,
|
||||||
relief='flat', padding=[10, 5])
|
relief='flat', padding=[10, 5])
|
||||||
style.map('Green.TButton', background=[('active', '#388E3C'), ('pressed',
|
style.map('Green.TButton', background=[('active', '#388E3C'), ('pressed',
|
||||||
'#1B5E20')]) # Manteniendo los tonos verdes originales para hover/pressed
|
'#1B5E20')])
|
||||||
|
|
||||||
# Botones de Acción Global (Action.TButton)
|
|
||||||
style.configure('Action.TButton', background=COLOR_ACCION, foreground=COLOR_BLANCO, font=FUENTE_NEGOCIOS,
|
style.configure('Action.TButton', background=COLOR_ACCION, foreground=COLOR_BLANCO, font=FUENTE_NEGOCIOS,
|
||||||
relief='flat', padding=[10, 5])
|
relief='flat', padding=[10, 5])
|
||||||
style.map('Action.TButton', background=[('active', COLOR_ACCION_HOVER), ('pressed', COLOR_ACCION_PRESSED)])
|
style.map('Action.TButton', background=[('active', COLOR_ACCION_HOVER), ('pressed', COLOR_ACCION_PRESSED)])
|
||||||
|
|
||||||
# NUEVO ESTILO: Botones pequeños para el editor de notas (SmallAction.TButton)
|
|
||||||
style.configure('SmallAction.TButton', background=COLOR_ACCION, foreground=COLOR_BLANCO,
|
style.configure('SmallAction.TButton', background=COLOR_ACCION, foreground=COLOR_BLANCO,
|
||||||
font=('Arial', 9, 'bold'),
|
font=(FUENTE_FAMILIA, 9, 'bold'),
|
||||||
relief='flat', padding=[5, 3])
|
relief='flat', padding=[5, 3])
|
||||||
style.map('SmallAction.TButton', background=[('active', COLOR_ACCION_HOVER), ('pressed', COLOR_ACCION_PRESSED)])
|
style.map('SmallAction.TButton', background=[('active', COLOR_ACCION_HOVER), ('pressed', COLOR_ACCION_PRESSED)])
|
||||||
|
|
||||||
def crear_seccion(self, parent_frame, titulo, acciones):
|
def crear_seccion(self, parent_frame, titulo, acciones):
|
||||||
"""Función helper para crear secciones de etiquetas y botones."""
|
"""Función helper para crear secciones de etiquetas y botones."""
|
||||||
if titulo:
|
if titulo:
|
||||||
# Usando FUENTE_NEGOCIOS
|
|
||||||
ttk.Label(parent_frame, text=titulo, font=FUENTE_NEGOCIOS).pack(fill="x", pady=(10, 0), padx=5)
|
ttk.Label(parent_frame, text=titulo, font=FUENTE_NEGOCIOS).pack(fill="x", pady=(10, 0), padx=5)
|
||||||
|
|
||||||
frame_botones = ttk.Frame(parent_frame, style='TFrame')
|
frame_botones = ttk.Frame(parent_frame, style='TFrame')
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue