Bitácora · Notas de construcción

Bitácora

Notas del día a día: qué construyo, qué decido y qué aprendo. Sin pulir de más — el trabajo real, en bruto. No son ensayos; para eso están los Decision Logs.

No hardcodeé los 100 pisos

En un roguelike personal decidí no escribir sus ~100 pisos a mano. Un servicio de dominio los genera desde una seed determinista: ensambla piezas de datos y la dificultad sube por curvas en JSON, no por código. El guiño es técnico — una seed reproduce la corrida exacta, la misma reproducibilidad que uso para volver a montar un incidente en producción o hacer replay de una transacción.

El aprendizaje: el rigor que hace predecible una plataforma crítica —determinismo, comportamiento dirigido por datos y decisiones registradas como ADR— no cambia con el dominio. Aplicado a un juego hobby es el mismo que evita sorpresas a las 3 de la madrugada en banca. La disciplina viaja; el dominio es anécdota.

Fragmento de código: un piso se genera de forma determinista desde una seed, con las reglas viviendo en datos (JSON)
Un piso = f(seed, altura): determinista y dirigido por datos.

Un cap de tokens de salida es un límite, no un gasto

El análisis de comida por foto en TrainO se volvió casi 2x más lento sin que hubiéramos hecho ningún deploy. No cambió el código. Cambió el comportamiento del proveedor del modelo: empezó a devolver el JSON pretty-printed. En platos con varios ítems, la respuesta empezó a chocar contra el límite de 1024 tokens de salida, y cada respuesta truncada activaba un retry con el modelo más caro. Resultado: las escalaciones pasaron de 0% a 29% en pocos días.

El aprendizaje fue simple: un límite de tokens de salida no es necesariamente un mecanismo de ahorro. Si lo aprietas demasiado, no reduces costo: solo conviertes una respuesta incompleta en un segundo intento completo, más lento y más caro.

El fix fue menos glamoroso, pero más correcto: subimos el cap de salida, forzamos JSON minificado (entre 30% y 50% menos tokens de salida) y dejamos de descartar respuestas truncadas — ahora rescatamos los ítems completos antes de decidir si hace falta escalar. También agregamos trazabilidad por registro: timings, outcome, modelo usado, fallback aplicado y causa de truncación. La próxima vez, el diagnóstico no será intuición. Será un query SQL.

Diagrama del pipeline de análisis de comida, antes y después: antes, el JSON pretty-printed supera el cap de 1024 tokens de salida, falla el parse y escala al modelo caro hasta fallar; después, con cap 2048 y JSON minificado, el mismo plato vuelve en 8.1 segundos sin escalación
La cadena completa: un cambio de formato del proveedor más un cap justo se convirtió en truncación, reintentos caros y el doble de latencia — sin ningún deploy propio.

El test no era flaky: era el calendario

Dos tests de regresión fallaban de forma intermitente. La lectura fácil era flakiness aleatorio, pero aislados fallaban igual todo el día: no eran aleatorios, eran deterministas por fecha. El generador siembra su PRNG diario con la fecha local, y el test creía fijarla con un mock de formatDateLocal apuntado al módulo equivocado — el código la importa de un módulo hermano, así que el mock era un no-op silencioso y el test quedaba a merced del reloj real (verde solo algunos días).

La moraleja doble: un mock en la ruta de import equivocada no te avisa, simplemente no se aplica; y «flaky» casi nunca es aleatorio, es determinista respecto a algo — acá, el día. El fix fue anclar el reloj con fake timers; el bug de fondo era confiar en un pin que nunca existió.

Captura de terminal: la prueba explicitSelection corrida bajo 12 fechas fijadas, en verde (PASS) solo 3 y en rojo (FAIL) las otras 9
La misma prueba bajo 12 fechas: verde solo 3 de 12. Mismo commit; lo único que cambia es la fecha del reloj.

Un LLM pequeño no sabe qué no sabe

En VaultOS, la barra de comandos usa un modelo local de 3B para frases como «dame el primer password que guardé» o «la contraseña más débil» — y devolvía siempre el mismo elemento. La causa: el modelo no tiene reloj ni sabe medir fuerza de contraseñas, y ante datos que no existen un LLM no dice «no sé»: adivina.

El fix no fue prompt engineering, fue determinismo: detecto la intención con reglas, ordeno los candidatos para que el ítem 1 sea la respuesta correcta por construcción, y las frases de calidad se resuelven contra la auditoría de seguridad — con un null honesto si no hay nada. El modelo solo elige de una lista que ya no le deja equivocarse.

Barra de búsqueda de VaultOS con la frase 'dame la contraseña más débil' y debajo la sugerencia de la IA local: Abrir · Cpanel
La frase no coincide con nada literal; la IA local la resuelve contra la auditoría y sugiere el elemento débil.

TrainO: de una hernia a un sistema experto

Entreno desde siempre. Hasta que una hernia discal me frenó y me dijeron que no volvería a entrenar. No lo acepté como veredicto: lo tomé como un problema que había que diagnosticar. Estudié y desglosé carga, recuperación y progresión hasta entenderlo —y volví. TrainO nació de ahí.

Por eso TrainO no reparte plantillas: razona. Calcula un readiness score cada día —sueño, fatiga acumulada, estado del sistema nervioso— y ajusta volumen y descansos. Cada sesión respeta tu recuperación real, no un calendario fijo. Es el sistema que yo hubiera necesitado.

Y no es una feature: es un sistema completo —entrenamiento, híbrido, cardio con GPS, un motor de nutrición con base científica (seis reglas que citan estudios reales, detección de adaptación metabólica), Apple Watch autónomo, modo coach y portal de nutricionista—. El reto fue mantener todo eso coherente: un sistema, no una suma de features.

VaultOS: IA local sobre una bóveda cifrada

VaultOS llegó a MVP funcional en Mac: una bóveda local-first donde la base de datos entera se cifra con AES-256-GCM, la clave se deriva con Argon2id y el desbloqueo puede ir por Touch ID.

Lo que más me gusta: corre IA local on-device, sin que un solo byte salga de la máquina. Sin nube, sin telemetría. 172 tests cubren el núcleo de cripto y datos. Privacidad por arquitectura, no por promesa.