títulos del ES en mayúscula de oración: 91 campos y la regla que lo vigila

Los encabezados llevaban limpios desde el 21 de julio, pero la regla solo
miraba <h2>/<h3>/<h4>: 28 de los 31 títulos publicados del ES seguían en
Title Case inglés, con sus meta_title, og_title y twitter_title detrás. Es
justo lo que el lector español lee en Google.

seo_case.py reescribe, y no reutiliza el detector tal cual porque no vale
para esto: `_title_case_words` decide con una sola prueba —«¿la he visto en
minúscula alguna vez?»— y eso deja fuera los verbos que solo viven en
titulares («Ocultan», «Conmocionó», «Penetró») y las palabras de dos letras,
que ni entran en el vocabulario. La primera versión devolvía títulos a medio
arreglar y rompía «Talavera la Real» y «vuelo Iberia». Ahora se decide con
dos pruebas —nunca en minúscula Y en mayúscula a media frase— más una lista
de palabras gramaticales, y las frases propias se enmascaran conservando las
posiciones para reescribir por desplazamiento sobre el original.

Decisión de Jose: «Ejército», «Gobierno» y demás instituciones conservan la
mayúscula; «Archivos» baja salvo en «Los Archivos PURSUE», que se protege
por frase porque ahí se distingue por contexto, no por palabra suelta.

check_title_case cierra el agujero en seo_watch. Simulado el ciclo entero
antes de tocar nada: 78 hallazgos → 16 tras el pase automático → 0 con la
decisión aplicada. Aplicado y verificado releyendo Ghost campo a campo (91
de 91), con backup pristine por post en ~/link-batch-backup.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
ChemaVX
2026-07-29 07:42:37 +00:00
co-authored by Claude Opus 5
parent 9823255851
commit 1ac51267fe
3 changed files with 720 additions and 6 deletions
+335
View File
@@ -0,0 +1,335 @@
#!/usr/bin/env python3
"""Mayúscula de oración en TÍTULOS y METAS del blog ES.
Por qué existe. La regla de Title Case inglés ya estaba escrita y funcionando
desde el 2026-07-21 —aquella limpieza quitó 219 encabezados— pero
`check_heading_case` solo mira `<h2>/<h3>/<h4>`. El título del post y el
`meta_title` nunca entraron en su ámbito, y son justo lo que el lector español
lee en Google. Barrido del 2026-07-29 sobre los 31 publicados del ES:
títulos en Title Case inglés 28 de 31
meta_title 16 de 31
og_title desincronizados de su meta_title 3
No se reescribe con un modelo ni con reglas de mayúsculas propias: se
MINUSCULIZAN EXACTAMENTE las palabras que marca `_title_case_words`, que es la
misma función, el mismo vocabulario del corpus y el mismo umbral que ya decide
esto en los encabezados. El resto del texto no se toca ni un byte, y el slug
NUNCA se toca (cambiarlo obligaría a un redirect 301 y no hay motivo).
Lo que la máquina NO decide. Hay palabras que llevan mayúscula cuando nombran a
la institución («el Ejército del Aire») y minúscula cuando son comunes («el
ejército recuperó los restos»). El corpus las escribe de las dos formas, así
que el vocabulario las marca y la propuesta las bajaría sin saber cuál es cuál.
Esas se apartan en DUDOSAS para que las mire una persona.
Uso:
python3 seo_case.py # en seco: informe de propuestas
python3 seo_case.py --md propuestas.md # además, deja el diff en fichero
python3 seo_case.py --apply --si-de-verdad
"""
import argparse
import datetime
import json
import os
import re
import subprocess
import sys
import tempfile
import seo_rules as R
from seo_audit import fetch_posts
from seo_watch import CASE_KEEP, CASE_PHRASES, lowercase_vocab
CLI = "ghst-es"
BACKUP_DIR = os.path.expanduser("~/link-batch-backup")
# Nombres de institución: conservan la mayúscula («el Ejército recuperó los
# restos», «lo que el Gobierno acaba de confirmar»). Salieron del barrido como
# dudosas —el corpus las escribe de las dos formas— y las resolvió Jose el
# 2026-07-29: institución, mayúscula.
#
# «Archivos» se cayó de esta lista en esa misma decisión: en «222 archivos
# OVNI» o «162 archivos secretos» es un nombre común y va en minúscula. El
# único que es nombre propio, «Los Archivos PURSUE», se protege por frase en
# CASE_PHRASES, que es donde se distingue por contexto y no por palabra suelta.
INSTITUCIONES = {"Ejército", "Gobierno", "Estado", "Fuerzas", "Armada",
"Iglesia", "Congreso", "Capitolio", "Pentágono"}
# Sin resolver: palabras que el barrido marque como ambiguas en el futuro. Un
# campo que contenga una de estas NO se aplica a medias, va a revisión humana.
DUDOSAS = set()
CAMPOS = ("title", "meta_title", "og_title", "twitter_title")
# Palabras gramaticales: van SIEMPRE en minúscula a media frase. Hacen falta
# explícitamente porque las dos pruebas del corpus no las alcanzan —
# `lowercase_vocab` solo recoge palabras de 3 letras o más, así que «el», «la»,
# «lo», «un» o «se» jamás entran, y en el cuerpo del blog aparecen capitalizadas
# a media frase lo bastante como para pasar por nombre propio. El resultado era
# «Betty y Barney Hill 1961: El primer caso…», mitad arreglado.
# Los artículos que SON parte de un topónimo («Los Ángeles», «Los Villares»,
# «El Yunque») no se ven afectados: van en CASE_PHRASES y se enmascaran antes.
FUNCION = {
"el", "la", "lo", "los", "las", "un", "una", "unos", "unas", "al", "del",
"de", "en", "y", "e", "o", "u", "a", "se", "no", "ni", "si", "su", "sus",
"que", "con", "por", "para", "sin", "sobre", "tras", "ante", "entre",
"es", "son", "fue", "ya", "más", "muy", "como", "cuando", "donde",
"dos", "tres", "sus", "le", "les", "me", "te", "nos",
}
_PAL = re.compile(r"[0-9A-Za-zÁÉÍÓÚÜÑáéíóúüñ][0-9A-Za-zÁÉÍÓÚÜÑáéíóúüñ'\-\.]*")
def nombres_propios(posts, minimo=1):
"""Palabras que el corpus escribe EN MAYÚSCULA A MEDIA FRASE.
Es la prueba que le faltaba a `lowercase_vocab`, y la razón está medida.
`lowercase_vocab` responde «¿la he visto alguna vez en minúscula?», y para
DETECTAR sobra: si nunca la has visto en minúscula, callas y no molestas.
Para REESCRIBIR no sirve, porque hay verbos vivos que solo existen en los
titulares —«Ocultan», «Conmocionó», «Penetró», «Desvelan», «Se Toparon»—
y el cuerpo del blog no los usa nunca en minúscula. La primera versión de
esta herramienta los dejaba en mayúscula y devolvía títulos a medio
arreglar, que es peor que no tocarlos.
Así que la decisión se toma con las DOS pruebas: una palabra conserva la
mayúscula solo si el corpus nunca la escribe en minúscula Y además la
escribe en mayúscula a media frase. «Roswell» pasa las dos; «Ocultan» no
pasa la segunda y baja.
"""
c = {}
for p in posts:
txt = re.sub(r"<[^>]+>", " ", p.get("html") or "")
txt = re.sub(r"\s+", " ", txt)
primera = True
for m in _PAL.finditer(txt):
tok = m.group(0)
antes = txt[:m.start()]
abre = primera or bool(re.search(r"[.?!¿¡:;»\"]\s*$", antes))
primera = False
if not abre and tok[:1].isupper() and tok[1:].islower():
c[tok] = c.get(tok, 0) + 1
return {w for w, n in c.items() if n >= minimo}
def _enmascara(texto):
"""Sustituye las frases propias por relleno de la MISMA longitud.
Tiene que conservar las posiciones: la reescritura se hace por desplazamiento
sobre el texto original, y `re.sub(frase, ' ')` —lo que hace el detector—
acorta la cadena y desalinea todo. Sin esto, «La Batalla de Los Ángeles»
perdería la mayúscula de «Los», que es parte del topónimo.
"""
for ph in CASE_PHRASES:
texto = re.sub(re.escape(ph), lambda m: "\0" * len(m.group(0)),
texto, flags=re.IGNORECASE)
return texto
def propone(texto, vocab, propios):
"""(nuevo, bajadas, dudosas). Solo minusculiza; nunca reordena ni recorta."""
if not texto:
return texto, [], []
mascara = _enmascara(texto)
chars = list(texto)
bajadas, dudosas = [], []
for m in _PAL.finditer(mascara):
tok = m.group(0)
# «Primera palabra» se mide sobre el ORIGINAL, no sobre la máscara. Si
# el título abre con una frase propia («Immaculate Constellation: El
# programa…»), la máscara la deja en ceros y la palabra siguiente
# parecía la primera del texto, así que se salvaba: quedaban «El
# programa UAP secreto» y «Archivo secreto OVNI» sin tocar.
antes = texto[:m.start()]
# En español, tras dos puntos NO va mayúscula: a diferencia del
# detector, aquí los dos puntos sí abren minúscula.
abre = (not re.search(r"[0-9A-Za-zÁÉÍÓÚÜÑáéíóúüñ]", antes)
or bool(re.search(r"[.?!]\s*$", antes)))
if abre or "\0" in tok:
continue
if not (tok[:1].isupper() and tok[1:].islower()):
continue # acrónimos (OVNI, EE.UU.) y ya-minúsculas
if tok in CASE_KEEP or tok in INSTITUCIONES:
continue
if tok.lower() not in FUNCION and tok.lower() not in vocab and tok in propios:
continue # nombre propio probado por las dos pruebas
if tok in DUDOSAS:
dudosas.append(tok)
continue
chars[m.start()] = tok[:1].lower()
bajadas.append(tok)
return "".join(chars), bajadas, dudosas
def revisa(posts):
"""Propuestas por post publicado.
Lo PROGRAMADO se deja fuera a propósito: sale del remate con las metas ya
puestas y aún se puede editar sin prisa; meterlo aquí mezcla una limpieza
de lo publicado con la cola de agosto.
"""
vocab = lowercase_vocab(posts)
propios = nombres_propios(posts)
fuera = []
for p in posts:
if p.get("status") != "published":
continue
cambios, revisar = {}, {}
for campo in CAMPOS:
actual = p.get(campo) or ""
nuevo, _, dud = propone(actual, vocab, propios)
if nuevo == actual:
continue
if dud:
# Un campo con una palabra dudosa NO se aplica a medias: dejaría
# «162 Archivos secretos», que está peor que como estaba. Va
# entero a revisión humana, con la propuesta parcial a la vista.
revisar[campo] = (nuevo, sorted(set(dud)))
else:
cambios[campo] = nuevo
# El og/twitter que hoy espeja al meta tiene que seguir espejándolo:
# si no, la limpieza deja el título social viejo en Title Case.
meta_nuevo = cambios.get("meta_title")
if meta_nuevo:
for campo in ("og_title", "twitter_title"):
if (p.get(campo) or "").strip() == (p.get("meta_title") or "").strip():
cambios[campo] = meta_nuevo
revisar.pop(campo, None)
if cambios or revisar:
fuera.append((p, cambios, revisar))
return fuera
def comprueba_limites(cambios):
"""Los límites duros de Ghost/SERP. Minusculizar no cambia la longitud,
así que esto solo puede saltar si el campo YA venía largo: se avisa y no
se cuenta como culpa de esta herramienta."""
avisos = []
mt = cambios.get("meta_title")
if mt and len(mt) > R.META_TITLE_MAX:
avisos.append(f"meta_title {len(mt)} car. > {R.META_TITLE_MAX} (ya venía largo)")
return avisos
def informe(fuera, md=None):
L = []
tocados = [f for f in fuera if f[1]]
n_campos = sum(len(c) for _, c, _ in fuera)
n_rev = sum(len(r) for _, _, r in fuera)
L.append(f"{len(tocados)} post(s) con títulos/metas en Title Case inglés "
f"{n_campos} campo(s) automáticos, {n_rev} a decisión humana\n")
for p, cambios, revisar in fuera:
L.append(f"── {p['slug']}")
for campo, nuevo in cambios.items():
L.append(f" {campo}")
L.append(f" antes: {p.get(campo)}")
L.append(f" ahora: {nuevo}")
for a in comprueba_limites(cambios):
L.append(f"{a}")
for campo, (nuevo, dud) in revisar.items():
L.append(f" {campo} ⚠ DECIDE UNA PERSONA ({', '.join(dud)}: "
f"¿institución o nombre común?)")
L.append(f" antes: {p.get(campo)}")
L.append(f" borr.: {nuevo}")
L.append("")
texto = "\n".join(L)
print(texto)
if md:
hoy = datetime.date.today().isoformat()
with open(md, "w") as fh:
fh.write(
"# Títulos y metas del ES en mayúscula de oración\n\n"
f"Propuesta de `seo_case.py`, generada EN SECO el {hoy}.\n\n"
"Solo se minusculizan las palabras que el propio corpus escribe en\n"
"minúscula en otros sitios (mismo vocabulario que usa la regla de\n"
"encabezados). El resto del texto y el slug no se tocan.\n\n"
"```\n" + texto + "\n```\n")
print(f"[propuestas escritas en {md}]")
return n_campos
def aplica(p, cambios):
"""Escribe en Ghost por el mismo camino que seo_finish: backup pristine,
`ghst-es post update --from-json` y re-lectura para verificar."""
os.makedirs(BACKUP_DIR, exist_ok=True)
ts = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
bpath = os.path.join(BACKUP_DIR, f"es-{p['slug'][:40]}-seocase-pristine-{ts}.json")
with open(bpath, "w") as fh:
json.dump({"id": p["id"], "slug": p["slug"],
**{k: p.get(k) for k in cambios}}, fh, ensure_ascii=False, indent=1)
fd, path = tempfile.mkstemp(suffix=".json", prefix="seo_case_")
os.close(fd)
try:
with open(path, "w") as fh:
json.dump(cambios, fh, ensure_ascii=False)
r = subprocess.run(f'{CLI} post update {p["id"]} --from-json "{path}"',
shell=True, capture_output=True, text=True, timeout=240)
finally:
os.unlink(path)
if "Slug:" not in r.stdout:
print(r.stdout, r.stderr)
return False, os.path.basename(bpath)
return True, os.path.basename(bpath)
def main():
ap = argparse.ArgumentParser(
description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
ap.add_argument("--json", help="posts guardados, en vez de pedirlos a Ghost")
ap.add_argument("--md", help="deja el diff completo en este fichero")
ap.add_argument("--apply", action="store_true")
ap.add_argument("--si-de-verdad", action="store_true",
help="obligatorio junto a --apply: son títulos publicados")
args = ap.parse_args()
if args.json:
data = json.load(open(args.json))
posts = data["posts"] if isinstance(data, dict) and "posts" in data else data
else:
posts = fetch_posts(CLI)
fuera = revisa(posts)
informe(fuera, args.md)
if not args.apply:
print("(en seco: no se ha tocado nada — repite con --apply --si-de-verdad)")
return 0
if not args.si_de_verdad:
print("✗ --apply necesita --si-de-verdad.")
return 1
ok = fallos = 0
esperado = {}
for p, cambios, _ in fuera:
if not cambios:
continue
bien, backup = aplica(p, cambios)
print(f" {'' if bien else ''} {p['slug']} (backup {backup})")
ok += bien
fallos += not bien
if bien:
esperado[p["slug"]] = cambios
print(f"\n{ok} post(s) actualizados, {fallos} fallo(s)")
# Verificación contra la realidad, no contra el código de salida: se
# re-lee Ghost y se compara campo a campo. Un «Slug:» en la salida del CLI
# solo prueba que la llamada volvió, no que el texto esté guardado.
print("\nreleyendo de Ghost para verificar…")
ahora = {q["slug"]: q for q in fetch_posts(CLI)}
malos = [(s, c) for s, camb in esperado.items() for c, v in camb.items()
if (ahora.get(s, {}).get(c) or "") != v]
if malos:
print(f"{len(malos)} campo(s) NO quedaron como se pidió:")
for s, c in malos[:8]:
print(f" {s[:36]} · {c}")
return 1
print(f"✓ verificado: {sum(len(c) for c in esperado.values())} campos "
f"guardados tal cual")
return 1 if fallos else 0
if __name__ == "__main__":
sys.exit(main())
+44 -6
View File
@@ -262,6 +262,9 @@ CASE_PHRASES = [
"Maria Blair", "Kenneth Arnold", "Memorándum Halt", "Moment of Contact",
"Los Ángeles", "Los Villares", "Puerto Rico", "El Yunque",
"Francisco Julio Padrón", # «Julio» aquí es nombre de pila, no el mes
"Talavera la Real", # el pueblo de Badajoz: «Real» es del topónimo
"Archivos PURSUE", # nombre propio de la desclasificación; el resto
# de «archivos» del corpus son nombre común
]
CASE_KEEP = {
"Pentágono", "Roswell", "Rendlesham", "Varginha", "Manises", "Nimitz",
@@ -277,7 +280,7 @@ CASE_KEEP = {
"Lake", "Huron", "Villares", "Yunque", "Ángeles", "Memorándum", "Moment",
"Contact", "Dr.", "Sr.", "San", "Santa", "Francisco", "David", "Kurt",
"Travis", "Donald", "Theresa", "Jesse", "Walter", "Maria", "Marco", "Eli",
"Kenneth", "Ártico", "Julio",
"Kenneth", "Ártico", "Julio", "Iberia", # la aerolínea del caso Manises
}
_CASE_WORD = re.compile(r"[0-9A-Za-zÁÉÍÓÚÜÑáéíóúüñ][0-9A-Za-zÁÉÍÓÚÜÑáéíóúüñ'\-\.]*")
_CASE_ABBR = re.compile(r"(?:^|\s)(?:[A-ZÁÉÍÓÚÑ]{1,3}|Dr|Sr|Sra|vs|etc)\.\s*$")
@@ -370,6 +373,34 @@ def check_heading_case(posts, site):
return out
def check_title_case(posts, site):
"""Lo mismo, pero en el TÍTULO y el meta_title — que es lo que se lee en Google.
El agujero se vio el 2026-07-29: los encabezados llevaban limpios desde el
21 de julio y, sin embargo, 28 de los 31 títulos publicados del ES seguían
en Title Case inglés, con 16 meta_title detrás. La regla estaba escrita y
funcionando; simplemente nunca había mirado ahí.
Umbral de 2, igual que en los encabezados y por el mismo motivo. Se miran
también `og_title` y `twitter_title` porque son los que viajan a X y a
Facebook, y suelen ser copia del meta: si se limpia uno y no los otros, el
título viejo sigue circulando por ahí.
"""
if site != "es":
return []
vocab = lowercase_vocab(posts)
out = []
for p in posts:
if p.get("status") != "published":
continue
for campo in ("title", "meta_title", "og_title", "twitter_title"):
texto = (p.get(campo) or "").strip()
malas = _title_case_words(texto, vocab)
if len(malas) >= 2:
out.append((p["slug"], f"{campo}: {texto[:44]}", malas[:4]))
return out
def check_rules(posts, site):
"""Violaciones accionables (excluye waivers e INFO), agrupadas por regla."""
out = {}
@@ -385,7 +416,7 @@ def check_rules(posts, site):
# ---------- salida ----------
def build_report(roto, nocanon, prematuro, envejecido, n_sitemap, sm_malos, reglas, mayus):
def build_report(roto, nocanon, prematuro, envejecido, n_sitemap, sm_malos, reglas, mayus, titulos):
L = []
if roto:
L.append(f"🔴 {len(roto)} enlace(s) interno(s) ROTO(S):")
@@ -408,6 +439,9 @@ def build_report(roto, nocanon, prematuro, envejecido, n_sitemap, sm_malos, regl
if mayus:
L.append(f"🔤 {len(mayus)} encabezado(s) en Title Case inglés (el ES va en mayúscula de oración):")
L += [f" {s[:24]}: «{t[:34]}» ({', '.join(w)})" for s, t, w in mayus[:6]]
if titulos:
L.append(f"🔠 {len(titulos)} título/meta en Title Case inglés (es lo que se lee en Google):")
L += [f" {s[:20]}: {t}" for s, t, _ in titulos[:6]]
if reglas:
tot = sum(len(v) for v in reglas.values())
L.append(f"📋 {tot} violación(es) accionable(s) del auditor:")
@@ -417,7 +451,7 @@ def build_report(roto, nocanon, prematuro, envejecido, n_sitemap, sm_malos, regl
return "\n".join(L)
def fingerprint(roto, nocanon, prematuro, envejecido, sm_malos, reglas, mayus):
def fingerprint(roto, nocanon, prematuro, envejecido, sm_malos, reglas, mayus, titulos):
return json.dumps({
"roto": sorted(f"{s}|{u}" for s, u in roto),
"nocanon": sorted(f"{s}|{u}" for s, u in nocanon),
@@ -425,6 +459,7 @@ def fingerprint(roto, nocanon, prematuro, envejecido, sm_malos, reglas, mayus):
"envejecido": sorted(f"{s}|{t}|{m}" for s, _, t, m in envejecido),
"sitemap": sorted(u for u, _ in sm_malos),
"mayus": sorted(f"{s}|{t}" for s, t, _ in mayus),
"titulos": sorted(f"{s}|{t}" for s, t, _ in titulos),
"reglas": {k: sorted(v) for k, v in reglas.items()},
}, sort_keys=True)
@@ -450,13 +485,16 @@ def run_site(site):
n_sm, sm_malos = check_sitemap()
reglas = check_rules(posts, site)
mayus = check_heading_case(posts, site)
titulos = check_title_case(posts, site)
body = build_report(roto, nocanon, prematuro, envejecido, n_sm, sm_malos, reglas, mayus)
body = build_report(roto, nocanon, prematuro, envejecido, n_sm, sm_malos, reglas, mayus,
titulos)
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)
hallazgos = bool(roto or nocanon or prematuro or envejecido or sm_malos or mayus)
fp = fingerprint(roto, nocanon, prematuro, envejecido, sm_malos, reglas, mayus, titulos)
hallazgos = bool(roto or nocanon or prematuro or envejecido or sm_malos or mayus
or titulos)
return header + "\n" + body, fp, hallazgos
+341
View File
@@ -0,0 +1,341 @@
# Títulos y metas del ES en mayúscula de oración
Propuesta de `seo_case.py`, generada EN SECO el 2026-07-29.
Solo se minusculizan las palabras que el propio corpus escribe en
minúscula en otros sitios (mismo vocabulario que usa la regla de
encabezados). El resto del texto y el slug no se tocan.
```
25 post(s) con títulos/metas en Title Case inglés → 72 campo(s) automáticos, 19 a decisión humana
── trans-en-provence-1981-ovni-gepan
title
antes: La Noche del Misterio en Trans-en-Provence: El Caso OVNI Mejor Documentado de la Historia
ahora: La noche del misterio en Trans-en-Provence: el caso OVNI mejor documentado de la historia
── chiles-whitted-1948-piloto-eastern-airlines-ovni
title
antes: El Encuentro de Chiles-Whitted: Cuando Dos Pilotos Presenciaron lo Inexplicable a 5.000 Pies de Altura
ahora: El encuentro de Chiles-Whitted: cuando dos pilotos presenciaron lo inexplicable a 5.000 pies de altura
og_title
antes: Chiles-Whitted 1948: Pilotos ante lo Inexplicable
ahora: Chiles-Whitted 1948: pilotos ante lo inexplicable
── batalla-los-angeles-1942-ovni-antiaereo
title
antes: La Batalla de Los Ángeles 1942: Pánico Antiaéreo Bajo Reflectores y Misterio Sin Resolver
ahora: La batalla de Los Ángeles 1942: pánico antiaéreo bajo reflectores y misterio sin resolver
og_title
antes: La Batalla de Los Ángeles 1942: Pánico y Misterio
ahora: La batalla de Los Ángeles 1942: pánico y misterio
── vaticano-extraterrestres-iglesia-catolica
title
antes: El Vaticano y los Extraterrestres: Una Iglesia Abierta al Misterio del Cosmos
ahora: El Vaticano y los extraterrestres: una Iglesia abierta al misterio del cosmos
og_title
antes: El Vaticano y los Extraterrestres
ahora: El Vaticano y los extraterrestres
── pascagoula-1973-abduccion-hickson-parker
title
antes: Pascagoula 1973: La Noche que Cambió a Dos Pescadores para Siempre
ahora: Pascagoula 1973: la noche que cambió a dos pescadores para siempre
meta_title
antes: Pascagoula 1973: La Abducción Alien Mejor Documentada
ahora: Pascagoula 1973: la abducción alien mejor documentada
og_title
antes: Pascagoula 1973: La Abducción Alien Mejor Documentada
ahora: Pascagoula 1973: la abducción alien mejor documentada
twitter_title
antes: Pascagoula 1973: La Abducción Alien Mejor Documentada
ahora: Pascagoula 1973: la abducción alien mejor documentada
── los-villares-1996-el-encuentro-extraterrestre-que-marco-la-ufologia-espanola
title
antes: Los Villares 1996: El Encuentro Extraterrestre que Marcó la Ufología Española
ahora: Los Villares 1996: el encuentro extraterrestre que marcó la ufología española
meta_title
antes: Los Villares 1996: El Encuentro OVNI que Marcó España
ahora: Los Villares 1996: el encuentro OVNI que marcó España
og_title
antes: Los Villares 1996: El Encuentro OVNI que Marcó España
ahora: Los Villares 1996: el encuentro OVNI que marcó España
twitter_title
antes: Los Villares 1996: El Encuentro OVNI que Marcó España
ahora: Los Villares 1996: el encuentro OVNI que marcó España
── gary-mckinnon-el-hacker-que-vio-algo-en-los-servidores-de-la-nasa
title
antes: Gary McKinnon: El Hacker que Vio Algo en los Servidores de la NASA
ahora: Gary McKinnon: el hacker que vio algo en los servidores de la NASA
meta_title
antes: Gary McKinnon: El Hacker que Penetró el Pentágono
ahora: Gary McKinnon: el hacker que penetró el Pentágono
og_title
antes: Gary McKinnon: El Hacker que Penetró el Pentágono
ahora: Gary McKinnon: el hacker que penetró el Pentágono
twitter_title
antes: Gary McKinnon: El Hacker que Penetró el Pentágono
ahora: Gary McKinnon: el hacker que penetró el Pentágono
── gimbal-gofast-uap-uss-theodore-roosevelt-2015
title
antes: Gimbal y GoFast: Lo que los Vídeos del USS Theodore Roosevelt Siguen sin Explicar
ahora: Gimbal y GoFast: lo que los vídeos del USS Theodore Roosevelt siguen sin explicar
── grusch-capitolio-9-junio-2026-divulgacion-uap
title
antes: Grusch en el Capitolio: El Día que la Divulgación UAP Pasó del Testimonio a la Exigencia
ahora: Grusch en el Capitolio: el día que la divulgación UAP pasó del testimonio a la exigencia
── aaro-vs-grusch-pentagono-testimonio-contradiccion
title
antes: AARO vs. Grusch: Cuando el Pentágono y un Testigo bajo Juramento se Contradicen
ahora: AARO vs. Grusch: cuando el Pentágono y un testigo bajo juramento se contradicen
── archivos-pursue-uap-bases-nucleares-desclasificado
title ⚠ DECIDE UNA PERSONA (Archivos, Gobierno: ¿institución o nombre común?)
antes: Los Archivos PURSUE y las Bases Nucleares: Lo que el Gobierno Acaba de Confirmar
borr.: Los Archivos PURSUE y las bases nucleares: lo que el Gobierno acaba de confirmar
── 222-archivos-ovni-desclasificados-pentagono-2026
title ⚠ DECIDE UNA PERSONA (Archivos: ¿institución o nombre común?)
antes: 222 Archivos OVNI Desclasificados: Lo que Revelan, lo que Ocultan y lo que Sigue sin Respuesta
borr.: 222 Archivos OVNI desclasificados: lo que revelan, lo que ocultan y lo que sigue sin respuesta
meta_title ⚠ DECIDE UNA PERSONA (Archivos: ¿institución o nombre común?)
antes: 222 Archivos OVNI: Lo que Revelan y lo que Ocultan
borr.: 222 Archivos OVNI: lo que revelan y lo que ocultan
og_title ⚠ DECIDE UNA PERSONA (Archivos: ¿institución o nombre común?)
antes: 222 Archivos OVNI: Lo que Revelan y lo que Ocultan
borr.: 222 Archivos OVNI: lo que revelan y lo que ocultan
twitter_title ⚠ DECIDE UNA PERSONA (Archivos: ¿institución o nombre común?)
antes: 222 Archivos OVNI: Lo que Revelan y lo que Ocultan
borr.: 222 Archivos OVNI: lo que revelan y lo que ocultan
── kecksburg-1965-objeto-ejercito-pennsylvania
meta_title
antes: Kecksburg 1965: El Roswell de Pennsylvania
ahora: Kecksburg 1965: el Roswell de Pennsylvania
og_title
antes: Kecksburg 1965: El Roswell de Pennsylvania
ahora: Kecksburg 1965: el Roswell de Pennsylvania
twitter_title
antes: Kecksburg 1965: El Roswell de Pennsylvania
ahora: Kecksburg 1965: el Roswell de Pennsylvania
title ⚠ DECIDE UNA PERSONA (Ejército: ¿institución o nombre común?)
antes: Kecksburg 1965: El Objeto que el Ejército Recuperó y Nadie Explicó
borr.: Kecksburg 1965: el objeto que el Ejército recuperó y nadie explicó
── immaculate-constellation-programa-secreto-pentagon-ovni
title
antes: Immaculate Constellation: El Programa Secreto del Pentágono que Archiva Todos los OVNIs
ahora: Immaculate Constellation: el programa secreto del Pentágono que archiva todos los OVNIs
meta_title
antes: Immaculate Constellation: Archivo Secreto OVNI del Pentágono
ahora: Immaculate Constellation: archivo secreto OVNI del Pentágono
og_title
antes: Immaculate Constellation: Archivo Secreto OVNI del Pentágono
ahora: Immaculate Constellation: archivo secreto OVNI del Pentágono
twitter_title
antes: Immaculate Constellation: Archivo Secreto OVNI del Pentágono
ahora: Immaculate Constellation: archivo secreto OVNI del Pentágono
── cefaa-chile-unidad-oficial-ovni
title
antes: CEFAA: Chile, el Único País del Mundo con una Unidad Oficial Investigando OVNIs
ahora: CEFAA: Chile, el único país del mundo con una unidad oficial investigando OVNIs
meta_title
antes: CEFAA: Chile y su Unidad Oficial de Investigación OVNI
ahora: CEFAA: Chile y su unidad oficial de investigación OVNI
og_title
antes: CEFAA: Chile y su Unidad Oficial de Investigación OVNI
ahora: CEFAA: Chile y su unidad oficial de investigación OVNI
twitter_title
antes: CEFAA: Chile y su Unidad Oficial de Investigación OVNI
ahora: CEFAA: Chile y su unidad oficial de investigación OVNI
── segunda-desclasificacion-ovni-mayo-2026
title
antes: Segunda Desclasificación OVNI Mayo 2026: Apolo 12, Bases Nucleares y 51 Vídeos Inéditos
ahora: Segunda desclasificación OVNI mayo 2026: Apolo 12, bases nucleares y 51 vídeos inéditos
meta_title
antes: Segunda Desclasificación OVNI: Apolo 12 y Bases Nucleares
ahora: Segunda desclasificación OVNI: Apolo 12 y bases nucleares
og_title
antes: Segunda Desclasificación OVNI: Apolo 12 y Bases Nucleares
ahora: Segunda desclasificación OVNI: Apolo 12 y bases nucleares
twitter_title
antes: Segunda Desclasificación OVNI: Apolo 12 y Bases Nucleares
ahora: Segunda desclasificación OVNI: Apolo 12 y bases nucleares
── travis-walton-1975-abduccion-ovni-arizona
title
antes: Fuego en el Cielo: La Abducción de Travis Walton que Conmocionó a Arizona en 1975
ahora: Fuego en el cielo: la abducción de Travis Walton que conmocionó a Arizona en 1975
meta_title
antes: Travis Walton 1975: La Abducción con Seis Testigos
ahora: Travis Walton 1975: la abducción con seis testigos
og_title
antes: Travis Walton 1975: La Abducción con Seis Testigos
ahora: Travis Walton 1975: la abducción con seis testigos
twitter_title
antes: Travis Walton 1975: La Abducción con Seis Testigos
ahora: Travis Walton 1975: la abducción con seis testigos
── betty-y-barney-hill-1961-el-primer-caso-de-abduccion-extraterrestre-documentado
title
antes: Betty y Barney Hill 1961: El Primer Caso de Abducción Extraterrestre Documentado
ahora: Betty y Barney Hill 1961: el primer caso de abducción extraterrestre documentado
meta_title
antes: Betty y Barney Hill 1961: La Primera Abducción Documentada
ahora: Betty y Barney Hill 1961: la primera abducción documentada
og_title
antes: Betty y Barney Hill 1961: La Primera Abducción Documentada
ahora: Betty y Barney Hill 1961: la primera abducción documentada
twitter_title
antes: Betty y Barney Hill 1961: La Primera Abducción Documentada
ahora: Betty y Barney Hill 1961: la primera abducción documentada
── las-revelaciones-del-pentagono-sobre-uap-un-hito-historico-en-la-busqueda-de-la-verdad-sobre-fenomenos-anomalos-no-identificados
title
antes: Las Revelaciones del Pentágono sobre UAP: Un Hito Histórico en la Búsqueda de la Verdad sobre Fenómenos Anómalos No Identificados
ahora: Las revelaciones del Pentágono sobre UAP: un hito histórico en la búsqueda de la verdad sobre fenómenos anómalos no identificados
meta_title
antes: Immaculate Constellation: El Programa UAP Secreto
ahora: Immaculate Constellation: el programa UAP secreto
og_title
antes: Immaculate Constellation: El Programa UAP Secreto
ahora: Immaculate Constellation: el programa UAP secreto
twitter_title
antes: Immaculate Constellation: El Programa UAP Secreto
ahora: Immaculate Constellation: el programa UAP secreto
── phoenix-lights-1997-la-noche-cuando-arizona-vio-lo-inexplicable
title
antes: Phoenix Lights 1997: La Noche Cuando Arizona Vio lo Inexplicable
ahora: Phoenix Lights 1997: la noche cuando Arizona vio lo inexplicable
meta_title
antes: Phoenix Lights 1997: Dos Eventos que Arizona No Olvidó
ahora: Phoenix Lights 1997: dos eventos que Arizona no olvidó
og_title
antes: Phoenix Lights 1997: Dos Eventos que Arizona No Olvidó
ahora: Phoenix Lights 1997: dos eventos que Arizona no olvidó
twitter_title
antes: Phoenix Lights 1997: Dos Eventos que Arizona No Olvidó
ahora: Phoenix Lights 1997: dos eventos que Arizona no olvidó
── varginha-1996-el-caso-ovni-mas-intrigante-de-brasil
title
antes: Varginha 1996: El Caso OVNI Más Intrigante de Brasil
ahora: Varginha 1996: el caso OVNI más intrigante de Brasil
meta_title ⚠ DECIDE UNA PERSONA (Ejército: ¿institución o nombre común?)
antes: Caso Varginha 1996: El OVNI que el Ejército Silenció
borr.: Caso Varginha 1996: el OVNI que el Ejército silenció
og_title ⚠ DECIDE UNA PERSONA (Ejército: ¿institución o nombre común?)
antes: Caso Varginha 1996: El OVNI que el Ejército Silenció
borr.: Caso Varginha 1996: el OVNI que el Ejército silenció
twitter_title ⚠ DECIDE UNA PERSONA (Ejército: ¿institución o nombre común?)
antes: Caso Varginha 1996: El OVNI que el Ejército Silenció
borr.: Caso Varginha 1996: el OVNI que el Ejército silenció
── el-incidente-nimitz-2004-cuando-pilotos-de-caza-f-18-se-toparon-con-lo-inexplicable
title
antes: El Incidente Nimitz 2004: Cuando Pilotos de Caza F-18 Se Toparon con lo Inexplicable
ahora: El incidente Nimitz 2004: cuando pilotos de caza F-18 se toparon con lo inexplicable
meta_title
antes: Nimitz 2004: El Vídeo UAP que el Pentágono Confirmó
ahora: Nimitz 2004: el vídeo UAP que el Pentágono confirmó
og_title
antes: Nimitz 2004: El Vídeo UAP que el Pentágono Confirmó
ahora: Nimitz 2004: el vídeo UAP que el Pentágono confirmó
twitter_title
antes: Nimitz 2004: El Vídeo UAP que el Pentágono Confirmó
ahora: Nimitz 2004: el vídeo UAP que el Pentágono confirmó
── eeuu-desclasifica-162-archivos-secretos-sobre-ovnis-y-vida-extraterrestre-el-pentagono-abre-sus-bovedas
title ⚠ DECIDE UNA PERSONA (Archivos: ¿institución o nombre común?)
antes: EEUU Desclasifica 162 Archivos Secretos sobre Ovnis y Vida Extraterrestre: El Pentágono Abre sus Bóvedas
borr.: EEUU desclasifica 162 Archivos secretos sobre ovnis y vida extraterrestre: el Pentágono abre sus bóvedas
meta_title ⚠ DECIDE UNA PERSONA (Archivos: ¿institución o nombre común?)
antes: 162 Archivos OVNI Desclasificados: Lo Que Dice el Pentágono
borr.: 162 Archivos OVNI desclasificados: lo que dice el Pentágono
og_title ⚠ DECIDE UNA PERSONA (Archivos: ¿institución o nombre común?)
antes: 162 Archivos OVNI Desclasificados: Lo Que Dice el Pentágono
borr.: 162 Archivos OVNI desclasificados: lo que dice el Pentágono
twitter_title ⚠ DECIDE UNA PERSONA (Archivos: ¿institución o nombre común?)
antes: 162 Archivos OVNI Desclasificados: Lo Que Dice el Pentágono
borr.: 162 Archivos OVNI desclasificados: lo que dice el Pentágono
── roswell-1947-el-misterio-que-cambio-la-historia-de-la-ufologia
title
antes: Roswell 1947: El Misterio Que Cambió la Historia de la Ufología
ahora: Roswell 1947: el misterio que cambió la historia de la ufología
meta_title ⚠ DECIDE UNA PERSONA (Ejército: ¿institución o nombre común?)
antes: Roswell 1947: El Misterio que el Ejército Nunca Explicó
borr.: Roswell 1947: el misterio que el Ejército nunca explicó
og_title ⚠ DECIDE UNA PERSONA (Ejército: ¿institución o nombre común?)
antes: Roswell 1947: El Misterio que el Ejército Nunca Explicó
borr.: Roswell 1947: el misterio que el Ejército nunca explicó
twitter_title ⚠ DECIDE UNA PERSONA (Ejército: ¿institución o nombre común?)
antes: Roswell 1947: El Misterio que el Ejército Nunca Explicó
borr.: Roswell 1947: el misterio que el Ejército nunca explicó
── la-explosion-de-luz-de-canarias-1976-testigos-militares-espanoles-desvelan-un-secreto-de-la-guerra-fria
title
antes: La Explosión de Luz de Canarias 1976: Testigos Militares Españoles Desvelan un Secreto de la Guerra Fría
ahora: La explosión de luz de Canarias 1976: testigos militares españoles desvelan un secreto de la Guerra Fría
meta_title ⚠ DECIDE UNA PERSONA (Ejército: ¿institución o nombre común?)
antes: Canarias 1976: El OVNI que Vio el Ejército Español
borr.: Canarias 1976: el OVNI que vio el Ejército español
og_title ⚠ DECIDE UNA PERSONA (Ejército: ¿institución o nombre común?)
antes: Canarias 1976: El OVNI que Vio el Ejército Español
borr.: Canarias 1976: el OVNI que vio el Ejército español
twitter_title ⚠ DECIDE UNA PERSONA (Ejército: ¿institución o nombre común?)
antes: Canarias 1976: El OVNI que Vio el Ejército Español
borr.: Canarias 1976: el OVNI que vio el Ejército español
── el-testimonio-de-david-grusch-el-denunciante-que-cambio-la-conversacion-sobre-ovnis-en-el-congreso-de-ee-uu
title
antes: El Testimonio de David Grusch: El Denunciante que Cambió la Conversación sobre OVNIs en el Congreso de EE.UU.
ahora: El testimonio de David Grusch: el denunciante que cambió la conversación sobre OVNIs en el Congreso de EE.UU.
meta_title
antes: David Grusch en el Congreso: Lo Que los Medios No Contaron
ahora: David Grusch en el Congreso: lo que los medios no contaron
og_title
antes: David Grusch en el Congreso: Lo Que los Medios No Contaron
ahora: David Grusch en el Congreso: lo que los medios no contaron
twitter_title
antes: David Grusch en el Congreso: Lo Que los Medios No Contaron
ahora: David Grusch en el Congreso: lo que los medios no contaron
── la-noche-que-el-cielo-se-nego-a-revelar-sus-secretos-el-caso-manises-de-1979
title
antes: La Noche que el Cielo se Negó a Revelar sus Secretos: El Caso Manises de 1979
ahora: La noche que el cielo se negó a revelar sus secretos: el caso Manises de 1979
meta_title
antes: Manises 1979: El OVNI que Obligó a Aterrizar un Vuelo Iberia
ahora: Manises 1979: el OVNI que obligó a aterrizar un vuelo Iberia
og_title
antes: Manises 1979: El OVNI que Obligó a Aterrizar un Vuelo Iberia
ahora: Manises 1979: el OVNI que obligó a aterrizar un vuelo Iberia
twitter_title
antes: Manises 1979: El OVNI que Obligó a Aterrizar un Vuelo Iberia
ahora: Manises 1979: el OVNI que obligó a aterrizar un vuelo Iberia
── el-incidente-ovni-de-rendlesham-forest-1980-el-caso-mas-documentado-de-la-historia-militar-britanica
title
antes: El Incidente OVNI de Rendlesham Forest 1980: El Caso Más Documentado de la Historia Militar Británica
ahora: El incidente OVNI de Rendlesham Forest 1980: el caso más documentado de la historia militar británica
meta_title
antes: Rendlesham Forest 1980: El OVNI que Vieron los Militares
ahora: Rendlesham Forest 1980: el OVNI que vieron los militares
og_title
antes: Rendlesham Forest 1980: El OVNI que Vieron los Militares
ahora: Rendlesham Forest 1980: el OVNI que vieron los militares
twitter_title
antes: Rendlesham Forest 1980: El OVNI que Vieron los Militares
ahora: Rendlesham Forest 1980: el OVNI que vieron los militares
```