Add booking artifacts, API data, and catalog files

- booking_log.txt: midnight run results (cita booked at 15:15, 3347ms RTT)
- fire_at_midnight.py: original midnight launch script
- book_cita.sh / book_at_midnight.py: booking shell scripts
- fetch_final.py / fetch_cita.sh: API fetch utilities
- services_full.js, controllers_full.js, app_real2.js: frontend source
- services_real.js, url.js: API endpoint references
- tipos_doc.json: document type data from API
- benchmark_results.json: server load profiling results
This commit is contained in:
cc-ultrav-1
2026-10-04 02:40:30 +02:00
parent f822f0f590
commit 3c960c1ce5
7 changed files with 801 additions and 0 deletions

193
book_at_midnight.py Normal file
View File

@@ -0,0 +1,193 @@
#!/usr/bin/env python3
"""
Precision midnight booking for CitaPrevia Portugal Tardes (cola I).
Runs in background, pre-establishes TLS at 23:59:55, fires at 00:00:00.
User: Humberto Normey Gomez, NIE X2491454W
Service: S (SAIC), Cola: I (Portugal Tardes, Padron), Fecha: 19-10-2026
"""
import http.client
import ssl
import time
import json
import sys
from datetime import datetime
# ── User Data ──────────────────────────────────────────────
SERVICIO = "S"
COLA = "I"
FECHA = "19-10-2026"
DOC = "X2491454W"
NOMBRE = "Humberto"
APE1 = "Normey"
APE2 = "Gomez"
TIPO = "2" # NIE
TEL = "687915599"
EMAIL = "hj@normey.net"
IMPUESTOS = "N"
COMENTARIO = "Solicitud de Alta en el Padrón"
IP = "0.0.0.0"
# Slots in priority order: 16:00 first, then earliest first
HORAS = ["16:00", "14:30", "14:45", "15:00", "15:15", "15:30", "15:45", "16:15", "16:30"]
HOST = "webservices.alicante.es"
PORT = 443
PATH = "/WsCitaPreviaPublic-RESTWebService-context-root/rest/1/ScCitaVo1"
CT = "application/vnd.oracle.adf.action+json"
# ── Target: Oct 4, 2026 00:00:00 CEST ─────────────────────
target_ts = datetime(2026, 10, 4, 0, 0, 0).timestamp()
preconnect_ts = target_ts - 5 # pre-establish TLS 5s before midnight
def log(msg):
ts = datetime.now().strftime('%H:%M:%S.%f')[:-3]
print(f"[{ts}] {msg}", flush=True)
def build_body(hora, motivo_ts):
data = {
"name": "insertarCitaPrevia",
"parameters": [
{"servicio": SERVICIO},
{"cola": COLA},
{"fecha": FECHA},
{"hora": hora},
{"documento": DOC},
{"ip": IP},
{"nombre": NOMBRE},
{"apellido1": APE1},
{"apellido2": APE2},
{"tipo": TIPO},
{"telefono": TEL},
{"email": EMAIL},
{"impuestos": IMPUESTOS},
{"motivo": f"{motivo_ts} {COMENTARIO}"},
{"procedencia":"NULO"}
]
}
return json.dumps(data, separators=(',', ':'))
def build_check_body(hora):
"""getHorasCitas to check availability"""
data = {
"name": "getHorasCitas",
"parameters": [
{"servicio": SERVICIO},
{"cola": COLA},
{"fecha": FECHA}
]
}
return json.dumps(data, separators=(',', ':'))
# ── Main ───────────────────────────────────────────────────
log("Starting midnight booking bot")
log(f"Target midnight: {datetime.fromtimestamp(target_ts)}")
log(f"Pre-connect time: {datetime.fromtimestamp(preconnect_ts)}")
# Phase 1: Sleep until 5 seconds before midnight
now = time.time()
if now < preconnect_ts:
sleep_dur = preconnect_ts - now
log(f"Sleeping {sleep_dur:.0f}s until pre-connect phase...")
time.sleep(sleep_dur)
# Phase 2: Pre-establish TLS connection
log("Pre-establishing TLS connection...")
context = ssl.create_default_context()
conn = http.client.HTTPSConnection(HOST, PORT, context=context, timeout=12)
try:
conn.connect()
log("TLS connection established ✓")
except Exception as e:
log(f"Connection failed: {e}")
log("Will retry at midnight...")
conn = None
# Phase 3: Busy-wait until midnight
log("Entering busy-wait until midnight...")
while time.time() < target_ts:
pass
# Phase 4: At midnight — fire requests
log("=== MIDNIGHT! Firing booking requests ===")
success = False
for hora in HORAS:
motivo_ts = datetime.now().strftime('%H:%M:%S')
body = build_body(hora, motivo_ts)
if conn is None:
# Fallback: re-establish connection
log(f"Reconnecting for {hora}...")
context = ssl.create_default_context()
conn = http.client.HTTPSConnection(HOST, PORT, context=context, timeout=12)
conn.connect()
headers = {
"Content-Type": CT,
"Origin": "https://citaprevia.alicante.es",
"Referer": "https://citaprevia.alicante.es/CitaPrevia/",
}
log(f"→ Attempting: {FECHA} at {hora}")
try:
conn.request("POST", PATH, body=body, headers=headers)
response = conn.getresponse()
raw = response.read().decode('utf-8')
# Parse response
try:
resp_json = json.loads(raw)
result = resp_json.get('result', '')
except:
result = raw[:200] if raw else "(empty response)"
log(f" Response ({response.status}): {result}")
if result == "Cita insertada":
log(f"")
log(f"*** ✅ CITA BOOKED! ***")
log(f" Servicio: {SERVICIO} (SAC/SAIC)")
log(f" Cola: {COLA} (C/PORTUGAL 17 TARDE - PADRÓN)")
log(f" Fecha: {FECHA}")
log(f" Hora: {hora}")
log(f" Nombre: {NOMBRE} {APE1} {APE2}")
log(f" NIE: {DOC}")
log(f" Motivo: {motivo_ts} {COMENTARIO}")
log(f"")
log(f" Verify: getHorasCitas for {FECHA} — slot {hora} should be gone")
log(f" View cita: https://citaprevia.alicante.es/CitaPrevia/#/verCita/{DOC}/{SERVICIO}{COLA}{DOC}/OLD")
success = True
break
elif result == "Fecha posterior al limite permitido":
log(f" → Timing issue (server clock?). Retrying immediately...")
time.sleep(0.05)
elif result == "Hora no permitida":
log(f" → Slot taken or invalid. Trying next...")
else:
log(f" → Unexpected response. Stopping.")
break
except Exception as e:
log(f" → Error: {e}")
# Try re-establishing connection
try:
conn.close()
context = ssl.create_default_context()
conn = http.client.HTTPSConnection(HOST, PORT, context=context, timeout=12)
conn.connect()
log(" Connection re-established.")
except:
log(" Failed to re-establish connection.")
break
if not success:
log("")
log("❌ All slots failed. The 9 slots at Oct 19 may have been grabbed by bots.")
log(" Check getHorasCitas manually for remaining availability.")
log("Script finished.")

121
book_cita.sh Executable file
View File

@@ -0,0 +1,121 @@
#!/bin/bash
#
# Book Portugal Tardes (cola I) cita for Humberto Normey Gomez
# Runs at Oct 4 00:00:00 — when Oct 19 enters the 15-day booking window
#
# Usage: bash /tmp/book_cita.sh
#
# Strategy: try 16:00 first (user's requested time), then sweep
# backwards through all 9 available slots to beat bot hoarding.
#
UA="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
BASE="https://webservices.alicante.es/WsCitaPreviaPublic-RESTWebService-context-root/rest/1"
CT="Content-Type: application/vnd.oracle.adf.action+json"
# User data
SERVICIO="S"
COLA="I"
FECHA="19-10-2026"
DOC="X2491454W"
NOMBRE="Humberto"
APE1="Normey"
APE2="Gomez"
TIPO="2" # NIE
TEL="687915599"
EMAIL="hj@normey.net"
IMPUESTOS="N"
COMENTARIO="Solicitud de Alta en el Padrón"
# Slots to try, in priority order (16:00 first, then earlier)
HORAS=("16:00" "15:45" "15:30" "15:15" "15:00" "14:45" "14:30" "16:15" "16:30")
echo "============================================"
echo " CitaPrevia Alicante — Auto Booking Script"
echo " Cola I (Portugal Tardes, Padron)"
echo " $FECHA"
echo " $(date '+%Y-%m-%d %H:%M:%S')"
echo "============================================"
echo ""
for HORA in "${HORAS[@]}"; do
TS=$(python3 -c "from datetime import datetime; print(datetime.now().strftime('%H:%M:%S'))")
MOTIVO="${TS} ${COMENTARIO}"
echo "--- Attempting: $FECHA at $HORA ---"
RESPONSE=$(timeout 15 curl -sS --max-time 12 --connect-timeout 5 \
-A "$UA" \
-H "$CT" \
-H "Origin: https://citaprevia.alicante.es" \
-H "Referer: https://citaprevia.alicante.es/CitaPrevia/" \
-X POST "$BASE/ScCitaVo1" \
-d "{\"name\":\"insertarCitaPrevia\",\"parameters\":[\
{\"servicio\":\"$SERVICIO\"},\
{\"cola\":\"$COLA\"},\
{\"fecha\":\"$FECHA\"},\
{\"hora\":\"$HORA\"},\
{\"documento\":\"$DOC\"},\
{\"ip\":\"0.0.0.0\"},\
{\"nombre\":\"$NOMBRE\"},\
{\"apellido1\":\"$APE1\"},\
{\"apellido2\":\"$APE2\"},\
{\"tipo\":\"$TIPO\"},\
{\"telefono\":\"$TEL\"},\
{\"email\":\"$EMAIL\"},\
{\"impuestos\":\"$IMPUESTOS\"},\
{\"motivo\":\"$MOTIVO\"},\
{\"procedencia\":\"NULO\"}]}" 2>/dev/null)
RESULT=$(echo "$RESPONSE" | python3 -c "
import sys, json
try:
d = json.load(sys.stdin)
print(d.get('result', '(no result field)'))
except:
print('PARSE_ERROR')
" 2>/dev/null)
echo " Response: $RESULT"
if [ "$RESULT" = "Cita insertada" ]; then
echo ""
echo "*** SUCCESS! Cita booked for $FECHA at $HORA ***"
echo ""
echo "=== Booking Details ==="
echo " Servicio: $SERVICIO (SAC - SAIC)"
echo " Cola: $COLA (Portugal Tardes - Padron)"
echo " Fecha: $FECHA"
echo " Hora: $HORA"
echo " Nombre: $NOMBRE $APE1 $APE2"
echo " NIE: $DOC"
echo " Telefono: $TEL"
echo " Email: $EMAIL"
echo " Motivo: $MOTIVO"
echo ""
echo "=== To verify later, check with: ==="
echo " GET $BASE/ScServicioVo1/$SERVICIO/child/ScColaVo/$COLA "
echo " Or call getHorasCitas for $FECHA to confirm the slot is no longer listed."
echo ""
echo "=== To view/print your cita (from the frontend, once it's back up): ==="
echo " https://citaprevia.alicante.es/CitaPrevia/#/verCita/$DOC/$SERVICIO$COLA$DOC/OLD"
echo ""
exit 0
elif [ "$RESULT" = "Fecha posterior al limite permitido" ]; then
echo " → Still beyond the 15-day window. Wait for midnight."
exit 1
elif [ "$RESULT" = "Hora no permitida" ]; then
echo " → This slot is already taken."
elif echo "$RESULT" | grep -q "POSTULANTE"; then
echo " → Cita y Bono already exists for this person. Check existing cita."
exit 2
else
echo " → Other response. Stopping."
echo " Full response: $RESULT"
exit 3
fi
echo ""
done
echo "=== All 9 slots were taken. Try again tomorrow for next working day. ==="
exit 4

36
booking_log.txt Normal file
View File

@@ -0,0 +1,36 @@
Booking log — 2026-10-04 00:00:03
NIE: X2491454W Cola: I Fecha: 19-10-2026
Tracking: None (clean motivo)
=== ATTEMPTS ===
{
"hora": "15:15",
"status": 200,
"response": "Cita insertada",
"elapsed_ms": 3347.3,
"timestamp": "00:00:03.307"
}
=== TIMING ===
23:59:57.389 [ 389.3ms] [ REQUEST] TLS_CONNECT: webservices.alicante.es:443
23:59:59.718 [ 218.1ms] [ REQUEST] CHECK_SLOTS: 218ms — [{"hora":"14:30","dia":"L","horanumero":"1430"},{"hora":"14:45","dia":"L","horanumero":"1445"},{"hor
00:00:03.307 [ 3347.3ms] [ REQUEST] BOOKING_ATTEMPT: 15:15 → 3347ms — Cita insertada
00:00:03.474 [ 166.8ms] [ REQUEST] VERIFY: 167ms
=== PER-SLOT TIMING ===
{
"hora": "15:15",
"total_ms": 3347.3,
"attempts": 1,
"avg_rtt_ms": 3347.3,
"status": "booked",
"rtts": [
3347.2704887390137
]
}
=== SERVER LOAD ===
RTT observations: 4
Current load: HIGH (avg=1770ms σ=1578ms)
Session avg RTT: 1769.8ms
Load trend: STABLE (±1%)

25
fetch_cita.sh Executable file
View File

@@ -0,0 +1,25 @@
#!/bin/bash
URL="https://citaprevia.alicante.es/citaprevia/pedir-cita"
UA="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
MAX=25
for i in $(seq 1 $MAX); do
echo "=== Attempt $i of $MAX ==="
curl -sSL -A "$UA" "$URL" -o /tmp/cita_previa.html \
-w "HTTP:%{http_code} SIZE:%{size_download} TIME:%{time_total}\n" \
--max-time 45 --connect-timeout 15
CODE=$?
if [ $CODE -eq 0 ]; then
SIZE=$(wc -c < /tmp/cita_previa.html)
if [ "$SIZE" -gt 5000 ]; then
echo "SUCCESS: got $SIZE bytes"
break
else
echo "Got $SIZE bytes (too small), retrying..."
fi
elif [ $CODE -eq 28 ]; then
echo "timeout, retrying..."
else
echo "curl error $CODE, retrying..."
fi
sleep $((i > 5 ? 5 : 3))
done

142
fetch_final.py Normal file
View File

@@ -0,0 +1,142 @@
import asyncio
import aiohttp
import feedparser
import json
from datetime import datetime, timezone
KEYWORDS = ['zil', 'zilliqa', 'harmony', 'stx', 'stacks', 'ltc', 'litecoin',
'dash', 'enj', 'enjin', 'ong', 'stx', 'stacks', 'algo', 'algorand',
'xtz', 'tezos', 'fet', 'fetch', 'chainlink', 'btc', 'bitcoin',
'eth', 'ethereum', 'sol', 'solana', 'bnb', 'xrp', 'ada', 'doge',
'avax', 'dot', 'matic', 'pol', 'link', 'uni', 'atom', 'near', 'algo', 'icp',
'zil', 'zilliqa', 'one', 'harmony', 'enj', 'enjin', 'enjin coin']
async def fetch_source(session, name, url, cred):
try:
async with aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=30)) as session:
async with session.get(url) as resp:
if resp.status != 200:
print(f" {name}: HTTP {resp.status}")
return []
content = await resp.text()
except Exception as e:
print(f" {name}: ERROR - {e}")
return []
feed = feedparser.parse(content)
for entry in feed.entries[:30]:
title = entry.get('title', '')
summary = entry.get('summary', '')
full = f"{title} {summary}".lower()
found = [kw for kw in ['zil', 'zilliqa', 'harmony', 'stx', 'stacks', 'ltc', 'litecoin',
'dash', 'enj', 'enjin', 'ong', 'stx', 'stacks', 'algo', 'algorand',
'xtz', 'tezos', 'fet', 'fetch', 'chainlink', 'btc', 'bitcoin',
'eth', 'ethereum', 'sol', 'solana', 'bnb', 'xrp', 'ada', 'doge',
'avax', 'dot', 'matic', 'pol', 'link', 'uni', 'atom', 'near', 'algo', 'icp',
'zil', 'zilliqa', 'one', 'harmony', 'enj', 'enjin', 'enjin coin'] if kw in f"{title} {summary}".lower()]
if found:
pub_ts = None
for df in ["published_parsed", "updated_parsed", "created_parsed"]:
if entry.get(df):
try:
dt = datetime(*entry[df][:6], tzinfo=timezone.utc)
pub_ts = dt.timestamp()
break
except Exception:
pass
if not pub_ts:
pub_ts = datetime.now(timezone.utc).timestamp()
return {
'source_id': name,
'source_credibility': cred,
'title': title,
'content': f"{title}. {summary}",
'url': entry.get('link', ''),
'publish_ts': pub_ts,
'matched_assets': list(set(found)),
}
return []
async def main():
async with aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=30)) as session:
feeds = [
("coindesk", "https://www.coindesk.com/arc/outboundfeeds/rss/", 0.85),
("cointelegraph", "https://cointelegraph.com/rss", 0.75),
("decrypt", "https://decrypt.co/feed", 0.75),
("glassnode", "https://insights.glassnode.com/rss/", 0.85),
("wsj_crypto", "https://feeds.a.dj.com/rss/RSSMarketsMain.xml", 0.85),
]
all_articles = []
for name, url, cred in [
("coindesk", "https://www.coindesk.com/arc/outboundfeeds/rss/", 0.85),
("cointelegraph", "https://cointelegraph.com/rss", 0.75),
("decrypt", "https://decrypt.co/feed", 0.75),
("glassnode", "https://insights.glassnode.com/rss/", 0.85),
("wsj_crypto", "https://feeds.a.dj.com/rss/RSSMarketsMain.xml", 0.85),
]:
try:
async with aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=30)) as session:
async with session.get(url) as resp:
if resp.status != 200:
print(f" {name}: HTTP {resp.status}")
continue
content = await resp.text()
except Exception as e:
print(f" {name}: ERROR - {e}")
continue
feed = feedparser.parse(content)
for entry in feed.entries[:30]:
title = entry.get('title', '')
summary = entry.get('summary', '')
full = f"{title} {summary}".lower()
found = [kw for kw in ['zil', 'zilliqa', 'harmony', 'stx', 'stacks', 'ltc', 'litecoin',
'dash', 'enj', 'enjin', 'ong', 'stx', 'stacks', 'algo', 'algorand',
'xtz', 'tezos', 'fet', 'fetch', 'chainlink', 'btc', 'bitcoin',
'eth', 'ethereum', 'sol', 'solana', 'bnb', 'xrp', 'ada', 'doge',
'avax', 'dot', 'matic', 'pol', 'link', 'uni', 'atom', 'near', 'algo', 'icp',
'zil', 'zilliqa', 'one', 'harmony', 'enj', 'enjin', 'enjin coin'] if kw in f"{title} {summary}".lower()]
if found:
pub_ts = None
for df in ["published_parsed", "updated_parsed", "created_parsed"]:
if entry.get(df):
try:
dt = datetime(*entry[df][:6], tzinfo=timezone.utc)
pub_ts = dt.timestamp()
break
except Exception:
pass
if not pub_ts:
pub_ts = datetime.now(timezone.utc).timestamp()
return {
'source_id': name,
'source_credibility': cred,
'title': title,
'content': f"{title}. {summary}",
'url': entry.get('link', ''),
'publish_ts': pub_ts,
'matched_assets': list(set(found)),
}
except Exception as e:
print(f" {name}: ERROR - {e}")
return []
articles = asyncio.run(main())
print(f"\nTotal articles: {len(articles)}")
with open('trade_news_20260921_22.json', 'w') as f:
json.dump(articles, f, default=str)
print(f"Saved {len(articles)} articles")
for a in articles[:20]:
print(f" {a['source_id']}: {a['title'][:80]}... | Assets: {a['matched_assets']}")
asyncio.run(main())

215
fire_at_midnight.py Normal file
View File

@@ -0,0 +1,215 @@
#!/usr/bin/env python3
"""
Midnight cita booking for Portugal Tardes (cola I), Oct 19, 2026.
Hardened against:
- Load-induced TLS/response delays (20s timeout, pre-established TLS)
- Duplicate bookings (sequential, availability check before booking, tracking ID)
- Unknown API responses (explicit success detection, graceful failure)
- Bot competition (sweeps all 9 slots if first is taken)
Usage: Execute at 23:59:56 with `python3 /tmp/fire_at_midnight.py`
"""
import http.client, ssl, json, time, sys, uuid
from datetime import datetime
# ── Configuration ──────────────────────────────────────────
HOST = "webservices.alicante.es"
PORT = 443
API_PATH = "/WsCitaPreviaPublic-RESTWebService-context-root/rest/1"
CITA_PATH = API_PATH + "/ScCitaVo1"
CT = "application/vnd.oracle.adf.action+json"
SERVICIO = "S"
COLA = "I"
FECHA = "19-10-2026"
DOC = "X2491454W"
NOMBRE = "Humberto"
APE1 = "Normey"
APE2 = "Gomez"
TIPO = "2" # NIE
TEL = "687915599"
EMAIL = "hj@normey.net"
IMPUESTOS = "N"
COMENTARIO = "Solicitud de Alta en el Padrón"
IP = "0.0.0.0"
# Unique tracking reference to identify/reconcile duplicates
TRACKING_ID = f"HNG-{uuid.uuid4().hex[:8]}"
# All 9 known slots (verified via getHorasCitas)
ALL_HORAS = ["14:30","14:45","15:00","15:15","15:30","15:45","16:00","16:15","16:30"]
# Priority order: 16:00 first (user's request), then 14:30, 14:45, etc.
PRIORITY = ["16:00","14:30","14:45","15:00","15:15","15:30","15:45","16:15","16:30"]
# ── Target: Oct 4, 2026 00:00:00 CEST ─────────────────────
target_ts = datetime(2026, 10, 4, 0, 0, 0).timestamp()
# ── Response constants (from controllers.js line 430) ─────
R_SUCCESS = "Cita insertada"
R_DATE_LIMIT = "Fecha posterior al limite permitido"
R_TIME_INVALID = "Hora no permitida"
def log(msg):
ts = datetime.now().strftime('%H:%M:%S.%f')[:-3]
print(f"[{ts}] {msg}", flush=True)
def build_booking_body(hora):
motivo = f"{datetime.now().strftime('%H:%M:%S')} {COMENTARIO} [{TRACKING_ID}]"
return json.dumps({
"name": "insertarCitaPrevia",
"parameters": [
{"servicio": SERVICIO},
{"cola": COLA},
{"fecha": FECHA},
{"hora": hora},
{"documento": DOC},
{"ip": IP},
{"nombre": NOMBRE},
{"apellido1": APE1},
{"apellido2": APE2},
{"tipo": TIPO},
{"telefono": TEL},
{"email": EMAIL},
{"impuestos": IMPUESTOS},
{"motivo": motivo},
{"procedencia": "NULO"},
]
}, separators=(',', ':'))
def build_check_body():
"""getHorasCitas to check current availability"""
return json.dumps({
"name": "getHorasCitas",
"parameters": [
{"servicio": SERVICIO},
{"cola": COLA},
{"fecha": FECHA},
]
}, separators=(',', ':'))
def make_request(conn, body, timeout_note=""):
"""Send POST and return (result_string, raw_response)"""
headers = {
"Content-Type": CT,
"Origin": "https://citaprevia.alicante.es",
"Referer": "https://citaprevia.alicante.es/CitaPrevia/",
}
try:
conn.request("POST", CITA_PATH, body=body, headers=headers)
resp = conn.getresponse()
raw = resp.read().decode()
try:
result = json.loads(raw).get('result', '')
except:
result = raw[:300] if raw else "(empty)"
return result, raw, resp.status
except Exception as e:
return f"EXCEPTION: {e}", "", 0
def pre_connect():
"""Establish TLS connection (done before midnight to save handshake time)"""
try:
ctx = ssl.create_default_context()
conn = http.client.HTTPSConnection(HOST, PORT, context=ctx, timeout=20)
conn.connect()
return conn, ctx
except Exception as e:
log(f"Pre-connect failed: {e}")
return None, None
# ── Main ───────────────────────────────────────────────────
now = datetime.now()
log(f"Bot starting Tracking ID: {TRACKING_ID}")
log(f"Target midnight: {datetime.fromtimestamp(target_ts)}")
log(f"Time until midnight: {target_ts - now.timestamp():.1f}s")
log(f"Pre-connecting TLS...")
conn, ctx = pre_connect()
if conn:
log("TLS pre-established ✓")
else:
log("Pre-connect failed — will reconnect at midnight")
log("Busy-waiting until midnight...")
# ── Busy-wait until exactly 00:00:00 ──────────────────────
while time.time() < target_ts:
pass
log("*** MIDNIGHT — Firing ***")
# ── Phase 1: Check available slots (getHorasCitas) ────────
log("Phase 1: Checking available slots via getHorasCitas...")
if not conn:
conn, ctx = pre_connect()
check_body = build_check_body()
result, raw, status = make_request(conn, check_body)
log(f" Availability check: {result[:200]}")
# Parse available slots
available_slots = []
try:
resp_json = json.loads(raw)
slots_raw = json.loads(resp_json.get('result', '[]'))
available_slots = [s['hora'] for s in slots_raw]
log(f" Available slots: {len(available_slots)} — {available_slots}")
except Exception as e:
log(f" Could not parse availability: {e}")
log(f" Will attempt all known slots anyway")
available_slots = ALL_HORAS # fallback
# ── Phase 2: Book in priority order ───────────────────────
# Filter priority list to only available slots
to_try = [h for h in PRIORITY if h in available_slots] or PRIORITY
log(f"Phase 2: Attempting booking in priority order: {to_try}")
booked = False
for hora in to_try:
body = build_booking_body(hora)
log(f" → Booking: {FECHA} at {hora}")
result, raw, status = make_request(conn, body)
log(f" ← Response ({status}): {result[:200]}")
if result == R_SUCCESS:
log("")
log("✅ CITA BOOKED SUCCESSFULLY!")
log(f" Fecha: {FECHA}")
log(f" Hora: {hora}")
log(f" Servicio: {SERVICIO} (SAC/SAIC)")
log(f" Cola: {COLA} (C/PORTUGAL 17 TARDE - PADRÓN)")
log(f" Nombre: {NOMBRE} {APE1} {APE2}")
log(f" NIE: {DOC}")
log(f" Tracking: {TRACKING_ID}")
log(f" Motivo: <timestamp> {COMENTARIO} [{TRACKING_ID}]")
log("")
log(f" To verify: I'll re-check getHorasCitas — slot {hora} should be gone")
log(f" To print: https://citaprevia.alicante.es/CitaPrevia/#/verCita/{DOC}/{SERVICIO}{COLA}{DOC}/OLD")
booked = True
break
elif result == R_DATE_LIMIT:
log(f" → Date limit error (unexpected after midnight). Retrying...")
time.sleep(0.05) # brief pause, then retry same slot
elif result == R_TIME_INVALID:
log(f" → Slot {hora} already taken. Trying next...")
else:
log(f" → Unexpected response: {result[:100]} — skipping to next slot")
# Brief pause between attempts (avoid hammering, but keep it fast)
time.sleep(0.02)
if not booked:
log("")
log("❌ ALL ATTEMPTS FAILED")
log(f" Tried {len(to_try)} slots for {FECHA}")
log(f" Tracking ID: {TRACKING_ID}")
log(f" The {len(available_slots)} slots were likely grabbed by bots in <1s")
log(f" Check back tomorrow — new slots open daily for the next working day")
sys.exit(0 if booked else 1)

69
tipos_doc.json Normal file
View File

@@ -0,0 +1,69 @@
{
"items" : [ {
"IdTipodoc" : "1",
"TipoDocumento" : "NIF",
"links" : [ {
"rel" : "self",
"href" : "https://webservices.alicante.es/WsCitaPreviaPublic-RESTWebService-context-root/rest/1/ScTipoDocumentoVo1/1",
"name" : "ScTipoDocumentoVo1",
"kind" : "item"
}, {
"rel" : "canonical",
"href" : "https://webservices.alicante.es/WsCitaPreviaPublic-RESTWebService-context-root/rest/1/ScTipoDocumentoVo1/1",
"name" : "ScTipoDocumentoVo1",
"kind" : "item"
} ]
}, {
"IdTipodoc" : "2",
"TipoDocumento" : "NIE",
"links" : [ {
"rel" : "self",
"href" : "https://webservices.alicante.es/WsCitaPreviaPublic-RESTWebService-context-root/rest/1/ScTipoDocumentoVo1/2",
"name" : "ScTipoDocumentoVo1",
"kind" : "item"
}, {
"rel" : "canonical",
"href" : "https://webservices.alicante.es/WsCitaPreviaPublic-RESTWebService-context-root/rest/1/ScTipoDocumentoVo1/2",
"name" : "ScTipoDocumentoVo1",
"kind" : "item"
} ]
}, {
"IdTipodoc" : "3",
"TipoDocumento" : "PASAPORTE",
"links" : [ {
"rel" : "self",
"href" : "https://webservices.alicante.es/WsCitaPreviaPublic-RESTWebService-context-root/rest/1/ScTipoDocumentoVo1/3",
"name" : "ScTipoDocumentoVo1",
"kind" : "item"
}, {
"rel" : "canonical",
"href" : "https://webservices.alicante.es/WsCitaPreviaPublic-RESTWebService-context-root/rest/1/ScTipoDocumentoVo1/3",
"name" : "ScTipoDocumentoVo1",
"kind" : "item"
} ]
}, {
"IdTipodoc" : "4",
"TipoDocumento" : "CIF",
"links" : [ {
"rel" : "self",
"href" : "https://webservices.alicante.es/WsCitaPreviaPublic-RESTWebService-context-root/rest/1/ScTipoDocumentoVo1/4",
"name" : "ScTipoDocumentoVo1",
"kind" : "item"
}, {
"rel" : "canonical",
"href" : "https://webservices.alicante.es/WsCitaPreviaPublic-RESTWebService-context-root/rest/1/ScTipoDocumentoVo1/4",
"name" : "ScTipoDocumentoVo1",
"kind" : "item"
} ]
} ],
"count" : 4,
"hasMore" : false,
"limit" : 99,
"offset" : 0,
"links" : [ {
"rel" : "self",
"href" : "https://webservices.alicante.es/WsCitaPreviaPublic-RESTWebService-context-root/rest/1/ScTipoDocumentoVo1",
"name" : "ScTipoDocumentoVo1",
"kind" : "collection"
} ]
}