From e94f9ee1f4f9ac3df16b5bb723b23ae067cc9082 Mon Sep 17 00:00:00 2001
From: Lizandro Guarnizo <77708265+lizandrogd@users.noreply.github.com>
Date: Wed, 13 May 2026 22:45:22 -0500
Subject: [PATCH] up
---
resources/views/renovaciones/contratos.html | 33 +++++++++++++
rest/controllers/contrato_controller.go | 54 +++++++++++++++++++++
rest/routes/renovaciones.go | 1 +
3 files changed, 88 insertions(+)
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">
+
+
+ ✓ Pagado
+
+
+ ⏳ Pendiente
+
@@ -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)
|