fix: seguridad del webhook de soporte, pagos en contabilidad y Telegram para tareas

- soporte: el webhook de correo entrante era público sin ninguna validación;
  ahora exige una API key (query ?key= o header) comparada en tiempo constante.
  Además evita tickets duplicados por reintentos del proveedor (dedup por
  Message-Id) y enhebra respuestas del mismo remitente en vez de abrir un
  ticket nuevo por cada correo.
- contabilidad: marcar una cuenta por cobrar/pagar como pagada ahora crea y
  vincula la Transaccion correspondiente (antes el dashboard de ingresos/
  egresos nunca reflejaba esos pagos). Se corrige además que actualizar una
  cuenta por cobrar borraba su transaccion_id en cada PUT.
- tareas: se activa por defecto el canal Telegram para tarea_asignada (estaba
  apagado desde el seed original) y se agrega un flujo real de vinculación de
  Telegram para el staff interno (código temporal + verificación), igual al
  que ya existía para los usuarios del portal — sin esto el chat_id de cada
  usuario había que pegarlo a mano y la notificación nunca llegaba.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Lizandro GD
2026-08-03 01:25:17 +00:00
co-authored by Claude Sonnet 5
parent 1127d944c7
commit d2bf699b60
14 changed files with 612 additions and 100 deletions
+61 -3
View File
@@ -490,17 +490,50 @@ func CreateCuentaCobro(cc *CuentaCobro) error {
return app.Http.Database.DB.Create(cc).Error
}
// UpdateCuentaCobro actualiza los campos editables de una cuenta por cobrar.
// No toca transaccion_id a propósito: ese vínculo solo lo debe crear
// MarcarCuentaCobroPagada, para no perderlo en una edición cualquiera.
func UpdateCuentaCobro(cc *CuentaCobro) error {
return app.Http.Database.DB.Model(&CuentaCobro{}).Where("id = ?", cc.ID).Updates(map[string]interface{}{
"cliente_id": cc.ClienteID,
"estado": cc.Estado,
"fecha_vencimiento": cc.FechaVencimiento,
"fecha_pago": cc.FechaPago,
"transaccion_id": cc.TransaccionID,
"notas": cc.Notas,
}).Error
}
// MarcarCuentaCobroPagada registra el pago de una cuenta por cobrar: crea la
// Transaccion de tipo "ingreso" correspondiente y la vincula. Es idempotente —
// si la cuenta ya estaba pagada, no crea una transacción duplicada.
func MarcarCuentaCobroPagada(id uint, fechaPago time.Time) error {
var cc CuentaCobro
if err := app.Http.Database.DB.Preload("Cliente").First(&cc, id).Error; err != nil {
return err
}
if cc.Estado == "pagado" {
return nil
}
desc := fmt.Sprintf("Pago cuenta por cobrar #%d: %s", cc.ID, cc.Descripcion)
if cc.Cliente.Nombre != "" {
desc = fmt.Sprintf("Pago cuenta por cobrar #%d (%s): %s", cc.ID, cc.Cliente.Nombre, cc.Descripcion)
}
t := &Transaccion{
Fecha: fechaPago,
Tipo: "ingreso",
Descripcion: desc,
Valor: cc.Valor,
}
if err := app.Http.Database.DB.Create(t).Error; err != nil {
return fmt.Errorf("no se pudo crear la transacción de pago: %w", err)
}
return app.Http.Database.DB.Model(&CuentaCobro{}).Where("id = ?", id).Updates(map[string]interface{}{
"estado": "pagado",
"fecha_pago": fechaPago,
"transaccion_id": t.ID,
}).Error
}
func DeleteCuentaCobro(id uint) error {
return app.Http.Database.DB.Delete(&CuentaCobro{}, id).Error
}
@@ -548,10 +581,35 @@ func UpdateCuentaPagar(cp *CuentaPagar) error {
}).Error
}
// MarcarCuentaPagarPagada registra el pago de una cuenta por pagar: crea la
// Transaccion de tipo "egreso" correspondiente y la vincula. Es idempotente —
// si la cuenta ya estaba pagada, no crea una transacción duplicada.
func MarcarCuentaPagarPagada(id uint, fechaPago time.Time) error {
var cp CuentaPagar
if err := app.Http.Database.DB.Preload("Entidad").First(&cp, id).Error; err != nil {
return err
}
if cp.Estado == "pagado" {
return nil
}
desc := fmt.Sprintf("Pago cuenta por pagar #%d: %s", cp.ID, cp.Descripcion)
if cp.Entidad.Nombre != "" {
desc = fmt.Sprintf("Pago cuenta por pagar #%d (%s): %s", cp.ID, cp.Entidad.Nombre, cp.Descripcion)
}
t := &Transaccion{
Fecha: fechaPago,
Tipo: "egreso",
Descripcion: desc,
Valor: cp.Valor,
EntidadID: &cp.EntidadID,
}
if err := app.Http.Database.DB.Create(t).Error; err != nil {
return fmt.Errorf("no se pudo crear la transacción de pago: %w", err)
}
return app.Http.Database.DB.Model(&CuentaPagar{}).Where("id = ?", id).Updates(map[string]interface{}{
"estado": "pagado",
"fecha_pago": fechaPago,
"estado": "pagado",
"fecha_pago": fechaPago,
"transaccion_id": t.ID,
}).Error
}