import datetime import shelve import os # Nombre del archivo de base de datos para persistencia DB_FILE = 'alarm_data.db' def get_db_path(): """Obtiene la ruta completa para el archivo shelve.""" return os.path.join(os.path.dirname(os.path.abspath(__file__)), DB_FILE) def load_alarms(): """Carga todas las alarmas persistentes del archivo shelve.""" alarms = [] try: with shelve.open(get_db_path()) as db: if 'alarms' in db: alarms = list(db['alarms']) except Exception as e: print(f"Error al cargar alarmas: {e}") return alarms def save_alarms(alarms): """Guarda la lista completa de alarmas en el archivo shelve.""" try: with shelve.open(get_db_path(), writeback=True) as db: db['alarms'] = alarms except Exception as e: print(f"Error al guardar alarmas: {e}") def add_alarm(hour, minute, message): """Añade una nueva alarma y la guarda.""" alarms = load_alarms() new_id = max([a['id'] for a in alarms] + [0]) + 1 new_alarm = { 'id': new_id, 'hour': int(hour), 'minute': int(minute), 'message': message, 'active': True } alarms.append(new_alarm) save_alarms(alarms) return alarms def delete_alarm(alarm_id): """Elimina una alarma por su ID.""" alarms = load_alarms() alarms = [alarm for alarm in alarms if alarm['id'] != alarm_id] save_alarms(alarms) return alarms def toggle_alarm(alarm_id, active_state): """Cambia el estado activo de una alarma.""" alarms = load_alarms() for alarm in alarms: if alarm['id'] == alarm_id: alarm['active'] = active_state break save_alarms(alarms) return alarms def postpone_alarm(alarm_id): """ Posiciona la alarma un minuto más tarde y la marca como activa. IMPORTANTE: Esto sobrescribe la hora original de la alarma. """ alarms = load_alarms() for alarm in alarms: if alarm['id'] == alarm_id: # Creamos un objeto datetime temporal para calcular la nueva hora current_time = datetime.datetime(1, 1, 1, alarm['hour'], alarm['minute']) new_time = current_time + datetime.timedelta(minutes=1) alarm['hour'] = new_time.hour alarm['minute'] = new_time.minute alarm['active'] = True # Vuelve a estar activa para sonar en 1 minuto break save_alarms(alarms) return alarms def check_alarms(root, alarm_list_ref, notify_callback): """ Función principal de verificación de alarmas. Utiliza una función de callback para mostrar el POP-UP. """ now = datetime.datetime.now() current_hour = now.hour current_minute = now.minute alarms = load_alarms() for alarm in alarms: if alarm['active'] and alarm['hour'] == current_hour and alarm['minute'] == current_minute: # 1. Llamar a la función de notificación del POP-UP en main_app notify_callback(alarm) # 2. Desactivar temporalmente la alarma en la lógica interna (en caso de que el usuario no la posponga o detenga) # Esto previene que suene en cada segundo dentro del mismo minuto. alarm['active'] = False # 3. Guardar el cambio de estado save_alarms(alarms) # 4. Actualizar la lista visible en la UI alarm_list_ref.update_list(alarms) break # Volver a programar la comprobación para el siguiente segundo root.after(1000, check_alarms, root, alarm_list_ref, notify_callback) # alarm_logic.py (COMIENZO DEL ARCHIVO) import datetime import shelve import os import tkinter as tk # NECESARIO para Toplevel y otros widgets from tkinter import ttk # NECESARIO si usas widgets ttk # ... (rest of the initial code) ... # ... (all functions like load_alarms, postpone_alarm, check_alarms, etc.) ... # --- NUEVA CLASE MOVIDA DESDE main_app.py --- class CustomAlarmDialog(tk.Toplevel): """Ventana de diálogo personalizada para la alarma con botones renombrados.""" def __init__(self, master, alarm_data): super().__init__(master) self.transient(master) self.title("🔔 ¡ALARMA ACTIVA!") self.result = None self.grab_set() self.focus_set() self.geometry("300x150") self.resizable(False, False) message_text = f"ALARMA SONANDO!\n\nMensaje: {alarm_data['message']}" tk.Label(self, text="⚠️", font=("Arial", 20)).pack(pady=5) tk.Label(self, text=message_text, font=("Arial", 10, "bold")).pack(pady=5) button_frame = tk.Frame(self) button_frame.pack(pady=10) # Botón POSPONER tk.Button(button_frame, text="POSPONER (1 min)", command=lambda: self.on_action('posponer'), bg='blue', fg='white').pack(side=tk.LEFT, padx=10) # Botón DETENER tk.Button(button_frame, text="DETENER", command=lambda: self.on_action('detener'), bg='red', fg='white').pack(side=tk.LEFT, padx=10) self.protocol("WM_DELETE_WINDOW", lambda: self.on_action('detener')) self.master.update_idletasks() width = self.winfo_width() height = self.winfo_height() x = (self.master.winfo_width() // 2) - (width // 2) y = (self.master.winfo_height() // 2) - (height // 2) self.geometry('+%d+%d' % (self.master.winfo_x() + x, self.master.winfo_y() + y)) self.master.wait_window(self) def on_action(self, action): self.result = action self.grab_release() self.destroy()