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"
- El código ya fue canjeado en otro dispositivo → generar uno nuevo desde el panel admin.
- 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
- Verificar que el dispositivo no fue revocado en el panel admin.
- Verificar que el
ad_client_iddel 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 |