Skip to main content

10. Casos Especiales de Integración con Quartup

Descripción General

El framework implementa soluciones específicas para adaptarse a las características propias del sistema Quartup:

  • Tablas con constructores parametrizados: Mecanismos para gestionar tablas que requieren parámetros dinámicos durante su inicialización
  • Campos calculados mediante SQL: Estrategias para definir y configurar campos calculados con expresiones SQL que pueden incluir parámetros contextuales

Esta sección documenta estas implementaciones y proporciona guías paso a paso para utilizarlas correctamente en diferentes situaciones.

Parámetros Dinámicos en Constructores de Tablas

Problema

Algunas clases tabla en Quartup requieren parámetros dinámicos durante su inicialización, como identificadores de tienda, modos de operación, o filtros específicos. Estos valores no pueden ser codificados de manera estática ya que varían según el contexto de la solicitud.

Solución Implementada

La solución consiste en utilizar el contenedor de dependencias de la aplicación (a través de QuApp::getContainer()) para almacenar y recuperar estos valores dinámicos. Un middleware específico se encarga de establecer estos valores en el contenedor antes de que se construyan las instancias de los modelos.

Ejemplo de Implementación

1. Modelo con Parámetros Dinámicos

namespace Infrastructures\Legacy\PPos\PosartticVArticulo;

use App\Base\DB\BaseModelTree as Model;
use App\Core\QuApp;

class PosartticVArticuloModel extends Model
{
public function __construct()
{
try {
$postie_id = QuApp::getContainer()->get('postie_id');
} catch (\Throwable $th) {
$postie_id = null;
}

try {
$HoT = QuApp::getContainer()->get('HoT');
} catch (\Throwable $th) {
$HoT = 'H';
}

if (!$postie_id) {
throw new \Exception('El header x-tienda es requerido');
}

$constructor = "POSTIE_ID:{$postie_id},CLASE_HABITUAL:{$HoT},COMPRA_VENTA:FV";
$this->table = "posarttic_v({$constructor})";
$this->map = [
"PK" => 'id'
];
parent::__construct();
}
}

2. Middleware para Configurar los Parámetros

namespace ApiLayer\Modules\Tpv\Middlewares;

use App\Core\Interfaces\Middleware;
use App\Core\RequestFactory as Request;
use App\Core\QuApp;
use Infrastructures\Legacy\PPos\PostieTienda\PostieTiendaRepository;
use Infrastructures\Legacy\PPos\PostieTienda\PostieTiendaModel;

class XTiendaClaseHabitualMiddleware implements Middleware
{
public function handle(Request $request, callable $next)
{
$tiendaRepository = new PostieTiendaRepository(new PostieTiendaModel());
//queremos la clase habitual de la tienda
$tiendaRepository->selectable(['posartcla_id']);
try {
$xTienda = $request->headers->get('x-tienda');
} catch (\Throwable $th) {
throw new \Exception('Se requiere el header x-tienda');
}
try {
$tienda = $tiendaRepository->selectById($xTienda);
$claseHabitualTienda = $tienda['posartcla_id'];
} catch (\Throwable $th) {
throw new \Exception('Hubo un error al obtener la tienda');
}
$HoT = 'H'; //H = hace referencia a la clase habitual de la tienda, T = todas las clases
if (isset($request->type)) {
if ($request->type != $claseHabitualTienda) {
$HoT = 'T';
}
}

$container = QuApp::getContainer();
$container->set('postie_id', function () use ($xTienda) {
return $xTienda;
});
$container->set('HoT', function () use ($HoT) {
return $HoT;
});

return $next($request);
}
}

3. Configuración de Rutas con el Middleware

namespace ApiLayer\Modules\Tpv\Domains\Carts;

use App\Core\Router\Route;
use ApiLayer\Modules\Tpv\Middlewares\XTiendaClaseHabitualMiddleware;
use App\Middlewares\AuthMiddleware;

$middlewares = ['before' => [
AuthMiddleware::class,
XTiendaClaseHabitualMiddleware::class
]];
Route::group(\ApiLayer\Modules\Tpv\Domains\Carts\CartsController::class, function () use ($middlewares) {
Route::get('tpv/carts/', 'index', $middlewares);
Route::get('tpv/carts/:id', 'show', $middlewares);
Route::post('tpv/carts/', 'store', $middlewares);
Route::post('tpv/carts/line', 'storeLine', $middlewares);
Route::put('tpv/carts/:id', 'update', $middlewares);
Route::put('tpv/carts/line/:id', 'updateLine', $middlewares);
Route::delete('tpv/carts/:id', 'delete', $middlewares);
Route::delete('tpv/carts/line/:id', 'deleteLine', $middlewares);
});

Ventajas del Enfoque

  1. Desacoplamiento: El modelo no necesita conocer de dónde provienen los parámetros.
  2. Flexibilidad: Los parámetros pueden provenir de diferentes fuentes (headers, URL, cuerpo de petición).
  3. Mantenibilidad: La lógica para obtener y procesar los parámetros está centralizada en el middleware.
  4. Reutilización: El mismo middleware puede ser aplicado a múltiples rutas que requieran los mismos parámetros.

Consideraciones Importantes

  • Asegúrate de que el middleware se ejecute antes de que se instancien los modelos que dependen de estos parámetros.
  • Maneja adecuadamente los casos de error, como la ausencia de headers requeridos.
  • Define valores por defecto cuando sea posible para evitar errores inesperados.

Campos Calculados mediante SQL

Problema

Quartup utiliza métodos para definir campos calculados mediante expresiones SQL. Si estos campos no están definidos en el constructor de la clase de Quartup, necesitamos un mecanismo para configurarlos en nuestro framework.

Solución Implementada

Se ha extendido el trait CollectionTrait para soportar la definición de campos calculados mediante la propiedad $fieldExecSelect en los modelos. Esta propiedad contiene un mapeo de nombres de campo a expresiones SQL que se configuran en la tabla Quartup durante la operación de colección.

Ejemplo de Implementación

1. Extensión en CollectionTrait

// En App\Base\DB\CollectionTrait

public function collection($request)
{
try {
$page = $request["page"] ?? 1; // La página solicitada, por defecto la página 1 si no se proporciona
$itemsPerPage = $request["itemsPerPage"] ?? 30; // Cantidad de elementos por página, por defecto 30 si no se especifica
$filters = $request["filters"] ?? null;
$permanentFilters = $request["permanentFilters"] ?? null;

// Configuración de campos calculados si están definidos en el modelo
if (isset($this->fieldExecSelect)) {
foreach ($this->fieldExecSelect as $field => $sql) {
$this->qu_table->getOTable()->setFieldExecSelect($field, $sql);
}
}

// Resto de la implementación...
} catch (\Throwable $th) {
throw $th;
}
}

2. Modelo con Campos Calculados

namespace Infrastructures\Legacy\PMaes\MaesartiProducts;

use App\Base\DB\BaseModelTree as Model;

class MaesartiProductsModel extends Model
{
protected $fieldExecSelect;

public function __construct()
{
$this->table = 'maesarti';
$this->fieldExecSelect = [
"stockPendienteEntrar" => "SELECT almamovilinGetStock(maesarti.cod_articulo,'DISPON','00000000','99991231')",
"stockPendienteSalir" => "SELECT cialdocolinGetStockPdte('PV',maesarti.cod_articulo,'DISPON','00000000','99991231','ST')",
];
$this->map = [
'PK' => 'id',
];
parent::__construct();
}
}

Parámetros Dinámicos en Expresiones SQL

Al igual que con los constructores de tablas, las expresiones SQL para campos calculados pueden requerir parámetros dinámicos. Para estos casos, se puede implementar la misma estrategia basada en el contenedor de dependencias:

Ejemplo con Parámetros Dinámicos en SQL

namespace Infrastructures\Legacy\PMaes\MaesartiProducts;

use App\Base\DB\BaseModelTree as Model;
use App\Core\QuApp;

class MaesartiProductsModel extends Model
{
protected $fieldExecSelect;

public function __construct()
{
$this->table = 'maesarti';

// Obtener parámetros dinámicos del contenedor
try {
$fechaInicio = QuApp::getContainer()->get('fechaInicio');
$fechaFin = QuApp::getContainer()->get('fechaFin');
} catch (\Throwable $th) {
// Valores por defecto si no están disponibles
$fechaInicio = '00000000';
$fechaFin = '99991231';
}

// Usar parámetros dinámicos en las expresiones SQL
$this->fieldExecSelect = [
"stockPendienteEntrar" => "SELECT almamovilinGetStock(maesarti.cod_articulo,'DISPON','{$fechaInicio}','{$fechaFin}')",
"stockPendienteSalir" => "SELECT cialdocolinGetStockPdte('PV',maesarti.cod_articulo,'DISPON','{$fechaInicio}','{$fechaFin}','ST')",
];

$this->map = [
'PK' => 'id',
];
parent::__construct();
}
}

Middleware para Configurar Parámetros SQL

namespace ApiLayer\Modules\PMaes\Middlewares;

use App\Core\Interfaces\Middleware;
use App\Core\RequestFactory as Request;
use App\Core\QuApp;

class SqlParametersMiddleware implements Middleware
{
public function handle(Request $request, callable $next)
{
// Obtener fechas de los parámetros de consulta o establecer valores predeterminados
$fechaInicio = $request->query->get('fechaInicio', date('Ymd', strtotime('-30 days')));
$fechaFin = $request->query->get('fechaFin', date('Ymd'));

// Validar formato de fechas
if (!preg_match('/^\d{8}$/', $fechaInicio) || !preg_match('/^\d{8}$/', $fechaFin)) {
throw new \Exception('Formato de fecha inválido. Debe ser YYYYMMDD');
}

// Registrar en el contenedor
$container = QuApp::getContainer();
$container->set('fechaInicio', function () use ($fechaInicio) {
return $fechaInicio;
});
$container->set('fechaFin', function () use ($fechaFin) {
return $fechaFin;
});

return $next($request);
}
}

Ventajas de Este Enfoque

  1. Flexibilidad: Permite que las expresiones SQL utilicen valores que varían según el contexto de la solicitud.
  2. Coherencia: Utiliza el mismo patrón ya establecido para los parámetros en constructores de tablas.
  3. Personalización: Los consumidores de la API pueden influir en el comportamiento de los campos calculados a través de parámetros.
  4. Seguridad: Los parámetros pueden ser validados antes de ser utilizados en expresiones SQL.
  5. Declarativo: Los campos calculados se definen de manera clara y declarativa en el modelo.
  6. Centralizado: Toda la lógica relacionada con la estructura de datos está contenida en el modelo.
  7. Transparente: Los campos calculados son tratados como campos regulares por el resto del framework.
  8. Mantenible: Las expresiones SQL están almacenadas en un solo lugar, facilitando su mantenimiento.

Consideraciones Adicionales

  • Aplica validación estricta a los parámetros que se utilizarán en expresiones SQL para prevenir inyección SQL.
  • Define claramente qué parámetros afectan a qué campos calculados en la documentación de la API.
  • Establece valores predeterminados sensatos para todos los parámetros dinámicos.
  • Considera el uso de consultas parametrizadas cuando sea posible, en lugar de concatenar valores directamente.
  • Para casos complejos, puedes implementar un servicio dedicado a la construcción de expresiones SQL que maneje de manera segura la incorporación de parámetros.
  • Las expresiones SQL deben ser compatibles con la base de datos subyacente de Quartup.
  • Es recomendable documentar el propósito y comportamiento esperado de cada campo calculado.
  • El rendimiento puede verse afectado si se utilizan expresiones SQL complejas o si se definen demasiados campos calculados.
  • Asegúrate de que las referencias a tablas y columnas en las expresiones SQL sean correctas y existan en la base de datos.

Este enfoque proporciona una solución coherente y escalable para manejar tanto los parámetros dinámicos en constructores de tablas como en expresiones SQL para campos calculados, manteniendo la misma filosofía de diseño en todo el framework.

Mejor Práctica para la Implementación

Parámetros Dinámicos en Constructores

  1. Define middleware específicos para cada conjunto de parámetros relacionados.
  2. Aplica estos middleware a nivel de grupo de rutas para que afecten a todos los endpoints que lo requieran.
  3. Proporciona mensajes de error claros cuando falten parámetros requeridos.
  4. Utiliza valores predeterminados cuando sea posible para mejorar la robustez.

Campos Calculados

  1. Agrupa los campos calculados por funcionalidad o dominio de negocio.
  2. Documenta el propósito y comportamiento esperado de cada campo.
  3. Considera el impacto en el rendimiento y optimiza cuando sea necesario.
  4. Incluye estos campos en la documentación de la API para que los consumidores conozcan su disponibilidad.

Conclusión

Estos mecanismos permiten adaptar el framework para trabajar de manera eficiente con las peculiaridades de Quartup, manteniendo una arquitectura limpia y desacoplada. Aunque son soluciones específicas para problemas concretos, siguen principios de diseño sólidos y mantienen la consistencia con el resto del framework.