diff --git a/server.js b/server.js index 72ed178..6c3f991 100644 --- a/server.js +++ b/server.js @@ -2018,6 +2018,85 @@ app.post("/public/assignment/:token/reject", async (req, res) => { } }); +// ========================================== +// 💰 MOTOR FINANCIERO Y CONTABILIDAD (PREPARADO PARA ROBOT PDF) +// ========================================== +// Creamos la tabla financiera preparada para facturas y robots +pool.query(` + CREATE TABLE IF NOT EXISTS service_financials ( + id SERIAL PRIMARY KEY, + scraped_id INT REFERENCES scraped_services(id) ON DELETE CASCADE UNIQUE, + amount DECIMAL(10,2) DEFAULT 0.00, + payment_method TEXT, + is_paid BOOLEAN DEFAULT false, + invoice_ref TEXT, + pdf_raw_data JSONB, + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW() + ); +`).catch(console.error); + +// Obtener toda la contabilidad +app.get("/financials", authMiddleware, async (req, res) => { + try { + // 1. Truco de magia: Si hay servicios que no tienen ficha financiera, se la creamos automáticamente. + // Si tiene compañía asignada, le ponemos "Cobro Banco" por defecto. Si no, "Pendiente". + await pool.query(` + INSERT INTO service_financials (scraped_id, payment_method) + SELECT id, + CASE WHEN raw_data->>'Compañía' IS NOT NULL AND raw_data->>'Compañía' != '' AND raw_data->>'Compañía' != 'Particular' + THEN 'Cobro Banco' + ELSE 'Pendiente' END + FROM scraped_services + WHERE owner_id = $1 AND id NOT IN (SELECT scraped_id FROM service_financials) + `, [req.user.accountId]); + + // 2. Devolvemos la lista cruzando las finanzas con los datos del servicio + const q = await pool.query(` + SELECT f.*, s.service_ref, s.raw_data, s.status + FROM service_financials f + JOIN scraped_services s ON f.scraped_id = s.id + WHERE s.owner_id = $1 + ORDER BY f.updated_at DESC + `, [req.user.accountId]); + + res.json({ ok: true, financials: q.rows }); + } catch(e) { + console.error("Error financiero:", e); + res.status(500).json({ ok: false }); + } +}); + +// Guardar un cobro/pago +app.put("/financials/:id", authMiddleware, async (req, res) => { + try { + const { amount, payment_method } = req.body; + const parsedAmount = parseFloat(amount) || 0; + + // Si el importe es mayor que 0, se marca como pagado automáticamente + const isPaid = parsedAmount > 0; + + await pool.query(` + UPDATE service_financials + SET amount = $1, payment_method = $2, is_paid = $3, updated_at = NOW() + WHERE scraped_id = $4 + `, [parsedAmount, payment_method, isPaid, req.params.id]); + + // LOG AUTOMÁTICO DE TRAZABILIDAD + const userQ = await pool.query("SELECT full_name FROM users WHERE id=$1", [req.user.sub]); + const userName = userQ.rows[0]?.full_name || "Sistema"; + await pool.query( + "INSERT INTO scraped_service_logs (scraped_id, user_name, action, details) VALUES ($1, $2, $3, $4)", + [req.params.id, userName, "Cobro Actualizado", `Importe: ${parsedAmount}€ | Método: ${payment_method}`] + ); + + res.json({ ok: true, is_paid: isPaid }); + } catch(e) { + console.error(e); + res.status(500).json({ ok: false }); + } +}); + // ========================================== // 📖 MOTOR DE TRAZABILIDAD (LOGS)