package controllers import ( "math" "strconv" "time" "github.com/gofiber/fiber/v2" "github.com/sujit-baniya/fiber-boilerplate/pkg/models" "github.com/sujit-baniya/fiber-boilerplate/pkg/services" ) func ContratosView(c *fiber.Ctx) error { return c.Render("renovaciones/contratos", fiber.Map{ "user": c.Locals("user"), "modules": c.Locals("userModules"), }, "layouts/main") } func GetContratos(c *fiber.Ctx) error { page, _ := strconv.Atoi(c.Query("page", "1")) limit, _ := strconv.Atoi(c.Query("limit", "10")) if page < 1 { page = 1 } offset := (page - 1) * limit records, total, err := models.GetAllContratos(limit, offset, c.Query("search"), c.Query("estado")) if err != nil { return c.Status(500).JSON(fiber.Map{"error": err.Error()}) } // Enriquecer con días restantes type ContratoDTO struct { models.Contrato DiasRestantes int `json:"dias_restantes"` Urgencia string `json:"urgencia"` // verde | amarillo | rojo } dtos := make([]ContratoDTO, len(records)) now := time.Now() for i, r := range records { dias := int(r.FechaVencimiento.Sub(now).Hours() / 24) urgencia := "verde" if dias <= 0 { urgencia = "rojo" } else if dias <= 30 { urgencia = "amarillo" } dtos[i] = ContratoDTO{Contrato: r, DiasRestantes: dias, Urgencia: urgencia} } return c.JSON(fiber.Map{ "registros": dtos, "total": total, "totalPages": int(math.Ceil(float64(total) / float64(limit))), "page": page, "limit": limit, }) } func CreateContrato(c *fiber.Ctx) error { type Input struct { ClienteID uint `json:"cliente_id" form:"cliente_id"` ServicioID uint `json:"servicio_id" form:"servicio_id"` FechaInicio string `json:"fecha_inicio" form:"fecha_inicio"` FechaVencimiento string `json:"fecha_vencimiento" form:"fecha_vencimiento"` PrecioAcordado float64 `json:"precio_acordado" form:"precio_acordado"` AutoRenovar bool `json:"auto_renovar" form:"auto_renovar"` Notas string `json:"notas" form:"notas"` } var inp Input if err := c.BodyParser(&inp); err != nil { return c.Status(400).JSON(fiber.Map{"error": err.Error()}) } fi, err := time.Parse("2006-01-02", inp.FechaInicio) if err != nil { return c.Status(400).JSON(fiber.Map{"error": "Fecha inicio inválida"}) } fv, err := time.Parse("2006-01-02", inp.FechaVencimiento) if err != nil { return c.Status(400).JSON(fiber.Map{"error": "Fecha vencimiento inválida"}) } m := models.Contrato{ ClienteID: inp.ClienteID, ServicioID: inp.ServicioID, FechaInicio: fi, FechaVencimiento: fv, PrecioAcordado: inp.PrecioAcordado, AutoRenovar: inp.AutoRenovar, Notas: inp.Notas, Estado: "activo", } if err := models.CreateContrato(m); err != nil { return c.Status(500).JSON(fiber.Map{"error": err.Error()}) } return c.Status(201).JSON(fiber.Map{"message": "Contrato creado", "ok": true}) } func UpdateContrato(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"}) } type Input struct { Estado string `json:"estado" form:"estado"` FechaVencimiento string `json:"fecha_vencimiento" form:"fecha_vencimiento"` PrecioAcordado float64 `json:"precio_acordado" form:"precio_acordado"` AutoRenovar bool `json:"auto_renovar" form:"auto_renovar"` Notas string `json:"notas" form:"notas"` } var inp Input if err := c.BodyParser(&inp); err != nil { return c.Status(400).JSON(fiber.Map{"error": err.Error()}) } existing, err := models.GetContratoByID(uint(id)) if err != nil { return c.Status(404).JSON(fiber.Map{"error": "No encontrado"}) } if inp.FechaVencimiento != "" { fv, err := time.Parse("2006-01-02", inp.FechaVencimiento) if err == nil { existing.FechaVencimiento = fv } } if inp.Estado != "" { existing.Estado = inp.Estado } existing.PrecioAcordado = inp.PrecioAcordado existing.AutoRenovar = inp.AutoRenovar existing.Notas = inp.Notas if err := models.UpdateContrato(*existing); err != nil { return c.Status(500).JSON(fiber.Map{"error": err.Error()}) } return c.JSON(fiber.Map{"message": "Actualizado", "ok": true}) } func RenovarContrato(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"}) } existing, err := models.GetContratoByID(uint(id)) if err != nil { return c.Status(404).JSON(fiber.Map{"error": "No encontrado"}) } // Calcular nueva fecha según periodicidad del servicio nuevaInicio := existing.FechaVencimiento.AddDate(0, 0, 1) nuevaVenc := calcularFechaVencimiento(nuevaInicio, existing.Servicio.Periodicidad) nuevo := models.Contrato{ ClienteID: existing.ClienteID, ServicioID: existing.ServicioID, FechaInicio: nuevaInicio, FechaVencimiento: nuevaVenc, PrecioAcordado: existing.PrecioAcordado, AutoRenovar: existing.AutoRenovar, Estado: "activo", Notas: "Renovación automática desde contrato #" + strconv.Itoa(int(existing.ID)), } if err := models.CreateContrato(nuevo); err != nil { return c.Status(500).JSON(fiber.Map{"error": err.Error()}) } // Marcar anterior como renovado existing.Estado = "renovado" models.UpdateContrato(*existing) return c.JSON(fiber.Map{"message": "Renovado", "ok": true}) } func EnviarCorreoContrato(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"}) } if err := services.EnviarCorreoManual(contrato); err != nil { return c.Status(500).JSON(fiber.Map{"error": err.Error()}) } return c.JSON(fiber.Map{"message": "Correo enviado", "ok": true}) } func DeleteContrato(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"}) } if err := models.DeleteContrato(uint(id)); err != nil { return c.Status(500).JSON(fiber.Map{"error": err.Error()}) } return c.JSON(fiber.Map{"message": "Eliminado", "ok": true}) } func calcularFechaVencimiento(desde time.Time, periodicidad string) time.Time { switch periodicidad { case "mensual": return desde.AddDate(0, 1, 0) case "trimestral": return desde.AddDate(0, 3, 0) case "semestral": return desde.AddDate(0, 6, 0) case "anual": return desde.AddDate(1, 0, 0) default: return desde.AddDate(1, 0, 0) } }