# Serie Ledger, Parte I: El modelo de partida doble

> La mayoría de los sistemas financieros empiezan igual. Estás construyendo una wallet, un usuario tiene $100, le envía $30 a un amigo, así que escribes balance = balance - 30 en una fila y balance = balance + 30 en la otra.

Por Shodipo Ayomide, publicado el 30 jun 2026. Versión web: https://blog.shodipoayomide.com/es/ledger-series-part-i-the-double-entry-model

![Serie Ledger, Parte I: El modelo de partida doble](https://blog.shodipoayomide.com/banners/ledger-series-part-i-the-double-entry-model.png)

La mayoría de los sistemas financieros empiezan igual. Estás construyendo una wallet, un usuario tiene *\$100*, le envía *\$30* a un amigo, así que escribes `balance = balance - 30` en una fila y `balance = balance + 30` en la otra. El saldo pasa a ser un simple número que guardas en la wallet y actualizas cada vez, como el total de una tarjeta de regalo. Funciona, y lo subes a prod así.

Luego la empresa crece, ahora tienes tarjetas de débito/crédito, rieles cripto, pagos en fiat, comisiones, reembolsos, unos cuantos proveedores de pago, un proveedor de wallets. El sistema financiero termina repartido entre servicios y tablas que pertenecen a distintos equipos. Y un trimestre, finanzas te dice que hay *$40,000* sin justificar, que no aparecen por ningún lado.

Técnicamente el dinero no ha desaparecido. Está en tu cuenta bancaria, cada centavo está ahí. Lo que ha desaparecido es la atribución o la referencia, el registro de *a quién pertenece*. Una columna `balance` en tu db guarda un número pero no toda la historia que hay detrás, así que cuando el número y la realidad no coinciden, no hay forma de reproducir cómo se llegó ahí. Todo ingeniero que haya trabajado en algún sistema financiero clave tiene su propia versión de esta historia de terror, ¡pregunta!

La solución no es simplemente una columna `balance` mejor pensada. Es un modelo que los bancos y los sistemas financieros tradicionales llevan años usando, y sobre el que todavía funciona hoy cualquier ledger serio: la partida doble.

## Por qué falla un solo número

Guardar el dinero como un número mutable, en partida simple, falla por tres razones principales, y suelen aparecer a las 2:45 AM de la madrugada.

Y no puedes rastrear para entender qué pasó, porque no hay un historial claro. `balance = 70` no dice nada sobre los movimientos que lo produjeron, es solo un número. Cuando está mal, no hay nada que puedas auditar para reconstruir el camino de vuelta.

No hay un segundo lado. Restar `$30` de una wallet no registra adónde fueron esos *$30*. El origen y el destino del dinero solo existen en la línea de código de la aplicación que se haya ejecutado, no en los datos centrales del sistema.

Y encima, no te protege en absoluto contra las condiciones de carrera que ocurrirán/podrían ocurrir. Dos peticiones leen `balance = 100` al mismo tiempo, cada una resta *30* y cada una escribe *70*. El dinero se movió dos veces y la columna seguirá pareciendo correcta.

## Los tres principios básicos

Un ledger de partida doble reemplaza ese número mutable por un registro de movimientos de solo adición, construido a partir de tres objetos.

1. Una cuenta: Es simplemente un fondo de valor independiente. La wallet de un usuario, el efectivo de la empresa, las comisiones que has cobrado, cada uno de ellos es una cuenta.

2. Una entrada: Ahora es un único débito o crédito contra una única cuenta. Una entrada SIEMPRE DEBE escribirse una sola vez y nunca modificarse ni borrarse. Cada una es un registro permanente de valor que entra o sale de una única cuenta *"inmutable"*.

3. Una transacción: Es un grupo de entradas que se mueven como una sola, todo o nada. Este es el objeto que mantiene unido todo tu sistema.

Las entradas no se escriben directamente. Escribes una transacción, y una transacción lleva sus entradas. La escritura en sí corre dentro de una sola txn de la db, así que es todo o nada, `tx, _ := db.Begin(); tx.Exec(...); tx.Commit()`. Por ejemplo, una transferencia de *$30* de *Alfred* a *Tobi* es una transacción con dos entradas, una que le quita a *Alfred* y otra que le da a *Tobi*, y o se confirman las dos o no se confirma ninguna. Nunca debería existir un estado válido en el que a *Alfred* se le haya debitado y a *Tobi* nunca se le haya acreditado. Esa agrupación atómica es la garantía.

## Débitos y créditos

Ahora viene la parte que confunde a bastante gente. Un débito no es una resta y un crédito no es una suma. Son simplemente las dos únicas direcciones que puede tomar un movimiento, y si una dirección concreta *aumenta ↑* o *disminuye ↓* una cuenta depende del tipo de cuenta en el que cae. Para que quede claro, simplemente vuelve a leer la sección "Los tres principios básicos"

Toda cuenta es de uno de un puñado de tipos posibles, como vimos arriba, y el tipo es lo que fija su saldo normal, el lado que hace que el saldo suba. Así que solo vas a tratar con dos. Primero, las cuentas de naturaleza deudora, activos y gastos. Un débito es lo que las aumenta, un crédito es lo que las disminuye. Después, las cuentas de naturaleza acreedora, pasivos, patrimonio e ingresos. Un crédito es lo que las aumenta, un débito es lo que las disminuye.

La diferencia queda clara en cuanto lo aplicas a un ejemplo de wallet. Los *$100* que un usuario tiene contigo no son tu dinero, es dinero que le debes, así que en tus libros es un pasivo, de naturaleza acreedora. Los dólares reales que guardas en su nombre son un activo, que es de naturaleza deudora.

Ahora un depósito tiene sentido. Por ejemplo, un usuario deposita *100 USDC*, y dos cosas pasan a ser ciertas a la vez: tienes *100 USDC* más, un activo que aumenta, lo cual es un débito, y le debes al usuario *100 USDC* más, un pasivo que aumenta, lo cual es un crédito. Ocurrió un movimiento, dos entradas, y cuadran.

![depósito de 100usdc dos entradas](https://i.ibb.co/BHc5VCQW/Screenshot-2026-06-30-at-11-44-10-AM.png)

Débito y crédito no son opuestos en conflicto. Son dos vistas de exactamente los mismos *$100*, registradas al mismo tiempo, en una sola transacción.

Desde una analogía con la física hay otra forma de verlo y entenderlo todo, que es la famosa tercera ley del movimiento de Newton *"A toda acción le corresponde una reacción igual y opuesta"*

En cualquier transacción, los débitos y los créditos tienen que salir iguales. Todas y cada una de las veces, cero excepciones. Σ débitos − Σ créditos = 0

Una fuerza nunca aparece sola, siempre llega con su gemela igual y opuesta. El valor en este escenario del ledger funciona exactamente igual. Nunca cae en una cuenta sin una entrada igual y opuesta en otra, y las dos mantienen los libros cuadrados.

Los *100 USDC* que debitas en tu custodia y los 100 USDC que acreditas al usuario son iguales en tamaño pero no son lo mismo, uno es un activo que ahora tienes, el otro es un pasivo que ahora debes. Así que aquí nada se mueve, sino que ambos lados crecen a la vez, un activo nuevo y un pasivo nuevo igual nacen juntos, y justo por eso los libros siguen cuadrados.

## La regla estricta

En cada transacción, el total de débitos debe ser igual al total de créditos. Una transacción que no cuadra no puede existir en ningún lado.

Esta regla es lo que convierte "nunca perder dinero" de una esperanza en un atributo clave del sistema en el que trabajas. El valor no puede salir de una cuenta sin llegar a otra, porque si no la transacción no cuadra. Tu custodia no debería desviarse de lo que les debes a tus usuarios, porque cada movimiento registra ambos lados a la vez. Cuando algo está mal, el ledger no te devuelve en silencio un saldo incorrecto, rechaza la escritura directamente.

En la práctica, el núcleo de un ledger es esta única comprobación. En este caso las cantidades se guardan como enteros no negativos en unidades menores (centavos, o wei), y la dirección la lleva un campo `side` en lugar de un signo:

```typescript
type Entry = { accountId: string; side: "DR" | "CR"; amountMinor: bigint; assetId: string };

function assertBalanced(entries: Entry[]) {
  if (entries.length === 0) throw new Error("empty txn");

  const net = new Map<string, bigint>();

  for (const e of entries) {
    if (e.amountMinor <= 0n) throw new Error(`bad amount: ${e.accountId} ${e.amountMinor}`);
    const delta = e.side === "DR" ? e.amountMinor : -e.amountMinor;
    net.set(e.assetId, (net.get(e.assetId) ?? 0n) + delta);
  }

  for (const [asset, balance] of net) {
    if (balance !== 0n) throw new Error(`unbalanced: ${asset} off by ${balance}`);
  }
}
```

Dos cosas ahí importan más de lo que parece. El saldo se comprueba *por activo*, así que una transacción que toca dólares y bitcoin tiene que cuadrar en cada uno por separado, una moneda nunca puede liquidar a otra en silencio. Y las cantidades son `bigint`, no de punto flotante, porque el punto flotante y el dinero son otra historia de terror para otro día.

Una cosa más en la que fijarse: en ninguna parte de ese fragmento se toca una columna `balance`. El código solo comprueba que las entradas cuadren, nunca escribe un saldo en ningún lado. Y ese es el punto de partida correcto, un saldo nunca es un hecho que fijas directamente. No escribes `balance = 500`. Sumas las entradas, `debits - credits`, interpretadas según el lado normal de la cuenta. Las entradas son la verdad. El saldo es solo una pregunta que le haces.

Y aquí hay otra cosa que se puede hacer mal. Que esté definido por sus entradas no significa que las vuelvas a sumar en cada lectura. Si haces eso, simplemente estás sumando todo el historial de una cuenta cada vez, eso es `O(n)`, y `n` no para de crecer. La cuenta que toca cada transacción, tu tesorería y demás, acabaría siendo la lectura más lenta. Así que no vuelves a sumar. Guardas el saldo, lo actualizas en el momento en que cae cada entrada, y lo mantienes sincronizado con el log. El log sigue siendo la fuente de verdad. El número guardado es en realidad solo una caché que tiene que coincidir con él. Derivado en significado, almacenado en la práctica, las dos cosas, no una u otra.

La misma idea en una imagen, una transacción que se abre en dos entradas cuadradas que caen en dos cuentas:

![dos entradas, dos cuentas](https://i.ibb.co/xKQv3d4S/Screenshot-2026-06-30-at-2-04-51-PM.png)

## Por qué esta es la base

Un sistema de partida simple te pide que confíes en un número. Un sistema de partida doble se niega a dejar que un número se sostenga por sí solo, un saldo no es algo que afirmas, es algo que las entradas obligan a que sea cierto. Funciona igual que un índice único, que vuelve imposibles los duplicados en lugar de dejarlos como algo que simplemente no deberías hacer, el conjunto de reglas de cuadre convierte perder dinero en una escritura que no se confirma en lugar de una diferencia que descubres meses o años después, con tu licencia en entredicho en una audiencia ante un panel. Esa es la razón por la que casi todos los sistemas financieros se construyen así.

Esta primera parte también es solo el esqueleto. Un ledger real tiene que manejar fondos que aún no se han liquidado, un depósito cripto esperando confirmaciones de bloque, un retiro que podría fallar y tener que revertirse al usuario que lo inició. En la Parte II, vamos a aplicar este modelo a movimientos reales, y veremos cómo un solo saldo se convierte en tres.
