Files
INFRAESTRUCTURA/scripts/recordatorios_msgbox.py

227 lines
8.2 KiB
Python

"""recordatorios_msgbox.py — Recordatorios con cuadros de dialogo (MessageBox) en Windows.
Muestra un message box cuando la hora de un recordatorio llega (o ya ha pasado y no esta hecho).
- Si el usuario pulsa SI -> se marca como hecho (done=True) y no vuelve a salir.
- Si pulsa NO -> se pospone 5 minutos (snooze).
- Si se ejecuta con --done <id> se marca como terminado (lo usa el asistente cuando el usuario dice "terminada").
- Si se ejecuta con --listar muestra el estado de todos los recordatorios.
Fichero de datos: recordatorios_msgbox.json (formato: id, texto, fecha [diario|YYYY-MM-DD], hora HH:MM, done).
Uso:
python scripts/recordatorios_msgbox.py # bucle de monitorizacion (correr en background)
python scripts/recordatorios_msgbox.py --listar # listar estado
python scripts/recordatorios_msgbox.py --done <id> # marcar como terminado
python scripts/recordatorios_msgbox.py --add "texto" --hora HH:MM [--fecha diario|YYYY-MM-DD]
python scripts/recordatorios_msgbox.py --test <id> # mostrar el cuadro sin esperar a la hora
python scripts/recordatorios_msgbox.py --sync # crear recordatorios desde pendientes.json (tareas con campo hora)
"""
import argparse
import ctypes
import json
import sys
import time
from datetime import datetime
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent))
import infra_paths # noqa: E402 (resolucion de rutas del repo INFRAESTRUCTURA)
DATA = infra_paths.ESTADO_DIR / 'recordatorios_msgbox.json'
PENDIENTES = infra_paths.biblioteca_repo() / 'pendientes.json'
# Constantes de MessageBoxW de Win32
MB_OK = 0x0
MB_YESNO = 0x4
MB_ICONINFORMATION = 0x40
MB_ICONQUESTION = 0x20
MB_SYSTEMMODAL = 0x1000
IDYES = 6
IDNO = 7
SLEEP_SEG = 15 # cadencia de comprobacion en segundos
SNOOZE_MIN = 5 # minutos a posponer si pulsa NO
def cargar():
try:
return json.loads(DATA.read_text(encoding='utf-8-sig'))
except FileNotFoundError:
return {'recordatorios': []}
def guardar(data):
DATA.write_text(json.dumps(data, ensure_ascii=False, indent=1), encoding='utf-8')
def fecha_hoy():
return datetime.now().strftime('%Y-%m-%d')
def hora_ahora():
return datetime.now().strftime('%H:%M')
def msgbox(texto, titulo, yesno=False):
"""Muestra un cuadro de dialogo nativo de Windows. Devuelve IDYES/IDNO/1."""
flags = MB_SYSTEMMODAL | MB_ICONINFORMATION
if yesno:
flags |= MB_YESNO
else:
flags |= MB_OK
return ctypes.windll.user32.MessageBoxW(None, texto, titulo, flags)
def es_hora(r):
"""Comprueba si toca mostrar el recordatorio."""
if r.get('done'):
return False
if r.get('fecha', 'diario') == 'diario':
return True
return r.get('fecha') <= fecha_hoy()
def run():
data = cargar()
ultima_vez = {} # id -> 'HH:MM' ya mostrado hoy (o fecha mostrada)
print(f'[recordatorios_msgbox] Monitorizando {len(data["recordatorios"])} recordatorios. Ctrl+C para salir.')
while True:
data = cargar()
ahora = fecha_hoy() + ' ' + hora_ahora()
for r in data['recordatorios']:
if not es_hora(r):
continue
# evita repetir en la misma hora ya mostrada para ese id
clave = f"{r['id']}-{fecha_hoy()}-{r.get('hora')}"
if ultima_vez.get(clave):
continue
if hora_ahora() >= r.get('hora', ''):
ultima_vez[clave] = True
texto = r.get('texto', '')
res = msgbox(texto, f'RECORDATORIO {r.get("hora")}', yesno=True)
if res == IDYES:
r['done'] = True
guardar(data)
print(f'[{ahora}] #{r["id"]} marcado como HECHO: {texto[:60]}')
else:
# snooze: fuerza a no repetir en los proximos SNOOZE_MIN minutos
ultima_vez[clave] = True
print(f'[{ahora}] #{r["id"]} pospuesto (NO).')
time.sleep(SLEEP_SEG)
def listar():
data = cargar()
if not data['recordatorios']:
print('No hay recordatorios.')
return
print(f'{"ID":>3} {"HORA":>5} {"FECHA":<12} {"ESTADO":<10} TAREA')
for r in sorted(data['recordatorios'], key=lambda x: (x.get('hora', ''), x.get('id', 0))):
estado = 'HECHO' if r.get('done') else 'PENDIENTE'
print(f'{r.get("id", 0):>3} {r.get("hora", ""):>5} {str(r.get("fecha", "")):<12} {estado:<10} {r.get("texto", "")[:70]}')
def marcar_done(i):
data = cargar()
for r in data['recordatorios']:
if r.get('id') == i:
r['done'] = True
guardar(data)
print(f'#{i} marcado como terminado. Ya no saldra mas.')
return
print(f'No existe el recordatorio #{i}')
def add(texto, hora, fecha):
data = cargar()
ids = [r.get('id', 0) for r in data['recordatorios']]
nid = max(ids) + 1 if ids else 1
data['recordatorios'].append({'id': nid, 'texto': texto, 'fecha': fecha, 'hora': hora, 'done': False})
guardar(data)
print(f'Recordatorio #{nid} anadido: {hora} [{fecha}] {texto}')
def test(i):
data = cargar()
r = next((x for x in data['recordatorios'] if x.get('id') == i), None)
if not r:
print(f'No existe el recordatorio #{i}')
return
res = msgbox(r.get('texto', ''), f'TEST RECORDATORIO {r.get("hora")}', yesno=True)
if res == IDYES:
r['done'] = True
guardar(data)
print(f'#{i} marcado como HECHO (test).')
def sync():
"""Crea recordatorios desde pendientes.json: tareas activas con campo 'hora' (HH:MM).
La fecha sale del campo 'date' del pendiente o 'diario' si es de hoy."""
try:
data = cargar()
pends = json.loads(PENDIENTES.read_text(encoding='utf-8-sig'))
except FileNotFoundError:
print(f'[sync] No existe {PENDIENTES.name}. Nada que sincronizar.')
return
hoy = fecha_hoy()
creados = 0
for p in pends.get('pending', []):
if p.get('done'):
continue
hora = p.get('hora')
if not hora:
continue
fecha_p = p.get('date', hoy)
texto = p.get('task', '')
# evita duplicados: mismo pendiente_id, o mismo texto y misma hora
pid = p.get('id')
if any(r.get('pendiente_id') == pid for r in data['recordatorios']):
continue
if any(r.get('texto') == texto and r.get('hora') == hora for r in data['recordatorios']):
continue
ids = [r.get('id', 0) for r in data['recordatorios']]
nid = max(ids) + 1 if ids else 1
data['recordatorios'].append({
'id': nid, 'texto': f'[pendiente #{p.get("id")}] {texto}',
'fecha': fecha_p if fecha_p else 'diario', 'hora': hora, 'done': False,
'pendiente_id': p.get('id')
})
creados += 1
print(f'[sync] #{nid} creado desde pendiente #{p.get("id")}: {hora} {texto[:60]}')
if creados:
guardar(data)
print(f'[sync] Sincronizacion completa. {creados} recordatorio(s) nuevo(s).')
def main():
ap = argparse.ArgumentParser(description='Recordatorios con message box')
ap.add_argument('--listar', action='store_true', help='listar estado de los recordatorios')
ap.add_argument('--done', type=int, metavar='ID', help='marcar recordatorio como terminado')
ap.add_argument('--add', metavar='TEXTO', help='anadir recordatorio (usa --hora y --fecha)')
ap.add_argument('--hora', default='12:00', help='hora HH:MM (por defecto 12:00)')
ap.add_argument('--fecha', default='diario', help='fecha YYYY-MM-DD o "diario" (por defecto diario)')
ap.add_argument('--test', type=int, metavar='ID', help='mostrar el cuadro ahora (sin esperar hora)')
ap.add_argument('--sync', action='store_true', help='crear recordatorios desde pendientes.json con hora')
args = ap.parse_args()
if args.sync:
sync()
elif args.listar:
listar()
elif args.done is not None:
marcar_done(args.done)
elif args.add:
add(args.add, args.hora, args.fecha)
elif args.test is not None:
test(args.test)
else:
run()
if __name__ == '__main__':
try:
sys.stdout.reconfigure(encoding='utf-8')
except Exception:
pass
main()