---
name: render_paper_pdf
kind: function
lang: py
domain: datascience
version: "1.0.0"
purity: impure
signature: "def render_paper_pdf(paper_dir: str) -> dict"
description: "Convierte un paper académico IMRaD escrito en Markdown (papers/<slug>/paper.md, con frontmatter YAML opcional title/authors/date/abstract + cuerpo) en un PDF papers/<slug>/out/paper.pdf. REUTILIZA el paginador de flujo del paquete automatic_eda (el mismo motor del PDF móvil A5 de los informes EDA): no reimplementa paginación ni toca matplotlib. Cada sección IMRaD (encabezado de nivel 1, p.ej. # Introduction, # Methods) se mapea a un Chapter que empieza en página nueva; el motor parsea por sí mismo headings, listas, tablas pipe, párrafos y **negrita** dentro del texto. Como el motor NO entiende la sintaxis de imagen Markdown ![alt](src), esta función detecta esas líneas y las parte en bloques Image separados, resolviendo el src relativo a base_dir y base_dir/figures/. La portada (si hay título) lista autores y fecha (DD/MM/AAAA si parseable) más el abstract. dict-no-throw: nunca lanza, devuelve {status, pdf_path, n_pages, note}."
tags: [papers, pdf, academic, render, report, imrad, mobile, automatic-eda, markdown, no-cut, matplotlib, datascience, python]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: "error_go_core"
imports: [os, re, datetime, yaml, "datascience.automatic_eda"]
params:
  - name: paper_dir
    desc: "ruta al directorio del paper (papers/<slug>/, del que se lee paper.md) O directamente la ruta a un archivo paper.md (cualquier ruta terminada en .md). El directorio base para resolver figuras y escribir el PDF es el dirname del paper.md. Si el paper.md no existe (incluida una ruta totalmente inexistente) devuelve status='error' sin crash."
output: "dict (nunca lanza): {status: 'ok'|'error', pdf_path: str|None, n_pages: int, note: str}. En éxito status='ok', pdf_path es la ruta del PDF escrito (<base_dir>/out/paper.pdf) y n_pages el total de páginas. En error status='error', pdf_path=None, n_pages=0 y note explica la causa (paper.md no encontrado, fallo del motor, o excepción inesperada)."
tested: true
tests: ["test_golden_genera_pdf_con_portada_y_secciones", "test_edge_sin_frontmatter_ni_figuras", "test_edge_path_inexistente_no_revienta", "test_edge_figura_inexistente_degrada", "test_acepta_ruta_directa_al_md"]
test_file_path: "python/functions/datascience/render_paper_pdf_test.py"
file_path: "python/functions/datascience/render_paper_pdf.py"
---

## Ejemplo

```python
from datascience import render_paper_pdf

# Estructura del paper:
#   papers/zz-demo/paper.md          (frontmatter YAML + cuerpo IMRaD)
#   papers/zz-demo/figures/fig1.png  (figuras referenciadas con ![alt](figures/fig1.png))
#
# paper.md:
#   ---
#   title: A Minimal IMRaD Paper
#   authors: [Ada Lovelace, Alan Turing]
#   date: 2026-06-30
#   abstract: Demostramos que el motor pagina un paper sin cortar nada.
#   ---
#   # Introduction
#   Texto con **negrita** y una lista:
#   - Punto uno.
#   ![Figura 1](figures/fig1.png)
#   # Methods
#   | Métrica | Valor |
#   | --- | --- |
#   | Precisión | 0.91 |

res = render_paper_pdf("papers/zz-demo")
print(res["status"], res["n_pages"], res["pdf_path"])
# -> ok 3 papers/zz-demo/out/paper.pdf

# También acepta la ruta directa al .md:
render_paper_pdf("papers/zz-demo/paper.md")
```

## Cuando usarla

Cuando tengas un paper académico (o cualquier documento IMRaD) escrito en
Markdown y quieras un **PDF móvil A5 listo para leer**, sin montar LaTeX ni
configurar un pipeline de pandoc. Úsala después de redactar `paper.md` con su
frontmatter (título, autores, fecha, abstract) y secciones de nivel 1; obtienes
`out/paper.pdf` con portada, una página nueva por sección IMRaD, tablas que se
parten repitiendo la cabecera y figuras escaladas para caber enteras —
garantía de no-corte heredada del motor `automatic_eda`. Es la capa de
presentación PDF del grupo `papers`.

## Gotchas

- **Impura**: escribe `out/paper.pdf` (y crea el directorio `out/`) junto al
  `paper.md`. Necesita **matplotlib** instalado en el venv (lo usa el motor
  `automatic_eda.render_pdf` con backend headless `Agg`; corre en agentes/CI sin
  display). `pyyaml` es opcional: si falta, el frontmatter se parsea con un
  parser line-based `clave: valor` degradado.
- **Reutiliza el motor `automatic_eda.render_pdf`**: NO reimplementa paginación
  ni toca matplotlib. `render_pdf` no tiene ID propio en el registry (es parte
  del paquete de soporte `automatic_eda`), por eso `uses_functions` queda vacío;
  la dependencia real es ese motor del paquete.
- **Nunca lanza** (dict-no-throw): `paper.md` inexistente → `{status:"error",
  pdf_path:None, note:"paper.md no encontrado: ..."}`; cualquier excepción
  inesperada → `{status:"error", note:"fallo: ..."}`. Frontmatter ausente o
  incompleto degrada limpio (sin portada, el cuerpo entero se pagina).
- **Figuras relativas a `figures/`**: el `src` de `![alt](src)` se resuelve
  probando `<base_dir>/<src>` y `<base_dir>/figures/<basename>`; usa el primero
  que exista. Si ninguno existe, el motor **degrada** dibujando
  "(imagen no encontrada: ...)" — el PDF se genera igual, no crashea. Las URLs
  `http(s)` se dejan como texto Markdown, no se descargan.
- **Solo imágenes en línea propia**: el motor `_place_markdown` NO entiende
  `![alt](src)`; esta función solo convierte a `Image` las líneas cuyo único
  contenido es la imagen. Una imagen embebida a mitad de un párrafo se quedaría
  como texto crudo.
- **A5 portrait mobile-first**: el formato (tamaño de página, tipografía, pie
  `Capítulo · vX.Y.Z`) lo fija el motor EDA y no es configurable desde aquí.
