De script a producto
Había un script de 200 líneas. Funcionaba. Ahí no acababa la historia — sin el paso siguiente, no habría familia.
Había un script. Doscientas líneas. Funcionaba. Ahí no se acababa la historia — sin el paso siguiente, Lumware no sería una familia.
El contexto
El primer host era un main.py de doscientas líneas. Abría
un fichero, calculaba colores para cada LED, escribía por
serie. Sin tests. Sin logs estructurados. Sin separación
entre leer, transformar y enviar.
Funcionaba porque todo estaba en la cabeza de una persona — la mía. El día que lo dejé un mes y lo volví a abrir, tuve que volver a entender qué hacía. La segunda semana le añadí una “pequeña mejora” y dejó de funcionar. Ningún test me avisó porque no había ninguno.
La opción fácil: arreglar la regresión y seguir. La opción real: parar y tratarlo como producto.
La decisión
Tres cambios simultáneos, no negociables:
1. Estructura modular. El paquete lumware/scripts/
deja de ser un fichero y pasa a ser cinco áreas con fronteras
claras:
core/— events, settings, scheduler, cache, colors.protocol/— codec binario, CRC, definiciones de frame.transport/— serie real y loopback para tests.importers/— imágenes y vídeos a frames RGB.server/— FastAPI + WebSocket para la UI web.
Más cli/ (la capa Unity-stdin) y orchestrator.py
(playback en thread propio).
2. Tests reales. No demos. No asserts en if __name__.
Tres niveles: unit en core/, integration en server/ y
orchestrator/, compat para el contrato Unity stdin. Suelo
de cobertura del 80 % en core/, anclado en pyproject.toml
(hoy rodando por el 98 %).
3. Convenciones explícitas. Imports relativos dentro del
paquete. print() prohibido en el core (solo EventBus).
time.perf_counter siempre para el scheduler — nunca
time.time(). Settings inmutables. tools/ queda al
margen: puede hacer shell=True, pero nada del runtime
lo importa.
Por qué estas reglas y no otras. Cada regla está anclada a una razón concreta: el
print()prohibido viene del contrato Unity (la UI espera eventos serializados); elperf_counterviene de un bug real de derivas con wall-clock en el código antiguo; las Settings frozen vienen de no fiarse de mutaciones cruzadas cuando el playback y la UI corren en threads distintos. Elruff.extend-excludeestá comentado en elpyproject.tomlcon la story que retira cada exclusión, así los legacy paths no se eternizan.
Este paso no inventa nada nuevo. Coge lo que ya funcionaba y lo reorganiza alrededor de fronteras bien definidas. Pero es lo que hace posibles Lumware Capture y Lumware Stage — comparten vocabulario (frames, transport, host, codec) porque ese vocabulario, aquí, tiene un significado explícito y contrastable.
Lo que viene
En el próximo post bajamos al nivel físico: tira NeoPixel, fuente de alimentación, level shifter, tierra común. Seis componentes y el motivo real de cada uno — porque el software puede ser perfecto y el primer LED seguir haciéndote color aleatorio si la masa no está compartida.