# Parse Markdown — Suite de funciones

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

## Funciones a implementar

### 1. extract_frontmatter

- **Dominio:** core
- **Lang:** Python
- **Purity:** pure
- **Signature:** `extract_frontmatter(content: str) -> tuple[str, dict | None]`
- **Descripcion:** Extrae YAML frontmatter (delimitado por `---`) del inicio de un string markdown. Retorna el contenido sin frontmatter y el dict parseado (o None si no hay).
- **Algoritmo:**
  1. Regex `^---\n(.*?)\n---\n` con DOTALL
  2. Si match, parsear cada linea `key: value` (o usar `yaml.safe_load`)
  3. Retornar (contenido_restante, dict_frontmatter)
- **Deps:** `re`, opcionalmente `yaml`
- **Tests:** contenido con frontmatter, sin frontmatter, frontmatter vacio, frontmatter con listas

### 2. find_headings

- **Dominio:** core
- **Lang:** Python
- **Purity:** pure
- **Signature:** `find_headings(content: str) -> list[tuple[int, int, str, int]]`
- **Descripcion:** Encuentra todos los headings markdown (# a ######), excluyendo los que estan dentro de code blocks (``` ... ```), HTML comments (<!-- ... -->) y bloques indentados.
- **Retorno:** Lista de `(start_pos, end_pos, title, level)`
- **Algoritmo:**
  1. Recopilar rangos excluidos: code blocks (triple backtick), HTML comments, bloques indentados (4 espacios/tab)
  2. Regex `^(#{1,6})\s+(.+)$` con MULTILINE
  3. Filtrar matches cuya posicion cae en un rango excluido
  4. Filtrar headings escapados (`\#`)
- **Deps:** `re`
- **Tests:** headings normales, headings dentro de code blocks (no deben detectarse), headings escapados, headings en HTML comments

### 3. smart_split_content

- **Dominio:** core
- **Lang:** Python
- **Purity:** pure
- **Signature:** `smart_split_content(content: str, max_tokens: int = 1024, max_chars: int = 8000) -> list[str]`
- **Descripcion:** Divide contenido grande en partes respetando limites de tokens y caracteres. Divide por parrafos (doble newline). Si un parrafo individual excede el limite, lo corta por caracteres.
- **Algoritmo:**
  1. Split por `\n\n` (parrafos)
  2. Acumular parrafos mientras no excedan max_tokens/max_chars
  3. Si un parrafo individual excede, cortarlo en chunks de max_chars
  4. Retornar lista de partes
- **Deps:** `re`
- **Tests:** contenido corto (1 parte), contenido largo, parrafo gigante que requiere forzar corte

### 4. estimate_token_count

- **Dominio:** core
- **Lang:** Python
- **Purity:** pure
- **Signature:** `estimate_token_count(content: str) -> int`
- **Descripcion:** Estimacion rapida de tokens sin tokenizer. CJK chars ~0.7 token/char, otros ~0.3 token/char.
- **Algoritmo:**
  1. Contar chars CJK con regex `[\u4e00-\u9fff\u3040-\u30ff\uac00-\ud7af]`
  2. Contar otros non-whitespace chars
  3. Retornar `int(cjk * 0.7 + otros * 0.3)`
- **Deps:** `re`
- **Tests:** texto solo latin, texto solo CJK, texto mixto, texto vacio

### 5. sanitize_for_path

- **Dominio:** core
- **Lang:** Python
- **Purity:** pure
- **Signature:** `sanitize_for_path(text: str, max_length: int = 50) -> str`
- **Descripcion:** Convierte texto a nombre seguro para uso en paths. Remueve caracteres especiales, reemplaza espacios con `_`, trunca con hash suffix si excede max_length.
- **Algoritmo:**
  1. Regex: mantener solo `\w`, CJK ranges, espacios y guiones
  2. Reemplazar espacios por `_`, strip underscores
  3. Si vacio, retornar "section"
  4. Si excede max_length: truncar y anadir `_` + sha256[:8]
- **Deps:** `re`, `hashlib`
- **Tests:** texto normal, texto con caracteres especiales, texto muy largo, texto vacio, texto CJK
