v3.6 - Envio automatizado WhatsApp Web con busqueda por nombre y telefono, deteccion de ticks via msg-meta/SVG title, y validacion integral de selectores

This commit is contained in:
2026-06-25 13:29:47 -05:00
commit ec5ebbd20f
15 changed files with 1627 additions and 0 deletions

View File

@@ -0,0 +1,133 @@
import os
import time
from datetime import datetime, timedelta
from .config import EVIDENCIAS_DIR, MAX_REINTENTOS, MINUTOS_ANTICIPACION, WAIT_CHAT
from .driver_session import ensure_session_active
from .utils import asegurar_directorio, log_text
from .whatsapp import abrir_chat, enviar_mensaje, obtener_nombre_chat_actual, validate_sent, verificar_ticks
def construir_resultado(tipo, destino, mensaje, hora, minuto):
return {
"tipo": tipo,
"destino": destino,
"mensaje": mensaje,
"hora_programada": f"{hora if hora is not None else ''}:{minuto if minuto is not None else ''}",
"estado": "NO_INTENTADO",
"intento_exitoso": None,
"timestamp": None,
"captura": None,
"nota": "",
}
def obtener_hora_minuto(row):
try:
return int(float(row["hora"])), int(float(row["minuto"]))
except Exception:
return None, None
def esperar_hora_programada(hora, minuto, destino):
if hora is None or minuto is None:
return
ahora = datetime.now()
hora_envio = datetime(ahora.year, ahora.month, ahora.day, hora, minuto)
if hora_envio <= ahora + timedelta(minutes=MINUTOS_ANTICIPACION):
hora_envio = ahora + timedelta(minutes=2)
espera = (hora_envio - datetime.now()).total_seconds()
if espera > 0:
print(f"Esperando {int(espera)}s hasta {hora_envio.strftime('%H:%M')} para destino {destino}")
time.sleep(espera)
def guardar_captura_evidencia(driver, destino):
nombre_chat = obtener_nombre_chat_actual(driver)
asegurar_directorio(EVIDENCIAS_DIR)
safe = "".join(c for c in nombre_chat if c.isalnum() or c in (" ", "_", "-")).strip().replace(" ", "_")[:40]
if nombre_chat == "chat_desconocido":
nombre_captura = os.path.join(EVIDENCIAS_DIR, f"envio_{destino[:12]}_{int(time.time())}.png")
else:
nombre_captura = os.path.join(EVIDENCIAS_DIR, f"envio_{safe}_{int(time.time())}.png")
try:
driver.save_screenshot(nombre_captura)
return nombre_captura
except Exception:
return None
def procesar_row(driver, row):
tipo = str(row["tipo"]).strip().lower()
destino = str(row["id_destino"]).strip()
mensaje = str(row["mensaje"])
hora, minuto = obtener_hora_minuto(row)
resultado = construir_resultado(tipo, destino, mensaje, hora, minuto)
esperar_hora_programada(hora, minuto, destino)
for intento in range(1, MAX_REINTENTOS + 1):
print(f"Intento {intento}/{MAX_REINTENTOS} -> {tipo} '{destino}'")
if not ensure_session_active(driver):
resultado["estado"] = "ERROR_SESSION"
resultado["nota"] = "No se pudo recuperar sesion"
resultado["timestamp"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
log_text(f"{destino} | ERROR_SESSION")
return resultado
if not abrir_chat(driver, tipo, destino):
resultado["estado"] = "NO_ENCONTRADO"
resultado["nota"] = "No se encontro chat/grupo"
resultado["timestamp"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
log_text(f"{destino} | NO_ENCONTRADO")
return resultado
try:
time.sleep(WAIT_CHAT)
if not enviar_mensaje(driver, mensaje):
raise Exception("Error al ejecutar accion de envio")
time.sleep(1)
validacion = validate_sent(driver, mensaje, timeout=15)
if validacion is True:
resultado["estado"] = verificar_ticks(driver)
resultado["nota"] = "ENVIADO_CONFIRMED"
elif validacion is None:
print("La sesion se perdio durante validacion. Intentando recuperacion...")
if ensure_session_active(driver):
print("Sesion recuperada. Reintentando envio.")
time.sleep(2)
continue
resultado["estado"] = "ERROR_SESSION"
resultado["nota"] = "Sesion perdida y no recuperada"
resultado["timestamp"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
log_text(f"{destino} | ERROR_SESSION durante validacion")
return resultado
else:
resultado["estado"] = "ENVIADO (SIN CONFIRMACION)"
resultado["nota"] = "ENVIADO_SIN_CONFIRMACION"
resultado["captura"] = guardar_captura_evidencia(driver, destino)
resultado["intento_exitoso"] = intento
resultado["timestamp"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
log_text(f"{destino} | {resultado['estado']} | intento {intento} | captura:{resultado['captura']}")
print(f"Resultado para {destino}: {resultado['estado']}")
return resultado
except Exception as exc:
print(f"Error en intento {intento} con {destino}: {exc}")
log_text(f"{destino} | ERROR intento {intento}: {exc}")
resultado["nota"] = str(exc)
if intento < MAX_REINTENTOS:
print("Reintentando en 3s...")
time.sleep(3)
resultado["estado"] = "FALLIDO"
resultado["timestamp"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
log_text(f"{destino} | FALLIDO despues de {MAX_REINTENTOS} intentos")
return resultado