From d297404c32a340d8ef06b054ae6e61d26f186c52 Mon Sep 17 00:00:00 2001 From: marsalva Date: Thu, 2 Apr 2026 21:38:25 +0000 Subject: [PATCH] Actualizar server.js --- server.js | 119 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) diff --git a/server.js b/server.js index 635d7f4..494cb19 100644 --- a/server.js +++ b/server.js @@ -4566,6 +4566,125 @@ app.post("/protection/config", authMiddleware, async (req, res) => { } catch (e) { res.status(500).json({ ok: false, error: e.message }); } }); +// ========================================== +// šŸ›”ļø RUTAS PÚBLICAS: CONTRATACIƓN DE PLANES +// ========================================== + +// 1. Obtener la información pĆŗblica de la empresa, planes y comprobar si YA tiene seguro +app.get("/public/portal/:token/protection", async (req, res) => { + try { + const { token } = req.params; + + // Identificar al cliente y sacar el ID de la empresa dueƱa + const clientQ = await pool.query("SELECT id, full_name, phone, owner_id FROM clients WHERE portal_token = $1", [token]); + if (clientQ.rowCount === 0) return res.status(404).json({ ok: false, error: "Token invĆ”lido" }); + + const client = clientQ.rows[0]; + const ownerId = client.owner_id; + + // Comprobar si el cliente YA tiene un plan activo + let cleanPhone = String(client.phone || "").replace(/[^0-9]/g, ""); + if (cleanPhone.length > 9) cleanPhone = cleanPhone.slice(-9); + + const subQ = await pool.query(` + SELECT s.status, p.name as plan_name + FROM protection_subscriptions s + JOIN protection_plans p ON s.plan_id = p.id + WHERE s.company_id = $1 AND s.client_phone LIKE $2 AND s.status != 'expirado' + ORDER BY s.created_at DESC LIMIT 1 + `, [ownerId, `%${cleanPhone}%`]); + + const hasActivePlan = subQ.rowCount > 0; + const activePlanDetails = hasActivePlan ? subQ.rows[0] : null; + + // Extraer datos de la empresa, configuración y planes + const [companyQ, configQ, plansQ] = await Promise.all([ + pool.query("SELECT full_name FROM users WHERE id = $1", [ownerId]), + pool.query("SELECT contract_text FROM protection_config WHERE company_id = $1", [ownerId]), + pool.query("SELECT * FROM protection_plans WHERE company_id = $1 ORDER BY price DESC", [ownerId]) + ]); + + res.json({ + ok: true, + client: { name: client.full_name }, + company: { name: companyQ.rows[0]?.full_name || "Empresa" }, + config: configQ.rows[0] || {}, + plans: plansQ.rows, + hasActivePlan: hasActivePlan, + activePlanDetails: activePlanDetails + }); + + } catch (e) { + console.error("Error cargando portal protección:", e); + res.status(500).json({ ok: false, error: "Error interno del servidor" }); + } +}); + +// 2. Firmar contrato, generar suscripción y redirigir a Stripe +app.post("/public/portal/:token/protection/subscribe", async (req, res) => { + try { + const { token } = req.params; + const { plan_id, signature, pdf_document } = req.body; // pdf_document viene en Base64 + + // 1. Validar cliente + const clientQ = await pool.query("SELECT * FROM clients WHERE portal_token = $1", [token]); + if (clientQ.rowCount === 0) return res.status(404).json({ ok: false, error: "Token invĆ”lido" }); + + const client = clientQ.rows[0]; + const ownerId = client.owner_id; + + // 2. Validar Plan + const planQ = await pool.query("SELECT * FROM protection_plans WHERE id = $1 AND company_id = $2", [plan_id, ownerId]); + if (planQ.rowCount === 0) return res.status(404).json({ ok: false, error: "El plan seleccionado no existe" }); + const plan = planQ.rows[0]; + + // 3. Crear suscripción en la Base de Datos (Estado: Impagado hasta que Stripe confirme) + const subInsert = await pool.query(` + INSERT INTO protection_subscriptions (company_id, plan_id, client_name, client_dni, client_phone, payment_status, status) + VALUES ($1, $2, $3, $4, $5, 'impagado', 'activo') + RETURNING id + `, [ownerId, plan.id, client.full_name, null, client.phone]); + + const subscriptionId = subInsert.rows[0].id; + + // 4. Guardamos la firma y el registro + await pool.query("UPDATE protection_subscriptions SET contract_pdf_url = $1 WHERE id = $2", ['PDF_FIRMADO_BASE64', subscriptionId]); + await pool.query("INSERT INTO protection_activity (company_id, type, description) VALUES ($1, 'alta', $2)", [ownerId, `Suscripción iniciada vĆ­a Web: ${client.full_name} (${plan.name})`]); + + // 5. Integración con Stripe + const ownerConfigQ = await pool.query("SELECT billing_settings FROM users WHERE id = $1", [ownerId]); + const billingSettings = ownerConfigQ.rows[0]?.billing_settings || {}; + + if (!billingSettings.stripe_enabled || !billingSettings.stripe_sk) { + return res.status(400).json({ ok: false, error: "La empresa no tiene habilitados los pagos con tarjeta." }); + } + + const stripe = new Stripe(billingSettings.stripe_sk, { apiVersion: "2023-10-16" }); + + const session = await stripe.checkout.sessions.create({ + payment_method_types: ['card'], + line_items: [{ + price_data: { + currency: 'eur', + product_data: { name: `Plan de Protección: ${plan.name}`, description: `Suscripción ${plan.type}` }, + unit_amount: Math.round(plan.price * 100), + }, + quantity: 1, + }], + mode: 'payment', + metadata: { subscription_id: subscriptionId, owner_id: ownerId, type: 'protection_plan' }, + success_url: `https://portal.integrarepara.es/pago_exito.html?sub_id=${subscriptionId}`, + cancel_url: `https://portal.integrarepara.es/plan-tranquilidad.html?token=${token}`, + }); + + res.json({ ok: true, checkout_url: session.url }); + + } catch (e) { + console.error("Error al procesar firma de plan:", e); + res.status(500).json({ ok: false, error: "Error al generar la suscripción." }); + } +}); + // ========================================== // šŸ•’ EL RELOJ DEL SISTEMA (Ejecutar cada minuto) // ==========================================