Skip to the content.

Próximos pasos para setlist-admin-usb

Read in English

Lee esto primero si vas a retomar este proyecto — seas Claude, cualquier otro agente de IA, un colaborador humano, o yo mismo en el futuro. Está escrito para entenderse en frío, sin necesitar el historial de la conversación que lo produjo.

Dónde están las cosas (al commit 9209a29, 2026-09-27 — revisa el

log de git por si hay algo más nuevo, este archivo se queda un poco atrás de los commits por naturaleza)

El renombramiento de terminología Show/Set/Bank (ver la entrada “Renamed the library’s terminology…” de CHANGELOG.md) y una validación completa en hardware real de setlist-admin-usb ya están comiteados, subidos a main, y confirmados como persistentes en el Pi real (sobrevivieron un reinicio real después de desplegarse correctamente — ver “Desplegar al Pi” más abajo sobre por qué eso no es automático). setlist-admin-wifi recibió los mismos cambios de código por paridad, pero no se ha probado en hardware propio (esta instantánea es del 2026-09-27, antes de que se encontrara un dongle WiFi funcional — ver las entradas “Dónde están las cosas” más abajo y su propio SPECIFICATION.md para el estado actual).

Los bugs reales encontrados y corregidos durante esta ronda están detallados en la sección “Sin publicar” de docs/es/CHANGELOG.md y en SPECIFICATION.md secciones 0/14 (en inglés) — no los vuelvas a descubrir de cero:

Se agregó Export Set: una vista a pantalla completa, en letra grande, con el repertorio del Set seleccionado, con un botón “Share” que la convierte en PNG y la entrega al menú nativo de compartir del teléfono. Ver SPECIFICATION.md sección 14 (en inglés) para el diseño.

Corregido y confirmado por el usuario (2026-09-27, después de lo anterior): seleccionar un Set en el desplegable nunca marcaba realmente ese Set como el activo para el pedal — solo crear un Set nuevo lo hacía, vía set_active_set. Cambiar entre Sets que ya existían no hacía nada para la reproducción, sin importar cuántas veces seleccionaras uno distinto. Corregido: “Reboot now to apply” ahora llama a POST /api/sets/active con el Set que esté seleccionado justo antes de reiniciar. No trasladado a setlist-admin-wifi — esa expansión no tiene botón propio de reinicio (su USAGE.md ya le dice al usuario que haga ssh pedal sudo reboot a mano), así que la corrección equivalente ahí necesitaría su propia decisión de diseño (ej. marcar como activo de inmediato al seleccionar en el desplegable, ya que no hay un momento de “aplicar” del cual colgarse) — todavía no diseñada, ni mucho menos construida.

También documentado (2026-09-27, sin cambio de código): una respuesta consolidada a “¿está la Pi protegida ante un corte de luz, y cuáles son las ventanas de riesgo reales?” en TROUBLESHOOTING.md/ docs/es/TROUBLESHOOTING.md (sección nueva cerca del inicio) y systemd/README.md sección 4. Versión corta: segura durante el uso normal en un show (root/boot/USB están respaldados en RAM o en solo-lectura en ese momento); las ventanas reales son el remontaje rw breve de /media/usb durante una edición de biblioteca, el overlay desactivado para desarrollo, y la edición a mano de cmdline.txt — las últimas dos solo de mantenimiento. Se señala una fuente de poder insuficiente (no un desenchufe limpio) como el disparador más probable en la práctica, según un evento de under-voltage observado directamente por este proyecto.

2026-10-01, incidente real investigado y corregido (cambio de código, todavía no desplegado a la Pi al momento de escribir esto — revisa VERSION/el log de git para saber si ya llegó ahí): el usuario reportó que el audio se detuvo por completo tras aproximadamente una hora de pruebas intensivas en hardware real. Investigando en vivo por SSH, resultó que el M-VAVE estaba apagado, y pedal-core.service se sorprendió a mitad de un bucle de crasheos (el contador de reinicios subiendo cada ~10s) — main.py trataba “controlador MIDI no encontrado” como fatal, saliendo y dependiendo por completo de que systemd reiniciara el proceso entero a ciegas (tumbando y volviendo a levantar los dos procesos mpv en cada ciclo — parpadeo visible, silencio total, cero explicación en pantalla) mientras el controlador siguiera ausente. Corregido: ver la entrada de CHANGELOG.md en “Sin publicar” y la nueva sección de TROUBLESHOOTING.md “El audio (y el video) se detienen…” para el detalle completo: main.py ahora reintenta la conexión MIDI dentro del mismo proceso sin tumbar mpv. (Una segunda corrección llegó en la misma tanda — registrar el propio stderr de mpv en vez de descartarlo — pero se revirtió el mismo día: ese log vive en el overlay de raíz respaldado en RAM sin rotación, así que dejarlo encendido permanentemente se consideró que no valía el riesgo constante de agotar la RAM por un beneficio que solo sirve durante una sesión activa de debugging. Captúralo temporalmente, a mano, la próxima vez que de verdad haga falta — no vuelvas a desplegar ese parche de forma permanente. Ver la entrada v2026.10.01 de CHANGELOG.md para el razonamiento completo.) También agregado, misma sesión, a pedido explícito del usuario: gestión del video de standby en las dos apps setlist-admin (elegir cualquier video de la biblioteca como el nuevo standby.mp4 — USAGE.md, library_ops.set_standby_video()). Desplegado y verificado en la Pi real (2026-10-01, commit 886d4d0, cortado como v2026.10.01): se completó el procedimiento completo de desactivar overlay/desplegar/probar/reactivar/reiniciar; los 324 tests (27+139+158) pasaron en la Pi misma; pedal-core.service reinició limpio con el M-VAVE conectado y quedó confirmado persistente después del reinicio final; get_standby() confirmó leer correctamente el standby.mp4 real del USB montado. No confirmado de forma independiente: tocar realmente “Set as standby” desde un teléfono y ver visualmente el nuevo video en loop — eso habría sobrescrito el standby.mp4 real y actualmente en uso de la banda (754MB), así que no se hizo en vivo sin que el usuario estuviera presente para confirmar el resultado y restaurar si hiciera falta. Haz esto primero, igual que el precedente de Export Set más abajo.

2026-10-01, más tarde el mismo día — botón “Optimize” a nivel de biblioteca, construido, desplegado y probado en hardware real (commits e1fafe6, 8f187fb): pedido real del usuario, justo después de la conversión manual del video de standby de arriba — en vez de re-codificar un video marcado a mano por SSH cada vez, la biblioteca de canciones ahora muestra un botón “Optimize” junto a cualquier archivo que codec_check.is_optimized() marque, respaldado por un nuevo daemon siempre activo, library-optimizer.service, y una cola de trabajos persistente basada en archivos (optimize_queue.py) para que un trabajo largo sobreviva a que el teléfono que lo encoló se desconecte. Ver la entrada “Sin publicar” de CHANGELOG.md y la sección 15 de SPECIFICATION.md (en inglés) para el diseño completo. Trasladado a las dos expansiones, 181 (USB) + 200 (WiFi) pruebas pasando.

Desplegado en la Pi real la misma sesión, ciclo completo de overlay (ver “Notas operativas” abajo — raspi-config nonint do_overlayfs 1 por sí solo no quitó el sufijo personalizado :recurse=0 de cmdline.txt, hizo falta un sed manual encima, igual que al desactivarlo; vale la pena recordarlo la próxima vez en vez de redescubrirlo). Se encontró y corrigió un bug real en el proceso: library-optimizer.service entraba en bucle de fallos bajo systemd (ModuleNotFoundError: No module named 'admin') porque library_optimizer.py nunca agregaba src/ al sys.path como sí hace server.py — invisible para la suite de pruebas, que ya pone src/ en el path ella misma. Corregido, redesplegado, confirmado corriendo de verdad (no solo “active” en medio de un bucle de fallos).

Después se hizo una prueba real de punta a punta por SSH: un clip HEVC sintético pequeño (ffmpeg -f lavfi testsrc, 3s) subido vía AdminAPI.upload_song(), encolado con request_song_optimization(), recogido por el daemon en pocos segundos, confirmado re-codificado a H.264 y la cola limpia (needs_optimization: False después). pedal-core.service y la reproducción de standby.mp4 no se vieron afectados en ningún momento. La canción de prueba se borró después. Todavía sin confirmar: el flujo real desde la app del celular — tocar “Optimize” desde la app misma, ver el botón decir “Optimizing…”, y en particular desconectar/reconectar el celular a mitad del trabajo para confirmar que el estado de verdad persiste visualmente, no solo a nivel de AdminAPI/archivo de cola como ya se probó arriba. Haz esto a continuación — el usuario está a punto de hacerlo justo al momento de escribir esto.

2026-10-01, más tarde el mismo día — un incidente real de pérdida de datos en standby.mp4, encontrado y recuperado: el usuario reportó que el video real de standby de la banda (~55 minutos, ~754MB) había desaparecido — reemplazado esa misma tarde desde el propio selector “Set as standby” de la app, sin forma de volver atrás. Causa raíz, confirmada en el código: library_ops.set_standby_video() simplemente copia la canción elegida directo sobre standby.mp4, sin preguntar nada. Funciona bien hasta que alguien elige el video equivocado, que es exactamente lo que pasó: el standby real de ~55 minutos de la banda desapareció, irrecuperable desde la propia Pi, y solo volvió porque por suerte todavía existía una copia en otro lado. Al usuario se le preguntó en el momento si quería agregar un respaldo automático y dijo que sí a recuperar el video perdido específicamente, pero la función de respaldo en sí nunca se construyó en la sesión que siguió — retómalo explícitamente en vez de asumir que ya está hecho. Una forma razonable: antes de copiar, mover el standby.mp4 actual a algo como backup/standby-previous.mp4 (un solo cupo, que se sobrescribe cada vez, no un historial que crece sin límite — este USB tiene espacio limitado y la meta no es un historial completo, solo “no perder el de antes de este”). Usa el mismo mecanismo de _atomic_copy_file()/ writable_usb() ya usado en otros lados, así que es un cambio pequeño y autocontenido una vez que alguien decida la forma exacta del nombre/ retención del respaldo. Ver “Decisiones de producto pendientes” abajo.

Se recuperó solo porque el usuario todavía tenía el archivo fuente original, sin convertir (un video real de la banda, 6.68GB, H.264 1080p pero a ~15.7Mbps — demasiado pesado para usar directo), en otro computador. Se re-codificó a ~1.8Mbps (coincide casi exacto con el bitrate del original perdido: 754MB en 55.66 minutos son ~1.8Mbps) usando h264_v4l2m2m tanto para decodificar como para codificar — confirmado en vivo a ~0.87x en tiempo real, muchísimo más rápido que el ~0.1x que este proyecto vio antes con decodificación HEVC solo por software, ya que la fuente aquí ya era H.264 y el códec por hardware de esta Pi lo maneja nativo en ambos sentidos. El resultado (~800MB) se subió a la biblioteca con un nombre nuevo (AdminAPI.upload_song() simple, deliberadamente no set_standby_video() — el usuario quería decidir él mismo si y cuándo volver a hacerlo standby, no que se forzara). Después lo hizo exactamente así, desde una sesión web temporal (ver abajo) y reinició para aplicarlo — confirmado por el propio socket IPC de mpv que standby.mp4 ya es ese archivo exacto. set_standby_video() sigue sin ningún paso de respaldo — ver “Decisiones de producto pendientes” abajo.

La misma investigación también mostró un detalle a recordar sobre transferencias: se conectó un dongle WiFi USB nuevo mientras un archivo de 6.68GB se transfería por scp a la Pi, y el usuario pidió apagar el Ethernet en cuanto fuera seguro — confirmado que la resolución mDNS/pedal.local se vuelve inestable con dos interfaces activas a la vez (eth0+wlan0) — la misma causa raíz que la versión ya documentada de este problema con eth0+eth1, solo que ahora con una interfaz WiFi en vez de otra cableada/tethered. Usar la IP directa del WiFi con -i ~/.ssh/id_ed25519_pedal -o IdentitiesOnly=yes lo resolvió cada vez.

También encontrado en vivo, vale la pena recordarlo tal cual: pkill -f '<patrón>' corrido por SSH puede matar su propia sesión de SSH si el texto del patrón que pasas aparece en la línea de comando del propio shell que lo invoca (y va a aparecer, de forma trivial, ya que acabas de escribir exactamente ese string como argumento) — pkill -f compara contra la línea de comando completa, no solo la del proceso que buscas. Mató el wrapper de bash remoto en vez de ffmpeg, y se vio como un simple exit 255 de SSH sin ningún error del lado remoto. Arreglo: compara por nombre exacto de proceso (pkill -TERM ffmpeg, sin -f), o haz que el patrón sea lo bastante específico para que no pueda coincidir también con su propia invocación.

También en esta misma investigación: un arreglo de código que solo se había “desplegado rápido” (traído mientras el overlay protector estaba activo, así que solo vivió en la capa superior respaldada en RAM) se perdió en silencio cuando el overlay se desactivó y reinició después por una razón no relacionada (necesitar espacio real en disco para la conversión de 6.68GB de arriba) — el checkout de git volvió al último commit que de verdad se había desplegado de forma durable. Se volvió a traer en cuanto se notó (confirma con git log --oneline -1 después de cualquier reinicio, cualquiera, no solo los de tu propio trabajo de despliegue — no asumas que un commit desplegado rápido sobrevivió solo porque nada de lo que tú hiciste debía reiniciar la Pi).

2026-10-02: Optimize/Cancel confirmado en vivo, un día completo de

bugs de hardware real, y un selector de idioma

El flujo real en la app del celular para el botón “Optimize” (punto 1 de abajo, tal como estaba al final del 2026-10-01) ya quedó confirmado a fondo — no solo una vez, sino a lo largo de una sesión extendida de uso real: se tocó “Optimize” desde el celular varias veces, se vio la ventana de advertencia, se vio el botón verde permanente “Optimizando” junto a “Cancel”, se tocó “Cancel” mismo (incluyendo mientras ffmpeg estaba codificando activamente, confirmado con cronometraje directo vía AdminAPI: la llamada devolvió en ~0.02s y el proceso real de ffmpeg desapareció en ~10s), y se confirmó que la canción vuelve a un estado limpio, sin optimizar, listo para un nuevo toque de “Optimize”. La persistencia al desconectar/reconectar que pedía este punto es inherente al diseño (la cola es basada en archivos, el demonio es una unidad systemd separada) y se puso a prueba de forma incidental durante todas las pruebas del día sin problema.

Se encontró y corrigió una cadena larga de bugs reales el mismo día, cada uno a partir de un reporte real del usuario mientras usaba la app activamente, no de una revisión de código — el detalle completo, una entrada por corrección, está en la sección [Sin publicar] de CHANGELOG.md (más reciente primero): un bug de centrado de formularios solo visible en tablet; una demora de ~37s entre iniciar sesión y que aparecieran las canciones (llamadas a ffprobe seriales, sin caché); un timeout transitorio de ffprobe bajo carga que se guardaba permanentemente como “no optimizado”; ffmpeg manteniendo abierta la fuente montada en el USB durante toda la codificación, bloqueando cualquier otra escritura de la biblioteca (por ejemplo, “crear Bank” sin relación) todo ese tiempo; un aviso obsoleto quedado dentro de la imagen para compartir de Export Set específicamente (el de pantalla ya se había quitado); falta de Cache-Control en archivos estáticos, dejando que una combinación vieja de app.js/index.html mostrara una página que parecía atascada mostrando solo la barra superior; el mismo hueco para GET /api/songs en particular, mostrando una canción como ya optimizada mientras un trabajo seguía corriendo de verdad; y, encontrado al final, cancelar un trabajo podía arrojar error 500 si se tocaba dentro de aproximadamente el primer minuto (mientras el nuevo paso de copia a scratch todavía leía la fuente del USB) — arreglado moviendo el marcador de cancelación fuera del USB por completo, a almacenamiento local de la Pi (optimize_queue.DEFAULT_STATE_DIR), ya que nunca necesitó vivir ahí en primer lugar. Confirmado en vivo con una llamada directa y cronometrada a AdminAPI.cancel_song_optimization() contra un trabajo real, activamente codificando.

También se agregó, el mismo día, por pedido explícito del usuario: un selector de idioma ES/EN completo (static/i18n.js, ambas expansiones) — un menú desplegable en la barra superior (que ahora dice “ChocolatePi - Setlist Admin”) cambia cada etiqueta, botón, confirmación, alerta y notificación entre inglés y español, guardado por navegador vía localStorage. Los nombres de canciones, pistas, Sets y Banks explícitamente nunca se traducen (son datos del usuario, no texto de interfaz) — cada función de renderizado en app.js deja esos valores fuera de las llamadas de traducción a propósito. Los mensajes de error que vienen del servidor siguen en inglés por ahora, un trabajo aparte, explícitamente diferido si alguna vez se quiere. Se agregó también una actualización automática (GET /api/songs cada 5s mientras algo esté en cola o corriendo, deteniéndose sola si no) para que el estado del botón Cancelar/Optimizar nunca necesite un refresco manual para ponerse al día.

Todavía sin hacer, vale la pena pronto: USAGE.md/docs/es/USAGE.md (ambas expansiones) y CHANGELOG.md/docs/es/CHANGELOG.md se actualizaron con todo lo anterior al momento de escribir esto — pero VERSION no se actualizó (no se “cortó” ninguna versión esta sesión; todo lo anterior sigue en [Sin publicar]). Si se quiere un punto de control limpio, ese es el siguiente paso pequeño: confirmar que nada se regresionó, subir el VERSION de ambas expansiones, renombrar [Sin publicar] a la fecha de hoy.

2026-10-02, más tarde el mismo día — un segundo incidente real, una ronda completa de pruebas de usuario, y una limpieza de espacio en disco. Iniciar un segundo trabajo de “Optimize” mientras la fuente de otro todavía se copiaba desde el USB también podía arrojar error 500, por la misma razón de fondo que el arreglo de cancelar de arriba (setlist-admin.service y library-optimizer.service son dos procesos separados — el candado dentro de un solo proceso en usb_mount.py nunca coordinaba entre ellos). Arreglado con un candado real entre procesos; ver la entrada “Sin publicar” de CHANGELOG.md.

El usuario luego corrió una ronda completa de pruebas manuales contra cada función tocada ese día, reportando uno por uno. Hallazgos reales, todos corregidos la misma sesión (detallados, uno por ítem, en CHANGELOG.md): un ciclo de caídas de pedal-core.service bajo carga pesada de CPU (confirmado en vivo — el pedal mostró la pantalla de inactividad de mpv durante un par de minutos); list_sets() listando carpetas reservadas del sistema de archivos como Sets seleccionables; “En cola” y “Optimizando” mostrándose idénticos (confirmado en vivo: esto causó cancelar el trabajo equivocado por error); una subida a la biblioteca lenta sin verificación temprana de nombre duplicado; errores de red crudos sin traducir; falta de retroalimentación táctil en los botones de login/standby; y numeración manual de Banks (ahora automática y secuencial, pedido real del usuario: un controlador MIDI recorre los Banks uno a la vez, así que nunca hay una razón real para elegir algo distinto al siguiente número — sin tope según la cantidad física de Banks de ningún controlador en particular).

Dos cosas que surgieron durante esta ronda no son bugs de código, confirmado investigando el comportamiento real en vez de suponer:

Ese hallazgo de poco espacio llevó a una limpieza real: los dos archivos de video más grandes sin optimizar en la biblioteca (varios GB combinados) fueron eliminados por el usuario desde la propia app, liberando aproximadamente 2.7GB (de ~925MB a ~3.7GB libres, confirmado con df antes/después). Un puñado más pequeño de archivos .wav con nombres genéricos raros (nombrados como “track N” en vez de un título de canción real, ~180MB combinados) se marcó como posible dato de prueba sobrante pero se dejó intacto a propósito, pendiente de que el usuario mismo confirme — no borrarlos sin que él lo pida.

Nota operativa, vale la pena recordar: usb-tether-watchdog.service hace cumplir activamente “setlist- admin.service solo corre mientras haya un teléfono conectado por USB” — vuelve a detener la app admin a los pocos segundos de cualquier systemctl start manual, confirmado en vivo vía su propia salida de journalctl (llamadas repetidas a systemctl stop setlist-admin. service, con pocos segundos de diferencia). Desde el 2026-10-03, esta ya no es la única forma de entrar — el watchdog también arranca la app admin automáticamente cuando el WiFi de la Pi está conectado a su propia red de casa de confianza, sin necesitar ningún truco manual (ver la entrada justo abajo, y “Reaching the app over WiFi” en USAGE.md). El truco de abajo ahora sirve principalmente para llegar a la app admin por alguna otra red en la que el watchdog no confía (uso de desarrollo/SSH): sudo systemctl stop usb-tether-watchdog.service primero, luego sudo systemctl start setlist-admin.service — y recordar hacer sudo systemctl start usb-tether-watchdog.service de nuevo después para restaurar el comportamiento automático normal, o la app admin se quedará corriendo (y alcanzable) indefinidamente en vez de seguir la convención de cable/WiFi de la que depende el resto de este proyecto.

2026-10-03: carril de video consciente de recursos (pedal base), y

la expansión de WiFi formalmente diferida al roadmap

Cambio en el pedal base, no en esta expansión, pero relevante para quien toque la Pi esta semana. src/core/ ganó un carril de video consciente de recursos: sin ninguna pantalla HDMI conectada, el proceso mpv que reproduce video (confirmado con un costo de ~106% CPU y ~300MB RAM de forma continua, incluso solo repitiendo el standby) nunca arranca, las pistas de solo audio siguen reproduciéndose normal, y las pistas de video caen a solo audio hasta que aparezca una pantalla. Conectar HDMI arranca el carril de video de nuevo (incluso detectando una pantalla conectada después del arranque, lo que antes necesitaba reiniciar manualmente pedal-core.service — confirmado como el reporte de bug con el que abrió el día de hoy). Desconectar HDMI en medio de un clip nunca corta el audio del clip — el carril solo se detiene realmente una vez que el clip actual (o el standby) queda libre para hacerlo. Se encontró y corrigió una regresión real el mismo día: la bandera de “detener” de _MpvProcess nunca se reseteaba entre ciclos de detener/arrancar, así que la primera reconexión después de una desconexión dejaba a mpv mostrando su pantalla ociosa (“Drop files or urls to play here”) en vez del standby — confirmado corregido a través de varios ciclos reales de desconexión/reconexión vía los propios logs pegados por el usuario. No tiene relación con el código de esta expansión; se menciona acá solo porque cambia qué significa “el pedal está en reposo” en la misma Pi donde corre esta expansión.

setlist-admin-wifi ahora es formalmente un ítem de roadmap, no uno de corto plazo. Se confirmó en vivo vía nmcli que la configuración inicial de la Pi (el paso de configuración de WiFi del Raspberry Pi Imager) ya guardó un perfil de WiFi (autoconnect: yes) para la red de casa de la banda — lo que significa que cualquier dongle USB de WiFi conectado a la Pi se reconecta a esa misma red por defecto, sin configuración adicional. Dado eso, revivir por completo la expansión setlist-admin-wifi (su propia interfaz de configuración de hotspot, almacenamiento cifrado de credenciales en el USB, etc.) no vale la pena a corto plazo. En su lugar, se agregó una funcionalidad más pequeña e independiente aquí mismo en setlist-admin-usb: ver “Alcanzar la app por WiFi” en USAGE.md. setlist-admin-wifi en sí queda intacta en cuanto a código — solo se replanteó en su propia documentación como “implementada, con tests unitarios, deliberadamente no avanzada por ahora.”

Más tarde el mismo día: los primeros incidentes reales de la funcionalidad de alcance por WiFi, una pregunta de diseño respondida, y la primera corrida de desinstalación/reinstalación. Una prueba QA real del usuario contra la funcionalidad nueva encontró un bug real a los pocos minutos de usarla: upload_song()/assign_track() leían el flujo de red de una subida dentro de _writable_usb(), así que una subida lenta por WiFi (confirmado en vivo, 76MB en ~15 minutos) dejaba a pedal-core.service detenido — sin standby, sin ninguna reproducción — durante toda la transferencia, no solo la escritura real (rápida) al disco. Corregido recibiendo primero a un archivo temporal local (AdminAPI._receive_to_scratch()), el mismo patrón de “copiar local primero, luego copiar” que ya usaba library_optimizer.py. Verificado en vivo con una subida sintética y controlada contra la Pi real: pedal-core.service se mantuvo activo durante toda la fase “lenta”, detenido solo ~1.4s al final — el mismo costo breve y preexistente que ya tiene cualquier escritura cuando mpv tiene el archivo abierto en ese momento.

Revisar ese mismo trabajo de Optimize sacó a la luz un segundo hallazgo real: se habían acumulado ~470MB de archivos temporales huérfanos en ~/pedal-optimizer-scratch/ por un día de reinicios del servicio que interrumpían trabajos en curso — recover_orphaned_jobs() reseteaba el estado de la cola pero nunca supo que esos archivos existían. Corregido con el mismo patrón: run_forever() ahora limpia todo el directorio temporal sin condiciones en cada arranque, antes del primer ciclo — siempre seguro, ya que un trabajo que vuelve a “queued” recibe una copia temporal nueva con un nombre aleatorio distinto la próxima vez. Confirmado en vivo: un reinicio registró “Removed 7 leftover scratch file(s),” liberando ~391MB al instante.

Pregunta de diseño, planteada explícitamente por el usuario: ¿relocalizar todo el montaje de la banda a una red de WiFi de casa nueva (router nuevo, lugar nuevo) necesita algún cambio de código, o la app admin simplemente vuelve a subir sola una vez se sigue el procedimiento documentado de cambio de red? Confirmado que no se necesita ningún cambio de código, por la propia implementación — wifi_connected_to_profile() solo lee el nombre de la conexión activa vía nmcli, nunca su SSID ni su contraseña, así que estructuralmente no puede verse afectada por a qué red real apunte un perfil, siempre que el perfil mantenga el nombre "preconfigured" (que es justo lo que hace el procedimiento documentado de nmcli connection modify). Se agregó un test de regresión dedicado que prueba esto y se documentó la garantía explícitamente en SPECIFICATION.md, USAGE.md, systemd/README.md, y TROUBLESHOOTING.md (ambos idiomas).

Esa misma revisión de documentación encontró de paso algunos cabos sueltos menores, ya preexistentes, al revisar todo de punta a punta: una referencia obsoleta al nombre de archivo “SETLIST_ADMIN_USB_SPECIFICATION.md” (el archivo real siempre se ha llamado SPECIFICATION.md) que quedó en varios docstrings desde antes de un renombre, el mismo problema en el propio código de setlist-admin-wifi (27 apariciones, corregidas mecánicamente de la misma forma), y que la sección 11 de SPECIFICATION.md contaba mal los servicios de install.sh (decía dos, en realidad son tres desde el 2026-10-01) sin mencionar los directorios temporales que limpia rollback.sh.

Finalmente: ver el punto 4 del “Plan de pruebas ordenado” abajo — rollback.sh se corrió de verdad, por primera vez, que es justo cómo se encontró el bug de orden de arriba (no leyendo el código — corriéndolo).

Lo que sigue genuinamente sin confirmar — haz esto antes de confiar en ello

  1. La corrección visual de Export Set — confirmada por el usuario, 2026-10-02, durante la ronda completa de pruebas: se abrió con “Live” seleccionado (título y lista numerada con contenido correcto), se cerró con ✕ y con el gesto de atrás del teléfono, “Share” produjo una imagen limpia sin el aviso que quedaba antes. Hecho.
  2. La corrección del salto de scroll — nunca tuvo una prueba dedicada aislada, pero se ejercitó incidentalmente docenas de veces durante esa misma ronda (renombrar, borrar, editar Banks/pistas) sin ninguna queja. Se trata como confirmado por uso extensivo informal; revisar solo si el salto se vuelve a ver de verdad.
  3. El soporte de navegador de escritorio/PC para el botón “Share” de Export Set sigue explícitamente sin certificar — navigator.share() con archivos adjuntos tiene poco soporte en navegadores de escritorio; el código cae a una descarga simple, pero esto nunca se ha probado desde un PC real. Hazlo cuando llegue la validación en hardware real de setlist-admin-wifi (ver “Dónde están las cosas” arriba), ya que esa es la expansión pensada para usarse desde un computador en la red de casa.
  4. Si los cuatro archivos .wav con nombres genéricos raros de la biblioteca (“track N” en vez de un título real) son contenido real del repertorio o datos de prueba sobrantes sigue siendo una pregunta abierta — se le preguntó al usuario el 2026-10-02, nunca respondió. No los borres sin que te lo pidan; pregunta de nuevo si vuelve a salir el tema.

Plan de pruebas ordenado — continúa acá

Es la misma lista que el usuario pidió seguir “paso a paso”, hace varias sesiones. Los puntos 1-4 ya están hechos:

  1. Reboot-to-apply — hecho, validado con un reinicio real.
  2. Resistencia a desconexión a mitad de edición — hecho; se encontró y corrigió el bug de archivos temporales huérfanos (ver arriba).
  3. Renombrar/borrar una canción de la biblioteca desde la app — confirmado funcionando, incluyendo la preocupación del salto de scroll (ver punto #2 de “sin confirmar” arriba).
  4. Desinstalar / rollback — hecho, 2026-10-03, corrido por primera vez en toda la historia de este proyecto. Se encontró y corrigió un bug real haciéndolo: rollback.sh desactivaba setlist-admin.service antes que usb-tether-watchdog.service, dejando una ventana donde el watchdog (ahora también consciente de WiFi, no solo de teléfono por cable) lo volvía a arrancar momentos después — confirmado en vivo vía systemctl status mostrándolo active de nuevo minutos después, con Loaded: not-found. Corregido desactivando primero el watchdog. Se repitió el ciclo completo después del arreglo (rollback.sh → install.sh) y se confirmó: pedal-core.service se mantuvo activo todo el tiempo, nunca se tocó; el hash del PIN (comparado con md5sum antes/después) y las 6 Banks del Set Live sobrevivieron byte por byte; la app admin volvió a subir sola por WiFi sin ningún teléfono conectado, justo después de la instalación nueva, exactamente como está diseñado.
  5. (Opcional, no bloqueante) Probar con un segundo teléfono (idealmente Android, para ejercitar los drivers rndis_host/cdc_ether/cdc_ncm que esta sesión solo probó con un iPhone y su ipheth).

Decisiones de producto pendientes — no construidas aún, necesitan decisión primero

La de mayor prioridad de las tres de abajo, dado que ya costó datos reales una vez: respaldar el standby.mp4 anterior antes de reemplazarlo. library_ops.set_standby_video() hoy simplemente copia la canción elegida directo sobre standby.mp4, sin preguntar nada — funciona bien hasta que alguien elige el video equivocado, que es exactamente lo que pasó el 2026-10-01 (ver “Dónde están las cosas” arriba): el standby real de ~55 minutos de la banda desapareció, irrecuperable desde la propia Pi, y solo volvió porque por suerte todavía existía una copia en otro lado. Ver la sección de arriba para la forma razonable sugerida (un solo cupo de respaldo, backup/standby-previous.mp4, sobrescrito cada vez).

Dos cosas más que el usuario planteó y pidió explícitamente dejar para después:

Notas operativas para quien despliegue al Pi después

Estas costaron tiempo real descubrirlas esta sesión — no las reaprendas por las malas: