Untitled static subpage: Thursday, 25 June 2026, 19:44

Date: 2026-06-25 00:05 Title: Control de Acceso por Dispositivo — Manual Técnico Type: page Description: Arquitectura, base de datos, endpoints API y configuración nginx para el sistema de control de dispositivos autorizados

Manual Técnico — Control de Acceso por Dispositivo

Sistema: KontrolesERP
Versión: 1.0
Fecha: Junio 2026
Aplica a: PWA Rutas (RTA), PWA Gerencia, ERP (KontrolesERP)


1. Resumen

El sistema de control de dispositivos restringe el acceso a las aplicaciones solo a dispositivos previamente autorizados por un administrador. Utiliza un mecanismo de código de activación de un solo uso que genera un token de dispositivo permanente. Cada aplicación almacena ese token de forma diferente según su naturaleza (localStorage para PWAs, cookie HTTP para el ERP).


2. Arquitectura

┌─────────────────────────────────────────────────────────┐
│                   nginx (NPM)                           │
│                                                         │
│  /rta/   ──► kontrolesrta_pwa:80                        │
│  /gerencia ─► kontrolesapi:80/ia-gerencial.html         │
│  /webui/  ──► [auth_request /_erp_device_check]         │
│                └─► kontrolesapi/api/devices/verify-erp  │
│                    (401) → /erp-gate                    │
│  /api    ──► kontrolesapi:80                            │
└─────────────────────────────────────────────────────────┘
         │                        │
         ▼                        ▼
  erp_app:8888             kontrolesapi:80
  (KontrolesERP)              (PHP/Apache)
         │                        │
         └──────────┬─────────────┘
                    ▼
           kontroleserp_db:5432
           (PostgreSQL — adempiere.api_device)

Contenedores involucrados

Contenedor Rol
nginx_proxy Nginx Proxy Manager — puerta de entrada SSL
kontrolesapi Backend PHP — gestión de dispositivos, activación
kontrolesrta_pwa Frontend Nginx — sirve la PWA Rutas
erp_app KontrolesERP (Tomcat) — el ERP
kontroleserp_db PostgreSQL — almacena tabla api_device

3. Base de Datos

Tabla adempiere.api_device

CREATE TABLE adempiere.api_device (
    device_id       UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    ad_client_id    INT          NOT NULL,           -- empresa propietaria
    device_name     VARCHAR(100) NOT NULL,           -- nombre descriptivo
    activation_code CHAR(8)      NOT NULL,           -- código de un solo uso
    code_used       CHAR(1)      NOT NULL DEFAULT 'N', -- 'Y' una vez canjeado
    device_token    UUID UNIQUE,                     -- token generado al activar
    revoked         CHAR(1)      NOT NULL DEFAULT 'N', -- 'Y' = acceso revocado
    created         TIMESTAMP    NOT NULL DEFAULT now(),
    last_seen       TIMESTAMP,                       -- última verificación
    notes           TEXT
);

Estados posibles de un dispositivo:

code_used revoked Estado Descripción
N N pending Código generado, no activado aún
Y N active Dispositivo autorizado y activo
Y Y revoked Acceso revocado por el administrador

Insertar código bootstrap (primer acceso)

INSERT INTO adempiere.api_device (ad_client_id, device_name, activation_code, notes)
VALUES (1000000, 'Admin Bootstrap', 'XXXXXXXX', 'Primer acceso');

4. API Endpoints

Base URL: https://rocarsadecv.kontroles.com/api

4.1 Activar dispositivo (público)

POST /api/devices/activate
Content-Type: application/json

{ "code": "XXXXXXXX" }

Respuesta exitosa (201):

{ "device_token": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "device_name": "Nombre del dispositivo" }

Respuesta fallida (404):

{ "error": "Código inválido o ya utilizado" }

El código queda marcado code_used='Y' y no puede reusarse. Si se requiere otro acceso, el administrador debe generar un nuevo código.


4.2 Verificar cookie ERP (público, solo uso interno nginx)

GET /api/devices/verify-erp
Cookie: erp_device_token=<uuid>

Retorna HTTP 200 si el token es válido y no revocado.
Retorna HTTP 401 si el token es inválido, está revocado o no existe.
No retorna cuerpo — solo el código de estado HTTP.


4.3 Listar dispositivos (requiere JWT)

GET /api/devices
Authorization: Bearer <jwt>
X-App-Source: rta
X-Device-Token: <device_token>

Respuesta (200):

{
  "devices": [
    {
      "device_id": "uuid",
      "device_name": "[ERP] PC Contabilidad",
      "activation_code": "ABCD1234",
      "status": "active",
      "last_seen": "24/06/2026 10:30",
      "created": "20/06/2026 09:00"
    }
  ]
}

4.4 Crear código de activación (requiere JWT)

POST /api/devices
Authorization: Bearer <jwt>
X-App-Source: rta
X-Device-Token: <device_token>
Content-Type: application/json

{ "device_name": "[ERP] PC Vendedor Juan", "notes": "Opcional" }

Respuesta (201):

{ "activation_code": "XK7TM3NP", "device_name": "[ERP] PC Vendedor Juan" }

El código usa el alfabeto ABCDEFGHJKLMNPQRSTUVWXYZ23456789 (sin caracteres ambiguos: 0, O, 1, I).


4.5 Revocar dispositivo (requiere JWT)

DELETE /api/devices/<device_id>
Authorization: Bearer <jwt>
X-App-Source: rta
X-Device-Token: <device_token>

Respuesta (200):

{ "message": "Dispositivo revocado" }

5. Cabeceras HTTP requeridas por PWA

Todas las peticiones de las PWAs deben incluir:

Cabecera PWA Rutas PWA Gerencia
X-App-Source rta gerencia
X-Device-Token <rs_device_token> <ia_device_token>

El backend (router.php) valida estas cabeceras para cada petición autenticada cuando el origen es rta o gerencia. Si el token es inválido o revocado, retorna HTTP 403.


6. Almacenamiento del token por aplicación

Aplicación Mecanismo Clave
PWA Rutas (/rta) localStorage rs_device_token
PWA Gerencia (/gerencia) localStorage ia_device_token
ERP (/webui/) Cookie HTTP erp_device_token (path=/, 30 días)

Nota: localStorage es por origen (https://rocarsadecv.kontroles.com). Si el usuario borra datos del navegador, pierde el token y necesita un nuevo código de activación.


7. Flujo de verificación ERP (Nginx auth_request)

Usuario → GET /webui/
    │
    ├─► Nginx: auth_request /_erp_device_check
    │       │
    │       └─► GET kontrolesapi/api/devices/verify-erp
    │               Cookie: erp_device_token=<uuid>
    │               │
    │               ├─ Token válido → HTTP 200 → Nginx permite la petición → erp_app:8888
    │               └─ Sin token / inválido → HTTP 401 → Nginx → 302 /erp-gate?next=/webui/
    │
    └─► GET /erp-gate → kontrolesapi/erp-gate.html
            │
            └─► Usuario ingresa código → POST /api/devices/activate
                    │
                    ├─ Éxito → JS setea cookie erp_device_token → redirect /webui/
                    └─ Error → muestra mensaje de error

8. Configuración Nginx (server_proxy.conf)

Archivo: nginx/server_proxy.conf (en este repositorio)
Ubicación en servidor: /home/administrador/kontroleserp/nginx-proxy-manager/data/nginx/custom/server_proxy.conf

Secciones relevantes para el control de dispositivos:

# Verificación interna (auth_request)
location = /_erp_device_check {
    internal;
    proxy_pass         http://kontrolesapi/api/devices/verify-erp;
    proxy_pass_request_body off;
    proxy_set_header   Content-Length "";
    proxy_set_header   Cookie         $http_cookie;
}

# Página de activación ERP
location = /erp-gate {
    proxy_pass http://kontrolesapi:80/erp-gate.html;
}

# ERP protegido
location /webui/ {
    auth_request    /_erp_device_check;
    error_page 401 = @erp_no_device;
    proxy_pass      http://erp_app:8888;
}

location @erp_no_device {
    return 302 /erp-gate?next=$request_uri;
}

Para aplicar cambios:

docker cp nginx/server_proxy.conf nginx_proxy:/data/nginx/custom/server_proxy.conf
docker exec nginx_proxy nginx -t && docker exec nginx_proxy nginx -s reload

9. Módulo PHP — modules/devices/handler.php

Función Descripción
_dv_activate() Canjea código, genera UUID token, marca code_used='Y'
_dv_verify_erp() Lee cookie, valida en DB, retorna 200/401 (sin cuerpo)
_dv_list() Lista dispositivos del cliente autenticado
_dv_create() Genera código de 8 chars e inserta registro
_dv_revoke() Marca revoked='Y'

10. Solución de Problemas

El usuario dice "Código inválido o ya utilizado"

  1. El código ya fue canjeado en otro dispositivo → generar uno nuevo desde el panel admin.
  2. El usuario borró datos del navegador (PWA) o cookies (ERP) después de haber activado → el token se perdió, generar un nuevo código.

El usuario con token válido recibe 403 en la PWA

  1. Verificar que el dispositivo no fue revocado en el panel admin.
  2. Verificar que el ad_client_id del dispositivo coincide con el del usuario autenticado.
SELECT d.device_name, d.revoked, d.ad_client_id, u.name
FROM adempiere.api_device d
JOIN adempiere.ad_user u ON u.ad_client_id = d.ad_client_id
WHERE d.device_token = '<uuid>';

El ERP no redirige a /erp-gate (sigue mostrando el ERP sin cookie)

El location /webui/ en server_proxy.conf tiene prioridad sobre el location / del NPM. Si esto no ocurre, verificar que el archivo fue copiado correctamente y nginx recargado:

docker exec nginx_proxy nginx -t
docker exec nginx_proxy cat /data/nginx/custom/server_proxy.conf | grep webui

Nginx retorna 500 en auth_request

El contenedor kontrolesapi no está accesible desde nginx_proxy. Verificar que ambos están en la red erp_net:

docker exec nginx_proxy curl -I http://kontrolesapi/api/health

11. Autorización Permanente para Dispositivos Administrador

Para los dispositivos del administrador del sistema conviene bypasear el flujo normal (código de un solo uso + cookie de 30 días) e insertar registros pre-activados directamente en la BD con tokens conocidos. Esto evita tener que re-activar cada mes.

11.1 Insertar dispositivos pre-activados en la BD

-- Ejecutar en el servidor destino (JAASA o INROCAR)
-- ad_client_id: 1000000 para JAASA/INROCAR
INSERT INTO adempiere.api_device (ad_client_id, device_name, activation_code, code_used, device_token, notes)
VALUES
  (1000000, '[ADMIN] MacBook Air M1',    'ADMMBA01', 'Y', gen_random_uuid(), 'Admin - acceso permanente'),
  (1000000, '[ADMIN] MacPro 2013',       'ADMMPC01', 'Y', gen_random_uuid(), 'Admin - acceso permanente'),
  (1000000, '[ADMIN] Celular 1',         'ADMCEL01', 'Y', gen_random_uuid(), 'Admin - acceso permanente'),
  (1000000, '[ADMIN] Celular 2',         'ADMCEL02', 'Y', gen_random_uuid(), 'Admin - acceso permanente')
RETURNING device_name, device_token;

Guardar los UUIDs que retorna el RETURNING — son los tokens que se cargarán en cada navegador.

Para INROCAR (sin SSH directo), usar el endpoint admin:

# 1. Crear código
curl -s -X POST https://host.kontroles.com/api/devices/admin \
  -H "X-Monitor-Key: kontroles_monitor_2024" \
  -H "Content-Type: application/json" \
  -d '{"device_name": "[ADMIN] MacBook Air M1", "notes": "Admin - acceso permanente", "ad_client_id": 1000000}'
# → {"activation_code":"XXXXXXXX","device_name":"..."}

# 2. Activar inmediatamente para obtener el token UUID
curl -s -X POST https://host.kontroles.com/api/devices/activate \
  -H "Content-Type: application/json" \
  -d '{"code": "XXXXXXXX"}'
# → {"device_token":"xxxxxxxx-xxxx-...","device_name":"..."}

11.2 Cargar el token en el navegador (una vez por navegador)

Abrir el sitio correspondiente (erp.jaasadecv.com o host.kontroles.com), luego F12 → Consola.

Chrome bloquea pegar código por primera vez: escribir allow pasting y Enter, luego sí se puede pegar.

Pegar el siguiente script reemplazando TOKEN con el UUID del dispositivo:

const T = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'; // UUID del dispositivo
document.cookie = `erp_device_token=${T}; path=/; max-age=315360000; SameSite=Lax`; // ~10 años
localStorage.setItem('rs_device_token', T);   // PWA Rutas (/rta)
localStorage.setItem('ia_device_token', T);   // PWA Gerencia (/gerencia)
location.reload();

Un mismo token sirve para el ERP, /rta y /gerencia del mismo dominio.

11.3 Dispositivos autorizados — JAASA (erp.jaasadecv.com) — Jun 2026

Dispositivo Token
MacBook Air M1 324f9cfc-02ae-4650-aada-77053b5d5e8a
MacPro 2013 Ubuntu e3f1d35d-4afe-4a14-a6cd-ea70a7caca3b
Celular 1 3a37934b-dc3f-48c8-9cd8-361c62d82aec
Celular 2 85adc233-2d16-40c3-acb3-48a6f7dc86ab

11.4 Dispositivos autorizados — INROCAR (host.kontroles.com) — Jun 2026

Dispositivo Token
MacBook Air M1 6619d752-182c-6141-c768-3c8807b325e8
MacPro 2013 Ubuntu ecfeb289-0acc-35fe-3da5-1135c0aeab37
Celular 1 494a30c1-d98d-8f97-aa90-10b29166caf6
Celular 2 f25a2db5-0ac9-a5c0-8b1f-0db092b7b194

Si se pierde el token (limpieza de navegador), repetir solo el paso 11.2 con el UUID de la tabla. No es necesario generar un código nuevo ni tocar la BD.


12. Variables de entorno relevantes

Variable Contenedor Descripción
DB_SCHEMA kontrolesapi Schema PostgreSQL (valor: adempiere)
JWT_SECRET kontrolesapi Clave para firmar tokens JWT
DB_HOST kontrolesapi Host de la base de datos