# Patrones de Diseño en PYMETORY: de las Convenciones del Framework a las Decisiones de Dominio ## Parte 1 — Introducción Todo sistema de software construido sobre un framework moderno hereda, desde su primera línea de código, un conjunto de patrones de diseño. PYMETORY no es la excepción: al edificarse sobre Laravel 11 adopta automáticamente el patrón Modelo-Vista-Controlador (MVC) como organización general, el patrón Active Record a través del ORM Eloquent, una cadena de *middleware* (Chain of Responsibility) para el procesamiento de peticiones HTTP y un contenedor de servicios (Service Container) que gestiona la inyección de dependencias. Estos patrones no se "eligieron" en el sentido estricto; vienen incorporados en la arquitectura del ecosistema. Conviene afirmarlo con claridad desde el inicio: **heredar patrones del framework no es un demérito, sino ingeniería responsable**. Reutilizar soluciones que la comunidad ya validó —MVC, Active Record, el *pipeline* de *middleware*— evita reinventar mecanismos probados, reduce la superficie de error y permite concentrar el esfuerzo en lo que realmente distingue al sistema: su dominio. Un desarrollador que ignorara estas convenciones para construirlas desde cero no demostraría mayor mérito, sino menor criterio. Ahora bien, una tesis de grado no puede limitarse a enumerar lo que el framework provee. Describir que "se usó MVC" o que "Eloquent implementa Active Record" informa sobre la *herramienta*, no sobre el *diseñador*. El valor académico aparece cuando se muestra **dónde el desarrollador tomó decisiones**: dónde existían alternativas, cómo se evaluaron y por qué se optó por una en función de los requisitos reales del sistema de gestión de inventarios. Por esa razón, este capítulo se organiza en tres niveles de profundidad creciente. Primero se documentan los **patrones heredados del framework**, que PYMETORY usa y cuya configuración concreta en el proyecto se explica. Luego se abordan los **tres patrones más frecuentes en las tesis de Tecnología en Sistemas de la Universidad del Valle sede Tuluá** —MVC, Factory y un patrón de cadena/comportamiento— mostrando cómo aplican con ejemplos reales del código de PYMETORY. Finalmente, se exponen los **cinco patrones donde el proyecto tomó decisiones propias** frente a alternativas legítimas, con la justificación de cada elección. El objetivo es demostrar que estos niveles no compiten entre sí: se complementan. --- ## Parte 2 — Mapa de relaciones entre los patrones El siguiente diagrama muestra cómo se estratifican los patrones en PYMETORY. Las capas no se contradicen: cada una se apoya en la inferior y resuelve un tipo de problema distinto. ``` ┌───────────────────────────────────────────────────────────────────────┐ │ CAPA 1 — HEREDADOS DEL FRAMEWORK (Laravel 11) │ │ Provistos por el ecosistema; los usamos, no los reclamamos como aporte │ │ │ │ ┌──────────┐ ┌────────────────────┐ ┌──────────────┐ ┌──────────┐ │ │ │ MVC │ │ Active Record │ │ Middleware │ │ Service │ │ │ │ (Laravel)│ │ (Eloquent ORM) │ │ pipeline │ │Container │ │ │ └──────────┘ └────────────────────┘ └──────────────┘ └──────────┘ │ └───────────────────────────────────┬─────────────────────────────────────┘ │ se apoya en ▼ ┌───────────────────────────────────────────────────────────────────────┐ │ CAPA 2 — CONVENCIONALES QUE APLICAMOS CON USO REAL │ │ Patrones comunes en otras tesis, instanciados con propósito en el código│ │ │ │ ┌────────────────────┐ ┌────────────────────┐ ┌──────────────────┐ │ │ │ Factory Method │ │ Chain of Resp. │ │ Strategy │ │ │ │ (pruebas PHPUnit) │ │ (RBAC middleware) │ │ (alertas multi- │ │ │ │ LoteFactory.php │ │ CheckRole.php │ │ canal) │ │ │ └────────────────────┘ └────────────────────┘ └──────────────────┘ │ └───────────────────────────────────┬─────────────────────────────────────┘ │ complementa con ▼ ┌───────────────────────────────────────────────────────────────────────┐ │ CAPA 3 — DECISIONES PROPIAS (se evaluó y se descartó una alternativa) │ │ │ │ Active Record ⟵ en lugar de ⟶ Repository / Data Mapper │ │ Transaction Script ⟵ en lugar de ⟶ Domain Model │ │ Strategy ⟵ en lugar de ⟶ Observer │ │ │ │ Evidencia: Lote.php · ConsumptionController.php · AlertService.php │ └───────────────────────────────────────────────────────────────────────┘ ``` La lectura del diagrama es ascendente: el framework (Capa 1) provee los cimientos; sobre ellos se instancian patrones convencionales con uso efectivo (Capa 2); y en los puntos donde el dominio de inventarios planteó una disyuntiva real, se tomó una decisión documentada (Capa 3). Un mismo patrón puede aparecer en dos capas —Chain of Responsibility es framework *y* uso convencional; Active Record es framework *y* decisión frente a Repository— precisamente porque las capas describen *perspectivas*, no compartimentos estancos. --- ## Parte 3 — Los tres patrones más comunes en tesis de Univalle Tuluá que también aplican en PYMETORY En los trabajos de grado de Tecnología en Sistemas de la sede Tuluá, los patrones más reportados suelen ser MVC, Factory y un patrón de comportamiento de tipo cadena u observación. PYMETORY los aplica, y a continuación se explican en lenguaje accesible —con analogías que un operario de bodega entendería— junto con su instanciación real. ### 3.1 MVC (Modelo-Vista-Controlador) **En lenguaje humano.** MVC es como el funcionamiento de un restaurante. El **mesero** (controlador) recibe el pedido del cliente y coordina; la **cocina y la despensa** (modelo) guardan los ingredientes y preparan el plato según las reglas de la receta; y el **plato servido en la mesa** (vista) es lo que el cliente finalmente ve. El cliente nunca entra a la cocina, y el cocinero no atiende mesas: cada quien tiene su papel. **Cómo se aplica en PYMETORY.** Laravel impone MVC como organización general. El controlador `InventoryController` recibe la petición, consulta el modelo `Lote` y entrega los datos a una vista React mediante Inertia: ```php // app/Http/Controllers/InventoryController.php (extracto del método index) $lotesActivos = Lote::with(['material', 'bodega'])->fefoOrder()->get()->map(...); return Inertia::render('Dashboard', ['lotes' => $lotesActivos, 'stats' => $stats]); ``` **Cómo se acomoda a nuestro caso.** PYMETORY emplea una variante moderna del patrón: la "Vista" no es una plantilla del servidor, sino una aplicación React 19 conectada mediante el protocolo Inertia.js. El controlador sigue siendo el coordinador y el modelo sigue conteniendo los datos y las reglas, pero la presentación se delega a un *frontend* reactivo. Es MVC adaptado a una arquitectura de página única (SPA). **¿Por qué esta decisión y no las vistas tradicionales de Blade?** Laravel ofrece Blade como motor de plantillas del lado del servidor —la opción convencional en la mayoría de tesis del ecosistema. Es una elección perfectamente válida para aplicaciones con interacciones simples de formulario. Sin embargo, PYMETORY requería **tres capacidades que Blade no ofrece sin una segunda capa de JavaScript**: (1) un escáner QR con acceso a la cámara del dispositivo vía `getUserMedia()`, (2) un tablero Kanban con *drag and drop* en tiempo real usando `@dnd-kit`, y (3) una previsualización de etiquetas que renderiza códigos de barras y QR dinámicamente con `JsBarcode` y `react-qr-code`. Inertia.js permitió conservar el flujo MVC de Laravel —el controlador sigue coordinando, el modelo sigue conteniendo las reglas— mientras la Vista gana reactividad sin necesidad de construir una API REST separada. **No se descartó Blade por considerarlo inferior; se adoptó React+Inertia porque el dominio exigía una interfaz que Blade, por arquitectura, no puede proveer sin una segunda capa que habría duplicado la lógica de renderizado.** ### 3.2 Factory (Factory Method) **En lenguaje humano.** Una *factory* es como un molde de repostería. En vez de armar cada galleta a mano —midiendo harina, azúcar y mantequilla una por una cada vez— se usa un molde que produce galletas idénticas y bien formadas con un solo movimiento. Si necesitas cien galletas para una prueba de sabor, el molde te las da rápido y todas iguales. **Cómo se aplica en PYMETORY.** El patrón Factory Method construye objetos complejos para las pruebas automatizadas. `LoteFactory` produce un lote válido —con sus dependencias de material y bodega— en una sola invocación: ```php // database/factories/LoteFactory.php : 14-23 public function definition(): array { return [ 'material_id' => Material::factory(), // moldes anidados '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', ]; } ``` **Cómo se acomoda a nuestro caso.** En muchas tesis el patrón Factory se menciona en el marco teórico sin un uso comprobable. En PYMETORY es **infraestructura efectiva de pruebas**: las 60 pruebas PHPUnit dependen de las *factories* para preparar sus datos. Sin este molde, cada prueba tendría que ensamblar manualmente un lote con su material y su bodega, multiplicando el código y la fragilidad. ### 3.3 Chain of Responsibility (Cadena de Responsabilidad) **En lenguaje humano.** Es exactamente el filtro de seguridad de un aeropuerto: primero revisan tu documento, luego pasas por el escáner, luego validan tu pase de abordar. Cada puesto revisa **una sola cosa** y, si la apruebas, te deja avanzar al siguiente. Si fallas en cualquier puesto, no pasas —y no es necesario llegar al final para detenerte. **Cómo se aplica en PYMETORY.** El control de acceso encadena varias validaciones antes de permitir una acción protegida: ```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, 'No tienes permisos de ' . strtoupper($role) . '...'); } return $next($request); // deja avanzar al siguiente "puesto" } ``` ```php // routes/web.php : 52 Route::get('/...', [Controller::class, 'metodo']) ->middleware(['auth', 'verified', 'role:admin']); ``` **Cómo se acomoda a nuestro caso.** La cadena `auth → verified → role:admin` es el filtro de aeropuerto del sistema: primero verifica que el usuario haya iniciado sesión, luego que haya confirmado su correo, luego que tenga rol de administrador. Agregar un nuevo "puesto de control" (por ejemplo, un límite de peticiones) es declarativo y no obliga a modificar ningún controlador. --- ## Parte 4 — Los cinco patrones donde tomamos decisiones propias Las definiciones formales, los fragmentos extensos y los diagramas UML de estos cinco patrones se desarrollan en las secciones X.1 a X.6. Aquí el foco es la **decisión de diseño**: qué alternativa legítima se consideró, por qué no encajaba en PYMETORY y por qué la opción adoptada resultó más apropiada. El tono es de reconocimiento: ninguna alternativa es "mala"; cada una es la elección correcta bajo condiciones que nuestro caso no cumple. ### 4.1 Strategy **El patrón en una frase.** Encapsula una familia de algoritmos intercambiables y permite elegir cuál ejecutar en tiempo de ejecución. **Alternativa que no usamos: Observer.** Observer es la elección correcta cuando existe un evento que debe difundirse automáticamente a un conjunto de suscriptores que reaccionan por su cuenta —como una lista de correo donde todos los inscritos reciben el mismo boletín. Es un patrón excelente para sistemas de eventos. **Por qué Strategy encaja mejor en PYMETORY.** Nuestro problema no es difundir un evento a suscriptores pasivos, sino **seleccionar cuál algoritmo de envío ejecutar** según la configuración que el administrador define (Telegram, correo, notificación en app). El sistema decide caso por caso, leyendo banderas de configuración, no avisa a una lista fija. Esa selección condicional de algoritmo es la definición de Strategy. Evidencia: `app/Services/AlertService.php`, método `send()` (líneas 14–25). ```php // app/Services/AlertService.php : 14-25 public function send(string $tipo, string $titulo, string $mensaje, ...): void { $this->sendInApp($tipo, $titulo, $mensaje, $accionUrl, $icono); if (self::isActive('notif_telegram_activo')) { $this->sendTelegram(...); } if (self::isActive('notif_email_activo')) { $this->sendEmail(...); } } ``` **Analogía.** Es como un jefe de bodega que, según las instrucciones del día, decide si despacha un pedido por camión, por motocicleta o en persona. Elige el medio según la situación; no le manda el mismo aviso a todos los transportistas a la vez. ### 4.2 Chain of Responsibility **El patrón en una frase.** Pasa una petición por una serie de manejadores, cada uno con una única responsabilidad, hasta que uno la atiende o la detiene. **Alternativa que no usamos: una verificación única ("guard" monolítico).** Concentrar todas las comprobaciones de seguridad en un solo bloque —un único `if` que valida sesión, correo y rol a la vez— es perfectamente válido en sistemas con una sola regla de acceso simple y estable. **Por qué la cadena encaja mejor en PYMETORY.** El sistema requiere **varias validaciones independientes y reordenables**. Separarlas en eslabones permite que cada uno cambie sin afectar a los demás y que las políticas de seguridad se compongan de forma declarativa en las rutas, sin tocar los controladores. Evidencia: `app/Http/Middleware/CheckRole.php` (líneas 20–27) y `routes/web.php` (línea 52). **Analogía.** El filtro de aeropuerto descrito en la Parte 3: varios puestos en fila, cada uno revisando una sola cosa, en lugar de un único guardia tratando de revisarlo todo simultáneamente. ### 4.3 Active Record **El patrón en una frase.** Cada objeto representa una fila de la base de datos y contiene tanto su persistencia como sus reglas de dominio. **Alternativa que no usamos: Repository / Data Mapper.** El patrón Repository es valioso y recomendable cuando se necesita independencia respecto del motor de persistencia, cuando se anticipan múltiples fuentes de datos o cuando el dominio es lo bastante complejo como para justificar una capa de abstracción dedicada. En esos contextos, separar el dominio del acceso a datos es la decisión correcta. **Por qué Active Record encaja mejor en PYMETORY.** Eloquent ya implementa Active Record de forma nativa, y la persistencia del proyecto es única (MySQL). Añadir una capa Repository encima habría producido una envoltura que solo reenvía llamadas —una abstracción sin beneficio para la complejidad real del sistema. Conservamos Active Record y aprovechamos que el modelo `Lote` concentra las reglas del dominio: criticidad por vencimiento y valorización viven en un solo lugar. Evidencia: `app/Models/Lote.php`, `getIsCriticalAttribute()` (líneas 57–66) y `scopeFefoOrder()` (líneas 78–81). ```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); } ``` **Analogía.** Es como un empleado de bodega que conoce su propio estante: sabe qué hay, sabe cuándo vence y sabe cuánto vale, sin necesidad de un intermediario que vaya y vuelva con la información. Para una bodega de tamaño moderado, ese empleado autosuficiente es más ágil que montar toda una oficina de consultas. ### 4.4 Transaction Script **El patrón en una frase.** Organiza la lógica de negocio como un procedimiento único que atiende una petición de principio a fin. **Alternativa que no usamos: Domain Model.** Un Domain Model rico —objetos de dominio que combinan datos y comportamiento y colaboran entre sí— es la elección correcta cuando las reglas de negocio son numerosas, interdependientes y cambian con frecuencia. Para esos dominios complejos, distribuir la lógica entre objetos es lo adecuado. **Por qué Transaction Script encaja mejor en PYMETORY.** El despacho FEFO es un procedimiento lineal y acotado: ordenar lotes por vencimiento, descontar cantidades, marcar los agotados y registrar cada movimiento, todo dentro de una transacción atómica. Resolverlo con un Domain Model multiplicaría clases e indirecciones sin beneficio proporcional. Evidencia: `app/Http/Controllers/ConsumptionController.php`, método `store()` (líneas 38–67). ```php // app/Http/Controllers/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')->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([...]); // registro en el Kardex $remaining -= $consumption; } }); ``` **Analogía.** Es como seguir una receta de cocina paso a paso: alistar ingredientes, mezclarlos en orden y hornear, todo de corrido. Para un plato sencillo no se necesita una brigada de cocina con estaciones especializadas; basta un cocinero que ejecute la receta de principio a fin, y que si algo sale mal, deseche todo y empiece de nuevo (la transacción "todo o nada"). ### 4.5 Factory Method **El patrón en una frase.** Delega la creación de objetos a un componente especializado en construirlos. **Alternativa que no usamos: la construcción manual repetida.** Crear cada objeto a mano en cada prueba es aceptable cuando los objetos son triviales y las pruebas, muy pocas. En ese escenario mínimo, una *factory* podría parecer innecesaria. **Por qué Factory Method encaja mejor en PYMETORY.** Con 60 pruebas y objetos que arrastran dependencias anidadas (un lote requiere material y bodega), la construcción manual sería insostenible. La *factory* centraliza esa construcción y la hace declarativa y consistente. Evidencia: `database/factories/LoteFactory.php`, método `definition()` (líneas 14–23). **Analogía.** El molde de repostería de la Parte 3: cuando necesitas muchas unidades bien formadas y repetibles, el molde es infraestructura, no lujo. --- ## Parte 5 — Tabla de concesiones | Patrón descartado | Cuándo SÍ es la elección correcta | Por qué no aplica en PYMETORY | Referencia | |-------------------|-----------------------------------|-------------------------------|------------| | **Observer** | Cuando un evento debe difundirse automáticamente a múltiples suscriptores que reaccionan por su cuenta. | El requisito es *seleccionar* un canal según configuración, no difundir a una lista. Corresponde a Strategy. | Gamma et al. (1994) | | **Repository / Data Mapper** | Cuando se requiere independencia del motor de persistencia, múltiples fuentes de datos o un dominio muy complejo. | Eloquent ya es Active Record; persistencia única en MySQL. La capa sería redundante. | Fowler (2002) | | **Domain Model** | Cuando las reglas de negocio son numerosas, interdependientes y volátiles. | El despacho FEFO es un procedimiento lineal y transaccional de ~30 líneas. | Fowler (2002) | | **Guard monolítico** | Cuando existe una única regla de acceso simple y estable. | Se necesitan varias validaciones independientes y reordenables (auth, verified, rol). | Gamma et al. (1994) | | **Construcción manual de datos de prueba** | Cuando los objetos son triviales y las pruebas, escasas. | 60 pruebas con dependencias anidadas vuelven la construcción manual frágil e insostenible. | Gamma et al. (1994) | > Nota: MVC, Active Record y la cadena de *middleware* no figuran como "descartados" > porque PYMETORY **sí los usa**: son convenciones provistas por Laravel. No se > reclaman como aporte propio, pero tampoco se rechazan; constituyen la base > arquitectónica sobre la cual se tomaron las decisiones de la Capa 3. --- ## Parte 6 — Cierre PYMETORY no descartó patrones por capricho ni por afán de distinguirse. Cada patrón se evaluó frente a los requisitos reales del sistema de gestión de inventarios siguiendo un criterio simple y verificable. **Donde el framework ya había resuelto el problema —MVC, Active Record vía Eloquent, la cadena de *middleware*, el contenedor de servicios— se aprovechó la solución existente**, porque reinventarla habría sido irresponsable. **Donde la complejidad del dominio no justificaba una abstracción adicional —Repository sobre Eloquent, un Domain Model para un despacho lineal— se optó por abstenerse**, reconociendo que esas alternativas son valiosas en contextos que el proyecto, en su escala actual, no presenta. **Y donde el dominio exigía una solución específica —la selección configurable de canales de alerta, el control de acceso por capas— se decidió de forma deliberada y se documentó la decisión.** Tres de esas decisiones consistieron, de hecho, en *no* usar el patrón que un enfoque convencional habría incluido. Esa capacidad de elegir un patrón, y sobre todo de abstenerse de aplicarlo cuando no aporta valor, es la manifestación del pensamiento crítico de diseño que este trabajo busca demostrar: en PYMETORY los patrones no son una lista heredada, sino una bitácora de decisiones. --- ## 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, Service Container*. https://laravel.com/docs