20-sep: migracion watchers (Telegram+recordatorios) a INFRAESTRUCTURA + estado/agentes/changelog
This commit is contained in:
226
scripts/recordatorios_msgbox.py
Normal file
226
scripts/recordatorios_msgbox.py
Normal file
@@ -0,0 +1,226 @@
|
||||
"""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()
|
||||
Reference in New Issue
Block a user