Files
soft_usite/rest/controllers/diagnostico_controller.go
T
2026-05-12 19:50:12 -05:00

259 lines
8.5 KiB
Go

package controllers
import (
"encoding/json"
"fmt"
"time"
"github.com/gofiber/fiber/v2"
"github.com/sujit-baniya/fiber-boilerplate/app"
"github.com/sujit-baniya/fiber-boilerplate/pkg/models"
"github.com/sujit-baniya/fiber-boilerplate/pkg/services"
)
type PasoDiagnostico struct {
Paso string `json:"paso"`
OK bool `json:"ok"`
Mensaje string `json:"mensaje"`
Ms int64 `json:"ms"`
}
// EjecutarTestCiclo ejecuta el ciclo completo de notificaciones sobre datos temporales:
// 1. Envío de bienvenida
// 2. Marcar contrato como pagado + enviar confirmación de pago
// 3. Idempotencia (no reenviar si ya se envió hoy)
//
// Crea y destruye los datos de prueba dentro de la misma transacción de limpieza.
// POST /app/api/diagnostico/ciclo-notificaciones
func EjecutarTestCiclo(c *fiber.Ctx) error {
var body struct {
TestEmail string `json:"test_email"`
}
if err := c.BodyParser(&body); err != nil || body.TestEmail == "" {
return c.Status(400).JSON(fiber.Map{"error": "test_email requerido"})
}
db := app.Http.Database.DB
suffix := fmt.Sprintf("_diag_%d", time.Now().UnixNano())
pasos := []PasoDiagnostico{}
allOK := true
paso := func(nombre string, fn func() error) {
t0 := time.Now()
err := fn()
ms := time.Since(t0).Milliseconds()
ok := err == nil
msg := "OK"
if !ok {
msg = err.Error()
allOK = false
}
pasos = append(pasos, PasoDiagnostico{Paso: nombre, OK: ok, Mensaje: msg, Ms: ms})
}
// ── Datos temporales ──────────────────────────────────────────────────────
var plantBienvenida, plantPago models.PlantillaCorreo
var reglaBienvenida, reglaPago models.NotificacionRegla
var cliente models.Cliente
var contrato models.Contrato
// ── Cleanup siempre al finalizar ──────────────────────────────────────────
defer func() {
if contrato.ID > 0 {
db.Unscoped().Where("cliente_id = ?", cliente.ID).Delete(&models.NotificacionLog{})
db.Unscoped().Delete(&contrato)
}
if cliente.ID > 0 {
db.Unscoped().Delete(&cliente)
}
if reglaBienvenida.ID > 0 {
db.Unscoped().Delete(&reglaBienvenida)
}
if reglaPago.ID > 0 {
db.Unscoped().Delete(&reglaPago)
}
if plantBienvenida.ID > 0 {
db.Unscoped().Delete(&plantBienvenida)
}
if plantPago.ID > 0 {
db.Unscoped().Delete(&plantPago)
}
}()
// ── SETUP: crear datos de prueba ──────────────────────────────────────────
paso("setup: plantilla bienvenida", func() error {
plantBienvenida = models.PlantillaCorreo{
Nombre: "DIAG Bienvenida" + suffix,
Asunto: "[TEST] Bienvenido al sistema",
CuerpoHTML: "<p>Hola {{.ClienteNombre}}, bienvenido. Tu vencimiento es {{.FechaVencimiento}}.</p>",
Tipo: "bienvenida",
}
return db.Create(&plantBienvenida).Error
})
paso("setup: plantilla pago confirmado", func() error {
plantPago = models.PlantillaCorreo{
Nombre: "DIAG Pago Confirmado" + suffix,
Asunto: "[TEST] Tu pago fue recibido",
CuerpoHTML: "<p>Hola {{.ClienteNombre}}, recibimos tu pago de ${{.Total}}. ¡Gracias!</p>",
Tipo: "pago",
}
return db.Create(&plantPago).Error
})
paso("setup: regla bienvenida", func() error {
if plantBienvenida.ID == 0 {
return fmt.Errorf("plantilla bienvenida no creada")
}
reglaBienvenida = models.NotificacionRegla{
Nombre: "DIAG Regla Bienvenida" + suffix,
TipoEvento: "bienvenida",
PlantillaID: plantBienvenida.ID,
Activo: true,
AplicaA: "todos",
PasarelaEnlace: "ninguna",
}
return db.Create(&reglaBienvenida).Error
})
paso("setup: regla pago_recibido", func() error {
if plantPago.ID == 0 {
return fmt.Errorf("plantilla pago no creada")
}
reglaPago = models.NotificacionRegla{
Nombre: "DIAG Regla Pago" + suffix,
TipoEvento: "pago_recibido",
PlantillaID: plantPago.ID,
Activo: true,
AplicaA: "todos",
PasarelaEnlace: "ninguna",
}
return db.Create(&reglaPago).Error
})
paso("setup: cliente y contrato temporales", func() error {
cliente = models.Cliente{
Nombre: "DIAG Cliente" + suffix,
Email: body.TestEmail,
Empresa: "Diagnóstico Sistema",
Activo: true,
}
if err := db.Create(&cliente).Error; err != nil {
return err
}
now := time.Now()
contrato = models.Contrato{
ClienteID: cliente.ID,
Cliente: cliente,
FechaInicio: now,
FechaVencimiento: now.AddDate(1, 0, 0),
PrecioAcordado: 99.00,
Estado: "activo",
}
if err := db.Create(&contrato).Error; err != nil {
return err
}
contrato.Cliente = cliente
return nil
})
// Si el setup falló, no continuar con los pasos funcionales
if !allOK {
return c.JSON(fiber.Map{"pasos": pasos, "ok": false, "resumen": "Setup falló — ver pasos anteriores"})
}
// ── PASO 1: Enviar correo de bienvenida ───────────────────────────────────
paso("paso 1 — envío de bienvenida", func() error {
return services.EnviarNotificacionGrupo(&reglaBienvenida, &cliente, []models.Contrato{contrato})
})
// Verificar log en DB
paso("paso 1 — verificar log en historial", func() error {
var count int64
db.Model(&models.NotificacionLog{}).
Where("cliente_id = ? AND regla_id = ? AND estado = 'enviado'", cliente.ID, reglaBienvenida.ID).
Count(&count)
if count == 0 {
return fmt.Errorf("no se encontró log con estado='enviado' para la regla bienvenida")
}
return nil
})
// ── PASO 2: Simular recepción de pago ─────────────────────────────────────
paso("paso 2 — marcar contrato como pagado", func() error {
return models.MarcarContratoPagado(contrato.ID)
})
paso("paso 2 — verificar pago_confirmado en DB", func() error {
var c models.Contrato
db.Select("pago_confirmado, fecha_pago").First(&c, contrato.ID)
if !c.PagoConfirmado {
return fmt.Errorf("pago_confirmado sigue siendo false tras MarcarContratoPagado")
}
if c.FechaPago == nil {
return fmt.Errorf("fecha_pago es nil tras MarcarContratoPagado")
}
return nil
})
paso("paso 2 — envío de confirmación de pago", func() error {
// EnviarCorreoConfirmacionPago es void — usar EnviarNotificacionGrupo directamente
// para capturar el error (la función void loguea pero no retorna)
reloadContrato, err := models.GetContratoByID(contrato.ID)
if err != nil {
return err
}
return services.EnviarNotificacionGrupo(&reglaPago, &cliente, []models.Contrato{*reloadContrato})
})
paso("paso 2 — verificar log de pago en historial", func() error {
var count int64
db.Model(&models.NotificacionLog{}).
Where("cliente_id = ? AND regla_id = ? AND estado = 'enviado'", cliente.ID, reglaPago.ID).
Count(&count)
if count == 0 {
return fmt.Errorf("no se encontró log con estado='enviado' para la regla pago_recibido")
}
return nil
})
// ── PASO 3: Idempotencia ──────────────────────────────────────────────────
paso("paso 3 — idempotencia (no reenviar si ya enviado hoy)", func() error {
// Verificar que YaEnviadoHoy retorna true para ambas reglas
if !models.YaEnviadoHoy(cliente.ID, reglaBienvenida.ID) {
return fmt.Errorf("YaEnviadoHoy debería ser true para bienvenida, pero retornó false")
}
if !models.YaEnviadoHoy(cliente.ID, reglaPago.ID) {
return fmt.Errorf("YaEnviadoHoy debería ser true para pago_recibido, pero retornó false")
}
return nil
})
// Contar emails en historial para el resumen
var totalLogs int64
db.Model(&models.NotificacionLog{}).Where("cliente_id = ?", cliente.ID).Count(&totalLogs)
// Serializar IDs para debug
idsJSON, _ := json.Marshal(map[string]uint{
"cliente_id": cliente.ID,
"contrato_id": contrato.ID,
"regla_bienvenida_id": reglaBienvenida.ID,
"regla_pago_id": reglaPago.ID,
"plantilla_bienvenida": plantBienvenida.ID,
"plantilla_pago": plantPago.ID,
})
resumen := fmt.Sprintf("Ciclo completado — %d emails enviados al historial, destinatario: %s",
totalLogs, body.TestEmail)
if !allOK {
resumen = "Ciclo con errores — revisar pasos"
}
return c.JSON(fiber.Map{
"pasos": pasos,
"ok": allOK,
"resumen": resumen,
"ids_tmp": string(idsJSON),
})
}