Update: nueva version Horux Despachos

This commit is contained in:
consultoria-as
2026-04-27 22:09:36 -06:00
commit 6b36db1403
614 changed files with 125926 additions and 0 deletions

View 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);
});