remate: modo corto para el post que solo ha perdido el alt
Sustituir la imagen destacada en Ghost BORRA feature_image_alt: el campo va atado a la imagen, no al post. El vigilante volvía a considerar candidato al post y, como no distinguía casos, iba a relanzar el remate COMPLETO para rellenar 191 caracteres: revisión editorial otra vez, el enlazador sobre un artículo que ya tenía sus cuatro enlaces, posible cambio de slug —que rompe la pareja de hreflang recién declarada— y otra sesión entera del plan, el mismo día en que la cuota ya se había agotado una vez. solo_falta_alt() distingue el caso y build_prompt() sirve un encargo de 2.248 caracteres en vez de 7.237, que además PROHÍBE tocar texto, slug, metas, excerpt, tags y enlaces: ahí cualquier cambio es una regresión, no una mejora. El modo va en el nombre del log y en la cabecera de Telegram, porque al revisar por qué cambió un post lo primero que quieres saber es cuál corrió. Escribe el alt por el MISMO camino que el modo largo (seo_finish prep → Read de la imagen → seo_finish apply), así que no hay una segunda forma de escribirlo que pueda derivar de la primera. Once tests, y los que importan son los del falso positivo: un post recién creado NO debe entrar por el corto (se quedaría sin remate y parecería hecho), ni uno con el remate interrumpido a medias (meta_title sin meta_description). Verificado por el camino real sobre un borrador canario en el estado exacto —imagen y metas puestas, sin alt—: eligió el modo alt, escribió 189/191 caracteres describiendo la imagen, y de doce campos comparados NO tocó ninguno; el cuerpo quedó literalmente idéntico. Canario borrado después. Y hub-en/hub-es con los dos artículos publicados hoy (Socorro 1964 y Cash-Landrum ES), que seo_estado cazó fuera del índice a los minutos de publicarse. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+32
-4
@@ -60,6 +60,8 @@ STATE = os.path.join(STATE_DIR, "state.json")
|
||||
LOCK = os.path.join(STATE_DIR, "lock")
|
||||
LOGS = os.path.join(STATE_DIR, "logs")
|
||||
PROMPT_TMPL = os.path.join(os.path.dirname(os.path.abspath(__file__)), "remate_prompt.md")
|
||||
# Encargo corto para el post que YA está rematado y solo ha perdido el alt.
|
||||
PROMPT_ALT = os.path.join(os.path.dirname(os.path.abspath(__file__)), "remate_alt_prompt.md")
|
||||
# Sobreescribible por env para poder ensayar el camino de fallo con un binario
|
||||
# que casca, sin tocar código (feedback_canario_camino_real: hay que VER fallar
|
||||
# la alerta antes de fiarse de ella).
|
||||
@@ -159,9 +161,27 @@ def release_lock():
|
||||
pass
|
||||
|
||||
|
||||
def solo_falta_alt(p):
|
||||
"""El post ya está rematado y solo ha perdido el texto alternativo.
|
||||
|
||||
Pasa cada vez que se SUSTITUYE la imagen destacada en Ghost: el campo
|
||||
`feature_image_alt` va atado a la imagen, no al post, y se borra. Sin
|
||||
distinguir este caso, el vigilante relanzaba el remate COMPLETO para
|
||||
rellenar un campo de 191 caracteres: rehacía la revisión editorial, volvía a
|
||||
pasar el enlazador sobre un artículo que ya tenía sus enlaces, podía
|
||||
recambiar el slug —rompiendo la pareja de hreflang recién declarada— y se
|
||||
comía otra sesión entera del plan. Visto el 2026-07-30 al regenerar la
|
||||
infografía de Socorro.
|
||||
"""
|
||||
return bool(p.get("feature_image")
|
||||
and p.get("meta_title") and p.get("meta_description")
|
||||
and not p.get("feature_image_alt"))
|
||||
|
||||
|
||||
def build_prompt(site, post):
|
||||
cfg = SITES[site]
|
||||
with open(PROMPT_TMPL) as f:
|
||||
plantilla = PROMPT_ALT if solo_falta_alt(post) else PROMPT_TMPL
|
||||
with open(plantilla) as f:
|
||||
tmpl = Template(f.read())
|
||||
return tmpl.substitute(
|
||||
site=site, site_name=cfg["site_name"], canonical=cfg["canonical"],
|
||||
@@ -189,7 +209,10 @@ def run_remate(site, post):
|
||||
"""Lanza claude con el checklist y devuelve (ok, texto_para_telegram, log)."""
|
||||
os.makedirs(LOGS, exist_ok=True)
|
||||
ts = datetime.datetime.utcnow().strftime("%Y%m%d-%H%M%S")
|
||||
log = os.path.join(LOGS, f"{ts}-{site}-{post['slug'][:40]}.log")
|
||||
# El modo va en el NOMBRE del log: al revisar por qué un post cambió, lo
|
||||
# primero que quieres saber es si corrió el checklist entero o solo el alt.
|
||||
modo = "alt" if solo_falta_alt(post) else "full"
|
||||
log = os.path.join(LOGS, f"{ts}-{site}-{modo}-{post['slug'][:40]}.log")
|
||||
prompt = build_prompt(site, post)
|
||||
|
||||
try:
|
||||
@@ -294,7 +317,8 @@ def main():
|
||||
"ts": datetime.datetime.utcnow().isoformat()}
|
||||
save_state(state)
|
||||
|
||||
print(f"[{site}] rematando {p['slug']} …")
|
||||
modo = "alt" if solo_falta_alt(p) else "completo"
|
||||
print(f"[{site}] rematando ({modo}) {p['slug']} …")
|
||||
ok, texto, log = run_remate(site, p)
|
||||
|
||||
if ok:
|
||||
@@ -306,7 +330,11 @@ def main():
|
||||
save_state(state)
|
||||
|
||||
if ok:
|
||||
msg = f"🪄 Remate automático [{site.upper()}] {p['slug']}\n\n{texto}"
|
||||
cabecera = ("🖼 Alt rehecho [%s] %s\n(la imagen se sustituyó y Ghost borró "
|
||||
"el alt; el resto del remate NO se ha tocado)"
|
||||
% (site.upper(), p["slug"]) if modo == "alt"
|
||||
else "🪄 Remate automático [%s] %s" % (site.upper(), p["slug"]))
|
||||
msg = f"{cabecera}\n\n{texto}"
|
||||
else:
|
||||
msg = (f"🚨 remate-watch: el remate de [{site.upper()}] «{p['slug']}» "
|
||||
f"FALLÓ: {texto}\n\nLog: {log}\n"
|
||||
|
||||
Reference in New Issue
Block a user