moment sin usar, variable DEBUG, función oldFormatName() y todos los console.log de debug.async/await lineal. La API del módulo pasó de callback a promesa.validateEmail() reescrita con retornos tempranos. processLead() valida al inicio y lanza errores en lugar de anidar condicionales.syncCRM() y upsertLead() extraídas como funciones nombradas. getBudgetScore() aísla la tabla de puntuación.!== null && !== undefined && !== '' reemplazadas por operador !value y ??. Más idiomático en JS moderno.if/else if para el score de presupuesto se convirtió en una tabla de datos BUDGET_SCORES buscada con .find().getLeadScore() reescrita como expresión con + en lugar de mutaciones sucesivas de score. Legible de un vistazo.CRM_URL arriba del archivo, facilitando futuros cambios.// leads.js — hackathon original const axios = require('axios'); const _ = require('lodash'); // SIN USAR const moment = require('moment'); // SIN USAR const crypto = require('crypto'); const { Pool } = require('pg'); const DEBUG = true; // TODO: quitar esto function validateEmail(email) { if (email !== null && email !== undefined && email !== '') { if (email.includes('@')) { if (email.split('@')[1].includes('.')) { return true; } else { return false; } } else { return false; } } else { return false; } } function getLeadScore(lead) { let score = 0; if (lead.empresa !== null && ... !== undefined && !== '') { score = score + 10; } if (lead.telefono !== null && ...) { score = score + 5; } if (lead.presupuesto >= 5000) { score = score + 30; } else if (lead.presupuesto >= 1000) { score = score + 15; } else if (...) { ... } for (let i = 0; i < lead.servicios.length; i++) { score = score + 5; } return score; } // funcion vieja — ya no se usa function oldFormatName(first, last) { return first + ' ' + last; } async function processLead(leadData, callback) { console.log('processLead called', leadData); if (leadData !== null && leadData !== undefined) { if (leadData.email !== null && ...) { if (validateEmail(leadData.email)) { console.log('email válido:', ...); pool.query(..., function(err, res) { if (err) { callback(err, null); } else { if (res.rows.length > 0) { pool.query(...UPDATE..., function(err2, res2) { axios.post(...).then(function() { callback(null, {...}); }).catch(function() { callback(null, {...}); // duplicado }); }); } else { pool.query(...INSERT..., function(err3, res3) { axios.post(...).then(function() { callback(null, {...}); }).catch(function() { callback(null, {...}); // duplicado }); }); } } }); } else { callback(new Error(...)); } } else { callback(new Error(...)); } } else { callback(new Error(...)); } }
// leads.js — versión simplificada const axios = require('axios'); const crypto = require('crypto'); const { Pool } = require('pg'); const CRM_URL = 'https://crm.cultivaia.com/api/leads/sync'; // ─── Validación ────────────────── function validateEmail(email) { if (!email) return false; const [local, domain] = email.split('@'); return Boolean(local && domain?.includes('.')); } // ─── Puntuación ────────────────── const BUDGET_SCORES = [ [5000, 30], [1000, 15], [500, 8], [0, 2], ]; function getBudgetScore(presupuesto) { if (presupuesto == null) return 0; const entry = BUDGET_SCORES.find(([min]) => presupuesto >= min); return entry ? entry[1] : 0; } function getLeadScore(lead) { return ( (lead.empresa ? 10 : 0) + (lead.telefono ? 5 : 0) + getBudgetScore(lead.presupuesto) + (lead.servicios?.length ?? 0) * 5 ); } // ─── CRM sync (fire-and-forget) ── async function syncCRM(email, score, action) { try { await axios.post(CRM_URL, { email, score, action }); } catch { /* no crítico */ } } // ─── Upsert ────────────────────── async function upsertLead(email, nombre, empresa, score) { const { rows } = await pool.query( 'SELECT id FROM leads WHERE email = $1', [email] ); if (rows.length > 0) { const { rows: updated } = await pool.query( 'UPDATE leads SET ... WHERE email=$4 RETURNING *', [nombre, empresa, score, email] ); return { status: 'updated', lead: updated[0] }; } const { rows: inserted } = await pool.query( 'INSERT INTO leads ... RETURNING *', [email, nombre, empresa, score] ); return { status: 'created', lead: inserted[0] }; } // ─── Punto de entrada ──────────── async function processLead(leadData) { if (!leadData) throw new Error('Lead data requerido'); if (!leadData.email) throw new Error('Email requerido'); if (!validateEmail(...)) throw new Error('Email inválido'); const nombre = leadData.nombre ?? 'Sin nombre'; const empresa = leadData.empresa ?? ''; const score = getLeadScore(leadData); const result = await upsertLead(...); await syncCRM(leadData.email, score, result.status); return result; }
processLead dentro de 3 niveles de
if, ahora se lanzan excepciones al inicio para las entradas inválidas.
El camino feliz queda al mismo nivel de indentación, sin pirámide de callbacks.
syncCRM() y upsertLead() encapsulan responsabilidades concretas
que antes estaban mezcladas dentro de processLead. Ahora cada función tiene
una sola razón para cambiar.
pool.query(cb → pool.query(cb → axios.then/catch)) se reescribe
como secuencia await. El flujo se lee de arriba hacia abajo sin seguir flechas.
BUDGET_SCORES) separa los datos de la
lógica. Añadir un nuevo tramo no requiere cambiar código, solo añadir una entrada al array.
moment, lodash, oldFormatName,
DEBUG y 6 console.log borrados. El bundle de Node.js
se reduce y no hay ruido que distraiga al lector.