Ir al contenido

FIFO Global vs. Ledger de Custodia por Partida Doble

⚖️ FIFO Global vs. Ledger de Custodia por Partida Doble

Sección titulada «⚖️ FIFO Global vs. Ledger de Custodia por Partida Doble»

En las herramientas de fiscalidad cripto, los motores de cálculo suelen confundir dos preguntas de naturaleza completamente distinta:

  1. FIFO Fiscal: ¿Qué lote de adquisición consume una venta? (Determina la ganancia o pérdida patrimonial tributable).
  2. Asignación de Custodia: ¿Qué cuenta o billetera física custodia el saldo ahora mismo?

Responder a ambas preguntas utilizando la misma estructura de datos genera errores matemáticos y fiscales graves. En esta página se explica cómo Criptofolio desacopló de forma permanente el consumo de lotes fiscales de la custodia por cuentas.


🚫 El Problema de las 73 Ganancias Fantasmas

Sección titulada «🚫 El Problema de las 73 Ganancias Fantasmas»

En versiones iniciales y en la mayoría de herramientas del mercado, las transferencias entre cuentas propias se interpretaban erróneamente como ventas y compras:

  • Un WITHDRAWAL desde Kraken a una Ledger fría cerraba un lote fiscal real como si se tratara de un SELL.
  • El DEPOSIT receptor abría un lote nuevo con coste de adquisición cero.

Medido sobre un conjunto de datos real, este defecto produjo:

  • 73 eventos de venta fantasma a partir de cero transacciones reales de venta (SELL).
  • Creación de 64 lotes con coste cero de 96, inflando artificialmente las ganancias patrimoniales en decenas de miles de euros.
  • Inversión de ganancias en pérdidas debido a incoherencias de signos entre diferentes exchanges.

⚖️ Dos Ordenaciones que Jamás Deben Fusionarse

Sección titulada «⚖️ Dos Ordenaciones que Jamás Deben Fusionarse»

La legislación fiscal española (IRPF de la AEAT) exige un criterio FIFO global por activo homogéneo: al vender un activo, se debe agotar el lote más antiguo adquirido de dicho activo, sin importar en qué cuenta se adquirió o desde qué billetera se realiza la venta.

Dividir los lotes para reflejar traslados entre billeteras alteraría su fecha original y violaría la ley tributaria.

FIFO FISCAL LEDGER DE CUSTODIA POR PARTIDA DOBLE
───────────────────────────────────── ─────────────────────────────────────────
Ámbito: Global por activo Ámbito: Por (Cuenta, Activo)
Orden: acquisition_timestamp Orden: acquisition_timestamp
Determina:Lote que consume una VENTA Determina:Saldo físico actual en cada cuenta
Efecto: Fiscal (Ganancia o Pérdida) Efecto: Ninguno (Trazabilidad interna)
Vista: v_fifo_matches (m_*) Vista: v_lot_custody_allocation

[!CAUTION] Invariante de Lote: Un lote de adquisición jamás se divide en nuevas filas al trasladarse entre cuentas. Una compra permanece como una única fila con su acquisition_timestamp y su unit_cost_fiat intactos para siempre. Las transferencias internas únicamente añaden apuntes al ledger de custodia.


📖 El Ledger de Custodia por Partida Doble

Sección titulada «📖 El Ledger de Custodia por Partida Doble»

En lugar de recurrir a heurísticas frágiles de emparejamiento por ventana temporal (que fallan con comisiones de red, demoras de confirmación o retiros escalonados), Criptofolio utiliza un ledger de custodia por partida doble:

Columna Tipo Descripción
id TEXT (UUID) Clave primaria única.
tax_lot_id TEXT (UUID) Clave foránea al lote inmutable de adquisición.
account_id TEXT (UUID) Identificador de la cuenta o billetera receptora/emisora.
qty_delta TEXT Delta numérico con signo (+ para depósitos, - para retiros).

Al transferir fondos desde Kraken a una Ledger fría:

  1. Se añade un registro con qty_delta: -0.5 BTC para account_id: Kraken.
  2. Se añade un registro con qty_delta: +0.5 BTC para account_id: Cold_Ledger.
  3. El tax_lot subyacente (0.5 BTC comprado el 15-01-2023 a 20.000 €/BTC) se conserva íntegro e inalterado.

El motor analítico compila ambas ordenaciones mediante un grafo acíclico dirigido (DAG) de vistas SQL:

flowchart TD
    Raw["ledger.spot_transactions"] --> Flattened["v_flattened_fifo_events"]
    Flattened --> Acq["v_acquisitions (Compras, Staking, Airdrops)"]
    Flattened --> Disp["v_disposals (Ventas, Swaps, Pagos)"]

    Acq & Disp --> FifoMatches["v_fifo_matches\n(Join acumulativo de solapamiento de intervalos)"]
    FifoMatches --> TaxLots["v_calculated_tax_lots\n(Materializado en m_calculated_tax_lots)"]

    TaxLots --> Custody["v_lot_custody_allocation\n(Distribución física por cuenta)"]

Gracias a que la cadena de vistas está materializada en tablas físicas (m_calculated_tax_lots), el sistema las reconstruye en un único proceso en lote tras la ingesta, en lugar de recalcular costosos joins en cada petición.