Con dos tipos de transacción basta

modelo de datos

Una de las primeras versiones de HiBill podía registrar un movimiento de dinero de cuatro formas distintas: un gasto, un préstamo, una devolución y una liquidación. Cada uno tenía su propia tabla, su propia pantalla y sus propias reglas.

Hoy tiene dos. Aquí va el porqué, y lo que aprendimos al desmontarlos.

Tres objetos que en secreto eran uno

Fíjate en lo que guardaban de verdad tres de los cuatro.

Un préstamo era: el dinero fue de A a B, y ahora B le debe a A.

Una devolución era: el dinero fue de B a A y canceló parte de lo que B debía.

Una liquidación era: el dinero fue de B a A y canceló parte de lo que B debía, y además la marcábamos como definitiva.

Tres nombres. Un solo hecho. El dinero pasó de exactamente una persona a exactamente otra. Las diferencias eran todas narrativas (lo que el usuario quería decir con ello), no estructurales. Y habíamos codificado esa narrativa en el esquema, donde se endureció en tres caminos de código que había que mantener sincronizados para siempre.

La pista fue el campo de estado de las liquidaciones. Las liquidaciones tenían los estados pending, paid y cancelled. La interfaz ponía paid todas y cada una de las veces. Habíamos construido una máquina de estados y no la usamos nunca, porque en la vida real una liquidación se registra después de que el dinero ya se ha movido. Un estado muerto en un esquema es una señal clara de que ese objeto no debería existir.

Lo que de verdad es distinto

Solo una distinción sobrevivió al contacto con el uso real: cuántas personas hay en cada extremo.

Un gasto va de uno a varios. Alguien (o varias personas) pagó, y un grupo de personas comparte el coste. Lo interesante es el reparto.

El encabezado y el desglose de una transacción: pagaste €186.40, dividido entre cuatro, y te deben €139.80

Una transferencia va de uno a uno. El dinero salió de una persona y llegó, entero, a otra. No hay reparto que calcular: quien lo recibe se queda con todo el importe.

Es una diferencia estructural de verdad, y es la única. Así que esos son los dos tipos.

Todo lo que podía expresar el antiguo modelo de cuatro objetos, este también lo expresa:

Lo que quieres registrar

Cómo

Una cena, dividida entre cinco

Gasto

Prestarle €50 a un amigo

Transferencia

Devolver esos €50

Transferencia, en sentido contrario

Dejar un saldo a cero

Transferencia

Saldar cuentas dejó de ser una ceremonia especial. No tiene una pantalla propia ni un estado aparte. Es una transferencia que resulta que deja un saldo a cero, y aparece en el historial junto a todo lo demás, porque eso es lo que es. Registrarla es igual que registrar cualquier otra cosa: escribe le di 15 a anna y la revisión lo lee como una transferencia (le diste €15.00 a Anna Lopes), sin ningún reparto que comprobar.

«le di 15 a Anna Lopes» leído como: le diste €15.00 a Anna Lopes

Las invariantes, en un solo sitio

Reducir los tipos permitió enunciar las reglas una vez y aplicarlas una vez. Hay un único archivo que responde a «¿es válida esta transacción?», y dice más o menos esto:

Para un gasto: los importes de quienes pagaron, cuando hay pagadores, tienen que sumar el total; un gasto también puede empezar vacío, con las personas añadidas más tarde o con cada uno eligiendo su parte desde un enlace compartido. Cada participante debe su parte.

Para una transferencia: exactamente un pagador, exactamente un destinatario, y la parte del destinatario es el importe completo.

Ese es todo el reglamento específico de cada tipo. Cuando teníamos cuatro objetos, reglas equivalentes estaban repartidas por cuatro servicios y se iban separando: el camino de varios pagadores tenía comprobaciones que el de las liquidaciones no tenía. Ahora hay una sola puerta, y todo pasa por ella.

La única excepción

Hay un tercer tipo en la base de datos que nunca creas tú: Simplificación.

Cuando el sistema detecta un círculo de deudas (tú le debes a Marco, Marco le debe a Aya y Aya te debe a ti), puede reducir ese círculo sin cambiar cuánto sale perdiendo de verdad cada uno. Esos ajustes tienen que registrarse como entradas reales, o los saldos se moverían sin explicación.

Así que son transacciones, con la etiqueta Simplificación, en el historial, donde puedes verlas. La regla que nos impusimos: ningún saldo cambia sin un motivo visible. Una cifra que se desvía en silencio es peor que una cifra un poco demasiado grande.

¿Valió la pena?

La reescritura borró más código del que añadió, lo que suele ser buena señal. Pero la verdadera recompensa estuvo en lo que se volvió más fácil después.

Las divisiones abiertas, la asignación de los artículos de un recibo, los saldos en varias monedas, añadir a personas que no se han registrado: cada una de estas cosas habría necesitado antes cuatro implementaciones, una por objeto, y por eso se habían ido aplazando sin hacer ruido. Sobre dos tipos con invariantes compartidas, eran pequeñas.

Ese es el argumento para reducir un modelo, y en realidad no se trata de elegancia. Cada objeto de más en un esquema multiplica el coste de cada función que vayas a añadir. Estábamos pagando 4× en todo para conservar distinciones que solo existían en nuestra cabeza.


Los dos tipos ya están hoy en HiBill. Si quieres el detalle de cómo se calculan los saldos sobre ellos, cómo funcionan los saldos es el artículo que lo complementa.

Sigue leyendo