diff --git a/CHANGELOG_INFRA.md b/CHANGELOG_INFRA.md
index 1261b6d..299ce8a 100644
--- a/CHANGELOG_INFRA.md
+++ b/CHANGELOG_INFRA.md
@@ -10,6 +10,7 @@
- **2026-09-20-plugin-global-opencode-relanza-omniroute** [opencode] Plugin global OpenCode relanza OmniRoute (aplicado en: sobremesa)
- **2026-09-20-migracion-de-watchers-telegram-recordatorios-a-infraestructu** [watchers] Migracion de watchers (Telegram + recordatorios) a INFRAESTRUCTURA (aplicado en: sobremesa)
- **2026-09-20-primera-version-de-morning-routine-wol-tapo-autologon-y-arra** [scripts] Primera version de Morning Routine (WoL, Tapo, AutoLogon y Arranque Apps) (aplicado en: sobremesa)
+- **2026-09-20-fix-bot-telegram-control-pc-confirmacion-watchdog** [scripts] Fix bot Telegram control PC: confirmacion + watchdog (aplicado en: sobremesa)
## Historial
@@ -68,3 +69,11 @@
- **Transferible**: si
- **Aplicado en**: sobremesa
- **Detalle**: Scripts para encender PC via RPi+Tapo, hacer autologon en Windows y lanzar Chrome, Edge, WhatsApp y Telegram al arrancar
+
+### 2026-09-20 · Fix bot Telegram control PC: confirmacion + watchdog
+- **id**: `2026-09-20-fix-bot-telegram-control-pc-confirmacion-watchdog`
+- **Equipo**: sobremesa
+- **Categoria**: scripts
+- **Transferible**: si
+- **Aplicado en**: sobremesa
+- **Detalle**: Corregidos 4 problemas del bot telegram_bot_pc.py: (1) confirmacion obligatoria antes de apagar, (2) watchdog automatico de estado tras apagar (30s + ping), (3) watchdog de encendido con 3 reintentos (90s + 3x15s ping), (4) documentado problema autologon usuario nuevo. Archivos: telegram_bot_pc.py (corregido), docs/TELEGRAM_BOT_PC_FIXES.md, docs/FIX_PROBLEMA_AUTOLOGON_USUARIO_NUEVO.md
diff --git a/changelog_infra.json b/changelog_infra.json
index a7afa2d..9ccff66 100644
--- a/changelog_infra.json
+++ b/changelog_infra.json
@@ -84,6 +84,18 @@
"sobremesa"
],
"detalle": "Scripts para encender PC via RPi+Tapo, hacer autologon en Windows y lanzar Chrome, Edge, WhatsApp y Telegram al arrancar"
+ },
+ {
+ "id": "2026-09-20-fix-bot-telegram-control-pc-confirmacion-watchdog",
+ "fecha": "2026-09-20",
+ "equipo": "sobremesa",
+ "categoria": "scripts",
+ "titulo": "Fix bot Telegram control PC: confirmacion + watchdog",
+ "transferible": true,
+ "aplicado_en": [
+ "sobremesa"
+ ],
+ "detalle": "Corregidos 4 problemas del bot telegram_bot_pc.py: (1) confirmacion obligatoria antes de apagar, (2) watchdog automatico de estado tras apagar (30s + ping), (3) watchdog de encendido con 3 reintentos (90s + 3x15s ping), (4) documentado problema autologon usuario nuevo. Archivos: telegram_bot_pc.py (corregido), docs/TELEGRAM_BOT_PC_FIXES.md, docs/FIX_PROBLEMA_AUTOLOGON_USUARIO_NUEVO.md"
}
]
}
diff --git a/docs/FIX_PROBLEMA_AUTOLOGON_USUARIO_NUEVO.md b/docs/FIX_PROBLEMA_AUTOLOGON_USUARIO_NUEVO.md
new file mode 100644
index 0000000..f2b102d
--- /dev/null
+++ b/docs/FIX_PROBLEMA_AUTOLOGON_USUARIO_NUEVO.md
@@ -0,0 +1,97 @@
+# FIX: Problema de autologon creando usuario nuevo
+
+## Problema detectado (20-sep-2026)
+
+Al configurar el autologon en Windows, se creó un **usuario nuevo que pide contraseña** en lugar de usar el usuario actual.
+
+## Causa raíz
+
+El script `enable_autologon.ps1` configura correctamente el registro de Windows, PERO Windows crea un usuario nuevo si:
+
+1. **El nombre de usuario no coincide exactamente** con el formato del usuario actual
+2. **El dominio está mal configurado** (LOCAL vs nombre del PC vs WORKGROUP)
+3. **La contraseña no es correcta** (Windows crea usuario nuevo por seguridad)
+
+## Verificación del problema
+
+Ejecutar en PowerShell como **Administrador**:
+
+```powershell
+# Ver configuración actual de autologon
+Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" | Select-Object AutoAdminLogon, DefaultUserName, DefaultDomainName
+
+# Ver usuario actual real
+Write-Host "Usuario actual: $env:USERNAME"
+Write-Host "Dominio actual: $env:USERDOMAIN"
+Write-Host "Nombre completo: $env:USERDOMAIN\$env:USERNAME"
+```
+
+## Solución 1: DESHABILITAR autologon (RECOMENDADO)
+
+**IMPORTANTE:** En un PC de trabajo con datos sensibles, **NO se debe usar autologon** por seguridad.
+
+```powershell
+# Ejecutar como Administrador
+Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" -Name "AutoAdminLogon" -Value "0"
+Remove-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" -Name "DefaultPassword" -ErrorAction SilentlyContinue
+Write-Host "[OK] Autologon deshabilitado" -ForegroundColor Green
+```
+
+Tras esto:
+1. **Eliminar el usuario nuevo creado** en Configuración → Cuentas → Familia y otros usuarios
+2. Al arrancar el PC pedirá la contraseña normalmente
+3. El control remoto por Telegram seguirá funcionando (solo enciende/apaga, no hace login)
+
+## Solución 2: Corregir autologon (SOLO si realmente lo necesitas)
+
+Si REALMENTE necesitas autologon (ej: PC dedicado sin datos sensibles):
+
+```powershell
+# Ejecutar como Administrador
+$password = Read-Host "Introduce tu contraseña/PIN ACTUAL" -AsSecureString
+$passwordPlain = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($password))
+
+$winlogonPath = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon"
+
+Set-ItemProperty -Path $winlogonPath -Name "AutoAdminLogon" -Value "1"
+Set-ItemProperty -Path $winlogonPath -Name "DefaultUserName" -Value $env:USERNAME
+Set-ItemProperty -Path $winlogonPath -Name "DefaultPassword" -Value $passwordPlain
+Set-ItemProperty -Path $winlogonPath -Name "DefaultDomainName" -Value $env:COMPUTERNAME # CLAVE: usar el nombre del PC, NO el dominio
+
+Write-Host "[OK] Autologon configurado para $env:COMPUTERNAME\$env:USERNAME" -ForegroundColor Green
+```
+
+## Solución 3: Login RDP automático remoto (FUTURO)
+
+Para el caso de uso real (controlar el PC desde Telegram), lo que necesitas es:
+
+1. **Encender el PC** → Wake-on-LAN (YA FUNCIONA)
+2. **Esperar que arranque Windows** → Ping (YA FUNCIONA)
+3. **Hacer login automático al RDP** → Esto requiere:
+ - Crear conexión RDP guardada con credenciales (`.rdp`)
+ - Ejecutar `mstsc /v:IP` desde otro equipo
+ - O usar `cmdkey` + `mstsc` para login sin interacción
+
+Este caso de uso **NO requiere autologon de Windows**, solo conexión RDP automática.
+
+## Recomendación final
+
+**Para el sobremesa (PC de trabajo):**
+- ❌ **NO usar autologon** (datos de empresa, sensible)
+- ✅ **Usar control Telegram** solo para encender/apagar
+- ✅ **Login manual** al llegar al despacho
+- ✅ **RDP desde portátil** cuando estés fuera (con credenciales guardadas)
+
+**Para el portátil (si fuera necesario):**
+- Evaluar caso por caso
+- Considerar PIN en lugar de contraseña completa
+- Configurar timeout de bloqueo corto
+
+## Script de verificación
+
+```powershell
+# Verificar estado actual de autologon
+.\INFRAESTRUCTURA\scripts\check_autologon_status.ps1
+```
+
+(Pendiente de crear)
diff --git a/docs/TELEGRAM_BOT_PC_FIXES.md b/docs/TELEGRAM_BOT_PC_FIXES.md
new file mode 100644
index 0000000..27f8348
--- /dev/null
+++ b/docs/TELEGRAM_BOT_PC_FIXES.md
@@ -0,0 +1,130 @@
+# Correcciones Bot Telegram Control PC (20-sep-2026)
+
+## Problemas reportados por el usuario
+
+1. ✅ **Apagado forzoso sin confirmación** → El bot apagaba directamente sin esperar confirmación del asistente
+2. ✅ **No comprobaba estado tras apagar** → No verificaba automáticamente si el PC se apagó correctamente
+3. ✅ **Encendido no respondía** → Enviaba WoL pero no verificaba si el PC arrancó
+4. ✅ **Login crea usuario nuevo** → Problema de configuración de autologon (documentado en `FIX_PROBLEMA_AUTOLOGON_USUARIO_NUEVO.md`)
+
+## Cambios implementados
+
+### 1. Apagado con confirmación obligatoria
+
+**ANTES:**
+```python
+# Apagaba directamente al pulsar el botón
+await apagar_pc() → requests.post(HA_WEBHOOK_APAGAR)
+```
+
+**AHORA:**
+```python
+# Pide confirmación con botones SÍ/NO
+await apagar_pc() → muestra mensaje de confirmación
+await apagar_pc_confirmar() → ejecuta apagado SOLO tras confirmar
+```
+
+**Flujo:**
+1. Usuario pulsa "🔴 Apagar PC"
+2. Bot muestra mensaje: "⚠️ ¿Seguro que quieres apagar?"
+3. Botones: "✅ SÍ, apagar" | "❌ Cancelar"
+4. Solo si confirma → ejecuta webhook
+
+### 2. Watchdog de estado tras apagar
+
+**ANTES:**
+```python
+# Enviaba comando y mostraba mensaje genérico
+response = requests.post(HA_WEBHOOK_APAGAR)
+await msg.edit_text("✅ PC apagado correctamente") # ASUME que funcionó
+```
+
+**AHORA:**
+```python
+# Espera 30s y VERIFICA con ping
+response = requests.post(HA_WEBHOOK_APAGAR)
+await asyncio.sleep(30) # Espera a que se apague
+
+# Ping de verificación
+result = subprocess.run(["powershell", "-Command", f"Test-Connection -ComputerName {PC_IP} -Count 1 -Quiet"])
+online = "True" in result.stdout
+
+if online:
+ await msg.edit_text("⚠️ APAGADO FALLIDO - PC sigue online")
+else:
+ await msg.edit_text("✅ PC APAGADO CONFIRMADO - IP sin respuesta")
+```
+
+### 3. Watchdog de encendido con reintentos
+
+**ANTES:**
+```python
+# Enviaba WoL y mostraba mensaje genérico
+response = requests.post(HA_WEBHOOK_ENCENDER)
+await msg.edit_text("✅ Proceso iniciado. Usa /estado para verificar.") # Usuario tenía que comprobar manualmente
+```
+
+**AHORA:**
+```python
+# Espera 90s y verifica con 3 reintentos
+response = requests.post(HA_WEBHOOK_ENCENDER)
+await asyncio.sleep(90) # Espera inicial
+
+# 3 intentos de ping con 15s entre cada uno
+intentos = 0
+max_intentos = 3
+online = False
+
+while intentos < max_intentos and not online:
+ result = subprocess.run([...ping...])
+ online = "True" in result.stdout
+ if not online:
+ intentos += 1
+ await asyncio.sleep(15)
+
+if online:
+ await msg.edit_text("✅ PC ENCENDIDO CORRECTAMENTE - Responde en {tiempo}s")
+else:
+ await msg.edit_text("❌ ENCENDIDO FALLIDO - Verifica regleta/WoL/BIOS")
+```
+
+### 4. Botón de cancelar
+
+Añadido callback `cancelar` que vuelve al menú principal si el usuario cancela el apagado.
+
+## Tiempos de espera
+
+| Operación | Tiempo espera | Verificaciones |
+|-----------|---------------|----------------|
+| **Apagado** | 30 segundos | 1 ping |
+| **Encendido** | 90 + (hasta 45s) | 3 intentos de ping con 15s entre cada uno |
+
+## Archivos modificados
+
+- `INFRAESTRUCTURA/scripts/telegram_bot_pc.py` — Bot corregido
+- `INFRAESTRUCTURA/docs/FIX_PROBLEMA_AUTOLOGON_USUARIO_NUEVO.md` — Solución al problema de usuario nuevo
+- `INFRAESTRUCTURA/docs/TELEGRAM_BOT_PC_FIXES.md` — Este documento
+
+## Cómo usar el bot corregido
+
+```powershell
+# Detener el bot anterior si está corriendo
+Get-Process python | Where-Object { $_.CommandLine -match "telegram_bot_pc" } | Stop-Process
+
+# Arrancar bot corregido
+python C:\Users\juanm\Documents\Github\INFRAESTRUCTURA\scripts\telegram_bot_pc.py
+```
+
+En Telegram:
+1. `/start` o `/menu` → Menú principal
+2. "🔴 Apagar PC" → **AHORA pide confirmación**
+3. "✅ SÍ, apagar" → Ejecuta y **verifica automáticamente en 30s**
+4. "🟢 Encender PC" → Ejecuta y **verifica automáticamente en ~90-135s**
+5. "📊 Estado PC" → Ping manual (sin cambios)
+
+## Pendientes
+
+- [ ] Crear tarea programada para arrancar el bot al iniciar Windows
+- [ ] Añadir logging a archivo (`telegram_bot_pc.log`)
+- [ ] Decidir sobre autologon (ver `FIX_PROBLEMA_AUTOLOGON_USUARIO_NUEVO.md`)
+- [ ] Script de verificación de configuración: `check_autologon_status.ps1`
diff --git a/scripts/telegram_bot_pc.py b/scripts/telegram_bot_pc.py
index 8f0f183..690bb66 100644
--- a/scripts/telegram_bot_pc.py
+++ b/scripts/telegram_bot_pc.py
@@ -12,6 +12,7 @@ Uso:
"""
import os
import sys
+import asyncio
import requests
import subprocess
from dotenv import load_dotenv
@@ -65,33 +66,95 @@ async def menu(update: Update, context: ContextTypes.DEFAULT_TYPE):
)
async def apagar_pc(update: Update, context: ContextTypes.DEFAULT_TYPE):
- """Apaga el PC"""
+ """Apaga el PC con confirmación"""
query = update.callback_query if update.callback_query else None
+ # CONFIRMAR antes de apagar
+ keyboard = [
+ [
+ InlineKeyboardButton("✅ SÍ, apagar", callback_data='apagar_confirmar'),
+ InlineKeyboardButton("❌ Cancelar", callback_data='cancelar')
+ ]
+ ]
+ reply_markup = InlineKeyboardMarkup(keyboard)
+
+ mensaje = (
+ "⚠️ CONFIRMAR APAGADO\n\n"
+ "¿Seguro que quieres apagar el PC?\n\n"
+ "Se ejecutará:\n"
+ "🔹 Cerrar ventanas\n"
+ "🔹 Apagar luces (barras + cuartillo)\n"
+ "🔹 Apagar pantalla\n"
+ "🔹 Apagar regleta\n"
+ "🔹 Shutdown completo"
+ )
+
if query:
- await query.answer("🔴 Apagando PC...")
- msg = await query.message.reply_text("🔴 Apagando PC...")
+ await query.answer()
+ await query.message.edit_text(mensaje, parse_mode='HTML', reply_markup=reply_markup)
else:
- msg = await update.message.reply_text("🔴 Apagando PC...")
+ await update.message.reply_text(mensaje, parse_mode='HTML', reply_markup=reply_markup)
+
+async def apagar_pc_confirmar(update: Update, context: ContextTypes.DEFAULT_TYPE):
+ """Ejecuta el apagado tras confirmación"""
+ query = update.callback_query
+ await query.answer("🔴 Apagando PC...")
+
+ msg = await query.message.edit_text("🔴 Apagando PC...")
try:
response = requests.post(HA_WEBHOOK_APAGAR, timeout=10)
if response.status_code == 200:
mensaje = (
- "✅ PC apagado correctamente\n\n"
- "🔹 Luces apagadas: barras, cuartillo\n"
- "🔹 Regleta: apagada\n"
- "🔹 PC: sin corriente"
+ "✅ Apagado iniciado\n\n"
+ "🔹 Luces: apagando\n"
+ "🔹 Pantalla: apagando\n"
+ "🔹 PC: shutdown en 10s\n\n"
+ "⏳ Verificando estado en 30s..."
)
+ await msg.edit_text(mensaje, parse_mode='HTML')
+
+ # WATCHDOG: comprobar estado tras 30 segundos
+ await asyncio.sleep(30)
+
+ # Verificar si realmente se apagó
+ try:
+ result = subprocess.run(
+ ["powershell", "-Command", f"Test-Connection -ComputerName {PC_IP} -Count 1 -Quiet"],
+ capture_output=True,
+ timeout=5,
+ text=True
+ )
+ online = "True" in result.stdout
+
+ if online:
+ mensaje_final = (
+ "⚠️ APAGADO FALLIDO\n\n"
+ f"El PC sigue respondiendo en {PC_IP}\n"
+ "Puede que el apagado se cancelara."
+ )
+ else:
+ mensaje_final = (
+ "✅ PC APAGADO CONFIRMADO\n\n"
+ f"🔹 IP {PC_IP}: sin respuesta\n"
+ "🔹 Estado: Offline"
+ )
+ except:
+ mensaje_final = (
+ "✅ Apagado completado\n\n"
+ "No se pudo verificar estado (timeout)"
+ )
+
+ await msg.edit_text(mensaje_final, parse_mode='HTML')
else:
mensaje = f"❌ Error al apagar: HTTP {response.status_code}"
+ await msg.edit_text(mensaje, parse_mode='HTML')
except Exception as e:
mensaje = f"❌ Error: {e}"
-
- await msg.edit_text(mensaje, parse_mode='HTML')
+ await msg.edit_text(mensaje, parse_mode='HTML')
async def encender_pc(update: Update, context: ContextTypes.DEFAULT_TYPE):
- """Enciende el PC"""
+ """Enciende el PC y verifica que arrancó"""
query = update.callback_query if update.callback_query else None
if query:
@@ -105,18 +168,63 @@ async def encender_pc(update: Update, context: ContextTypes.DEFAULT_TYPE):
if response.status_code == 200:
mensaje = (
"✅ Proceso de encendido iniciado\n\n"
- "🔹 Regleta: encendida\n"
+ "🔹 Regleta: encendiendo\n"
"🔹 Esperando 60 segundos...\n"
- "🔹 Wake-on-LAN enviado\n\n"
- "⏳ El PC debería estar listo en ~2 minutos.\n"
- "Usa /estado para verificar."
+ "🔹 Wake-on-LAN: enviando\n\n"
+ "⏳ Verificando en 90 segundos..."
)
+ await msg.edit_text(mensaje, parse_mode='HTML')
+
+ # WATCHDOG: esperar 90s y verificar si arrancó
+ await asyncio.sleep(90)
+
+ # Verificar si el PC arrancó
+ intentos = 0
+ max_intentos = 3
+ online = False
+
+ while intentos < max_intentos and not online:
+ try:
+ result = subprocess.run(
+ ["powershell", "-Command", f"Test-Connection -ComputerName {PC_IP} -Count 2 -Quiet"],
+ capture_output=True,
+ timeout=10,
+ text=True
+ )
+ online = "True" in result.stdout
+ if not online:
+ intentos += 1
+ if intentos < max_intentos:
+ await asyncio.sleep(15)
+ except:
+ intentos += 1
+ if intentos < max_intentos:
+ await asyncio.sleep(15)
+
+ if online:
+ mensaje_final = (
+ "✅ PC ENCENDIDO CORRECTAMENTE\n\n"
+ f"🔹 IP {PC_IP}: responde\n"
+ "🔹 Estado: Online\n"
+ "🔹 Tiempo: ~{} segundos".format(90 + intentos * 15)
+ )
+ else:
+ mensaje_final = (
+ "❌ ENCENDIDO FALLIDO\n\n"
+ f"El PC no responde en {PC_IP}\n"
+ "Verifica:\n"
+ "🔹 Regleta encendida\n"
+ "🔹 PC físicamente apagado\n"
+ "🔹 Wake-on-LAN habilitado en BIOS"
+ )
+
+ await msg.edit_text(mensaje_final, parse_mode='HTML')
else:
mensaje = f"❌ Error al encender: HTTP {response.status_code}"
+ await msg.edit_text(mensaje, parse_mode='HTML')
except Exception as e:
mensaje = f"❌ Error: {e}"
-
- await msg.edit_text(mensaje, parse_mode='HTML')
+ await msg.edit_text(mensaje, parse_mode='HTML')
async def estado_pc(update: Update, context: ContextTypes.DEFAULT_TYPE):
"""Verifica si el PC está encendido"""
@@ -165,16 +273,26 @@ async def estado_pc(update: Update, context: ContextTypes.DEFAULT_TYPE):
await msg.edit_text(mensaje, parse_mode='HTML')
+async def cancelar(update: Update, context: ContextTypes.DEFAULT_TYPE):
+ """Cancela una operación y vuelve al menú"""
+ query = update.callback_query
+ await query.answer("Cancelado")
+ await menu(update, context)
+
async def button_handler(update: Update, context: ContextTypes.DEFAULT_TYPE):
"""Maneja los botones del menú"""
query = update.callback_query
if query.data == 'apagar':
await apagar_pc(update, context)
+ elif query.data == 'apagar_confirmar':
+ await apagar_pc_confirmar(update, context)
elif query.data == 'encender':
await encender_pc(update, context)
elif query.data == 'estado':
await estado_pc(update, context)
+ elif query.data == 'cancelar':
+ await cancelar(update, context)
def main():
"""Arranca el bot"""