Actualizar server.js
This commit is contained in:
127
server.js
127
server.js
@@ -1008,18 +1008,21 @@ app.get("/services/active", authMiddleware, async (req, res) => {
|
|||||||
} catch (e) { res.status(500).json({ ok: false }); }
|
} catch (e) { res.status(500).json({ ok: false }); }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// AÑADIDO: Ruta para fijar la cita o el estado operativo (REGLA ESTRICTA)
|
||||||
app.put("/services/set-appointment/:id", authMiddleware, async (req, res) => {
|
app.put("/services/set-appointment/:id", authMiddleware, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
let { date, time, status_operativo, ...extra } = req.body;
|
let { date, time, status_operativo, ...extra } = req.body;
|
||||||
|
|
||||||
const current = await pool.query('SELECT raw_data FROM scraped_services WHERE id = $1 AND owner_id = $2', [id, req.user.accountId]);
|
// REPARADO: Añadido assigned_to al SELECT para poder avisar al operario
|
||||||
|
const current = await pool.query('SELECT raw_data, assigned_to FROM scraped_services WHERE id = $1 AND owner_id = $2', [id, req.user.accountId]);
|
||||||
if (current.rowCount === 0) return res.status(404).json({ ok: false, error: 'No encontrado' });
|
if (current.rowCount === 0) return res.status(404).json({ ok: false, error: 'No encontrado' });
|
||||||
|
|
||||||
const oldDate = current.rows[0].raw_data.scheduled_date || "";
|
const oldDate = current.rows[0].raw_data.scheduled_date || "";
|
||||||
const oldTime = current.rows[0].raw_data.scheduled_time || "";
|
const oldTime = current.rows[0].raw_data.scheduled_time || "";
|
||||||
const newDate = date || "";
|
const newDate = date || "";
|
||||||
const newTime = time || "";
|
const newTime = time || "";
|
||||||
|
let finalAssignedTo = current.rows[0].assigned_to; // Lo guardamos por defecto
|
||||||
|
|
||||||
// BLINDAJE DE ESTADO VACÍO
|
// BLINDAJE DE ESTADO VACÍO
|
||||||
if (status_operativo === "") status_operativo = null;
|
if (status_operativo === "") status_operativo = null;
|
||||||
@@ -1030,7 +1033,7 @@ app.put("/services/set-appointment/:id", authMiddleware, async (req, res) => {
|
|||||||
stName = (statusQ.rows[0]?.name || "").toLowerCase();
|
stName = (statusQ.rows[0]?.name || "").toLowerCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- MOTOR DE EVENTOS ---
|
// --- MOTOR DE EVENTOS Y DESASIGNACIÓN ---
|
||||||
if (stName.includes('asignado')) {
|
if (stName.includes('asignado')) {
|
||||||
const waEnviadoExito = await triggerWhatsAppEvent(req.user.accountId, id, 'wa_evt_assigned');
|
const waEnviadoExito = await triggerWhatsAppEvent(req.user.accountId, id, 'wa_evt_assigned');
|
||||||
if (waEnviadoExito) {
|
if (waEnviadoExito) {
|
||||||
@@ -1039,7 +1042,24 @@ app.put("/services/set-appointment/:id", authMiddleware, async (req, res) => {
|
|||||||
status_operativo = estadoEsperando.rows[0].id;
|
status_operativo = estadoEsperando.rows[0].id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (stName.includes('citado') && newDate !== "") {
|
}
|
||||||
|
else if (stName.includes('pendiente de asignar') || stName.includes('desasignado')) {
|
||||||
|
// DESASIGNAR: Notificar al operario y borrar rastro
|
||||||
|
const oldWorkerId = finalAssignedTo || current.rows[0].raw_data.assigned_to;
|
||||||
|
if (oldWorkerId) {
|
||||||
|
const workerQ = await pool.query("SELECT full_name, phone FROM users WHERE id=$1", [oldWorkerId]);
|
||||||
|
if (workerQ.rowCount > 0) {
|
||||||
|
const w = workerQ.rows[0];
|
||||||
|
const ref = current.rows[0].raw_data.service_ref || current.rows[0].raw_data["Referencia"] || id;
|
||||||
|
const msg = `⚠️ *AVISO DE DESASIGNACIÓN*\n\nHola ${w.full_name}, se te ha retirado el expediente *#${ref}*.\n\nYa no tienes que atender este servicio.`;
|
||||||
|
sendWhatsAppAuto(w.phone, msg, `cliente_${req.user.accountId}`, false).catch(console.error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
extra.assigned_to = null;
|
||||||
|
extra.assigned_to_name = null;
|
||||||
|
finalAssignedTo = null; // Borramos el físico también
|
||||||
|
}
|
||||||
|
else if (stName.includes('citado') && newDate !== "") {
|
||||||
if (oldDate === "") await triggerWhatsAppEvent(req.user.accountId, id, 'wa_evt_date');
|
if (oldDate === "") await triggerWhatsAppEvent(req.user.accountId, id, 'wa_evt_date');
|
||||||
else if (oldDate !== newDate || oldTime !== newTime) await triggerWhatsAppEvent(req.user.accountId, id, 'wa_evt_update');
|
else if (oldDate !== newDate || oldTime !== newTime) await triggerWhatsAppEvent(req.user.accountId, id, 'wa_evt_update');
|
||||||
} else if (stName.includes('camino')) {
|
} else if (stName.includes('camino')) {
|
||||||
@@ -1050,7 +1070,8 @@ app.put("/services/set-appointment/:id", authMiddleware, async (req, res) => {
|
|||||||
|
|
||||||
const updatedRawData = { ...current.rows[0].raw_data, ...extra, "scheduled_date": newDate, "scheduled_time": newTime, "status_operativo": status_operativo };
|
const updatedRawData = { ...current.rows[0].raw_data, ...extra, "scheduled_date": newDate, "scheduled_time": newTime, "status_operativo": status_operativo };
|
||||||
|
|
||||||
await pool.query('UPDATE scraped_services SET raw_data = $1 WHERE id = $2 AND owner_id = $3', [JSON.stringify(updatedRawData), id, req.user.accountId]);
|
// ACTUALIZAMOS RAW DATA Y EL OPERARIO FÍSICO
|
||||||
|
await pool.query('UPDATE scraped_services SET raw_data = $1, assigned_to = $2 WHERE id = $3 AND owner_id = $4', [JSON.stringify(updatedRawData), finalAssignedTo, id, req.user.accountId]);
|
||||||
|
|
||||||
res.json({ ok: true });
|
res.json({ ok: true });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -1059,104 +1080,6 @@ app.put("/services/set-appointment/:id", authMiddleware, async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// AÑADIDO: Ruta para alta de expedientes manuales (Cola o Directo)
|
|
||||||
app.post("/services/manual-high", authMiddleware, async (req, res) => {
|
|
||||||
try {
|
|
||||||
const { phone, name, address, description, guild_id, assigned_to, mode } = req.body;
|
|
||||||
const serviceRef = "MAN-" + Date.now().toString().slice(-6);
|
|
||||||
const rawData = { "Nombre Cliente": name, "Teléfono": phone, "Dirección": address, "Descripción": description, "guild_id": guild_id };
|
|
||||||
const insert = await pool.query(`
|
|
||||||
INSERT INTO scraped_services (owner_id, provider, service_ref, raw_data, status, automation_status, assigned_to)
|
|
||||||
VALUES ($1, 'MANUAL', $2, $3, 'pending', $4, $5) RETURNING id
|
|
||||||
`, [req.user.accountId, serviceRef, JSON.stringify(rawData), mode === 'auto' ? 'manual' : 'completed', mode === 'manual' ? assigned_to : null]);
|
|
||||||
|
|
||||||
// Disparar Bienvenida / Alta
|
|
||||||
triggerWhatsAppEvent(req.user.accountId, insert.rows[0].id, 'wa_evt_welcome');
|
|
||||||
|
|
||||||
res.json({ ok: true });
|
|
||||||
} catch (e) { res.status(500).json({ ok: false }); }
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
app.get("/discovery/mappings", authMiddleware, async (req, res) => {
|
|
||||||
try {
|
|
||||||
const q = await pool.query("SELECT provider, original_key, target_key FROM variable_mappings WHERE owner_id = $1", [req.user.accountId]);
|
|
||||||
res.json(q.rows);
|
|
||||||
} catch (e) { res.status(500).json({ ok: false }); }
|
|
||||||
});
|
|
||||||
|
|
||||||
app.post("/discovery/save", authMiddleware, async (req, res) => {
|
|
||||||
const client = await pool.connect();
|
|
||||||
try {
|
|
||||||
const { provider, mappings } = req.body;
|
|
||||||
await client.query('BEGIN');
|
|
||||||
for (const m of mappings) {
|
|
||||||
await client.query(`INSERT INTO variable_mappings (owner_id, provider, original_key, target_key, is_ignored) VALUES ($1, $2, $3, $4, $5) ON CONFLICT (owner_id, provider, original_key) DO UPDATE SET target_key = EXCLUDED.target_key, is_ignored = EXCLUDED.is_ignored`, [req.user.accountId, provider, m.original, m.target, m.ignored]);
|
|
||||||
}
|
|
||||||
await client.query('COMMIT');
|
|
||||||
res.json({ ok: true });
|
|
||||||
} catch (e) { await client.query('ROLLBACK'); res.status(500).json({ ok: false }); } finally { client.release(); }
|
|
||||||
});
|
|
||||||
|
|
||||||
// AÑADIDO: Asegura que el cliente exista. Si no existe, lo crea y le asigna un token.
|
|
||||||
app.post("/clients/ensure", authMiddleware, async (req, res) => {
|
|
||||||
try {
|
|
||||||
const { phone, name, address } = req.body;
|
|
||||||
const p = normalizePhone(phone);
|
|
||||||
if(!p) return res.status(400).json({ok: false, error: "Sin teléfono"});
|
|
||||||
|
|
||||||
// 1. Buscamos si ya existe
|
|
||||||
const q = await pool.query("SELECT * FROM clients WHERE phone=$1 AND owner_id=$2 LIMIT 1", [p, req.user.accountId]);
|
|
||||||
if (q.rowCount > 0) return res.json({ ok: true, client: q.rows[0] });
|
|
||||||
|
|
||||||
// 2. Si no existe, lo creamos al vuelo en la agenda para que se genere su portal_token
|
|
||||||
const insert = await pool.query(
|
|
||||||
"INSERT INTO clients (owner_id, full_name, phone, addresses) VALUES ($1, $2, $3, $4) RETURNING *",
|
|
||||||
[req.user.accountId, name || 'Asegurado', p, JSON.stringify(address ? [address] : [])]
|
|
||||||
);
|
|
||||||
res.json({ ok: true, client: insert.rows[0] });
|
|
||||||
} catch (e) { res.status(500).json({ ok: false }); }
|
|
||||||
});
|
|
||||||
|
|
||||||
app.get("/clients", authMiddleware, async (req, res) => {
|
|
||||||
try {
|
|
||||||
const { search } = req.query;
|
|
||||||
let query = `SELECT c.*, c.portal_token, (SELECT COUNT(*) FROM services s WHERE s.client_id = c.id) as service_count FROM clients c WHERE c.owner_id = $1`;
|
|
||||||
const params = [req.user.accountId];
|
|
||||||
if (search) { query += ` AND (c.full_name ILIKE $2 OR c.phone ILIKE $2)`; params.push(`%${search}%`); }
|
|
||||||
query += ` ORDER BY c.created_at DESC LIMIT 50`;
|
|
||||||
const q = await pool.query(query, params);
|
|
||||||
res.json({ ok: true, clients: q.rows });
|
|
||||||
} catch (e) { res.status(500).json({ ok: false }); }
|
|
||||||
});
|
|
||||||
|
|
||||||
app.get("/clients/:id/details", authMiddleware, async (req, res) => {
|
|
||||||
try {
|
|
||||||
const clientId = req.params.id;
|
|
||||||
const clientQ = await pool.query("SELECT id, full_name, phone, addresses, email, notes, portal_token, created_at FROM clients WHERE id=$1 AND owner_id=$2", [clientId, req.user.accountId]);
|
|
||||||
if (clientQ.rowCount === 0) return res.status(404).json({ ok: false });
|
|
||||||
const servicesQ = await pool.query(`SELECT s.*, st.name as status_name, st.color as status_color, u.full_name as assigned_name FROM services s LEFT JOIN service_statuses st ON s.status_id = st.id LEFT JOIN users u ON s.assigned_to = u.id WHERE s.client_id = $1 ORDER BY s.created_at DESC`, [clientId]);
|
|
||||||
res.json({ ok: true, client: clientQ.rows[0], services: servicesQ.rows });
|
|
||||||
} catch (e) { res.status(500).json({ ok: false }); }
|
|
||||||
});
|
|
||||||
|
|
||||||
app.post("/clients", authMiddleware, async (req, res) => {
|
|
||||||
try {
|
|
||||||
const { full_name, phone, email, address, notes } = req.body;
|
|
||||||
const p = normalizePhone(phone);
|
|
||||||
const q = await pool.query("INSERT INTO clients (owner_id, full_name, phone, email, addresses, notes) VALUES ($1, $2, $3, $4, $5, $6) RETURNING id", [req.user.accountId, full_name, p, email, JSON.stringify([address]), notes]);
|
|
||||||
res.json({ ok: true, id: q.rows[0].id });
|
|
||||||
} catch (e) { res.status(500).json({ ok: false }); }
|
|
||||||
});
|
|
||||||
|
|
||||||
app.put("/clients/:id", authMiddleware, async (req, res) => {
|
|
||||||
try {
|
|
||||||
const { full_name, email, notes, addresses } = req.body;
|
|
||||||
await pool.query("UPDATE clients SET full_name=$1, email=$2, notes=$3, addresses=$4 WHERE id=$5 AND owner_id=$6", [full_name, email, notes, JSON.stringify(addresses), req.params.id, req.user.accountId]);
|
|
||||||
res.json({ ok: true });
|
|
||||||
} catch (e) { res.status(500).json({ ok: false }); }
|
|
||||||
});
|
|
||||||
|
|
||||||
// ==========================================
|
// ==========================================
|
||||||
// 📝 RUTAS DE PLANTILLAS DE MENSAJES
|
// 📝 RUTAS DE PLANTILLAS DE MENSAJES
|
||||||
// ==========================================
|
// ==========================================
|
||||||
|
|||||||
Reference in New Issue
Block a user