estado: cerrar los siete puntos ciegos del vigilante, y pasarlo a diario
Recuento de los fallos de esta semana: diecisiete. Ni uno solo fue un caso que
el vigilante juzgara mal — los diecisiete estaban en sitios donde el vigilante
no mira. Eso es una buena noticia: los puntos ciegos son enumerables y el
juicio equivocado no.
Lo que no miraba nadie, con el fallo real que lo motiva:
tags 5 duplicados vivieron TRES SEMANAS repartiendo 7 posts entre dos
archivos flacos, los dos ofrecidos a Google en sitemap-tags.xml
páginas todas las reglas eran de posts; /about no tiene metas en ninguno
de los dos sitios
<head> el EN emite DOS Article con titulares distintos, y sigue con
twitter:site=@ghost y article:publisher=facebook.com/ghost
robots.txt el bloque «Cloudflare Managed» apareció solo en el EN y nadie lo
decidió; cambia sin pasar por git
hreflang lo comprobaba SOLO seo_audit.py, que NO TIENE TIMER
hub se publicó una vez y se pudrió: «31 casos» con 33
programados check_rules hace `if status != "published": continue`, así que un
programado con la meta mal es invisible hasta el día DESPUÉS de
publicarse — cuando ya lo ha visto Google
Decisiones:
- módulo aparte y no dentro de seo_watch.py (ya pasa de 500 líneas), pero
colgando del MISMO vigilante: una huella por sitio y un solo aviso. Dos
notificadores compitiendo es como se deja de leer un informe
- robots.txt se compara contra una INSTANTÁNEA versionada. Sin una foto
contra la que comparar, un cambio que nadie hizo es invisible para siempre
- un chequeo que revienta se convierte en HALLAZGO, no tumba a los demás. Lo
peligroso de un vigilante no es que falle: es que falle y parezca limpio
- og_*/twitter_* vacíos en una PÁGINA quedan fuera por COMPROBACIÓN, no por
comodidad: Ghost los deriva de las metas al renderizar, verificado sobre el
HTML público del hub ES. Avisar de eso sería mandar a arreglar algo que ya
está bien
Timer semanal → DIARIO. Con 2 posts/semana más programados y deriva de tema y
robots, el lunes dejaba hasta seis días de exposición. No añade ruido: solo
avisa si hay hallazgos Y han cambiado, así que un día limpio no manda nada.
8 tests, cada uno la cicatriz de un fallo real, incluidos los dos que hacen
creíble a un vigilante: que DETECTA la avería (probado con deriva simulada de
robots) y que un chequeo roto se oye.
Primera ejecución real: 8 hallazgos, todos preexistentes, 32 s.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+16
-6
@@ -45,6 +45,7 @@ from concurrent.futures import ThreadPoolExecutor
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
import seo_rules as R
|
||||
import seo_exceptions as X
|
||||
import seo_estado as E
|
||||
|
||||
# Los dos sitios. Ojo al canónico: EN es www, ES es el APEX — al revés.
|
||||
SITES = {
|
||||
@@ -416,7 +417,8 @@ def check_rules(posts, site):
|
||||
|
||||
# ---------- salida ----------
|
||||
|
||||
def build_report(roto, nocanon, prematuro, envejecido, n_sitemap, sm_malos, reglas, mayus, titulos):
|
||||
def build_report(roto, nocanon, prematuro, envejecido, n_sitemap, sm_malos, reglas, mayus,
|
||||
titulos, estado):
|
||||
L = []
|
||||
if roto:
|
||||
L.append(f"🔴 {len(roto)} enlace(s) interno(s) ROTO(S):")
|
||||
@@ -446,13 +448,16 @@ def build_report(roto, nocanon, prematuro, envejecido, n_sitemap, sm_malos, regl
|
||||
tot = sum(len(v) for v in reglas.values())
|
||||
L.append(f"📋 {tot} violación(es) accionable(s) del auditor:")
|
||||
L += [f" {k}: {len(v)}" for k, v in sorted(reglas.items(), key=lambda x: -len(x[1]))[:6]]
|
||||
L += E.informe(estado)
|
||||
if not L:
|
||||
L.append("✅ Sin hallazgos: enlaces, sitemap y reglas OK.")
|
||||
L.append("✅ Sin hallazgos: enlaces, sitemap, reglas y estado del sitio OK.")
|
||||
return "\n".join(L)
|
||||
|
||||
|
||||
def fingerprint(roto, nocanon, prematuro, envejecido, sm_malos, reglas, mayus, titulos):
|
||||
def fingerprint(roto, nocanon, prematuro, envejecido, sm_malos, reglas, mayus, titulos,
|
||||
estado):
|
||||
return json.dumps({
|
||||
"estado": {k: sorted(v) for k, v in estado.items()},
|
||||
"roto": sorted(f"{s}|{u}" for s, u in roto),
|
||||
"nocanon": sorted(f"{s}|{u}" for s, u in nocanon),
|
||||
"prematuro": sorted(f"{s}|{t}" for s, t, _, _ in prematuro),
|
||||
@@ -486,15 +491,20 @@ def run_site(site):
|
||||
reglas = check_rules(posts, site)
|
||||
mayus = check_heading_case(posts, site)
|
||||
titulos = check_title_case(posts, site)
|
||||
# Los chequeos de SITIO (tags, páginas, head, robots, hreflang, hub,
|
||||
# programados). Van aquí y no en un timer aparte porque comparten lo que
|
||||
# hace útil a este vigilante: una sola huella por sitio y un solo aviso.
|
||||
estado = E.revisa(site, posts)
|
||||
|
||||
body = build_report(roto, nocanon, prematuro, envejecido, n_sm, sm_malos, reglas, mayus,
|
||||
titulos)
|
||||
titulos, estado)
|
||||
pub = sum(1 for p in posts if p.get("status") == "published")
|
||||
header = (f"── [{site.upper()}] {cfg['host']}\n"
|
||||
f" {pub} publicados, {len(posts)} totales, sitemap {n_sm} URLs")
|
||||
fp = fingerprint(roto, nocanon, prematuro, envejecido, sm_malos, reglas, mayus, titulos)
|
||||
fp = fingerprint(roto, nocanon, prematuro, envejecido, sm_malos, reglas, mayus, titulos,
|
||||
estado)
|
||||
hallazgos = bool(roto or nocanon or prematuro or envejecido or sm_malos or mayus
|
||||
or titulos)
|
||||
or titulos or estado)
|
||||
return header + "\n" + body, fp, hallazgos
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user