Actualizado 4 junio 2026 · 12 min de lectura
Crear PDF desde HTML y CSS con API REST (sin instalar nada)
Convertir HTML a PDF es una necesidad común: facturas, reportes, certificados, contratos. Pero las soluciones tradicionales requieren instalar software pesado en tu servidor.
En este tutorial te muestro cómo convertir cualquier HTML+CSS a PDF profesional con una sola llamada API. Sin wkhtmltopdf, sin Puppeteer, sin Docker.
Por qué no usar wkhtmltopdf o Puppeteer
| Herramienta | Problema |
|---|---|
| wkhtmltopdf | Proyecto abandonado (sin actualizaciones desde 2020). Vulnerabilidades de seguridad conocidas. Renderizado basado en WebKit antiguo: no soporta flexbox ni grid. |
| Puppeteer / Playwright | Requiere instalar Chromium (+300MB). Lento (2-5 segundos por PDF). Consume mucha RAM. Difícil de escalar. |
| ReportLab | No acepta HTML. Debes posicionar cada elemento manualmente con coordenadas X/Y. |
| API externa (Reportia) | Envías HTML → recibes PDF. Sin instalar nada. ~200ms por documento. |
Paso 1: Escribe tu plantilla HTML
Puedes usar cualquier HTML válido con CSS moderno. Flexbox, grid, variables CSS, @media print — todo funciona.
<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: system-ui; padding: 40px; color: #1a1a1a; }
.header { display: flex; justify-content: space-between; border-bottom: 2px solid #059669; padding-bottom: 20px; }
.logo { font-size: 24px; font-weight: 700; color: #059669; }
table { width: 100%; border-collapse: collapse; margin-top: 30px; }
th { background: #f0fdf4; text-align: left; padding: 10px; }
td { padding: 10px; border-bottom: 1px solid #e5e7eb; }
.total { font-size: 20px; font-weight: 700; text-align: right; margin-top: 20px; }
</style>
</head>
<body>
<div class="header">
<div class="logo">Mi Empresa</div>
<div>Factura #001<br>Fecha: {{ fecha }}</div>
</div>
<table>
<thead><tr><th>Concepto</th><th>Cantidad</th><th>Precio</th></tr></thead>
<tbody>
{% for item in items %}
<tr><td>{{ item.desc }}</td><td>{{ item.qty }}</td><td>${{ item.price }}</td></tr>
{% endfor %}
</tbody>
</table>
<p class="total">Total: ${{ total }}</p>
</body>
</html>
Las variables entre {{ }} son Jinja2 — se reemplazan automáticamente con los datos que envíes.
Paso 2: Envía el HTML a la API
Con cURL
curl -X POST https://reportia.4l3.org/v1/render \
-H "Content-Type: application/json" \
-d '{
"template_html": "<tu HTML aquí>",
"data": {
"fecha": "2026-06-04",
"items": [
{"desc": "Diseño web", "qty": 1, "price": 15000},
{"desc": "Hosting anual", "qty": 1, "price": 2400}
],
"total": 17400
},
"format": "pdf"
}' \
--output factura.pdf
Con Python
import requests
html = open("mi-plantilla.html").read()
r = requests.post("https://reportia.4l3.org/v1/render", json={
"template_html": html,
"data": {
"fecha": "2026-06-04",
"items": [
{"desc": "Diseño web", "qty": 1, "price": 15000},
{"desc": "Hosting anual", "qty": 1, "price": 2400},
],
"total": 17400,
},
"format": "pdf",
})
with open("factura.pdf", "wb") as f:
f.write(r.content)
print(f"PDF generado: {len(r.content):,} bytes")
Con JavaScript (Node.js)
const fs = require('fs');
const res = await fetch('https://reportia.4l3.org/v1/render', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
template_html: fs.readFileSync('mi-plantilla.html', 'utf8'),
data: {
fecha: '2026-06-04',
items: [
{ desc: 'Diseño web', qty: 1, price: 15000 },
{ desc: 'Hosting anual', qty: 1, price: 2400 },
],
total: 17400,
},
format: 'pdf',
}),
});
fs.writeFileSync('factura.pdf', Buffer.from(await res.arrayBuffer()));
console.log('PDF generado');
Con PHP
$html = file_get_contents('mi-plantilla.html');
$ch = curl_init('https://reportia.4l3.org/v1/render');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_POSTFIELDS => json_encode([
'template_html' => $html,
'data' => [
'fecha' => '2026-06-04',
'items' => [
['desc' => 'Diseño web', 'qty' => 1, 'price' => 15000],
],
'total' => 15000,
],
'format' => 'pdf',
]),
CURLOPT_RETURNTRANSFER => true,
]);
$pdf = curl_exec($ch);
file_put_contents('factura.pdf', $pdf);
echo "PDF: " . strlen($pdf) . " bytes\n";
Prueba ahora mismo
50 PDFs gratis al mes. Sin tarjeta. Sin registro para la API básica.
Abrir el editor visual → | Ver documentación API →Paso 3: Formatos de salida
Además de PDF, puedes recibir el documento en otros formatos:
- PDF — "format": "pdf" (default)
- Excel — "format": "xlsx" (si tu HTML tiene tablas)
- CSV — "format": "csv"
- HTML — "format": "html" (renderizado final sin variables)
Ventajas vs. soluciones self-hosted
- Sin dependencias: no instalas nada. Una llamada HTTP desde cualquier lenguaje.
- Rápido: ~200ms por PDF. Sin cold start, sin esperar que Chromium arranque.
- CSS moderno: flexbox, grid, variables CSS, @media print.
- Jinja2 nativo: loops, condicionales, filtros. No necesitas pre-renderizar.
- Multi-formato: el mismo template genera PDF, Excel, CSV o HTML.
Preguntas frecuentes
¿Puedo usar imágenes?
Sí. Usa URLs absolutas (<img src="https://...">) o imágenes en base64. Las imágenes relativas no se resuelven porque no hay servidor local.
¿Soporta páginas múltiples?
Sí. Usa page-break-before: always en CSS para forzar saltos de página. También puedes configurar el tamaño de página con @page { size: letter; margin: 2cm; }.
¿Funciona con tablas largas?
Sí. Las tablas se paginan automáticamente. Los headers se repiten en cada página si usas <thead>.
¿Cuánto cuesta?
Plan Free: 50 PDFs/mes con marca de agua. Pro: $9 USD/mes, 1,000 PDFs sin marca. Lifetime: $25 USD pago único. Ver planes →