Actualizar server.js

This commit is contained in:
2026-03-29 16:26:11 +00:00
parent ed969620ef
commit b42b1c6ba0

View File

@@ -3481,6 +3481,9 @@ app.post("/budgets/:id/convert", authMiddleware, async (req, res) => {
if (bq.rowCount === 0) return res.status(404).json({ok: false}); if (bq.rowCount === 0) return res.status(404).json({ok: false});
const budget = bq.rows[0]; const budget = bq.rows[0];
// 🟢 Saber si ya estaba pagado antes de convertirlo
const isAlreadyPaid = budget.status === 'paid';
// 1. Montamos el Raw Data para el servicio // 1. Montamos el Raw Data para el servicio
const rawData = { const rawData = {
"Nombre Cliente": budget.client_name, "Nombre Cliente": budget.client_name,
@@ -3491,7 +3494,8 @@ app.post("/budgets/:id/convert", authMiddleware, async (req, res) => {
"guild_id": guild_id || null, "guild_id": guild_id || null,
"assigned_to": assigned_to || null, "assigned_to": assigned_to || null,
"scheduled_date": date || "", "scheduled_date": date || "",
"scheduled_time": time || "" "scheduled_time": time || "",
"is_paid": isAlreadyPaid // 🟢 Inyección limpia estructural
}; };
// 2. Insertamos en el Panel Operativo (Buzón) empezando en manual // 2. Insertamos en el Panel Operativo (Buzón) empezando en manual
@@ -3507,11 +3511,13 @@ app.post("/budgets/:id/convert", authMiddleware, async (req, res) => {
const newServiceId = insertSvc.rows[0].id; const newServiceId = insertSvc.rows[0].id;
// 3. Marcamos presupuesto como convertido y le enlazamos la ficha financiera por el total // 3. Marcamos presupuesto y enlazamos ficha financiera
await pool.query("UPDATE budgets SET status='converted' WHERE id=$1", [budget.id]); const finalBudgetStatus = isAlreadyPaid ? 'paid' : 'converted';
await pool.query("UPDATE budgets SET status=$1 WHERE id=$2", [finalBudgetStatus, budget.id]);
await pool.query( await pool.query(
"INSERT INTO service_financials (scraped_id, amount, payment_method) VALUES ($1, $2, 'Pendiente')", "INSERT INTO service_financials (scraped_id, amount, payment_method, is_paid) VALUES ($1, $2, $3, $4)",
[newServiceId, budget.total] [newServiceId, budget.total, isAlreadyPaid ? 'Tarjeta (Stripe)' : 'Pendiente', isAlreadyPaid]
); );
// 4. Si pide automatización, la disparamos internamente llamando a nuestra propia IP (127.0.0.1) // 4. Si pide automatización, la disparamos internamente llamando a nuestra propia IP (127.0.0.1)
@@ -4319,34 +4325,32 @@ app.post("/public/portal/:token/budget/:id/checkout", async (req, res) => {
}); });
// B) WEBHOOK DE STRIPE (El chivatazo invisible que avisa cuando el cliente YA ha pagado) // B) WEBHOOK DE STRIPE (El chivatazo invisible que avisa cuando el cliente YA ha pagado)
// Nota: Stripe necesita que el body llegue "crudo" para verificar las firmas, por eso le quitamos el express.json
app.post("/webhook/stripe", express.raw({ type: 'application/json' }), async (req, res) => { app.post("/webhook/stripe", express.raw({ type: 'application/json' }), async (req, res) => {
try { try {
const sig = req.headers['stripe-signature'];
const body = req.body; const body = req.body;
// Por ahora, como es un entorno SaaS complejo, procesaremos el evento de forma directa
// En producción, es altamente recomendable verificar el 'endpoint_secret' de cada webhook
const event = JSON.parse(body); const event = JSON.parse(body);
if (event.type === 'checkout.session.completed') { if (event.type === 'checkout.session.completed') {
const session = event.data.object; const session = event.data.object;
// Extraer la "matrícula" oculta que le pusimos al pago
const budgetId = session.metadata.budget_id; const budgetId = session.metadata.budget_id;
const ownerId = session.metadata.owner_id; const ownerId = session.metadata.owner_id;
const amountTotal = (session.amount_total / 100).toFixed(2); // De céntimos a Euros const amountTotal = (session.amount_total / 100).toFixed(2);
console.log(`💰 [STRIPE WEBHOOK] ¡PAGO RECIBIDO! Presupuesto PRE-${budgetId} por ${amountTotal} (Owner: ${ownerId})`); console.log(`💰 [STRIPE WEBHOOK] ¡PAGO RECIBIDO! Presupuesto PRE-${budgetId} por ${amountTotal}`);
// 1. Marcar el presupuesto como "Convertido/Pagado" // 1. 🟢 Lo marcamos con el estado puro 'paid'
await pool.query("UPDATE budgets SET status = 'converted' WHERE id = $1 AND owner_id = $2", [budgetId, ownerId]); await pool.query("UPDATE budgets SET status = 'paid' WHERE id = $1 AND owner_id = $2", [budgetId, ownerId]);
// 2. Si ya existía un servicio asociado, marcarlo en contabilidad // 2. Si ya existía un servicio asociado, le inyectamos la variable "is_paid: true"
const sq = await pool.query("SELECT id FROM scraped_services WHERE service_ref = $1 AND owner_id = $2", [`PRE-${budgetId}`, ownerId]); const sq = await pool.query("SELECT id, raw_data FROM scraped_services WHERE service_ref = $1 AND owner_id = $2", [`PRE-${budgetId}`, ownerId]);
if (sq.rowCount > 0) { if (sq.rowCount > 0) {
const serviceId = sq.rows[0].id; const serviceId = sq.rows[0].id;
let rawData = sq.rows[0].raw_data || {};
rawData.is_paid = true; // 🟢 Inyección limpia en el JSON de datos
await pool.query("UPDATE scraped_services SET raw_data = $1 WHERE id = $2", [JSON.stringify(rawData), serviceId]);
await pool.query(` await pool.query(`
INSERT INTO service_financials (scraped_id, amount, payment_method, is_paid) INSERT INTO service_financials (scraped_id, amount, payment_method, is_paid)
@@ -4354,7 +4358,6 @@ app.post("/webhook/stripe", express.raw({ type: 'application/json' }), async (re
ON CONFLICT (scraped_id) DO UPDATE SET is_paid = true, payment_method = 'Tarjeta (Stripe)' ON CONFLICT (scraped_id) DO UPDATE SET is_paid = true, payment_method = 'Tarjeta (Stripe)'
`, [serviceId, amountTotal]); `, [serviceId, amountTotal]);
// 3. Trazabilidad
await pool.query("INSERT INTO scraped_service_logs (scraped_id, user_name, action, details) VALUES ($1, $2, $3, $4)", await pool.query("INSERT INTO scraped_service_logs (scraped_id, user_name, action, details) VALUES ($1, $2, $3, $4)",
[serviceId, "Stripe API", "Pago Confirmado", `El cliente ha abonado ${amountTotal}€ por pasarela segura.`] [serviceId, "Stripe API", "Pago Confirmado", `El cliente ha abonado ${amountTotal}€ por pasarela segura.`]
); );