Untitled static subpage: Thursday, 25 June 2026, 19:44
Date: 2026-06-25 00:08 Title: KontrolesERP — Guía de Despliegue y Ecosistema Type: page Description: Arquitectura completa, post-inicialización de base de datos, configuración DTE y checklist para nueva instalación o sincronización
KontrolesERP — Guía de Despliegue y Ecosistema
Versión: 2026-06-03
Stack: KontrolesERP · PostgreSQL 14 · Spring Boot 17 · PHP 8.2 · Docker Compose
ÍNDICE
- Ecosistema — Arquitectura completa
- Post-inicialización de BD — Configuración DTE
- Checklist — Nueva instalación o sincronización
1. ECOSISTEMA
1.1 Mapa de contenedores y flujos
INTERNET
│
puerto 80/443
│
┌───────▼────────┐
│ nginx_proxy │ jc21/nginx-proxy-manager
│ (NPM) │ Admin UI: puerto 81
└───────┬────────┘
│ SSL / reverse proxy por subdominio
┌─────────────────┼─────────────────────────────────┐
│ │ │ │
/erp ──▼── /admin─▼─ /files▼─ /pos,/rta,/api
┌──────────────┐ ┌──────────────┐ ┌──────────┐ ┌──────────────────┐
│ erp_app │ │ dte-worker │ │filebrowser│ │ kontrolesapi │
│ (KontrolesERP) │ │ Spring Boot │ │ :80 │ │ PHP+Apache │
│ :8888 │ │ :8080 │ └────┬─────┘ └───────┬──────────┘
└──────┬───────┘ └──────┬───────┘ │ │
│ │ ./dtes │ ┌──────┴──────┐
│ R/W │ R/W (volume) │ │ pos_pwa │
└────────┬────────┘ │ │ :3000 │
│ │ ├─────────────┤
┌──────▼──────┐ │ │ rta_pwa │
│ PostgreSQL │ │ │ :3001 │
│ :5432 │ │ └─────────────┘
│db00_kontroles│ │
└──────────────┘ ┌──────▼──────┐
│ ./dtes/ │
│{clientId}/ │
│ pdfs/{yyyyMM}│
│ json/{yyyyMM}│
└─────────────┘
dte-worker ─── HTTPS ──► MH (api.dtes.mh.gob.sv)
1.2 Servicios — descripción detallada
| Contenedor | Imagen | Puerto interno | Descripción |
|---|---|---|---|
administrador-db-1 |
postgres:14-alpine |
5432 | BD principal. BD: db00_kontroleserp, schema: adempiere |
erp_app |
rocarsadecv/inrocardevsuite:latest |
8888, 4444 | KontrolesERP — Tomcat embebido. |
nginx_proxy |
jc21/nginx-proxy-manager:latest |
80, 443, 81 | Proxy reverso, SSL automático Let's Encrypt, panel UI en :81 |
dte-worker |
rocarsadecv/dte-worker:latest |
8080 | Firmador DTE El Salvador. Polls c_invoice PENDING, firma con PKCS12, envía a MH |
kontrolesapi |
rocarsadecv/kontrolesapi:latest |
8090→80 | API REST PHP. Endpoints: /socios, /ventas, /catalogo, /auth |
kontrolespos_pwa |
nginx:alpine |
3001→80 | PWA Punto de Venta. Proxy a kontrolesapi |
kontrolesrta_pwa |
nginx:alpine |
3000→80 | PWA Rutas / Ventas. Proxy a kontrolesapi |
filebrowser |
filebrowser/filebrowser:latest |
interno→80 | Gestor de archivos DTE (PDF + JSON) por empresa |
kontrolesweb |
rocarsadecv/kontrolesweb:latest |
8099→80 | Sitio corporativo HTMLy CMS |
1.3 Volúmenes y directorios clave
{deploy_dir}/ # directorio base en el servidor
├── KontrolesERPEnv.properties # conexión BD para erp_app
├── docker-compose.yml
├── .env # PG_VOLUME_NAME, JWT_SECRET
├── nginx.conf # nginx_proxy (si aplica)
├── init/ # solo en primer deploy (--with-db)
│ ├── 01_init_db.sql # crea usuario adempiere, extensiones
│ └── KontrolesERP_pg.dmp # backup de la BD seed
├── reports/ # ← montado en erp_app Y dte-worker
│ └── dtes/
│ └── generic/ # JRXMLs genéricos por tipo DTE
│ ├── dte_01.jrxml # FAC
│ ├── dte_03.jrxml # CCF
│ ├── dte_04.jrxml # NR
│ ├── dte_05.jrxml # NC
│ ├── dte_06.jrxml # ND
│ ├── dte_07.jrxml # CR
│ ├── dte_08.jrxml # CL
│ ├── dte_09.jrxml # DCL
│ ├── dte_11.jrxml # FEX
│ ├── dte_14.jrxml # FSE
│ ├── dte_15.jrxml # CD
│ └── dte_generic.jrxml # fallback genérico
├── dtes/ # ← montado en dte-worker Y filebrowser
│ └── {ad_client_id}/ # ej: 1000000
│ ├── pdfs/{yyyyMM}/ # PDFs autorizados
│ └── json/{yyyyMM}/ # JSONs autorizados
├── firmadordtes/
│ └── logs/
├── filebrowser/
│ ├── settings.json # baseURL=/files, root=/srv, port=80
│ └── filebrowser.db # base de datos interna filebrowser
├── kontrolesrta/
│ ├── frontend/ # build de la PWA
│ └── nginx.conf
├── kontrolespos/
│ ├── frontend/
│ └── nginx.conf
└── kontrolesweb/
├── content/
└── config/
1.4 Flujo de vida de un DTE
KontrolesERP (erp_app)
│
│ 1. Usuario completa factura → docstatus='CO'
│ Trigger emh_trg_invoice_pending() → emh_status='PENDING'
│
▼
dte-worker (polling cada 30s)
│
│ 2. SELECT c_invoice WHERE emh_status='PENDING'
│ AND processed='Y' AND docstatus='CO'
│
│ 3. emh_dtesjson(invoiceId) → genera JSON + inserta emh_docs
│
│ 4. FirmadorService → firma PKCS12 + RSA-SHA512
│
│ 5. POST api.dtes.mh.gob.sv/fesv/recepciondte
│
│ 6. Respuesta MH → emh_docsline (codigoMsg, selloRecibido)
│
│ 7. codigoMsg IN ('001','002') AND selloRecibido ≠ ''
│ → emh_status='SENT' (sino → 'ERROR')
│
▼
Email scheduler (polling cada 60s)
│
│ 8. Busca SENT con sello válido y sin email previo
│
│ 9. PdfService.generateAndSave() → PDF con QR MH
│ QR URL: admin.factura.gob.sv/consultaPublica?ambiente=&codGen=&fechaEmi=
│
│ 10. EmailService.send() → PDF + JSON al receptor
│
└─► Archivos en ./dtes/{clientId}/pdfs/{yyyyMM}/ y json/{yyyyMM}/
1.5 Subdominio y URLs por cliente
| URL | Destino | Notas |
|---|---|---|
{empresa}.kontroles.com |
erp_app:8888 | Panel ERP KontrolesERP |
{empresa}.kontroles.com/admin |
dte-worker:8080 | Configuración DTE, credenciales MH |
{empresa}.kontroles.com/files |
filebrowser:80 | PDFs y JSONs autorizados |
{empresa}.kontroles.com (POS) |
kontrolespos_pwa:3001 | PWA Punto de Venta |
{empresa}.kontroles.com (RTA) |
kontrolesrta_pwa:3000 | PWA Rutas/Ventas |
host.kontroles.com |
erp_app / dte-worker | Panel administrador INROCAR |
2. POST-INIT BD — Configuración DTE
2.1 Paso 0 — Ejecutar schema DTE
# En el servidor, dentro del contenedor PostgreSQL:
docker exec -i administrador-db-1 psql -U postgres -d db00_kontroleserp \
< emh_setup_completo.sql
Este script es idempotente (usa IF NOT EXISTS). Crea:
- Columnas adicionales en tablas KontrolesERP (emh_status, emh_tipodoc, etc.)
- Tablas propias: emh_credentials, emh_seqs, emh_docs, emh_docsline, emh_invalidacion, emh_contingencia, emh_emailconfig, emh_emaillog, emh_fileshare_config, c_bp_documents
- Funciones: emh_dtesjson(), emh_anulacion_json(), emh_contingencia_json(), emh_numero_a_letras()
- Vistas: emh_v_emisor, emh_v_receptor, emh_v_cuerpo
- Trigger: emh_trg_invoice_pending (marca PENDING al completar factura)
2.2 Paso 1 — Datos del Emisor (AD_OrgInfo)
UPDATE adempiere.ad_orginfo SET
emh_nrc = '2926478', -- NRC sin guiones
emh_codactividad = '74900', -- Código CAT-019 MH
emh_descactividad = 'OTRAS ACTIVIDADES PROFESIONALES...',
emh_nombrecomercial = 'INROCAR',
emh_tipoestablec = '02', -- 01=Sucursal 02=Casa Matriz 07=Bodega
emh_correo = 'no-reply@empresa.com',
emh_codestablemh = 'M001', -- Asignado por MH (4 dígitos)
emh_codestable = '0001', -- Código interno
emh_codpuntoventamh = 'P001', -- Asignado por MH (4 dígitos)
emh_codpuntoventa = '0001'
WHERE ad_client_id = {AD_CLIENT_ID} AND ad_org_id = {AD_ORG_ID};
El NIT se obtiene de
ad_org.value(debe estar en formato sin guiones).
2.3 Paso 2 — Credenciales MH
-- Subir certificado P12 como bytea:
INSERT INTO adempiere.emh_credentials (
ad_client_id, ad_org_id,
mh_user, mh_pass,
cert_file, -- bytea del archivo .p12
cert_pass,
ambiente -- '00'=pruebas '01'=producción
) VALUES (
{AD_CLIENT_ID}, {AD_ORG_ID},
'{usuario_mh}', '{password_mh}',
pg_read_binary_file('/ruta/al/certificado.p12'),
'{password_certificado}',
'00'
);
Desde el panel admin: https://host.kontroles.com/admin → sección Credenciales.
2.4 Paso 3 — Secuencias DTE (emh_seqs)
Una fila por cada tipo de DTE que la empresa emita:
INSERT INTO adempiere.emh_seqs (ad_client_id, ad_org_id, tipodoc, serie, currentnext)
VALUES
({CLIENT}, {ORG}, '01', 'M001P001', 1), -- FAC (Factura)
({CLIENT}, {ORG}, '03', 'M001P001', 1), -- CCF (Crédito Fiscal)
({CLIENT}, {ORG}, '04', 'M001P001', 1), -- NR (Nota Remisión)
({CLIENT}, {ORG}, '05', 'M001P001', 1), -- NC (Nota Crédito)
({CLIENT}, {ORG}, '06', 'M001P001', 1), -- ND (Nota Débito)
({CLIENT}, {ORG}, '07', 'M001P001', 1), -- CR (Retención)
({CLIENT}, {ORG}, '08', 'M001P001', 1), -- CL (Liquidación)
({CLIENT}, {ORG}, '09', 'M001P001', 1), -- DCL (Doc. Contable Liq.)
({CLIENT}, {ORG}, '11', 'M001P001', 1), -- FEX (Exportación)
({CLIENT}, {ORG}, '14', 'M001P001', 1), -- FSE (Sujeto Excluido)
({CLIENT}, {ORG}, '15', 'M001P001', 1) -- CD (Donación)
ON CONFLICT (ad_client_id, ad_org_id, tipodoc) DO NOTHING;
serie=codEstableMH (4) || codPuntoVentaMH (4).
El campocurrentnextse incrementa automáticamente con cada DTE emitido.
2.5 Paso 4 — DocTypes KontrolesERP ↔ Tipos DTE
-- Mapear cada c_doctype con su tipo DTE
UPDATE adempiere.c_doctype SET emh_tipodoc = '01'
WHERE ad_client_id={CLIENT} AND name ILIKE '%factura%' AND issotrx='Y';
UPDATE adempiere.c_doctype SET emh_tipodoc = '03'
WHERE ad_client_id={CLIENT} AND name ILIKE '%crédito fiscal%';
UPDATE adempiere.c_doctype SET emh_tipodoc = '05'
WHERE ad_client_id={CLIENT} AND name ILIKE '%nota de crédito%';
UPDATE adempiere.c_doctype SET emh_tipodoc = '06'
WHERE ad_client_id={CLIENT} AND name ILIKE '%nota de débito%';
-- Verificar resultado:
SELECT c_doctype_id, name, emh_tipodoc
FROM adempiere.c_doctype
WHERE ad_client_id={CLIENT} AND emh_tipodoc IS NOT NULL;
2.6 Paso 5 — Departamentos (c_region.emh_deptcode)
UPDATE adempiere.c_region SET emh_deptcode='01' WHERE name ILIKE '%Ahuachapán%' AND c_country_id=173;
UPDATE adempiere.c_region SET emh_deptcode='02' WHERE name ILIKE '%Santa Ana%' AND c_country_id=173;
UPDATE adempiere.c_region SET emh_deptcode='03' WHERE name ILIKE '%Sonsonate%' AND c_country_id=173;
UPDATE adempiere.c_region SET emh_deptcode='04' WHERE name ILIKE '%Chalatenango%' AND c_country_id=173;
UPDATE adempiere.c_region SET emh_deptcode='05' WHERE name ILIKE '%Cuscatlán%' AND c_country_id=173;
UPDATE adempiere.c_region SET emh_deptcode='06' WHERE name ILIKE '%La Libertad%' AND c_country_id=173;
UPDATE adempiere.c_region SET emh_deptcode='07' WHERE name ILIKE '%San Salvador%' AND c_country_id=173;
UPDATE adempiere.c_region SET emh_deptcode='08' WHERE name ILIKE '%Cabañas%' AND c_country_id=173;
UPDATE adempiere.c_region SET emh_deptcode='09' WHERE name ILIKE '%San Vicente%' AND c_country_id=173;
UPDATE adempiere.c_region SET emh_deptcode='10' WHERE name ILIKE '%La Paz%' AND c_country_id=173;
UPDATE adempiere.c_region SET emh_deptcode='11' WHERE name ILIKE '%Usulután%' AND c_country_id=173;
UPDATE adempiere.c_region SET emh_deptcode='12' WHERE name ILIKE '%San Miguel%' AND c_country_id=173;
UPDATE adempiere.c_region SET emh_deptcode='13' WHERE name ILIKE '%Morazán%' AND c_country_id=173;
UPDATE adempiere.c_region SET emh_deptcode='14' WHERE name ILIKE '%La Unión%' AND c_country_id=173;
2.7 Paso 6 — Unidades de Medida MH (c_uom.emh_mhcode)
-- Valores más comunes (catálogo MH)
UPDATE adempiere.c_uom SET emh_mhcode=59 WHERE uomsymbol='Und'; -- Unidad
UPDATE adempiere.c_uom SET emh_mhcode=58 WHERE uomsymbol='Kg'; -- Kilogramo
UPDATE adempiere.c_uom SET emh_mhcode=57 WHERE uomsymbol='L'; -- Litro
UPDATE adempiere.c_uom SET emh_mhcode=99 WHERE emh_mhcode IS NULL; -- Otro (fallback)
2.8 Paso 7 — Configuración Email SMTP
INSERT INTO adempiere.emh_emailconfig (
ad_client_id, ad_org_id,
smtp_host, smtp_port, smtp_user, smtp_pass,
from_email, from_name,
use_tls
) VALUES (
{CLIENT}, {ORG},
'smtp.empresa.com', 587,
'usuario@empresa.com', 'password',
'facturacion@empresa.com', 'Empresa SA de CV',
'Y'
)
ON CONFLICT (ad_client_id, ad_org_id) DO UPDATE SET
smtp_host=EXCLUDED.smtp_host, smtp_port=EXCLUDED.smtp_port,
smtp_user=EXCLUDED.smtp_user, smtp_pass=EXCLUDED.smtp_pass,
from_email=EXCLUDED.from_email, from_name=EXCLUDED.from_name;
Desde el panel admin: https://host.kontroles.com/admin → sección Email Config.
2.9 Paso 8 — Filebrowser (gestor archivos DTE)
-- Registrar empresa en filebrowser_config
-- Esto crea el usuario en Filebrowser vía API POST /files/api/users
INSERT INTO adempiere.emh_fileshare_config (
ad_client_id, username, password, scope
) VALUES (
{CLIENT}, '{subdomain}', '{Password12chars!}', '/{CLIENT}'
);
La contraseña Filebrowser debe tener mínimo 12 caracteres.
El scope controla qué carpeta ve el usuario (generalmente/{ad_client_id}).
2.10 Paso 9 — NIT/NRC de clientes (c_bp_documents)
Para que los DTEs lleven los datos fiscales correctos del receptor:
-- NIT de un cliente
INSERT INTO adempiere.c_bp_documents (c_bpartner_id, ad_client_id, doc_type, doc_value)
VALUES ({BP_ID}, {CLIENT}, 'NIT', '00000000000000') -- 14 dígitos sin guiones
ON CONFLICT DO NOTHING;
-- NRC de un cliente
INSERT INTO adempiere.c_bp_documents (c_bpartner_id, ad_client_id, doc_type, doc_value)
VALUES ({BP_ID}, {CLIENT}, 'NRC', '0000000')
ON CONFLICT DO NOTHING;
-- Actividad económica del cliente (opcional, mejora calidad del DTE)
UPDATE adempiere.c_bpartner SET
emh_codactividad = '45301',
emh_descactividad = 'Venta de partes y piezas...'
WHERE c_bpartner_id = {BP_ID};
2.11 Paso 10 — Nginx Proxy Manager (NPM)
- Acceder a
http://{servidor}:81 - Credenciales iniciales:
admin@example.com/changeme→ cambiar inmediatamente - Crear Proxy Hosts para cada servicio:
| Subdominio | Tipo | Forward Host | Forward Port | SSL |
|---|---|---|---|---|
{empresa}.kontroles.com |
HTTP | erp_app | 8888 | Let's Encrypt |
{empresa}.kontroles.com/admin |
HTTP | dte-worker | 8080 | mismo cert |
{empresa}.kontroles.com/files |
HTTP | filebrowser | 80 | mismo cert |
{empresa}.kontroles.com (POS) |
HTTP | kontrolespos_pwa | 80 | mismo cert |
{empresa}.kontroles.com (RTA) |
HTTP | kontrolesrta_pwa | 80 | mismo cert |
2.12 Resumen del orden de ejecución
1. docker compose up -d ← levantar stack
2. psql < emh_setup_completo.sql ← schema DTE (idempotente)
3. UPDATE ad_orginfo ... ← datos emisor
4. INSERT emh_credentials ... ← credenciales MH
5. INSERT emh_seqs ... ← secuencias por tipo DTE
6. UPDATE c_doctype SET emh_tipodoc ... ← mapeo doctypes
7. UPDATE c_region SET emh_deptcode ... ← departamentos MH
8. UPDATE c_uom SET emh_mhcode ... ← unidades medida MH
9. INSERT emh_emailconfig ... ← SMTP
10. INSERT emh_fileshare_config ... ← Filebrowser
11. Configurar NPM (subdominio + SSL)
12. Reiniciar dte-worker ← toma la config nueva
3. ACTUALIZACIÓN DE UN SERVIDOR EXISTENTE
Para aplicar mejoras de código, correcciones de SQL o nuevos diseños de PDF a un servidor
ya instalado (TEST u otro cliente como JAASA), usar update_dte.sh:
Flujo completo de actualización
────────────────────────────────────────────────────────────────
PASO A (local): Aplicar cambios de código Java
Editar PdfService.java, WorkerScheduler.java, etc.
PASO B (local): Construir y publicar nueva imagen Docker
cd despliegue_cliente/
./build_all.sh dte ← compila firmadorDtes y sube a DockerHub
PASO C (remoto): Aplicar al servidor destino
./update_dte.sh 192.168.1.99 ← TEST
./update_dte.sh 190.86.183.194 ← JAASA de CV
Flags opcionales:
--skip-sql — no actualiza emh_config_dte (si solo cambió código)
--skip-image — no toca el contenedor (si solo cambiaron JRXML/SQL)
--user root — usuario SSH diferente al default (administrador)
────────────────────────────────────────────────────────────────
update_dte.sh ejecuta tres pasos en orden:
1. Sincroniza reports/dtes/ → el servidor (JRXMLs por tipo DTE y por cliente)
2. Aplica SQL (idempotente): emh_tables_ddl → emh_views_functions → emh_generarjson_v2
- Crea columnas/tablas nuevas si aún no existen
- Actualiza funciones y vistas
- Actualiza filas de emh_config_dte con ON CONFLICT DO UPDATE
- No toca: facturas, socios, credenciales MH, secuencias, ni datos del cliente
3. Actualiza imagen: docker compose pull dte-worker + docker compose up -d --no-deps dte-worker
Reportes JRXML personalizados por cliente
Colocar el diseño custom en reports/dtes/{ad_client_id}/dte_{tipodoc}.jrxml:
despliegue_cliente/
└── reports/
└── dtes/
├── generic/ ← diseño genérico para todos
│ ├── dte_01.jrxml
│ ├── dte_03.jrxml
│ └── ...
└── 1000001/ ← diseño exclusivo de ad_client_id=1000001
└── dte_01.jrxml ← sobreescribe generic/dte_01.jrxml solo para este cliente
El worker detecta el JRXML modificado automáticamente (compara lastModified),
sin necesidad de reiniciar el contenedor.
4. CHECKLIST — NUEVA INSTALACIÓN O SINCRONIZACIÓN
4.1 Infraestructura
- [ ] Servidor Linux con Docker + Docker Compose instalados
- [ ] Puertos abiertos: 80, 443, 81 (NPM), 22 (SSH), 5432 (opcional, solo LAN)
- [ ] DNS: CNAME
{empresa}.kontroles.com→ IP del servidor (o dominio base) - [ ] Espacio en disco: mínimo 20 GB para BD + PDFs/JSONs
- [ ] RAM: mínimo 4 GB (recomendado 8 GB para múltiples clientes)
4.2 Repositorios y secretos
- [ ] Clonar o actualizar
despliegue_cliente/en el servidor - [ ] Crear
.envconPG_VOLUME_NAMEyJWT_SECRET - [ ] Verificar credenciales DockerHub en
dte-secrets.properties(para push, no para pull) - [ ] Certificados MH (.p12) disponibles y accesibles
- [ ]
kontroles-secrets.propertiescon passwords de DockerHub
4.3 Imágenes Docker (DockerHub: rocarsadecv)
- [ ]
rocarsadecv/inrocardevsuite:latest— ERP - [ ]
rocarsadecv/dte-worker:latest— Firmador DTE - [ ]
rocarsadecv/kontrolesapi:latest— API REST - [ ]
rocarsadecv/kontrolesweb:latest— Sitio web
docker pull rocarsadecv/inrocardevsuite:latest
docker pull rocarsadecv/dte-worker:latest
docker pull rocarsadecv/kontrolesapi:latest
docker pull rocarsadecv/kontrolesweb:latest
4.4 Base de Datos
Instalación nueva (primer deploy)
- [ ] Preparar backup seed:
KontrolesERP_pg.dmpeninit/ - [ ] Preparar
init/01_init_db.sql(crea usuarioadempiere, extensionespgcrypto,uuid-ossp) - [ ] Ejecutar
deploy.sh --with-db {ip_servidor}(crea BD desde el seed) - [ ] Verificar volumen:
docker volume ls | grep postgres
Sincronización con instalación existente
- [ ] Hacer backup de la BD origen:
bash docker exec administrador-db-1 pg_dump -U postgres -Fc db00_kontroleserp \ > backup_$(date +%Y%m%d).dump - [ ] Copiar backup al nuevo servidor via SCP
- [ ] Restaurar:
bash docker exec -i administrador-db-1 pg_restore -U postgres -d db00_kontroleserp \ --no-owner --role=postgres < backup.dump - [ ] Verificar usuario
adempieretiene permisos en schemaadempiere
Post-restauración siempre
- [ ] Ejecutar
emh_setup_completo.sql(idempotente, aplica cualquier schema nuevo) - [ ] Verificar
emh_dtesjsonfunction existe:sql SELECT proname FROM pg_proc WHERE proname='emh_dtesjson'; - [ ] Verificar trigger
emh_trg_invoice_pendingactivo enc_invoice
4.5 Configuración DTE por cliente (nuevo cliente)
- [ ] Ejecutar Paso 1-10 del Sección 2
- [ ] Verificar que
ad_orginfotiene NIT (enad_org.value), NRC, codEstableMH, codPuntoVentaMH - [ ] Subir certificado
.p12aemh_credentials - [ ] Confirmar
ambiente='00'para pruebas,'01'para producción - [ ] Verificar secuencias
emh_seqspara todos los tipos DTE que usará el cliente - [ ] Probar envío manual: crear factura de prueba →
docstatus='CO'→ verificaremh_status='SENT'
4.6 Reportes y Templates
- [ ] Sincronizar
reports/dtes/generic/*.jrxml(11 archivos) al servidor:bash rsync -az despliegue_cliente/reports/ {servidor}:{deploy_dir}/reports/ - [ ] Si hay reportes customizados por cliente, colocar en:
reports/dtes/{ad_client_id}/dte_{tipodoc}.jrxml(tiene prioridad sobre generic/) - [ ] Reportes Jasper KontrolesERP (balances, libros de venta, etc.) en
/opt/erp/reports/dentro del contenedorerp_app:bash docker cp reporte.jrxml erp_app:/opt/erp/reports/
4.7 Frontends (PWAs)
- [ ] Construir PWAs:
build_all.sh(generakontrolesrta/frontend/ykontrolespos/frontend/) - [ ] Verificar
kontrolesrta/nginx.confcon la URL correcta de kontrolesapi - [ ] Verificar
kontrolespos/nginx.confcon la URL correcta de kontrolesapi - [ ] La API URL en las PWAs apunta a
{empresa}.kontroles.com/api(relativa, vía proxy NPM)
4.8 Nginx Proxy Manager
- [ ] NPM operativo:
curl -s http://{servidor}:81 - [ ] Exportar/importar configuración NPM si es sincronización:
- Datos en
./nginx-proxy-manager/data/(sqlite) — copiar volumen completo
- Datos en
- [ ] Para instalación nueva: crear proxy hosts manualmente (ver tabla Paso 10 §2.11)
- [ ] SSL: verificar Let's Encrypt activo para cada subdominio
- [ ] Probar HTTPS:
curl -I https://{empresa}.kontroles.com
4.9 Filebrowser
- [ ] Verificar
filebrowser/settings.jsonexiste conbaseURL=/files - [ ] Primer acceso:
https://{empresa}.kontroles.com/files→ admin/admin → cambiar password - [ ] Ejecutar INSERT en
emh_fileshare_configpara crear usuario por empresa (§2.9) - [ ] Verificar carpetas
./dtes/{ad_client_id}/con permisos correctos
4.10 Validación final end-to-end
1. Acceder al ERP: https://{empresa}.kontroles.com
└─ Login con usuario/password de la empresa
2. Crear una Factura (FAC):
└─ Sales → Invoices → New
└─ Completar (docstatus → CO)
└─ Verificar emh_status PENDING → SENT en BD:
SELECT documentno, emh_status FROM c_invoice ORDER BY created DESC LIMIT 1;
3. Verificar PDF generado:
└─ GET https://{empresa}.kontroles.com/admin/invoices/{id}/pdf
└─ Verificar QR con portal MH: admin.factura.gob.sv/consultaPublica?...
4. Verificar email enviado:
└─ SELECT * FROM emh_emaillog ORDER BY created DESC LIMIT 1;
5. Verificar archivo en Filebrowser:
└─ https://{empresa}.kontroles.com/files
4.11 Diferencias empresa vs empresa (multi-tenant)
Cada empresa en KontrolesERP se separa por ad_client_id. Los datos que son exclusivos por empresa:
| Objeto | Separación |
|---|---|
emh_credentials |
ad_client_id + ad_org_id |
emh_seqs |
ad_client_id + ad_org_id + tipodoc |
emh_emailconfig |
ad_client_id + ad_org_id |
emh_fileshare_config |
ad_client_id |
c_doctype.emh_tipodoc |
ad_client_id |
ad_orginfo (datos emisor) |
ad_org_id |
| Reportes custom | reports/dtes/{ad_client_id}/ |
| Archivos DTE | ./dtes/{ad_client_id}/ |
| Subdominio NPM | Configurado manualmente por empresa |
APÉNDICE — Scripts de verificación rápida
Verificar estado DTE de un cliente
-- Estado general de envíos del día
SELECT dt.emh_tipodoc, inv.emh_status, COUNT(*) AS n
FROM adempiere.c_invoice inv
JOIN adempiere.c_doctype dt ON dt.c_doctype_id = inv.c_doctype_id
WHERE inv.ad_client_id = {CLIENT}
AND inv.dateinvoiced = CURRENT_DATE
AND dt.emh_tipodoc IS NOT NULL
GROUP BY dt.emh_tipodoc, inv.emh_status
ORDER BY dt.emh_tipodoc, inv.emh_status;
-- Últimos errores
SELECT inv.documentno, inv.emh_msg, inv.updated
FROM adempiere.c_invoice inv
WHERE inv.ad_client_id = {CLIENT}
AND inv.emh_status = 'ERROR'
ORDER BY inv.updated DESC LIMIT 10;
-- Secuencias actuales
SELECT tipodoc, serie, currentnext
FROM adempiere.emh_seqs
WHERE ad_client_id = {CLIENT}
ORDER BY tipodoc;
Reintentar documentos en ERROR
-- Solo si el error fue transitorio (red, timeout):
UPDATE adempiere.c_invoice
SET emh_status = 'PENDING', updated = NOW()
WHERE c_invoice_id IN (
SELECT inv.c_invoice_id
FROM adempiere.c_invoice inv
WHERE inv.emh_status = 'ERROR'
AND inv.ad_client_id = {CLIENT}
AND inv.emh_msg ILIKE '%timeout%' -- ajustar según el error
);
Forzar regeneración de PDF
curl -o documento.pdf \
"https://{empresa}.kontroles.com/admin/invoices/{id}/pdf"
Documento generado para KontrolesERP — SaaS ERP El Salvador
Actualizado: 2026-06-03