Apunta los scripts de sync prod al Hetzner nuevo y suma trabajo pendiente

- sync_translations_to_prod.py / sync_audio_to_prod.py / sync_carta_from_prod.py:
  migran de FEA_PROD_HOST/PASS (CDMON, password) a FEA_PROD_SSH_HOST/PASS +
  FEA_PROD_DOCKER_CONTAINER (Hetzner/Coolify, auth por clave), con wrapping
  docker exec y el fix del bug de redirecciones (wc -c/cat) resuelto en el host
  en vez de dentro del contenedor.
- fea_translate_helper.php: subcomando clone_new para clonar en ID local nuevo
  cuando el ID de prod ya está ocupado localmente.
- translate_post.py: --dry-run.
- tts_produce.py: --allow-default-voice (voz Nico solo si se permite
  explícitamente para autores sin voz clonada) + fix voice_for_author.
- minimax_tts.py: parámetro speed en t2a/_synth_chunk.
- mirror-antiguo/deploy/nginx-mirror.conf: redirects de URLs históricas de
  catálogo y vista imprimible EFFA.
- Importaciones humanas de Pagola (carta 738) + scripts/manifest de la
  fase A Enrique, y release_issue181_prod.sh / cdmon-retire-fewp1.sh.
- .gitignore: excluye docs/backups/ (dumps SQL, mismo motivo que backups/*).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-05 07:09:29 -04:00
parent 159080f0c8
commit df7bf98ef1
21 changed files with 980 additions and 110 deletions
+14 -4
View File
@@ -271,7 +271,8 @@ def save_state(state: dict) -> None:
# ── Orquestación ─────────────────────────────────────────────────────────────
def process_post(post_id: int, langs: list[str], status: str, force: bool, state: dict) -> None:
def process_post(post_id: int, langs: list[str], status: str, force: bool, state: dict,
*, dry_run: bool = False) -> None:
src = read_post(post_id)
if src.get("lang") and src["lang"] != "es":
log(f"#{post_id} no es ES (lang={src['lang']}) — saltado")
@@ -286,8 +287,14 @@ def process_post(post_id: int, langs: list[str], status: str, force: bool, state
state["done"][key] = existing
continue
if existing and force:
if dry_run:
log(f" {lang}: PLAN regenerar traducción previa #{existing}")
continue
php_helper("unlink", str(post_id), lang)
log(f" {lang}: --force, eliminada traducción previa #{existing}")
if dry_run:
log(f" {lang}: PLAN traducir con {ENGINE} y crear como {status}")
continue
try:
t0 = time.time()
title = translate_text(src["title"], lang, is_title=True)
@@ -312,6 +319,7 @@ def main() -> int:
ap.add_argument("--langs", default="en,fr,it,pt", help="Idiomas destino separados por coma.")
ap.add_argument("--status", default="draft", choices=["draft", "publish"], help="Estado de la traducción.")
ap.add_argument("--force", action="store_true", help="Regenera aunque ya exista la traducción.")
ap.add_argument("--dry-run", action="store_true", help="Muestra el plan sin llamar al LLM ni escribir en WordPress/estado.")
args = ap.parse_args()
langs = [l.strip() for l in args.langs.split(",") if l.strip() in LANG_NAMES]
@@ -329,9 +337,11 @@ def main() -> int:
state = load_state()
for pid in ids:
process_post(pid, langs, args.status, args.force, state)
save_state(state)
log(f"FIN. {len(state['done'])} traducciones registradas, "
process_post(pid, langs, args.status, args.force, state, dry_run=args.dry_run)
if not args.dry_run:
save_state(state)
mode = "DRY-RUN" if args.dry_run else "FIN"
log(f"{mode}. {len(state['done'])} traducciones registradas, "
f"{len(state.get('errors', {}))} errores. Estado: {STATE_FILE}")
return 0