cuando termines y verifica que esté todo subido

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-15 01:33:35 +02:00
parent e1e9bb7499
commit a90b7443e4
20 changed files with 1855 additions and 2 deletions
@@ -0,0 +1,90 @@
---
name: save_onlyoffice_file
kind: function
lang: bash
domain: shell
purity: impure
version: 1.1.0
description: "Fuerza el guardado (Ctrl+S) de un documento abierto en una instancia de OnlyOffice Desktop en Linux/X11 y confirma que llego a disco por cambio de mtime. Primer paso del flujo seguro guardar -> actualizar -> recargar; evita perder cambios no guardados cuando un build regenera el archivo leyendo del disco."
signature: "save_onlyoffice_file(file_path: string, [instance: string]) -> json"
error_type: error_go_core
tags: [onlyoffice, desktop, x11, gui, save, persist]
uses_functions: []
uses_types: []
file_path: bash/functions/shell/save_onlyoffice_file.sh
params:
- name: file_path
desc: "ruta al documento abierto en OnlyOffice cuyo guardado se quiere forzar. Debe existir. Se normaliza a ruta absoluta y se usa su basename para localizar la ventana."
- name: instance
desc: "nombre del slot/instancia para etiquetar la salida JSON (default: 'demo'). Usar el MISMO valor que en open/reload/close del mismo documento por coherencia."
output: "linea JSON a stdout: {\"instance\":\"<i>\",\"file\":\"<abs>\",\"wid\":\"<hex>|null\",\"status\":\"saved\"|\"no_change\"|\"no_window\",\"dialog_confirmed\":0|1[,\"mtime_before\":N,\"mtime_after\":N]}. dialog_confirmed=1 si se envio Return para cerrar el dialogo modal de formato. Exit 0 salvo error de dependencia o archivo inexistente (exit 1)."
---
Fuerza el guardado (Ctrl+S) de un documento abierto en una instancia de ONLYOFFICE
Desktop Editors en Linux/X11 y confirma que el guardado llegó a disco observando el
cambio de `mtime` del archivo.
Existe para cerrar una ventana de pérdida de datos: OnlyOffice mantiene los cambios
en memoria hasta que el usuario guarda. Cualquier proceso que regenere el archivo
leyendo del disco (un build que refresca hojas, un script de sincronización)
perdería el trabajo manual no guardado. Esta función vuelca ese trabajo a disco
ANTES de tocar el archivo, de modo que el paso de actualización pueda preservarlo.
Es el primer paso del flujo seguro de refresco:
```
save_onlyoffice_file -> (actualizar el archivo en disco) -> reload_onlyoffice_file
```
## Ejemplo
```bash
# Forzar el guardado de un xlsx abierto en la instancia "afiliados"
bash bash/functions/shell/save_onlyoffice_file.sh \
/home/enmanuel/afiliados/programas_afiliados.xlsx afiliados
# {"instance":"afiliados","file":"/home/enmanuel/afiliados/programas_afiliados.xlsx","wid":"0x0a20002a","status":"saved","mtime_before":1718380000,"mtime_after":1718380042}
# Via fn run (tras fn index)
./fn run save_onlyoffice_file /home/enmanuel/afiliados/programas_afiliados.xlsx afiliados
# Encadenado con la actualización y la recarga (flujo seguro completo)
bash bash/functions/shell/save_onlyoffice_file.sh "$XLSX" afiliados
python build_xlsx.py # regenera solo las hojas gestionadas
./fn run reload_onlyoffice_file "$XLSX" afiliados
```
## Cuando usarla
Llámala SIEMPRE justo antes de regenerar o modificar en disco un archivo que el
usuario pueda tener abierto en OnlyOffice, para no pisar sus cambios sin guardar.
Es el primer eslabón del flujo guardar -> actualizar -> recargar. Si no hay ventana
abierta para ese archivo, es un no-op seguro (status `no_window`).
## Gotchas
- **Orden crítico**: guarda ANTES de actualizar el archivo. Si actualizas primero y
guardas OnlyOffice después, OnlyOffice sobrescribe tu actualización con su copia
en memoria (vieja). El flujo correcto es save -> update -> reload.
- **status `no_change`**: el `mtime` no cambió. Normalmente significa que no había
cambios pendientes (no es un error).
- **Auto-confirmación del diálogo de formato (v1.1.0)**: si tras Ctrl+S el guardado no
se completa en ~1.2s, la función asume que OnlyOffice mostró un diálogo modal
("mantener formato") y le envía Return, que acepta la opción por defecto (mantener el
formato actual). El campo `dialog_confirmed` indica si se envió. Si no había diálogo,
el Return va al editor y solo mueve de celda (no altera datos). Para suprimir el
diálogo de forma permanente, desmárcalo en OnlyOffice: Configuración avanzada →
desactivar el aviso de formato al guardar.
- **status `no_window`**: no hay ninguna ventana cuyo título contenga el basename del
archivo. No hay nada que guardar; el disco ya es la única fuente de verdad.
- **Detección por basename**: dos archivos con el mismo nombre en rutas distintas
colisionan al localizar la ventana (igual que open/reload).
- **X11 obligatorio**: depende de `xdotool` (y `stat` de coreutils). No funciona en
Wayland puro sin XWayland.
- **Foco**: la función activa la ventana (`windowactivate --sync`) para que Ctrl+S
llegue al editor. Roba el foco un instante; es esperable.
## Capability growth log
- v1.1.0 (2026-06-15) — auto-confirma el diálogo modal "mantener formato" enviando
Return a la ventana activa cuando el guardado no se completa en ~1.2s; añade el campo
`dialog_confirmed` a la salida JSON.
@@ -0,0 +1,107 @@
#!/usr/bin/env bash
# save_onlyoffice_file — fuerza el guardado (Ctrl+S) de un documento abierto en una
# instancia de ONLYOFFICE Desktop Editors en Linux/X11 y confirma que el archivo se
# escribio a disco observando el cambio de mtime.
#
# Para que existe: OnlyOffice mantiene los cambios en memoria hasta que el usuario
# guarda. Cualquier proceso que regenere el .xlsx leyendo del disco (por ejemplo un
# build que refresca hojas) perderia el trabajo manual no guardado. Esta funcion
# vuelca ese trabajo a disco ANTES de tocar el archivo, de modo que el paso de
# actualizacion pueda preservarlo. Es el primer paso del flujo seguro:
# save_onlyoffice_file -> (actualizar el archivo) -> reload_onlyoffice_file
#
# La ventana se localiza por el basename del archivo (OnlyOffice titula la ventana
# "<basename> — ONLYOFFICE"), igual que open_onlyoffice_file. Si no hay ventana
# abierta para ese basename no hay nada que guardar: se devuelve status "no_window"
# con exit 0 (el disco ya es la unica fuente de verdad).
#
# Funcion impura: envia eventos de teclado a X11 (xdotool) y lee el estado del
# sistema de archivos. Imprime una linea JSON con el resultado a stdout.
#
# No usamos `set -e`: los pipelines de busqueda de ventanas (xdotool|head) pueden no
# matchear y no deben abortar el script. Mantenemos -u y pipefail con guardas.
set -uo pipefail
save_onlyoffice_file() {
local file_path="${1:-}"
local instance="${2:-demo}"
# --- 1. Validacion de dependencias del sistema ---
local dep
for dep in xdotool stat; do
if ! command -v "$dep" >/dev/null 2>&1; then
echo "error: dependencia ausente: '$dep' (instala xdotool, coreutils)" >&2
return 1
fi
done
# --- 2. Validacion de argumentos ---
if [ -z "$file_path" ]; then
echo "error: uso: save_onlyoffice_file <file_path> [instance]" >&2
return 1
fi
if [ ! -f "$file_path" ]; then
echo "error: el archivo no existe: '$file_path'" >&2
return 1
fi
local abs_path
abs_path="$(cd "$(dirname "$file_path")" && pwd)/$(basename "$file_path")"
local base
base="$(basename "$abs_path")"
# --- 3. Localizar la ventana de OnlyOffice por basename ---
local wid=""
wid="$(xdotool search --name "$base" 2>/dev/null | head -1 || true)"
if [ -z "$wid" ]; then
printf '{"instance":"%s","file":"%s","wid":null,"status":"no_window"}\n' \
"$instance" "$abs_path"
return 0
fi
local hex
hex="$(printf '0x%08x' "$wid" 2>/dev/null || echo "$wid")"
# --- 4. mtime antes de guardar ---
local mtime_before
mtime_before="$(stat -c %Y "$abs_path" 2>/dev/null || echo 0)"
# --- 5. Enfocar la ventana y enviar Ctrl+S ---
xdotool windowactivate --sync "$wid" >/dev/null 2>&1 || true
xdotool key --clearmodifiers --window "$wid" ctrl+s >/dev/null 2>&1 || true
# --- 6. Esperar el guardado; auto-confirmar el dialogo de formato si aparece ---
# OnlyOffice puede mostrar un dialogo modal ("mantener formato") al guardar. Si el
# mtime no cambia en ~1.2s asumimos que hay un modal esperando y le enviamos Return:
# acepta la opcion por defecto, que es mantener el formato actual del archivo. Si no
# habia dialogo, el Return va al editor y solo mueve de celda (inofensivo: no altera
# datos). El intento se repite mientras el guardado no se confirme.
local mtime_after="$mtime_before" i=0 confirmed=0
local max=27 # ~8s a 0.3s por iteracion
until [ "$mtime_after" -gt "$mtime_before" ] || [ "$i" -ge "$max" ]; do
read -r -t 0.3 _ </dev/null 2>/dev/null || true
mtime_after="$(stat -c %Y "$abs_path" 2>/dev/null || echo "$mtime_before")"
i=$((i + 1))
# A partir de ~1.2s sin guardar, confirmar el dialogo modal con Return.
if [ "$i" -ge 4 ] && [ "$mtime_after" -le "$mtime_before" ]; then
local dlg
dlg="$(xdotool getactivewindow 2>/dev/null || true)"
if [ -n "$dlg" ]; then
xdotool key --clearmodifiers --window "$dlg" Return >/dev/null 2>&1 || true
confirmed=1
fi
fi
done
local status="saved"
if [ "$mtime_after" -le "$mtime_before" ]; then
# Sin cambio de mtime: no habia nada pendiente que guardar.
status="no_change"
fi
printf '{"instance":"%s","file":"%s","wid":"%s","status":"%s","dialog_confirmed":%s,"mtime_before":%s,"mtime_after":%s}\n' \
"$instance" "$abs_path" "$hex" "$status" "$confirmed" "$mtime_before" "$mtime_after"
return 0
}
# Ejecutable directo: `bash save_onlyoffice_file.sh <file> [instance]`.
if [ "${BASH_SOURCE[0]}" = "${0}" ]; then
save_onlyoffice_file "$@"
fi