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:
- FIFO Fiscal: ¿Qué lote de adquisición consume una venta? (Determina la ganancia o pérdida patrimonial tributable).
- 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
WITHDRAWALdesde Kraken a una Ledger fría cerraba un lote fiscal real como si se tratara de unSELL. - El
DEPOSITreceptor 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_timestampDetermina:Lote que consume una VENTA Determina:Saldo físico actual en cada cuentaEfecto: 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_timestampy suunit_cost_fiatintactos 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:
Estructura de la Tabla: lot_custody_entries
Sección titulada «Estructura de la Tabla: lot_custody_entries»| 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:
- Se añade un registro con
qty_delta: -0.5 BTCparaaccount_id: Kraken. - Se añade un registro con
qty_delta: +0.5 BTCparaaccount_id: Cold_Ledger. - El
tax_lotsubyacente (0.5 BTCcomprado el 15-01-2023 a 20.000 €/BTC) se conserva íntegro e inalterado.
🦆 Grafo de Vistas Vectorizadas en DuckDB
Sección titulada «🦆 Grafo de Vistas Vectorizadas en DuckDB»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.