Capa de Servicios

NoxPanel sigue un patrón de capa de servicios (Service Layer) que encapsula la lógica de negocio separándola de las vistas y los modelos. Cada servicio se encarga de un dominio funcional específico y coordina operaciones entre modelos Django e integraciones externas.

HostingService

Ubicación: hosting/services.py

Servicio principal para la gestión de hosting multi-tenant. Abstrae las operaciones del motor de hosting con lógica de cuotas, validación y aislamiento de tenant.

Constructor

HostingService(tenant: Optional[HostingTenant] = None)

Recibe un tenant opcional. Cuando se proporciona, todas las operaciones se limitan a ese tenant. Si es None, se permite el modo admin.

Métodos de Gestión de Clientes

create_tenant_client(tenant, user) ISPConfigClient

Crea un cliente en el motor de hosting para un par tenant/usuario. Genera credenciales únicas con formato {tenant.slug}_{user.username}, invoca ISPConfigAPIClient.create_client() con las cuotas del tenant, y persiste el mapeo en la base de datos Django.

Métodos de Sitios Web

create_site(tenant, domain, ip_address="*") HostingSite

Crea un sitio web verificando la cuota del tenant. Obtiene o crea el cliente del motor de hosting necesario, llama a la API del motor de hosting usando credenciales admin (create_site_for_client), y registra el sitio en Django con el ID del recurso retornado.

list_sites(tenant) List[HostingSite]

Lista todos los sitios activos del tenant.

get_site(tenant, site_id) Optional[HostingSite]

Obtiene un sitio específico con validación de pertenencia al tenant.

delete_site(tenant, site_id) bool

Elimina un sitio del motor de hosting mediante la API admin y lo marca como is_active=False en Django (soft-delete).

request_ssl_certificate(site) Dict

Solicita un certificado Let’s Encrypt para un sitio. Obtiene los datos actuales del sitio desde el motor de hosting, activa ssl_letsencrypt=y y ejecuta un update completo (el motor de hosting requiere enviar todos los campos).

Métodos de Bases de Datos

create_database(tenant, name, db_user=None, site=None) HostingDatabase

Crea una base de datos MySQL verificando cuotas. Genera usuario y contraseña, llama a ISPConfigAPIClient.create_database() que primero crea el usuario de DB y luego la base de datos. Registra en Django.

list_databases(tenant) List[HostingDatabase]

Lista bases de datos activas del tenant.

Workflow All-in-One: provision_full_site

provision_full_site(tenant, domain, ...) Dict

Workflow completo tipo cPanel que crea todos los recursos de un dominio en un solo paso:

  1. Site: Crea el sitio web en el motor de hosting.

  2. DNS Zone: Crea la zona DNS con registros por defecto (A, CNAME, MX, TXT/SPF, A para mail).

  3. Mail Domain: Configura el dominio de email en el motor de hosting.

  4. FTP Account: Crea cuenta FTP con usuario {slug}_{dominio} y permisos correctos (uid/gid del motor de hosting).

  5. Database: Crea base de datos MySQL con nombre sanitizado.

  6. Traefik Config: Genera configuración de Traefik para SSL automático.

Cada paso es independiente y tolerante a fallos: si uno falla, los demás continúan. El resultado incluye un campo errors con los errores ocurridos.

Parámetros opcionales: create_dns, create_mail, create_ftp, create_db, create_traefik (todos True por defecto).

Sincronización

sync_tenant_from_hosting_engine(tenant) Dict

Sincroniza recursos de un tenant desde el motor de hosting hacia Django. Consulta la API admin para obtener sitios y bases de datos del cliente del motor de hosting asociado al tenant y realiza update_or_create en los modelos Django. Actualiza el campo last_sync del ISPConfigClient.

Métodos Helper Internos

  • _get_admin_client(): Obtiene instancia del cliente del motor de hosting con credenciales admin.

  • _get_tenant_client(ispconfig_client): Obtiene cliente del motor de hosting con credenciales del tenant.

  • _generate_password(length=20): Genera contraseña segura usando secrets.choice().

  • _get_ispconfig_client_for_tenant(tenant): Busca el ISPConfigClient activo del tenant.

  • _create_dns_zone_for_site(): Crea zona DNS local y en el motor de hosting.

  • _create_ispconfig_dns_records(): Crea registros A, MX, TXT en el motor de hosting.

  • _create_default_dns_records(): Crea registros DNS locales en Django.

  • _create_mail_domain_for_site(): Crea dominio de mail en el motor de hosting.

  • _create_default_ftp_account(): Crea cuenta FTP con uid/gid del motor de hosting.

  • _create_default_database(): Crea base de datos con nombre sanitizado.

Cliente del Motor de Hosting (API)

Ubicación: integrations/ispconfig_client.py

Cliente Python para la API JSON del motor de hosting ubicada en /remote/json.php. Todas las operaciones usan autenticación por sesión.

Autenticación y Conexión

__init__(base_url=None, username=None, password=None)

Inicializa el cliente. Si no se proporcionan credenciales, las lee de settings.py o del archivo compartido de contraseñas del motor de hosting.

ping() Dict

Verifica conectividad: realiza login y retorna {"ok": True}.

_login() str

Autentica con el motor de hosting y obtiene un session_id reutilizable. Lanza ISPConfigNotConfigured si las credenciales faltan o son inválidas.

logout() Dict

Cierra la sesión activa del motor de hosting.

_call(method, **params) Dict

Método base para todas las llamadas API. Realiza POST a /remote/json.php?{method} con cuerpo JSON. Retorna {"ok": True, "response": ...} en éxito o {"ok": False, "error": ...} en fallo.

Gestión de Clientes

list_clients() Dict

Lista todos los clientes del motor de hosting con sus datos completos.

get_client(client_id) Dict

Obtiene información detallada de un cliente específico.

create_client(username, password, company_name, contact_name, email, **kwargs) Dict

Crea un cliente con todas las cuotas configurables (web, DB, mail, FTP, DNS, shell). Si el username ya existe, busca el cliente existente.

Gestión de Sitios Web

list_sites() / list_sites_by_client(client_id) Dict

Listar sitios web (todos o por cliente).

create_site(domain, ip_address, **kwargs) Dict

Crea sitio básico (requiere client_id en kwargs).

create_site_for_client(client_id, domain, ip_address, **kwargs) Dict

Versión completa con todos los campos requeridos por el motor de hosting (PHP-FPM, subdomain www, CGI, SSI, suexec, etc.).

delete_site(site_id) Dict

Elimina un sitio web.

Gestión de FTP, Mail, DNS, Databases

Cada recurso tiene métodos CRUD consistentes:

  • FTP: list_ftp_users, create_ftp_user, update_ftp_user, delete_ftp_user.

  • Mail Domains: list_mail_domains, create_mail_domain, delete_mail_domain.

  • Mailboxes: list_mailboxes, create_mailbox, update_mailbox, delete_mailbox.

  • Mail Aliases: list_mail_aliases, create_mail_alias, delete_mail_alias.

  • Databases: create_database (crea usuario + DB), delete_database.

  • DNS Zones: list_dns_zones, create_dns_zone, delete_dns_zone.

  • DNS Records: list_dns_records, create_dns_record, update_dns_record, delete_dns_record. Usa métodos específicos por tipo (dns_a_add, dns_mx_add, etc.).

  • Shell Users: list_shell_users, create_shell_user, delete_shell_user.

  • SSL: request_letsencrypt_certificate(site_id, client_id).

InvoiceService

Ubicación: billing/services.py

Servicio para generación y gestión de facturas.

generate_invoice_for_tenant(tenant, period_start, period_end, ...) Invoice

Genera una factura completa para un tenant:

  1. Crea la factura con número secuencial (INV-{año}-{seq:04d}).

  2. Snapshot de datos fiscales del BillingProfile (inmutabilidad).

  3. Agrega líneas por cada suscripción activa con su precio efectivo.

  4. Agrega líneas por registros de uso no facturados del periodo.

  5. Marca los UsageRecord como invoiced=True.

  6. Recalcula subtotal, IVA (21% default) y total.

Ejecuta todo en una transacción atómica (@transaction.atomic).

mark_invoice_paid(invoice, gateway, gateway_payment_id, amount) Payment

Marca una factura como pagada: crea un registro Payment con estado completed y actualiza el estado de la factura a paid.

_next_invoice_number() str

Genera el siguiente número de factura secuencial por año.

_format_address(profile) str

Formatea la dirección fiscal del perfil de facturación.

UsageTracker

Ubicación: billing/services.py

Servicio para registrar el consumo de recursos facturables.

record_vm_usage(subscription, vm_name, vm_id, hours=1) UsageRecord

Registra uso horario de una máquina virtual. Calcula el total como hours × effective_price de la suscripción.

record_hosting_usage(subscription, site_name, site_id, days=1) UsageRecord

Registra uso diario de hosting. Calcula el precio diario como effective_price / 30 y el total como days × daily_price.

Estos métodos son invocados por las tareas periódicas de Celery:

  • billing.track_vm_usage_hourly: Cada hora.

  • billing.track_hosting_usage_daily: Cada 24 horas.

TicketService

Ubicación: ticketing/services.py

Servicio principal del sistema de helpdesk.

create_ticket(subject, body, requester, ...) Ticket

Crea un ticket nuevo con los siguientes pasos:

  1. Instancia el Ticket con todos los datos proporcionados.

  2. Busca y aplica la política SLA correspondiente a la prioridad.

  3. Calcula las fechas límite: sla_first_response_due y sla_resolution_due.

  4. Guarda el ticket y crea el primer TicketMessage.

add_reply(ticket, body, author, is_agent, message_type) TicketMessage

Añade una respuesta al ticket:

  • Si es de agente y no hay primera respuesta registrada, marca first_responded_at.

  • Si es de agente, cambia el estado a pending (esperando cliente).

  • Si es del cliente y el estado era pending, cambia a open.

assign_ticket(ticket, agent)

Asigna un ticket a un agente. Si el ticket estaba en estado new, lo cambia a open.

resolve_ticket(ticket)

Marca el ticket como resolved y registra resolved_at.

close_ticket(ticket)

Cierra el ticket definitivamente y registra closed_at.

EmailSender

Ubicación: ticketing/services.py

Servicio para enviar respuestas de tickets por email.

send_reply(ticket, message, mail_account=None)

Envía una respuesta de ticket al email del solicitante:

  1. Obtiene la cuenta de email activa (MailAccount).

  2. Construye el email con headers In-Reply-To y References para mantener el threading de email.

  3. Según el protocolo de la cuenta:

    • IMAP/POP3: Usa smtplib directamente con STARTTLS y credenciales descifradas con decrypt_value().

    • OAuth2 Microsoft: Delega a MicrosoftOAuth.send_email().

    • OAuth2 Google: Delega a GoogleOAuth.send_email().

Soporta cuerpo en texto plano y HTML (MIMEMultipart('alternative')).

Tareas Periódicas Celery

Las siguientes tareas se ejecutan automáticamente según el schedule definido en settings.py (CELERY_BEAT_SCHEDULE):

Tarea

Frecuencia

Descripción

billing.track_vm_usage_hourly

Cada hora

Registra uso horario de VMs activas usando UsageTracker.

billing.track_hosting_usage_daily

Cada 24h

Registra uso diario de sitios de hosting activos.

billing.generate_monthly_invoices

Diaria

Verifica si es fin de mes y genera facturas para todos los tenants con suscripciones activas.

billing.check_overdue_invoices

Cada hora

Marca facturas vencidas como overdue según su due_date.

ticketing.fetch_emails

Cada 5 min

Consulta las cuentas de email activas vía IMAP/OAuth2 y crea tickets automáticamente con los emails recibidos.

ticketing.check_sla_breaches

Cada 15 min

Verifica tickets abiertos contra sus plazos SLA y genera alertas.

ticketing.auto_close_resolved

Diaria

Cierra automáticamente tickets en estado resolved que lleven un periodo configurable sin actividad.

Mixins Reutilizables

Ubicación: hosting/mixins.py

Mixins que eliminan duplicación de código en ViewSets y vistas:

is_admin_user(user) bool

Centraliza la verificación de permisos admin (superuser o rol admin).

get_tenant_or_403(request) HostingTenant

Obtiene el tenant activo de la petición o lanza PermissionDenied.

TenantQuerySetMixin

Mixin para ViewSets que filtra querysets por tenant automáticamente. Los admins ven todos los registros; los usuarios regulares solo los de su tenant.

TenantCreateMixin

Auto-asigna el tenant del usuario en operaciones perform_create().

SoftDeleteMixin

Implementa borrado suave (is_active=False) con validación de pertenencia al tenant y logging.