telegram
This commit is contained in:
@@ -87,12 +87,9 @@ func Migrate() {
|
|||||||
// Sistema de notificaciones por evento
|
// Sistema de notificaciones por evento
|
||||||
&models.NotifEventoConfig{},
|
&models.NotifEventoConfig{},
|
||||||
&models.SistemaNotificacion{},
|
&models.SistemaNotificacion{},
|
||||||
&models.ServidorAlertaUmbral{},
|
|
||||||
// Submódulo Partner
|
// Submódulo Partner
|
||||||
&models.PartnerRecurso{},
|
&models.PartnerRecurso{},
|
||||||
&models.PartnerComunicado{},
|
&models.PartnerComunicado{},
|
||||||
// Integración VCard API Admin (Laravel)
|
|
||||||
&models.VcardApiConfig{},
|
|
||||||
); err != nil {
|
); err != nil {
|
||||||
log.Fatalf("Error during main migration: %v", err)
|
log.Fatalf("Error during main migration: %v", err)
|
||||||
}
|
}
|
||||||
@@ -425,7 +422,6 @@ func SeedIntegraciones() {
|
|||||||
entries := []struct{ title, desc, url string }{
|
entries := []struct{ title, desc, url string }{
|
||||||
{"Hostinger", "Panel de VPS, dominios, hosting y DNS de Hostinger", "/app/hostinger"},
|
{"Hostinger", "Panel de VPS, dominios, hosting y DNS de Hostinger", "/app/hostinger"},
|
||||||
{"Cloudflare", "Gestión de zonas, DNS, SSL y firewall en Cloudflare", "/app/cloudflare"},
|
{"Cloudflare", "Gestión de zonas, DNS, SSL y firewall en Cloudflare", "/app/cloudflare"},
|
||||||
{"VCard API", "Integración con el sistema VCard externo (Laravel + Sanctum): usuarios, membresías, vcards, pagos y más", "/app/vcard-api"},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var insertados []models.Submodules
|
var insertados []models.Submodules
|
||||||
@@ -740,7 +736,6 @@ func MigratePortal() {
|
|||||||
&models.ProyectoFase{},
|
&models.ProyectoFase{},
|
||||||
&models.ProyectoAvance{},
|
&models.ProyectoAvance{},
|
||||||
&models.ProyectoEntregable{},
|
&models.ProyectoEntregable{},
|
||||||
&models.ProyectoDocumento{},
|
|
||||||
&models.ProyectoTicket{},
|
&models.ProyectoTicket{},
|
||||||
&models.TicketMensaje{},
|
&models.TicketMensaje{},
|
||||||
&models.PortalUser{},
|
&models.PortalUser{},
|
||||||
|
|||||||
@@ -75,12 +75,8 @@
|
|||||||
<p class="text-sm text-slate-600 mt-1" x-text="av.contenido"></p>
|
<p class="text-sm text-slate-600 mt-1" x-text="av.contenido"></p>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex gap-1 ml-4">
|
<div class="flex gap-1 ml-4">
|
||||||
<button @click="toggleAvanceVisible(av)"
|
<button @click="toggleAvanceVisible(av)" :title="av.visible ? 'Ocultar' : 'Publicar'" class="btn-icon" :class="av.visible ? 'text-green-500' : 'text-slate-400'">
|
||||||
:title="av.visible ? 'Visible en portal · clic para ocultar' : 'Oculto en portal · clic para publicar'"
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/><path stroke-linecap="round" stroke-linejoin="round" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/></svg>
|
||||||
class="btn-icon flex items-center gap-1 px-2 py-1 rounded-lg text-xs font-medium border transition-colors"
|
|
||||||
:class="av.visible ? 'text-green-600 border-green-200 bg-green-50 hover:bg-green-100' : 'text-slate-400 border-slate-200 bg-slate-50 hover:bg-slate-100'">
|
|
||||||
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/><path stroke-linecap="round" stroke-linejoin="round" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/></svg>
|
|
||||||
<span x-text="av.visible ? 'Visible' : 'Oculto'"></span>
|
|
||||||
</button>
|
</button>
|
||||||
<button @click="deleteAvance(av.ID)" class="btn-icon text-red-400"><svg class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/></svg></button>
|
<button @click="deleteAvance(av.ID)" class="btn-icon text-red-400"><svg class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/></svg></button>
|
||||||
</div>
|
</div>
|
||||||
@@ -310,17 +306,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Toast -->
|
|
||||||
<div x-show="toast.show" x-cloak x-transition
|
|
||||||
class="fixed bottom-6 right-6 z-50 px-5 py-3 rounded-xl shadow-lg text-white text-sm font-medium flex items-center gap-2"
|
|
||||||
:class="toast.type === 'error' ? 'bg-red-500' : 'bg-[#8eb02f]'">
|
|
||||||
<svg class="w-4 h-4 shrink-0" fill="none" stroke="currentColor" stroke-width="2.5" viewBox="0 0 24 24">
|
|
||||||
<path x-show="toast.type !== 'error'" stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7"/>
|
|
||||||
<path x-show="toast.type === 'error'" stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"/>
|
|
||||||
</svg>
|
|
||||||
<span x-text="toast.msg"></span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -340,7 +325,6 @@ function proyectoDetalle(proyectoId, slug) {
|
|||||||
showDocumentoModal: false,
|
showDocumentoModal: false,
|
||||||
documentoForm: { tipo:'contrato', nombre:'', descripcion:'' },
|
documentoForm: { tipo:'contrato', nombre:'', descripcion:'' },
|
||||||
documentoFile: null, documentoError:'',
|
documentoFile: null, documentoError:'',
|
||||||
toast: { show: false, msg: '', type: 'ok' },
|
|
||||||
|
|
||||||
async init() {
|
async init() {
|
||||||
await Promise.all([this.loadFases(), this.loadAvances(), this.loadEntregables(), this.loadTickets(), this.loadDocumentos()]);
|
await Promise.all([this.loadFases(), this.loadAvances(), this.loadEntregables(), this.loadTickets(), this.loadDocumentos()]);
|
||||||
@@ -406,10 +390,8 @@ function proyectoDetalle(proyectoId, slug) {
|
|||||||
await this.loadAvances();
|
await this.loadAvances();
|
||||||
},
|
},
|
||||||
async toggleAvanceVisible(av) {
|
async toggleAvanceVisible(av) {
|
||||||
const nuevoEstado = !av.visible;
|
await axios.put(`/app/proyectos/${this.proyectoId}/avances/${av.ID}`, {...av, visible:!av.visible});
|
||||||
await axios.put(`/app/proyectos/${this.proyectoId}/avances/${av.ID}`, {...av, visible:nuevoEstado});
|
|
||||||
await this.loadAvances();
|
await this.loadAvances();
|
||||||
this.showToast(nuevoEstado ? 'Avance visible en el portal del cliente' : 'Avance oculto en el portal del cliente');
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// ─── Entregables ───
|
// ─── Entregables ───
|
||||||
@@ -480,11 +462,6 @@ function proyectoDetalle(proyectoId, slug) {
|
|||||||
t._reply='';
|
t._reply='';
|
||||||
},
|
},
|
||||||
|
|
||||||
showToast(msg, type = 'ok') {
|
|
||||||
this.toast = { show: true, msg, type };
|
|
||||||
setTimeout(() => { this.toast.show = false; }, 3000);
|
|
||||||
},
|
|
||||||
|
|
||||||
// ─── Helpers ───
|
// ─── Helpers ───
|
||||||
faseBadgeClass(e) { return {pendiente:'badge-slate',en_progreso:'badge-yellow',completado:'badge-green',bloqueado:'badge-red'}[e]||'badge-slate'; },
|
faseBadgeClass(e) { return {pendiente:'badge-slate',en_progreso:'badge-yellow',completado:'badge-green',bloqueado:'badge-red'}[e]||'badge-slate'; },
|
||||||
avanceBadgeClass(t) { return {update:'badge-blue',milestone:'badge-green',nota:'badge-slate',alerta:'badge-yellow'}[t]||'badge-slate'; },
|
avanceBadgeClass(t) { return {update:'badge-blue',milestone:'badge-green',nota:'badge-slate',alerta:'badge-yellow'}[t]||'badge-slate'; },
|
||||||
|
|||||||
@@ -55,6 +55,12 @@
|
|||||||
<span x-show="!webhookLoading[cfg.ID]">🔗 Webhook portal</span>
|
<span x-show="!webhookLoading[cfg.ID]">🔗 Webhook portal</span>
|
||||||
<span x-show="webhookLoading[cfg.ID]">Registrando…</span>
|
<span x-show="webhookLoading[cfg.ID]">Registrando…</span>
|
||||||
</button>
|
</button>
|
||||||
|
<button @click="checkWebhook(cfg)" :disabled="webhookInfoLoading[cfg.ID]"
|
||||||
|
class="flex-1 text-xs border border-slate-300 text-slate-600 px-3 py-1.5 rounded hover:bg-slate-50 transition-colors disabled:opacity-50"
|
||||||
|
title="Consulta getWebhookInfo en Telegram para este bot">
|
||||||
|
<span x-show="!webhookInfoLoading[cfg.ID]">📡 Estado webhook</span>
|
||||||
|
<span x-show="webhookInfoLoading[cfg.ID]">Consultando…</span>
|
||||||
|
</button>
|
||||||
<button @click="openEdit(cfg)"
|
<button @click="openEdit(cfg)"
|
||||||
class="text-xs border border-blue-400 text-blue-500 px-3 py-1.5 rounded hover:bg-blue-50">Editar</button>
|
class="text-xs border border-blue-400 text-blue-500 px-3 py-1.5 rounded hover:bg-blue-50">Editar</button>
|
||||||
<button @click="confirmDelete(cfg.ID)"
|
<button @click="confirmDelete(cfg.ID)"
|
||||||
@@ -228,6 +234,7 @@ function telegramApp() {
|
|||||||
form: { nombre: '', bot_token: '', chat_id: '', notas: '', activo: true },
|
form: { nombre: '', bot_token: '', chat_id: '', notas: '', activo: true },
|
||||||
testLoading: {}, testResult: {}, testOk: {},
|
testLoading: {}, testResult: {}, testOk: {},
|
||||||
webhookLoading: {}, webhookResult: {}, webhookOk: {},
|
webhookLoading: {}, webhookResult: {}, webhookOk: {},
|
||||||
|
webhookInfoLoading: {},
|
||||||
sendForm: { config_ids: [], titulo: '', mensaje: '' },
|
sendForm: { config_ids: [], titulo: '', mensaje: '' },
|
||||||
sendLoading: false, sendResult: '', sendOk: true,
|
sendLoading: false, sendResult: '', sendOk: true,
|
||||||
logs: [], logPage: 1, logTotalPages: 1, logTotal: 0,
|
logs: [], logPage: 1, logTotalPages: 1, logTotal: 0,
|
||||||
@@ -312,6 +319,32 @@ function telegramApp() {
|
|||||||
this.webhookLoading[cfg.ID] = false;
|
this.webhookLoading[cfg.ID] = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
async checkWebhook(cfg) {
|
||||||
|
this.webhookInfoLoading[cfg.ID] = true;
|
||||||
|
this.webhookResult[cfg.ID] = '';
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/app/telegram/${cfg.ID}/webhook-info`);
|
||||||
|
const d = await res.json();
|
||||||
|
const info = d.result || {};
|
||||||
|
const hasPortalURL = (info.url || '').includes('/webhooks/telegram-portal');
|
||||||
|
const hasError = !!(info.last_error_message || info.last_synchronization_error_date);
|
||||||
|
|
||||||
|
this.webhookOk[cfg.ID] = !!d.ok && hasPortalURL && !hasError;
|
||||||
|
|
||||||
|
const parts = [];
|
||||||
|
if (info.url) parts.push(`URL: ${info.url}`);
|
||||||
|
if (typeof info.pending_update_count !== 'undefined') parts.push(`Pendientes: ${info.pending_update_count}`);
|
||||||
|
if (info.last_error_message) parts.push(`Último error: ${info.last_error_message}`);
|
||||||
|
if (!parts.length) parts.push(d.description || 'Sin datos de webhook');
|
||||||
|
|
||||||
|
this.webhookResult[cfg.ID] = (this.webhookOk[cfg.ID] ? '✅ ' : '⚠️ ') + parts.join(' | ');
|
||||||
|
} catch (e) {
|
||||||
|
this.webhookOk[cfg.ID] = false;
|
||||||
|
this.webhookResult[cfg.ID] = '❌ Error consultando webhook: ' + e.message;
|
||||||
|
} finally {
|
||||||
|
this.webhookInfoLoading[cfg.ID] = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
async sendMessage() {
|
async sendMessage() {
|
||||||
this.sendResult = '';
|
this.sendResult = '';
|
||||||
|
|||||||
@@ -822,6 +822,12 @@ func PortalTelegramValidar(c *fiber.Ctx) error {
|
|||||||
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "No autenticado"})
|
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "No autenticado"})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Si ya está vinculado (por ejemplo, vía webhook), responder éxito de inmediato.
|
||||||
|
full, err := models.GetPortalUserByID(u.ID)
|
||||||
|
if err == nil && full != nil && strings.TrimSpace(full.TelegramChatID) != "" {
|
||||||
|
return c.JSON(fiber.Map{"ok": true, "chat_id": full.TelegramChatID})
|
||||||
|
}
|
||||||
|
|
||||||
// Recuperar el token vigente del usuario
|
// Recuperar el token vigente del usuario
|
||||||
tkn, err := models.GetTelegramPortalTokenByUser(u.ID)
|
tkn, err := models.GetTelegramPortalTokenByUser(u.ID)
|
||||||
if err != nil || tkn == nil {
|
if err != nil || tkn == nil {
|
||||||
@@ -829,11 +835,15 @@ func PortalTelegramValidar(c *fiber.Ctx) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
configs, _ := models.GetAllTelegramConfigs()
|
configs, _ := models.GetAllTelegramConfigs()
|
||||||
|
hadWebhookConflict := false
|
||||||
for _, cfg := range configs {
|
for _, cfg := range configs {
|
||||||
if !cfg.Activo || cfg.BotToken == "" {
|
if !cfg.Activo || cfg.BotToken == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
chatID, found := searchTokenInUpdates(cfg.BotToken, tkn.Token)
|
chatID, found, reason := searchTokenInUpdates(cfg.BotToken, tkn.Token)
|
||||||
|
if reason == "webhook_conflict" {
|
||||||
|
hadWebhookConflict = true
|
||||||
|
}
|
||||||
if !found {
|
if !found {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -848,22 +858,36 @@ func PortalTelegramValidar(c *fiber.Ctx) error {
|
|||||||
return c.JSON(fiber.Map{"ok": true, "chat_id": fmt.Sprintf("%d", chatID)})
|
return c.JSON(fiber.Map{"ok": true, "chat_id": fmt.Sprintf("%d", chatID)})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Revalidar por si el webhook hizo la vinculación mientras corría esta petición.
|
||||||
|
full, err = models.GetPortalUserByID(u.ID)
|
||||||
|
if err == nil && full != nil && strings.TrimSpace(full.TelegramChatID) != "" {
|
||||||
|
return c.JSON(fiber.Map{"ok": true, "chat_id": full.TelegramChatID})
|
||||||
|
}
|
||||||
|
|
||||||
|
if hadWebhookConflict {
|
||||||
|
return c.Status(200).JSON(fiber.Map{
|
||||||
|
"ok": false,
|
||||||
|
"error": "El bot está en modo webhook. Envía /vincular " + tkn.Token + " al bot y espera unos segundos; luego vuelve a verificar.",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return c.Status(200).JSON(fiber.Map{"ok": false, "error": "No se encontró el mensaje. Asegúrate de enviar /vincular " + tkn.Token + " al bot."})
|
return c.Status(200).JSON(fiber.Map{"ok": false, "error": "No se encontró el mensaje. Asegúrate de enviar /vincular " + tkn.Token + " al bot."})
|
||||||
}
|
}
|
||||||
|
|
||||||
// searchTokenInUpdates llama getUpdates y busca un mensaje que contenga el token.
|
// searchTokenInUpdates llama getUpdates y busca un mensaje que contenga el token.
|
||||||
// Retorna el chat_id y true si lo encuentra.
|
// Retorna el chat_id y true si lo encuentra.
|
||||||
func searchTokenInUpdates(botToken, token string) (int64, bool) {
|
func searchTokenInUpdates(botToken, token string) (int64, bool, string) {
|
||||||
apiURL := fmt.Sprintf("https://api.telegram.org/bot%s/getUpdates?limit=100", botToken)
|
apiURL := fmt.Sprintf("https://api.telegram.org/bot%s/getUpdates?limit=100", botToken)
|
||||||
resp, err := http.Get(apiURL) //nolint:noctx
|
resp, err := http.Get(apiURL) //nolint:noctx
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[searchTokenInUpdates] error getUpdates: %v", err)
|
log.Printf("[searchTokenInUpdates] error getUpdates: %v", err)
|
||||||
return 0, false
|
return 0, false, "network_error"
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
var result struct {
|
var result struct {
|
||||||
OK bool `json:"ok"`
|
OK bool `json:"ok"`
|
||||||
|
Description string `json:"description"`
|
||||||
Result []struct {
|
Result []struct {
|
||||||
Message struct {
|
Message struct {
|
||||||
Chat struct {
|
Chat struct {
|
||||||
@@ -873,8 +897,18 @@ func searchTokenInUpdates(botToken, token string) (int64, bool) {
|
|||||||
} `json:"message"`
|
} `json:"message"`
|
||||||
} `json:"result"`
|
} `json:"result"`
|
||||||
}
|
}
|
||||||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil || !result.OK {
|
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||||
return 0, false
|
return 0, false, "decode_error"
|
||||||
|
}
|
||||||
|
if !result.OK {
|
||||||
|
desc := strings.ToLower(strings.TrimSpace(result.Description))
|
||||||
|
if strings.Contains(desc, "can't use getupdates method while webhook is active") {
|
||||||
|
return 0, false, "webhook_conflict"
|
||||||
|
}
|
||||||
|
if desc != "" {
|
||||||
|
log.Printf("[searchTokenInUpdates] Telegram API !ok: %s", result.Description)
|
||||||
|
}
|
||||||
|
return 0, false, "api_not_ok"
|
||||||
}
|
}
|
||||||
|
|
||||||
upperToken := strings.ToUpper(token)
|
upperToken := strings.ToUpper(token)
|
||||||
@@ -882,8 +916,8 @@ func searchTokenInUpdates(botToken, token string) (int64, bool) {
|
|||||||
txt := strings.ToUpper(strings.TrimSpace(upd.Message.Text))
|
txt := strings.ToUpper(strings.TrimSpace(upd.Message.Text))
|
||||||
if strings.Contains(txt, upperToken) && upd.Message.Chat.ID != 0 {
|
if strings.Contains(txt, upperToken) && upd.Message.Chat.ID != 0 {
|
||||||
log.Printf("[searchTokenInUpdates] token=%s chatID=%d", token, upd.Message.Chat.ID)
|
log.Printf("[searchTokenInUpdates] token=%s chatID=%d", token, upd.Message.Chat.ID)
|
||||||
return upd.Message.Chat.ID, true
|
return upd.Message.Chat.ID, true, ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return 0, false
|
return 0, false, ""
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
"net/url"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -492,25 +491,10 @@ func DownloadDocumento(c *fiber.Ctx) error {
|
|||||||
if !strings.HasPrefix(clean, "uploads/") {
|
if !strings.HasPrefix(clean, "uploads/") {
|
||||||
return c.Status(403).JSON(fiber.Map{"error": "Acceso denegado"})
|
return c.Status(403).JSON(fiber.Map{"error": "Acceso denegado"})
|
||||||
}
|
}
|
||||||
nombre := item.OriginalName
|
c.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, item.OriginalName))
|
||||||
if nombre == "" {
|
|
||||||
nombre = item.Nombre
|
|
||||||
}
|
|
||||||
c.Set("Content-Disposition", attachmentDisposition(nombre))
|
|
||||||
return c.SendFile(clean)
|
return c.SendFile(clean)
|
||||||
}
|
}
|
||||||
|
|
||||||
// attachmentDisposition genera un header Content-Disposition con fallback ASCII y encoding RFC 5987.
|
|
||||||
func attachmentDisposition(name string) string {
|
|
||||||
safe := strings.Map(func(r rune) rune {
|
|
||||||
if r > 127 || r == '"' || r == '\\' || r == '/' || r == '\n' || r == '\r' {
|
|
||||||
return '_'
|
|
||||||
}
|
|
||||||
return r
|
|
||||||
}, name)
|
|
||||||
return fmt.Sprintf(`attachment; filename="%s"; filename*=UTF-8''%s`, safe, url.PathEscape(name))
|
|
||||||
}
|
|
||||||
|
|
||||||
// ─── Tickets (admin) ──────────────────────────────────────────────────────────
|
// ─── Tickets (admin) ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
func GetTickets(c *fiber.Ctx) error {
|
func GetTickets(c *fiber.Ctx) error {
|
||||||
|
|||||||
+1
-45
@@ -21,7 +21,7 @@ func UserRoutes(app fiber.Router) {
|
|||||||
|
|
||||||
// Rutas de la aplicación
|
// Rutas de la aplicación
|
||||||
|
|
||||||
// web me redireccione a /
|
// web me redireccione a /
|
||||||
protected.Get("/web", func(c *fiber.Ctx) error { return c.Redirect("/", http.StatusSeeOther) })
|
protected.Get("/web", func(c *fiber.Ctx) error { return c.Redirect("/", http.StatusSeeOther) })
|
||||||
app.Get("/web", func(c *fiber.Ctx) error { return c.Redirect("/", http.StatusSeeOther) })
|
app.Get("/web", func(c *fiber.Ctx) error { return c.Redirect("/", http.StatusSeeOther) })
|
||||||
protected.Get("/", controllers.App)
|
protected.Get("/", controllers.App)
|
||||||
@@ -71,23 +71,17 @@ func UserRoutes(app fiber.Router) {
|
|||||||
|
|
||||||
// Rutas de servidor
|
// Rutas de servidor
|
||||||
protected.Get("/servidor", middlewares.MenuMiddleware, controllers.Servidor)
|
protected.Get("/servidor", middlewares.MenuMiddleware, controllers.Servidor)
|
||||||
protected.Get("/servidor-dashboard", middlewares.MenuMiddleware, controllers.ServidorDashboard)
|
|
||||||
protected.Get("/loadservidorselect", controllers.GetServidor)
|
protected.Get("/loadservidorselect", controllers.GetServidor)
|
||||||
protected.Get("/loadservidor", controllers.GetServidor)
|
protected.Get("/loadservidor", controllers.GetServidor)
|
||||||
protected.Post("/servidor", controllers.CreateServidor)
|
protected.Post("/servidor", controllers.CreateServidor)
|
||||||
protected.Put("/servidor/:id", controllers.UpdateServidor)
|
protected.Put("/servidor/:id", controllers.UpdateServidor)
|
||||||
protected.Delete("/servidor/:id", controllers.DeleteServidor)
|
protected.Delete("/servidor/:id", controllers.DeleteServidor)
|
||||||
protected.Get("/servidor-dashboard/:id", controllers.GetServidorDashboard)
|
|
||||||
protected.Get("/conx-ping/:id", controllers.PingConexion)
|
|
||||||
protected.Post("/servidor/:id/agent-token", controllers.GenerateAgentToken)
|
|
||||||
protected.Post("/servidor/:id/sync-hostinger", controllers.SyncServidorFromHostinger)
|
|
||||||
|
|
||||||
// Rutas de proveedores de servidor
|
// Rutas de proveedores de servidor
|
||||||
protected.Get("/prov_servidor", middlewares.MenuMiddleware, controllers.ProvServidor)
|
protected.Get("/prov_servidor", middlewares.MenuMiddleware, controllers.ProvServidor)
|
||||||
protected.Get("/loadprovservidor", controllers.GetProvServidor)
|
protected.Get("/loadprovservidor", controllers.GetProvServidor)
|
||||||
protected.Post("/provservidor", controllers.CreateProvServidor)
|
protected.Post("/provservidor", controllers.CreateProvServidor)
|
||||||
protected.Put("/provservidor/:id", controllers.UpdateProvServidor)
|
protected.Put("/provservidor/:id", controllers.UpdateProvServidor)
|
||||||
protected.Put("/provservidor/:id/integraciones", controllers.SetProvServidorIntegraciones)
|
|
||||||
protected.Delete("/provservidor/:id", controllers.DeleteProvServidor)
|
protected.Delete("/provservidor/:id", controllers.DeleteProvServidor)
|
||||||
|
|
||||||
// Rutas de tipos de servidor
|
// Rutas de tipos de servidor
|
||||||
@@ -221,41 +215,6 @@ func UserRoutes(app fiber.Router) {
|
|||||||
protected.Get("/saas-api/logs", middlewares.MenuMiddleware, controllers.SaasDispatchLogIndex)
|
protected.Get("/saas-api/logs", middlewares.MenuMiddleware, controllers.SaasDispatchLogIndex)
|
||||||
protected.Get("/loadsaasdispatchlogs", controllers.GetSaasDispatchLogs)
|
protected.Get("/loadsaasdispatchlogs", controllers.GetSaasDispatchLogs)
|
||||||
|
|
||||||
// ─── VCard API (integración Admin Laravel) ────────────────────────────────
|
|
||||||
protected.Get("/vcard-api", middlewares.MenuMiddleware, controllers.VcardApiIndex)
|
|
||||||
protected.Post("/vcard-api/login", controllers.VcardApiLogin)
|
|
||||||
protected.Get("/vcard-api/config", controllers.VcardApiGetConfig)
|
|
||||||
protected.Post("/vcard-api/config", controllers.VcardApiSaveConfig)
|
|
||||||
// Usuarios (GET)
|
|
||||||
protected.Get("/vcard-api/usuarios", controllers.VcardApiUsuarios)
|
|
||||||
protected.Get("/vcard-api/usuarios/:id/membresia", controllers.VcardApiMembresia)
|
|
||||||
protected.Get("/vcard-api/usuarios/:userId/vcards", controllers.VcardApiVcardsByUsuario)
|
|
||||||
protected.Get("/vcard-api/usuarios/:userId/pagos", controllers.VcardApiPagosByUsuario)
|
|
||||||
protected.Get("/vcard-api/usuarios/:userId/transacciones", controllers.VcardApiTransaccionesByUsuario)
|
|
||||||
protected.Get("/vcard-api/usuarios/:userId/logs", controllers.VcardApiLogsByUsuario)
|
|
||||||
protected.Get("/vcard-api/usuarios/:userId/miniwebs", controllers.VcardApiMiniwebsByUsuario)
|
|
||||||
protected.Get("/vcard-api/usuarios/:id", controllers.VcardApiUsuario)
|
|
||||||
// Usuarios (mutaciones)
|
|
||||||
protected.Put("/vcard-api/usuarios/:id", controllers.VcardApiUsuarioUpdate)
|
|
||||||
protected.Post("/vcard-api/usuarios/:id/activar", controllers.VcardApiUsuarioActivar)
|
|
||||||
protected.Post("/vcard-api/usuarios/:id/desactivar", controllers.VcardApiUsuarioDesactivar)
|
|
||||||
protected.Post("/vcard-api/usuarios/:id/activar-membresia", controllers.VcardApiActivarMembresia)
|
|
||||||
protected.Post("/vcard-api/usuarios/:id/desactivar-membresia", controllers.VcardApiDesactivarMembresia)
|
|
||||||
protected.Post("/vcard-api/usuarios/:id/cambiar-plan", controllers.VcardApiCambiarPlan)
|
|
||||||
// VCards
|
|
||||||
protected.Get("/vcard-api/vcards", controllers.VcardApiVcards)
|
|
||||||
protected.Get("/vcard-api/vcards/:id", controllers.VcardApiVcard)
|
|
||||||
protected.Put("/vcard-api/vcards/:id", controllers.VcardApiVcardUpdate)
|
|
||||||
// Resto (solo GET)
|
|
||||||
protected.Get("/vcard-api/planes", controllers.VcardApiPlanes)
|
|
||||||
protected.Get("/vcard-api/planes/:id", controllers.VcardApiPlan)
|
|
||||||
protected.Put("/vcard-api/planes/:id", controllers.VcardApiPlanUpdate)
|
|
||||||
protected.Get("/vcard-api/pagos", controllers.VcardApiPagos)
|
|
||||||
protected.Get("/vcard-api/transacciones", controllers.VcardApiTransacciones)
|
|
||||||
protected.Get("/vcard-api/logs", controllers.VcardApiLogs)
|
|
||||||
protected.Get("/vcard-api/miniwebs", controllers.VcardApiMiniwebs)
|
|
||||||
protected.Get("/vcard-api/miniwebs/:id", controllers.VcardApiMiniweb)
|
|
||||||
|
|
||||||
// ─── Telegram ─────────────────────────────────────────────────────────────
|
// ─── Telegram ─────────────────────────────────────────────────────────────
|
||||||
protected.Get("/telegram", middlewares.MenuMiddleware, controllers.TelegramIndex)
|
protected.Get("/telegram", middlewares.MenuMiddleware, controllers.TelegramIndex)
|
||||||
protected.Get("/loadtelegram", controllers.GetTelegramConfigs)
|
protected.Get("/loadtelegram", controllers.GetTelegramConfigs)
|
||||||
@@ -346,9 +305,6 @@ func UserRoutes(app fiber.Router) {
|
|||||||
protected.Get("/notif-config", middlewares.MenuMiddleware, controllers.NotifConfigIndex)
|
protected.Get("/notif-config", middlewares.MenuMiddleware, controllers.NotifConfigIndex)
|
||||||
protected.Get("/notif-config/data", controllers.GetNotifConfigs)
|
protected.Get("/notif-config/data", controllers.GetNotifConfigs)
|
||||||
protected.Post("/notif-config", controllers.SaveNotifConfig)
|
protected.Post("/notif-config", controllers.SaveNotifConfig)
|
||||||
// Umbrales de alerta de servidores
|
|
||||||
protected.Get("/servidor-alerta-config", controllers.GetServidorAlertaConfig)
|
|
||||||
protected.Post("/servidor-alerta-config", controllers.SaveServidorAlertaConfig)
|
|
||||||
|
|
||||||
// Partner Recursos (documentación y comunicados para partners)
|
// Partner Recursos (documentación y comunicados para partners)
|
||||||
protected.Get("/partner-recursos", middlewares.MenuMiddleware, controllers.PartnerRecursosIndex)
|
protected.Get("/partner-recursos", middlewares.MenuMiddleware, controllers.PartnerRecursosIndex)
|
||||||
|
|||||||
Reference in New Issue
Block a user