Communitygithub.com

mariosakka/report-to-pdf

Convierte un informe en Markdown (encabezados, párrafos, listas, bloques de código con resaltado de sintaxis Pygments, tablas, etc.) en un PDF con estilo profesional y determinista, incluyendo portada, encabezado/pie de página, tabla de contenido interactiva y tema oscuro opcional.

¿Qué es report-to-pdf?

Esta habilidad para agentes de IA (como Claude Code, Cursor o Codex) transforma archivos Markdown en documentos PDF con un diseño profesional y consistente. Es ideal para desarrolladores y usuarios que necesitan exportar informes, reseñas o documentación a PDF de forma repetible, sin variaciones de formato entre ejecuciones. El script parsea un subconjunto de Markdown (encabezados, listas, bloques de código con resaltado mediante Pygments, tablas con bordes y rayas, imágenes, matemáticas inline y display) y genera un PDF con portada personalizable (logo, título, subtítulo, versión), tabla de contenido interactiva con enlaces a páginas, encabezado y pie de página dinámicos, y soporte para tema oscuro. Compatible con cualquier sistema que ejecute Python 3, el script instala automáticamente las dependencias necesarias (fpdf2, Pillow, Pygments). Es la herramienta perfecta para convertir "este Markdown en un PDF" o "hacer que este informe se vea profesional" cada vez.

Compatible con✓Claude Code~Codex CLI✓Cursor
npx skills add https://github.com/mariosakka/my-agent-skills/tree/HEAD/plugins/general/skills/report-to-pdf

Preguntar en tu IA favorita

Abre un nuevo chat con esta habilidad de agente ya precargada.

Vista previa

Del README del skill

alt
alt

Documentación

report-to-pdf — De informe Markdown a PDF profesional

Renderizador determinista de Markdown a PDF, controlado por script. Analiza un subconjunto restringido de Markdown de "informe" con un pequeño analizador de bloques hecho a mano (sin biblioteca Markdown externa) y lo maqueta a través de fpdf2 con las mismas convenciones visuales que el generador build-user-guide.py del módulo ColeteOnline Magento: portada, encabezado/pie de página, TDC, barras de acento para encabezados de sección, bloques de código sombreados, tablas con bordes y rayas. La lógica de diseño nunca cambia entre ejecuciones con la misma entrada: mismo informe de entrada, mismo PDF de salida, siempre, sin LLM en el bucle de renderizado.

Cuándo usarlo

  • "Convierte esta revisión en Markdown a un PDF para el mentor."
  • "Exporta mi informe a un PDF profesional."
  • "Quiero que cada vez se vea igual, sin que un LLM lo reformatee cada ejecución."
  • Cualquier informe/escrito en .md que tenga encabezados, listas, bloques de código o tablas y necesite una versión PDF de calidad para cliente/mentor/marca.

Cómo ejecutar

python3 <directorio-del-skill>/scripts/build_report_pdf.py <entrada.md> [opciones]

(<directorio-del-skill> es el directorio base de este skill, la carpeta que contiene este SKILL.md).

Opciones:

  • -o / --output <ruta> — ruta del PDF de salida (por defecto: <entrada>.pdf junto al archivo fuente).
  • --logo <ruta> — imagen de logo opcional (PNG/WebP/JPEG, cualquier formato que pueda abrir Pillow) en la portada. Repite la bandera para varios logos, dibujados lado a lado en una fila (también configurable mediante frontmatter logo: a.png, b.png, rutas relativas al archivo .md).
  • --title, --subtitle, --version, --vendor, --author — sobrescribe los valores correspondientes del frontmatter (ver más abajo); las banderas de CLI tienen prioridad.
  • --cover center|left — diseño de la portada. Todo el bloque de título (logo, título, subtítulo, autor, versión) se centra verticalmente en ambos modos; center (por defecto) también lo centra horizontalmente, left lo alinea a la izquierda. También configurable mediante frontmatter cover:.
  • --no-header — omite el encabezado de página y el espacio que reserva, de modo que el contenido de cada página comience en el mismo margen superior (también configurable mediante frontmatter header: false).
  • --accent "R,G,B" — sobrescribe el color de acento usado para la barra superior de la portada y las barras de acento de los encabezados de sección (por defecto 220,50,50, rojo ColeteOnline).
  • --dark — renderiza con el tema oscuro en lugar del tema claro predeterminado (también configurable mediante frontmatter theme: dark; --dark tiene prioridad si ambos están presentes). Ver "Tema oscuro" más abajo.

El script instala automáticamente fpdf2, Pillow y Pygments mediante pip install --user si falla alguna importación, reflejando la autoinstalación de Pillow del script de referencia. Los tres fueron verificados como importables y el script se ejecutó de principio a fin durante el desarrollo (ver Pruebas más abajo).

Convenciones de Markdown que espera

Bloque de frontmatter opcional al principio del archivo:

---
title: Título de mi informe
subtitle: Un subtítulo de una línea
version: 1.0
vendor: ColeteOnline SRL
accent: 220,50,50
---

Todas las claves son opcionales y tienen valores predeterminados sensatos:

  • title — se usa el primer encabezado # H1 encontrado en el cuerpo, luego el nombre del archivo de entrada.
  • subtitle — se omite (no se muestra en la portada).
  • version — se usa la fecha de hoy (AAAA-MM-DD); establece version: none para ocultar la línea de versión.
  • vendor — se omite (la línea de copyright se omite en el pie de página de la portada si no se establece).
  • author — nombre(s) del autor(es), se muestra en la portada debajo del subtítulo (ej. author: Jane Doe, John Smith). Se omite si no se establece.
  • cover — center (por defecto) o left; ver --cover arriba.
  • header — false omite el encabezado de página; ver --no-header.
  • justify — true justifica el texto del cuerpo, los encabezados y las celdas de las tablas; el valor predeterminado es alineado a la izquierda (el texto justificado estira los espacios entre palabras en líneas envueltas cortas, ej. un encabezado de dos líneas).
  • accent — se usa el rojo de marca ColeteOnline 220,50,50.

Cuerpo Markdown y cómo se renderiza cada elemento:

MarkdownRenderizado como
# TítuloEl primer # en el archivo es solo el título de la portada (se consume, no se repite en el cuerpo). Cualquier # posterior es un encabezado principal real (ver más abajo).
# Sección Principal (después del primer #)Encabezado principal. Siempre comienza su propia página nueva (a menos que el cursor ya esté en la parte superior de una página en blanco), gran barra izquierda de color, negrita 16pt, texto negro — y obtiene una entrada en la Tabla de Contenido. Úsalo solo para divisiones de nivel superior genuinas; cada una cuesta una página.
## SubsecciónSubencabezado. Negrita 13pt, texto de color acento con una subrayado corto de color acento (visualmente distinto de los niveles superior e inferior) — no fuerza un salto de página, solo continúa el flujo. Aún obtiene una entrada en la TDC. Tiene su propia comprobación de "no dejar un encabezado al final de una página".
### Sub-subsección (y ####+)Encabezado terciario. Negrita 12pt, gris neutro, regla delgada de ancho completo, sin salto de página, sin entrada en la TDC (mantiene la TDC escaneable).
Texto de párrafoPárrafo de cuerpo justificado a la izquierda, 10pt.
- elemento / * elementoLista con viñetas (•).
1. elementoLista numerada.
```lenguaje ... ```Bloque de código monoespaciado sombreado y con bordes; la etiqueta de lenguaje (si está presente) se imprime como una pequeña etiqueta en cursiva sobre el bloque y dirige el resaltado de sintaxis real de Pygments — ver más abajo.
`a
--- / *** / ___ en su propia líneaEspaciador vertical (no un salto de página).
<br> en su propia líneaUna línea de cuerpo en blanco explícita (5.5 mm). Apila varias para más espacio. El renderizador no agrega espacios propios al inicio de una página, así que usa esto cuando quieras espacio extra.
<!-- pagebreak --> en su propia líneaIniciar una nueva página (no hace nada si ya está al inicio de una).
![alt](ruta) en su propia líneaImagen centrada y con bordes, escalada para ajustarse; las rutas relativas se resuelven con respecto a la carpeta del archivo .md. PNG/JPEG/WebP y SVG (ver "Figuras, recuadros y matemáticas"). Una imagen rasterizada sin procesar de menos de 900 px de ancho tiene un tamaño máximo de ~130x105 mm; una figura (imagen seguida de un pie de foto), cualquier SVG y cualquier imagen de 900 px o más puede usar el ancho completo del texto (altura máxima 120 mm). Una figura que no cabe en el espacio restante de la página se reduce para que quepa (hasta el 70 % de su altura) en lugar de saltar a la página siguiente y dejar un espacio.
*Figura: ...* (una línea completa en cursiva justo después de una imagen)El pie de foto de la imagen: pequeño, centrado, en cursiva, mantenido en la misma página que la imagen. El texto alternativo nunca se imprime.
> texto líneasRecuadro de definición: un recuadro con bordes y ligeramente sombreado por cada blockquote (los términos en negrita y las matemáticas inline funcionan dentro; una línea > sin nada después comienza un nuevo párrafo en el mismo recuadro).
$...$Matemáticas inline, convertidas a texto Unicode (letras griegas, ≤ ≥ × ·, sub/superíndices, \frac{a}{b} -> (a)/b, \sqrt, \text{}, acentos). Un precio como $5 no son matemáticas.
$$...$$ (una línea, o en sus propias líneas)Matemáticas en display, una imagen centrada del primer backend de matemáticas que funcione (ver "Figuras, recuadros y matemáticas").
Bloque sangrado con 4 espacios después de una línea en blancoBloque de código sin etiqueta de lenguaje (las exportaciones de cuadernos imprimen la salida de celda de esta manera). No se aplica justo después de una lista.
**negrita**Texto en negrita real, a través del renderizador markdown=True incorporado de fpdf2.
[texto](url)Una anotación de enlace PDF real y clicable — solo se muestra el texto de visualización, no la URL sin procesar — con estilo en un color de enlace distinto.
*cursiva* / _cursiva_Cursiva renderizada (convertida internamente a la forma nativa __italic__ de fpdf2).
`código`Las comillas invertidas de código inline se eliminan — no hay soporte monoespaciado inline, se renderiza como texto sin formato.

Los encabezados, párrafos, viñetas, elementos numerados y celdas de tabla se renderizan a través de la ruta markdown=True de fpdf2, por lo que negrita y [texto](url) funcionan en todas partes donde aparece texto, incluso dentro de encabezados y celdas de tabla. Los bloques de código delimitados son la única excepción: su contenido se renderiza de manera completamente literal (sin análisis de markdown), por lo que una muestra de código que contenga **, __ o []() literales no se malinterpretará.

Figuras, recuadros y matemáticas

Escrito para que el acompañante markdown de un cuaderno (salida de nbconvert) se renderice directamente: report-to-pdf notebook.md reemplaza un paso de PDF basado en Chrome separado.

Imágenes SVG. ![alt](figure.svg) funciona. El dibujo SVG propio de fpdf2 omite las puntas de flecha <marker>, filtros y máscaras, dibuja texto en una fuente básica (sin griego) y colorea mal el texto sin fill, por lo que cada SVG se rasteriza a 2.5x y se incrusta como PNG, usando, en orden: un Chrome/Edge sin cabeza (se encuentra automáticamente; establece REPORT_PDF_BROWSER en una ruta de navegador para sobrescribir), luego cairosvg si es importable. Sin ninguno, el SVG se dibuja de forma nativa por fpdf2 y se muestra una advertencia de que puede estar incompleto. La rasterización se almacena en caché por ejecución.

Backends de matemáticas en display (--math auto|latex|mathtext|unicode, o frontmatter math:; por defecto auto). El script imprime qué backend se usó.

  1. latex: LaTeX real cuando pdflatex, xelatex o tectonic están en PATH (más pdftocairo, PyMuPDF o dvisvgm para hacer la imagen), o latex + dvipng. Cada fórmula se compila con standalone, amsmath, amssymb y xcolor a 12 pt, luego se recorta a un PNG transparente de 300 ppp en el color del texto. Instalación: MiKTeX (Windows), TeX Live (apt install texlive-latex-base texlive-latex-extra poppler-utils, macOS brew install --cask mactex-no-gui), o tectonic (binario único, descarga paquetes bajo demanda; conda install -c conda-forge tectonic o scoop install tectonic; probado de principio a fin en Windows con PyMuPDF como convertidor de imágenes).
  2. mathtext: subconjunto LaTeX de matplotlib (fracciones, sumas, griego, \text{} se asigna a \mathrm). No necesita TeX, solo matplotlib. Entornos como aligned no son compatibles y fallan.
  3. unicode: la misma conversión de texto que las matemáticas inline, centrada.

Un backend que falta o falla (por ejemplo, una fórmula que TeX rechaza) cae silenciosamente al siguiente; --math latex aún se degrada de la misma manera.

Prueba: python tests/run_checks.py (conversión de matemáticas, analizador, enrutamiento SVG, retroceso de backend con PATH simulado, compilación de extremo a extremo de tests/fixture.md). Renderiza un acompañante: python scripts/build_report_pdf.py notebook.md -o notebook.pdf.

Resaltado de sintaxis

Los bloques de código delimitados se tokenizan con lexers reales de Pygments (no un resaltador de expresiones regulares hecho a mano — el código es exactamente el tipo de cosa que un analizador ad hoc pequeño puede hacer sutilmente mal: escapes de cadenas, comentarios anidados, literales de plantilla). La etiqueta de lenguaje en el delimitador (```typescript, ```sql, ```python, ...) selecciona el lexer; un puñado de alias comunes se normalizan (ts/tsx → typescript, js/jsx → javascript, sh/shell/zsh → bash, yml → yaml, prisma → graphql — el lexer incorporado más cercano, y una buena coincidencia en la práctica, ya que la sintaxis @attribute(...) de Prisma y la sintaxis @directive(...) de GraphQL se tokenizan casi de manera idéntica).

El resaltado es una ventaja, nunca un requisito estricto para renderizar un bloque de código: una etiqueta de lenguaje faltante, un lenguaje que Pygments no reconoce (incluso después de la tabla de alias), o Pygments mismo no siendo importable, todo cae al comportamiento exacto anterior al resaltado: todo el bloque en un solo color plano, sin fallos, sin contenido faltante.

Paleta de colores (elegida para legibilidad contra el fondo gris claro del bloque de código, no vinculada a ningún tema de Pygments en particular):

Categoría de tokenColor
Palabras clave (interface, async, const, NOT NULL, palabras clave de tipo como string/number)Magenta
CadenasVerde
NúmerosMarrón/naranja
ComentariosGris
Nombres de funciones/clases, decoradores/atributos (@map, @relation)Azul / marrón
Incorporados (Promise, etc.)Verde azulado
Todo lo demás (identificadores, puntuación, operadores)El color base del código

Un tipo de token sin una entrada exacta en la paleta se resuelve recorriendo la jerarquía de tipos de token de Pygments (por ejemplo, Keyword.Declaration no tiene entrada propia, pero su padre Keyword sí) hasta que se encuentra una coincidencia; la raíz Token siempre coincide, por lo que esto nunca falla en resolver a algún color.

La lógica de ajuste de escala monoespaciada (una línea de código demasiado ancha para la página reduce la fuente de todo el bloque de manera uniforme en lugar de desbordarse) no se ve afectada por el resaltado: mide anchos de caracteres sin formato, que no cambian según el color en el que se dibuje un token.

Tema oscuro

Pasa --dark, o establece theme: dark en el frontmatter, para renderizar todo el documento en un tema oscuro en lugar del tema claro predeterminado. Cada color en el documento cambia a su contraparte adecuada para oscuro, no solo los bloques de código:

  • El fondo de la página se rellena de oscuro (fpdf2 no tiene una configuración nativa de "color de fondo de página", por lo que esto se hace rellenando la página completa en el gancho header(), antes de que se dibuje cualquier otra cosa encima, incluida la portada).
  • El texto del cuerpo, los encabezados (los tres niveles), el texto atenuado/pie de página, las líneas de regla, las rayas de cebra de las tablas y el encabezado/pie de página usan un conjunto de colores de modo oscuro separado, no solo una versión atenuada del claro.
  • El resaltado de sintaxis usa su propia paleta de modo oscuro (rosas/verdes/azules más brillantes elegidos para contrastar con el fondo oscuro del código), no los colores del modo claro atenuados.
  • El color del enlace (tanto los enlaces markdown [texto](url) como los enlaces de salto a página de la TDC) cambia a un azul más claro que sigue siendo legible sobre un fondo oscuro.

El color --accent (la barra superior, la barra izquierda del encabezado principal y el texto/subrayado del subencabezado) se usa tal cual en ambos temas: elige uno que funcione contra un fondo blanco y uno casi negro si planeas generar el mismo informe en ambos temas, o sobrescribe --accent por tema si no.

Tabla de Contenido

La TDC rellenada posteriormente (ver "Cómo se renderiza esto" arriba

…

[truncado para traducción]

Skills relacionados