Un sistema de recordatorios con alarma permite registrar una fecha y una hora, esperar hasta el momento programado y avisar al usuario mediante sonido, mensaje o notificación. Este proyecto combina fechas, bucles, persistencia, validación y ejecución en segundo plano. Una versión útil debe sobrevivir a reinicios, evitar perder alarmas y manejar correctamente las zonas horarias.
Para seguir esta guía conviene conocer datetime en Python, el uso de threads, las funciones, el manejo de excepciones y el registro con logging.
La documentación oficial de datetime explica fechas, horas y diferencias temporales. La documentación oficial del módulo time describe pausas y relojes, aunque para alarmas basadas en calendario trabajaremos principalmente con datetime.
Crear un recordatorio simple
from datetime import datetime
def crear_recordatorio(texto, fecha_hora):
return {
"texto": texto.strip(),
"fecha_hora": fecha_hora.isoformat(timespec="minutes"),
"completado": False,
}
Guardar la fecha en formato ISO facilita la lectura y recuperación. No guardes únicamente “dentro de 30 minutos”, porque después de reiniciar el programa necesitas un momento absoluto.
Leer fecha y hora
def leer_fecha_hora():
while True:
texto = input("Fecha y hora (AAAA-MM-DD HH:MM): ").strip()
try:
momento = datetime.strptime(texto, "%Y-%m-%d %H:%M")
except ValueError:
print("Formato inválido")
continue
if momento <= datetime.now():
print("El momento debe estar en el futuro")
continue
return momento
Esta versión utiliza la hora local del equipo. Para una aplicación que opera en varios países, utiliza objetos conscientes de zona horaria con zoneinfo.
Esperar de manera controlada
Una implementación ingenua podría dormir durante horas. Es mejor comprobar periódicamente:
import time
def esperar_hasta(momento):
while True:
restante = (momento - datetime.now()).total_seconds()
if restante <= 0:
return
time.sleep(min(restante, 30))
El intervalo máximo de treinta segundos permite reaccionar si el reloj cambia o si el programa necesita detenerse. Para muchas alarmas, no debes crear un bucle independiente por cada una; conviene mantener una cola ordenada.
Emitir una alarma
En Windows, winsound forma parte de la biblioteca estándar:
import winsound
def sonar_alarma():
frecuencia = 1000
duracion = 700
for _ in range(3):
winsound.Beep(frecuencia, duracion)
En otros sistemas puedes reproducir un archivo mediante una biblioteca compatible o mostrar una notificación. El núcleo de recordatorios no debe depender directamente del método de sonido.
def avisar(recordatorio, reproductor):
print(f"\nRECORDATORIO: {recordatorio['texto']}")
reproductor()
Guardar recordatorios en JSON
import json
from pathlib import Path
RUTA = Path("recordatorios.json")
def guardar_recordatorios(recordatorios):
temporal = RUTA.with_suffix(".tmp")
temporal.write_text(
json.dumps(recordatorios, ensure_ascii=False, indent=2),
encoding="utf-8",
)
temporal.replace(RUTA)
def cargar_recordatorios():
if not RUTA.exists():
return []
try:
datos = json.loads(RUTA.read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError):
return []
return datos if isinstance(datos, list) else []
La escritura mediante un archivo temporal reduce el riesgo de corrupción. En una aplicación importante, registra el error en vez de ignorarlo silenciosamente.
Encontrar alarmas pendientes
def pendientes(recordatorios):
return sorted(
(r for r in recordatorios if not r.get("completado")),
key=lambda r: r["fecha_hora"],
)
El formato ISO ordena correctamente cuando todas las fechas siguen la misma zona y estructura.
Procesar la próxima alarma
def procesar_siguiente(recordatorios, reproductor):
lista = pendientes(recordatorios)
if not lista:
return False
proximo = lista[0]
momento = datetime.fromisoformat(proximo["fecha_hora"])
esperar_hasta(momento)
avisar(proximo, reproductor)
proximo["completado"] = True
guardar_recordatorios(recordatorios)
return True
Si el programa estuvo apagado y encuentra una alarma vencida, debes decidir si avisar inmediatamente, marcarla como perdida o preguntar al usuario.
Ejecutar sin bloquear el menú
Una interfaz gráfica se congelaría si llama a esperar_hasta en el hilo principal. Puedes usar un hilo trabajador:
from threading import Event, Thread
parar = Event()
def trabajador(recordatorios, reproductor):
while not parar.is_set():
ahora = datetime.now()
for recordatorio in pendientes(recordatorios):
momento = datetime.fromisoformat(recordatorio["fecha_hora"])
if momento <= ahora:
avisar(recordatorio, reproductor)
recordatorio["completado"] = True
guardar_recordatorios(recordatorios)
parar.wait(15)
hilo = Thread(
target=trabajador,
args=(recordatorios, sonar_alarma),
daemon=True,
)
hilo.start()
Event.wait permite detener el hilo sin esperar a que termine un sleep largo. Protege la lista con un lock si el menú y el trabajador pueden modificarla simultáneamente.
Menú de terminal
def agregar(recordatorios):
texto = input("Recordatorio: ").strip()
if not texto:
print("El texto no puede estar vacío")
return
momento = leer_fecha_hora()
recordatorios.append(crear_recordatorio(texto, momento))
guardar_recordatorios(recordatorios)
def listar(recordatorios):
for indice, item in enumerate(pendientes(recordatorios), start=1):
print(f"{indice}. {item['fecha_hora']} - {item['texto']}")
Añade opciones para eliminar, posponer y marcar como completado. Para borrar, utiliza un identificador único en vez de depender de la posición visual.
Identificadores únicos
from uuid import uuid4
def crear_recordatorio(texto, fecha_hora):
return {
"id": str(uuid4()),
"texto": texto.strip(),
"fecha_hora": fecha_hora.isoformat(timespec="minutes"),
"completado": False,
}
Zonas horarias
from datetime import datetime
from zoneinfo import ZoneInfo
zona = ZoneInfo("America/Sao_Paulo")
momento = datetime(2026, 8, 20, 9, 0, tzinfo=zona)
Guarda el momento en UTC y conserva la zona del usuario para presentarlo. Evita mezclar datetimes conscientes y no conscientes porque Python no permite compararlos de forma segura.
Recordatorios recurrentes
Para una alarma diaria puedes calcular la próxima fecha después de completarla. No sumes siempre 24 horas si la zona aplica horario de verano; reconstruye la hora local del siguiente día y conviértela a UTC.
Notificaciones del sistema
Una mejora consiste en mostrar una notificación de escritorio. Mantén una interfaz de aviso genérica:
class Notificador:
def enviar(self, titulo, mensaje):
raise NotImplementedError
Después implementa adaptadores para sonido, correo, Telegram o notificación local sin modificar la lógica de programación.
Pruebas confiables
No esperes minutos reales durante los tests. Inyecta una función de reloj:
def esta_vencido(recordatorio, ahora):
momento = datetime.fromisoformat(recordatorio["fecha_hora"])
return momento <= ahora
Así puedes probar fechas pasadas, futuras y exactas. Simula también archivos corruptos, alarmas duplicadas, zona inválida y errores de reproducción.
Errores frecuentes
Los problemas habituales son usar un sleep de muchas horas, bloquear la interfaz, no guardar los datos, confiar en el reloj local sin zona, crear un thread por alarma y perder recordatorios después de un reinicio. También debes evitar reproducir sonido indefinidamente o no ofrecer una forma clara de detenerlo.
Conclusión
Un sistema de recordatorios confiable guarda momentos absolutos, valida las entradas, revisa periódicamente las alarmas y separa programación, persistencia y notificación. Empieza con JSON y una alarma local; luego añade zonas horarias, recurrencia, threads y una interfaz. Las pruebas deben controlar el reloj para ser rápidas y deterministas.






