diff --git a/pkg/models/notificacion_log.go b/pkg/models/notificacion_log.go index 8ec6f60..b4ebc90 100644 --- a/pkg/models/notificacion_log.go +++ b/pkg/models/notificacion_log.go @@ -20,6 +20,7 @@ type NotificacionLog struct { ErrorMsg string `json:"error_msg" gorm:"column:error_msg;type:text"` Asunto string `json:"asunto" gorm:"column:asunto"` PreviewHTML string `json:"preview_html" gorm:"column:preview_html;type:text"` + Pasarela string `json:"pasarela" gorm:"column:pasarela;type:varchar(20)"` // bold | dlocal | (vacío para correos de aviso) } func (NotificacionLog) TableName() string { return "notificaciones_log" } @@ -75,6 +76,7 @@ func UpdateNotificacionLog(n NotificacionLog) error { return app.Http.Database.DB.Model(&n).Updates(map[string]interface{}{ "estado": n.Estado, "error_msg": n.ErrorMsg, + "pasarela": n.Pasarela, }).Error } diff --git a/pkg/services/ciclo_notificaciones_test.go b/pkg/services/ciclo_notificaciones_test.go index 225b188..e1eb97c 100644 --- a/pkg/services/ciclo_notificaciones_test.go +++ b/pkg/services/ciclo_notificaciones_test.go @@ -325,6 +325,7 @@ func TestCicloNotificaciones(t *testing.T) { ®laBienvenida, &cliente, []models.Contrato{contrato}, + "", ) if err != nil { t.Fatalf("EnviarNotificacionGrupo (bienvenida): %v", err) diff --git a/pkg/services/cron_service.go b/pkg/services/cron_service.go index 24cac2b..c9ddad6 100644 --- a/pkg/services/cron_service.go +++ b/pkg/services/cron_service.go @@ -134,7 +134,7 @@ func procesarContratos(regla *models.NotificacionRegla, contratos []models.Contr continue } cliente := &grupoContratos[0].Cliente - if err := EnviarNotificacionGrupo(regla, cliente, grupoContratos); err != nil { + if err := EnviarNotificacionGrupo(regla, cliente, grupoContratos, ""); err != nil { log.Printf("[CRON] Error enviando a cliente %d: %v", clienteID, err) } else { log.Printf("[CRON] Enviado a cliente %d (%s) — %d contrato(s)", clienteID, cliente.Email, len(grupoContratos)) diff --git a/pkg/services/renovacion_service.go b/pkg/services/renovacion_service.go index d108eb9..80bd9af 100644 --- a/pkg/services/renovacion_service.go +++ b/pkg/services/renovacion_service.go @@ -163,8 +163,9 @@ func EnviarCorreoManual(contrato *models.Contrato) error { return sendErr } -// EnviarNotificacionGrupo envía un correo agrupado para un cliente con múltiples contratos -func EnviarNotificacionGrupo(regla *models.NotificacionRegla, cliente *models.Cliente, contratos []models.Contrato) error { +// EnviarNotificacionGrupo envía un correo agrupado para un cliente con múltiples contratos. +// pasarela indica la pasarela de pago que originó el evento (bold | dlocal | ""). +func EnviarNotificacionGrupo(regla *models.NotificacionRegla, cliente *models.Cliente, contratos []models.Contrato, pasarela string) error { p, err := models.GetPlantillaByID(regla.PlantillaID) if err != nil { return fmt.Errorf("plantilla no encontrada: %w", err) @@ -230,6 +231,7 @@ func EnviarNotificacionGrupo(regla *models.NotificacionRegla, cliente *models.Cl Estado: "pendiente", Asunto: p.Asunto, PreviewHTML: html, + Pasarela: pasarela, } savedLog, err := models.CreateNotificacionLog(logEntry) if err != nil { @@ -276,7 +278,14 @@ func ReenviarLog(logEntry *models.NotificacionLog) error { // EnviarCorreoConfirmacionPago envía el correo de "pago recibido" para un contrato. // Usa la regla/plantilla con tipo_evento = 'pago_recibido'. Si no existe, no hace nada. // Es idempotente: no reenvía si ya se envió hoy (YaEnviadoHoy por regla+cliente). -func EnviarCorreoConfirmacionPago(contratoID uint) { +// EnviarCorreoConfirmacionPago envía el correo de "pago recibido" para un contrato. +// pasarela (opcional): "bold", "dlocal", etc. Se guarda en el historial del cliente. +func EnviarCorreoConfirmacionPago(contratoID uint, pasarela ...string) { + pas := "" + if len(pasarela) > 0 { + pas = pasarela[0] + } + contrato, err := models.GetContratoByID(contratoID) if err != nil { log.Printf("[PAGO-CORREO] contrato %d no encontrado: %v", contratoID, err) @@ -296,10 +305,10 @@ func EnviarCorreoConfirmacionPago(contratoID uint) { return } - if err := EnviarNotificacionGrupo(regla, &contrato.Cliente, []models.Contrato{*contrato}); err != nil { + if err := EnviarNotificacionGrupo(regla, &contrato.Cliente, []models.Contrato{*contrato}, pas); err != nil { log.Printf("[PAGO-CORREO] error enviando correo de confirmación para contrato %d: %v", contratoID, err) } else { - log.Printf("[PAGO-CORREO] correo de confirmación enviado para contrato %d → %s", contratoID, contrato.Cliente.Email) + log.Printf("[PAGO-CORREO] correo de confirmación enviado para contrato %d → %s (pasarela: %s)", contratoID, contrato.Cliente.Email, pas) } } diff --git a/rest/controllers/api/bold_controller.go b/rest/controllers/api/bold_controller.go index 1617007..5351831 100644 --- a/rest/controllers/api/bold_controller.go +++ b/rest/controllers/api/bold_controller.go @@ -120,7 +120,7 @@ func BoldWebhook(c *fiber.Ctx) error { log.Printf("[BOLD] Webhook: contrato %d marcado como pagado", contratoID) // Notificar SaaS externos + enviar correo de confirmación (goroutine, no bloquea respuesta) go services.DispatchSaasPaymentNotification(contratoID, payerEmail, "bold", float64(monto), "COP") - go services.EnviarCorreoConfirmacionPago(contratoID) + go services.EnviarCorreoConfirmacionPago(contratoID, "bold") } } } diff --git a/rest/controllers/api/dlocal_controller.go b/rest/controllers/api/dlocal_controller.go index 06d84ed..0ced849 100644 --- a/rest/controllers/api/dlocal_controller.go +++ b/rest/controllers/api/dlocal_controller.go @@ -326,7 +326,7 @@ func DlocalWebhook(c *fiber.Ctx) error { log.Printf("[DLOCAL] Webhook: contrato %d marcado como pagado", contratoID) // Notificar SaaS externos + enviar correo de confirmación (goroutine, no bloquea respuesta) go services.DispatchSaasPaymentNotification(contratoID, payerEmail, "dlocal", monto, moneda) - go services.EnviarCorreoConfirmacionPago(contratoID) + go services.EnviarCorreoConfirmacionPago(contratoID, "dlocal") } } } diff --git a/rest/controllers/api/pago_controller.go b/rest/controllers/api/pago_controller.go index 0f22528..cad6b55 100644 --- a/rest/controllers/api/pago_controller.go +++ b/rest/controllers/api/pago_controller.go @@ -71,7 +71,7 @@ func PagoExitosoPage(c *fiber.Ctx) error { var contratoID uint if _, err := fmt.Sscanf(ref, "contrato-%d", &contratoID); err == nil && contratoID > 0 { _ = models.MarcarContratoPagado(contratoID) - go services.EnviarCorreoConfirmacionPago(contratoID) + go services.EnviarCorreoConfirmacionPago(contratoID, "bold") } estado, fechaPago := verificarPago(ref) return c.Render("pago_exitoso", fiber.Map{ @@ -136,7 +136,7 @@ func verificarPago(ref string) (confirmado bool, fechaPago string) { if l.Estado == "PAID" || l.Estado == "AUTHORIZED" { log.Printf("[PAGO-ESTADO] Contrato %d confirmado via dlocal_payment_log (id=%d)", contratoID, l.ID) _ = models.MarcarContratoPagado(contratoID) - go services.EnviarCorreoConfirmacionPago(contratoID) + go services.EnviarCorreoConfirmacionPago(contratoID, "dlocal") return true, l.CreatedAt.Format("02/01/2006 15:04") } } @@ -152,7 +152,7 @@ func verificarPago(ref string) (confirmado bool, fechaPago string) { } else if paid { log.Printf("[PAGO-ESTADO] Contrato %d confirmado via Bold API (payment_id=%s)", contratoID, paymentID) _ = models.MarcarContratoPagado(contratoID) - go services.EnviarCorreoConfirmacionPago(contratoID) + go services.EnviarCorreoConfirmacionPago(contratoID, "bold") if paymentID != "" && !models.IsBoldNotificationDuplicate("api-check-"+paymentID) { _ = models.SaveBoldWebhookLog(models.BoldWebhookLog{ NotificationID: "api-check-" + paymentID, @@ -178,7 +178,7 @@ func verificarPago(ref string) (confirmado bool, fechaPago string) { } else if paid { log.Printf("[PAGO-ESTADO] Contrato %d confirmado via dLocal API (payment_id=%s)", contratoID, paymentID) _ = models.MarcarContratoPagado(contratoID) - go services.EnviarCorreoConfirmacionPago(contratoID) + go services.EnviarCorreoConfirmacionPago(contratoID, "dlocal") notifID := "api-check-" + ref if !models.IsDlocalNotificationDuplicate(notifID) { _ = models.SaveDlocalPaymentLog(models.DlocalPaymentLog{ diff --git a/rest/controllers/contrato_controller.go b/rest/controllers/contrato_controller.go index 08cbfe6..675b97c 100644 --- a/rest/controllers/contrato_controller.go +++ b/rest/controllers/contrato_controller.go @@ -204,7 +204,7 @@ func EnviarCorreoContrato(c *fiber.Ctx) error { if err != nil { return c.Status(404).JSON(fiber.Map{"error": "Regla no encontrada"}) } - if err := services.EnviarNotificacionGrupo(regla, &contrato.Cliente, []models.Contrato{*contrato}); err != nil { + if err := services.EnviarNotificacionGrupo(regla, &contrato.Cliente, []models.Contrato{*contrato}, ""); err != nil { return c.Status(500).JSON(fiber.Map{"error": err.Error()}) } return c.JSON(fiber.Map{"message": "Correo enviado", "ok": true}) diff --git a/rest/controllers/diagnostico_controller.go b/rest/controllers/diagnostico_controller.go index 582c806..02d3038 100644 --- a/rest/controllers/diagnostico_controller.go +++ b/rest/controllers/diagnostico_controller.go @@ -164,7 +164,7 @@ func EjecutarTestCiclo(c *fiber.Ctx) error { // ── PASO 1: Enviar correo de bienvenida ─────────────────────────────────── paso("paso 1 — envío de bienvenida", func() error { - return services.EnviarNotificacionGrupo(®laBienvenida, &cliente, []models.Contrato{contrato}) + return services.EnviarNotificacionGrupo(®laBienvenida, &cliente, []models.Contrato{contrato}, "") }) // Verificar log en DB @@ -203,7 +203,7 @@ func EjecutarTestCiclo(c *fiber.Ctx) error { if err != nil { return err } - return services.EnviarNotificacionGrupo(®laPago, &cliente, []models.Contrato{*reloadContrato}) + return services.EnviarNotificacionGrupo(®laPago, &cliente, []models.Contrato{*reloadContrato}, "") }) paso("paso 2 — verificar log de pago en historial", func() error { diff --git a/rest/controllers/pasarelas_controller.go b/rest/controllers/pasarelas_controller.go index c453cce..724d89c 100644 --- a/rest/controllers/pasarelas_controller.go +++ b/rest/controllers/pasarelas_controller.go @@ -291,7 +291,7 @@ func ValidarDlocalLog(c *fiber.Ctx) error { // Si el pago se confirmó ahora (no estaba marcado antes), actualizar contrato if pagoConfirmado && !contrato.PagoConfirmado { _ = models.MarcarContratoPagado(contratoID) - go services.EnviarCorreoConfirmacionPago(contratoID) + go services.EnviarCorreoConfirmacionPago(contratoID, fuenteConfirmacion) } _ = models.UpdateDlocalPaymentLogDatos(uint(logID), payerEmail, monto) @@ -385,7 +385,7 @@ func ValidarBoldCallback(c *fiber.Ctx) error { // Actualizar contrato si se confirmó ahora if pagoConfirmado && !contrato.PagoConfirmado { _ = models.MarcarContratoPagado(contratoID) - go services.EnviarCorreoConfirmacionPago(contratoID) + go services.EnviarCorreoConfirmacionPago(contratoID, fuente) } // Actualizar estado del callback @@ -514,7 +514,7 @@ func ValidarBoldLog(c *fiber.Ctx) error { // Si el pago se confirmó ahora (no estaba marcado antes), actualizar contrato if pagoConfirmado && !contrato.PagoConfirmado { _ = models.MarcarContratoPagado(contratoID) - go services.EnviarCorreoConfirmacionPago(contratoID) + go services.EnviarCorreoConfirmacionPago(contratoID, fuenteConfirmacion) } // Actualizar log con los mejores datos disponibles