#!/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: 'mi_empresa_plus', }, 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 = `
Generados desde los templates en apps/api/src/services/email/templates/ con datos de ejemplo. Cada link abre el HTML renderizado tal como llegaría al inbox del cliente.
Si modificas un template, vuelve a correr pnpm email:preview para regenerar.