Update: nueva version Horux Despachos
This commit is contained in:
164
apps/api/scripts/preview-emails.mjs
Normal file
164
apps/api/scripts/preview-emails.mjs
Normal file
@@ -0,0 +1,164 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Genera los 8 templates de email como archivos HTML estáticos en
|
||||
* `apps/api/email-previews/` para revisar el diseño en el navegador
|
||||
* sin necesidad de SMTP configurado.
|
||||
*
|
||||
* Uso:
|
||||
* pnpm email:preview
|
||||
*
|
||||
* Tras correr, abre `apps/api/email-previews/index.html` para ver
|
||||
* el listado con links a cada template.
|
||||
*/
|
||||
import { writeFileSync, mkdirSync, rmSync } from 'node:fs';
|
||||
import { resolve, dirname } from 'node:path';
|
||||
import { fileURLToPath, pathToFileURL } from 'node:url';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const ROOT = resolve(__dirname, '..');
|
||||
const OUT_DIR = resolve(ROOT, 'email-previews');
|
||||
|
||||
// Datos de ejemplo realistas para cada template
|
||||
const SAMPLES = {
|
||||
'welcome.html': {
|
||||
label: 'Bienvenida',
|
||||
fixture: { nombre: 'Carlos Hernández', email: 'carlos@empresa.com', tempPassword: 'a3f2c891' },
|
||||
importPath: '../src/services/email/templates/welcome.ts',
|
||||
fnName: 'welcomeEmail',
|
||||
},
|
||||
'password-reset.html': {
|
||||
label: 'Recuperación de contraseña',
|
||||
fixture: { nombre: 'Carlos Hernández', resetUrl: 'https://horuxfin.com/reset-password?token=a8e4f...' },
|
||||
importPath: '../src/services/email/templates/password-reset.ts',
|
||||
fnName: 'passwordResetEmail',
|
||||
},
|
||||
'payment-confirmed.html': {
|
||||
label: 'Pago confirmado',
|
||||
fixture: { nombre: 'Carlos Hernández', amount: 780, plan: 'Business + IA', date: new Date().toLocaleDateString('es-MX') },
|
||||
importPath: '../src/services/email/templates/payment-confirmed.ts',
|
||||
fnName: 'paymentConfirmedEmail',
|
||||
},
|
||||
'payment-failed.html': {
|
||||
label: 'Pago rechazado',
|
||||
fixture: { nombre: 'Carlos Hernández', amount: 780, plan: 'Business + IA' },
|
||||
importPath: '../src/services/email/templates/payment-failed.ts',
|
||||
fnName: 'paymentFailedEmail',
|
||||
},
|
||||
'subscription-cancelled.html': {
|
||||
label: 'Suscripción cancelada',
|
||||
fixture: { nombre: 'Carlos Hernández', plan: 'Business + IA' },
|
||||
importPath: '../src/services/email/templates/subscription-cancelled.ts',
|
||||
fnName: 'subscriptionCancelledEmail',
|
||||
},
|
||||
'subscription-expiring.html': {
|
||||
label: 'Suscripción por vencer',
|
||||
fixture: { nombre: 'Carlos Hernández', plan: 'Business + IA', expiresAt: '15 de mayo, 2026' },
|
||||
importPath: '../src/services/email/templates/subscription-expiring.ts',
|
||||
fnName: 'subscriptionExpiringEmail',
|
||||
},
|
||||
'fiel-notification.html': {
|
||||
label: 'e.firma cargada (admin)',
|
||||
fixture: { clienteNombre: 'Empresa Demo SA de CV', clienteRfc: 'EDE123456AB1' },
|
||||
importPath: '../src/services/email/templates/fiel-notification.ts',
|
||||
fnName: 'fielNotificationEmail',
|
||||
},
|
||||
'weekly-update.html': {
|
||||
label: 'Actualización semanal',
|
||||
fixture: {
|
||||
nombre: 'Carlos Hernández',
|
||||
empresa: 'Empresa Demo SA de CV',
|
||||
periodoLabel: 'Abril 2026',
|
||||
kpis: {
|
||||
ingresos: 285430.50,
|
||||
egresos: 142900.00,
|
||||
utilidad: 142530.50,
|
||||
margen: 49.9,
|
||||
ivaBalance: 18420.00,
|
||||
ivaAFavorAcumulado: 32100.00,
|
||||
cfdisEmitidos: 47,
|
||||
cfdisRecibidos: 23,
|
||||
},
|
||||
alertas: [
|
||||
{ titulo: 'Cliente en lista negra', mensaje: '1 cliente con situación SAT "Definitivo".', prioridad: 'alta' },
|
||||
{ titulo: 'Concentración alta de proveedores', mensaje: 'IHH = 6,840. Más del 50% del gasto en 1 proveedor.', prioridad: 'media' },
|
||||
{ titulo: 'Pago en efectivo', mensaje: '3 facturas recibidas con forma de pago "01-Efectivo" este mes.', prioridad: 'baja' },
|
||||
],
|
||||
discrepanciasPorMes: [
|
||||
{ label: 'Abril 2026', count: 2 },
|
||||
{ label: 'Marzo 2026', count: 5 },
|
||||
{ label: 'Febrero 2026', count: 0 },
|
||||
{ label: 'Enero 2026', count: 1 },
|
||||
],
|
||||
fechaGeneracion: new Date().toLocaleString('es-MX', { dateStyle: 'long', timeStyle: 'short' }),
|
||||
},
|
||||
importPath: '../src/services/email/templates/weekly-update.ts',
|
||||
fnName: 'weeklyUpdateEmail',
|
||||
},
|
||||
'new-client-admin.html': {
|
||||
label: 'Nuevo cliente registrado (admin)',
|
||||
fixture: {
|
||||
clienteNombre: 'Empresa Demo SA de CV',
|
||||
clienteRfc: 'EDE123456AB1',
|
||||
adminEmail: 'admin@empresademo.com',
|
||||
adminNombre: 'Carlos Hernández',
|
||||
tempPassword: 'a3f2c891',
|
||||
databaseName: 'horux_ede123456ab1',
|
||||
plan: 'business_ia',
|
||||
},
|
||||
importPath: '../src/services/email/templates/new-client-admin.ts',
|
||||
fnName: 'newClientAdminEmail',
|
||||
},
|
||||
};
|
||||
|
||||
async function main() {
|
||||
// Limpia output previo y recrea
|
||||
try { rmSync(OUT_DIR, { recursive: true, force: true }); } catch {}
|
||||
mkdirSync(OUT_DIR, { recursive: true });
|
||||
|
||||
const generated = [];
|
||||
for (const [filename, sample] of Object.entries(SAMPLES)) {
|
||||
const modPath = resolve(__dirname, sample.importPath);
|
||||
const mod = await import(pathToFileURL(modPath).href);
|
||||
const fn = mod[sample.fnName];
|
||||
if (typeof fn !== 'function') {
|
||||
console.error(`[email:preview] FAIL: ${sample.fnName} no exportada en ${modPath}`);
|
||||
continue;
|
||||
}
|
||||
const html = fn(sample.fixture);
|
||||
const outPath = resolve(OUT_DIR, filename);
|
||||
writeFileSync(outPath, html, 'utf8');
|
||||
generated.push({ filename, label: sample.label });
|
||||
console.log(`[email:preview] ✓ ${filename}`);
|
||||
}
|
||||
|
||||
// Index navegable
|
||||
const indexHtml = `<!DOCTYPE html>
|
||||
<html lang="es"><head><meta charset="utf-8"><title>Email previews — Horux 360</title>
|
||||
<style>
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; max-width: 720px; margin: 40px auto; padding: 0 24px; color: #1E293B; }
|
||||
h1 { font-size: 24px; margin-bottom: 8px; }
|
||||
p.muted { color: #64748B; margin-top: 0; }
|
||||
ul { list-style: none; padding: 0; }
|
||||
li { margin: 8px 0; padding: 14px 18px; border: 1px solid #E2E8F0; border-radius: 8px; }
|
||||
li:hover { background: #F8FAFC; }
|
||||
a { color: #2563EB; text-decoration: none; font-weight: 500; }
|
||||
a:hover { text-decoration: underline; }
|
||||
small { color: #94A3B8; font-size: 12px; margin-left: 8px; }
|
||||
</style></head><body>
|
||||
<h1>Email previews — Horux 360</h1>
|
||||
<p class="muted">Generados desde los templates en <code>apps/api/src/services/email/templates/</code> con datos de ejemplo. Cada link abre el HTML renderizado tal como llegaría al inbox del cliente.</p>
|
||||
<ul>
|
||||
${generated.map(g => `<li><a href="${g.filename}">${g.label}</a> <small>(${g.filename})</small></li>`).join('\n ')}
|
||||
</ul>
|
||||
<p class="muted" style="margin-top:32px;font-size:13px;">Si modificas un template, vuelve a correr <code>pnpm email:preview</code> para regenerar.</p>
|
||||
</body></html>`;
|
||||
|
||||
writeFileSync(resolve(OUT_DIR, 'index.html'), indexHtml, 'utf8');
|
||||
console.log(`\n[email:preview] ${generated.length} templates generados.`);
|
||||
console.log(`[email:preview] Abre: ${resolve(OUT_DIR, 'index.html')}`);
|
||||
}
|
||||
|
||||
main().catch(err => {
|
||||
console.error('[email:preview] FAIL:', err);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user