# Parse PDF to Markdown

Fuente conceptual: OpenViking `openviking/parse/parsers/pdf.py` (AGPL-3.0)
Reimplementar desde cero. No copiar codigo.

## Funciones a implementar

### 1. pdf_to_markdown

- **Dominio:** core
- **Lang:** Python
- **Purity:** impure (I/O: lee archivo PDF)
- **Signature:** `pdf_to_markdown(pdf_path: str, heading_detection: str = "auto") -> tuple[str, dict]`
- **Descripcion:** Convierte un PDF a markdown. Extrae texto, tablas e inyecta headings detectados desde bookmarks o analisis de fuentes. Retorna (markdown_content, metadata_dict).
- **Algoritmo:**
  1. Abrir PDF con pdfplumber
  2. Detectar headings:
     - `"bookmarks"`: extraer outlines/bookmarks del PDF
     - `"font"`: analizar distribucion de font sizes para detectar headings
     - `"auto"`: intentar bookmarks primero, fallback a font
  3. Agrupar headings por pagina
  4. Por cada pagina: inyectar headings como `# titulo`, extraer texto, extraer tablas como markdown
  5. Unir todo con `\n\n`
- **Deps:** `pdfplumber`
- **Error type:** Exception (archivo no encontrado, PDF corrupto)
- **Tests:** PDF con bookmarks, PDF sin bookmarks, PDF con tablas, PDF vacio

### 2. extract_pdf_bookmarks

- **Dominio:** core
- **Lang:** Python
- **Purity:** impure (requiere objeto PDF abierto)
- **Signature:** `extract_pdf_bookmarks(pdf) -> list[dict]`
- **Descripcion:** Extrae la estructura de bookmarks/outlines de un PDF abierto con pdfplumber. Retorna lista de `{"level": int, "title": str, "page_num": int | None}`.
- **Algoritmo:**
  1. Acceder a `pdf.doc.get_outlines()`
  2. Construir mapping `objid -> page_number` desde pdf.pages
  3. Resolver cada destino de outline a su numero de pagina
  4. Limitar nivel a [1, 6]
- **Deps:** `pdfplumber`
- **Tests:** PDF con outlines, PDF sin outlines

### 3. detect_headings_by_font

- **Dominio:** core
- **Lang:** Python
- **Purity:** impure (requiere objeto PDF abierto)
- **Signature:** `detect_headings_by_font(pdf, min_delta: float = 2.0, max_levels: int = 4) -> list[dict]`
- **Descripcion:** Detecta headings analizando la distribucion de font sizes. El font size mas comun es el body; sizes significativamente mayores son headings.
- **Algoritmo:**
  1. Samplear font sizes (cada 5ta pagina) → Counter
  2. Determinar body_size (most_common)
  3. Heading sizes: font sizes >= body_size + min_delta Y con frecuencia < 50% del body
  4. Ordenar heading sizes desc → asignar niveles 1,2,3...
  5. Recorrer todas las paginas, extraer texto de chars con heading size
  6. Deduplicar: filtrar titulos que aparecen en >30% de paginas (son headers/footers)
- **Deps:** `pdfplumber`, `collections.Counter`
- **Tests:** PDF con headings claros por font, PDF con fuente uniforme (sin headings detectados)

### 4. format_table_markdown (reutilizable)

- **Dominio:** core
- **Lang:** Python
- **Purity:** pure
- **Signature:** `format_table_to_markdown(rows: list[list[str]], has_header: bool = True) -> str`
- **Descripcion:** Convierte una lista 2D de celdas a tabla markdown con alineacion de columnas.
- **Algoritmo:**
  1. Calcular max width por columna
  2. Formatear header row con `|`
  3. Anadir separador `| --- | --- |`
  4. Formatear data rows
  5. Escapar pipes en celdas
- **Deps:** ninguna
- **Tests:** tabla normal, tabla con celdas vacias, tabla con 1 fila, tabla vacia, celdas con pipes
