#!/usr/bin/env python3 """Fase 5 (local): paridad entre el fichero capturado y lo que sirve ahora el Joomla local. Detecta capturas truncadas, paginas de error congeladas y desfases de contenido. No toca produccion. """ import os, re, sys, random, hashlib, subprocess, json from urllib.parse import urlsplit BASE = "/home/rafa/joomla-migration/mirror-antiguo" RUN = open(os.path.join(BASE, "CURRENT_RUN")).read().strip() DIR = os.path.join(BASE, "runs", RUN) RAW = os.path.join(DIR, "raw", "antiguo.feadulta.com") N = int(sys.argv[1]) if len(sys.argv) > 1 else 200 TITLE = re.compile(r"]*>(.*?)", re.I | re.S) SCRIPTS = re.compile(r"<(script|style)[^>]*>.*?", re.I | re.S) TAGS = re.compile(r"<[^>]+>") WS = re.compile(r"\s+") # bloques que cambian entre peticiones. El contador de visitas de K2 ("Read N times") se # incrementa con nuestra propia peticion, asi que dos lecturas de la MISMA pagina nunca coinciden: # se normaliza en vez de contarlo como diferencia. VOLATILE = re.compile(r"[0-9a-f]{32}|csrf|token", re.I) HITS = re.compile(r"(Read|Visto|Le[iĆ­]do)\s+\d+\s+(times|veces)", re.I) def texthash(s): s = SCRIPTS.sub(" ", s) s = TAGS.sub(" ", s) s = WS.sub(" ", s).strip() s = VOLATILE.sub("", s) s = HITS.sub("HITS", s) return hashlib.sha256(s.encode("utf-8", "replace")).hexdigest(), len(s) def title(s): m = TITLE.search(s) return WS.sub(" ", m.group(1)).strip() if m else "" urls = [l.strip() for l in open(os.path.join(BASE, "inventory", "urls-input.txt"))] random.seed(20260729) sample = random.sample(urls, min(N, len(urls))) res = {"muestra": len(sample), "ok_status": 0, "falta_fichero": 0, "titulo_igual": 0, "titulo_distinto": 0, "texto_igual": 0, "texto_distinto": 0, "diffs": []} for u in sample: path = urlsplit(u).path fp = os.path.join(RAW, path.lstrip("/")) if path.endswith("/"): fp = os.path.join(fp, "index.html") if not os.path.exists(fp): res["falta_fichero"] += 1 res["diffs"].append({"url": u, "motivo": "fichero ausente"}) continue res["ok_status"] += 1 disk = open(fp, encoding="utf-8", errors="replace").read() live = subprocess.run( ["curl", "-s", "--max-time", "60", "-H", "Host: antiguo.feadulta.com", "http://127.0.0.1:8086" + path], capture_output=True).stdout.decode("utf-8", "replace") td, tl = title(disk), title(live) if td == tl: res["titulo_igual"] += 1 else: res["titulo_distinto"] += 1 res["diffs"].append({"url": u, "motivo": "titulo", "mirror": td[:120], "vivo": tl[:120]}) hd, ld = texthash(disk) hl, ll = texthash(live) if hd == hl: res["texto_igual"] += 1 else: res["texto_distinto"] += 1 res["diffs"].append({"url": u, "motivo": "texto", "len_mirror": ld, "len_vivo": ll}) out = os.path.join(DIR, "parity-report.json") json.dump(res, open(out, "w"), indent=2, ensure_ascii=False) for k in ("muestra", "falta_fichero", "titulo_igual", "titulo_distinto", "texto_igual", "texto_distinto"): print(k, "=", res[k]) print("informe:", out) for d in res["diffs"][:15]: print(" ", d)