feat(tareas): módulo de tablero kanban con drag & drop, comentarios y Telegram
- Modelos Tarea y TareaComentario con GORM, migración automática - Estados: por_hacer → en_progreso → revisión → hecho; prioridades: baja/media/alta/urgente - Drag & drop entre columnas via SortableJS (PUT /tarea/:id/estado) - CRUD completo: crear, editar, eliminar; asignación a usuario + fecha límite - Panel lateral de detalle: historial de comentarios, adjuntar archivos (uploads/tareas/:id) - Telegram: notifica asignación, cambio de estado y comentarios nuevos via sendTelegramAdmin - Seed automático bajo módulo "Administración", solo rol Administrador Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
0901ed0d53
commit
bc79553b7e
@@ -0,0 +1,85 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/sujit-baniya/fiber-boilerplate/app"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type Tarea struct {
|
||||
gorm.Model
|
||||
Titulo string `json:"titulo" gorm:"column:titulo;size:200"`
|
||||
Descripcion string `json:"descripcion" gorm:"column:descripcion;type:text"`
|
||||
Estado string `json:"estado" gorm:"column:estado;default:'por_hacer';index"`
|
||||
Prioridad string `json:"prioridad" gorm:"column:prioridad;default:'media'"`
|
||||
AsignadoID *uint `json:"asignado_id" gorm:"column:asignado_id;index"`
|
||||
Asignado *Users `json:"asignado" gorm:"foreignKey:AsignadoID"`
|
||||
CreadoPorID uint `json:"creado_por_id" gorm:"column:creado_por_id"`
|
||||
CreadoPor *Users `json:"creado_por" gorm:"foreignKey:CreadoPorID"`
|
||||
FechaLimite *time.Time `json:"fecha_limite" gorm:"column:fecha_limite"`
|
||||
Orden int `json:"orden" gorm:"column:orden;default:0"`
|
||||
}
|
||||
|
||||
func (Tarea) TableName() string { return "tarea" }
|
||||
|
||||
type TareaComentario struct {
|
||||
ID uint `json:"id" gorm:"primaryKey;autoIncrement"`
|
||||
TareaID uint `json:"tarea_id" gorm:"column:tarea_id;index"`
|
||||
AutorID uint `json:"autor_id" gorm:"column:autor_id"`
|
||||
Autor *Users `json:"autor" gorm:"foreignKey:AutorID"`
|
||||
Contenido string `json:"contenido" gorm:"column:contenido;type:text"`
|
||||
Archivos string `json:"archivos" gorm:"column:archivos;type:text"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
func (TareaComentario) TableName() string { return "tarea_comentario" }
|
||||
|
||||
func GetAllTareas() ([]Tarea, error) {
|
||||
var items []Tarea
|
||||
err := app.Http.Database.DB.
|
||||
Preload("Asignado").
|
||||
Preload("CreadoPor").
|
||||
Order("estado ASC, orden ASC, created_at ASC").
|
||||
Find(&items).Error
|
||||
return items, err
|
||||
}
|
||||
|
||||
func GetTareaByID(id uint) (*Tarea, error) {
|
||||
var t Tarea
|
||||
err := app.Http.Database.DB.
|
||||
Preload("Asignado").
|
||||
Preload("CreadoPor").
|
||||
First(&t, id).Error
|
||||
return &t, err
|
||||
}
|
||||
|
||||
func CreateTarea(t *Tarea) error {
|
||||
return app.Http.Database.DB.Create(t).Error
|
||||
}
|
||||
|
||||
func SaveTarea(t *Tarea) error {
|
||||
return app.Http.Database.DB.Save(t).Error
|
||||
}
|
||||
|
||||
func DeleteTarea(id uint) error {
|
||||
return app.Http.Database.DB.Delete(&Tarea{}, id).Error
|
||||
}
|
||||
|
||||
func CambiarEstadoTarea(id uint, estado string) error {
|
||||
return app.Http.Database.DB.Model(&Tarea{}).Where("id = ?", id).Update("estado", estado).Error
|
||||
}
|
||||
|
||||
func GetComentariosByTarea(tareaID uint) ([]TareaComentario, error) {
|
||||
var items []TareaComentario
|
||||
err := app.Http.Database.DB.
|
||||
Preload("Autor").
|
||||
Where("tarea_id = ?", tareaID).
|
||||
Order("created_at ASC").
|
||||
Find(&items).Error
|
||||
return items, err
|
||||
}
|
||||
|
||||
func CreateComentario(c *TareaComentario) error {
|
||||
return app.Http.Database.DB.Create(c).Error
|
||||
}
|
||||
@@ -254,6 +254,59 @@ func NotificarReniceAction(servidorNombre, accion string) {
|
||||
sendTelegramAdmin(msg)
|
||||
}
|
||||
|
||||
// ─── Tareas ──────────────────────────────────────────────────────────────────
|
||||
|
||||
func NotificarTareaAsignada(t *models.Tarea) {
|
||||
if t == nil || t.AsignadoID == nil {
|
||||
return
|
||||
}
|
||||
asignado := ""
|
||||
if t.Asignado != nil {
|
||||
asignado = t.Asignado.Name
|
||||
}
|
||||
msg := fmt.Sprintf("📋 <b>Nueva tarea asignada</b>\n<b>%s</b>\nAsignado a: %s\nPrioridad: %s\n\n%s",
|
||||
escapeTelegramHTML(t.Titulo),
|
||||
escapeTelegramHTML(asignado),
|
||||
escapeTelegramHTML(t.Prioridad),
|
||||
escapeTelegramHTML(t.Descripcion))
|
||||
sendTelegramAdmin(msg)
|
||||
}
|
||||
|
||||
func NotificarTareaEstado(t *models.Tarea) {
|
||||
if t == nil {
|
||||
return
|
||||
}
|
||||
etiquetas := map[string]string{
|
||||
"por_hacer": "📥 Por hacer",
|
||||
"en_progreso": "🔄 En progreso",
|
||||
"revision": "🔍 En revisión",
|
||||
"hecho": "✅ Hecho",
|
||||
}
|
||||
label := etiquetas[t.Estado]
|
||||
if label == "" {
|
||||
label = t.Estado
|
||||
}
|
||||
asignado := ""
|
||||
if t.Asignado != nil {
|
||||
asignado = "\nAsignado: " + escapeTelegramHTML(t.Asignado.Name)
|
||||
}
|
||||
msg := fmt.Sprintf("🔀 <b>Tarea movida → %s</b>\n<b>%s</b>%s",
|
||||
label,
|
||||
escapeTelegramHTML(t.Titulo),
|
||||
asignado)
|
||||
sendTelegramAdmin(msg)
|
||||
}
|
||||
|
||||
func NotificarTareaComentario(t *models.Tarea, contenido string) {
|
||||
if t == nil {
|
||||
return
|
||||
}
|
||||
msg := fmt.Sprintf("💬 <b>Nuevo comentario en tarea</b>\n<b>%s</b>\n\n%s",
|
||||
escapeTelegramHTML(t.Titulo),
|
||||
escapeTelegramHTML(contenido))
|
||||
sendTelegramAdmin(msg)
|
||||
}
|
||||
|
||||
func sendTelegramAdmin(mensaje string) {
|
||||
configs, err := models.GetAllTelegramConfigs()
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user