Auditada por Opus 4.8 · 3,005 palabras · 6 partes · Junio 2026
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 Chain of Responsibility— 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.
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.
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.
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.
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:
// 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.
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:
// 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.
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:
// 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"
}
// 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.
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.
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. Evidencia: app/Services/AlertService.php, método send() (líneas 14–25).
// 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.
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.
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. Conservamos Active Record y aprovechamos que el modelo Lote concentra las reglas del dominio en un solo lugar. Evidencia: app/Models/Lote.php, getIsCriticalAttribute() (líneas 57–66).
// 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.
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.
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).
// 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").
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.
| Patrón descartado | Cuándo SÍ es la elección correcta | Por qué no aplica en PYMETORY | Ref. |
|---|---|---|---|
| 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. | Gamma et al. |
| Repository / Data Mapper | Cuando se requiere independencia del motor de persistencia, múltiples fuentes o dominio muy complejo. | Eloquent ya es Active Record; persistencia única en MySQL. Capa redundante. | Fowler |
| Domain Model | Cuando las reglas de negocio son numerosas, interdependientes y volátiles. | Despacho FEFO es procedimiento lineal y transaccional de ~30 líneas. | Fowler |
| Guard monolítico | Cuando existe una única regla de acceso simple y estable. | Necesitamos varias validaciones independientes y reordenables. | Gamma et al. |
| Construcción manual de datos de prueba | Cuando los objetos son triviales y las pruebas, escasas. | 60 pruebas con dependencias anidadas: construcción manual insostenible. | Gamma et al. |
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.
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.