Certificados de finalización, premios de logro y diplomas de participación son esenciales para cursos en línea, talleres, conferencias y programas de capacitación corporativa. En lugar de diseñar cada certificado manualmente en una herramienta como Canva, puedes construir una aplicación web que los genere automáticamente a partir de una plantilla. En este tutorial, construiremos un generador de certificados basado en React que produce certificados PDF profesionales usando TongoRender.
Lo Que Construiremos
- Una plantilla de certificado diseñada con HTML y CSS
- Un formulario React para ingresar datos del destinatario
- Una ruta de API que renderiza el certificado como PDF
Paso 1: Diseño de la Plantilla de Certificado
export function certificateTemplate({ recipientName, courseName, completionDate, instructorName, certificateId }) {
return `
<!DOCTYPE html>
<html lang="es">
<head>
<link href="https://fonts.googleapis.com/css2?family=Great+Vibes&family=Montserrat:wght@300;400;600&display=swap" rel="stylesheet">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { width: 297mm; height: 210mm; font-family: 'Montserrat', sans-serif; }
.certificate {
width: 100%; height: 100%;
border: 3px solid #1a365d; padding: 15mm;
background: linear-gradient(135deg, #fefefe 0%, #f7fafc 100%);
}
.inner-border {
width: 100%; height: 100%;
border: 1px solid #c9a84c; padding: 15mm;
display: flex; flex-direction: column;
align-items: center; justify-content: center; text-align: center;
}
.title { font-family: 'Great Vibes', cursive; font-size: 48pt; color: #1a365d; }
.subtitle { font-size: 14pt; color: #4a5568; letter-spacing: 4px; text-transform: uppercase; margin: 10mm 0 5mm; }
.recipient { font-family: 'Great Vibes', cursive; font-size: 36pt; color: #c9a84c; margin: 5mm 0; }
.description { font-size: 12pt; color: #4a5568; max-width: 500px; line-height: 1.6; }
.cert-id { position: absolute; bottom: 10mm; right: 15mm; font-size: 8pt; color: #a0aec0; }
</style>
</head>
<body>
<div class="certificate">
<div class="inner-border">
<div class="title">Certificado de Finalización</div>
<div class="subtitle">Se otorga con orgullo a</div>
<div class="recipient">${recipientName}</div>
<div class="description">
Por completar exitosamente el curso
<strong>${courseName}</strong>
el ${completionDate}.
</div>
</div>
<div class="cert-id">ID del Certificado: ${certificateId}</div>
</div>
</body>
</html>`;
}
Paso 2: Construir el Formulario React
import { useState } from 'react';
export default function CertificateForm() {
const [formData, setFormData] = useState({
recipientName: '', courseName: '', completionDate: '', instructorName: '',
});
const [loading, setLoading] = useState(false);
const handleSubmit = async (e) => {
e.preventDefault();
setLoading(true);
const response = await fetch('/api/generate-certificate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(formData),
});
const blob = await response.blob();
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `certificado-${formData.recipientName.replace(/\s+/g, '-')}.pdf`;
a.click();
setLoading(false);
};
return (
<form onSubmit={handleSubmit}>
<input placeholder="Nombre del Destinatario" required
value={formData.recipientName}
onChange={e => setFormData({...formData, recipientName: e.target.value})} />
<input placeholder="Nombre del Curso" required
value={formData.courseName}
onChange={e => setFormData({...formData, courseName: e.target.value})} />
<input type="date" required
value={formData.completionDate}
onChange={e => setFormData({...formData, completionDate: e.target.value})} />
<input placeholder="Nombre del Instructor" required
value={formData.instructorName}
onChange={e => setFormData({...formData, instructorName: e.target.value})} />
<button type="submit" disabled={loading}>
{loading ? 'Generando...' : 'Generar Certificado'}
</button>
</form>
);
}
Paso 3: Crear la Ruta de API
import { certificateTemplate } from '../../templates/certificate';
import { randomUUID } from 'crypto';
export default async function handler(req, res) {
const { recipientName, courseName, completionDate, instructorName } = req.body;
const certificateId = randomUUID().split('-')[0].toUpperCase();
const html = certificateTemplate({ recipientName, courseName, completionDate, instructorName, certificateId });
const pdfResponse = await fetch('https://api.tongorender.io/v1/pdf', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': process.env.TONGORENDER_API_KEY,
},
body: JSON.stringify({
html, format: 'A4', landscape: true,
margin: { top: '0mm', bottom: '0mm', left: '0mm', right: '0mm' },
printBackground: true,
}),
});
const pdfBuffer = Buffer.from(await pdfResponse.arrayBuffer());
res.setHeader('Content-Type', 'application/pdf');
res.send(pdfBuffer);
}
Paso 4: Generación en Lote
const csv = require('csv-parse/sync');
async function batchGenerate(csvPath) {
const records = csv.parse(fs.readFileSync(csvPath), { columns: true });
for (const record of records) {
const pdf = await generateCertificate(record);
fs.writeFileSync(`certificates/${record.name.replace(/\s+/g, '-')}.pdf`, pdf);
console.log(`Certificado generado para ${record.name}`);
}
}
Consejos de Diseño para Certificados
- Usa fuentes decorativas con moderación — Fuentes cursivas como Great Vibes funcionan para nombres y títulos, pero el texto del cuerpo debe ser limpio y legible.
- Incluye un ID único — Hace que los certificados sean verificables.
- Imprime colores de fondo — Establece
printBackground: trueen las opciones de la API. - Prueba con nombres largos — Algunos destinatarios tienen nombres muy largos. Usa dimensionamiento responsivo de fuente.
- Agrega un código QR — Vincula a una URL de verificación para mayor autenticidad.
TongoRender renderiza web fonts, gradientes CSS y layouts complejos fielmente, haciéndolo ideal para la generación de certificados.
Construye tu generador de certificados con TongoRender — 100 renderizaciones gratuitas al mes, sin necesidad de tarjeta de crédito.