Ir al contenido

Arquitectura

Esta página describe cómo está organizada una instalación de Histrix y cómo el motor resuelve qué XML ejecutar. Es la base para entender por qué un mismo programa puede comportarse distinto en dos bases de la misma instalación.

Una instalación tiene el motor en la raíz y los datos de cada cliente separados en database/:

histrix/
src/ Motor (namespace \Histrix\)
admin/ Programas de administración y migraciones del core
modules/ Módulos funcionales compartidos (erp-base, erp-full, ...)
plugins/ Plugins del core
lang/ Traducciones de la interfaz (es, en, fr, pt)
js/ css/ Assets del cliente web
database/
config.xml Configuración de conexiones y empresas
{cliente}/
xml/ XML propios del cliente (sobrescriben los del módulo)
files/ Archivos subidos
log/{año}/{mes}/ Logs de SQL y errores
plugins/ Plugins propios del cliente
tests/ Tests de aceptación de la base
vendor/ Dependencias de Composer

database/ y modules/ son repositorios independientes del motor. Un cambio en un módulo o en los XML de un cliente no se versiona junto con el core.

database/config.xml declara una entrada <base> por cada base atendida. El atributo xmlPath define el directorio raíz de sus XML, es decir database/{xmlPath}/xml/:

<?xml version="1.0" encoding="UTF-8"?>
<sistema>
<empresa>
<img_fondo>histrix_back.jpg</img_fondo>
<lang>es</lang>
</empresa>
<conexiones>
<base id="cliente" tipo="mysql" xmlPath="cliente/">
<descripcion>Cliente S.A.</descripcion>
<base>htx_cliente</base>
<driver>mysql</driver>
<user>usuario</user>
<password>clave</password>
<host>db</host>
<empresa>
<nombre>Cliente S.A.</nombre>
<direccion>Dirección</direccion>
<cuit>30-00000000-0</cuit>
<logo_pdf_1 posx="6" width="30">logo.jpg</logo_pdf_1>
<logo_ini>logo.png</logo_ini>
<modulos>|clientes|proveedores|stock|iva|contabilidad</modulos>
</empresa>
</base>
</conexiones>
</sistema>

El atributo opcional group en <base> filtra qué bases aparecen en el selector según el subdominio por el que se accede.

Cada base tiene además un parámetro CONFIG::baseModule en la tabla HTXOPTIONS (por ejemplo erp-base o erp-full) que define contra qué módulo hace fallback. Al pedir un XML, el motor prueba las rutas en este orden y toma la primera que existe:

  1. database/{xmlPath}/xml/{dir}/{archivo}
  2. database/{xmlPath}/xml/{archivo}
  3. modules/{baseModule}/{dir}/{archivo}
  4. modules/{dir}/{archivo}
  5. admin/{dir}/{archivo}
  6. modules/erp-base/{dir}/{archivo}

Consecuencias prácticas:

  • Un XML colocado en la raíz del árbol del cliente (paso 2) satisface cualquier dir=, porque ese paso ignora el subdirectorio.
  • Si el xmlPath de una base apunta a un directorio vacío, todo se resuelve por fallback al módulo.
  • Dos bases con baseModule distinto pueden cargar versiones diferentes del mismo archivo. Si un programa se comporta distinto entre bases del mismo motor, comparar primero CONFIG::baseModule y el contenido de cada xmlPath.

Un XML en database/{cliente}/xml/ reemplaza por completo al del módulo: no hay merge parcial de campos ni de atributos. Por lo tanto:

  • Antes de modificar un override, compararlo con diff contra el original del módulo. Un override desactualizado se rompe en silencio cuando el archivo del módulo evoluciona.
  • Al crear un override, dejar registrado qué se cambió respecto del original.

Cuando se abre un programa, el motor parsea el XML y construye un contenedor (\Histrix\Data\Container) con las tablas, campos y condiciones declaradas. Ese contenedor se serializa en la sesión y queda identificado por un id de instancia, que es el que usan todas las operaciones siguientes:

GET /api/db/{db}/instance/xml/{ruta.xml} crea la instancia
GET /api/db/{db}/instance/{instance} lee datos
PUT /api/db/{db}/instance/{instance}/set/ actualiza un campo
POST /api/db/{db}/instance/{instance}/process/ graba
DELETE /api/db/{db}/instance/{instance} destruye la instancia

El estado vive en la sesión de PHP, no en el cliente: los campos calculados en el servidor, los valores de los filtros y las filas confirmadas de una grilla de ingreso se resuelven contra esa instancia. La interfaz web y cualquier cliente externo usan las mismas rutas.

  • PHP 8 con Slim 3 para el enrutamiento HTTP y Composer para las dependencias.
  • Acceso a datos por PDO. El tag <driver> de cada base selecciona la implementación: mysql (valor por omisión), mssql, dblib o sqlsrv para SQL Server, progress y sqlite.
  • OAuth 2.0 y OpenID Connect para autenticación de la API.
  • Redis como caché de aplicación y almacenamiento de las filas temporales de los formularios de ingreso.
  • jQuery, jQuery UI y DataTables en el cliente web.

El servidor Redis se resuelve, en este orden, desde los tags <redis_server> y <redis_password> de la base en config.xml, desde las variables de entorno REDIS_SERVER_HOST y REDIS_SERVER_PASSWORD, o por defecto localhost.