"error": {
"message": "You exceeded your current quota, please check your plan and billing details. For more information on this error, read the docs: https://platform.openai.com/docs/guides/error-codes/api-errors.",
"type": "insufficient_quota",
"param": null,
"code": "insufficient_quota"
}
}
This commit is contained in:
parent
3916075743
commit
0ea61c5112
|
|
@ -0,0 +1,24 @@
|
|||
# Módulo: logica/T2/getLocalTime.py
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
def obtener_hora_local():
|
||||
"""
|
||||
Devuelve la fecha y hora actual del sistema en un formato legible.
|
||||
Ej: "Jueves, 04 de Diciembre de 2025 | 12:07:05 AM"
|
||||
"""
|
||||
now = datetime.now()
|
||||
|
||||
# Formato de ejemplo:
|
||||
# %A: Día de la semana completo (ej: Jueves)
|
||||
# %d: Día del mes (ej: 04)
|
||||
# %B: Mes completo (ej: Diciembre)
|
||||
# %Y: Año (ej: 2025)
|
||||
# %H: Hora (24h) o %I: Hora (12h)
|
||||
# %p: AM/PM
|
||||
|
||||
# Usaremos una simple y clara: DD/MM/YYYY HH:MM:SS
|
||||
formato_completo = now.strftime("%d/%m/%Y %H:%M:%S")
|
||||
|
||||
return formato_completo
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
# Módulo: logica/T2/getWeather.py
|
||||
|
||||
import random
|
||||
|
||||
|
||||
def obtener_datos_clima():
|
||||
"""
|
||||
Simula la obtención de la temperatura y el estado del tiempo local.
|
||||
Devuelve la temperatura en grados Celsius y un estado del tiempo.
|
||||
"""
|
||||
# Simulación de datos
|
||||
temperatura = random.randint(10, 28)
|
||||
|
||||
# Simulación de estado del tiempo
|
||||
estados = ["Soleado ☀️", "Nublado ☁️", "Lluvia 🌧️", "Tormenta ⛈️"]
|
||||
estado_tiempo = random.choice(estados)
|
||||
|
||||
return f"{temperatura}°C ({estado_tiempo})"
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
# Módulo: vista/config.py
|
||||
|
||||
# --- CONSTANTES GENERALES ---
|
||||
|
||||
# Dimensiones y Tiempos
|
||||
ANCHO_PANEL_LATERAL = 300
|
||||
INTERVALO_RELOJ_MS = 1000
|
||||
INTERVALO_CLIMA_MS = 60000
|
||||
INTERVALO_RECURSOS_MS = 1000
|
||||
ANCHO_CARACTERES_PANEL_LATERAL = 35
|
||||
|
||||
# --- CONSTANTES DE ESTILO (Colores) ---
|
||||
|
||||
COLOR_FONDO = "#f9f9f9" # Fondo principal
|
||||
COLOR_ACCION = "#0078d4" # Azul para botones de acción/texto principal
|
||||
COLOR_ACCION_HOVER = "#005a9e" # Azul oscuro para estado activo/hover
|
||||
COLOR_ACCION_PRESSED = "#003c6e"# Azul muy oscuro para estado presionado
|
||||
COLOR_EXITO = "#4CAF50" # Verde para mensajes de éxito/notas
|
||||
COLOR_ADVERTENCIA = "#ffc107" # Amarillo para advertencias/chat
|
||||
COLOR_TEXTO = "#333333" # Texto oscuro principal
|
||||
COLOR_BLANCO = "white" # Fondo de áreas de contenido, texto en botones
|
||||
|
||||
# --- CONSTANTES DE FUENTE ---
|
||||
|
||||
# Familia de fuente principal
|
||||
FUENTE_FAMILIA = 'Arial'
|
||||
|
||||
# Tamaños y Estilos
|
||||
FUENTE_NORMAL = (FUENTE_FAMILIA, 9)
|
||||
FUENTE_NEGOCIOS = (FUENTE_FAMILIA, 10, 'bold') # Para botones y pestañas
|
||||
FUENTE_TITULO = (FUENTE_FAMILIA, 18, 'bold') # Para títulos grandes (ej: Chat)
|
||||
FUENTE_NOTA = (FUENTE_FAMILIA, 9, 'italic') # Para notas y texto de estado
|
||||
FUENTE_MONO = ('Consolas', 10) # Fuente monoespaciada para código/logs
|
||||
|
|
@ -2,30 +2,29 @@
|
|||
|
||||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
|
||||
|
||||
from logica.T1.trafficMeter import iniciar_monitor_red
|
||||
from logica.T1.graficos import crear_grafico_recursos, actualizar_historial_datos
|
||||
from matplotlib.figure import Figure
|
||||
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
|
||||
|
||||
# --- IMPORTACIÓN DE CONSTANTES DESDE vista/config.py ---
|
||||
from vista.config import *
|
||||
|
||||
class PanelCentral(ttk.Frame):
|
||||
"""Contiene el Notebook (subpestañas de T1), el panel de Notas y el panel de Chat."""
|
||||
|
||||
# Reducimos el intervalo de actualización a 1000ms (1 segundo) para gráficos fluidos
|
||||
INTERVALO_ACTUALIZACION_MS = 1000
|
||||
# Usamos la constante importada para el intervalo de actualización
|
||||
INTERVALO_ACTUALIZACION_MS = INTERVALO_RECURSOS_MS
|
||||
|
||||
def __init__(self, parent, *args, **kwargs):
|
||||
super().__init__(parent, *args, **kwargs)
|
||||
self.after_id = None # ID para controlar el timer de tk.after
|
||||
self.after_id = None
|
||||
self.parent_root = parent.winfo_toplevel()
|
||||
|
||||
# 1. INICIALIZACIÓN DE VARIABLES
|
||||
self.net_monitor = iniciar_monitor_red() # <-- INICIAR EL HILO DE RED
|
||||
|
||||
# Objeto Figure de Matplotlib (debe crearse antes de crear_sub_pestañas_t1)
|
||||
self.net_monitor = iniciar_monitor_red()
|
||||
self.figure = Figure(figsize=(5, 4), dpi=100)
|
||||
self.canvas = None # Se inicializará en crear_sub_pestañas_t1
|
||||
self.canvas = None
|
||||
|
||||
# 2. CONFIGURACIÓN DEL LAYOUT
|
||||
self.grid_columnconfigure(0, weight=3)
|
||||
|
|
@ -35,7 +34,6 @@ class PanelCentral(ttk.Frame):
|
|||
self.crear_area_principal_y_notas()
|
||||
self.crear_panel_chat_y_alumnos()
|
||||
|
||||
# La actualización se inicia al final de __init__
|
||||
self.iniciar_actualizacion_automatica()
|
||||
|
||||
def crear_area_principal_y_notas(self):
|
||||
|
|
@ -47,15 +45,15 @@ class PanelCentral(ttk.Frame):
|
|||
frame_izquierdo.grid_rowconfigure(1, weight=1)
|
||||
frame_izquierdo.grid_columnconfigure(0, weight=1)
|
||||
|
||||
# 1. El Notebook de T1 (Sub-pestañas)
|
||||
self.crear_sub_pestañas_t1(frame_izquierdo)
|
||||
self.crear_sub_pestañas_t1(frame_izquierdo) # Llamada al método que crea las pestañas
|
||||
|
||||
# 2. El Panel de Notas
|
||||
panel_notas = ttk.Frame(frame_izquierdo, style='Note.TFrame')
|
||||
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.",
|
||||
style='Note.TLabel', anchor="nw", justify=tk.LEFT, padding=10, font=('Arial', 9, 'italic')).pack(
|
||||
style='Note.TLabel', anchor="nw", justify=tk.LEFT, padding=10, font=FUENTE_NOTA).pack(
|
||||
expand=True, fill="both")
|
||||
|
||||
def crear_sub_pestañas_t1(self, parent_frame):
|
||||
|
|
@ -81,21 +79,19 @@ class PanelCentral(ttk.Frame):
|
|||
self.canvas_widget = self.canvas.get_tk_widget()
|
||||
self.canvas_widget.pack(expand=True, fill="both")
|
||||
|
||||
# La actualización automática iniciada en __init__ se encarga de esto
|
||||
sub_notebook.select(i)
|
||||
|
||||
# Contenido de otras pestañas
|
||||
elif sub_tab_text == "Navegador":
|
||||
contenido_area = tk.Text(frame, wrap="word", padx=15, pady=15, bg='white', relief="groove",
|
||||
borderwidth=1, font=('Consolas', 10), foreground="#555555")
|
||||
# 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)
|
||||
contenido_area.insert(tk.END,
|
||||
">>> Á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.")
|
||||
">>> Á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)
|
||||
|
||||
def actualizar_grafico_recursos(self):
|
||||
"""
|
||||
Obtiene los datos del sistema (incluyendo Red) y dibuja/redibuja el gráfico.
|
||||
"""
|
||||
def actualizar_recursos(self):
|
||||
"""Obtiene los datos del sistema (incluyendo Red) y dibuja/redibuja el gráfico."""
|
||||
try:
|
||||
# 1. Obtener datos de red del hilo (en KB/s)
|
||||
net_in, net_out = self.net_monitor.get_io_data_kb()
|
||||
|
|
@ -104,32 +100,28 @@ class PanelCentral(ttk.Frame):
|
|||
actualizar_historial_datos(net_in, net_out)
|
||||
|
||||
# 3. Redibujar el gráfico
|
||||
crear_grafico_recursos(self.figure)
|
||||
self.canvas.draw()
|
||||
if self.canvas:
|
||||
crear_grafico_recursos(self.figure)
|
||||
self.canvas.draw()
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Error al generar el gráfico de recursos: {e}"
|
||||
print(error_msg)
|
||||
# Manejo de errores visual en la pestaña (limpiar y mostrar error)
|
||||
if self.canvas_widget.winfo_exists():
|
||||
self.canvas_widget.pack_forget()
|
||||
|
||||
error_label = ttk.Label(self.grafico_frame, text=error_msg, foreground='red', style='TLabel')
|
||||
error_label.pack(pady=20)
|
||||
|
||||
# Detener la actualización para evitar bucle de error
|
||||
self.detener_actualizacion_automatica()
|
||||
|
||||
# 4. Programar la siguiente llamada (solo si no hay error crítico que detenga la actualización)
|
||||
self.after_id = self.after(self.INTERVALO_ACTUALIZACION_MS, self.actualizar_grafico_recursos)
|
||||
# 4. Programar la siguiente llamada
|
||||
self.after_id = self.after(self.INTERVALO_ACTUALIZACION_MS, self.actualizar_recursos)
|
||||
|
||||
# --- Control del Ciclo de Vida ---
|
||||
# --- Control del Ciclo de Vida (Actualizar los nombres de las llamadas) ---
|
||||
|
||||
def iniciar_actualizacion_automatica(self):
|
||||
"""Inicia el ciclo de actualización del gráfico de recursos."""
|
||||
print("Iniciando actualización automática de recursos.")
|
||||
# Programar la primera llamada inmediatamente
|
||||
self.after_id = self.after(0, self.actualizar_grafico_recursos)
|
||||
self.after_id = self.after(0, self.actualizar_recursos)
|
||||
|
||||
def detener_actualizacion_automatica(self):
|
||||
"""Detiene el ciclo de actualización periódica y el hilo de red."""
|
||||
|
|
@ -138,10 +130,9 @@ class PanelCentral(ttk.Frame):
|
|||
self.after_id = None
|
||||
print("Ciclo de actualización de gráficos detenido.")
|
||||
|
||||
# Detener el monitor de red al cerrar la aplicación
|
||||
if self.net_monitor:
|
||||
self.net_monitor.stop()
|
||||
self.net_monitor.join() # Esperar a que el hilo termine
|
||||
self.net_monitor.join()
|
||||
print("Hilo de TrafficMeter detenido.")
|
||||
|
||||
def crear_panel_chat_y_alumnos(self, ):
|
||||
|
|
@ -153,19 +144,20 @@ class PanelCentral(ttk.Frame):
|
|||
panel_chat.grid_rowconfigure(7, weight=0)
|
||||
panel_chat.grid_columnconfigure(0, weight=1)
|
||||
|
||||
# 1. Título "Chat"
|
||||
ttk.Label(panel_chat, text="Chat", foreground="#0078d4", font=("Arial", 18, "bold"), style='TLabel').grid(row=0,
|
||||
column=0,
|
||||
pady=(
|
||||
0,
|
||||
10),
|
||||
sticky="w")
|
||||
# 1. Título "Chat" - Usando COLOR_ACCION y FUENTE_TITULO
|
||||
ttk.Label(panel_chat, text="Chat", foreground=COLOR_ACCION, font=FUENTE_TITULO, style='TLabel').grid(row=0,
|
||||
column=0,
|
||||
pady=(
|
||||
0,
|
||||
10),
|
||||
sticky="w")
|
||||
|
||||
# 2. Área de Mensaje
|
||||
ttk.Label(panel_chat, text="Mensaje", style='TLabel').grid(row=1, column=0, sticky="w")
|
||||
|
||||
# Usando color de fondo simulado para el chat
|
||||
chat_text = tk.Text(panel_chat, height=6, width=30, bg='#fff8e1', relief="solid", borderwidth=1,
|
||||
font=('Arial', 10))
|
||||
font=('Arial', 10))
|
||||
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")
|
||||
|
|
@ -176,18 +168,19 @@ class PanelCentral(ttk.Frame):
|
|||
frame_alumno.grid(row=3 + i, column=0, sticky="ew", pady=5)
|
||||
frame_alumno.grid_columnconfigure(0, weight=1)
|
||||
|
||||
ttk.Label(frame_alumno, text=f"Alumno {i}", font=("Arial", 11, "bold"), style='Alumno.TLabel').grid(row=0,
|
||||
# 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,
|
||||
column=0,
|
||||
sticky="w")
|
||||
|
||||
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,
|
||||
sticky="ne")
|
||||
sticky="ne")
|
||||
|
||||
# 4. Reproductor de Música (Simulado)
|
||||
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))
|
||||
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)
|
||||
|
|
@ -9,16 +9,15 @@ from logica.T1.runVScode import abrir_vscode
|
|||
from logica.T1.textEditor import cargar_contenido_res_notes, guardar_contenido_res_notes
|
||||
from logica.T1.openBrowser import navegar_a_url
|
||||
|
||||
# --- IMPORTACIÓN DE CONSTANTES DESDE vista/config.py ---
|
||||
from vista.config import *
|
||||
|
||||
|
||||
class PanelLateral(ttk.Frame):
|
||||
"""Contiene el menú de botones, entradas para las tareas y el editor simple para res/notes."""
|
||||
|
||||
# Definimos un ancho fijo en caracteres. Esto es crucial para que Tkinter
|
||||
# no intente expandir el panel lateral más allá de lo deseado.
|
||||
ANCHO_CARACTERES_FIJO = 35
|
||||
|
||||
ANCHO_CARACTERES_FIJO = 35
|
||||
# Usamos la constante importada
|
||||
ANCHO_CARACTERES_FIJO = ANCHO_CARACTERES_PANEL_LATERAL
|
||||
|
||||
def __init__(self, parent, central_panel=None, *args, **kwargs):
|
||||
super().__init__(parent, *args, **kwargs)
|
||||
|
|
@ -26,15 +25,15 @@ class PanelLateral(ttk.Frame):
|
|||
|
||||
self.configurar_estilos_locales(parent)
|
||||
|
||||
# 1. Entrada superior (amarilla) - ¡Guardamos la referencia!
|
||||
# 1. Entrada superior (amarilla)
|
||||
self.entrada_superior = ttk.Entry(self, width=self.ANCHO_CARACTERES_FIJO, style='Yellow.TEntry')
|
||||
self.entrada_superior.pack(fill="x", pady=10, padx=5, ipady=3)
|
||||
self.entrada_superior.bind('<Return>', self.manejar_navegacion) # Opcional: Ejecutar con Enter
|
||||
self.entrada_superior.bind('<Return>', self.manejar_navegacion)
|
||||
|
||||
# 2. Área de Extracción/Navegación
|
||||
acciones_extraccion = [
|
||||
("Extraer datos", self.manejar_extraccion_datos),
|
||||
# 2. Asignamos el nuevo método de manejo a este botón
|
||||
# NOTA: Cambiamos el nombre de este comando si maneja una acción manual
|
||||
("Actualizar Recursos", self.manejar_extraccion_datos),
|
||||
("Navegar", self.manejar_navegacion),
|
||||
("Buscar API Google", lambda: accion_placeholder("Buscar API Google"))
|
||||
]
|
||||
|
|
@ -69,27 +68,29 @@ class PanelLateral(ttk.Frame):
|
|||
if navegar_a_url(url):
|
||||
# Limpiar la casilla si la navegación fue exitosa
|
||||
self.entrada_superior.delete(0, tk.END)
|
||||
|
||||
# --- LÓGICA DEL EDITOR res/notes ---
|
||||
|
||||
def crear_editor_res_notes(self):
|
||||
"""Crea el editor de texto simple para el archivo res/notes."""
|
||||
|
||||
ttk.Label(self, text="Editor Simple (res/notes)", font=('Arial', 11, 'bold')).pack(fill="x", pady=(10, 0),
|
||||
padx=5)
|
||||
# Usando FUENTE_NEGOCIOS
|
||||
ttk.Label(self, text="Editor Simple (res/notes)", font=FUENTE_NEGOCIOS).pack(fill="x", pady=(10, 0),
|
||||
padx=5)
|
||||
|
||||
frame_editor = ttk.Frame(self, padding=5)
|
||||
frame_editor.pack(fill="x", padx=5, pady=(0, 10))
|
||||
|
||||
# 1. Widget de texto - Aplicamos el ancho fijo
|
||||
# 1. Widget de texto - Aplicamos el ancho fijo, COLOR_BLANCO y FUENTE_MONO
|
||||
self.notes_text_editor = tk.Text(
|
||||
frame_editor,
|
||||
height=8,
|
||||
width=self.ANCHO_CARACTERES_FIJO,
|
||||
wrap="word",
|
||||
bg='white',
|
||||
bg=COLOR_BLANCO,
|
||||
relief="solid",
|
||||
borderwidth=1,
|
||||
font=('Consolas', 9) # Fuente tipo terminal
|
||||
font=FUENTE_MONO # Fuente tipo terminal
|
||||
)
|
||||
self.notes_text_editor.pack(fill="x", expand=False)
|
||||
|
||||
|
|
@ -97,19 +98,18 @@ class PanelLateral(ttk.Frame):
|
|||
frame_botones = ttk.Frame(frame_editor)
|
||||
frame_botones.pack(fill="x", pady=(5, 0))
|
||||
|
||||
# Se usa 'SmallAction.TButton' para reducir el padding y asegurar que quepan
|
||||
ttk.Button(frame_botones, text="Guardar", command=self.guardar_res_notes, style='SmallAction.TButton').pack(
|
||||
side=tk.RIGHT)
|
||||
ttk.Button(frame_botones, text="Cargar", command=self.cargar_res_notes, style='SmallAction.TButton').pack(
|
||||
side=tk.LEFT)
|
||||
|
||||
self.cargar_res_notes(initial_load=True) # Carga inicial
|
||||
self.cargar_res_notes(initial_load=True)
|
||||
|
||||
def cargar_res_notes(self, initial_load=False):
|
||||
"""Carga el contenido de res/notes al editor de texto lateral."""
|
||||
contenido = cargar_contenido_res_notes()
|
||||
|
||||
self.notes_text_editor.delete("1.0", tk.END) # Limpiar contenido actual
|
||||
self.notes_text_editor.delete("1.0", tk.END)
|
||||
|
||||
if "Error al cargar:" in contenido:
|
||||
self.notes_text_editor.insert(tk.END, contenido)
|
||||
|
|
@ -123,9 +123,6 @@ class PanelLateral(ttk.Frame):
|
|||
|
||||
def guardar_res_notes(self):
|
||||
"""Guarda el contenido del editor de texto lateral en res/notes."""
|
||||
# CORRECCIÓN: Quitamos .strip() para que el guardado refleje fielmente el contenido
|
||||
# del widget, incluyendo saltos de línea finales, lo que soluciona la confusión
|
||||
# de que "se guarda, pero el archivo está vacío".
|
||||
contenido = self.notes_text_editor.get("1.0", tk.END)
|
||||
|
||||
success, message = guardar_contenido_res_notes(contenido)
|
||||
|
|
@ -140,12 +137,12 @@ class PanelLateral(ttk.Frame):
|
|||
# --- MÉTODOS EXISTENTES ---
|
||||
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:
|
||||
print("Activando actualización del gráfico de Recursos (Manual)...")
|
||||
self.central_panel.actualizar_grafico_recursos()
|
||||
self.central_panel.actualizar_recursos()
|
||||
else:
|
||||
messagebox.showerror("Error", "El Panel Central no está inicializado.")
|
||||
|
||||
|
|
@ -160,29 +157,35 @@ class PanelLateral(ttk.Frame):
|
|||
messagebox.showerror("❌ Error en el Backup", message)
|
||||
|
||||
def configurar_estilos_locales(self, parent):
|
||||
"""Configura estilos para los widgets del panel lateral."""
|
||||
"""Configura estilos para los widgets del panel lateral, usando constantes importadas."""
|
||||
style = ttk.Style(parent)
|
||||
|
||||
# Estilos existentes
|
||||
style.configure('Yellow.TEntry', fieldbackground='#fff8e1', foreground='#333333', padding=[5, 5],
|
||||
# Estilos existentes (Usando constantes importadas)
|
||||
style.configure('Yellow.TEntry', fieldbackground='#fff8e1', foreground=COLOR_TEXTO, padding=[5, 5],
|
||||
relief='solid', borderwidth=1)
|
||||
style.configure('Green.TButton', background='#4CAF50', foreground='white', font=('Arial', 10, 'bold'),
|
||||
relief='flat', padding=[10, 5])
|
||||
style.map('Green.TButton', background=[('active', '#388E3C'), ('pressed', '#1B5E20')])
|
||||
|
||||
style.configure('Action.TButton', background='#0078d4', foreground='white', font=('Arial', 10, 'bold'),
|
||||
# Botones de Acción (Green.TButton)
|
||||
style.configure('Green.TButton', background=COLOR_EXITO, foreground=COLOR_BLANCO, font=FUENTE_NEGOCIOS,
|
||||
relief='flat', padding=[10, 5])
|
||||
style.map('Action.TButton', background=[('active', '#005a9e'), ('pressed', '#003c6e')])
|
||||
style.map('Green.TButton', background=[('active', '#388E3C'), ('pressed',
|
||||
'#1B5E20')]) # Manteniendo los tonos verdes originales para hover/pressed
|
||||
|
||||
# NUEVO ESTILO: Botones pequeños para el editor de notas
|
||||
style.configure('SmallAction.TButton', background='#0078d4', foreground='white', font=('Arial', 9, 'bold'),
|
||||
relief='flat', padding=[5, 3]) # <-- Padding reducido
|
||||
style.map('SmallAction.TButton', background=[('active', '#005a9e'), ('pressed', '#003c6e')])
|
||||
# Botones de Acción Global (Action.TButton)
|
||||
style.configure('Action.TButton', background=COLOR_ACCION, foreground=COLOR_BLANCO, font=FUENTE_NEGOCIOS,
|
||||
relief='flat', padding=[10, 5])
|
||||
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,
|
||||
font=('Arial', 9, 'bold'),
|
||||
relief='flat', padding=[5, 3])
|
||||
style.map('SmallAction.TButton', background=[('active', COLOR_ACCION_HOVER), ('pressed', COLOR_ACCION_PRESSED)])
|
||||
|
||||
def crear_seccion(self, parent_frame, titulo, acciones):
|
||||
"""Función helper para crear secciones de etiquetas y botones."""
|
||||
if titulo:
|
||||
ttk.Label(parent_frame, text=titulo, font=('Arial', 11, 'bold')).pack(fill="x", pady=(10, 0), padx=5)
|
||||
# Usando FUENTE_NEGOCIOS
|
||||
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.pack(fill="x", pady=5, padx=5)
|
||||
|
|
|
|||
|
|
@ -4,29 +4,35 @@ import tkinter as tk
|
|||
from tkinter import ttk
|
||||
from vista.panel_lateral import PanelLateral
|
||||
from vista.panel_central import PanelCentral
|
||||
from logica.T2.getLocalTime import obtener_hora_local
|
||||
from logica.T2.getWeather import obtener_datos_clima
|
||||
|
||||
# --- IMPORTACIÓN DE CONSTANTES DESDE vista/config.py ---
|
||||
from vista.config import *
|
||||
|
||||
|
||||
class VentanaPrincipal(tk.Tk):
|
||||
"""Clase principal de la aplicación que monta la interfaz con estilos nativos mejorados."""
|
||||
|
||||
# Definimos el ancho deseado para el panel lateral
|
||||
ANCHO_PANEL_LATERAL = 300
|
||||
"""Ventana principal de la aplicación con panel lateral y central, reloj y clima."""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.title("Proyecto Integrado - PSP (Estilo Moderno Nativo)")
|
||||
self.geometry("1200x800")
|
||||
|
||||
self.label_reloj = None
|
||||
self.reloj_after_id = None
|
||||
self.label_clima = None
|
||||
self.clima_after_id = None
|
||||
|
||||
style = ttk.Style()
|
||||
style.theme_use('clam')
|
||||
|
||||
self.config(bg="#f9f9f9")
|
||||
# Usando la constante importada
|
||||
self.config(bg=COLOR_FONDO)
|
||||
self.configurar_estilos(style)
|
||||
|
||||
# Configuración del manejador de protocolo para el botón de cierre (X)
|
||||
self.protocol("WM_DELETE_WINDOW", self.on_closing)
|
||||
|
||||
# Columna 0 (Lateral) no se expande (weight=0), Columna 1 (Central) sí se expande (weight=1)
|
||||
self.grid_rowconfigure(0, weight=1)
|
||||
self.grid_rowconfigure(1, weight=0)
|
||||
self.grid_columnconfigure(0, weight=0)
|
||||
|
|
@ -35,59 +41,120 @@ class VentanaPrincipal(tk.Tk):
|
|||
self.crear_paneles_principales()
|
||||
self.crear_barra_inferior()
|
||||
|
||||
def configurar_estilos(self, s: ttk.Style):
|
||||
"""Define estilos visuales personalizados sin dependencias externas."""
|
||||
self.iniciar_actualizacion_reloj()
|
||||
self.iniciar_actualizacion_clima()
|
||||
|
||||
COLOR_FONDO = "#f9f9f9"
|
||||
COLOR_ACCION = "#0078d4"
|
||||
COLOR_EXITO = "#4CAF50"
|
||||
COLOR_ADVERTENCIA = "#ffc107"
|
||||
COLOR_TEXTO = "#333333"
|
||||
def configurar_estilos(self, s: ttk.Style):
|
||||
"""Define estilos visuales personalizados sin dependencias externas usando constantes de config."""
|
||||
|
||||
# Los colores se importan, ya no se definen aquí
|
||||
|
||||
s.configure('TFrame', background=COLOR_FONDO)
|
||||
s.configure('TLabel', background=COLOR_FONDO, foreground=COLOR_TEXTO, font=('Arial', 9))
|
||||
s.configure('TLabel', background=COLOR_FONDO, foreground=COLOR_TEXTO, font=FUENTE_NORMAL)
|
||||
|
||||
s.configure('Action.TButton', background=COLOR_ACCION, foreground='white', font=('Arial', 10, 'bold'),
|
||||
relief='flat', padding=[10, 5])
|
||||
s.map('Action.TButton', background=[('active', '#005a9e'), ('pressed', '#003c6e')])
|
||||
s.configure('Action.TButton',
|
||||
background=COLOR_ACCION,
|
||||
foreground=COLOR_BLANCO,
|
||||
font=FUENTE_NEGOCIOS, # Usando FUENTE_NEGOCIOS
|
||||
relief='flat',
|
||||
padding=[10, 5])
|
||||
|
||||
# Mapeo de colores de estado de botón (usando las constantes de hover/pressed)
|
||||
s.map('Action.TButton',
|
||||
background=[('active', COLOR_ACCION_HOVER),
|
||||
('pressed', COLOR_ACCION_PRESSED)])
|
||||
|
||||
s.configure('Note.TFrame', background=COLOR_EXITO, borderwidth=0, relief="solid")
|
||||
s.configure('Note.TLabel', background=COLOR_EXITO, foreground='white', font=('Arial', 9, 'italic'))
|
||||
s.configure('Note.TLabel', background=COLOR_EXITO, foreground=COLOR_BLANCO, font=FUENTE_NOTA)
|
||||
|
||||
s.configure('Chat.TFrame', background=COLOR_ADVERTENCIA)
|
||||
s.configure('Chat.TLabel', background=COLOR_ADVERTENCIA)
|
||||
|
||||
s.configure('Alumno.TFrame', background='white', borderwidth=1, relief='solid')
|
||||
s.configure('Alumno.TLabel', background='white', foreground=COLOR_TEXTO)
|
||||
s.configure('Alumno.TFrame', background=COLOR_BLANCO, borderwidth=1, relief='solid')
|
||||
s.configure('Alumno.TLabel', background=COLOR_BLANCO, foreground=COLOR_TEXTO, font=FUENTE_NORMAL)
|
||||
|
||||
s.configure('TNotebook.Tab', padding=[10, 5], font=('Arial', 10, 'bold'))
|
||||
s.configure('TNotebook.Tab', padding=[10, 5], font=FUENTE_NEGOCIOS)
|
||||
s.map('TNotebook.Tab', background=[('selected', COLOR_FONDO)], foreground=[('selected', COLOR_ACCION)])
|
||||
|
||||
def crear_paneles_principales(self):
|
||||
"""Ensambla el panel lateral y el panel central en la rejilla, asegurando el ancho del lateral."""
|
||||
|
||||
# Panel Central (se expande en columna 1)
|
||||
self.panel_central = PanelCentral(self)
|
||||
self.panel_central.grid(row=0, column=1, sticky="nswe", padx=(5, 10), pady=10)
|
||||
|
||||
# Panel Lateral (se le aplica un ancho fijo para que no se expanda con el contenido)
|
||||
self.panel_lateral = PanelLateral(self, central_panel=self.panel_central, width=self.ANCHO_PANEL_LATERAL)
|
||||
# Usando la constante importada
|
||||
self.panel_lateral = PanelLateral(self, central_panel=self.panel_central, width=ANCHO_PANEL_LATERAL)
|
||||
self.panel_lateral.grid(row=0, column=0, sticky="nswe", padx=(10, 5), pady=10)
|
||||
|
||||
# Forzamos que el widget base respete el ancho definido, ignorando el tamaño del contenido (propagate=False)
|
||||
self.panel_lateral.grid_propagate(False)
|
||||
|
||||
# --- LÓGICA DE ACTUALIZACIÓN DE RELOJ ---
|
||||
def actualizar_reloj(self):
|
||||
"""Obtiene la hora local y actualiza la etiqueta del reloj."""
|
||||
try:
|
||||
hora_actual = obtener_hora_local()
|
||||
if self.label_reloj:
|
||||
self.label_reloj.config(text=f"Día y Hora: {hora_actual}")
|
||||
except Exception as e:
|
||||
if self.label_reloj:
|
||||
self.label_reloj.config(text="Error al obtener la hora")
|
||||
print(f"Error en el reloj: {e}")
|
||||
self.detener_actualizacion_reloj()
|
||||
return
|
||||
|
||||
# Usando la constante importada
|
||||
self.reloj_after_id = self.after(INTERVALO_RELOJ_MS, self.actualizar_reloj)
|
||||
|
||||
def iniciar_actualizacion_reloj(self):
|
||||
"""Inicia el ciclo de actualización del reloj."""
|
||||
print("Iniciando actualización automática del reloj.")
|
||||
self.reloj_after_id = self.after(0, self.actualizar_reloj)
|
||||
|
||||
def detener_actualizacion_reloj(self):
|
||||
"""Detiene el ciclo de actualización del reloj."""
|
||||
if self.reloj_after_id:
|
||||
self.after_cancel(self.reloj_after_id)
|
||||
self.reloj_after_id = None
|
||||
print("Ciclo de actualización del reloj detenido.")
|
||||
|
||||
# --- LÓGICA DE ACTUALIZACIÓN DE CLIMA ---
|
||||
def actualizar_clima(self):
|
||||
"""Obtiene la temperatura local y actualiza la etiqueta en la barra inferior."""
|
||||
try:
|
||||
datos_clima = obtener_datos_clima()
|
||||
if self.label_clima:
|
||||
self.label_clima.config(text=f"Temperatura: {datos_clima}")
|
||||
except Exception as e:
|
||||
if self.label_clima:
|
||||
self.label_clima.config(text="Error al obtener el clima")
|
||||
print(f"Error en la actualización del clima: {e}")
|
||||
self.detener_actualizacion_clima()
|
||||
return
|
||||
|
||||
# Usando la constante importada
|
||||
self.clima_after_id = self.after(INTERVALO_CLIMA_MS, self.actualizar_clima)
|
||||
|
||||
def iniciar_actualizacion_clima(self):
|
||||
"""Inicia el ciclo de actualización del clima."""
|
||||
print("Iniciando actualización automática del clima.")
|
||||
self.clima_after_id = self.after(0, self.actualizar_clima)
|
||||
|
||||
def detener_actualizacion_clima(self):
|
||||
"""Detiene el ciclo de actualización del clima."""
|
||||
if self.clima_after_id:
|
||||
self.after_cancel(self.clima_after_id)
|
||||
self.clima_after_id = None
|
||||
print("Ciclo de actualización del clima detenido.")
|
||||
|
||||
# --- FUNCIÓN DE CIERRE ---
|
||||
def on_closing(self):
|
||||
"""
|
||||
Se ejecuta cuando se presiona el botón 'X'. Detiene el ciclo de actualización
|
||||
y cierra la ventana principal de forma limpia.
|
||||
"""
|
||||
"""Se ejecuta cuando se presiona el botón 'X'. Detiene todos los ciclos y cierra la ventana."""
|
||||
self.detener_actualizacion_reloj()
|
||||
self.detener_actualizacion_clima()
|
||||
|
||||
if self.panel_central:
|
||||
# Llamar al método de limpieza del Panel Central (ciclo tk.after)
|
||||
self.panel_central.detener_actualizacion_automatica()
|
||||
|
||||
# Destruir el objeto Tkinter y terminar mainloop
|
||||
self.destroy()
|
||||
print("Aplicación cerrada limpiamente.")
|
||||
|
||||
|
|
@ -104,11 +171,16 @@ class VentanaPrincipal(tk.Tk):
|
|||
ttk.Label(frame_correo, text="Correos sin leer: 0", style='TLabel').pack(side="left")
|
||||
ttk.Button(frame_correo, text="↻", width=3, style='Action.TButton').pack(side="left", padx=5)
|
||||
|
||||
# Marco de Temperatura
|
||||
frame_temp = ttk.Frame(frame_inferior, style='TFrame')
|
||||
frame_temp.grid(row=0, column=1)
|
||||
ttk.Button(frame_temp, text="↻", width=3, style='Action.TButton').pack(side="left", padx=5)
|
||||
ttk.Label(frame_temp, text="Temperatura local: --", style='TLabel').pack(side="left")
|
||||
|
||||
self.label_clima = ttk.Label(frame_temp, text="Temperatura: --", style='TLabel')
|
||||
self.label_clima.pack(side="left")
|
||||
|
||||
# Marco de Fecha y Hora
|
||||
frame_fecha = ttk.Frame(frame_inferior, style='TFrame')
|
||||
frame_fecha.grid(row=0, column=2, sticky="e")
|
||||
ttk.Label(frame_fecha, text="Fecha Día y Hora: --/--/--", style='TLabel').pack(side="left")
|
||||
|
||||
self.label_reloj = ttk.Label(frame_fecha, text="Día y Hora: --/--/--", style='TLabel')
|
||||
self.label_reloj.pack(side="left")
|
||||
Loading…
Reference in New Issue