Diseño Integral de la Plataforma de Marketing - Planificación Futura e Implementación Actual

  1. Visión General del Sistema

1.1 Objetivos de Diseño

  • Gestionar el ciclo de vida completo de contratos (borrador → aprobación → asignación → cumplimeinto).
  • Soportar arquitectura multiinquilino (cada empresa con su configuración independiente).
  • Permitir configuración dinámica de la lógica de negocio mediante plantillas JSON.
  • Implementar un motor de ejecución flexible basado en reflexión.

1.2 Principios Fundamentales

Separación de reglas de negocio y código rígido, utilizando configuración externa para lograr adaptabilidad sin reinicios.

  1. Arquitectura del Sistema

2.1 Módulos Principales

Módulo Función Tecnología
Motor de Plantillas Interpreta JSON y construye modelos de negocio Jackson + anotaciones personalizadas
Ejecutor por Reflexión Carga dinámicamente clases de implementación según configuración JSON Reflexión Java + caché optimizada
Gestión de Diccionarios Mantiene configuraciones de diccionario de negocio, soporta multinivel y personalización Caché Redis
Motor de Flujo de Aprobación Interpreta dinámicamente configuraciones de flujo No implementado actualmente
  1. Especificación de Plantilla JSON

3.1 Ejemplo de Estructura de Plantilla

{
  "baseProperties": [
    {
      "componentType": "input:string/pop-up",
      "propertyName": "contractName",
      "displayName": "Nombre del contrato",
      "validator": {
        "type": "without",
        "content": ""
      },
      "autoLoadClass": {
        "rendering": {
          "className": "crear.comun.COMMON_RENDERING",
          "methodName": "CREATE_ADMIN_EDIT"
        },
        "save": {
          "className": "crear.comun.raiz.CONTRACT_NAME_",
          "methodName": "SAVE"
        },
        "verify": {
          "className": "crear.comun.raiz.CONTRACT_NAME_",
          "methodName": "VERIFY"
        }
      }
    }
  ],
  "projectProperties": [
    {
      "displayName": "Campo de negocio oculto - evento de objeto",
      "propertyName": "projects",
      "autoLoadClass": {
        "save": {
          "className": "crear.comun.proyecto.PROJECTS_",
          "methodName": "SAVE"
        }
      }
    },
    {
      "displayName": "Número de registro de proyecto adjudicado",
      "propertyName": "bidProjectRegisterNo",
      "componentType": "input:string/pop-up",
      "validator": {
        "type": "without",
        "content": ""
      },
      "autoLoadClass": {
        "rendering": {
          "className": "crear.comun.proyecto.BID_PROJECT_REGISTER_NO_",
          "methodName": "RENDERING"
        },
        "verify": {
          "className": "crear.comun.proyecto.BID_PROJECT_REGISTER_NO_",
          "methodName": "VERIFY"
        },
        "save": {
          "className": "crear.comun.proyecto.BID_PROJECT_REGISTER_NO_",
          "methodName": "SAVE"
        }
      }
    }
  ]
}

3.2 Puntos Clave de Diseño

  • Extensión del sistema de tipos: Soporte para tipos de formulario personalizados mediante el campo componentType; validación interna mediante validator; soporte para estructuras anidadas.
  • Mecanismo de validación: Clases de validación, visualización y guardado personalizables.
  • Inyección de dependencias mediante reflexión:
@SneakyThrows
public static void ejecutar(Plantilla plantilla, String tipo, Class<?>[] tiposParam, Object... argumentos) {
    Map<String, ClaseAutoCarga> mapa = plantilla.getClaseAutoCarga();
    if (mapa != null && mapa.containsKey(tipo)) {
        ClaseAutoCarga carga = mapa.get(tipo);
        String nombreClase = "com.nari.microsvc.marketing.workbench.handover.dynamic_code." + carga.getClassName();
        MethodHandle manejador;
        if (!CACHE_CLASE.get().getOrDefault(new ConcurrentHashMap<>()).containsKey(nombreClase)) {
            Class<?> clazz = Class.forName(nombreClase);
            manejador = MethodHandles.lookup().findConstructor(clazz, MethodType.methodType(void.class));
            CACHE_CLASE.get().putIfAbsent(nombreClase, manejador);
        } else {
            manejador = CACHE_CLASE.get().get(nombreClase);
        }
        ConcurrentMap<String, Method> metodos = CACHE_METODO.get().getOrDefault(nombreClase, new ConcurrentHashMap<>());
        try {
            Method metodo = metodos.get(carga.getMethodName());
            if (metodo != null) {
                metodo.invoke(manejador.invoke(), argumentos);
            } else {
                Object instancia = manejador.invoke();
                Method m = instancia.getClass().getMethod(carga.getMethodName(), tiposParam);
                m.invoke(instancia, argumentos);
                metodos.put(carga.getMethodName(), m);
            }
        } catch (InvocationTargetException e) {
            Throwable causa = e.getTargetException();
            if (causa instanceof CreateContractException) throw (CreateContractException) causa;
            if (causa instanceof SubmitVerifyException) throw (SubmitVerifyException) causa;
            throw new RuntimeException(e);
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
    }
}

  1. Diseño del Módulo de Consulta de Listas

4.1 Patrones de Diseño Aplicados

  • Cadena de responsabilidad: Procesamiento en pipeline mediante la clase abstracta PageDataAbstract, cada paso delega al siguiente.
  • Estrategia: Ensamblaje dinámico de condiciones de búsqueda mdeiante la interfaz CustomConditionStrategy.
  • Fábrica: Enrutamiento de fuentes de datos según el tipo de página (handover, performance).

4.2 Optimización de Rendimiento

@Cacheable(value = "cacheContratos", key = "#dto.page+':'+#dto.pageSize+':'+#dto.sortField")
public ResultadoPagina obtenerContratos(ConsultaDTO dto) { ... }

@CacheEvict(value = "cacheContratos", allEntries = true)
public void refrescarCache() { ... }

4.3 Metadatos de Campos Optimizados

{
  "escenario": "marketing_contract_desk",
  "fields": {
    "contractName": {
      "type": "STRING",
      "label": "Nombre del contrato",
      "search": {"type": "text"},
      "table": {"width": "350px"}
    },
    "contractNumber": {
      "type": "STRING",
      "label": "Número de contrato",
      "search": {"type": "text", "matches": "/^[A-Za-z0-9-]+$/"}
    }
  },
  "tableColumns": [1,2,3],
  "searchFields": [1,2]
}

  1. Módulo de Detalle de Contrato

5.1 Patrón de Cadena de Responsabilidad

La clase abstracta HandleDetails define la interfaz y mantiene un sucesor. Cada paso (datos, plantilla, externo, campos) se ejecuta en orden configurable.

public abstract class HandleDetails {
    protected HandleDetails sucesor;
    public void setSucesor(HandleDetails s) { this.sucesor = s; }
    public void ejecutar(ContratoConstruccion qry, ModeloVistaBasico vista) {
        procesar(qry, vista);
        if (sucesor != null) sucesor.ejecutar(qry, vista);
    }
    public abstract void procesar(ContratoConstruccion qry, ModeloVistaBasico vista);
}

5.2 Patrón Composite

Se utiliza Tree para construir flujos ramificados según el tipo de contrato.

@Data
public class Tree {
    private String nombre;
    private HandleDetails raiz;
    private List<HandleDetails> listaNodos;
}

  1. Sistema de Campos Extendidos de Negocio

6.1 Diseño de Tablas

CREATE TABLE IF NOT EXISTS `TRANSFER_BIZ_EXTEND_FIELD` (
    `ID` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
    `BIZ_MODULE` VARCHAR(64) NOT NULL,
    `BIZ_ENTITY_TYPE` VARCHAR(64) NOT NULL,
    `FIELD_CODE` VARCHAR(64) NOT NULL,
    `FIELD_NAME` VARCHAR(128) NOT NULL,
    `DATA_TYPE` VARCHAR(32) NOT NULL,
    `LENGTH` INT UNSIGNED DEFAULT 255,
    `PRECISION` INT UNSIGNED DEFAULT 19,
    `SCALE` INT UNSIGNED DEFAULT 4,
    `IS_REQUIRED` TINYINT UNSIGNED NOT NULL DEFAULT 0,
    `DEFAULT_VALUE` VARCHAR(512) DEFAULT NULL,
    `IS_SEARCHABLE` TINYINT UNSIGNED DEFAULT 0,
    `IS_FILTERABLE` TINYINT UNSIGNED DEFAULT 0,
    `IS_SORTABLE` TINYINT UNSIGNED DEFAULT 0,
    `STATUS` TINYINT UNSIGNED DEFAULT 1,
    `EXT_CONFIG` JSON DEFAULT NULL,
    `CREATE_TIME` DATETIME DEFAULT CURRENT_TIMESTAMP,
    `UPDATE_TIME` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    `CREATOR` VARCHAR(64),
    `UPDATER` VARCHAR(64),
    PRIMARY KEY (`ID`),
    UNIQUE KEY `UNIQ_FIELD_CODE` (`FIELD_CODE`),
    INDEX `IDX_BIZ_MODULE_ENTITY` (`BIZ_MODULE`, `BIZ_ENTITY_TYPE`),
    INDEX `IDX_STATUS` (`STATUS`)
) ENGINE=INNODB DEFAULT CHARSET=UTF8MB4;

CREATE TABLE IF NOT EXISTS `TRANSFER_BIZ_EXTEND_VALUE` (
    `ID` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
    `BIZ_MODULE` VARCHAR(64),
    `BIZ_ENTITY_TYPE` VARCHAR(64),
    `BIZ_ENTITY_ID` BIGINT UNSIGNED NOT NULL,
    `FIELD_CODE` VARCHAR(64),
    `FIELD_VALUE` TEXT,
    `CREATE_TIME` DATETIME DEFAULT CURRENT_TIMESTAMP,
    `UPDATE_TIME` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    PRIMARY KEY (`ID`),
    INDEX `IDX_ENTITY` (`BIZ_MODULE`, `BIZ_ENTITY_TYPE`, `BIZ_ENTITY_ID`),
    UNIQUE KEY `UNIQ_ENTITY_FIELD` (`BIZ_MODULE`, `BIZ_ENTITY_TYPE`, `BIZ_ENTITY_ID`, `FIELD_CODE`)
) ENGINE=INNODB DEFAULT CHARSET=UTF8MB4;

6.2 Patrón Estrategia para Manejo de Tipos

public class FabricaManejadorCampo {
    private static final Map<String, ManejadorValorCampo> manejadores = new HashMap<>();
    static {
        manejadores.put(EnumTipoDato.STRING.name(), new ManejadorString());
        // otros tipos...
    }
    public static ManejadorValorCampo obtener(String tipoDato) {
        return manejadores.get(tipoDato);
    }
}

  1. Sistema de Diccionario Multiinquilino

7.1 Estructura de Tablas

CREATE TABLE `sys_dict_type` (
  `id` bigint AUTO_INCREMENT,
  `tenant_id` bigint NOT NULL,
  `type_code` varchar(64) NOT NULL,
  `type_name` varchar(128),
  `is_system` tinyint DEFAULT 0,
  `is_shared` tinyint DEFAULT 0,
  `status` tinyint DEFAULT 1,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_tenant_type_code` (`tenant_id`,`type_code`)
) ENGINE=InnoDB;

CREATE TABLE `sys_dict_data` (
  `id` bigint AUTO_INCREMENT,
  `tenant_id` bigint NOT NULL,
  `type_id` bigint NOT NULL,
  `parent_id` bigint DEFAULT 0,
  `data_code` varchar(64),
  `data_name` varchar(128),
  `data_value` varchar(512),
  `path` varchar(255),
  `level` int DEFAULT 1,
  `sort` int DEFAULT 0,
  `status` tinyint DEFAULT 1,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_type_data_code` (`type_id`,`data_code`),
  KEY `idx_path` (`path`)
) ENGINE=InnoDB;

7.2 Campos Extendidos de Diccionario

Se definen tablas separadas para definición de campos extendidos y sus valores, con soporte para múltiples tipos (string, number, boolean, date, json).

// Ejemplo de definición de campo extendido
POST /api/dict/extend/define
{
    "typeId": 5,
    "fieldCode": "longitud",
    "fieldName": "Longitud",
    "fieldType": "number"
}

  1. Sistema de Botones Dinámicos

8.1 Arquitectura General

  • Analizador de configuración (Singleton): Interpreta YAML/JSON y genera objetos ButtonConfig.
  • Administrador de contexto (Singleton): Mantiene el contexto de ejecución (rol, escenario) con ThreadLocal.
  • Fábrica de estrategias (Fábrica + Estrategia): Decide visibilidad y comportamiento según configuración y contexto.
  • Ejecutor de acciones (Comando): Ejecuta la acción definida (llamada API, redirección, popup).
  • Observador de recarga en caliente: Escucha cambios en archivos de configuración y refresca las estrategias sin reinicio.
  1. Evolución de la Arquitectura

9.1 Desde MVC hasta Configuración por Plantillas JSON

  • 1.0 (MVC tradicional): Lógica de negocio rígida, alta dependencia código-base de datos, difícil extensión.
  • 2.0 (DDD): Mejor organización por dominios, pero aún acoplamiento con cambios de negocio.
  • 3.0 (Configuración basada en JSON): Las reglas de negocio se externalizan en plantillas JSON, se cargan dinámicamente mediante reflexión y estrategias, permitiendo adaptación sin reinicio y soporte multiinquilino.

9.2 Lecciones Aprendidas

  • El negocio es el motor principal de la arquitectuar; la elección debe coincidir con la complejidad real.
  • El desacoplamiento (capas, dominios, reglas vs código) es la clave para la escalabilidad.
  • La configuración externa es el camino hacia sistemas verdaderamente adaptables.

Publicado el 7-9 18:51