-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmonitor_citas.py
More file actions
272 lines (226 loc) · 11.6 KB
/
Copy pathmonitor_citas.py
File metadata and controls
272 lines (226 loc) · 11.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
#!/usr/bin/env python3
"""
Monitor de Citas - Consulado de España en México
Revisa periódicamente la disponibilidad de citas y notifica por email.
"""
import requests
import smtplib
import time
import json
from datetime import datetime, timedelta
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
import winsound # Para alerta de sonido en Windows (opcional)
# ============================================================================
# CONFIGURACIÓN - MODIFICA ESTOS VALORES
# ============================================================================
# Configuración del consulado (extraída de tu URL)
CONFIG = {
# La publickey se extrae de tu URL del widget
# Tu URL: https://www.citaconsular.es/es/hosteds/widgetdefault/2d8bebcf444f3db762074e5daef723a59/
"publickey": "2d8bebcf444f3db762074e5daef723a59",
# Estos valores necesitas obtenerlos de la página (ver instrucciones abajo)
"services": [""], # ID del servicio de pasaporte - DEBES COMPLETAR
"agendas": [""], # ID de la agenda - DEBES COMPLETAR
# Intervalo entre revisiones (en segundos)
# IMPORTANTE: No uses intervalos muy cortos para evitar bloqueos
"intervalo_segundos": 300, # 5 minutos
# Cuántos meses hacia adelante revisar
"meses_revisar": 3,
}
# Configuración de email (Gmail ejemplo)
EMAIL_CONFIG = {
"smtp_server": "smtp.gmail.com",
"smtp_port": 587,
"sender_email": "tu_email@gmail.com",
"sender_password": "tu_app_password", # Usa App Password, no tu contraseña normal
"receiver_email": "tu_email@gmail.com",
}
# ============================================================================
# CÓDIGO DEL MONITOR - NO MODIFICAR A MENOS QUE SEPAS LO QUE HACES
# ============================================================================
class MonitorCitas:
def __init__(self):
self.base_url = "https://www.citaconsular.es/onlinebookings/datetime/"
self.widget_url = f"https://www.citaconsular.es/es/hosteds/widgetdefault/{CONFIG['publickey']}/"
self.session = requests.Session()
self.session.headers.update({
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
"Referer": "https://www.citaconsular.es/",
"Accept": "application/json, text/javascript, */*; q=0.01",
"Accept-Language": "es-ES,es;q=0.9,en;q=0.8",
})
self.ultima_notificacion = None
def obtener_fechas_disponibles(self, fecha_inicio, fecha_fin):
"""Consulta la API para obtener fechas con citas disponibles."""
params = {
"callback": "",
"type": "default",
"publickey": CONFIG["publickey"],
"lang": "es",
"src": self.widget_url,
"start": fecha_inicio.strftime("%Y-%m-%d"),
"end": fecha_fin.strftime("%Y-%m-%d"),
}
# Agregar servicios y agendas
for service in CONFIG["services"]:
if service:
params["services[]"] = service
for agenda in CONFIG["agendas"]:
if agenda:
params["agendas[]"] = agenda
try:
response = self.session.get(self.base_url, params=params, timeout=30)
response.raise_for_status()
# La respuesta puede venir como JSONP, limpiamos
texto = response.text.strip()
if texto.startswith("(") and texto.endswith(")"):
texto = texto[1:-1]
data = json.loads(texto)
return data
except requests.exceptions.RequestException as e:
print(f"[ERROR] Error de conexión: {e}")
return None
except json.JSONDecodeError as e:
print(f"[ERROR] Error parseando respuesta: {e}")
print(f"Respuesta recibida: {response.text[:500]}")
return None
def buscar_citas_disponibles(self):
"""Busca citas disponibles en los próximos meses."""
citas_encontradas = []
fecha_actual = datetime.now()
for mes in range(CONFIG["meses_revisar"]):
fecha_inicio = fecha_actual + timedelta(days=mes * 30)
fecha_fin = fecha_inicio + timedelta(days=30)
print(f" Revisando {fecha_inicio.strftime('%Y-%m-%d')} a {fecha_fin.strftime('%Y-%m-%d')}...")
data = self.obtener_fechas_disponibles(fecha_inicio, fecha_fin)
if data and "Slots" in data:
for slot in data["Slots"]:
if slot.get("times") and len(slot["times"]) > 0:
citas_encontradas.append({
"fecha": slot["date"],
"horarios": slot["times"],
"cantidad": len(slot["times"])
})
# Pequeña pausa entre requests
time.sleep(2)
return citas_encontradas
def enviar_email(self, citas):
"""Envía notificación por email."""
if not EMAIL_CONFIG["sender_email"] or EMAIL_CONFIG["sender_email"] == "tu_email@gmail.com":
print("[AVISO] Email no configurado, saltando notificación por email")
return False
try:
mensaje = MIMEMultipart("alternative")
mensaje["Subject"] = "🎉 ¡HAY CITAS DISPONIBLES EN EL CONSULADO!"
mensaje["From"] = EMAIL_CONFIG["sender_email"]
mensaje["To"] = EMAIL_CONFIG["receiver_email"]
# Crear contenido del email
texto = "¡Se encontraron citas disponibles!\n\n"
html = "<html><body><h1>🎉 ¡Citas Disponibles!</h1><ul>"
for cita in citas:
texto += f"Fecha: {cita['fecha']} - {cita['cantidad']} horarios disponibles\n"
texto += f" Horarios: {', '.join(cita['horarios'])}\n\n"
html += f"<li><strong>{cita['fecha']}</strong>: {cita['cantidad']} horarios<br>"
html += f"<small>{', '.join(cita['horarios'])}</small></li>"
texto += f"\n¡Corre a reservar! {self.widget_url}"
html += f"</ul><p><a href='{self.widget_url}'>👉 IR A RESERVAR AHORA</a></p></body></html>"
mensaje.attach(MIMEText(texto, "plain"))
mensaje.attach(MIMEText(html, "html"))
with smtplib.SMTP(EMAIL_CONFIG["smtp_server"], EMAIL_CONFIG["smtp_port"]) as server:
server.starttls()
server.login(EMAIL_CONFIG["sender_email"], EMAIL_CONFIG["sender_password"])
server.sendmail(
EMAIL_CONFIG["sender_email"],
EMAIL_CONFIG["receiver_email"],
mensaje.as_string()
)
print("[OK] Email enviado correctamente")
return True
except Exception as e:
print(f"[ERROR] No se pudo enviar email: {e}")
return False
def hacer_sonido(self):
"""Reproduce un sonido de alerta (Windows)."""
try:
# Reproduce un beep de alerta
for _ in range(5):
winsound.Beep(1000, 500) # 1000 Hz por 500ms
time.sleep(0.3)
except:
print("\a" * 5) # Beep de terminal como fallback
def ejecutar(self):
"""Ejecuta el monitor en bucle."""
print("=" * 60)
print("MONITOR DE CITAS - CONSULADO DE ESPAÑA EN MÉXICO")
print("=" * 60)
print(f"Revisando cada {CONFIG['intervalo_segundos']} segundos")
print(f"Buscando en los próximos {CONFIG['meses_revisar']} meses")
print(f"URL del widget: {self.widget_url}")
print("-" * 60)
# Validar configuración
if not CONFIG["services"][0] or not CONFIG["agendas"][0]:
print("\n⚠️ ATENCIÓN: Necesitas configurar 'services' y 'agendas'")
print(" Ejecuta primero: python monitor_citas.py --obtener-ids")
print(" para obtener estos valores de la página.\n")
contador = 0
while True:
contador += 1
ahora = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
print(f"\n[{ahora}] Revisión #{contador}")
citas = self.buscar_citas_disponibles()
if citas:
print(f"\n🎉 ¡¡¡ SE ENCONTRARON {len(citas)} FECHA(S) CON CITAS !!!")
for cita in citas:
print(f" 📅 {cita['fecha']}: {cita['cantidad']} horarios")
print(f" Horarios: {', '.join(cita['horarios'][:5])}...")
# Evitar spam de notificaciones
ahora_ts = datetime.now()
if (self.ultima_notificacion is None or
(ahora_ts - self.ultima_notificacion).seconds > 1800): # 30 min entre notificaciones
self.enviar_email(citas)
self.hacer_sonido()
self.ultima_notificacion = ahora_ts
else:
print(" (Notificación ya enviada recientemente)")
print(f"\n 👉 Reserva aquí: {self.widget_url}")
else:
print(" No hay citas disponibles en este momento.")
print(f"\n Próxima revisión en {CONFIG['intervalo_segundos']} segundos...")
time.sleep(CONFIG['intervalo_segundos'])
def obtener_ids_desde_pagina():
"""Instrucciones para obtener los IDs necesarios."""
print("""
╔══════════════════════════════════════════════════════════════════╗
║ CÓMO OBTENER LOS IDs DE SERVICIOS Y AGENDAS ║
╠══════════════════════════════════════════════════════════════════╣
║ ║
║ 1. Abre la página del consulado en Chrome/Firefox ║
║ ║
║ 2. Presiona F12 para abrir las herramientas de desarrollador ║
║ ║
║ 3. Ve a la pestaña "Network" (Red) ║
║ ║
║ 4. En la página, selecciona el servicio de "Pasaporte" ║
║ y haz clic para ver el calendario ║
║ ║
║ 5. En Network, busca una petición a "datetime" ║
║ ║
║ 6. Haz clic en esa petición y mira los parámetros: ║
║ - services[]: este es tu ID de servicio (ej: bkt123456) ║
║ - agendas[]: este es tu ID de agenda (ej: bkt654321) ║
║ ║
║ 7. Copia esos valores y ponlos en la configuración del script ║
║ ║
╚══════════════════════════════════════════════════════════════════╝
""")
if __name__ == "__main__":
import sys
if len(sys.argv) > 1 and sys.argv[1] == "--obtener-ids":
obtener_ids_desde_pagina()
else:
monitor = MonitorCitas()
try:
monitor.ejecutar()
except KeyboardInterrupt:
print("\n\nMonitor detenido por el usuario.")