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"""