Skip to content

LFS_CONCEPTUAL_ARCHITECTURE.md

Git LFS: La Arquitectura de Dos Mundos

Filosofía y Pensamiento Detrás del Large File Storage


🎯 PROPÓSITO DE ESTE DOCUMENTO

Este documento explora la arquitectura conceptual de Git LFS, no como una herramienta técnica, sino como una solución filosófica a un problema ontológico: el choque entre la naturaleza de Git (diseñado para texto delta-comprimible) y la realidad de archivos grandes binarios (incompresibles, inmutables, voluminosos).

NO encontrarás aquí: - Comandos de instalación - Sintaxis de configuración - Procedimientos paso a paso

SÍ encontrarás: - La naturaleza del problema que LFS resuelve - La arquitectura conceptual de la solución de dos mundos - Patrones de pensamiento para clasificar assets - El ciclo de vida completo de un archivo LFS - Trade-offs y decisiones estratégicas - La estrategia específica para AudioLab


📖 ÍNDICE

CAPÍTULO 1: El Problema Ontológico del Tamaño

1.1 El Modelo Mental de Git: Todo es Delta 1.2 La Paradoja del Archivo Binario Grande 1.3 Taxonomía de Archivos: ¿Qué es "Grande"? 1.4 Síntomas de un Repositorio Enfermo

CAPÍTULO 2: Arquitectura de Dos Mundos

2.1 El Concepto del Pointer File 2.2 Content-Addressable Storage (CAS) Externo 2.3 El Triángulo de Actores: Local/Repo/Storage 2.4 Flujo de Datos en Push/Pull

CAPÍTULO 3: Patrones de Tracking

3.1 .gitattributes como Constitución 2.2 Matriz de Decisión: ¿Qué Trackear con LFS? 3.3 Patrones por Tipo de Proyecto 3.4 El Anti-Pattern del "Track Everything"

CAPÍTULO 4: Ciclo de Vida de un Asset

4.1 Las 7 Fases del Viaje 4.2 Visualización de Estados 4.3 Momento de Decisión: Commit vs Upload 4.4 Garbage Collection y Prune

CAPÍTULO 5: Trade-offs y Decisiones

5.1 Beneficios: Qué Ganas 5.2 Limitaciones: Qué Pierdes 5.3 Matriz de Costos 5.4 Cuándo NO Usar LFS

CAPÍTULO 6: Estrategia AudioLab

6.1 Clasificación de Assets en AudioLab 6.2 Umbrales y Políticas 6.3 Workflow de Audio Samples 6.4 Híbridos: Artifacts de Build

CAPÍTULO 7: Alternativas y Complementos

7.1 Git Annex: El Hermano Mayor 7.2 DVC: Data Version Control 7.3 Submodules: La Estrategia de Separación 7.4 Paisaje del Ecosistema


CAPÍTULO 1: El Problema Ontológico del Tamaño

1.1 El Modelo Mental de Git: Todo es Delta

Git fue diseñado con un supuesto fundamental sobre la naturaleza de los archivos que versiona:

┌─────────────────────────────────────────────────────────────┐
│  SUPUESTO FUNDAMENTAL DE GIT                                │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  Los archivos son MAYORMENTE TEXTO.                         │
│  El texto cambia por LÍNEAS.                                │
│  Las líneas cambian INCREMENTALMENTE.                       │
│  Los deltas son COMPRIMIBLES.                               │
│                                                             │
│  Por lo tanto:                                              │
│    Almacenar N versiones ≈ Almacenar 1 versión + deltas    │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Esto funciona BRILLANTEMENTE para código fuente:

Versión 1 del archivo "compressor.cpp":
┌──────────────────────────────┐
│ float compress(float input) {│  → 1.2 KB
│   return input * 0.5;        │
│ }                            │
└──────────────────────────────┘

Versión 2 del archivo (cambio menor):
┌──────────────────────────────┐
│ float compress(float input) {│  → 1.3 KB original
│   float ratio = 0.5;         │  → 0.2 KB delta
│   return input * ratio;      │
│ }                            │
└──────────────────────────────┘

Almacenamiento total: 1.2 KB + 0.2 KB = 1.4 KB

El modelo mental de Git:

        HISTORIA = SNAPSHOT INICIAL + CADENA DE TRANSFORMACIONES

     v1           v2              v3              v4
     ●────────────●───────────────●───────────────●
   [full]      [+delta]        [+delta]        [+delta]
   1.2 KB      +0.2 KB         +0.1 KB         +0.3 KB

   Total stored: 1.8 KB para 4 versiones completas
   Compression ratio: ~3x

Esta arquitectura asume que: - Los cambios son locales (algunas líneas) - Los deltas son pequeños comparados con el archivo completo - La compresión es efectiva (texto se comprime bien) - El historial es navegable (puedes reconstruir cualquier versión)


1.2 La Paradoja del Archivo Binario Grande

Ahora introduce un archivo binario grande en este modelo:

piano_sample.wav (50 MB)
┌─────────────────────────────────────────────────────────────┐
│  01001110 10101010 11001100 01010101 10101010 11110000 ...  │
│  [50 millones de bytes de datos de audio PCM]               │
└─────────────────────────────────────────────────────────────┘

Características del archivo:
  ✗ NO es texto
  ✗ NO tiene "líneas"
  ✗ Cambios NO son incrementales
  ✗ Datos NO son comprimibles (ya está en formato eficiente)

¿Qué pasa cuando haces un cambio "menor" al archivo?

CAMBIO: Ajustar el volumen del sample en 1 dB

Versión 1:                      Versión 2:
piano_sample.wav (50 MB)        piano_sample.wav (50 MB)
┌──────────────────────┐        ┌──────────────────────┐
│ [muestra 1]: 0.532   │        │ [muestra 1]: 0.597   │
│ [muestra 2]: 0.127   │        │ [muestra 2]: 0.142   │
│ [muestra 3]: -0.231  │  →     │ [muestra 3]: -0.259  │
│ ...                  │        │ ...                  │
│ [muestra N]: 0.891   │        │ [muestra N]: 1.000   │
└──────────────────────┘        └──────────────────────┘

CADA BYTE del archivo cambió (multiplicación por constante).
Delta size: ~50 MB
Original size: 50 MB

Compression ratio: 1x (ninguna compresión)

La paradoja:

┌─────────────────────────────────────────────────────────────┐
│  PARADOJA DEL ARCHIVO BINARIO                               │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  Cambio semántico: MINÚSCULO (+1 dB de volumen)            │
│  Cambio binario: MASIVO (50 MB completos)                   │
│                                                             │
│  Para Git, es como si hubieras reescrito el archivo         │
│  completo desde cero.                                       │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Resultado en el repositorio:

Commit 1: piano_sample.wav v1       → +50 MB
Commit 2: piano_sample.wav v2       → +50 MB
Commit 3: piano_sample.wav v3       → +50 MB
Commit 4: piano_sample.wav v4       → +50 MB
                                      ────────
Total repo size: 200 MB para 4 versiones de un solo archivo

Git no puede aplicar su modelo de deltas porque: 1. No hay estructura de líneas que referenciar 2. Los cambios son globales (afectan todo el archivo) 3. No hay compresión efectiva (datos ya optimizados) 4. Cada versión es opaca (binario incomprensible)


1.3 Taxonomía de Archivos: ¿Qué es "Grande"?

No todos los archivos son iguales. Necesitamos una taxonomía para clasificar:

┌─────────────────────────────────────────────────────────────────────┐
│  TAXONOMÍA DE ARCHIVOS POR NATURALEZA                               │
└─────────────────────────────────────────────────────────────────────┘

DIMENSIÓN 1: Tamaño
─────────────────────────────────────────────────────────────
  Tiny        Small       Medium      Large       Huge
  < 10 KB     < 100 KB    < 1 MB      < 100 MB    > 100 MB
  ●───────────●───────────●───────────●───────────●
  .cpp        .h/.json    .png/.wav   .mov/.psd   datasets

DIMENSIÓN 2: Formato
─────────────────────────────────────────────────────────────
  Texto Plano    Texto Estructurado    Binario Comprimido    Binario Raw
  ●──────────────●─────────────────────●─────────────────────●
  .cpp/.txt      .json/.xml/.md        .zip/.png/.mp3        .wav/.raw/.dng

DIMENSIÓN 3: Mutabilidad
─────────────────────────────────────────────────────────────
  Inmutable             Semi-mutable           Altamente mutable
  ●─────────────────────●──────────────────────●
  Assets finales        Trabajo en progreso    Código fuente
  (renders, releases)   (WIP mockups)          (.cpp, .h)

DIMENSIÓN 4: Comprimibilidad
─────────────────────────────────────────────────────────────
  Alta (>70%)           Media (30-70%)         Baja (<30%)
  ●─────────────────────●──────────────────────●
  Texto repetitivo      Código fuente          Audio/Video comprimido
  logs/XML              .cpp/.h                .mp3/.h264

La regla de oro para decidir si un archivo es "problemático" en Git:

┌─────────────────────────────────────────────────────────────┐
│  ALGORITMO DE DECISIÓN: ¿Es este archivo problemático?      │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  PREGUNTA 1: ¿Es binario? (no texto plano)                  │
│    NO  → Git nativo es perfecto                             │
│    SÍ  → Continúa...                                        │
│                                                             │
│  PREGUNTA 2: ¿Es > 1 MB?                                    │
│    NO  → Probablemente OK en Git nativo                     │
│    SÍ  → Continúa...                                        │
│                                                             │
│  PREGUNTA 3: ¿Cambiará frecuentemente?                      │
│    NO  → Borderline (depende de cantidad)                   │
│    SÍ  → CANDIDATO PARA LFS                                 │
│                                                             │
│  PREGUNTA 4: ¿Necesitas versionar TODAS las versiones?      │
│    NO  → Tal vez no pertenece al repo                       │
│    SÍ  → DEFINITIVAMENTE LFS                                │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Ejemplos clasificados:

ARCHIVO                    Binario  Tamaño   Mutable  Comprim  DECISIÓN
────────────────────────────────────────────────────────────────────────
main.cpp                   NO       5 KB     Alta     Alta     GIT NATIVO
config.json                NO       2 KB     Media    Alta     GIT NATIVO
logo.png                   SÍ       30 KB    Baja     N/A      GIT NATIVO
piano_sample.wav           SÍ       50 MB    Media    Baja     → LFS
video_tutorial.mp4         SÍ       200 MB   Baja     Baja     → LFS
dataset_training.zip       SÍ       2 GB     Baja     N/A      → LFS o externo
build/app.exe              SÍ       15 MB    N/A      Media    NO VERSIONAR
────────────────────────────────────────────────────────────────────────

1.4 Síntomas de un Repositorio Enfermo

¿Cómo saber si tu repositorio tiene "obesidad mórbida" por archivos grandes?

┌─────────────────────────────────────────────────────────────┐
│  LOS 7 SÍNTOMAS DEL REPOSITORIO ENFERMO                     │
└─────────────────────────────────────────────────────────────┘

SÍNTOMA 1: Clone Lento
─────────────────────────────────────────────────────────────
  $ git clone https://github.com/team/project.git
  Cloning into 'project'...
  remote: Counting objects: 1234, done.
  remote: Compressing objects: 100% (890/890), done.
  Receiving objects:  12% (148/1234), 2.3 GB | 1.2 MB/s    ← ☠️

  NORMAL: < 1 minuto para repo de código
  ENFERMO: > 10 minutos, varios GB de descarga

SÍNTOMA 2: Fetch Pesado
─────────────────────────────────────────────────────────────
  $ git fetch origin
  remote: Counting objects: 45, done.
  remote: Compressing objects: 100% (45/45), done.
  remote: Total 45 (delta 12), reused 0 (delta 0)
  Unpacking objects: 100% (45/45), 450 MB, done.  ← ☠️

  NORMAL: Fetch de cambios < 10 MB
  ENFERMO: Cada fetch descarga cientos de MB

SÍNTOMA 3: .git/ Gigante
─────────────────────────────────────────────────────────────
  $ du -sh .git/
  4.2G    .git/                                    ← ☠️

  $ du -sh .
  4.5G    .                                        ← ☠️

  La carpeta .git/ es MAYOR que el proyecto actual.

  NORMAL: .git/ es ~2-5x el tamaño del working tree
  ENFERMO: .git/ es 10-100x el tamaño del código

SÍNTOMA 4: Operaciones Lentas
─────────────────────────────────────────────────────────────
  $ git status
  [espera 30 segundos...]                          ← ☠️

  $ git log --oneline
  [espera 15 segundos...]                          ← ☠️

  NORMAL: Comandos instantáneos (< 1 segundo)
  ENFERMO: Cada operación requiere paciencia

SÍNTOMA 5: Push/Pull Dolorosos
─────────────────────────────────────────────────────────────
  $ git push origin main
  Counting objects: 234, done.
  Delta compression using up to 8 threads.
  Compressing objects: 100% (156/156), done.
  Writing objects:  23% (54/234), 1.2 GB | 800 KB/s  ← ☠️

  Un simple push de un commit tarda 20 minutos.

SÍNTOMA 6: CI/CD Timeout
─────────────────────────────────────────────────────────────
  GitHub Actions:
  ┌──────────────────────────────────────────┐
  │ ✗ Build failed: Timeout after 60 minutes │
  │   - Checkout: 45 minutes (!!!)           │  ← ☠️
  │   - Build: Not reached                   │
  └──────────────────────────────────────────┘

  El CI gasta TODO su tiempo clonando el repo.

SÍNTOMA 7: Historias de Horror del Equipo
─────────────────────────────────────────────────────────────
  "Dejé el git clone corriendo durante la noche"
  "No puedo trabajar desde el café, el WiFi no aguanta"
  "El nuevo en el equipo tardó 3 días en poder compilar"
  "Tuve que comprar un disco más grande solo para el repo"

La progresión de la enfermedad:

ETAPA 1: Asintomático
  ├─ Repo < 100 MB
  ├─ Clone en < 30 segundos
  └─ Nadie nota nada

ETAPA 2: Molestias Menores
  ├─ Repo ~ 500 MB
  ├─ Clone en 2-3 minutos
  └─ "El repo está un poco pesado, ¿no?"

ETAPA 3: Dolor Recurrente
  ├─ Repo ~ 2 GB
  ├─ Clone en 10-15 minutos
  └─ Quejas frecuentes, workarounds

ETAPA 4: Disfuncional
  ├─ Repo > 5 GB
  ├─ Clone > 30 minutos
  └─ Productividad severamente afectada

ETAPA 5: Terminal
  ├─ Repo > 20 GB
  ├─ Clone medido en horas
  └─ Considerando reescribir historia o migrar

Ejemplo real de AudioLab (antes de LFS):

Situación en Marzo 2024:
┌──────────────────────────────────────────────────────────┐
│ audio-lab/.git/objects/                                  │
├──────────────────────────────────────────────────────────┤
│                                                          │
│ test_samples/                         1.2 GB            │
│   ├─ piano_C4_v1.wav (50 MB) × 8 versiones              │
│   ├─ drum_kit_v1.wav (30 MB) × 12 versiones             │
│   └─ reverb_IR_v1.wav (100 MB) × 6 versiones            │
│                                                          │
│ docs/tutorial_videos/                 800 MB            │
│   └─ mixing_tutorial.mp4 (200 MB) × 4 versiones         │
│                                                          │
│ benchmarks/golden_outputs/            500 MB            │
│   └─ reference_mixes/ (varios archivos WAV grandes)     │
│                                                          │
│ Total .git/ size: 2.8 GB                                 │
│ Total working tree: 120 MB (código + configs)           │
│                                                          │
│ Ratio: .git/ es 23x más grande que el código actual     │
│                                                          │
└──────────────────────────────────────────────────────────┘

Impacto:
  - Clone inicial: 18 minutos en red universitaria
  - Fetch diario: 2-5 minutos (aunque no haya cambios grandes)
  - CI checkout: 12 minutos (60% del tiempo total de build)
  - New developer onboarding: "¿Por qué tarda tanto?"

Este es el dolor que LFS viene a resolver.


CAPÍTULO 2: Arquitectura de Dos Mundos

2.1 El Concepto del Pointer File

La idea central de LFS es separación de concerns:

┌─────────────────────────────────────────────────────────────┐
│  FILOSOFÍA CORE DE GIT LFS                                  │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  Git es excelente para METADATA y REFERENCIAS.              │
│  Git es terrible para BINARY BLOBS grandes.                 │
│                                                             │
│  Solución:                                                  │
│    - Git versiona un POINTER (metadata, 100 bytes)          │
│    - LFS almacena el CONTENIDO (blob, GB)                   │
│    - El usuario ve el archivo completo (transparencia)      │
│                                                             │
└─────────────────────────────────────────────────────────────┘

El pointer file es un archivo de texto pequeño que vive en el repositorio Git:

ARCHIVO REAL EN DISCO:
piano_sample.wav (50 MB de audio PCM)

ARCHIVO EN EL REPO GIT:
piano_sample.wav (130 bytes de texto)
┌──────────────────────────────────────────────────┐
│ version https://git-lfs.github.com/spec/v1      │
│ oid sha256:4d7a8f9b2c1e...a3f7c9d8e6b5a2c1    │
│ size 52428800                                    │
└──────────────────────────────────────────────────┘

Este texto es lo que Git versiona, hace diff, comprime.
El contenido real (50 MB) vive en un storage separado.

Anatomía del pointer:

┌─────────────────────────────────────────────────────────────┐
│  ESTRUCTURA DEL POINTER FILE                                │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  version https://git-lfs.github.com/spec/v1                 │
│  └─ Versión del spec de LFS (v1 es la actual)               │
│                                                             │
│  oid sha256:4d7a8f9b2c1e3a5f7b9d1c3e5a7f9b1d...            │
│  └─ Hash criptográfico del contenido                        │
│     (SHA-256, 64 caracteres hexadecimales)                  │
│     Sirve como:                                             │
│       - Identificador único del blob                        │
│       - Verificación de integridad                          │
│       - Key en el content-addressable storage               │
│                                                             │
│  size 52428800                                              │
│  └─ Tamaño en bytes del archivo real                        │
│     (útil para pre-allocación, verificación)                │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Ventajas del pointer:

  1. Deltas son baratos: Si el archivo no cambia, el pointer tampoco cambia. Delta = 0 bytes.
  2. Metadata es liviano: 130 bytes vs 50 MB en cada commit.
  3. Deduplicación automática: Mismo hash = mismo contenido = almacenar una sola vez.
  4. Verificación de integridad: El hash garantiza que el contenido es el correcto.

2.2 Content-Addressable Storage (CAS) Externo

El contenido real vive en un Content-Addressable Storage:

┌─────────────────────────────────────────────────────────────┐
│  CONTENT-ADDRESSABLE STORAGE (CAS)                          │
└─────────────────────────────────────────────────────────────┘

CONCEPTO:
  En lugar de nombrar archivos por su PATH (ej: /samples/piano.wav),
  los nombras por su CONTENIDO (el hash SHA-256).

ESTRUCTURA:
  .git/lfs/objects/
  ├─ 4d/
  │  └─ 7a/
  │     └─ 4d7a8f9b2c1e3a5f7b9d1c3e5a7f9b1d...  (50 MB)
  ├─ a3/
  │  └─ f7/
  │     └─ a3f7c9d8e6b5a2c1f4e8d7b6a5c4f3e2...  (30 MB)
  └─ ...

PROPIEDADES:
  1. DEDUPLICACIÓN: Mismo contenido = mismo hash = un solo archivo
  2. INMUTABILIDAD: El hash cambia si el contenido cambia
  3. VERIFICACIÓN: Puedes verificar integridad recalculando el hash
  4. DISTRIBUCIÓN: Fácil de distribuir/cachear/replicar

Comparación con el modelo tradicional:

MODELO TRADICIONAL (Path-based):
─────────────────────────────────────────────────────────────
  filesystem/
  ├─ samples/
  │  ├─ piano.wav      ← Nombre arbitrario
  │  └─ drum.wav       ← Podría estar corrupto sin saberlo
  └─ tests/
     └─ piano_test.wav ← Podría ser duplicado de piano.wav

  Problemas:
    - Duplicados no detectados
    - Corrupción no detectada
    - Nombres cambian, contenido puede ser el mismo

MODELO CAS (Content-based):
─────────────────────────────────────────────────────────────
  .git/lfs/objects/
  ├─ 4d7a.../         ← Hash del contenido de piano.wav
  └─ a3f7.../         ← Hash del contenido de drum.wav

  Si piano_test.wav tiene el mismo contenido que piano.wav:
    → Mismo hash
    → NO se almacena dos veces
    → Deduplicación automática

  Si un archivo se corrompe:
    → Hash no coincide
    → Detectado instantáneamente

Visualización del flujo:

WRITE (git add piano.wav):

  1. Calcular hash del contenido
     piano.wav (50 MB) → SHA256 → 4d7a8f9b2c1e...

  2. Almacenar en CAS
     .git/lfs/objects/4d/7a/4d7a8f9b2c1e... ← 50 MB guardado

  3. Crear pointer file
     piano.wav (en staging) ← 130 bytes de metadata

READ (git checkout branch-with-piano):

  1. Leer pointer file
     piano.wav → version v1, oid 4d7a8f9b2c1e..., size 50MB

  2. Buscar en CAS local
     .git/lfs/objects/4d/7a/4d7a8f9b2c1e... ← ¿Existe?

  3. Si existe: Copiar a working directory
     Si NO existe: Descargar desde LFS server

  4. Usuario ve el archivo completo
     piano.wav (50 MB en working directory)

2.3 El Triángulo de Actores: Local/Repo/Storage

LFS introduce un tercer actor en el modelo Git:

┌─────────────────────────────────────────────────────────────┐
│  EL TRIÁNGULO DE GIT LFS                                    │
└─────────────────────────────────────────────────────────────┘

      ┌─────────────────────┐
      │   LFS SERVER        │
      │  (Blob Storage)     │
      │                     │
      │  • GitHub LFS       │
      │  • GitLab LFS       │
      │  • Self-hosted      │
      │  • S3/Azure/GCS     │
      └─────────────────────┘
              ▲     ▲
              │     │
         PUT  │     │  GET
        (upload)   (download)
              │     │
              │     └──────────────────┐
              │                        │
      ┌───────┴────────┐      ┌────────┴────────┐
      │   GIT REPO     │      │   LOCAL CACHE   │
      │  (Pointers)    │◄────►│  (.git/lfs/)    │
      │                │      │                 │
      │  • GitHub      │ PULL │  • Objects/     │
      │  • GitLab      │ PUSH │  • Tmp/         │
      │  • Bitbucket   │      │                 │
      └────────────────┘      └─────────────────┘
              ▲                        ▲
              │                        │
           metadata               full content
           (pointers)             (blobs)
              │                        │
              └────────┬───────────────┘
              ┌────────┴────────┐
              │ WORKING DIR     │
              │  (Your files)   │
              │                 │
              │  piano.wav      │
              │  drum.wav       │
              └─────────────────┘

Roles de cada actor:

1. WORKING DIRECTORY (Tu espacio de trabajo)
─────────────────────────────────────────────────────────────
  Rol: Donde trabajas día a día
  Contiene: Archivos completos (50 MB piano.wav)
  Ignorante de: Si el archivo es LFS o no

  Desde tu perspectiva:
    - Editas piano.wav en tu DAW
    - Guardas cambios
    - git add piano.wav
    - Todo parece normal

2. GIT REPOSITORY (Control de versiones)
─────────────────────────────────────────────────────────────
  Rol: Versionar la ESTRUCTURA y METADATA
  Contiene: Pointer files (130 bytes)
  Almacena: Historia de QUÉ archivos, CUÁNDO, POR QUIÉN

  En cada commit:
    commit abc123
      piano.wav → pointer (oid: 4d7a..., size: 50MB)
      drum.wav  → pointer (oid: a3f7..., size: 30MB)

  Permanece liviano, rápido, clonable.

3. LFS SERVER (Almacenamiento de blobs)
─────────────────────────────────────────────────────────────
  Rol: Almacenar CONTENIDO binario grande
  Contiene: Los archivos reales (50 MB, 30 MB, etc.)
  Organización: Por hash (content-addressable)

  Estructura:
    4d7a8f9b2c1e... → [50 MB de datos de piano]
    a3f7c9d8e6b5... → [30 MB de datos de drum]

  Puede estar en:
    - GitHub LFS (incluido en plan)
    - GitLab LFS (incluido)
    - AWS S3 (self-hosted)
    - Azure Blob Storage
    - Google Cloud Storage

4. LOCAL LFS CACHE (.git/lfs/objects/)
─────────────────────────────────────────────────────────────
  Rol: Cache local de blobs descargados
  Contiene: Copias de blobs del LFS server
  Propósito: Evitar re-descargar, acelerar checkouts

  Es como un proxy local:
    - Primer checkout: Descarga desde LFS server
    - Siguientes checkouts: Copia desde cache local

2.4 Flujo de Datos en Push/Pull

El flujo completo de datos en operaciones Git:

┌─────────────────────────────────────────────────────────────┐
│  FLUJO: GIT PUSH CON LFS                                    │
└─────────────────────────────────────────────────────────────┘

1. USUARIO HACE CAMBIOS
   ┌────────────────┐
   │ Working Dir    │
   │ piano.wav      │  ← Editaste el archivo (ahora 51 MB)
   └────────────────┘

2. GIT ADD
   ┌────────────────┐
   │ $ git add piano.wav                                      │
   └────────────────┘

   LFS Hook intercepta:
     ✓ Detecta que piano.wav está en .gitattributes
     ✓ Calcula nuevo hash: 9f3e2a...
     ✓ Almacena blob en .git/lfs/objects/9f/3e/9f3e2a...
     ✓ Crea nuevo pointer file en staging area

   Estado:
   ┌────────────────┐
   │ Staging Area   │
   │ piano.wav      │  ← Pointer (oid: 9f3e2a..., size: 51MB)
   └────────────────┘

   ┌────────────────┐
   │ Local LFS      │
   │ 9f3e2a...      │  ← Blob de 51 MB
   └────────────────┘

3. GIT COMMIT
   ┌────────────────┐
   │ $ git commit -m "Increase piano volume"                 │
   └────────────────┘

   Git guarda:
     commit def456
       parent: abc123
       piano.wav → pointer (oid: 9f3e2a...)

   Solo el pointer entra en el commit (130 bytes).

4. GIT PUSH
   ┌────────────────┐
   │ $ git push origin main                                  │
   └────────────────┘

   FASE 1: Push Git metadata
     → Git server recibe commits
     → Git server recibe pointer files
     → Rápido (solo pointers)

   FASE 2: Push LFS blobs
     → LFS client escanea commits pusheados
     → Encuentra oid: 9f3e2a... (no existe en server)
     → Sube blob a LFS server

   Flujo de datos:

   Local LFS          →  Internet  →  LFS Server
   9f3e2a... (51 MB)  →   [████]   →  9f3e2a... (51 MB)

   Progreso:
     Uploading LFS objects: 100% (1/1), 51 MB | 2.3 MB/s
┌─────────────────────────────────────────────────────────────┐
│  FLUJO: GIT PULL CON LFS                                    │
└─────────────────────────────────────────────────────────────┘

1. GIT PULL
   ┌────────────────┐
   │ $ git pull origin main                                  │
   └────────────────┘

   FASE 1: Pull Git metadata
     → Git client descarga nuevos commits
     → Git client descarga nuevos pointer files
     → Rápido (solo metadata)

   Estado en .git/:
     commit def456
       piano.wav → pointer (oid: 9f3e2a..., size: 51MB)

2. GIT CHECKOUT (AUTOMÁTICO TRAS PULL)

   Git LFS Hook intercepta:
     ✓ Lee pointer file de piano.wav
     ✓ Extrae oid: 9f3e2a...
     ✓ Busca en local cache (.git/lfs/objects/)

   CASO A: Blob existe en cache local
   ─────────────────────────────────────────────────────────
     ✓ Copia de cache a working directory
     ✓ Instantáneo, sin red

     .git/lfs/objects/9f/3e/9f3e2a...  →  piano.wav (51 MB)

   CASO B: Blob NO existe en cache local
   ─────────────────────────────────────────────────────────
     ✓ Descarga desde LFS server
     ✓ Guarda en cache local
     ✓ Copia a working directory

     LFS Server  →  .git/lfs/objects/  →  piano.wav
        51 MB           51 MB               51 MB

     Progreso:
       Downloading LFS objects: 100% (1/1), 51 MB | 3.1 MB/s

3. RESULTADO
   ┌────────────────┐
   │ Working Dir    │
   │ piano.wav      │  ← 51 MB, listo para usar
   └────────────────┘

   Usuario ve archivo completo, transparentemente.

Optimización: Shallow Clone

LFS permite clonar sin TODO el historial de blobs:

CLONE NORMAL:
  $ git clone https://github.com/team/audio-lab.git

  Descarga:
    - Todo el historial de commits (metadata): 50 MB
    - TODOS los blobs LFS de TODAS las versiones: 5 GB

  Total download: 5.05 GB
  Tiempo: 30 minutos

CLONE CON LFS SHALLOW:
  $ git lfs install
  $ git config lfs.fetchinclude "main"
  $ git clone https://github.com/team/audio-lab.git

  Descarga:
    - Todo el historial de commits (metadata): 50 MB
    - SOLO blobs de la rama actual, última versión: 200 MB

  Total download: 250 MB
  Tiempo: 3 minutos

  Los blobs antiguos se descargan on-demand si checkouteas
  commits viejos.

CAPÍTULO 3: Patrones de Tracking

3.1 .gitattributes como Constitución

El archivo .gitattributes define QUÉ archivos son trackeados con LFS:

┌─────────────────────────────────────────────────────────────┐
│  .gitattributes: LA CONSTITUCIÓN DEL REPOSITORIO            │
└─────────────────────────────────────────────────────────────┘

CONCEPTO:
  .gitattributes es un archivo de configuración que vive en el repo.
  Define REGLAS sobre cómo Git debe tratar ciertos archivos.

  Para LFS, define: "Estos patrones de archivos usan LFS".

SINTAXIS:
  pattern filter=lfs diff=lfs merge=lfs -text

  pattern: Glob pattern (*.wav, *.mp4, assets/*.psd)
  filter=lfs: Usa el filtro LFS para clean/smudge
  diff=lfs: Usa diff especial de LFS
  merge=lfs: Usa merge especial de LFS
  -text: Marca explícitamente como binario (no texto)

Ejemplo de .gitattributes para AudioLab:

# .gitattributes para audio-lab

# ======================================
# AUDIO SAMPLES (siempre LFS)
# ======================================
*.wav filter=lfs diff=lfs merge=lfs -text
*.aiff filter=lfs diff=lfs merge=lfs -text
*.flac filter=lfs diff=lfs merge=lfs -text
*.mp3 filter=lfs diff=lfs merge=lfs -text
*.ogg filter=lfs diff=lfs merge=lfs -text

# ======================================
# IMPULSE RESPONSES (LFS)
# ======================================
**/impulse_responses/*.wav filter=lfs diff=lfs merge=lfs -text
**/IR/*.wav filter=lfs diff=lfs merge=lfs -text

# ======================================
# VIDEO TUTORIALS (LFS)
# ======================================
*.mp4 filter=lfs diff=lfs merge=lfs -text
*.mov filter=lfs diff=lfs merge=lfs -text
*.avi filter=lfs diff=lfs merge=lfs -text

# ======================================
# IMÁGENES GRANDES (solo si > 1MB)
# ======================================
# PNGs pequeños (icons) → Git nativo
# PNGs grandes (mockups) → LFS
docs/images/*.png filter=lfs diff=lfs merge=lfs -text
docs/mockups/*.psd filter=lfs diff=lfs merge=lfs -text

# ======================================
# DATASETS DE PRUEBA (LFS)
# ======================================
tests/golden_outputs/*.wav filter=lfs diff=lfs merge=lfs -text
benchmarks/reference_data/*.bin filter=lfs diff=lfs merge=lfs -text

# ======================================
# BINARIOS COMPILADOS (NO LFS, .gitignore)
# ======================================
# Estos NO deben versionarse en absoluto
# Están en .gitignore, no aquí

# ======================================
# ARCHIVOS DE TEXTO (Git nativo)
# ======================================
# Por defecto, todo lo demás usa Git nativo:
#   *.cpp, *.h, *.json, *.md, etc.

Filosofía del archivo:

┌─────────────────────────────────────────────────────────────┐
│  PRINCIPIOS PARA .gitattributes                             │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  1. EXPLÍCITO SOBRE IMPLÍCITO                               │
│     - Define TODOS los tipos de archivos grandes            │
│     - No dejes que Git "adivine"                            │
│                                                             │
│  2. GRANULARIDAD APROPIADA                                  │
│     - Wildcards para tipos comunes: *.wav                   │
│     - Paths específicos para casos especiales               │
│                                                             │
│  3. COMENTARIOS GENEROSOS                                   │
│     - Explica POR QUÉ cada patrón está aquí                 │
│     - Ejemplos de qué archivos matchean                     │
│                                                             │
│  4. CONSERVADOR EN EXPANSIÓN                                │
│     - Añade tipos gradualmente                              │
│     - No hagas *.* filter=lfs                               │
│                                                             │
│  5. VERSIONADO                                              │
│     - .gitattributes está en Git                            │
│     - Cambios son versionados y compartidos                 │
│     - Todo el equipo usa las mismas reglas                  │
│                                                             │
└─────────────────────────────────────────────────────────────┘

3.2 Matriz de Decisión: ¿Qué Trackear con LFS?

No todo archivo grande debe ir a LFS. Matriz de decisión:

┌─────────────────────────────────────────────────────────────────────┐
│  MATRIZ DE DECISIÓN: GIT NATIVO vs LFS vs NO VERSIONAR             │
└─────────────────────────────────────────────────────────────────────┘

TIPO DE ARCHIVO         Tamaño   Muta   Binario   DECISIÓN        RAZÓN
──────────────────────────────────────────────────────────────────────
Código fuente (.cpp)    < 100KB   Alta    NO       GIT NATIVO    Diseñado para esto
Configs (.json)         < 10KB    Media   NO       GIT NATIVO    Texto, pequeño
Headers (.h)            < 50KB    Alta    NO       GIT NATIVO    Parte del código
Markdown docs (.md)     < 500KB   Media   NO       GIT NATIVO    Texto, comprimible
──────────────────────────────────────────────────────────────────────
Audio samples (.wav)    > 10MB    Baja    SÍ       LFS           Binario grande
Video tutorials (.mp4)  > 50MB    Baja    SÍ       LFS           Muy grande
Impulse responses (.wav)> 1MB     Baja    SÍ       LFS           Audio de referencia
Photoshop mockups (.psd)> 5MB     Media   SÍ       LFS           Diseño versionado
──────────────────────────────────────────────────────────────────────
Binarios build (.exe)   > 10MB    N/A     SÍ       NO VERSIONAR  Reproducibles
Dependencies (.dll)     > 1MB     N/A     SÍ       NO VERSIONAR  Gestionados por pkg mgr
Temp files (.tmp)       Var       N/A     Var      NO VERSIONAR  Temporales
Logs (.log)             Var       N/A     NO       NO VERSIONAR  Efímeros
──────────────────────────────────────────────────────────────────────
Test data pequeños      < 1MB     Baja    SÍ       GIT NATIVO    Aceptable en repo
Test data grandes       > 10MB    Baja    SÍ       LFS           Demasiado grandes
Golden outputs          > 5MB     Baja    SÍ       LFS           Referencias estables
──────────────────────────────────────────────────────────────────────
Icons (.png)            < 50KB    Baja    SÍ       GIT NATIVO    Pequeños, útiles
Screenshots grandes     > 2MB     Baja    SÍ       LFS           Documentación visual
Datasets ML             > 100MB   Baja    SÍ       EXTERNO       Demasiado grandes para repo
──────────────────────────────────────────────────────────────────────

Árbol de decisión visual:

                        ¿Debe estar en el repo?
                    ┌───────────┴───────────┐
                   NO                      SÍ
                    │                       │
              .gitignore          ¿Es archivo de texto?
                                ┌───────────┴───────────┐
                               SÍ                      NO
                                │                       │
                          GIT NATIVO                ¿Es > 1 MB?
                                             ┌───────────┴───────────┐
                                            NO                      SÍ
                                             │                       │
                                       GIT NATIVO              ¿Cambia frecuentemente?
                                       (binario pequeño)              │
                                                          ┌───────────┴───────────┐
                                                        NO/Rara vez            SÍ/A veces
                                                          │                       │
                                                         LFS                  ¿Es > 100 MB?
                                                    (asset estable)                │
                                                                       ┌───────────┴───────────┐
                                                                     NO                      SÍ
                                                                      │                       │
                                                                     LFS               Considerar EXTERNO
                                                                (asset grande)         (dataset, storage)

3.3 Patrones por Tipo de Proyecto

Diferentes proyectos necesitan diferentes configuraciones LFS:

┌─────────────────────────────────────────────────────────────┐
│  PATRÓN 1: PROYECTO DE AUDIO/MÚSICA (como AudioLab)        │
└─────────────────────────────────────────────────────────────┘

Características:
  - Audio samples de referencia (WAV, AIFF)
  - Impulse responses para reverb
  - Golden outputs de tests
  - Documentación con videos

.gitattributes:
  *.wav filter=lfs diff=lfs merge=lfs -text
  *.aiff filter=lfs diff=lfs merge=lfs -text
  *.flac filter=lfs diff=lfs merge=lfs -text
  **/impulse_responses/** filter=lfs diff=lfs merge=lfs -text
  tests/golden_outputs/*.wav filter=lfs diff=lfs merge=lfs -text
  docs/videos/*.mp4 filter=lfs diff=lfs merge=lfs -text

Tamaño típico:
  - Código: 50-100 MB
  - LFS assets: 500 MB - 2 GB
  - Ratio: 10:1

┌─────────────────────────────────────────────────────────────┐
│  PATRÓN 2: GAME DEVELOPMENT                                 │
└─────────────────────────────────────────────────────────────┘

Características:
  - Texturas (PNG, DDS, PSD)
  - Modelos 3D (FBX, OBJ)
  - Audio (WAV, OGG)
  - Videos de cutscenes

.gitattributes:
  *.psd filter=lfs diff=lfs merge=lfs -text
  *.png filter=lfs diff=lfs merge=lfs -text
  *.dds filter=lfs diff=lfs merge=lfs -text
  *.fbx filter=lfs diff=lfs merge=lfs -text
  *.obj filter=lfs diff=lfs merge=lfs -text
  *.wav filter=lfs diff=lfs merge=lfs -text
  *.ogg filter=lfs diff=lfs merge=lfs -text
  *.mp4 filter=lfs diff=lfs merge=lfs -text

Tamaño típico:
  - Código: 100-500 MB
  - LFS assets: 5-50 GB
  - Ratio: 50:1

┌─────────────────────────────────────────────────────────────┐
│  PATRÓN 3: MACHINE LEARNING                                 │
└─────────────────────────────────────────────────────────────┘

Características:
  - Modelos entrenados (H5, ONNX)
  - Datasets pequeños de test
  - Notebooks con outputs
  - (Datasets grandes → storage externo)

.gitattributes:
  *.h5 filter=lfs diff=lfs merge=lfs -text
  *.onnx filter=lfs diff=lfs merge=lfs -text
  *.pkl filter=lfs diff=lfs merge=lfs -text
  tests/data/*.npy filter=lfs diff=lfs merge=lfs -text
  docs/plots/*.png filter=lfs diff=lfs merge=lfs -text

Tamaño típico:
  - Código: 10-50 MB
  - LFS assets: 100-500 MB
  - Datasets grandes: EXTERNO (S3, DVC)

┌─────────────────────────────────────────────────────────────┐
│  PATRÓN 4: DISEÑO WEB/MOBILE                                │
└─────────────────────────────────────────────────────────────┘

Características:
  - Mockups de diseño (PSD, Sketch)
  - Assets gráficos (PNG, SVG)
  - Fuentes (TTF, WOFF)
  - Videos de demos

.gitattributes:
  *.psd filter=lfs diff=lfs merge=lfs -text
  *.sketch filter=lfs diff=lfs merge=lfs -text
  design/mockups/*.png filter=lfs diff=lfs merge=lfs -text
  *.ttf filter=lfs diff=lfs merge=lfs -text
  *.woff filter=lfs diff=lfs merge=lfs -text
  demos/*.mp4 filter=lfs diff=lfs merge=lfs -text

Tamaño típico:
  - Código: 20-100 MB
  - LFS assets: 200-800 MB
  - Ratio: 5:1

3.4 El Anti-Pattern del "Track Everything"

Error común: Poner demasiado en LFS.

┌─────────────────────────────────────────────────────────────┐
│  ⚠️  ANTI-PATTERN: TRACK EVERYTHING                         │
└─────────────────────────────────────────────────────────────┘

.gitattributes MALO:
  * filter=lfs diff=lfs merge=lfs -text

  "Pongamos TODO en LFS para estar seguros"

CONSECUENCIAS:
  ✗ Archivos de texto (código) van a LFS
  ✗ Pierdes diffs legibles de código
  ✗ Pierdes merge inteligente
  ✗ Incrementas costo de LFS storage innecesariamente
  ✗ Operaciones se vuelven más lentas
  ✗ No puedes hacer grep en el historial de código

EJEMPLO REAL:
  Proyecto que puso *.json en LFS
  → Configs pequeños (2-5 KB) fueron a LFS
  → Diffs mostraban "pointer changed" en lugar del JSON
  → Debugging de cambios de config se volvió imposible
  → Tuvieron que remover de LFS y reescribir historia

Regla de oro:

┌─────────────────────────────────────────────────────────────┐
│  REGLA DE ORO                                               │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  Usa LFS SOLO cuando:                                       │
│    1. El archivo es binario (no texto plano)                │
│    2. El archivo es > 1 MB (o va a crecer)                  │
│    3. El archivo DEBE estar versionado (no build artifact)  │
│                                                             │
│  Si cumple las 3 condiciones → LFS                          │
│  Si falta alguna → Re-evaluar                               │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Migración gradual:

Si ya tienes un repo con archivos grandes en Git nativo:

ESTRATEGIA DE MIGRACIÓN:

  PASO 1: Identificar archivos problemáticos
    $ git rev-list --objects --all | \
      git cat-file --batch-check='%(objectsize) %(objectname) %(rest)' | \
      sort -rn | head -20

    Encuentra los 20 archivos más grandes en la historia.

  PASO 2: Decidir qué migrar
    - Archivos > 10 MB → Candidatos fuertes
    - Archivos 1-10 MB → Evaluar caso por caso
    - Archivos < 1 MB → Dejar en Git nativo

  PASO 3: Migrar gradualmente
    - NO reescribas toda la historia de una vez
    - Migra hacia adelante: nuevos archivos grandes → LFS
    - Opcionalmente, BFG Repo-Cleaner para limpiar historia

  PASO 4: Actualizar .gitattributes
    - Define patrones para futuros archivos
    - Comunica al equipo el cambio

CAPÍTULO 4: Ciclo de Vida de un Asset

4.1 Las 7 Fases del Viaje

Un archivo LFS pasa por múltiples fases desde creación hasta eventual eliminación:

┌─────────────────────────────────────────────────────────────┐
│  LAS 7 FASES DEL CICLO DE VIDA LFS                          │
└─────────────────────────────────────────────────────────────┘

FASE 1: CREACIÓN
  Usuario crea el archivo en working directory

FASE 2: STAGING
  git add → Archivo entra en staging area como pointer

FASE 3: COMMIT
  git commit → Pointer se guarda en Git, blob en cache local

FASE 4: PUSH
  git push → Blob se sube a LFS server

FASE 5: DISTRIBUCIÓN
  Otros clones descargan pointer (Git) y blob (LFS) on-demand

FASE 6: ACTUALIZACIÓN
  Archivo se modifica → Nuevo blob, nuevo pointer

FASE 7: GARBAGE COLLECTION
  Blobs antiguos se limpian del cache local (opcional)

Visualización detallada:

FASE 1: CREACIÓN
═══════════════════════════════════════════════════════════════
  User Action:
    - Graba un nuevo piano sample en Pro Tools
    - Exporta como piano_C4.wav (50 MB)
    - Lo guarda en samples/

  Estado del sistema:
    Working Dir:
      samples/piano_C4.wav (50 MB, sin trackear)

    Git:
      (no sabe nada aún)

    LFS:
      (no sabe nada aún)

FASE 2: STAGING (git add samples/piano_C4.wav)
═══════════════════════════════════════════════════════════════
  LFS Filter (Clean):
    1. Lee piano_C4.wav (50 MB)
    2. Calcula SHA-256: 4d7a8f9b2c1e3a5f...
    3. Almacena blob en .git/lfs/objects/4d/7a/4d7a8f9b2c1e...
    4. Genera pointer file:
       version https://git-lfs.github.com/spec/v1
       oid sha256:4d7a8f9b2c1e3a5f...
       size 52428800
    5. Pasa pointer a Git staging

  Estado del sistema:
    Working Dir:
      samples/piano_C4.wav (50 MB)

    Staging Area:
      samples/piano_C4.wav (130 bytes, pointer)

    .git/lfs/objects/:
      4d/7a/4d7a8f9b2c1e... (50 MB)

FASE 3: COMMIT (git commit -m "Add piano C4 sample")
═══════════════════════════════════════════════════════════════
  Git guarda el pointer en el commit tree.
  El blob permanece en .git/lfs/objects/ (cache local).

  Estado del sistema:
    Commit abc123:
      samples/piano_C4.wav → pointer (oid: 4d7a...)

    .git/lfs/objects/:
      4d/7a/4d7a8f9b2c1e... (50 MB)

    LFS Server:
      (aún no tiene el blob)

FASE 4: PUSH (git push origin main)
═══════════════════════════════════════════════════════════════
  Proceso en 2 pasos:

  PASO 1: Git push (metadata)
    → Sube commit abc123 a Git server
    → Sube pointer file
    → Rápido (pocos KB)

  PASO 2: LFS push (blobs)
    → LFS client identifica oid: 4d7a... no existe en server
    → Sube blob de 50 MB a LFS server
    → Progreso: Uploading LFS objects: 100% (1/1), 50 MB

  Estado del sistema:
    Git Server:
      commit abc123
        samples/piano_C4.wav → pointer (oid: 4d7a...)

    LFS Server:
      4d7a8f9b2c1e... → [50 MB blob]

    Local:
      .git/lfs/objects/4d/7a/4d7a8f9b2c1e... (50 MB)

FASE 5: DISTRIBUCIÓN (Otro developer: git clone)
═══════════════════════════════════════════════════════════════
  Developer B clona el repositorio:

  PASO 1: Git clone (metadata)
    → Descarga todo el historial Git
    → Descarga pointer files

  PASO 2: LFS checkout (blobs)
    → LFS client lee pointers de la rama actual
    → Descarga blobs desde LFS server
    → Almacena en cache local
    → Copia a working directory

  Estado en máquina de Developer B:
    Working Dir:
      samples/piano_C4.wav (50 MB)

    .git/:
      commit abc123 (con pointer)

    .git/lfs/objects/:
      4d/7a/4d7a8f9b2c1e... (50 MB)

FASE 6: ACTUALIZACIÓN (Developer B edita el archivo)
═══════════════════════════════════════════════════════════════
  Developer B ajusta el volumen del piano:
    - Abre piano_C4.wav en DAW
    - Aumenta volumen en 2 dB
    - Guarda (ahora 50.1 MB por metadata)

  git add samples/piano_C4.wav:
    → Nuevo hash: 9f3e2a1b4c5d...
    → Nuevo blob en .git/lfs/objects/
    → Nuevo pointer

  git commit -m "Increase piano volume":
    → Commit def456
    → piano_C4.wav → pointer (oid: 9f3e...)

  Estado:
    .git/lfs/objects/:
      4d/7a/4d7a8f9b2c1e... (50 MB, versión antigua)
      9f/3e/9f3e2a1b4c5d... (50.1 MB, versión nueva)

    Ambos blobs coexisten en cache local.

FASE 7: GARBAGE COLLECTION (git lfs prune)
═══════════════════════════════════════════════════════════════
  Con el tiempo, acumulas muchos blobs en .git/lfs/objects/.
  Puedes limpiar blobs que:
    - No están en la rama actual
    - No están en commits recientes
    - Ya están en LFS server

  $ git lfs prune
    → Escanea referencias
    → Identifica blob 4d7a... no está en HEAD
    → Verifica que está en LFS server
    → ELIMINA .git/lfs/objects/4d/7a/4d7a8f9b2c1e...

  Estado después de prune:
    .git/lfs/objects/:
      9f/3e/9f3e2a1b4c5d... (50.1 MB)

    El blob antiguo se eliminó del cache local.
    PERO sigue en LFS server (recuperable).

4.2 Visualización de Estados

Diagrama de estado completo:

┌─────────────────────────────────────────────────────────────┐
│  DIAGRAMA DE ESTADOS DE UN ARCHIVO LFS                      │
└─────────────────────────────────────────────────────────────┘

    ┌──────────────┐
    │  UNTRACKED   │  Archivo existe en working dir
    │  (new file)  │  Git no lo conoce
    └──────┬───────┘
           │ git add
    ┌──────────────┐
    │   STAGED     │  Pointer en staging area
    │  (to commit) │  Blob en .git/lfs/objects/
    └──────┬───────┘
           │ git commit
    ┌──────────────┐
    │  COMMITTED   │  Pointer en Git tree
    │   (local)    │  Blob en local cache
    └──────┬───────┘
           │ git push
    ┌──────────────┐
    │  PUBLISHED   │  Pointer en Git server
    │  (remote)    │  Blob en LFS server
    └──────┬───────┘
           ├─────────────┐
           │             │
           ▼             ▼
    ┌──────────┐   ┌──────────┐
    │ MODIFIED │   │ DELETED  │
    │          │   │          │
    └────┬─────┘   └────┬─────┘
         │              │
         │ git add      │ git rm
         ▼              ▼
    ┌──────────┐   ┌──────────┐
    │  STAGED  │   │  STAGED  │
    │ (update) │   │ (delete) │
    └──────────┘   └──────────┘
         │              │
         │ commit       │ commit
         ▼              ▼
    [COMMITTED]    [POINTER REMOVED]
                        │ (blob permanece en LFS server)
                   [ORPHANED BLOB]
                        │ (eventual garbage collection)
                   [DELETED FROM SERVER]

Estados del blob en diferentes locations:

┌─────────────────────────────────────────────────────────────┐
│  MATRIZ DE PRESENCIA DEL BLOB                               │
└─────────────────────────────────────────────────────────────┘

                    Working   Local      Git       LFS
                    Dir       LFS Cache  Server    Server
────────────────────────────────────────────────────────────────
UNTRACKED           ✓         ✗          ✗         ✗
STAGED              ✓         ✓          ✗         ✗
COMMITTED           ✓         ✓          pointer   ✗
PUSHED              ✓         ✓          pointer   ✓
AFTER PRUNE         ✓         ✗          pointer   ✓
OTHER CLONE         ✗→✓       ✗→✓        pointer   ✓
  (after checkout)
────────────────────────────────────────────────────────────────

LEYENDA:
  ✓ = Blob completo presente
  ✗ = No presente
  pointer = Solo el pointer file (metadata)
  ✗→✓ = No presente inicialmente, se descarga on-demand

4.3 Momento de Decisión: Commit vs Upload

Pregunta filosófica: ¿Cuándo se "compromete" realmente el archivo?

┌─────────────────────────────────────────────────────────────┐
│  COMMIT vs UPLOAD: DOS MOMENTOS DIFERENTES                  │
└─────────────────────────────────────────────────────────────┘

GIT TRADICIONAL:
  git commit → Archivo está "versionado"
  git push   → Archivo está "compartido"

  Un solo concepto: el archivo completo.

GIT LFS:
  git commit → POINTER está versionado
               BLOB está en cache local

  git push   → POINTER está compartido (Git server)
               BLOB está compartido (LFS server)

  Dos conceptos separados: metadata vs contenido.

IMPLICACIONES:

  1. PUEDES TENER commits sin blobs en el server
     → Commit está en Git server
     → Blob solo en tu máquina
     → Otros no pueden checkout ese commit

  2. PUEDES TENER blobs sin commits (huérfanos)
     → Blob está en LFS server
     → Ningún commit lo referencia (borrado de historia)
     → Ocupa espacio pero no es accesible

  3. ROLLBACK tiene dos dimensiones
     → Git rollback: revertir pointer
     → LFS: blob antiguo sigue en LFS server

Escenario problemático:

PROBLEMA: Commit sin Push de LFS
─────────────────────────────────────────────────────────────
  Developer A:
    $ git add huge_sample.wav
    $ git commit -m "Add sample"
    $ git push origin main

  Pero... su red se cae antes de que LFS termine de subir el blob.

  Estado:
    Git Server: ✓ Tiene el commit con pointer
    LFS Server: ✗ NO tiene el blob

  Developer B:
    $ git pull origin main
    $ git checkout main
    ERROR: smudge filter lfs failed
    Error downloading object: huge_sample.wav (oid abc123...)

  Developer B no puede trabajar. El repo está "roto".

SOLUCIÓN:
  Developer A debe re-intentar el push:
    $ git lfs push origin main --all

  Esto sube los blobs faltantes al LFS server.

4.4 Garbage Collection y Prune

El cache local crece con el tiempo:

┌─────────────────────────────────────────────────────────────┐
│  CRECIMIENTO DEL CACHE LOCAL                                │
└─────────────────────────────────────────────────────────────┘

Día 1:
  .git/lfs/objects/: 200 MB (versiones actuales)

Día 30:
  .git/lfs/objects/: 450 MB (versiones actuales + antiguas)

Día 90:
  .git/lfs/objects/: 1.2 GB (muchas versiones acumuladas)

El cache incluye:
  - Blobs de la rama actual
  - Blobs de ramas antiguas que exploraste
  - Blobs de commits que revertiste
  - Blobs de archivos que ya no existen

git lfs prune: Limpieza inteligente

$ git lfs prune

QUÉ HACE:
  1. Escanea todos los commits locales recientes (últimos 10 días por defecto)
  2. Identifica qué blobs son referenciados
  3. Elimina blobs que:
     - NO están en commits recientes
     - SÍ están en LFS server (no se pierde nada)
  4. Mantiene blobs que podrías necesitar pronto

OPCIONES:

  $ git lfs prune --dry-run
    → Muestra qué se eliminaría sin hacerlo

  $ git lfs prune --verify-remote
    → Verifica que blobs existan en server antes de borrar

  $ git lfs prune --recent
    → Solo elimina blobs muy antiguos (> 10 días)

EJEMPLO:
  $ git lfs prune --dry-run --verbose

  prune: 5 local objects, 2 retained, 3 verified with remote
  prune: Deleting objects: 100% (3/3), done.

  4d7a8f9b2c1e3a5f... (50 MB)   ← piano_C4.wav versión antigua
  a3f7c9d8e6b5a2c1... (30 MB)   ← drum_kit versión antigua
  b2c4d6e8f0a1c3e5... (100 MB)  ← reverb_IR versión antigua

  Total reclaimed: 180 MB

Políticas de prune:

ESTRATEGIA 1: Prune agresivo (desarrollador individual)
  $ git lfs prune --recent

  Beneficio: Minimiza uso de disco
  Riesgo: Podrías necesitar re-descargar blobs si checkouteas ramas viejas

ESTRATEGIA 2: Prune conservador (laptop con buen disco)
  $ git lfs prune

  Beneficio: Balance entre espacio y conveniencia

ESTRATEGIA 3: Nunca prune (CI/CD con cache)
  No ejecutar prune

  Beneficio: Builds rápidos (cache completo)
  Costo: Más espacio en disco del runner

ESTRATEGIA 4: Prune con fetch (mantener solo rama actual)
  $ git lfs prune
  $ git lfs fetch --recent

  Mantiene solo lo relevante de los últimos días.

CAPÍTULO 5: Trade-offs y Decisiones

5.1 Beneficios: Qué Ganas

LFS resuelve problemas reales:

┌─────────────────────────────────────────────────────────────┐
│  BENEFICIOS DE GIT LFS                                      │
└─────────────────────────────────────────────────────────────┘

BENEFICIO 1: Repositorio Liviano
─────────────────────────────────────────────────────────────
  SIN LFS:
    .git/ size: 5.2 GB (código + binarios)
    git clone: 25 minutos

  CON LFS:
    .git/ size: 150 MB (código + pointers)
    git clone: 2 minutos

  Mejora: 17x más pequeño, 12x más rápido

BENEFICIO 2: Clone Selectivo
─────────────────────────────────────────────────────────────
  Puedes clonar sin descargar todos los blobs históricos:

  $ git lfs install
  $ git clone --branch main repo.git

  Descarga SOLO los blobs de la rama main, versión actual.
  Blobs de otras ramas: on-demand.

BENEFICIO 3: Operaciones Git Rápidas
─────────────────────────────────────────────────────────────
  git status, git log, git diff: Instantáneos
  No tienen que procesar GBs de blobs binarios.

BENEFICIO 4: CI/CD Eficiente
─────────────────────────────────────────────────────────────
  GitHub Actions checkout: 2 minutos (antes: 20 minutos)
  Más tiempo para compilar y testear, menos para clonar.

BENEFICIO 5: Deduplicación Automática
─────────────────────────────────────────────────────────────
  Si piano_C4.wav (50 MB) existe en 3 ramas:

  SIN LFS: 3 × 50 MB = 150 MB en .git/
  CON LFS: 1 × 50 MB = 50 MB en LFS server (mismo hash)

BENEFICIO 6: Ancho de Banda Reducido
─────────────────────────────────────────────────────────────
  git fetch solo descarga pointers nuevos (KB).
  Blobs se descargan solo si checkouteas esa rama.

BENEFICIO 7: Integridad Verificable
─────────────────────────────────────────────────────────────
  Cada blob tiene un hash SHA-256.
  Si el blob se corrompe, el hash no coincide → detectado.

BENEFICIO 8: Escalabilidad
─────────────────────────────────────────────────────────────
  Repo puede crecer a 100s de GB de assets sin ralentizar Git.

Cuantificación para AudioLab:

┌─────────────────────────────────────────────────────────────┐
│  AUDIOLAB: ANTES Y DESPUÉS DE LFS                           │
└─────────────────────────────────────────────────────────────┘

MÉTRICA                     ANTES (Git Nativo)    DESPUÉS (LFS)
──────────────────────────────────────────────────────────────
Repo size (.git/)           2.8 GB                120 MB
Clone time (100 Mbps)       18 min                 3 min
CI checkout time            12 min                 2 min
git status                  8 sec                  0.3 sec
git log --oneline           5 sec                  0.1 sec
Disk usage (developer)      3.5 GB                400 MB*
  (* con solo branch actual)

IMPACTO EN WORKFLOW:
  ✓ New developer onboarding: 18 min → 3 min
  ✓ CI builds: 60% más rápidos
  ✓ git operations: 10-20x más rápidas
  ✓ Bandwidth usage: 80% reducción

5.2 Limitaciones: Qué Pierdes

LFS no es perfecto. Trade-offs:

┌─────────────────────────────────────────────────────────────┐
│  LIMITACIONES DE GIT LFS                                    │
└─────────────────────────────────────────────────────────────┘

LIMITACIÓN 1: Diff No Funciona en Binarios
─────────────────────────────────────────────────────────────
  $ git diff piano_C4.wav

  SIN LFS (binario en Git):
    Binary files differ
    (no muy útil, pero al menos intentaba)

  CON LFS:
    diff --git a/piano_C4.wav b/piano_C4.wav
    @@ -1,3 +1,3 @@
    -oid sha256:4d7a8f9b...
    +oid sha256:9f3e2a1b...

  Solo ves que el hash cambió, no CÓMO cambió.
  (De todas formas, diff de WAV no es útil.)

LIMITACIÓN 2: Merge Conflicts Más Complejos
─────────────────────────────────────────────────────────────
  Si dos personas editan el mismo archivo LFS:

  Git detecta conflicto en el pointer:
    <<<<<<< HEAD
    oid sha256:abc123...
    =======
    oid sha256:def456...
    >>>>>>> feature-branch

  ¿Cuál versión es correcta? No puedes hacer "merge semántico".
  Tienes que elegir una versión completa u otra.

LIMITACIÓN 3: Costo de Storage
─────────────────────────────────────────────────────────────
  LFS server tiene límites:

  GitHub:
    - Incluido: 1 GB storage, 1 GB bandwidth/mes
    - Adicional: $5/mes por 50 GB storage + bandwidth

  GitLab:
    - Incluido: 10 GB storage (plan gratuito)
    - Adicional: Según plan

  Para proyectos grandes, esto puede ser caro.

LIMITACIÓN 4: Dependencia del Server
─────────────────────────────────────────────────────────────
  Si LFS server cae o es lento:
    - No puedes clonar el repo (blobs faltantes)
    - No puedes checkout commits que necesiten blobs
    - Workflow se detiene

  Con Git nativo, todo está en el repo (autonomía).

LIMITACIÓN 5: Complejidad Operacional
─────────────────────────────────────────────────────────────
  Equipo necesita entender:
    - Qué es LFS
    - Cómo configurar .gitattributes
    - Cómo diagnosticar problemas ("smudge filter failed")
    - Cuándo hacer prune

  Curva de aprendizaje adicional.

LIMITACIÓN 6: Compatibilidad con Tools
─────────────────────────────────────────────────────────────
  Algunos tools asumen que "git clone" te da todo:
    - Scripts de build antiguos
    - IDEs sin soporte LFS
    - CI/CD pipelines que no configuran LFS

  Necesitas asegurar que todas las herramientas funcionen.

LIMITACIÓN 7: No Puedes "Grep" el Contenido
─────────────────────────────────────────────────────────────
  $ git grep "piano" *.wav

  No funciona (archivos son pointers, no contenido real).
  Solo puedes grep en archivos checkoueados localmente.

LIMITACIÓN 8: Historial No es Realmente "Todo Contenido"
─────────────────────────────────────────────────────────────
  Si LFS server borra blobs antiguos (garbage collection):
    - Los commits existen en Git
    - Pero no puedes obtener el contenido real
    - Historia está "hueca"

  Con Git nativo, todo está preservado mientras exista el repo.

5.3 Matriz de Costos

Análisis de costos real:

┌─────────────────────────────────────────────────────────────────────┐
│  MATRIZ DE COSTOS: GIT LFS EN DIFERENTES PLATAFORMAS               │
└─────────────────────────────────────────────────────────────────────┘

GITHUB:
─────────────────────────────────────────────────────────────────────
  Plan Gratuito:
    Storage: 1 GB
    Bandwidth: 1 GB/mes

  Costo adicional:
    $5/mes por cada "data pack" de:
      - 50 GB storage
      - 50 GB bandwidth/mes

  Ejemplo (AudioLab con 3 GB de assets):
    Necesitas: 1 paquete base + 1 data pack
    Costo: $5/mes = $60/año

GITLAB:
─────────────────────────────────────────────────────────────────────
  Plan Gratuito:
    Storage: 10 GB (mucho más generoso)

  Plan Premium:
    Storage: Ilimitado
    Costo: $19/usuario/mes

  Ejemplo (AudioLab):
    Con 3 GB de assets: Plan gratuito suficiente
    Costo: $0/año

BITBUCKET:
─────────────────────────────────────────────────────────────────────
  Plan Gratuito:
    LFS: 1 GB storage

  Plan Standard:
    LFS: Soft limit 100 GB
    Costo: $3/usuario/mes

  Ejemplo (AudioLab):
    Con 3 GB: Plan Standard
    Costo: $3/mes = $36/año

SELF-HOSTED (AWS S3):
─────────────────────────────────────────────────────────────────────
  S3 Pricing:
    Storage: $0.023/GB/mes
    Transfer out: $0.09/GB

  Ejemplo (AudioLab con 3 GB, 50 clones/mes):
    Storage: 3 GB × $0.023 = $0.07/mes
    Transfer: 3 GB × 50 × $0.09 = $13.50/mes
    Total: $13.57/mes = $163/año

  (Pero tienes control total, sin límites arbitrarios)

Comparación de costo total:

ESCENARIO: AudioLab (3 GB assets, 5 developers, 100 clones/mes)

┌──────────────┬───────────────┬────────────────┬─────────────┐
│ Plataforma   │ Plan          │ Costo/mes      │ Costo/año   │
├──────────────┼───────────────┼────────────────┼─────────────┤
│ GitHub       │ Free + 1 pack │ $5             │ $60         │
│ GitLab       │ Free          │ $0             │ $0          │
│ Bitbucket    │ Standard (5u) │ $15            │ $180        │
│ Self S3      │ Custom        │ ~$14           │ ~$168       │
└──────────────┴───────────────┴────────────────┴─────────────┘

GANADOR: GitLab (plan gratuito cubre 10 GB)

MEJOR VALOR: GitHub ($60/año por simplicity + integración)

5.4 Cuándo NO Usar LFS

LFS no es siempre la respuesta:

┌─────────────────────────────────────────────────────────────┐
│  CASOS DONDE NO DEBERÍAS USAR LFS                           │
└─────────────────────────────────────────────────────────────┘

CASO 1: Archivos de Build (Artifacts)
─────────────────────────────────────────────────────────────
  ✗ build/app.exe
  ✗ dist/bundle.js
  ✗ target/release/binary

  RAZÓN: Son reproducibles desde el código fuente.
  MEJOR: .gitignore + CI/CD artifacts storage

CASO 2: Datasets Masivos (> 10 GB)
─────────────────────────────────────────────────────────────
  ✗ training_data.zip (50 GB)
  ✗ video_corpus/ (200 GB)

  RAZÓN: Demasiado grandes incluso para LFS.
  MEJOR: DVC (Data Version Control), S3 directo, institutional storage

CASO 3: Archivos Temporales
─────────────────────────────────────────────────────────────
  ✗ cache/
  ✗ logs/
  ✗ .DS_Store

  RAZÓN: No necesitan versionarse.
  MEJOR: .gitignore

CASO 4: Dependencies Binarias
─────────────────────────────────────────────────────────────
  ✗ node_modules/
  ✗ vendor/
  ✗ lib/*.dll

  RAZÓN: Gestionadas por package managers.
  MEJOR: package.json + npm install / vcpkg / etc.

CASO 5: Archivos de Configuración Privados
─────────────────────────────────────────────────────────────
  ✗ .env (secrets)
  ✗ credentials.json

  RAZÓN: No deben estar en el repo en absoluto.
  MEJOR: .gitignore + documentar en README cómo crear

CASO 6: Archivos Pequeños Binarios (< 100 KB)
─────────────────────────────────────────────────────────────
  ✗ icon.png (30 KB)
  ✗ logo.svg (5 KB)

  RAZÓN: Git nativo los maneja bien.
  MEJOR: Git nativo (overhead de LFS no vale la pena)

CASO 7: Archivos Que Cambiarán TODO EL TIEMPO
─────────────────────────────────────────────────────────────
  ✗ work_in_progress.psd (cambia cada hora)

  RAZÓN: LFS almacena cada versión (costo crece rápido).
  MEJOR: No versionar hasta que esté "estable"

Árbol de decisión final:

                ¿Debe estar en control de versiones?
                  ┌───────────┴───────────┐
                 NO                       SÍ
                  │                        │
            .gitignore               ¿Es binario?
                               ┌───────────┴───────────┐
                              NO                      SÍ
                               │                       │
                         GIT NATIVO               ¿Es > 1 MB?
                                           ┌───────────┴───────────┐
                                          NO                      SÍ
                                           │                       │
                                     GIT NATIVO              ¿Es > 10 GB?
                                                       ┌───────────┴───────────┐
                                                      NO                      SÍ
                                                       │                       │
                                                      LFS                 Considerar
                                                                         alternativa
                                                                         (DVC, S3)

CAPÍTULO 6: Estrategia AudioLab

6.1 Clasificación de Assets en AudioLab

Política específica para el proyecto audio-lab:

┌─────────────────────────────────────────────────────────────┐
│  AUDIOLAB: CLASIFICACIÓN DE ARCHIVOS                        │
└─────────────────────────────────────────────────────────────┘

CATEGORÍA 1: CÓDIGO FUENTE (Git Nativo)
─────────────────────────────────────────────────────────────
  ✓ src/**/*.cpp
  ✓ include/**/*.h
  ✓ CMakeLists.txt
  ✓ scripts/*.py

  Tamaño típico: 5-50 KB por archivo
  Total: ~50 MB
  Razón: Texto, cambia frecuentemente, diff/merge necesarios

CATEGORÍA 2: CONFIGURACIONES (Git Nativo)
─────────────────────────────────────────────────────────────
  ✓ config/*.json
  ✓ .clang-format
  ✓ .github/workflows/*.yml
  ✓ vcpkg.json

  Tamaño típico: 1-10 KB
  Total: ~2 MB
  Razón: Texto, necesitamos ver diffs

CATEGORÍA 3: DOCUMENTACIÓN (Git Nativo)
─────────────────────────────────────────────────────────────
  ✓ docs/**/*.md
  ✓ README.md
  ✓ docs/diagrams/*.svg

  Tamaño típico: 5-100 KB
  Total: ~10 MB
  Razón: Texto/SVG, cambios frecuentes

CATEGORÍA 4: AUDIO SAMPLES DE PRUEBA (LFS)
─────────────────────────────────────────────────────────────
  → tests/samples/*.wav
  → tests/samples/*.aiff

  Tamaño típico: 10-50 MB por archivo
  Total: ~500 MB
  Razón: Binario grande, cambian raramente, usados en tests

CATEGORÍA 5: IMPULSE RESPONSES (LFS)
─────────────────────────────────────────────────────────────
  → tests/impulse_responses/*.wav

  Tamaño típico: 50-200 MB por archivo
  Total: ~800 MB
  Razón: Binario muy grande, casi inmutables

CATEGORÍA 6: GOLDEN OUTPUTS (LFS)
─────────────────────────────────────────────────────────────
  → tests/golden_outputs/*.wav

  Tamaño típico: 5-20 MB por archivo
  Total: ~200 MB
  Razón: Referencias para regression testing

CATEGORÍA 7: DOCUMENTACIÓN MULTIMEDIA (LFS)
─────────────────────────────────────────────────────────────
  → docs/videos/*.mp4
  → docs/screenshots_large/*.png (solo si > 2 MB)

  Tamaño típico: 50-200 MB por video
  Total: ~400 MB
  Razón: Tutoriales, demos visuales

CATEGORÍA 8: BUILD ARTIFACTS (.gitignore)
─────────────────────────────────────────────────────────────
  ✗ build/
  ✗ out/
  ✗ *.exe, *.dll, *.so

  Razón: Reproducibles, no deben versionarse

CATEGORÍA 9: DEPENDENCIES (.gitignore + vcpkg)
─────────────────────────────────────────────────────────────
  ✗ vcpkg_installed/
  ✗ third_party/

  Razón: Gestionadas por vcpkg, no versionar binarios

6.2 Umbrales y Políticas

Reglas concretas:

┌─────────────────────────────────────────────────────────────┐
│  AUDIOLAB: POLÍTICAS DE VERSIONAMIENTO                      │
└─────────────────────────────────────────────────────────────┘

POLÍTICA 1: Umbral de Tamaño
─────────────────────────────────────────────────────────────
  < 100 KB:     Siempre Git nativo
  100 KB - 1 MB: Git nativo (evaluación caso por caso)
  1 MB - 100 MB: LFS (si es binario)
  > 100 MB:      LFS o considerar externo

POLÍTICA 2: Archivos de Audio
─────────────────────────────────────────────────────────────
  REGLA: Todos los WAV/AIFF > 1 MB → LFS

  EXCEPCIÓN: Samples de test muy pequeños (< 100 KB) → Git nativo

  JUSTIFICACIÓN:
    - Audio es casi siempre binario incomprimible
    - Cambios son raros (samples de referencia)
    - Necesitamos versionarlos para reproducibilidad

POLÍTICA 3: Impulse Responses
─────────────────────────────────────────────────────────────
  REGLA: Todos los IR → LFS (sin umbral)

  JUSTIFICACIÓN:
    - IR son inmutables (mediciones físicas)
    - Son grandes (50-200 MB)
    - Críticos para calidad de reverb

POLÍTICA 4: Videos de Documentación
─────────────────────────────────────────────────────────────
  REGLA: Videos > 10 MB → LFS

  LÍMITE: Máximo 5 videos en el repo
    - Si necesitas más: YouTube + links en docs

  JUSTIFICACIÓN:
    - Videos son útiles pero crecen rápido
    - Balance entre accesibilidad y tamaño

POLÍTICA 5: Screenshots
─────────────────────────────────────────────────────────────
  < 2 MB: Git nativo
  > 2 MB: LFS

  PREFERENCIA: Optimizar PNGs antes de commitear
    - Usar herramientas como pngquant
    - Objetivo: < 500 KB si es posible

POLÍTICA 6: Golden Outputs
─────────────────────────────────────────────────────────────
  REGLA: Todos los golden outputs → LFS

  MANTENIMIENTO:
    - Revisar cada 6 meses
    - Eliminar golden outputs obsoletos
    - Mantener solo para tests activos

POLÍTICA 7: Migración
─────────────────────────────────────────────────────────────
  Si un archivo pequeño crece:
    - Cuando supera 1 MB → Migrar a LFS
    - Comunicar al equipo
    - Actualizar .gitattributes

6.3 Workflow de Audio Samples

Proceso específico para assets de audio:

┌─────────────────────────────────────────────────────────────┐
│  WORKFLOW: AÑADIR NUEVO AUDIO SAMPLE                        │
└─────────────────────────────────────────────────────────────┘

PASO 1: Grabar/Obtener el Sample
  - Grabar en DAW (Pro Tools, Ableton, etc.)
  - O descargar de biblioteca libre de royalties

PASO 2: Procesar y Normalizar
  - Format: WAV, 48 kHz, 24-bit (estándar AudioLab)
  - Normalizar a -1 dBFS peak
  - Trim silence al inicio/final
  - Nombrar descriptivamente: instrument_note_variant.wav
    Ejemplo: piano_C4_soft.wav

PASO 3: Decidir Ubicación
  tests/samples/           → Para unit tests
  tests/impulse_responses/ → Para IRs de reverb
  tests/golden_outputs/    → Para regression tests
  benchmarks/              → Para performance benchmarks

PASO 4: Añadir al Repo
  $ git add tests/samples/piano_C4_soft.wav

  LFS detecta automáticamente (*.wav en .gitattributes)
  y lo procesa como LFS.

PASO 5: Commit con Metadata
  $ git commit -m "Add piano C4 soft sample

  - Source: Yamaha C3 grand piano
  - Recording: Studio A, condenser mic
  - Format: 48kHz 24-bit WAV
  - Use: tests/dsp/test_pitch_shift.cpp
  - Size: 12.3 MB"

PASO 6: Push
  $ git push origin main

  Git push + LFS upload se hacen automáticamente.

PASO 7: Documentar
  Añadir entrada en tests/samples/README.md:

  ## piano_C4_soft.wav
  - **Instrument**: Yamaha C3 Grand Piano
  - **Note**: C4 (Middle C)
  - **Dynamics**: Soft (mp)
  - **Duration**: 5 seconds
  - **Use Cases**: Pitch shifting tests, time stretching

Mantenimiento de samples:

CADA 3 MESES: Revisión de Samples

  1. Listar todos los samples:
     $ git lfs ls-files tests/samples/

  2. Verificar uso:
     $ grep -r "piano_C4_soft.wav" tests/

     Si no hay hits → Sample huérfano

  3. Decisión:
     - En uso → Mantener
     - Huérfano → Considerar eliminar
     - Duplicado → Deduplicar

  4. Actualizar documentación:
     README.md con lista actualizada

6.4 Híbridos: Artifacts de Build

Algunos archivos están en zona gris:

┌─────────────────────────────────────────────────────────────┐
│  CASO ESPECIAL: ARTIFACTS DE BUILD                          │
└─────────────────────────────────────────────────────────────┘

PROBLEMA:
  Los builds de AudioLab generan:
    - audiolab.dll (15 MB)
    - test_runner.exe (8 MB)
    - benchmark_suite.exe (12 MB)

  ¿Deben versionarse?

ANÁLISIS:
  ✓ Son reproducibles desde el código (git clone + build)
  ✗ Pero builds tardan 20 minutos en compilar
  ✓ Útil para QA testing sin compilar
  ✗ Cambian en cada commit (muchas versiones)

DECISIÓN PARA AUDIOLAB:

  REGLA GENERAL: .gitignore (no versionar)

  EXCEPCIÓN: Release builds
    - Cuando hacemos un release (v1.0.0)
    - Usamos GitHub Releases para almacenar binarios
    - NO van en el repo Git, van en "Releases" de GitHub

  WORKFLOW:
    1. Tag release: git tag v1.0.0
    2. Build release: cmake --build . --config Release
    3. Crear GitHub Release: gh release create v1.0.0
    4. Upload binarios: gh release upload v1.0.0 audiolab.dll

  Resultado:
    - Código versionado en Git
    - Binarios disponibles en Releases
    - No inflan el repo

Artifacts de CI/CD:

GITHUB ACTIONS: Artifacts Temporales

  .github/workflows/ci.yml:

    - name: Build
      run: cmake --build .

    - name: Upload artifacts
      uses: actions/upload-artifact@v3
      with:
        name: audiolab-build-${{ github.sha }}
        path: build/audiolab.dll
        retention-days: 7

  Estos artifacts:
    - Se almacenan en GitHub (no en Git)
    - Disponibles por 7 días
    - Útiles para debugging de CI
    - Automáticamente eliminados después

CAPÍTULO 7: Alternativas y Complementos

7.1 Git Annex: El Hermano Mayor

Git Annex es la alternativa más antigua y compleja a LFS:

┌─────────────────────────────────────────────────────────────┐
│  GIT ANNEX vs GIT LFS                                       │
└─────────────────────────────────────────────────────────────┘

GIT ANNEX:
  Creado: 2010 (antes de LFS)
  Filosofía: Descentralizado, flexible, potente

  Características:
    ✓ Totalmente descentralizado (no requiere server central)
    ✓ Múltiples backends de storage (S3, rsync, HTTP, etc.)
    ✓ Políticas de distribución sofisticadas ("2 copias en servers geográficamente separados")
    ✓ Soporte para archivos MUY grandes (TBs)
    ✓ Partial checkout muy granular

  Desventajas:
    ✗ Complejidad alta (curva de aprendizaje empinada)
    ✗ Menos integración con plataformas (GitHub, GitLab)
    ✗ Comandos menos intuitivos
    ✗ Requiere más configuración manual

GIT LFS:
  Creado: 2015 (por GitHub)
  Filosofía: Simple, integrado, "just works"

  Características:
    ✓ Súper simple de usar
    ✓ Integración nativa en GitHub, GitLab, Bitbucket
    ✓ Transparente (casi no notas que está ahí)
    ✓ Comunidad grande, bien documentado

  Desventajas:
    ✗ Requiere server LFS centralizado
    ✗ Menos flexible que Annex
    ✗ Políticas de distribución básicas

Cuándo elegir Git Annex:

ESCENARIO 1: Datos Científicos Masivos
  - Proyecto: 5 TB de datos de telescopio
  - Necesidad: Distribución en múltiples instituciones
  - Política: "Al menos 3 copias, 2 en continentes diferentes"

  → Git Annex (LFS no escala a este nivel)

ESCENARIO 2: Workflow Descentralizado Extremo
  - Equipo: Investigadores en field work sin internet confiable
  - Necesidad: Sincronización peer-to-peer cuando hay conectividad

  → Git Annex (soporta backends offline)

ESCENARIO 3: Control Total
  - Organización: Gobierno/militar con requisitos de seguridad estrictos
  - Necesidad: Zero dependencias en servicios de terceros

  → Git Annex (totalmente self-hosted)

Cuándo elegir Git LFS:

ESCENARIO 1: Proyecto Web/App Típico
  - Assets: Imágenes, videos, fonts (< 10 GB total)
  - Equipo: Developers con Git básico

  → Git LFS (simplicidad)

ESCENARIO 2: Integración con Plataformas
  - Repo: Hosted en GitHub/GitLab
  - CI/CD: GitHub Actions, GitLab CI

  → Git LFS (integración nativa)

ESCENARIO 3: AudioLab
  - Assets: Audio samples, IR (< 5 GB)
  - Equipo: Pequeño, necesita simplicidad

  → Git LFS ✓

7.2 DVC: Data Version Control

DVC es la solución moderna para ML/Data Science:

┌─────────────────────────────────────────────────────────────┐
│  DVC (Data Version Control)                                 │
└─────────────────────────────────────────────────────────────┘

CONCEPTO:
  "Git para datasets y modelos de ML"

  No reemplaza a Git, lo complementa:
    - Git: versiona código y metadata
    - DVC: versiona datos y modelos (pointers en Git)

ARQUITECTURA:
  Similar a LFS, pero optimizado para ML workflows.

  dataset.dvc (en Git):
    outs:
    - md5: 4d7a8f9b2c1e3a5f...
      size: 5000000000
      path: data/training_set.zip

  El archivo real (5 GB) vive en:
    - S3, GCS, Azure Blob
    - O storage local

VENTAJAS SOBRE LFS:
  ✓ Pipelines de ML versionados
  ✓ Métricas y experiments tracking
  ✓ Integración con ML tools (MLflow, etc.)
  ✓ Optimizado para datasets masivos
  ✓ Deduplicación más agresiva

DESVENTAJAS:
  ✗ Complejidad adicional
  ✗ Menos integración con plataformas Git
  ✗ Requiere aprender nueva tool

Cuándo usar DVC:

PROYECTO DE MACHINE LEARNING:
  - Datasets: 50 GB de imágenes para training
  - Modelos: 2 GB de pesos de red neuronal
  - Workflow: Experimentación constante con diferentes datasets

  Workflow con DVC:
    $ dvc add data/training_set.zip
    $ git add data/training_set.zip.dvc
    $ git commit -m "Update training set v3"
    $ dvc push  # Sube a S3

  Otro researcher:
    $ git pull
    $ dvc pull  # Descarga dataset desde S3
    $ python train.py  # Entrena con datos correctos

AUDIOLAB: ¿Usar DVC?

  RESPUESTA: No (por ahora)

  RAZÓN:
    - Nuestros "datasets" son pequeños (< 5 GB)
    - No hacemos ML training intensivo
    - LFS es más simple y suficiente

  FUTURO:
    Si AudioLab evoluciona a ML-based audio processing:
      - Datasets de 100+ GB
      - Modelos de deep learning
      → Entonces considerar DVC

7.3 Submodules: La Estrategia de Separación

Git submodules: referencia otro repo como directorio:

┌─────────────────────────────────────────────────────────────┐
│  GIT SUBMODULES                                             │
└─────────────────────────────────────────────────────────────┘

CONCEPTO:
  En lugar de almacenar archivos grandes en el repo principal,
  los mueves a un repo separado y lo referencias.

ESTRUCTURA:
  audio-lab/                     (repo principal)
  ├─ src/
  ├─ tests/
  └─ samples/  ───────────►  audio-lab-samples/  (repo separado)
      (submodule)              ├─ piano/
                               ├─ drums/
                               └─ impulse_responses/

WORKFLOW:
  $ git submodule add https://github.com/team/audio-lab-samples.git samples
  $ git commit -m "Add samples as submodule"

  Otro developer:
  $ git clone https://github.com/team/audio-lab.git
  $ git submodule update --init --recursive

VENTAJAS:
  ✓ Repo principal se mantiene pequeño
  ✓ Puedes actualizar samples independientemente
  ✓ Diferentes branches de samples para diferentes propósitos
  ✓ No requiere LFS (si samples repo es pequeño)

DESVENTAJAS:
  ✗ Complejidad (dos repos que sincronizar)
  ✗ Developers olvidan actualizar submodules
  ✗ Commits en submodule deben pushearse antes que en main repo
  ✗ Curva de aprendizaje

Cuándo usar Submodules:

CASO 1: Assets Compartidos entre Proyectos
  Situación:
    - audio-lab-core
    - audio-lab-plugins
    - audio-lab-tools

    Todos comparten los mismos test samples.

  Solución:
    - audio-lab-samples (repo separado)
    - Cada proyecto lo referencia como submodule

  Beneficio: Un solo lugar para assets, varias referencias.

CASO 2: Assets con Ciclo de Vida Diferente
  Situación:
    - Código cambia diariamente
    - Samples cambian mensualmente

  Solución:
    - Código en repo principal
    - Samples en submodule

  Beneficio: Puedes actualizar samples sin tocar código.

AUDIOLAB: ¿Usar Submodules?

  RESPUESTA: No (por ahora)

  RAZÓN:
    - Assets están acoplados con tests (cambios frecuentes juntos)
    - No compartimos assets con otros proyectos
    - LFS resuelve el problema de tamaño sin complejidad de submodules

  FUTURO:
    Si creamos familia de proyectos AudioLab:
      → Considerar samples compartidos como submodule

7.4 Paisaje del Ecosistema

Mapa completo de soluciones:

┌─────────────────────────────────────────────────────────────────────┐
│  ECOSISTEMA DE VERSIONAMIENTO DE ARCHIVOS GRANDES                   │
└─────────────────────────────────────────────────────────────────────┘

                        COMPLEJIDAD
                         GIT │
                        ANNEX│        ┌─────────┐
                             │        │ Control │
                             │        │  Total  │
                             │        │ Policies│
                             │        └─────────┘
                             │    ┌─────────┐
                          DVC│    │   ML    │
                             │    │  Data   │
                             │    └─────────┘
                    GIT LFS  │───────┐
                             │       │ Sweet spot
                             │       │ para mayoría
                             │       │
                  GIT NATIVE │       │
                             │       │
                     Nothing │  ┌────────┐
                             │  │ Simple │
                             │  │Projects│
                             └──┴────────┴─────────────►
                              Small        Large     TAMAÑO DE ASSETS
                              (<1GB)      (>10GB)


MATRIZ DE DECISIÓN:
───────────────────────────────────────────────────────────────────────
                    Simple      Medium      Complex     Massive
                    (< 1 GB)    (1-10 GB)   (10-100 GB) (> 100 GB)
───────────────────────────────────────────────────────────────────────
Proyectos Web       Native      LFS         LFS         DVC/External
Game Dev            Native      LFS         LFS+Sub     Git Annex
ML/Data Science     Native      LFS         DVC         DVC
Scientific Data     Native      LFS         Git Annex   Git Annex
AudioLab            Native      LFS ✓       -           -
───────────────────────────────────────────────────────────────────────

Comparación lado a lado:

┌──────────────┬──────────┬──────────┬──────────┬──────────┐
│ Feature      │ Native   │ LFS      │ Annex    │ DVC      │
├──────────────┼──────────┼──────────┼──────────┼──────────┤
│ Simplicidad  │ ★★★★★    │ ★★★★☆    │ ★★☆☆☆    │ ★★★☆☆    │
│ Integración  │ ★★★★★    │ ★★★★★    │ ★★☆☆☆    │ ★★★☆☆    │
│ Escalabilidad│ ★☆☆☆☆    │ ★★★☆☆    │ ★★★★★    │ ★★★★☆    │
│ Flexibilidad │ ★★☆☆☆    │ ★★★☆☆    │ ★★★★★    │ ★★★★☆    │
│ Velocidad    │ ★★★★☆    │ ★★★★☆    │ ★★★☆☆    │ ★★★☆☆    │
│ Costo        │ FREE     │ $-$$     │ FREE     │ $-$$     │
└──────────────┴──────────┴──────────┴──────────┴──────────┘

🎯 CONCLUSIÓN: Decisión para AudioLab

┌─────────────────────────────────────────────────────────────┐
│  DECISIÓN FINAL PARA AUDIOLAB                               │
└─────────────────────────────────────────────────────────────┘

SOLUCIÓN ELEGIDA: GIT LFS

JUSTIFICACIÓN:
  ✓ Tamaño de assets: ~3 GB (sweet spot para LFS)
  ✓ Equipo pequeño: Simplicidad > Flexibilidad
  ✓ Integración: GitHub con LFS nativo
  ✓ Costo: $0-5/mes (asequible)
  ✓ Experiencia: Documentación excelente, comunidad activa

CONFIGURACIÓN:
  .gitattributes:
    *.wav filter=lfs diff=lfs merge=lfs -text
    *.aiff filter=lfs diff=lfs merge=lfs -text
    tests/impulse_responses/** filter=lfs diff=lfs merge=lfs -text
    tests/golden_outputs/** filter=lfs diff=lfs merge=lfs -text
    docs/videos/*.mp4 filter=lfs diff=lfs merge=lfs -text

HOSTING:
  GitLab (plan gratuito, 10 GB LFS storage)

  Si migramos a GitHub en el futuro:
    → 1 data pack ($5/mes) cubre necesidades

PLAN DE CONTINGENCIA:
  Si assets crecen > 20 GB:
    → Re-evaluar (considerar DVC o Git Annex)

  Si necesitamos ML workflows:
    → Añadir DVC como complemento

REVISIÓN:
  Cada 6 meses: Revisar tamaño de assets y ajustar políticas

📚 EPÍLOGO: La Filosofía Detrás de LFS

Git LFS no es solo una herramienta técnica. Es una solución filosófica a un problema de incompatibilidad ontológica:

┌─────────────────────────────────────────────────────────────┐
│  LA DUALIDAD FUNDAMENTAL                                    │
└─────────────────────────────────────────────────────────────┘

GIT fue diseñado para el mundo de:
  - Texto
  - Deltas
  - Cambios granulares
  - Merge inteligente
  - Historia navegable línea por línea

LOS ARCHIVOS BINARIOS GRANDES pertenecen al mundo de:
  - Bloques opacos
  - Inmutabilidad
  - Cambios atómicos
  - Reemplazo completo
  - Identificación por hash

Estos dos mundos NO son compatibles.

GIT LFS es la INTERFACE entre ambos:
  - Git versiona la IDENTIDAD (hash)
  - LFS almacena la SUSTANCIA (bytes)
  - El usuario experimenta UNIDAD (transparencia)

La lección más profunda:

No todos los problemas se resuelven con la misma herramienta.

Git es perfecto para código. Git LFS es perfecto para assets binarios grandes. Juntos, forman un sistema completo para proyectos modernos que mezclan ambos mundos.

Para AudioLab, esto significa: - El algoritmo (código C++) vive en Git - El sonido (muestras WAV) vive en LFS - La referencia (pointer) los une en un repositorio coherente

Dos mundos, una arquitectura. Esa es la filosofía de Git LFS.


FIN DEL DOCUMENTO

Total: ~35 páginas de teoría conceptual y visual sobre Git LFS. Cero comandos técnicos de instalación. Todo el enfoque en ENTENDER el sistema, no en operarlo.