up
This commit is contained in:
@@ -118,8 +118,9 @@ func BoldWebhook(c *fiber.Ctx) error {
|
||||
log.Printf("[BOLD] Webhook: error marcando contrato %d como pagado: %v", contratoID, err)
|
||||
} else {
|
||||
log.Printf("[BOLD] Webhook: contrato %d marcado como pagado", contratoID)
|
||||
// Notificar SaaS externos asociados al contrato (goroutine, no bloquea respuesta)
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,7 +152,7 @@ func SeePlanes(c *fiber.Ctx) error {
|
||||
response, err := services.SeePlanes(*dlocalConfig)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||
"error": fmt.Sprintf("Error al obtener los planes", err),
|
||||
"error": fmt.Sprintf("Error al obtener los planes: %v", err),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -324,8 +324,9 @@ func DlocalWebhook(c *fiber.Ctx) error {
|
||||
log.Printf("[DLOCAL] Webhook: error marcando contrato %d como pagado: %v", contratoID, err)
|
||||
} else {
|
||||
log.Printf("[DLOCAL] Webhook: contrato %d marcado como pagado", contratoID)
|
||||
// Notificar SaaS externos asociados al contrato (goroutine, no bloquea respuesta)
|
||||
// Notificar SaaS externos + enviar correo de confirmación (goroutine, no bloquea respuesta)
|
||||
go services.DispatchSaasPaymentNotification(contratoID, payerEmail, "dlocal", monto, moneda)
|
||||
go services.EnviarCorreoConfirmacionPago(contratoID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,6 +118,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)
|
||||
return true, l.CreatedAt.Format("02/01/2006 15:04")
|
||||
}
|
||||
}
|
||||
@@ -133,6 +134,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)
|
||||
if paymentID != "" && !models.IsBoldNotificationDuplicate("api-check-"+paymentID) {
|
||||
_ = models.SaveBoldWebhookLog(models.BoldWebhookLog{
|
||||
NotificationID: "api-check-" + paymentID,
|
||||
@@ -156,6 +158,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)
|
||||
notifID := "api-check-" + ref
|
||||
if !models.IsDlocalNotificationDuplicate(notifID) {
|
||||
_ = models.SaveDlocalPaymentLog(models.DlocalPaymentLog{
|
||||
|
||||
@@ -228,6 +228,123 @@ func DeleteContrato(c *fiber.Ctx) error {
|
||||
return c.JSON(fiber.Map{"message": "Eliminado", "ok": true})
|
||||
}
|
||||
|
||||
// GetHistorialContrato devuelve la línea de tiempo de actividad de un contrato
|
||||
func GetHistorialContrato(c *fiber.Ctx) error {
|
||||
id, err := strconv.ParseUint(c.Params("id"), 10, 32)
|
||||
if err != nil {
|
||||
return c.Status(400).JSON(fiber.Map{"error": "ID inválido"})
|
||||
}
|
||||
contrato, err := models.GetContratoByID(uint(id))
|
||||
if err != nil {
|
||||
return c.Status(404).JSON(fiber.Map{"error": "No encontrado"})
|
||||
}
|
||||
|
||||
notifLogs, _ := models.GetLogsByContratoID(uint(id))
|
||||
pagoLogs, _ := models.GetDispatchLogsByContratoID(uint(id))
|
||||
|
||||
// Construir timeline unificado
|
||||
type Evento struct {
|
||||
Tipo string `json:"tipo"` // creacion | renovacion | notificacion | pago
|
||||
Icono string `json:"icono"`
|
||||
Titulo string `json:"titulo"`
|
||||
Detalle string `json:"detalle"`
|
||||
Estado string `json:"estado"` // ok | error | info
|
||||
FechaISO string `json:"fecha"`
|
||||
}
|
||||
|
||||
var timeline []Evento
|
||||
|
||||
// Evento: creación del contrato
|
||||
timeline = append(timeline, Evento{
|
||||
Tipo: "creacion",
|
||||
Icono: "document",
|
||||
Titulo: "Contrato creado",
|
||||
Detalle: "Inicio: " + contrato.FechaInicio.Format("02/01/2006") + " · Vence: " + contrato.FechaVencimiento.Format("02/01/2006"),
|
||||
Estado: "info",
|
||||
FechaISO: contrato.CreatedAt.Format(time.RFC3339),
|
||||
})
|
||||
|
||||
// Evento: renovaciones (updated_at con estado renovado — heurístico por estado)
|
||||
if contrato.Estado == "renovado" {
|
||||
timeline = append(timeline, Evento{
|
||||
Tipo: "renovacion",
|
||||
Icono: "refresh",
|
||||
Titulo: "Contrato renovado",
|
||||
Detalle: "Nuevo vencimiento: " + contrato.FechaVencimiento.Format("02/01/2006"),
|
||||
Estado: "ok",
|
||||
FechaISO: contrato.UpdatedAt.Format(time.RFC3339),
|
||||
})
|
||||
}
|
||||
|
||||
// Eventos: notificaciones enviadas
|
||||
for _, n := range notifLogs {
|
||||
estado := "ok"
|
||||
if n.Estado == "fallido" {
|
||||
estado = "error"
|
||||
} else if n.Estado == "pendiente" {
|
||||
estado = "info"
|
||||
}
|
||||
detalle := n.Asunto
|
||||
if n.Estado == "fallido" && n.ErrorMsg != "" {
|
||||
detalle += " · Error: " + n.ErrorMsg
|
||||
}
|
||||
reglaLabel := ""
|
||||
if n.Regla.ID > 0 {
|
||||
reglaLabel = " (" + n.Regla.Nombre + ")"
|
||||
}
|
||||
timeline = append(timeline, Evento{
|
||||
Tipo: "notificacion",
|
||||
Icono: "mail",
|
||||
Titulo: "Notificación enviada" + reglaLabel,
|
||||
Detalle: detalle,
|
||||
Estado: estado,
|
||||
FechaISO: n.CreatedAt.Format(time.RFC3339),
|
||||
})
|
||||
}
|
||||
|
||||
// Eventos: pagos / dispatches
|
||||
for _, p := range pagoLogs {
|
||||
estado := "ok"
|
||||
if p.Estado == "failed" {
|
||||
estado = "error"
|
||||
}
|
||||
detalle := p.Referencia
|
||||
if p.PayerEmail != "" {
|
||||
detalle += " · " + p.PayerEmail
|
||||
}
|
||||
if p.Fuente != "" {
|
||||
detalle += " · " + p.Fuente
|
||||
}
|
||||
timeline = append(timeline, Evento{
|
||||
Tipo: "pago",
|
||||
Icono: "currency",
|
||||
Titulo: "Pago recibido",
|
||||
Detalle: detalle,
|
||||
Estado: estado,
|
||||
FechaISO: p.CreatedAt.Format(time.RFC3339),
|
||||
})
|
||||
}
|
||||
|
||||
// Ordenar por fecha desc
|
||||
for i := 0; i < len(timeline)-1; i++ {
|
||||
for j := i + 1; j < len(timeline); j++ {
|
||||
if timeline[j].FechaISO > timeline[i].FechaISO {
|
||||
timeline[i], timeline[j] = timeline[j], timeline[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return c.JSON(fiber.Map{
|
||||
"contrato": fiber.Map{
|
||||
"id": contrato.ID,
|
||||
"cliente": contrato.Cliente.Nombre,
|
||||
"estado": contrato.Estado,
|
||||
},
|
||||
"timeline": timeline,
|
||||
"total": len(timeline),
|
||||
})
|
||||
}
|
||||
|
||||
func calcularFechaVencimiento(desde time.Time, periodicidad string) time.Time {
|
||||
switch periodicidad {
|
||||
case "mensual":
|
||||
|
||||
@@ -33,6 +33,7 @@ func RenovacionesRoutes(protected fiber.Router) {
|
||||
protected.Delete("/api/contratos/:id", controllers.DeleteContrato)
|
||||
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)
|
||||
|
||||
// ─── Plantillas de correo ─────────────────────────────────────────
|
||||
protected.Get("/plantillas-correo", middlewares.MenuMiddleware, controllers.PlantillasView)
|
||||
|
||||
Reference in New Issue
Block a user