# Capítulo X — Patrones de Diseño en la Arquitectura de PYMETORY ## X.1 Introducción: fundamentación teórica El diseño de software orientado a objetos enfrenta de manera recurrente un conjunto acotado de problemas estructurales y de comportamiento. La respuesta sistemática a estos problemas se formalizó con la publicación de *Design Patterns: Elements of Reusable Object-Oriented Software* por Gamma, Helm, Johnson y Vlissides —el denominado "Gang of Four" (GoF)— en 1994, obra que catalogó veintitrés patrones agrupados en tres familias: creacionales, estructurales y de comportamiento (Gamma et al., 1994). Un patrón de diseño no es código reutilizable, sino una descripción de una solución probada a un problema recurrente dentro de un contexto determinado; su valor reside en proporcionar un vocabulario compartido y en codificar experiencia de diseño acumulada. En el ámbito específico de las aplicaciones empresariales —sistemas con lógica de negocio rica, persistencia en bases de datos relacionales y múltiples capas— el catálogo del GoF resulta insuficiente por sí solo. Martin Fowler complementó este corpus en *Patterns of Enterprise Application Architecture* (PoEAA, 2002), donde formalizó patrones arquitectónicos como Active Record, Data Mapper, Transaction Script, Domain Model y Service Layer, orientados explícitamente a la organización de la lógica de dominio y al mapeo objeto-relacional (Fowler, 2002). PYMETORY se construye sobre el framework Laravel 11, cuya arquitectura adopta de forma nativa varios de estos patrones como convenciones de primer orden. El ORM Eloquent es una implementación canónica del patrón Active Record; el sistema de middleware HTTP materializa una Chain of Responsibility; y las factories de prueba implementan el patrón Factory Method (Laravel, 2024; Stauffer, 2019). El presente capítulo no se limita a enumerar estos patrones, sino que los analiza en su instanciación concreta dentro del código fuente del proyecto —verificada sobre un grafo de conocimiento de 3,243 nodos, 3,192 aristas y 367 comunidades de código— y discute las alternativas de diseño descartadas, su impacto sobre los atributos de calidad del sistema y su relación con los principios SOLID (Martin, 2003). Atendiendo a la observación del director de proyecto —"prefiero cinco patrones bien sustentados que once de relleno"— se seleccionaron cinco patrones de elevado peso arquitectónico y presencia inequívoca en el código: **Strategy**, **Chain of Responsibility**, **Active Record**, **Transaction Script** y **Factory Method**. --- ## X.2 Análisis profundo de los patrones implementados ### X.2.1 Strategy (Estrategia) **Definición formal.** El patrón Strategy "define una familia de algoritmos, encapsula cada uno de ellos y los hace intercambiables. Strategy permite que el algoritmo varíe independientemente de los clientes que lo utilizan" (Gamma et al., 1994). Pertenece a la categoría de patrones de comportamiento. **Problema que resuelve.** PYMETORY debe emitir alertas (vencimientos FEFO, stock bajo) a través de múltiples canales —notificación en aplicación, Telegram y correo electrónico— donde la combinación de canales activos se decide en tiempo de ejecución según la configuración del administrador. Codificar esta selección mediante condicionales dispersos en cada punto de emisión produciría duplicación y acoplamiento. Strategy encapsula cada algoritmo de entrega en un método autónomo y delega la selección a la configuración persistida. **Ubicación en el código.** `app/Services/AlertService.php`, método `send()` (líneas 14–25), con las estrategias concretas en `sendInApp()`, `sendTelegram()` y `sendEmail()` (líneas 30–110). ```php // app/Services/AlertService.php : 14-25 public function send(string $tipo, string $titulo, string $mensaje, ?string $accionUrl = null, ?string $icono = null): void { $this->sendInApp($tipo, $titulo, $mensaje, $accionUrl, $icono); if (self::isActive('notif_telegram_activo')) { $this->sendTelegram($tipo, $titulo, $mensaje); } if (self::isActive('notif_email_activo')) { $this->sendEmail($tipo, $titulo, $mensaje); } } ``` Una segunda instancia del mismo patrón aparece en la estrategia de ordenamiento de inventario: el scope `scopeFefoOrder()` (`app/Models/Lote.php`, líneas 78–81) encapsula el algoritmo FEFO (*First-Expired-First-Out*) como una estrategia de consulta intercambiable. **Justificación.** La emisión de alertas es un punto de variación natural del sistema: nuevos canales (p. ej. WhatsApp) pueden añadirse como nuevas estrategias sin modificar a los clientes que invocan `send()`, satisfaciendo el principio Abierto/Cerrado (OCP). --- ### X.2.2 Chain of Responsibility (Cadena de Responsabilidad) **Definición formal.** Este patrón "evita acoplar el emisor de una petición a su receptor, dando a más de un objeto la oportunidad de manejar la petición. Encadena los objetos receptores y pasa la petición a lo largo de la cadena hasta que un objeto la maneja" (Gamma et al., 1994). **Problema que resuelve.** El control de acceso basado en roles (RBAC) requiere validar, antes de ejecutar cualquier acción de un controlador, que el usuario esté autenticado, tenga el correo verificado y posea el rol adecuado. Resolver estas comprobaciones dentro de cada método de controlador violaría el principio de responsabilidad única y dispersaría la lógica de seguridad. **Ubicación en el código.** `app/Http/Middleware/CheckRole.php` (líneas 20–27), aplicado declarativamente en `routes/web.php`. ```php // app/Http/Middleware/CheckRole.php : 20-27 public function handle(Request $request, Closure $next, string $role): Response { if (!$request->user() || $request->user()->role !== $role) { abort(403, 'Acceso denegado para rol ' . strtoupper($role)); } return $next($request); // delega al siguiente eslabón } // routes/web.php Route::get('/...', [Controller::class, 'metodo']) ->middleware(['auth', 'verified', 'role:admin']); ``` **Justificación.** La cadena `auth → verified → role:admin` es la materialización literal del patrón: cada eslabón tiene una única responsabilidad y propaga la petición mediante `$next($request)` solo si su condición se cumple, o la interrumpe en caso contrario. Añadir o reordenar comprobaciones es una operación declarativa que no toca el código de los controladores. --- ### X.2.3 Active Record **Definición formal.** "Un objeto que envuelve una fila de una tabla o vista de base de datos, encapsula el acceso a la base de datos y añade lógica de dominio sobre esos datos" (Fowler, 2002). **Problema que resuelve.** PYMETORY necesita mapear entidades del dominio (lotes, materiales, bodegas, movimientos) a tablas relacionales y, simultáneamente, asociarles reglas de negocio como la criticidad por vencimiento o la valorización económica. Active Record fusiona persistencia y comportamiento en una sola clase. **Ubicación en el código.** `app/Models/Lote.php`, con atributos computados de dominio en las líneas 57–66 y scopes de consulta en las líneas 78–103. ```php // app/Models/Lote.php : 57-66 public function getIsCriticalAttribute(): bool { return $this->days_until_expiration <= 15 && $this->status !== 'consumed'; } public function getValorTotalAttribute(): float { return round(($this->quantity ?? 0) * ($this->unit_cost ?? 0), 2); } // app/Models/Lote.php : 78-81 public function scopeFefoOrder($query) { return $query->where('status', '!=', 'consumed') ->orderBy('expiration_date', 'asc'); } ``` **Justificación.** La regla de criticidad (vencimiento ≤ 15 días) reside en un único punto del modelo, garantizando consistencia entre el dashboard, los reportes y el módulo de consumo. Esta centralización es precisamente lo que el patrón persigue. --- ### X.2.4 Transaction Script **Definición formal.** "Organiza la lógica de negocio mediante procedimientos donde cada procedimiento maneja una única petición de la capa de presentación" (Fowler, 2002). **Problema que resuelve.** El despacho de inventario con lógica FEFO es una operación transaccional que recorre secuencialmente los lotes ordenados por vencimiento, descuenta cantidades, marca lotes consumidos y registra cada movimiento en el Kardex. Es un proceso esencialmente procedimental, con una única transacción atómica, que no justifica la complejidad de un modelo de dominio rico. **Ubicación en el código.** `app/Http/Controllers/ConsumptionController.php`, método `store()` (líneas 18–71) y `app/Http/Controllers/TransferController.php`, método `store()` (líneas 54–145). ```php // ConsumptionController.php : 38-67 (extracto) DB::transaction(function () use ($material, $quantityToConsume, $validated) { $lotes = Lote::where('material_id', $material->id) ->where('status', 'active') ->orderBy('expiration_date', 'asc') // FEFO ->get(); $remaining = $quantityToConsume; foreach ($lotes as $lote) { if ($remaining <= 0) break; $consumption = min($lote->quantity, $remaining); $lote->quantity -= $consumption; if ($lote->quantity <= 0) $lote->status = 'consumed'; $lote->save(); Movimiento::create([ 'lote_id' => $lote->id, 'user_id' => Auth::id(), 'type' => 'salida', 'quantity' => $consumption, 'reason' => $validated['reason'], 'description' => 'Despacho FEFO de ' . $material->name, ]); $remaining -= $consumption; } }); ``` **Justificación.** La operación es lineal, con un único punto de entrada por petición y fuerte garantía transaccional (`DB::transaction`). Transaction Script ofrece la solución más legible y mantenible para este caso, sin la sobreingeniería de distribuir el algoritmo entre múltiples objetos de dominio. --- ### X.2.5 Factory Method **Definición formal.** "Define una interfaz para crear un objeto, pero deja que las subclases decidan qué clase instanciar. Factory Method permite que una clase delegue la instanciación a sus subclases" (Gamma et al., 1994). Es un patrón creacional. **Problema que resuelve.** La suite de 60 pruebas PHPUnit requiere construir objetos de dominio válidos y coherentes de forma repetible. Instanciar manualmente cada Lote con sus dependencias (Material, Bodega) en cada prueba generaría duplicación masiva y fragilidad. **Ubicación en el código.** `database/factories/LoteFactory.php`, método `definition()` (líneas 14–23). ```php // database/factories/LoteFactory.php : 14-23 public function definition(): array { return [ 'material_id' => Material::factory(), // fábricas anidadas 'bodega_id' => Bodega::factory(), 'batch_number' => 'LT-' . fake()->unique()->numberBetween(1000, 9999), 'quantity' => 100, 'unit_cost' => 5.50, 'expiration_date' => now()->addDays(30), 'status' => 'active', ]; } ``` **Justificación.** `LoteFactory` desacopla la construcción de objetos complejos de las pruebas que los consumen y compone fábricas anidadas, reduciendo el banco de pruebas a expresiones declarativas. --- ## X.3 Comparativa con enfoques alternativos **Active Record frente a Data Mapper.** Data Mapper (Fowler, 2002) separa por completo los objetos de dominio de la lógica de persistencia mediante una capa intermedia (mapper), lo que favorece dominios muy complejos y persistencia agnóstica. Sin embargo, introduce considerable ceremonia. Para PYMETORY, cuyo dominio es de complejidad moderada y cuya persistencia es exclusivamente MySQL vía Eloquent, Active Record ofrece una relación coste/beneficio superior. El sacrificio —menor portabilidad de la lógica de dominio fuera de la capa de persistencia— es asumible dado el alcance. **Transaction Script frente a Domain Model.** Un Domain Model (Fowler, 2002) distribuye la lógica entre objetos ricos que combinan datos y comportamiento, idóneo cuando las reglas son numerosas y altamente interdependientes. El despacho FEFO es un algoritmo procedimental acotado y transaccional. Transaction Script es la elección pragmáticamente correcta para la escala actual del sistema. --- ## X.4 Impacto en los atributos de calidad - **Mantenibilidad.** La encapsulación de algoritmos (Strategy) y la centralización de reglas en el modelo (Active Record) reducen la dispersión de lógica. La cadena de middleware permite alterar la política de seguridad de forma declarativa. - **Testeabilidad.** Factory Method es el habilitador directo de las 60 pruebas PHPUnit. La separación de CheckRole como middleware aislado permite probar el control de acceso de forma independiente. - **Escalabilidad.** Strategy permite añadir canales de alerta sin regresión; Chain of Responsibility permite insertar nuevas validaciones sin reescritura. --- ## X.5 Relación con los principios SOLID | Patrón | Principio SOLID reforzado | Mecanismo | |--------|---------------------------|-----------| | Strategy | Abierto/Cerrado (OCP) | Nuevos canales como nuevas estrategias, sin modificar `send()` | | Chain of Responsibility | Responsabilidad Única (SRP) | Cada middleware valida una sola condición | | Active Record | SRP* — con tensión | Centraliza dominio + persistencia | | Transaction Script | SRP | Un procedimiento por petición de presentación | | Factory Method | Inversión de Dependencias (DIP) | El cliente depende de la abstracción Factory | > **Nota de honestidad técnica.** Active Record introduce una tensión deliberada con el SRP, al unir persistencia y lógica de dominio en una sola clase. Esta es una concesión consciente: el análisis SOLID del sistema reporta cumplimiento de 3 de 5 principios, y esta tensión es una de las causas documentadas. --- ## X.6 Tabla resumen de impacto | # | Patrón | Categoría | Evidencia | Mant. | Test. | Escal. | SOLID | |---|--------|-----------|-----------|:--:|:--:|:--:|:--:| | 1 | Strategy | GoF | AlertService.php:14-25 | Alto | Medio | Alto | OCP | | 2 | Chain of Responsibility | GoF | CheckRole.php:20-27 | Alto | Alto | Medio | SRP | | 3 | Active Record | PoEAA | Lote.php:10-103 | Medio | Alto | Medio | SRP* | | 4 | Transaction Script | PoEAA | ConsumptionController:38-69 | Medio | Medio | Bajo | SRP | | 5 | Factory Method | GoF | LoteFactory.php:14-23 | Alto | Alto | Alto | DIP | --- ## Referencias - Gamma, E., Helm, R., Johnson, R., & Vlissides, J. (1994). *Design Patterns: Elements of Reusable Object-Oriented Software*. Addison-Wesley. - Fowler, M. (2002). *Patterns of Enterprise Application Architecture*. Addison-Wesley. - Martin, R. C. (2003). *Agile Software Development: Principles, Patterns, and Practices*. Prentice Hall. - Stauffer, M. (2019). *Laravel: Up & Running* (2.ª ed.). O'Reilly Media. - Laravel. (2024). Laravel 11.x Documentation: Eloquent ORM, Middleware, Database Testing. https://laravel.com/docs