Nada estaba roto. Y cuatro cosas eran falsas igual.
Abrí la sesión con una lista de cinco tareas pendientes. La idea era ordenada y aburrida: revisar el estado de PRISMA antes de seguir construyendo encima. En vez de leerlas y confiar, decidí ir a comprobar cada una contra la realidad.
De las cinco, tres eran reales. Una estaba a medio hacer sin que nadie lo hubiera anotado. Y una llevaba tres semanas terminada.
El subdominio del producto respondía. Tenía DNS, tenía certificado, servía la página. El ticket seguía en “En revisión” porque quedaban dos casillas sin marcar — dos casillas de cosas que ya habían ocurrido. Nadie mintió. Nadie se equivocó. El trabajo estaba hecho y el tablero no se enteró, porque un tablero no se entera de nada: alguien tiene que ir a contarle.
Eso fue lo primero. Después el día se convirtió en una sola pregunta, repetida cuatro veces.
1. El archivo que se contradecía a sí mismo
Hay un archivo de estado que todos mis agentes leen al arrancar. Es la fuente de verdad del proyecto activo: qué está aprobado, qué está bloqueado, bajo qué condiciones.
El 13 de agosto tomé una decisión — eliminar un tope operativo que le ponía techo al producto: máximo dos clientes de pago simultáneos. La decisión quedó escrita. Un campo del archivo se actualizó.
Los otros seis no.
Y esos seis son los que un agente lee primero. Un agente que abriera la lista de condiciones, sin llegar al bloque donde estaba la corrección, volvía a congelar un producto que yo había desbloqueado de forma explícita. No por un error de código: por leer con atención un documento que decía algo que había dejado de ser cierto.
Corregir eso no fue reescribir seis líneas. Fue ir campo por campo preguntando lo mismo: ¿esto sigue siendo verdad?
2. La válvula, y una especificación que describía otro producto
Lo que sí se construyó hoy fue una válvula de capacidad. PRISMA promete entregar en 72 horas y tiene cero entregas de pago cerradas. La válvula existe para que, si entran más pedidos de los que puedo entregar, nadie quede esperando en silencio.
Quiero decir bien esa parte: lo que la válvula evita no es la escasez. Es el silencio. Una fila con un número y un correo es infinitamente mejor que un formulario que lo acepta todo y una persona que no vuelve a saber de ti.
Al escribir el código contrasté la especificación del ticket con lo que el producto realmente hace. Dos afirmaciones de la especificación eran falsas. Decía que el botón de pago de la landing pasaba a formulario de lista de espera — y la landing no tiene botón de pago. El pago se pide por correo, después de revisar el caso. El ticket describía un flujo que el producto ya no tenía.
3. El correo que prometía algo que el código no podía cumplir
Este es el momento del día.
Escribí el correo que recibe alguien que quiso entrar y quedó en fila. Decía, con la mejor intención del mundo: tu precio queda congelado, pase lo que pase con el precio mientras tanto.
Leído solo, es un buen correo. Alfred lo cruzó contra el código y encontró que no existía ningún mecanismo para congelar nada. El monto salía de una constante, y el correo de aprobación posterior lo recalculaba desde ahí. Si yo subía el precio mientras alguien esperaba, el sistema le habría cotizado el valor nuevo a una persona que tenía el viejo prometido por escrito.
No hay bug que reportar. No hay error en los logs. No hay test en rojo. El sistema hace exactamente lo que dice el código, y el código nunca supo del compromiso.
La promesa se rompía sola, sin que nadie la rompiera.
En el mismo veto aparecieron dos más: una frase describía solo uno de los tres estados en que la válvula se abre —con los otros dos habría sido mentira—, y el reloj de las 72 horas estaba mal descrito respecto de los términos públicos, que incluyen devolución del 100%. Un plazo mal contado por escrito, con plata de por medio, no es un detalle de copy.
Y hubo una cuarta, que apareció al verificar la de Alfred. Él marcó que numerar la posición en la fila compromete atender por orden de llegada. Al comprobarlo resultó peor: mi propio manual de operación ya documentaba una regla que no es orden de llegada. El número no prometía de más — contradecía una política escrita.
La regla que me llevo: todo texto que promete comportamiento futuro es una especificación, y hay que verificarlo contra el sistema igual que se verifica una función. Las cuatro quedaron cableadas como tests que fallan si una edición futura las reintroduce. Una regla que solo vive en un comentario se pierde en el cambio siguiente.
4. La herramienta con la que estaba verificando también mentía
En medio de todo esto hice la pregunta que faltaba: ¿por qué hay que tocar la base de datos de otro producto para esto?
Es una sola base para seis productos, separados por prefijo de nombre de tabla. La carpeta donde viven las migraciones se llama como el producto más viejo, porque fue el primero que las necesitó, y el resto fue apilando ahí. Ese nombre convirtió una duda legítima en una respuesta larga, y la respuesta tenía que ser con evidencia, no con confianza.
Así que tomé una foto de todas las tablas antes de migrar. Treinta y dos tablas, seis productos, alrededor de 1.975 filas. Y la tomé con n_live_tup, que es lo primero que aparece cuando buscas cómo contar filas rápido en Postgres.
Decía que una tabla de otro producto tenía 0 filas. Tiene 15.
n_live_tup es una estimación del planner. En tablas con poca escritura puede quedar desactualizada de forma indefinida.
Lo grave no es el número equivocado. Es que la comparación habría pasado. Antes y después me habrían dado el mismo valor incorrecto, y mi verificación de seguridad habría reportado “todo intacto” sin haber mirado nada. Una verificación que se apoya en una estimación no verifica: tranquiliza.
La rehice con count(*). Ahí sí quedó demostrado, tabla por tabla, que los cinco productos ajenos no cambiaron una sola fila. Y el aislamiento por convención de nombres dejó de depender de mi memoria: el script que aplica migraciones ahora aborta antes de conectarse si el SQL menciona tablas fuera del producto declarado. Lo probé con los casos que debe bloquear —un borrado y una tabla eliminada de otros productos—, no solo con el caso feliz. Un guard que solo se probó cuando debía pasar no es un guard.
El hilo
Cierro el día con 13 commits, 460 tests verdes (41 nuevos) y un PR abierto. Nada de eso es la historia.
La historia es que cuatro capas distintas de mi sistema estaban afirmando cosas que no eran ciertas, y ninguna estaba fallando. El gestor de tareas decía que un ticket estaba abierto y estaba cerrado hacía tres semanas. El archivo de estado decía que había un tope que ya no existía. El correo prometía un precio que el código no sabía sostener. Y la herramienta con la que iba a verificar todo eso decía 0 donde había 15.
Ninguna alarma. Ningún log rojo. Ninguna prueba caída.
Un sistema no te avisa cuando deja de describir la realidad. Sigue funcionando igual de bien, y esa es exactamente la parte peligrosa. Que algo esté bien y que un sistema diga que está bien son dos hechos distintos, y la distancia entre ellos crece sola, todos los días, sin que nadie haga nada.
Hoy no construí casi nada. Fui a comprobar. Es el día menos vistoso que he tenido en meses, y probablemente el que más deuda me ahorró.