Próximos pasos para setlist-admin-usb
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:
- Fallo transitorio de
umount(EBUSY) → ahora reintenta. - El USB de biblioteca se ha observado desmontándose solo, sin ninguna
evidencia correspondiente en ningún registro — causa raíz no
confirmada, pero se capturó una caída real de voltaje
(
vcgencmd get_throttledmostró subvoltaje) durante esta sesión, la pista más fuerte hasta ahora.usb_mount.pyahora se autorepara de todas formas. - Un doble toque podía hacer chocar dos ciclos de remontaje →
writable_usb()ahora es un candado de todo el proceso. - Que el teléfono se desconecte a mitad de una subida dejaba archivos
huérfanos
.part/.swaptmppara siempre → ahora se limpian en cada arranque del servidor. rename_song()/delete_song()no verificaban que el archivo siguiera existiendo → corregido, ya sigue el mismo patrón que el resto delibrary_ops.py.- Frontend: faltaba manejo de errores, el scroll saltaba al recargar
listas, y el destello de confirmación en los botones aparecía al
recibir respuesta en vez de al tocar → todo corregido. Ver los
comentarios de
flashSuccess()/clearFlash()/beginScrollPreservation()enstatic/app.jspara el razonamiento — sigue ese mismo patrón para cualquier botón nuevo que agregues.
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:
- Una demora que el usuario midió informalmente como “~40 segundos” para que una acción de Optimizar/Cancelar se actualizara visualmente resultó, según las marcas de tiempo del propio log de acceso del servidor, ser de ~2 segundos de punta a punta — no se encontró ningún cuello de botella en el backend. Nota para la próxima vez: si esto se repite, captura la hora exacta en que tocaste el botón para compararla con el log, ya que “se sintió lento” no basta por sí solo para perseguir un arreglo específico.
- Poner un video grande, todavía sin optimizar, directamente como
standby falló de plano con un
OSError: [Errno 28] No space left on devicereal — el USB de la biblioteca estaba en ~925MB libres (89% usado, confirmado condf), muy lejos de lo necesario para copiar un video sin procesar de varios GB. No es un bug de código — el arreglo fue liberar espacio, no cambiar el comportamiento.
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
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.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.- 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 desetlist-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. - Si los cuatro archivos
.wavcon 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:
Reboot-to-apply— hecho, validado con un reinicio real.Resistencia a desconexión a mitad de edición— hecho; se encontró y corrigió el bug de archivos temporales huérfanos (ver arriba).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).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.shdesactivabasetlist-admin.serviceantes queusb-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íasystemctl statusmostrándoloactivede nuevo minutos después, conLoaded: 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.servicese mantuvo activo todo el tiempo, nunca se tocó; el hash del PIN (comparado conmd5sumantes/después) y las 6 Banks del SetLivesobrevivieron 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.- (Opcional, no bloqueante) Probar con un segundo teléfono
(idealmente Android, para ejercitar los drivers
rndis_host/cdc_ether/cdc_ncmque esta sesión solo probó con un iPhone y suipheth).
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:
- Mostrar el nombre de archivo crudo tal cual está guardado (ej. “A -
Perro.wav”) en vez del nombre visible en las tarjetas principales de
Bank/track (no en Export Set, que ya hace esto por diseño — ver
sección 14). Se discutió a fondo; se identificaron riesgos reales
(redundante con el círculo de letra que ya se muestra, el texto más
largo se corta más agresivamente, las mayúsculas de la extensión se
vuelven visibles/inconsistentes, y no revela realmente un nombre
de archivo mal formado en el USB, ya que esos son invisibles para
list_tracks()/list_songs()de todas formas). Si se retoma: debería ser un cambio solo visual — el flujo de “Renombrar” debe seguir editando únicamente la parte del nombre visible, nunca la letra/extensión directamente, o se reabre exactamente el riesgo de fallo silencioso de nombres de archivo quelibrary_ops.pyfue construido para hacer estructuralmente imposible (ver el docstring del propio módulo). - Una recomendación formal en la app/documentación contra editar el
USB a mano una vez que una expansión está instalada. El usuario
preguntó cómo maneja la app actualmente que alguien renombre/borre
archivos directamente en el USB fuera de la app. Respuesta (verificada
en el código, esta sesión): ya es mayormente seguro — nada queda
en caché, cada pantalla relee el USB de nuevo, y
list_tracks()/_require_set()/_require_bank()/assign_song_to_slot()todos revisan el estado actual de forma defensiva. El único hueco real encontrado (rename_song()/delete_song()sin verificar existencia) ya está corregido. Lo que no se hizo: escribir de verdad una recomendación de “no edites el USB a mano una vez que uses la app” enLIBRARY.md/USAGE.md. Vale la pena hacerlo si el usuario todavía lo quiere, pero es una adición de documentación, no una corrección de código — nada está actualmente roto por ediciones manuales más allá de ese hueco ya corregido.
Notas operativas para quien despliegue al Pi después
Estas costaron tiempo real descubrirlas esta sesión — no las reaprendas por las malas:
- Verificar versiones: este proyecto y cada expansión ahora llevan
un archivo
VERSIONbasado en fecha (vAAAA.MM.DD) — ver la sección “Versionado” delCHANGELOG.mdraíz para el esquema. En la Pi real:ssh -4 pedal "cat ~/chocolatepi-repo/VERSION ~/chocolatepi-repo/expansions/*/VERSION". Recuerda que un archivoVERSIONsolo refleja la realidad si alguien lo actualizó en el mismo commit que el cambio — el hash del commit al que está sincronizado siempre es la fuente de verdad definitiva. - SSH: siempre
ssh -4 pedal/scp -4 ... pedal:...(forzar IPv4; tenereth0+eth1+mDNS mezclados causa cuelgues de varios minutos en comandos triviales si no). - El Pi tiene DOS interfaces de red independientes que importan:
eth0es el Ethernet/LAN de casa (lo que usa SSH);eth1es el teléfono que esté conectado por USB en ese momento (ej.172.20.10.2/28para un iPhone con Personal Hotspot). Son totalmente independientes — desconectar el Ethernet no afecta la capacidad de un teléfono conectado de llegar asetlist-admin, y viceversa. pedal-core.serviceNO corre desde el checkout de git. SuExecStartapunta a/home/hesner/pedal_src_test/src/main.py, una copia de archivos planos separada. Cualquier cambio asrc/core/library.py/core.py/main.pydebe copiarse a ambos~/chocolatepi-repo/src/y~/pedal_src_test/src/(y limpiar__pycache__bajopedal_src_test/src/*/) — actualizar solo el checkout de git no hace nada para el pedal en vivo, en silencio. Esta inconsistencia probablemente debería limpiarse en algún momento (apuntarpedal-core.serviceal checkout de git en su lugar), pero no se hizo, para evitar un cambio riesgoso no relacionado a mitad de sesión.- CRÍTICO: la raíz del sistema de archivos del Pi es un overlay
respaldado en RAM por defecto (
overlayroot=tmpfs:recurse=0encmdline.txt, según la sección 4 desystemd/README.md). Cualquier cosa escrita bajo/home/(el checkout de git,pedal_src_test, lo que sea) mientras el overlay está activo — un simplescp/tar xzf/git pull— funciona para el arranque actual pero se borra en silencio en el siguiente reinicio. Esto nos afectó de verdad esta sesión (horas de trabajo “desplegado” desaparecieron tras un reinicio no planeado). Para desplegar algo que deba sobrevivir un reinicio:ssh -4 pedal "sudo mount -o remount,rw /boot/firmware && sudo sed -i 's/overlayroot=tmpfs:recurse=0 //' /boot/firmware/cmdline.txt && sudo mount -o remount,ro /boot/firmware && sudo reboot"(confirmado otra vez el 2026-10-01:sudo raspi-config nonint do_overlayfs 1por sí solo no funciona aquí — su lógica de coincidencia no reconoce el sufijo personalizado:recurse=0, así que dejacmdline.txtcompletamente intacto y el overlay sigue montado después del reinicio. Usa siempre elseddirecto de arriba, nodo_overlayfs 1, para desactivarlo.)- Espera a que vuelva (
until ssh -4 pedal "echo up" 2>/dev/null; do sleep 3; done), luego confirma conmount | grep -E ' / 'que la raíz es un montajeext4 rwnormal, nooverlay. - Despliega (ver abajo), corre las pruebas en el propio Pi, reinicia los servicios systemd afectados, verifica.
- Reactiva:
ssh -4 pedal "sudo raspi-config nonint do_overlayfs 0 && sudo mount -o remount,rw /boot/firmware && sudo sed -i 's/overlayroot=tmpfs /overlayroot=tmpfs:recurse=0 /' /boot/firmware/cmdline.txt && sudo mount -o remount,ro /boot/firmware && sudo reboot"(nota:do_overlayfs 0vuelve a quitar:recurse=0cada vez — siempre reagrégalo a mano antes de este reinicio, o te encontrarás con el viejo bug de modo de emergencia por USB faltante deTROUBLESHOOTING.md). - Espera a que vuelva, y luego verifica que la persistencia
realmente funcionó:
git status --porcelainen~/chocolatepi-repodebería mostrar limpio lo que acabas de comitear (o tu diff esperado si aún no lo comiteas), y los servicios afectados ya deberían estar corriendo el código nuevo (arrancan solos al reiniciar).- Un despliegue rápido y de bajo riesgo que solo estás probando (sin
intentar hacerlo durable todavía) puede saltarse todo esto y solo
hacer
scp/tardirecto al overlay ya activo — solo recuerda que no sobrevivirá un reinicio hasta que hagas el paso a paso de arriba.
- Un despliegue rápido y de bajo riesgo que solo estás probando (sin
intentar hacerlo durable todavía) puede saltarse todo esto y solo
hacer
- Agrupa despliegues de varios archivos en un tarball, no en un ciclo
de
scpindividuales. Un ciclo descppor archivo se probó una vez y falló en silencio en transferir la mayoría de los archivos (sin ningún error mostrado). Patrón confiable:tar czf /tmp/x.tar.gz <archivos>,scp -4 /tmp/x.tar.gz pedal:/tmp/, luegossh -4 pedal "cd ~/chocolatepi-repo && tar xzf /tmp/x.tar.gz && rm /tmp/x.tar.gz". ntfs-3g(el sistema de archivos del USB de biblioteca) no soportamount -o remount,rw/ro— se rehúsa directamente. Todo remontaje, automatizado o manual por SSH, debe ser unumountreal seguido de unmount -o <modo>fresco.- Claude específicamente no tiene ninguna herramienta de
navegador/automatización visual en este entorno. La lógica del lado
del servidor (llamadas HTTP, tiempos, respuestas de error) se puede y
se debe verificar directamente — ya sea con peticiones HTTP reales
contra el servicio corriendo, o importando
AdminAPIdirectamente en un script de Python puntual por SSH contra el USB real montado (usando un Set de prueba desechable, limpiado después). Pero cualquier cosa visual — colores de botones, posición de una notificación, si una vista a pantalla completa realmente se renderiza — necesita que el usuario mire su teléfono y reporte de vuelta. No afirmes que una corrección visual está confirmada funcionando sin eso.