diff --git a/pkg/models/servidor.go b/pkg/models/servidor.go index f2a8581..b9189dd 100755 --- a/pkg/models/servidor.go +++ b/pkg/models/servidor.go @@ -27,8 +27,9 @@ type Servidor struct { AgentLastSeen *time.Time `json:"agent_last_seen" gorm:"column:agent_last_seen"` MetricasJson string `json:"metricas_json" gorm:"column:metricas_json;type:text"` // Integración Hostinger - HostingerVpsID *int `json:"hostinger_vps_id" gorm:"column:hostinger_vps_id"` - HostingerState string `json:"hostinger_state" gorm:"column:hostinger_state"` + HostingerVpsID *int `json:"hostinger_vps_id" gorm:"column:hostinger_vps_id"` + HostingerState string `json:"hostinger_state" gorm:"column:hostinger_state"` + HostingerSubscriptionID string `json:"hostinger_subscription_id" gorm:"column:hostinger_subscription_id"` } func (Servidor) TableName() string { diff --git a/pkg/services/hostinger_service.go b/pkg/services/hostinger_service.go index 6b35a77..2d6eace 100644 --- a/pkg/services/hostinger_service.go +++ b/pkg/services/hostinger_service.go @@ -111,16 +111,17 @@ type HostingerIPAddress struct { // HostingerVPS representa una máquina virtual VPS. type HostingerVPS struct { - ID int `json:"id"` - Hostname string `json:"hostname"` - State string `json:"state"` - Plan string `json:"plan"` - DataCenter string `json:"data_center"` - CPU int `json:"cpus"` - RAMBytes int64 `json:"memory"` - DiskBytes int64 `json:"disk"` - IPV4 []HostingerIPAddress `json:"ipv4"` - IPV6 []HostingerIPAddress `json:"ipv6"` + ID int `json:"id"` + Hostname string `json:"hostname"` + State string `json:"state"` + Plan string `json:"plan"` + DataCenter string `json:"data_center"` + CPU int `json:"cpus"` + RAMBytes int64 `json:"memory"` + DiskBytes int64 `json:"disk"` + IPV4 []HostingerIPAddress `json:"ipv4"` + IPV6 []HostingerIPAddress `json:"ipv6"` + SubscriptionID string `json:"subscription_id"` } // HostingerDomain representa un dominio en el portafolio. @@ -303,6 +304,20 @@ func (c *HostingerClient) GetDomainNameServers(domain string) (*HostingerNameSer return &resp.NameServers, nil } +// GetSubscriptionByID obtiene una suscripción de billing por su ID. +func (c *HostingerClient) GetSubscriptionByID(subID string) (*HostingerSubscription, error) { + path := fmt.Sprintf("/billing/v1/subscriptions/%s", subID) + body, err := c.getRaw(context.Background(), path) + if err != nil { + return nil, err + } + var sub HostingerSubscription + if err := json.Unmarshal(body, &sub); err != nil { + return nil, fmt.Errorf("hostinger: no se pudo interpretar suscripción: %w", err) + } + return &sub, nil +} + // GetOrders obtiene las suscripciones de facturación. func (c *HostingerClient) GetOrders() ([]HostingerSubscription, error) { body, err := c.getRaw(context.Background(), "/billing/v1/subscriptions") diff --git a/resources/views/servidor.html b/resources/views/servidor.html index 264cf5f..2545bc0 100755 --- a/resources/views/servidor.html +++ b/resources/views/servidor.html @@ -275,15 +275,21 @@ -
- +
+ +
+
+ +
+
@@ -454,6 +460,7 @@ tipo_servidor_id: data.tipo_servidor_id, hostinger_vps_id: data.hostinger_vps_id, hostinger_state: data.hostinger_state, + hostinger_subscription_id: data.hostinger_subscription_id, created_at: data.CreatedAt, })); console.log('📊 Servidores procesados:', this.datos.length); @@ -625,6 +632,7 @@ prov_servidor_id: this.selectedItem.prov_servidor_id, tipo_servidor_id: this.selectedItem.tipo_servidor_id, hostinger_vps_id: this.selectedItem.hostinger_vps_id ? parseInt(this.selectedItem.hostinger_vps_id) : null, + hostinger_subscription_id: this.selectedItem.hostinger_subscription_id || '', }; this.setLoading(true); diff --git a/resources/views/servidor_dashboard.html b/resources/views/servidor_dashboard.html index 68a5c2f..56cc4d2 100644 --- a/resources/views/servidor_dashboard.html +++ b/resources/views/servidor_dashboard.html @@ -244,6 +244,16 @@

+
+

🔗 Hostinger VPS

+

+ ID: + + Sub: + +

+
+

📡 Último Ping

diff --git a/rest/controllers/servidor_controller.go b/rest/controllers/servidor_controller.go index bfe4787..9736426 100755 --- a/rest/controllers/servidor_controller.go +++ b/rest/controllers/servidor_controller.go @@ -232,14 +232,31 @@ func SyncServidorFromHostinger(c *fiber.Ctx) error { updates["disco"] = fmt.Sprintf("%d GB", vps.DiskBytes) } - // Vencimiento: buscar suscripción cuyo nombre contenga el hostname del VPS - if subs, sErr := client.GetOrders(); sErr == nil { - for _, sub := range subs { - if sub.ExpiresAt != "" && len(sub.ExpiresAt) >= 10 && - (strings.Contains(strings.ToLower(sub.Name), strings.ToLower(vps.Hostname)) || - strings.Contains(strings.ToLower(sub.Name), "vps")) { - updates["vencimiento"] = sub.ExpiresAt[:10] - break + // Vencimiento: resolución en cascada + // 1) subscription_id que viene directo del VPS (el más confiable) + // 2) subscription_id ya guardado en el servidor local + // 3) fallback fuzzy por nombre + subID := vps.SubscriptionID + if subID == "" && servidor.HostingerSubscriptionID != "" { + subID = servidor.HostingerSubscriptionID + } + + if subID != "" { + // Lookup directo — sin ambigüedad + if sub, sErr := client.GetSubscriptionByID(subID); sErr == nil && sub.ExpiresAt != "" && len(sub.ExpiresAt) >= 10 { + updates["vencimiento"] = sub.ExpiresAt[:10] + updates["hostinger_subscription_id"] = subID + } + } else { + // Fallback: buscar por nombre si aún no tenemos subscription_id + if subs, sErr := client.GetOrders(); sErr == nil { + for _, sub := range subs { + if sub.ExpiresAt != "" && len(sub.ExpiresAt) >= 10 && + strings.Contains(strings.ToLower(sub.Name), strings.ToLower(vps.Hostname)) { + updates["vencimiento"] = sub.ExpiresAt[:10] + updates["hostinger_subscription_id"] = sub.ID + break + } } } }