Actualizar server.js
This commit is contained in:
73
server.js
73
server.js
@@ -906,6 +906,79 @@ async function procesarConIA(ownerId, mensajeCliente, datosExpediente) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==========================================
|
||||||
|
// 📱 OTP PARA PORTAL DEL CLIENTE (ACCESO WEB)
|
||||||
|
// ==========================================
|
||||||
|
app.post("/public/auth/request-otp", async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { phone, owner_id } = req.body;
|
||||||
|
if (!phone || !owner_id) return res.status(400).json({ ok: false, error: "Faltan datos" });
|
||||||
|
|
||||||
|
const p = normalizePhone(phone);
|
||||||
|
// Generamos un código aleatorio de 4 dígitos
|
||||||
|
const code = String(Math.floor(1000 + Math.random() * 9000));
|
||||||
|
const codeHash = await bcrypt.hash(code, 10);
|
||||||
|
|
||||||
|
// Lo guardamos en la tabla de login_codes (ponemos user_id NULL porque es un cliente, no un operario)
|
||||||
|
await pool.query(`
|
||||||
|
INSERT INTO login_codes (user_id, phone, code_hash, purpose, expires_at)
|
||||||
|
VALUES (NULL, $1, $2, 'client_portal', NOW() + INTERVAL '15 minutes')
|
||||||
|
`, [p, codeHash]);
|
||||||
|
|
||||||
|
const msg = `👋 *Asistencia Técnica*\n\nTu código seguro de acceso al portal es: *${code}*\n\nCaduca en 15 minutos.`;
|
||||||
|
|
||||||
|
// Enviamos el WhatsApp usando la instancia de la empresa correspondiente
|
||||||
|
sendWhatsAppAuto(p, msg, `cliente_${owner_id}`, false).catch(console.error);
|
||||||
|
|
||||||
|
res.json({ ok: true });
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Error solicitando OTP cliente:", e);
|
||||||
|
res.status(500).json({ ok: false });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post("/public/auth/verify-otp", async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { phone, code, owner_id } = req.body;
|
||||||
|
const p = normalizePhone(phone);
|
||||||
|
|
||||||
|
// 1. Buscamos si el código existe, es de cliente, y no está caducado
|
||||||
|
const q = await pool.query(`
|
||||||
|
SELECT id, code_hash FROM login_codes
|
||||||
|
WHERE phone = $1 AND purpose = 'client_portal' AND consumed_at IS NULL AND expires_at > NOW()
|
||||||
|
ORDER BY created_at DESC LIMIT 1
|
||||||
|
`, [p]);
|
||||||
|
|
||||||
|
if (q.rowCount === 0) return res.status(400).json({ ok: false, error: "Código inválido o caducado" });
|
||||||
|
|
||||||
|
// 2. Comprobamos que el código de 4 dígitos coincide
|
||||||
|
const valid = await bcrypt.compare(String(code), q.rows[0].code_hash);
|
||||||
|
if (!valid) return res.status(400).json({ ok: false, error: "Código incorrecto" });
|
||||||
|
|
||||||
|
// 3. Quemamos el código para que no se pueda usar 2 veces
|
||||||
|
await pool.query("UPDATE login_codes SET consumed_at = NOW() WHERE id = $1", [q.rows[0].id]);
|
||||||
|
|
||||||
|
// 4. Magia: Buscamos si este cliente ya nos ha llamado antes para pre-rellenar su formulario
|
||||||
|
const cleanPhoneForSearch = String(phone).replace(/\D/g, "");
|
||||||
|
const clientQ = await pool.query(
|
||||||
|
"SELECT full_name, addresses FROM clients WHERE phone LIKE $1 AND owner_id = $2 LIMIT 1",
|
||||||
|
[`%${cleanPhoneForSearch}%`, owner_id]
|
||||||
|
);
|
||||||
|
|
||||||
|
let clientData = null;
|
||||||
|
let exists = false;
|
||||||
|
if (clientQ.rowCount > 0) {
|
||||||
|
clientData = clientQ.rows[0];
|
||||||
|
exists = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json({ ok: true, exists, client: clientData });
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Error verificando OTP cliente:", e);
|
||||||
|
res.status(500).json({ ok: false });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// ==========================================
|
// ==========================================
|
||||||
// 🌐 EMBUDO PÚBLICO DE ENTRADA DE CLIENTES
|
// 🌐 EMBUDO PÚBLICO DE ENTRADA DE CLIENTES
|
||||||
// ==========================================
|
// ==========================================
|
||||||
|
|||||||
Reference in New Issue
Block a user