Skip to content

← Volver al listado

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.

3 min de lectura

Había un script. Doscientas líneas. Funcionaba. Ahí no se acababa la historia — sin el paso siguiente, Lumware no sería una familia.

Díptico: a la izquierda el main.py monolítico inicial; a la derecha el árbol actual de lumware/scripts/ con core, protocol, transport, importers, server, cli + orchestrator.

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); el perf_counter viene 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. El ruff.extend-exclude está comentado en el pyproject.toml con 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.