Wellbeing Guard (AHK v2)


🛡️ Wellbeing Guard (AHK v2)

Tu guardián de salud digital invisible.

Este script es una utilidad de bienestar digital diseñada para Windows, replicando las mejores características de salud de sistemas como GNOME (Linux) o Android. Su objetivo es proteger tu vista y tu postura sin interrumpir tu flujo de trabajo de manera molesta, pero asegurando que tomes los descansos necesarios.

🌟 Características Principales

1. ⏱️ Monitor de Tiempo de Pantalla (Screen Time)

  • Qué hace: Rastrea silenciosamente cuánto tiempo real pasas usando la computadora cada día.

  • Inteligencia: No cuenta el tiempo si dejaste la PC prendida y te fuiste. Solo suma segundos si detecta actividad (teclado/mouse) o si estás leyendo activamente (menos de 5 minutos de inactividad total).

  • Persistencia: Guarda tu progreso diario en un archivo local (WellbeingStats.ini), por lo que si reinicias la PC, no pierdes tu cuenta del día.

2. 👁️ Protector Visual (Regla 20-20-20)

  • Frecuencia: Cada 20 minutos de uso activo.

  • Acción: Muestra una alerta suave recordándote mirar a un punto lejano (6 metros / 20 pies) durante 20 segundos.

  • Beneficio: Previene la fatiga visual digital y la sequedad ocular.

3. 🚶 Recordatorio de Movimiento

  • Frecuencia: Cada 60 minutos (1 hora).

  • Acción: Te sugiere levantarte, estirar las piernas y caminar un poco.

  • Beneficio: Combate el sedentarismo y problemas circulatorios derivados de estar sentado mucho tiempo.

4. 🛑 Sistema Anti-Bloqueo Inteligente (Smart Keep-Awake)

  • El Problema: Normalmente, si te alejas de la PC para cumplir con el ejercicio de caminar, Windows podría bloquearse o apagar la pantalla, interrumpiendo tu música o descargas.

  • La Solución: Cuando salta una alerta de Wellbeing, el script activa un modo especial (ES_DISPLAY_REQUIRED) que fuerza a la pantalla a mantenerse encendida.

  • Regreso: Al volver y dar clic en “Listo, continuar”, el script devuelve el control de energía a Windows, permitiendo que se bloquee normalmente si vuelves a irte.

5. 📊 Dashboard de Estadísticas

  • Interfaz: Una ventana moderna y limpia (GUI) que muestra:

    • Tiempo total de hoy.

    • Barra de progreso (meta de 8 horas).

    • Estado de los próximos recordatorios.

  • Acceso: Accesible desde el ícono en la bandeja del sistema (al lado del reloj).

6. 🧠 Lógica de “No Molestar”

  • El script respeta tu ausencia. Si detecta que no has tocado el mouse o teclado en el último minuto justo cuando iba a saltar una alerta, asume que no estás frente a la pantalla y pospone la alerta para no acumular ventanas molestas ni mantener la pantalla encendida innecesariamente.

🛠️ Resumen Técnico (Para el usuario avanzado)

  • Lenguaje: AutoHotkey v2.0+

  • API Calls: Utiliza DllCall(“SetThreadExecutionState”) para la gestión de energía.

  • Recursos: Extremadamente ligero (~2-3 MB de RAM), no consume CPU mientras espera.

  • Portabilidad: Es un solo archivo .ahk (o .exe si lo compilas), no requiere instalación.

#Requires AutoHotkey v2.0

#SingleInstance Force

Persistent

  

; ==============================================================================

; CONFIGURACIÓN Y VARIABLES GLOBALES

; ==============================================================================

global APP_NAME := "Wellbeing Guard"

global LOG_FILE := "WellbeingStats.ini"

global TODAY_DATE := FormatTime(, "yyyyMMdd")

  

; Tiempos en milisegundos

global EYE_INTERVAL  := 20 * 60 * 1000  ; 20 Minutos (Ojos)

global MOVE_INTERVAL := 60 * 60 * 1000  ; 60 Minutos (Cuerpo)

  

; Estado

global ScreenTimeSeconds := 0

global IsPaused := false

global AlertOpen := false ; Bandera para saber si hay una alerta activa

  

; Cargar datos previos

IniPath := A_ScriptDir "\" LOG_FILE

try {

    SavedTime := IniRead(IniPath, "DailyStats", TODAY_DATE, "0")

    ScreenTimeSeconds := Integer(SavedTime)

} catch {

    ScreenTimeSeconds := 0

}

  

; ==============================================================================

; TRAY MENU

; ==============================================================================

A_IconTip := APP_NAME " - Monitoreo Activo"

TraySetIcon("shell32.dll", 239) ; Icono de reloj/escudo

  

MainTray := A_TrayMenu

MainTray.Delete()

MainTray.Add("📊 Abrir Dashboard", ShowDashboard)

MainTray.Add("⏸️ Pausar/Reanudar", TogglePause)

MainTray.Add()

MainTray.Add("❌ Salir", ExitAppFunc)

MainTray.Default := "📊 Abrir Dashboard"

  

; Iniciar Timers

SetTimer(UpdateScreenTime, 1000)

SetTimer(CheckEyeAlert, EYE_INTERVAL)

SetTimer(CheckMoveAlert, MOVE_INTERVAL)

  

; Mostrar Dashboard al inicio

ShowDashboard()

  

return ; Fin sección auto-execute

  

; ==============================================================================

; GUI PRINCIPAL (DASHBOARD)

; ==============================================================================

ShowDashboard(*) {

    if WinExist(APP_NAME " Dashboard") {

        WinActivate(APP_NAME " Dashboard")

        return

    }

  

    global MyGui := Gui("+MinimizeBox", APP_NAME " Dashboard")

    MyGui.SetFont("s10", "Segoe UI")

    MyGui.Add("Text", "w350 Center vDateTitle", "Estadísticas para: " FormatTime(, "LongDate"))

    ; --- GRUPO TIEMPO ---

    MyGui.Add("GroupBox", "w370 h130 Section", "Tiempo de Pantalla Hoy")

    ; CORRECCIÓN AQUÍ: Usar SetFont antes del control

    MyGui.SetFont("s25 bold c0066cc")

    MyGui.Add("Text", "xs+10 ys+30 w350 Center vTimeDisplay", FormatSeconds(ScreenTimeSeconds))

    MyGui.SetFont("s10 norm cDefault") ; Restablecer fuente normal

    MyGui.Add("Progress", "xs+10 ys+90 w350 h20 c00cc66 vDayProgress", 0)

    ; --- GRUPO RECORDATORIOS ---

    MyGui.Add("GroupBox", "w370 h100 xs Section", "Próximos Descansos")

    MyGui.Add("Text", "xs+20 ys+30 w150 vNextEye", "👁️ Ojos: Calculando...")

    MyGui.Add("Text", "x+20 ys+30 w150 vNextMove", "🚶 Cuerpo: Calculando...")

    MyGui.Add("Button", "xs+135 ys+65 w100", "Ocultar").OnEvent("Click", (*) => MyGui.Destroy())

  

    MyGui.Show()

    UpdateDashboardUI()

}

  

UpdateDashboardUI() {

    try {

        if !IsSet(MyGui) || !WinExist(APP_NAME " Dashboard")

            return

        MyGui["TimeDisplay"].Text := FormatSeconds(ScreenTimeSeconds)

        ; Barra de progreso (Meta de 8 horas = 28800 segundos)

        percent := (ScreenTimeSeconds / 28800) * 100

        MyGui["DayProgress"].Value := percent

        if (IsPaused) {

            MyGui["NextEye"].Text := "👁️ Ojos: PAUSADO"

            MyGui["NextMove"].Text := "🚶 Cuerpo: PAUSADO"

        } else {

            MyGui["NextEye"].Text := "👁️ Ojos: Activo"

            MyGui["NextMove"].Text := "🚶 Cuerpo: Activo"

        }

    }

}

  

; ==============================================================================

; LÓGICA DE TIEMPO

; ==============================================================================

UpdateScreenTime() {

    if (IsPaused || AlertOpen)

        return

  

    ; Si el usuario no mueve el mouse/teclado por 5 min, no contamos

    if (A_TimeIdlePhysical > 300000)

        return

  

    global ScreenTimeSeconds += 1

    ; Guardar cada minuto

    if (Mod(ScreenTimeSeconds, 60) = 0)

        IniWrite(ScreenTimeSeconds, IniPath, "DailyStats", TODAY_DATE)

    UpdateDashboardUI()

}

  

FormatSeconds(s) {

    hours := s // 3600

    rem := Mod(s, 3600)

    mins := rem // 60

    secs := Mod(rem, 60)

    return Format("{:02}:{:02}:{:02}", hours, mins, secs)

}

  

; ==============================================================================

; ALERTAS Y KEEP AWAKE

; ==============================================================================

CheckEyeAlert() {

    ; Si está pausado, o hay alerta abierta, o el usuario lleva 1 min inactivo, no molestar

    if (IsPaused || AlertOpen || A_TimeIdlePhysical > 60000)

        return

    ShowAlert("👁️ Descanso Visual (20-20-20)", "Mira a un punto lejano (6m) durante 20 segundos.", "Eyes")

}

  

CheckMoveAlert() {

    if (IsPaused || AlertOpen || A_TimeIdlePhysical > 120000)

        return

  

    ShowAlert("🚶 Hora de Moverse", "Llevas 1 hora sentado.`nLevántate y camina un poco.", "Move")

}

  

ShowAlert(Title, Msg, Type) {

    global AlertOpen := true

    ; 1. PREVENIR BLOQUEO DE PANTALLA

    PreventSleep(true)

    SoundBeep(800, 200)

  

    global AlertGui := Gui("+AlwaysOnTop +ToolWindow -MinimizeBox", Title)

    AlertGui.SetFont("s12", "Segoe UI")

    AlertGui.BackColor := "White"

    AlertGui.Add("Text", "w400 Center cBlack", Msg)

    ; Botón aceptar

    AlertGui.SetFont("s11 bold")

    Btn := AlertGui.Add("Button", "w200 h40 x110 y+20 Default", "✅ Listo, continuar")

    Btn.OnEvent("Click", (*) => CloseAlert(AlertGui, Type))

    AlertGui.Show()

}

  

CloseAlert(thisGui, Type) {

    thisGui.Destroy()

    ; 2. PERMITIR BLOQUEO NUEVAMENTE

    PreventSleep(false)

    global AlertOpen := false

    ; Reiniciar el contador específico

    if (Type = "Eyes")

        SetTimer(CheckEyeAlert, EYE_INTERVAL)

    else if (Type = "Move")

        SetTimer(CheckMoveAlert, MOVE_INTERVAL)

}

  

; ==============================================================================

; FUNCIÓN CRÍTICA: GESTIÓN DE ENERGÍA

; ==============================================================================

PreventSleep(enable) {

    if (enable) {

        ; ES_CONTINUOUS | ES_SYSTEM_REQUIRED | ES_DISPLAY_REQUIRED

        ; 0x80000003 fuerza a Windows a no apagar pantalla ni dormir

        DllCall("SetThreadExecutionState", "UInt", 0x80000003)

    } else {

        ; ES_CONTINUOUS (0x80000000) restaura el estado normal

        DllCall("SetThreadExecutionState", "UInt", 0x80000000)

    }

}

  

; ==============================================================================

; UTILIDADES

; ==============================================================================

TogglePause(*) {

    global IsPaused := !IsPaused

    UpdateDashboardUI()

    if IsPaused

        TrayTip("Pausado", "Wellbeing no te enviará alertas.")

    else

        TrayTip("Reanudado", "Monitoreo de salud activo.")

}

  

ExitAppFunc(*) {

    IniWrite(ScreenTimeSeconds, IniPath, "DailyStats", TODAY_DATE)

    ExitApp

}