> For the complete documentation index, see [llms.txt](https://docs.plenit.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.plenit.com/referencia/cliente-sdk/cliente-sdk-python.md).

# Cliente SDK Python

El apartado actual detalla el cliente SDK Python de Jotelulu para el acceso simplificado al API Pública de Jotelulu.

### 📖 Documentación de API

#### Endpoints Principales

| Servicio            | Endpoint                                              | Descripción                     |
| ------------------- | ----------------------------------------------------- | ------------------------------- |
| **Core**            | `/core/v1/organizations`                              | Gestión de organizaciones       |
| **Core**            | `/core/v1/organizations/{id}/users`                   | Gestión de usuarios             |
| **Servers**         | `/servers/v1/subscriptions/{id}/instances`            | Instancias de servidor          |
| **Servers**         | `/servers/v1/subscriptions/{id}/networks`             | Gestión de redes                |
| **Remote Desktops** | `/remote-desktops/v1/subscriptions/{id}/instances`    | Instancias de escritorio remoto |
| **Remote Desktops** | `/remote-desktops/v1/subscriptions/{id}/applications` | Aplicaciones                    |

#### Modelos Principales

* **Organization**: Representa una organización
* **User**: Usuario del sistema
* **Subscription**: Suscripción a servicios
* **Instance**: Instancia de servidor o escritorio remoto
* **Application**: Aplicación instalable
* **NetworkInterface**: Interfaz de red
* **FirewallInboundRule**: Regla de firewall

#### 🚀 Instalación en Entorno Virtual (Recomendado)

```bash
# Crear entorno virtual
python -m venv jotelulu-env

# Activar entorno virtual
# En Windows:
jotelulu-env\Scripts\activate
# En Linux/macOS:
source jotelulu-env/bin/activate

# Instalar el SDK
pip install generated-client-python
```

### 🔐 Autenticación con JWT

#### Configuración Básica

```python
import openapi_client
from openapi_client.configuration import Configuration
from openapi_client.api_client import ApiClient

# Configurar el cliente con JWT
configuration = Configuration(
    host="https://connect-eu1.jotelulu.com",  # Producción
    access_token="your_jwt_token_here"
)

# Crear cliente API
api_client = ApiClient(configuration)
```

#### Configuración de Entornos

```python
# Entornos disponibles
ENVIRONMENTS = {
    'production': 'https://connect-eu1.jotelulu.com'
}

# Configuración por entorno
def create_jotelulu_client(environment='production', token=None):
    configuration = Configuration(
        host=ENVIRONMENTS[environment],
        access_token=token
    )
    return ApiClient(configuration)
```

#### Manejo Seguro de Tokens

```python
import os
from openapi_client import Configuration, ApiClient

class JoteluluClient:
    def __init__(self, environment='production'):
        # Obtener token desde variables de entorno
        token = os.getenv('JOTELULU_JWT_TOKEN')
        if not token:
            raise ValueError("JOTELULU_JWT_TOKEN environment variable is required")
        
        self.configuration = Configuration(
            host=ENVIRONMENTS.get(environment, ENVIRONMENTS['production']),
            access_token=token
        )
        self.api_client = ApiClient(self.configuration)
    
    def get_organizations_api(self):
        from openapi_client.api.organizations_api import OrganizationsApi
        return OrganizationsApi(self.api_client)
    
    def get_instances_api(self):
        from openapi_client.api.instances_api import InstancesApi
        return InstancesApi(self.api_client)
```

### 🐍 Integración con Django

#### 1. Instalación en Proyecto Django

```bash
# Crear nuevo proyecto Django
django-admin startproject mi_proyecto_jotelulu
cd mi_proyecto_jotelulu

# Crear aplicación
python manage.py startapp jotelulu_integration

# Instalar dependencias
pip install django
pip install generated-client-python
```

#### 2. Configuración en settings.py

```python
# settings.py
import os

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'jotelulu_integration',  # Tu aplicación
]

# Configuración Jotelulu
JOTELULU_SETTINGS = {
    'ENVIRONMENT': os.getenv('JOTELULU_ENV', 'production'),
    'JWT_TOKEN': os.getenv('JOTELULU_JWT_TOKEN'),
    'API_ENDPOINTS': {
        'production': 'https://connect-eu1.jotelulu.com'
    }
}
```

#### 3. Servicio Django para Jotelulu

```python
# jotelulu_integration/services.py
from django.conf import settings
import openapi_client
from openapi_client.api.organizations_api import OrganizationsApi
from openapi_client.api.instances_api import InstancesApi
from openapi_client.api.applications_api import ApplicationsApi
from openapi_client.exceptions import ApiException

class JoteluluService:
    def __init__(self):
        jotelulu_config = settings.JOTELULU_SETTINGS
        environment = jotelulu_config['ENVIRONMENT']
        
        self.configuration = openapi_client.Configuration(
            host=jotelulu_config['API_ENDPOINTS'][environment],
            access_token=jotelulu_config['JWT_TOKEN']
        )
        self.api_client = openapi_client.ApiClient(self.configuration)
    
    def get_user_organizations(self):
        """Obtener organizaciones del usuario actual"""
        try:
            api_instance = OrganizationsApi(self.api_client)
            response = api_instance.list_user_organizations()
            return response.data
        except ApiException as e:
            print(f"Error al obtener organizaciones: {e}")
            return []
    
    def get_organization_users(self, organization_id):
        """Obtener usuarios de una organización"""
        try:
            api_instance = OrganizationsApi(self.api_client)
            response = api_instance.list_organization_users(organization_id)
            return response.data
        except ApiException as e:
            print(f"Error al obtener usuarios: {e}")
            return []
    
    def get_remote_desktop_instances(self, subscription_id):
        """Obtener instancias de escritorio remoto"""
        try:
            api_instance = InstancesApi(self.api_client)
            response = api_instance.list_remote_desktop_subscription_instances(subscription_id)
            return response.data
        except ApiException as e:
            print(f"Error al obtener instancias: {e}")
            return []
```

#### 4. Views Django

```python
# jotelulu_integration/views.py
from django.shortcuts import render
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
from .services import JoteluluService

def dashboard(request):
    """Vista principal del dashboard"""
    jotelulu = JoteluluService()
    organizations = jotelulu.get_user_organizations()
    
    context = {
        'organizations': organizations,
        'user': request.user
    }
    return render(request, 'jotelulu_integration/dashboard.html', context)

def organization_detail(request, organization_id):
    """Detalle de una organización"""
    jotelulu = JoteluluService()
    users = jotelulu.get_organization_users(organization_id)
    
    return JsonResponse({
        'organization_id': organization_id,
        'users': [{'id': user.id, 'name': user.name, 'email': user.email} for user in users]
    })

@csrf_exempt
def create_organization_user(request, organization_id):
    """Crear usuario en organización"""
    if request.method == 'POST':
        import json
        from openapi_client.model.create_organization_user_request import CreateOrganizationUserRequest
        from openapi_client.model.create_organization_user_request_data import CreateOrganizationUserRequestData
        
        data = json.loads(request.body)
        jotelulu = JoteluluService()
        
        try:
            api_instance = jotelulu.get_organizations_api()
            user_request = CreateOrganizationUserRequest(
                data=CreateOrganizationUserRequestData(
                    email=data['email'],
                    name=data['name'],
                    last_name=data['lastName'],
                    password=data.get('password')
                )
            )
            
            response = api_instance.create_organization_user(organization_id, user_request)
            return JsonResponse({'success': True, 'user_id': response.data.user_id})
            
        except Exception as e:
            return JsonResponse({'success': False, 'error': str(e)})
    
    return JsonResponse({'error': 'Method not allowed'}, status=405)
```

#### 5. URLs Django

```python
# jotelulu_integration/urls.py
from django.urls import path
from . import views

app_name = 'jotelulu_integration'

urlpatterns = [
    path('', views.dashboard, name='dashboard'),
    path('organization/<str:organization_id>/', views.organization_detail, name='organization_detail'),
    path('organization/<str:organization_id>/users/', views.create_organization_user, name='create_user'),
]

# urls.py principal del proyecto
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('jotelulu/', include('jotelulu_integration.urls')),
]
```

### 📚 Ejemplos de Uso

#### Gestión de Organizaciones

```python
from openapi_client.api.organizations_api import OrganizationsApi
from openapi_client.model.create_organization_user_request import CreateOrganizationUserRequest

# Inicializar cliente
client = JoteluluClient()
org_api = client.get_organizations_api()

# Listar organizaciones del usuario
organizations = org_api.list_user_organizations()
print(f"Organizaciones encontradas: {len(organizations.data)}")

# Listar usuarios de una organización
org_id = "ORG-123"
users = org_api.list_organization_users(org_id)
for user in users.data:
    print(f"Usuario: {user.name} ({user.email})")

# Crear nuevo usuario
new_user_request = CreateOrganizationUserRequest(
    data={
        'email': 'nuevo@ejemplo.com',
        'name': 'Nuevo',
        'lastName': 'Usuario',
        'password': 'password123',
        'twoFactorAuthRequired': True
    }
)
result = org_api.create_organization_user(org_id, new_user_request)
print(f"Usuario creado con ID: {result.data.user_id}")
```

#### Gestión de Instancias y Servidores

```python
from openapi_client.api.instances_api import InstancesApi
from openapi_client.api.instance_api import InstanceApi

# APIs de instancias
instances_api = InstancesApi(api_client)
instance_api = InstanceApi(api_client)

# Listar instancias de escritorio remoto
subscription_id = "SUB-456"
rd_instances = instances_api.list_remote_desktop_subscription_instances(subscription_id)

for instance in rd_instances.data:
    print(f"Instancia: {instance.name} - Estado: {instance.state}")
    
    # Obtener progreso de despliegue
    if instance.state == 'creating':
        progress = instance_api.get_instance_progress(subscription_id, instance.id)
        print(f"Progreso: {progress.data.progress}")

# Listar instancias de servidor
server_instances = instance_api.list_server_subscription_instances(subscription_id)
print(f"Instancias de servidor: {len(server_instances.data)}")
```

#### Gestión de Aplicaciones

```python
from openapi_client.api.applications_api import ApplicationsApi
from openapi_client.model.create_instance_application_request import CreateInstanceApplicationRequest

apps_api = ApplicationsApi(api_client)

# Listar aplicaciones disponibles
available_apps = apps_api.list_applications()
print("Aplicaciones disponibles:")
for app in available_apps.data:
    print(f"- {app.name} (ID: {app.id})")

# Listar aplicaciones de una suscripción
subscription_apps = apps_api.list_subscription_applications(subscription_id)
print(f"Aplicaciones instaladas: {len(subscription_apps.data)}")

# Instalar nueva aplicación
install_request = CreateInstanceApplicationRequest(
    data={
        'instanceId': 'INST-789',
        'applicationId': 'APP-123'
    }
)
result = apps_api.create_instance_application(subscription_id, install_request)
print(f"Aplicación instalada: {result.data[0].id}")
```

#### Gestión de Redes y Firewall

```python
from openapi_client.api.networks_api import NetworksApi
from openapi_client.api.firewall_api import FirewallApi
from openapi_client.model.create_isolated_network_request import CreateIsolatedNetworkRequest
from openapi_client.model.create_firewall_inbound_rule_request import CreateFirewallInboundRuleRequest

networks_api = NetworksApi(api_client)
firewall_api = FirewallApi(api_client)

# Crear red aislada
network_request = CreateIsolatedNetworkRequest(
    data={
        'name': 'Mi Red Privada',
        'network': '192.168.100.0',
        'netmask': '255.255.255.0',
        'gateway': '192.168.100.1'
    }
)
network = networks_api.create_isolated_network(subscription_id, network_request)
print(f"Red creada: {network.data.name}")

# Crear regla de firewall
firewall_rule = CreateFirewallInboundRuleRequest(
    data={
        'protocol': 'TCP',
        'publicNetworkId': 'NET-456',
        'privateNicAddressIpId': 'NIC-789',
        'publicPort': 80,
        'privatePort': 8080,
        'origin': '0.0.0.0/0'
    }
)
rule = firewall_api.create_firewall_inbound_rule(
    subscription_id, 
    network.data.main_network_id, 
    firewall_rule
)
print(f"Regla de firewall creada: {rule.data.id}")
```

### 🛠️ Manejo de Errores

```python
from openapi_client.exceptions import ApiException
import logging

# Configurar logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

def safe_api_call(api_function, *args, **kwargs):
    """Wrapper para llamadas seguras a la API"""
    try:
        return api_function(*args, **kwargs)
    except ApiException as e:
        logger.error(f"Error de API: {e.status} - {e.reason}")
        logger.error(f"Cuerpo de respuesta: {e.body}")
        
        # Manejo específico por código de error
        if e.status == 401:
            logger.error("Token JWT inválido o expirado")
        elif e.status == 403:
            logger.error("Permisos insuficientes")
        elif e.status == 404:
            logger.error("Recurso no encontrado")
        elif e.status >= 500:
            logger.error("Error interno del servidor")
        
        return None
    except Exception as e:
        logger.error(f"Error inesperado: {str(e)}")
        return None

# Ejemplo de uso
organizations = safe_api_call(org_api.list_user_organizations)
if organizations:
    print(f"Organizaciones obtenidas: {len(organizations.data)}")
else:
    print("No se pudieron obtener las organizaciones")
```

### 🔧 Configuración Avanzada

#### Cliente Personalizado con Retry

```python
import time
from functools import wraps

def retry_on_failure(max_retries=3, delay=1):
    """Decorador para reintentar llamadas fallidas"""
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            for attempt in range(max_retries):
                try:
                    return func(*args, **kwargs)
                except ApiException as e:
                    if e.status >= 500 and attempt < max_retries - 1:
                        time.sleep(delay * (2 ** attempt))  # Backoff exponencial
                        continue
                    raise
            return None
        return wrapper
    return decorator

class RobustJoteluluClient(JoteluluClient):
    @retry_on_failure(max_retries=3)
    def get_organizations_with_retry(self):
        api = self.get_organizations_api()
        return api.list_user_organizations()
```

#### Configuración de Timeout

```python
from openapi_client.configuration import Configuration
from openapi_client.api_client import ApiClient

# Configuración con timeouts personalizados
configuration = Configuration(
    host="https://connect-eu1.jotelulu.com",
    access_token="your_token"
)

# Configurar timeouts
api_client = ApiClient(configuration)
api_client.rest_client.pool_manager.connection_pool_kw['timeout'] = 30
```

***


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.plenit.com/referencia/cliente-sdk/cliente-sdk-python.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
