diff --git a/resources/views/renovaciones/contratos.html b/resources/views/renovaciones/contratos.html index f421702..8857acc 100644 --- a/resources/views/renovaciones/contratos.html +++ b/resources/views/renovaciones/contratos.html @@ -65,6 +65,13 @@ 'bg-blue-100 text-blue-700': d.estado==='renovado' }" x-text="d.estado"> + + +
@@ -77,6 +84,14 @@ + + + + + + @@ -326,6 +341,7 @@ document.addEventListener('alpine:init', () => { historialModal: false, historialTitulo: '', historialTimeline: [], historialLoading: false, clientes: [], servicios: [], reglas: [], reglasBienvenida: [], selectedId: null, + verificandoID: null, form: { cliente_id:'', servicio_ids:[], fecha_inicio:'', fecha_vencimiento:'', precio_acordado:0, estado:'activo', auto_renovar:false, notas:'' }, toast: { show: false, msg: '', type: 'ok' }, @@ -444,6 +460,23 @@ document.addEventListener('alpine:init', () => { } catch(e) { this.showToast(e.response?.data?.error||'Error', 'error'); } }, + async verificarPago(d) { + this.verificandoID = d.ID; + try { + const { data } = await axios.post(`/app/api/contratos/${d.ID}/verificar-pago`); + if (data.confirmado) { + this.showToast(data.mensaje || '¡Pago confirmado!', 'success'); + await this.loadData(); + } else { + this.showToast(data.mensaje || 'Pago aún pendiente', 'info'); + } + } catch(e) { + this.showToast(e.response?.data?.error || 'Error al verificar', 'error'); + } finally { + this.verificandoID = null; + } + }, + enviarCorreo(d) { this.notifContratoId = d.ID; this.notifCliente = (d.cliente?.nombre || '') + (d.cliente?.email ? ' <'+d.cliente.email+'>' : ''); diff --git a/rest/controllers/contrato_controller.go b/rest/controllers/contrato_controller.go index adabd8b..0483b77 100644 --- a/rest/controllers/contrato_controller.go +++ b/rest/controllers/contrato_controller.go @@ -373,3 +373,57 @@ func calcularFechaVencimiento(desde time.Time, periodicidad string) time.Time { return desde.AddDate(1, 0, 0) } } + +// VerificarPagoBold consulta la API de Bold para un contrato específico y confirma el pago si está pagado. +// POST /api/contratos/:id/verificar-pago +func VerificarPagoBold(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 64) + if err != nil { + return c.Status(400).JSON(fiber.Map{"error": "ID inválido"}) + } + + contrato, err := models.GetContratoParaVerificacion(uint(id)) + if err != nil { + return c.Status(404).JSON(fiber.Map{"error": "Contrato no encontrado"}) + } + if contrato.PagoConfirmado { + return c.JSON(fiber.Map{"ok": true, "confirmado": true, "mensaje": "El pago ya estaba confirmado"}) + } + if contrato.EnlacePagoLinkID == "" { + return c.JSON(fiber.Map{"ok": false, "confirmado": false, "mensaje": "Este contrato no tiene enlace de pago generado"}) + } + + boldCfg, err := models.GetBoldConfig() + if err != nil { + return c.Status(500).JSON(fiber.Map{"error": "Sin configuración Bold activa"}) + } + + paid, paymentID, monto, err := services.CheckBoldLinkPaid(boldCfg, contrato.EnlacePagoLinkID) + if err != nil { + return c.Status(500).JSON(fiber.Map{"error": "Error consultando Bold: " + err.Error()}) + } + if !paid { + return c.JSON(fiber.Map{"ok": true, "confirmado": false, "mensaje": "El enlace aún no ha sido pagado"}) + } + + ref := "contrato-" + strconv.FormatUint(id, 10) + if ok, _ := models.MarcarContratoPagado(uint(id)); ok { + go services.EnviarCorreoConfirmacionPago(uint(id), "bold") + } + go models.EnrichBoldCallbackLog(ref, contrato.Cliente.Email, monto) + + notifID := "api-check-" + paymentID + if paymentID != "" && !models.IsBoldNotificationDuplicate(notifID) { + _ = models.SaveBoldWebhookLog(models.BoldWebhookLog{ + NotificationID: notifID, + Tipo: "API_CHECK", + PaymentID: paymentID, + Referencia: ref, + PayerEmail: contrato.Cliente.Email, + Monto: monto, + Procesado: true, + }) + } + + return c.JSON(fiber.Map{"ok": true, "confirmado": true, "monto": monto, "mensaje": "¡Pago confirmado!"}) +} diff --git a/rest/routes/renovaciones.go b/rest/routes/renovaciones.go index 7a293a3..27ca185 100644 --- a/rest/routes/renovaciones.go +++ b/rest/routes/renovaciones.go @@ -34,6 +34,7 @@ func RenovacionesRoutes(protected fiber.Router) { protected.Post("/api/contratos/:id/renovar", controllers.RenovarContrato) protected.Post("/api/contratos/:id/enviar-correo", controllers.EnviarCorreoContrato) protected.Get("/api/contratos/:id/historial", controllers.GetHistorialContrato) + protected.Post("/api/contratos/:id/verificar-pago", controllers.VerificarPagoBold) // ─── Plantillas de correo ───────────────────────────────────────── protected.Get("/plantillas-correo", middlewares.MenuMiddleware, controllers.PlantillasView)