Referencia API

Documentación completa de todos los endpoints

Visión General

La API de KeyNexus sigue los principios REST y utiliza JSON para todas las peticiones y respuestas.

Base URL

https://keynexus.es/api

Content-Type

application/json

Autenticación

Las credenciales se envían en el body de la petición, no en headers:

appIdID de la aplicación (ej: Loi0W4jxVWVcnXqy)
secretKeyClave secreta de la aplicación
Content-Typeapplication/json (header requerido)
CookieSesión JWT para endpoints autenticados

Autenticación API

Seguridad: Nunca expongas tu Secret Key en el código del cliente. Usa el flujo de autenticación adecuado para cada caso.

Validación de Licencia (Cliente)

Para validar licencias desde aplicaciones de escritorio o cliente.

curl -X POST https://keynexus.es/api/client \
  -H "Content-Type: application/json" \
  -d '{
    "action": "license",
    "appId": "app_xxxxxxxxxxxx",
    "secretKey": "sk_xxxxxxxxxxxxxxxxxxxx",
    "key": "XXXXX-XXXXX-XXXXX-XXXXX",
    "hwid": "hardware_id"
  }'

Session Token (Usuario Autenticado)

Para obtener información del usuario autenticado en el portal cliente.

curl https://keynexus.es/api/client/me \
  -H "Cookie: client_token=eyJhbGciOiJIUzI1NiIs..."

Endpoints

API Cliente (Aplicaciones Externas)

POST/api/clientaction: init | license | login | register | check
POST/api/client/authLogin usuario/contraseña (web)
POST/api/client/logoutCerrar sesión
GET/api/client/meObtener usuario actual
GET/api/client/sessionsVer sesiones activas

Licencias

GET/api/licensesListar licencias
POST/api/licensesCrear licencia manual
POST/api/licenses/generateGenerar múltiples licencias
POST/api/licenses/validateValidar licencia
GET/api/licenses/verifyVerificar licencia
POST/api/licenses/:key/extendExtender licencia
POST/api/licenses/:key/revokeRevocar licencia

Usuarios (Dashboard)

GET/api/usersListar usuarios de apps
GET/api/applications/:id/usersUsuarios de una app
PUT/api/applications/:id/users/:userIdActualizar usuario
DELETE/api/applications/:id/users/:userIdEliminar usuario

Sesiones

GET/api/sessionsListar sesiones
POST/api/sessionsCrear sesión (interno)
DELETE/api/sessions/:idRevocar sesión

Blacklist / Whitelist

GET/api/whitelistListar whitelist/blacklist
POST/api/whitelistAñadir entrada
GET/api/blacklistListar blacklist
POST/api/blacklistAñadir a blacklist

Formato de Respuestas

Todas las respuestas siguen un formato consistente:

✓ Init Exitoso

{
  "success": true,
  "message": "Aplicación inicializada correctamente",
  "appName": "MiApp",
  "version": "1.0.0",
  "freeMode": false
}

✓ Licencia Válida

{
  "success": true,
  "message": "Licencia válida",
  "license": {
    "type": "time-based",
    "expiresAt": "2026-01-04T02:04:24.473Z",
    "daysLeft": 30
  }
}

✗ Licencia Inválida

{
  "success": false,
  "message": "Licencia no válida"
}

✗ HWID Bloqueado

{
  "success": false,
  "message": "HWID en lista negra"
}

Códigos de Error

MensajeHTTPDescripción
Aplicación no encontrada200appId o secretKey incorrectos
Licencia no válida200La licencia no existe
Licencia expirada200La licencia ha expirado
Licencia revocada200La licencia fue revocada por el admin
HWID no autorizado200HWID no coincide con el registrado (HWID lock activo)
HWID en lista negra200El HWID está en la blacklist
IP en lista negra200La IP está en la blacklist
Credenciales inválidas200Usuario o contraseña incorrectos
Cuenta baneada200El usuario está baneado
Nota: Todas las respuestas tienen HTTP 200. Usa el campo success para determinar si la operación fue exitosa.

Seguridad

Protección avanzada para tu aplicación

Bloqueo HWID (Hardware ID)

El bloqueo HWID vincula una licencia a un dispositivo específico, previniendo que se comparta o use en múltiples máquinas.

Componentes del HWID

  • • ID del procesador (CPU)
  • • MAC Address de la red
  • • Serial del disco duro
  • • Serial de la BIOS
  • • ID de la placa base

Modos de Bloqueo

  • Strict: Todos los componentes deben coincidir
  • Flexible: Mayoría de componentes (3/5)
  • Lenient: Al menos 2 componentes
Obtener HWID del sistema (C#)csharp
using System.Management;

public static string GetHwid()
{
    var components = new List<string>();
    
    // CPU ID
    var cpu = new ManagementObjectSearcher("SELECT ProcessorId FROM Win32_Processor");
    foreach (var obj in cpu.Get())
        components.Add(obj["ProcessorId"]?.ToString() ?? "");
    
    // Disk Serial
    var disk = new ManagementObjectSearcher("SELECT SerialNumber FROM Win32_DiskDrive");
    foreach (var obj in disk.Get())
        components.Add(obj["SerialNumber"]?.ToString() ?? "");
    
    // BIOS Serial
    var bios = new ManagementObjectSearcher("SELECT SerialNumber FROM Win32_BIOS");
    foreach (var obj in bios.Get())
        components.Add(obj["SerialNumber"]?.ToString() ?? "");
    
    // Combinar y hashear
    var combined = string.Join("-", components);
    using var sha = SHA256.Create();
    var hash = sha.ComputeHash(Encoding.UTF8.GetBytes(combined));
    return Convert.ToBase64String(hash);
}
Reset HWID: Puedes permitir a los usuarios resetear su HWID un número limitado de veces desde el dashboard o API.

Seguridad IP

KeyNexus ofrece múltiples capas de protección basadas en IP:

Rate Limiting

Limita peticiones por IP para prevenir ataques de fuerza bruta.

Geo-Blocking

Bloquea acceso desde países específicos.

VPN Detection

Detecta y bloquea conexiones VPN/Proxy.

Configurar restricciones de IPjson
{
  "ipSecurity": {
    "rateLimit": {
      "enabled": true,
      "maxRequests": 60,
      "windowMs": 60000
    },
    "geoBlocking": {
      "enabled": true,
      "mode": "blacklist",
      "countries": ["RU", "CN", "KP"]
    },
    "vpnDetection": {
      "enabled": true,
      "action": "block"
    },
    "ipWhitelist": [
      "192.168.1.0/24",
      "10.0.0.0/8"
    ]
  }
}

Whitelist / Blacklist

Controla el acceso a tu aplicación mediante listas de permitidos y bloqueados:

Whitelist (Permitir)

  • IP:192.168.1.100
  • HWID:abc123...
  • País:US, CA, MX
  • Rango:10.0.0.0/8

Blacklist (Bloquear)

  • IP:203.0.113.50
  • HWID:xyz789...
  • País:*(todos excepto whitelist)
  • Pattern:192.168.*.*
API: Agregar a blacklistbash
curl -X POST https://keynexus.es/api/v1/blacklist \
  -H "Authorization: Bearer YOUR_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "ip",
    "value": "203.0.113.50",
    "reason": "Intento de piratería detectado",
    "expiresAt": null
  }'

Anti-Debug y Protección

Nota: La protección anti-debug se implementa en el lado del cliente y requiere integración con el SDK.

Técnicas de Protección

Detección de debuggers (OllyDbg, x64dbg, etc.)
Anti-tampering del ejecutable
Detección de máquinas virtuales
Verificación de integridad del código
Ofuscación de strings y llamadas API
Comunicación encriptada (AES-256)
Habilitar protecciones en el SDKcsharp
var KeyNexus = new KeyNexusClient(new KeyNexusOptions
{
    ApplicationId = "app_xxx",
    SecretKey = "key_xxx",
    
    // Opciones de seguridad
    Security = new SecurityOptions
    {
        AntiDebug = true,
        AntiVM = true,
        IntegrityCheck = true,
        EncryptCommunication = true,
        
        // Callback cuando se detecta manipulación
        OnTamperDetected = (reason) =>
        {
            // Reportar al servidor y cerrar
            KeyNexus.ReportTamper(reason);
            Environment.Exit(1);
        }
    }
});

Integraciones

SDKs y ejemplos para cada lenguaje

C#
C# / .NET

Instalaciónbash
dotnet add package KeyNexus.SDK --version 2.0.0
Ejemplo completo de integracióncsharp
using KeyNexus;
using KeyNexus.Models;

class Program
{
    static KeyNexusClient KeyNexus;

    static async Task Main(string[] args)
    {
        // Inicializar cliente
        KeyNexus = new KeyNexusClient(new KeyNexusOptions
        {
            ApplicationId = "app_xxxxxxxxxxxx",
            SecretKey = "sk_xxxxxxxxxxxxxxxxxxxx",
            Version = "1.0.0"
        });

        // Inicializar aplicación
        var initResult = await KeyNexus.InitializeAsync();
        if (!initResult.Success)
        {
            Console.WriteLine($"Error: {initResult.Message}");
            return;
        }

        Console.WriteLine($"App: {initResult.Data.Name} v{initResult.Data.Version}");

        // Solicitar licencia al usuario
        Console.Write("Ingresa tu licencia: ");
        var license = Console.ReadLine();

        // Login con licencia
        var loginResult = await KeyNexus.LoginWithLicenseAsync(new LicenseLoginRequest
        {
            LicenseKey = license,
            Hwid = HardwareInfo.GetHwid()
        });

        if (!loginResult.Success)
        {
            Console.WriteLine($"Login fallido: {loginResult.Message}");
            return;
        }

        var user = loginResult.Data.User;
        Console.WriteLine($"¡Bienvenido, {user.Username}!");
        Console.WriteLine($"Suscripción expira: {user.SubscriptionExpiry}");

        // Tu aplicación continúa aquí...
        await RunMainApplication();
    }

    static async Task RunMainApplication()
    {
        // Verificar periódicamente que la sesión sigue válida
        while (true)
        {
            var checkResult = await KeyNexus.CheckSessionAsync();
            if (!checkResult.Success)
            {
                Console.WriteLine("Sesión expirada. Cerrando...");
                break;
            }

            // Tu lógica de aplicación aquí
            await Task.Delay(60000); // Check cada minuto
        }
    }
}

Py
Python

Instalaciónbash
pip install KeyNexus-python
Ejemplo de integraciónpython
from KeyNexus import KeyNexusClient, HardwareInfo

# Inicializar cliente
client = KeyNexusClient(
    application_id="app_xxxxxxxxxxxx",
    SECRET_KEY="sk_xxxxxxxxxxxxxxxxxxxx",
    version="1.0.0"
)

# Inicializar aplicación
init_result = client.initialize()
if not init_result.success:
    print(f"Error: {init_result.message}")
    exit(1)

print(f"App: {init_result.data.name} v{init_result.data.version}")

# Login con licencia
license_key = input("Ingresa tu licencia: ")
hwid = HardwareInfo.get_hwid()

login_result = client.login_with_license(license_key, hwid)

if not login_result.success:
    print(f"Error: {login_result.message}")
    exit(1)

user = login_result.data.user
print(f"¡Bienvenido, {user.username}!")
print(f"Expira: {user.subscription_expiry}")

# Tu aplicación continúa aquí...
def main_loop():
    while True:
        check = client.check_session()
        if not check.success:
            print("Sesión expirada")
            break
        
        # Tu lógica aquí
        import time
        time.sleep(60)

main_loop()

C++
C++

Instalación con vcpkgbash
vcpkg install KeyNexus-cpp
Ejemplo de integracióncpp
#include <KeyNexus/KeyNexus.hpp>
#include <iostream>

int main() {
    // Inicializar cliente
    KeyNexus::Client client(
        "app_xxxxxxxxxxxx",      // Application ID
        "sk_xxxxxxxxxxxxxxxxxxxx", // API Key
        "1.0.0"                   // Version
    );

    // Inicializar aplicación
    auto initResult = client.initialize();
    if (!initResult.success) {
        std::cerr << "Error: " << initResult.message << std::endl;
        return 1;
    }

    std::cout << "App: " << initResult.data.name << std::endl;

    // Solicitar licencia
    std::string license;
    std::cout << "Licencia: ";
    std::cin >> license;

    // Login
    auto hwid = KeyNexus::Hardware::getHwid();
    auto loginResult = client.loginWithLicense(license, hwid);

    if (!loginResult.success) {
        std::cerr << "Error: " << loginResult.message << std::endl;
        return 1;
    }

    std::cout << "Bienvenido, " << loginResult.data.user.username << "!" << std::endl;

    // Tu aplicación continúa aquí...
    return 0;
}
Rs

Rust

[dependencies]
KeyNexus = "2.0"
Go

Go

go get github.com/KeyNexus/KeyNexus-go

Ejemplos JavaScript & React

Implementaciones completas para aplicaciones web con JavaScript vanilla, React, Next.js y más.

SDK Oficial de JavaScript/TypeScript

Recomendado: Usa el SDK oficial de KeyNexus para JavaScript/TypeScript. Incluye validación automática de HWID, manejo de errores y soporte completo para TypeScript.

Instalación

npm install keynexus

Uso Básico

app.jsjavascript
import KeyNexusClient from 'keynexus';

// Inicializar cliente
const client = new KeyNexusClient({
  appId: 'app_xxxxxxxxxxxx',
  secretKey: 'sk_xxxxxxxxxxxxxxxxxxxx'
});

// El HWID se genera automáticamente
console.log('HWID:', client.getHWID());

// Validar licencia
try {
  const result = await client.validateLicense('XXXXX-XXXXX-XXXXX-XXXXX');
  
  if (result.success) {
    console.log('✅ Licencia válida!');
    console.log('Tipo:', result.license?.type);
    console.log('Expira:', result.license?.expiresAt);
    console.log('Días restantes:', result.license?.daysLeft);
  }
} catch (error) {
  if (error.name === 'InvalidLicenseError') {
    console.log('❌ Licencia inválida');
  } else if (error.name === 'HWIDMismatchError') {
    console.log('❌ HWID no coincide');
  } else if (error.name === 'ExpiredLicenseError') {
    console.log('❌ Licencia expirada');
  }
}

// Login con usuario/contraseña
const loginResult = await client.loginWithPassword('usuario', 'contraseña');
if (loginResult.success) {
  console.log('Bienvenido', loginResult.user?.username);
}

// Obtener información del usuario
const userInfo = await client.getUserInfo();
console.log('Email:', userInfo.user?.email);

JavaScript Vanilla (Sin SDK)

1. Validar Licencia (Cliente)

validateLicense.jsjavascript
async function validateLicense(appId, secretKey, licenseKey, hwid) {
  try {
    const response = await fetch('https://keynexus.es/api/client', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        action: 'license',
        appId: appId,
        secretKey: secretKey,
        key: licenseKey,
        hwid: hwid
      })
    });

    const data = await response.json();
    
    if (data.success) {
      console.log('✓ Licencia válida');
      console.log('Usuario:', data.user.username);
      console.log('Expira:', data.user.subscriptionExpiry);
      return data;
    } else {
      console.error('✗ Licencia inválida:', data.message);
      return null;
    }
  } catch (error) {
    console.error('Error al validar licencia:', error);
    return null;
  }
}

// Uso
const result = await validateLicense(
  'app_xxxxxxxxxxxx',
  'sk_xxxxxxxxxxxxxxxxxxxx', 
  'XXXXX-XXXXX-XXXXX-XXXXX',
  'hardware-id-12345'
);

2. Login Usuario/Contraseña

userLogin.jsjavascript
async function loginUser(appId, secretKey, username, password, hwid) {
  try {
    const response = await fetch('https://keynexus.es/api/client', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        action: 'login',
        appId: appId,
        secretKey: secretKey,
        username: username,
        password: password,
        hwid: hwid
      })
    });

    const data = await response.json();
    
    if (data.success) {
      // Guardar token en localStorage
      localStorage.setItem('client_token', data.token);
      console.log('✓ Login exitoso');
      return data.user;
    } else {
      console.error('✗ Login fallido:', data.message);
      return null;
    }
  } catch (error) {
    console.error('Error en login:', error);
    return null;
  }
}

// Uso
const user = await loginUser(
  'app_xxxxxxxxxxxx',
  'sk_xxxxxxxxxxxxxxxxxxxx',
  'usuario123',
  'miPassword',
  'hardware-id-12345'
);

3. Obtener Usuario Actual

getCurrentUser.jsjavascript
async function getCurrentUser() {
  try {
    const token = localStorage.getItem('client_token');
    
    if (!token) {
      console.error('No hay sesión activa');
      return null;
    }

    const response = await fetch('https://keynexus.es/api/client/me', {
      headers: {
        'Cookie': `client_token=${token}`
      }
    });

    const data = await response.json();
    
    if (data.authenticated) {
      console.log('Usuario:', data.user.username);
      return data.user;
    } else {
      console.error('Sesión expirada');
      localStorage.removeItem('client_token');
      return null;
    }
  } catch (error) {
    console.error('Error al obtener usuario:', error);
    return null;
  }
}

React + Hooks

Custom Hook para Autenticación

useAuth.jsjavascript
import { useState, useEffect } from 'react';

export function useAuth() {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  // Configuración
  const APP_ID = 'app_xxxxxxxxxxxx';
  const SECRET_KEY = 'sk_xxxxxxxxxxxxxxxxxxxx';
  const API_URL = 'https://keynexus.es/api';

  // Verificar sesión al cargar
  useEffect(() => {
    checkSession();
  }, []);

  async function checkSession() {
    const token = localStorage.getItem('client_token');
    if (!token) {
      setLoading(false);
      return;
    }

    try {
      const res = await fetch(`${API_URL}/client/me`, {
        credentials: 'include'
      });
      const data = await res.json();

      if (data.authenticated) {
        setUser(data.user);
      } else {
        localStorage.removeItem('client_token');
      }
    } catch (err) {
      setError(err.message);
    } finally {
      setLoading(false);
    }
  }

  async function loginWithLicense(licenseKey, hwid) {
    setLoading(true);
    setError(null);

    try {
      const res = await fetch(`${API_URL}/client`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          action: 'license',
          appId: APP_ID,
          secretKey: SECRET_KEY,
          key: licenseKey,
          hwid: hwid
        })
      });

      const data = await res.json();

      if (data.success) {
        setUser(data.user);
        localStorage.setItem('client_token', data.token);
        return { success: true };
      } else {
        setError(data.message);
        return { success: false, message: data.message };
      }
    } catch (err) {
      setError(err.message);
      return { success: false, message: err.message };
    } finally {
      setLoading(false);
    }
  }

  async function loginWithPassword(username, password, hwid) {
    setLoading(true);
    setError(null);

    try {
      const res = await fetch(`${API_URL}/client`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          action: 'login',
          appId: APP_ID,
          secretKey: SECRET_KEY,
          username: username,
          password: password,
          hwid: hwid
        })
      });

      const data = await res.json();

      if (data.success) {
        setUser(data.user);
        localStorage.setItem('client_token', data.token);
        return { success: true };
      } else {
        setError(data.message);
        return { success: false, message: data.message };
      }
    } catch (err) {
      setError(err.message);
      return { success: false, message: err.message };
    } finally {
      setLoading(false);
    }
  }

  async function logout() {
    try {
      await fetch(`${API_URL}/client/logout`, { 
        method: 'POST',
        credentials: 'include'
      });
    } catch (err) {
      console.error('Error en logout:', err);
    } finally {
      setUser(null);
      localStorage.removeItem('client_token');
    }
  }

  return {
    user,
    loading,
    error,
    loginWithLicense,
    loginWithPassword,
    logout,
    isAuthenticated: !!user
  };
}

Componente de Login

LoginComponent.jsxjavascript
import { useState } from 'react';
import { useAuth } from './useAuth';

export function LoginComponent() {
  const { loginWithLicense, loginWithPassword, loading, error } = useAuth();
  const [mode, setMode] = useState('license'); // 'license' o 'password'
  const [licenseKey, setLicenseKey] = useState('');
  const [username, setUsername] = useState('');
  const [password, setPassword] = useState('');

  // Generar HWID simple (en producción usa algo más robusto)
  const getHWID = () => {
    return `hwid_${navigator.userAgent}_${screen.width}x${screen.height}`;
  };

  const handleLicenseLogin = async (e) => {
    e.preventDefault();
    const result = await loginWithLicense(licenseKey, getHWID());
    
    if (result.success) {
      alert('✓ Licencia activada correctamente');
    } else {
      alert(`✗ Error: ${result.message}`);
    }
  };

  const handlePasswordLogin = async (e) => {
    e.preventDefault();
    const result = await loginWithPassword(username, password, getHWID());
    
    if (result.success) {
      alert('✓ Login exitoso');
    } else {
      alert(`✗ Error: ${result.message}`);
    }
  };

  return (
    <div className="max-w-md mx-auto p-6 bg-white rounded-lg shadow">
      <h2 className="text-2xl font-bold mb-4">Iniciar Sesión</h2>
      
      {/* Selector de modo */}
      <div className="flex gap-2 mb-4">
        <button
          onClick={() => setMode('license')}
          className={`px-4 py-2 rounded ${mode === 'license' ? 'bg-blue-500 text-white' : 'bg-gray-200'}`}
        >
          Con Licencia
        </button>
        <button
          onClick={() => setMode('password')}
          className={`px-4 py-2 rounded ${mode === 'password' ? 'bg-blue-500 text-white' : 'bg-gray-200'}`}
        >
          Usuario/Contraseña
        </button>
      </div>

      {/* Formulario de Licencia */}
      {mode === 'license' && (
        <form onSubmit={handleLicenseLogin} className="space-y-4">
          <div>
            <label className="block text-sm font-medium mb-1">
              Clave de Licencia
            </label>
            <input
              type="text"
              value={licenseKey}
              onChange={(e) => setLicenseKey(e.target.value.toUpperCase())}
              placeholder="XXXXX-XXXXX-XXXXX-XXXXX"
              className="w-full px-3 py-2 border rounded"
              required
            />
          </div>
          <button
            type="submit"
            disabled={loading}
            className="w-full bg-blue-500 text-white py-2 rounded hover:bg-blue-600 disabled:opacity-50"
          >
            {loading ? 'Validando...' : 'Activar Licencia'}
          </button>
        </form>
      )}

      {/* Formulario de Usuario/Contraseña */}
      {mode === 'password' && (
        <form onSubmit={handlePasswordLogin} className="space-y-4">
          <div>
            <label className="block text-sm font-medium mb-1">Usuario</label>
            <input
              type="text"
              value={username}
              onChange={(e) => setUsername(e.target.value)}
              className="w-full px-3 py-2 border rounded"
              required
            />
          </div>
          <div>
            <label className="block text-sm font-medium mb-1">Contraseña</label>
            <input
              type="password"
              value={password}
              onChange={(e) => setPassword(e.target.value)}
              className="w-full px-3 py-2 border rounded"
              required
            />
          </div>
          <button
            type="submit"
            disabled={loading}
            className="w-full bg-blue-500 text-white py-2 rounded hover:bg-blue-600 disabled:opacity-50"
          >
            {loading ? 'Iniciando...' : 'Iniciar Sesión'}
          </button>
        </form>
      )}

      {/* Errores */}
      {error && (
        <div className="mt-4 p-3 bg-red-100 text-red-700 rounded">
          {error}
        </div>
      )}
    </div>
  );
}

Dashboard de Usuario

UserDashboard.jsxjavascript
import { useAuth } from './useAuth';

export function UserDashboard() {
  const { user, logout, loading } = useAuth();

  if (loading) {
    return <div className="text-center p-8">Cargando...</div>;
  }

  if (!user) {
    return <LoginComponent />;
  }

  return (
    <div className="max-w-2xl mx-auto p-6">
      <div className="bg-white rounded-lg shadow p-6">
        <div className="flex justify-between items-center mb-6">
          <h2 className="text-2xl font-bold">Mi Cuenta</h2>
          <button
            onClick={logout}
            className="px-4 py-2 bg-red-500 text-white rounded hover:bg-red-600"
          >
            Cerrar Sesión
          </button>
        </div>

        <div className="space-y-4">
          <div className="border-b pb-3">
            <p className="text-sm text-gray-500">Usuario</p>
            <p className="text-lg font-medium">{user.username}</p>
          </div>

          <div className="border-b pb-3">
            <p className="text-sm text-gray-500">Email</p>
            <p className="text-lg">{user.email || 'No configurado'}</p>
          </div>

          <div className="border-b pb-3">
            <p className="text-sm text-gray-500">Licencia</p>
            <p className="text-lg font-mono">{user.licenseKey || 'Sin licencia'}</p>
          </div>

          <div className="border-b pb-3">
            <p className="text-sm text-gray-500">Suscripción Expira</p>
            <p className="text-lg">
              {user.subscriptionExpiry 
                ? new Date(user.subscriptionExpiry).toLocaleDateString() 
                : 'Nunca'}
            </p>
          </div>

          <div className="border-b pb-3">
            <p className="text-sm text-gray-500">Estado</p>
            <span className={`inline-block px-3 py-1 rounded text-sm font-medium ${
              user.status === 'active' 
                ? 'bg-green-100 text-green-700' 
                : 'bg-red-100 text-red-700'
            }`}>
              {user.status === 'active' ? 'Activo' : user.status}
            </span>
          </div>

          <div className="border-b pb-3">
            <p className="text-sm text-gray-500">Total de Accesos</p>
            <p className="text-lg">{user.totalLogins || 0}</p>
          </div>

          <div>
            <p className="text-sm text-gray-500">Último Acceso</p>
            <p className="text-lg">
              {user.lastLogin 
                ? new Date(user.lastLogin).toLocaleString() 
                : 'Nunca'}
            </p>
          </div>
        </div>
      </div>
    </div>
  );
}

Next.js 14+ (App Router)

Next.js 14 con App Router requiere considerar Server Components vs Client Components. Usa "use client" para componentes con hooks.

Middleware de Autenticación

middleware.jsjavascript
import { NextResponse } from 'next/server';

export function middleware(request) {
  const token = request.cookies.get('client_token')?.value;
  const isAuthPage = request.nextUrl.pathname.startsWith('/login');
  const isProtectedRoute = request.nextUrl.pathname.startsWith('/dashboard');

  // Redirigir a login si no hay token
  if (isProtectedRoute && !token) {
    return NextResponse.redirect(new URL('/login', request.url));
  }

  // Redirigir a dashboard si ya está autenticado
  if (isAuthPage && token) {
    return NextResponse.redirect(new URL('/dashboard', request.url));
  }

  return NextResponse.next();
}

export const config = {
  matcher: ['/dashboard/:path*', '/login']
};

Server Action para Login

app/actions/auth.jsjavascript
'use server';

import { cookies } from 'next/headers';

export async function loginWithLicense(licenseKey, hwid) {
  try {
    const res = await fetch('https://keynexus.es/api/client', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        action: 'license',
        appId: process.env.KEYNEXUS_APP_ID,
        secretKey: process.env.KEYNEXUS_SECRET_KEY,
        key: licenseKey,
        hwid: hwid
      })
    });

    const data = await res.json();

    if (data.success) {
      // Guardar cookie
      cookies().set('client_token', data.token, {
        httpOnly: true,
        secure: process.env.NODE_ENV === 'production',
        sameSite: 'lax',
        maxAge: 60 * 60 * 24 * 7 // 7 días
      });

      return { success: true, user: data.user };
    }

    return { success: false, message: data.message };
  } catch (error) {
    return { success: false, message: error.message };
  }
}

export async function logout() {
  cookies().delete('client_token');
}

Tips & Best Practices

Seguridad: Nunca expongas tu Secret Key en el código del cliente. Para aplicaciones web, usa un backend proxy o Server Actions.
HWID: En navegadores web, genera un ID único basado en fingerprinting (user agent, resolución, timezone, etc). Librerías recomendadas: FingerprintJS.
Cache: Implementa cache local de la información del usuario para reducir llamadas a la API y mejorar la experiencia.

Validar Fecha de Expiración

Cuando valides licencias o usuarios, la API devuelve información sobre la expiración. Aquí te mostramos cómo manejarla correctamente:

JavaScript - Validar Expiración

validateExpiration.jsjavascript
function isLicenseExpired(expirationDate) {
  if (!expirationDate) {
    // Sin fecha de expiración = licencia vitalicia
    return false;
  }
  
  const expiry = new Date(expirationDate);
  const now = new Date();
  
  return now > expiry;
}

function getDaysUntilExpiration(expirationDate) {
  if (!expirationDate) {
    return Infinity; // Licencia vitalicia
  }
  
  const expiry = new Date(expirationDate);
  const now = new Date();
  const diffTime = expiry.getTime() - now.getTime();
  const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
  
  return diffDays;
}

// Ejemplo de uso
async function checkLicenseStatus() {
  const response = await fetch('https://keynexus.es/api/client', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      action: 'license',
      appId: 'app_xxxxxxxxxxxx',
      secretKey: 'sk_xxxxxxxxxxxxxxxxxxxx',
      key: 'XXXXX-XXXXX-XXXXX-XXXXX',
      hwid: 'hardware-id-12345'
    })
  });
  
  const data = await response.json();
  
  if (data.success) {
    const expiresAt = data.license.expiresAt;
    
    if (isLicenseExpired(expiresAt)) {
      console.log('❌ Licencia expirada');
      return false;
    }
    
    const daysLeft = getDaysUntilExpiration(expiresAt);
    
    if (daysLeft === Infinity) {
      console.log('✅ Licencia vitalicia');
    } else if (daysLeft <= 7) {
      console.log(`⚠️  Licencia expira en ${daysLeft} días`);
    } else {
      console.log(`✅ Licencia válida por ${daysLeft} días más`);
    }
    
    return true;
  }
  
  return false;
}

React - Hook para Expiración

useLicenseExpiration.jsjavascript
import { useState, useEffect } from 'react';

export function useLicenseExpiration(expirationDate) {
  const [status, setStatus] = useState({
    isExpired: false,
    daysLeft: 0,
    isLifetime: false,
    showWarning: false
  });
  
  useEffect(() => {
    if (!expirationDate) {
      setStatus({
        isExpired: false,
        daysLeft: Infinity,
        isLifetime: true,
        showWarning: false
      });
      return;
    }
    
    const checkExpiration = () => {
      const expiry = new Date(expirationDate);
      const now = new Date();
      const diffTime = expiry.getTime() - now.getTime();
      const daysLeft = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
      
      setStatus({
        isExpired: daysLeft <= 0,
        daysLeft: Math.max(0, daysLeft),
        isLifetime: false,
        showWarning: daysLeft > 0 && daysLeft <= 7
      });
    };
    
    checkExpiration();
    // Verificar cada hora
    const interval = setInterval(checkExpiration, 1000 * 60 * 60);
    
    return () => clearInterval(interval);
  }, [expirationDate]);
  
  return status;
}

// Uso en componente
function LicenseStatus({ user }) {
  const { isExpired, daysLeft, isLifetime, showWarning } = 
    useLicenseExpiration(user.subscriptionExpiry);
  
  if (isExpired) {
    return (
      <div className="bg-red-100 text-red-700 p-3 rounded">
        ❌ Tu licencia ha expirado
      </div>
    );
  }
  
  if (isLifetime) {
    return (
      <div className="bg-green-100 text-green-700 p-3 rounded">
        ✅ Licencia vitalicia
      </div>
    );
  }
  
  if (showWarning) {
    return (
      <div className="bg-yellow-100 text-yellow-700 p-3 rounded">
        ⚠️  Tu licencia expira en {daysLeft} días
      </div>
    );
  }
  
  return (
    <div className="bg-blue-100 text-blue-700 p-3 rounded">
      ✅ Licencia activa ({daysLeft} días restantes)
    </div>
  );
}

Python - Validar Expiración

validate_expiration.pypython
from datetime import datetime, timezone

def is_license_expired(expiration_date):
    """Verifica si una licencia ha expirado"""
    if not expiration_date:
        # Sin fecha de expiración = licencia vitalicia
        return False
    
    expiry = datetime.fromisoformat(expiration_date.replace('Z', '+00:00'))
    now = datetime.now(timezone.utc)
    
    return now > expiry

def get_days_until_expiration(expiration_date):
    """Calcula días hasta la expiración"""
    if not expiration_date:
        return float('inf')  # Licencia vitalicia
    
    expiry = datetime.fromisoformat(expiration_date.replace('Z', '+00:00'))
    now = datetime.now(timezone.utc)
    diff = expiry - now
    
    return max(0, diff.days)

# Ejemplo de uso
import requests

def validate_license(app_id, secret_key, license_key, hwid):
    response = requests.post('https://keynexus.es/api/client', json={
        'action': 'license',
        'appId': app_id,
        'secretKey': secret_key,
        'key': license_key,
        'hwid': hwid
    })
    
    data = response.json()
    
    if data['success']:
        expires_at = data['license'].get('expiresAt')
        
        if is_license_expired(expires_at):
            print('❌ Licencia expirada')
            return False
        
        days_left = get_days_until_expiration(expires_at)
        
        if days_left == float('inf'):
            print('✅ Licencia vitalicia')
        elif days_left <= 7:
            print(f'⚠️  Licencia expira en {days_left} días')
        else:
            print(f'✅ Licencia válida por {days_left} días más')
        
        return True
    
    return False

¿Necesitas ayuda? Revisa la sección de Solución de Problemas