<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Blog Maria Tech</title>
	<atom:link href="https://www.mariatech.com.mx/blog/feed/" rel="self" type="application/rss+xml" />
	<link>https://www.mariatech.com.mx/blog</link>
	<description>Desarrollo web, tecnologías de la información y marketing digital</description>
	<lastBuildDate>Thu, 15 Oct 2026 17:19:14 +0000</lastBuildDate>
	<language>es</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.1.4</generator>

<image>
	<url>https://www.mariatech.com.mx/blog/wp-content/uploads/2024/05/cropped-icon_wordpress-32x32.png</url>
	<title>Blog Maria Tech</title>
	<link>https://www.mariatech.com.mx/blog</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>Cache::remember() en Laravel: qué es, cómo funciona y cuándo deberías usarlo</title>
		<link>https://www.mariatech.com.mx/blog/laravel-php/cache-remember-laravel-como-funciona-cuando-usarlo/</link>
					<comments>https://www.mariatech.com.mx/blog/laravel-php/cache-remember-laravel-como-funciona-cuando-usarlo/#respond</comments>
		
		<dc:creator><![CDATA[Victor Santillan]]></dc:creator>
		<pubDate>Thu, 15 Oct 2026 17:16:08 +0000</pubDate>
				<category><![CDATA[Laravel / PHP]]></category>
		<guid isPermaLink="false">https://www.mariatech.com.mx/blog/?p=324</guid>

					<description><![CDATA[Cuando una aplicación consulta constantemente la misma información...]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Cuando una aplicación consulta constantemente la misma información, especialmente si proviene de una API externa o de una consulta pesada a base de datos, repetir el mismo proceso en cada petición puede generar latencia innecesaria y aumentar el consumo de recursos.</p>



<p class="wp-block-paragraph">Laravel incluye una herramienta muy práctica para resolver este tipo de escenarios: <code>Cache::remember()</code>. Su objetivo es sencillo: consultar primero si un valor ya existe en caché y, únicamente si no existe o expiró, ejecutar la lógica necesaria para obtenerlo nuevamente.</p>



<h2 class="wp-block-heading">¿Qué hace Cache::remember()?</h2>



<p class="wp-block-paragraph">La forma general de utilizarlo es la siguiente:</p>



<pre class="wp-block-code"><code>$data = Cache::remember(
    'example.key',
    now()-&gt;addHours(24),
    function () {
        return SomeService::getData();
    }
);</code></pre>



<p class="wp-block-paragraph">Internamente, Laravel sigue una lógica equivalente a esta:</p>



<pre class="wp-block-code"><code>if (Cache::has('example.key')) {
    return Cache::get('example.key');
}

$data = SomeService::getData();

Cache::put(
    'example.key',
    $data,
    now()-&gt;addHours(24)
);

return $data;</code></pre>



<p class="wp-block-paragraph">La ventaja de <code>remember()</code> es que concentra todo ese flujo en una sola instrucción.</p>



<h2 class="wp-block-heading">¿Dónde guarda Laravel la información?</h2>



<p class="wp-block-paragraph">El lugar donde se almacena depende del <em>cache store</em> configurado en Laravel. Normalmente se define desde el archivo <code>.env</code>.</p>



<pre class="wp-block-code"><code>CACHE_STORE=file</code></pre>



<p class="wp-block-paragraph">Algunos stores comunes son:</p>



<ul class="wp-block-list">
<li><strong>file:</strong> guarda la información en archivos dentro de <code>storage/framework/cache/data</code>.</li>



<li><strong>database:</strong> almacena los valores en una tabla de caché dentro de la base de datos.</li>



<li><strong>redis:</strong> utiliza Redis como almacenamiento de alto rendimiento.</li>



<li><strong>array:</strong> mantiene los valores únicamente durante la ejecución actual, por lo que no persisten entre peticiones.</li>
</ul>



<p class="wp-block-paragraph">Esto significa que <code>Cache::remember()</code> no necesariamente guarda información solo en memoria. Con un store persistente como <code>file</code>, <code>database</code> o <code>redis</code>, el dato puede seguir disponible incluso después de que la petición HTTP haya terminado.</p>



<h2 class="wp-block-heading">Ejemplo práctico: consumir una API externa</h2>



<p class="wp-block-paragraph">Supongamos que una aplicación necesita obtener un catálogo desde una API externa. Si dicho catálogo cambia pocas veces al día, consultarlo nuevamente en cada petición no tendría mucho sentido.</p>



<pre class="wp-block-code"><code>$icons = Cache::remember(
    'iconify.collection.mdi',
    now()-&gt;addHours(24),
    function () {
        return $this-&gt;fetchCollectionFromApi('mdi');
    }
);</code></pre>



<p class="wp-block-paragraph">La primera vez que se ejecuta, Laravel consulta la API, guarda el resultado y lo devuelve. Las siguientes peticiones recuperan directamente el valor cacheado hasta que expire.</p>



<pre class="wp-block-preformatted">Aplicación
   ↓
Cache::remember()
   ↓
¿Existe el dato?
   ├── Sí → devolver caché
   └── No → consultar API
              ↓
          guardar resultado
              ↓
          devolver información</pre>



<h2 class="wp-block-heading">¿Cuándo se recomienda usar Cache::remember()?</h2>



<p class="wp-block-paragraph">Es especialmente útil cuando se cumplen tres condiciones: la información se consulta con frecuencia, obtenerla tiene un costo y puede tolerar cierto tiempo sin actualizarse.</p>



<ul class="wp-block-list">
<li><strong>APIs externas:</strong> catálogos, metadatos, servicios de terceros o información que no cambia constantemente.</li>



<li><strong>Consultas pesadas:</strong> reportes, agregaciones, conteos o dashboards que implican varias operaciones en base de datos.</li>



<li><strong>Catálogos relativamente estáticos:</strong> países, estados, configuraciones, tipos de registros o listas de referencia.</li>



<li><strong>Datos procesados:</strong> información que necesita varias consultas o transformaciones antes de ser mostrada.</li>



<li><strong>Contenido muy consultado:</strong> datos que son solicitados constantemente por múltiples usuarios.</li>
</ul>



<h2 class="wp-block-heading">Ejemplo con una consulta a base de datos</h2>



<pre class="wp-block-code"><code>$categories = Cache::remember(
    'categories.enabled',
    now()-&gt;addHour(),
    function () {
        return Category::where('enabled', true)
            -&gt;orderBy('name')
            -&gt;get();
    }
);</code></pre>



<p class="wp-block-paragraph">Mientras el caché siga vigente, Laravel evita ejecutar nuevamente la consulta y entrega el resultado almacenado.</p>



<h2 class="wp-block-heading">¿Cuándo no conviene usarlo?</h2>



<p class="wp-block-paragraph">No todo debería almacenarse en caché. Existen datos donde la precisión en tiempo real es más importante que el rendimiento.</p>



<ul class="wp-block-list">
<li>Inventario disponible.</li>



<li>Saldos financieros.</li>



<li>Estado de una transacción.</li>



<li>Disponibilidad inmediata de una reservación.</li>



<li>Información que cambia cada pocos segundos.</li>



<li>Datos críticos de seguridad sin una estrategia clara de invalidación.</li>
</ul>



<p class="wp-block-paragraph">En estos casos, mostrar información desactualizada podría provocar errores funcionales o incluso afectar procesos importantes del sistema.</p>



<h2 class="wp-block-heading">La importancia del tiempo de expiración</h2>



<p class="wp-block-paragraph">Una de las decisiones más importantes al trabajar con caché es definir cuánto tiempo puede mantenerse vigente la información.</p>



<pre class="wp-block-code"><code>now()-&gt;addMinutes(10);
now()-&gt;addHour();
now()-&gt;addHours(24);
now()-&gt;addDay();</code></pre>



<p class="wp-block-paragraph">No existe un tiempo universal. Depende de qué tan rápido cambie el dato y cuánto tiempo sea aceptable trabajar con una versión anterior.</p>



<h2 class="wp-block-heading">¿Qué pasa cuando el caché expira?</h2>



<p class="wp-block-paragraph">No necesitas crear un proceso adicional para actualizarlo. Cuando la entrada expira, la siguiente petición que ejecute <code>Cache::remember()</code> volverá a ejecutar el callback, obtendrá una versión nueva y la almacenará nuevamente.</p>



<p class="wp-block-paragraph">También puedes eliminar manualmente una entrada:</p>



<pre class="wp-block-code"><code>Cache::forget('iconify.collection.mdi');</code></pre>



<p class="wp-block-paragraph">O limpiar todo el caché de la aplicación mediante Artisan:</p>



<pre class="wp-block-code"><code>php artisan cache:clear</code></pre>



<p class="wp-block-paragraph">Este último comando debe utilizarse con cuidado porque elimina todas las entradas del store de caché utilizado por la aplicación.</p>



<h2 class="wp-block-heading">Una regla práctica</h2>



<pre class="wp-block-preformatted">¿La información se consulta muchas veces?
        ↓ sí
¿Obtenerla es relativamente costoso?
        ↓ sí
¿Puede tolerar estar desactualizada algunos minutos u horas?
        ↓ sí
Cache::remember() es un buen candidato.</pre>



<h2 class="wp-block-heading">Conclusión</h2>



<p class="wp-block-paragraph"><code>Cache::remember()</code> es una de esas herramientas de Laravel que pueden parecer sencillas, pero generan una mejora importante cuando se utilizan en el escenario adecuado. Reduce consultas repetitivas, evita solicitudes innecesarias a servicios externos y ayuda a mejorar los tiempos de respuesta de una aplicación.</p>



<p class="wp-block-paragraph">La clave no está en almacenar todo en caché, sino en identificar qué información se consulta constantemente, qué tan costoso es obtenerla y durante cuánto tiempo puede mantenerse sin actualizarse.</p>



<p class="wp-block-paragraph">En Maria Tech desarrollamos soluciones de software a medida con Laravel y otras tecnologías, buscando que cada sistema sea mantenible, eficiente y preparado para crecer conforme lo requiera el negocio.</p>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-794e3cfa wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<div class="wp-block-buttons is-layout-flex wp-block-buttons-is-layout-flex">
<div class="wp-block-button"><a class="wp-block-button__link wp-element-button" href="https://wa.me/523414305984" target="_blank" rel="noreferrer noopener">(+52) 341-430-59-84</a></div>
</div>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<div class="wp-block-buttons is-layout-flex wp-block-buttons-is-layout-flex">
<div class="wp-block-button"><a class="wp-block-button__link wp-element-button" href="mailto:info@mariatech.com.mx" target="_blank" rel="noreferrer noopener">info@mariatech.com.mx</a></div>
</div>
</div>
</div>
]]></content:encoded>
					
					<wfw:commentRss>https://www.mariatech.com.mx/blog/laravel-php/cache-remember-laravel-como-funciona-cuando-usarlo/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Cómo versionar automáticamente tus archivos JS en Laravel (sin compilar nada)</title>
		<link>https://www.mariatech.com.mx/blog/javascript/como-versionar-automaticamente-tus-archivos-js/</link>
					<comments>https://www.mariatech.com.mx/blog/javascript/como-versionar-automaticamente-tus-archivos-js/#respond</comments>
		
		<dc:creator><![CDATA[Luis Duran]]></dc:creator>
		<pubDate>Sat, 12 Sep 2026 04:58:48 +0000</pubDate>
				<category><![CDATA[JavaScript]]></category>
		<guid isPermaLink="false">https://www.mariatech.com.mx/blog/?p=318</guid>

					<description><![CDATA[Si cambias un archivo JavaScript, lo subes a producción y tus usuarios siguen viendo el comportamiento anterior, no estás viendo un error de tu código: estás viendo la caché del navegador. Este artículo explica por qué pasa y cómo resolverlo en una línea de Blade, sin agregar procesos de compilación ni dependencias nuevas al proyecto. [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Si cambias un archivo JavaScript, lo subes a producción y tus usuarios siguen viendo el comportamiento anterior, no estás viendo un error de tu código: estás viendo la caché del navegador.</p>



<p class="wp-block-paragraph">Este artículo explica por qué pasa y cómo resolverlo en una línea de Blade, sin agregar procesos de compilación ni dependencias nuevas al proyecto.</p>



<h2 class="wp-block-heading">Por qué el navegador se queda con la copia vieja</h2>



<p class="wp-block-paragraph">Cuando el navegador descarga <code>https://tusitio.com/js/cart.js</code>, guarda una copia local. La próxima vez que necesite ese archivo, no tiene ninguna razón para volver a pedirlo: la dirección es la misma, así que asume que el contenido también.</p>



<p class="wp-block-paragraph">Ese comportamiento es deseable. Es lo que hace que tu sitio cargue rápido en la segunda visita. El problema es que el navegador no tiene forma de saber que tú cambiaste el archivo.</p>



<p class="wp-block-paragraph">La solución conocida es cambiar la dirección cuando cambia el contenido, agregando un parámetro de versión:</p>



<pre class="wp-block-code"><code>&lt;script src="/js/cart.js?v=1.0.0"&gt;&lt;/script&gt;
</code></pre>



<p class="wp-block-paragraph">Para el navegador, <code>cart.js?v=1.0.0</code> y <code>cart.js?v=1.0.1</code> son dos direcciones distintas. Al cambiar el número, descarga de nuevo.</p>



<h2 class="wp-block-heading">El detalle que hace que esto falle</h2>



<p class="wp-block-paragraph">Escrito así, el número se sube a mano. Y ahí está el punto débil: para que funcione, quien modifica el archivo tiene que acordarse de abrir <em>otro</em> archivo distinto, encontrar la línea correcta y cambiar un dígito, justo en el momento en que su cabeza está en el bug que acaba de arreglar.</p>



<p class="wp-block-paragraph">Es fácil que se pase por alto, y cuando se pasa, el síntoma aparece mucho después y en la computadora de otra persona. Nadie relaciona una cosa con la otra.</p>



<p class="wp-block-paragraph">En un proyecto que revisamos hace poco encontramos los tres estados conviviendo: un archivo con <code>?v=1.0.0</code>, otro con <code>?v=1.0.2</code> que llevaba varios cambios sin actualizar, y un tercero igual de crítico sin ningún parámetro.</p>



<p class="wp-block-paragraph">Vale la pena la distinción: el mecanismo estaba bien planteado. Lo que fallaba era que dependía de un paso manual.</p>



<h2 class="wp-block-heading">La solución: que el número lo calcule el sistema</h2>



<p class="wp-block-paragraph">PHP puede leer la fecha de última modificación de un archivo con <code>filemtime()</code>. Ese valor cambia solo cuando el archivo cambia. Es exactamente el número de versión que necesitamos.</p>



<pre class="wp-block-code"><code>&lt;script src="{{ asset('js/cart.js') }}?v={{ filemtime(public_path('js/cart.js')) }}"&gt;&lt;/script&gt;
</code></pre>



<p class="wp-block-paragraph">Al modificar <code>cart.js</code>, su fecha de modificación cambia, el parámetro <code>?v=</code> cambia y el navegador descarga la copia nueva. Si no lo modificas, el número se mantiene y el navegador sigue usando lo que ya tiene en caché, que es justo lo que queremos.</p>



<p class="wp-block-paragraph">Sin build. Sin Node. Sin recordar nada.</p>



<h3 class="wp-block-heading">Agrega la protección contra archivo faltante</h3>



<p class="wp-block-paragraph"><code>filemtime()</code> emite un warning y devuelve <code>false</code> si el archivo no existe. En producción el archivo siempre está, pero si tienes <code>APP_DEBUG=true</code> en local y renombras el archivo un momento, ese warning puede escalar a excepción y tumbarte la página con un mensaje que no apunta al verdadero problema.</p>



<p class="wp-block-paragraph">Se resuelve así:</p>



<pre class="wp-block-code"><code>&lt;script src="{{ asset('js/cart.js') }}?v={{ @filemtime(public_path('js/cart.js')) ?: '' }}"&gt;&lt;/script&gt;
</code></pre>



<p class="wp-block-paragraph">La arroba silencia el warning y el <code>?:</code> deja el parámetro vacío en lugar de imprimir <code>false</code>. El script sigue cargando.</p>



<h3 class="wp-block-heading">Si tienes varios archivos, usa un helper</h3>



<p class="wp-block-paragraph">Repetir la ruta dos veces por archivo se vuelve incómodo rápido. Una clase pequeña lo resuelve:</p>



<pre class="wp-block-code"><code>&lt;?php

namespace App\View;

class Asset
{
    /**
     * URL del asset con parámetro de versión derivado de su fecha de modificación.
     */
    public static function versioned(string $path): string
    {
        $version = @filemtime(public_path($path)) ?: '';

        return asset($path) . ($version ? '?v=' . $version : '');
    }
}
</code></pre>



<p class="wp-block-paragraph">Y en la plantilla:</p>



<pre class="wp-block-code"><code>&lt;script src="{{ \App\View\Asset::versioned('js/cart.js') }}"&gt;&lt;/script&gt;
&lt;script src="{{ \App\View\Asset::versioned('js/custom.js') }}"&gt;&lt;/script&gt;
&lt;link rel="stylesheet" href="{{ \App\View\Asset::versioned('css/custom.css') }}"&gt;
</code></pre>



<p class="wp-block-paragraph">Colocarla bajo <code>app/</code> te ahorra tocar <code>composer.json</code>, porque PSR-4 ya cubre ese directorio. Si prefieres una función global en <code>app/helpers.php</code>, funciona igual, pero recuerda que entonces necesitas registrarla en el autoload y ejecutar <code>composer dump-autoload</code> en cada despliegue.</p>



<h2 class="wp-block-heading">Cómo comprobar que funciona</h2>



<p class="wp-block-paragraph">No te quedes con que la URL se ve distinta. Verifica que el navegador realmente descargó el archivo.</p>



<ol class="wp-block-list">
<li>Abre la página y las herramientas de desarrollo, en la pestaña Red.</li>



<li>Localiza tu archivo JS y anota el valor de <code>?v=</code>.</li>



<li>Modifica el archivo en el servidor. Si solo quieres probar el mecanismo sin cambiar código, <code>touch public/js/cart.js</code> basta para actualizar su fecha.</li>



<li>Recarga con F5, sin vaciar caché a mano.</li>
</ol>



<p class="wp-block-paragraph">Deberías ver un <code>?v=</code> distinto. Pero lo importante es la columna Tamaño: si dice un valor en KB, el archivo se descargó; si dice &#8220;(memory cache)&#8221; o &#8220;(disk cache)&#8221;, se sirvió de la caché y algo no funcionó.</p>



<p class="wp-block-paragraph">Desde la consola también puedes verificarlo:</p>



<pre class="wp-block-code"><code>performance.getEntriesByType('resource')
  .filter(r =&gt; r.name.includes('cart.js'))
  .map(r =&gt; ({ url: r.name, bytes: r.transferSize, deCache: r.transferSize === 0 }));
</code></pre>



<p class="wp-block-paragraph">Si <code>transferSize</code> es mayor que cero, el archivo vino de la red.</p>



<p class="wp-block-paragraph">Comprueba también que al modificar un archivo solo cambie el parámetro de <em>ese</em> archivo. Cada uno lleva su propio control, y eso evita descargas innecesarias de los demás.</p>



<h2 class="wp-block-heading">Un detalle sobre tu despliegue</h2>



<p class="wp-block-paragraph"><code>git checkout</code> y <code>git pull</code> no preservan la fecha de modificación de los archivos. Si despliegas así, el parámetro de versión va a cambiar en cada despliegue, incluso para archivos que no tocaste.</p>



<p class="wp-block-paragraph">No es un problema: el usuario descarga unos KB de más una vez por despliegue y nunca recibe una copia desactualizada, que es lo que importa. Si quieres que el parámetro cambie estrictamente cuando cambia el contenido, sustituye <code>filemtime()</code> por un hash:</p>



<pre class="wp-block-code"><code>$version = substr(md5_file(public_path($path)), 0, 12);
</code></pre>



<p class="wp-block-paragraph">El costo es leer el archivo completo en cada petición en lugar de consultar solo sus metadatos. Para archivos chicos es despreciable; si te preocupa, guárdalo en caché.</p>



<h2 class="wp-block-heading">¿Y cuándo conviene compilar con Vite o Mix?</h2>



<p class="wp-block-paragraph">Si tu proyecto ya tiene un proceso de compilación funcionando, úsalo: <code>@vite</code> y <code>mix()</code> resuelven esto con un hash del contenido y además te dan minificación y empaquetado.</p>



<p class="wp-block-paragraph">Esta solución está pensada para un escenario distinto y bastante común: proyectos donde el JavaScript vive en <code>public/</code>, se edita directamente y no hay pipeline montado. Ahí, incorporar los archivos a un proceso de compilación es una migración con su propio trabajo y sus propios riesgos, mientras que el problema de la caché es urgente hoy.</p>



<p class="wp-block-paragraph">Un punto a considerar antes de decidirte por compilar: <code>mix()</code> lanza una excepción si el manifiesto está desincronizado con los archivos. Si esa llamada vive en una plantilla compartida, un despliegue sin compilar no te rompe una sección, te deja el sitio entero en blanco. Vale la pena tenerlo previsto en tu proceso de despliegue.</p>



<p class="wp-block-paragraph">Las dos soluciones pueden convivir. Puedes resolver la caché hoy con esta línea y migrar a un pipeline completo cuando el proyecto lo pida, sin prisa y con todos los assets a la vez.</p>



<h2 class="wp-block-heading">Resumen</h2>



<ul class="wp-block-list">
<li>Agrega <code>?v={{ @filemtime(public_path($ruta)) ?: '' }}</code> a tus etiquetas de script y estilo.</li>



<li>Si son varios archivos, encapsúlalo en un helper bajo <code>app/</code>.</li>



<li>Verifica en la pestaña Red que el archivo se descargue, no solo que la URL cambie.</li>



<li>Revisa si tienes otros mecanismos que dependan de un paso manual: son los que fallan en silencio.</li>
</ul>



<p class="wp-block-paragraph"></p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.mariatech.com.mx/blog/javascript/como-versionar-automaticamente-tus-archivos-js/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Por qué deberías probar las relaciones entre tus modelos (y no solo la lógica de negocio)</title>
		<link>https://www.mariatech.com.mx/blog/laravel-php/por-que-deberias-probar-las-relaciones-entre-tus-modelos-y-no-solo-la-logica-de-negocio/</link>
					<comments>https://www.mariatech.com.mx/blog/laravel-php/por-que-deberias-probar-las-relaciones-entre-tus-modelos-y-no-solo-la-logica-de-negocio/#respond</comments>
		
		<dc:creator><![CDATA[Luis Duran]]></dc:creator>
		<pubDate>Tue, 04 Aug 2026 00:08:04 +0000</pubDate>
				<category><![CDATA[Laravel / PHP]]></category>
		<category><![CDATA[MySQL]]></category>
		<guid isPermaLink="false">https://www.mariatech.com.mx/blog/?p=314</guid>

					<description><![CDATA[Cuando pensamos en escribir pruebas automatizadas, lo primero que suele venir a la mente son las reglas de negocio: que un cálculo dé el resultado correcto, que una validación rechace lo que debe rechazar, que un flujo termine en el estado esperado. Todo eso es importante. Pero hay una capa más silenciosa, que rara vez [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Cuando pensamos en escribir pruebas automatizadas, lo primero que suele venir a la mente son las reglas de negocio: que un cálculo dé el resultado correcto, que una validación rechace lo que debe rechazar, que un flujo termine en el estado esperado. Todo eso es importante. Pero hay una capa más silenciosa, que rara vez recibe atención y que, cuando falla, produce los errores más difíciles de diagnosticar: las relaciones entre modelos.</p>



<p class="wp-block-paragraph">En este artículo vamos a ver por qué vale la pena dedicarles pruebas propias, qué conviene verificar exactamente, y cómo escribirlas y ejecutarlas paso a paso. Los ejemplos usan Eloquent, el ORM de Laravel, pero las ideas se trasladan a cualquier ORM con relaciones.</p>



<h2 class="wp-block-heading">El problema: las relaciones fallan en silencio</h2>



<p class="wp-block-paragraph">Una relación mal definida no suele lanzar un error inmediato. El código compila, la aplicación arranca, y todo parece funcionar hasta que, en algún punto, una consulta devuelve una colección vacía donde debería haber datos, o carga el registro equivocado.</p>



<p class="wp-block-paragraph">El caso más común aparece cuando el nombre de la clave foránea no sigue la convención que el ORM espera. Eloquent asume un patrón de nombres por defecto: para una relación hacia una tabla <code>documentos</code>, busca una columna <code>documento_id</code>. Si tu base de datos usa otra convención, el ORM buscará una columna que no existe y la relación devolverá vacío, sin quejarse.</p>



<p class="wp-block-paragraph">Este tipo de fallo es especialmente traicionero porque no se manifiesta donde está el error, sino mucho más adelante, en una vista que no muestra datos o en un reporte con totales incompletos. Una prueba de relación lo atrapa en el acto.</p>



<h2 class="wp-block-heading">Dónde vive la prueba</h2>



<p class="wp-block-paragraph">En Laravel, las pruebas viven en el directorio <code>tests/</code>, dividido en dos carpetas:</p>



<ul class="wp-block-list">
<li><code>tests/Unit/</code> para pruebas que no tocan la base de datos.</li>



<li><code>tests/Feature/</code> para las que sí.</li>
</ul>



<p class="wp-block-paragraph">Las pruebas de relaciones van en <code>tests/Feature/</code>, porque necesitan crear registros y consultarlos.</p>



<p class="wp-block-paragraph">Para crear el archivo:</p>



<pre class="wp-block-code"><code>php artisan make:test DocumentoRelacionesTest
</code></pre>



<p class="wp-block-paragraph">Eso genera <code>tests/Feature/DocumentoRelacionesTest.php</code>.</p>



<h2 class="wp-block-heading">El esqueleto de la clase</h2>



<pre class="wp-block-code"><code>&lt;?php

namespace Tests\Feature;

use App\Models\Documento;
use App\Models\Impuesto;
use App\Models\LineaDeDetalle;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

class DocumentoRelacionesTest extends TestCase
{
    use RefreshDatabase;

    // Aquí van los casos
}
</code></pre>



<p class="wp-block-paragraph"><code>RefreshDatabase</code> es la pieza clave: ejecuta las migraciones antes de la suite y envuelve cada prueba en una transacción que se revierte al terminar. Así cada caso arranca con una base limpia y no puedes ensuciar datos reales por accidente.</p>



<p class="wp-block-paragraph">Cada caso es un método público cuyo nombre empieza con <code>test</code>, o que lleva el atributo <code>#[Test]</code> si prefieres nombres sin prefijo.</p>



<h2 class="wp-block-heading">Los cuatro casos que conviene cubrir</h2>



<h3 class="wp-block-heading">1. Que la relación carga en ambos sentidos</h3>



<p class="wp-block-paragraph">Las relaciones tienen dos extremos y ambos pueden estar mal definidos de forma independiente. Si un padre tiene muchos hijos, verifica que desde el padre llegas a todos sus hijos, y que desde un hijo llegas a su padre.</p>



<p class="wp-block-paragraph">Aquí es donde las factories hacen el trabajo pesado. Una factory es una receta para crear registros de prueba con valores realistas y, bien configurada, encadena automáticamente la creación de los registros de los que depende:</p>



<pre class="wp-block-code"><code>public function test_la_linea_llega_a_su_documento()
{
    // Crea la línea y, de forma automática, su documento padre
    $linea = LineaDeDetalle::factory()-&gt;create();

    $this-&gt;assertNotNull($linea-&gt;documento);
    $this-&gt;assertSame($linea-&gt;id_documento, $linea-&gt;documento-&gt;id);
}

public function test_el_documento_ve_todas_sus_lineas()
{
    $documento = Documento::factory()-&gt;create();

    LineaDeDetalle::factory()-&gt;count(3)-&gt;create(&#091;
        'id_documento' =&gt; $documento-&gt;id,
    ]);

    $this-&gt;assertCount(3, $documento-&gt;lineas);
}
</code></pre>



<p class="wp-block-paragraph">Nota el <code>fresh()</code> que no aparece aquí y sí en otros casos: <code>$documento-&gt;lineas</code> se carga por primera vez en el momento de la aserción, así que todavía no hay nada en caché. Si hubieras accedido a la relación <em>antes</em> de crear las líneas, Eloquent devolvería la colección vacía que ya tenía guardada.</p>



<h3 class="wp-block-heading">2. Que la carga anticipada funciona</h3>



<p class="wp-block-paragraph">La carga anticipada, o <em>eager loading</em>, evita el problema de las N+1 consultas: en lugar de una consulta por cada registro relacionado, se hacen dos en total. Es fácil creer que la tienes y que no esté surtiendo efecto.</p>



<p class="wp-block-paragraph">La forma de comprobarlo es contar las consultas que realmente se ejecutan:</p>



<pre class="wp-block-code"><code>use Illuminate\Support\Facades\DB;

public function test_la_carga_anticipada_evita_consultas_por_cada_linea()
{
    Documento::factory()-&gt;count(5)-&gt;hasLineas(3)-&gt;create();

    DB::enableQueryLog();

    Documento::with('lineas')-&gt;get()-&gt;each(
        fn ($documento) =&gt; $documento-&gt;lineas-&gt;count()
    );

    // Una consulta para documentos, una para todas las líneas
    $this-&gt;assertCount(2, DB::getQueryLog());
}
</code></pre>



<p class="wp-block-paragraph">Si quitas el <code>with('lineas')</code>, esta prueba falla con seis consultas en vez de dos. Esa es la señal de que la aserción está midiendo lo que crees.</p>



<h3 class="wp-block-heading">3. Que el borrado se comporta como esperas</h3>



<p class="wp-block-paragraph">Si configuraste que al borrar un padre se borren sus hijos en cascada, pruébalo:</p>



<pre class="wp-block-code"><code>public function test_al_borrar_el_documento_se_borran_sus_lineas()
{
    $documento = Documento::factory()-&gt;hasLineas(2)-&gt;create();
    $idsLineas = $documento-&gt;lineas-&gt;pluck('id');

    $documento-&gt;delete();

    foreach ($idsLineas as $id) {
        $this-&gt;assertDatabaseMissing('lineas_de_detalle', &#091;'id' =&gt; $id]);
    }
}
</code></pre>



<p class="wp-block-paragraph">Aquí hay un detalle que conviene conocer: si la cascada está definida en la base de datos con <code>onDelete('cascade')</code>, el comportamiento depende del motor. Las pruebas de Laravel suelen correr sobre SQLite en memoria por velocidad, y SQLite no aplica claves foráneas a menos que se activen explícitamente. Una prueba que pasa en tu entorno podría no reflejar lo que ocurre en producción si los motores difieren.</p>



<p class="wp-block-paragraph">Puedes verificar contra qué motor estás corriendo:</p>



<pre class="wp-block-code"><code>php artisan test --filter=test_al_borrar_el_documento -v
</code></pre>



<p class="wp-block-paragraph">Y si quieres que las pruebas usen el mismo motor que producción, configúralo en <code>phpunit.xml</code>:</p>



<pre class="wp-block-code"><code>&lt;env name="DB_CONNECTION" value="mysql"/&gt;
&lt;env name="DB_DATABASE" value="almcns_testing"/&gt;
</code></pre>



<p class="wp-block-paragraph">Es más lento, pero prueba lo que de verdad va a ocurrir.</p>



<h3 class="wp-block-heading">4. Que los datos de captura histórica se conservan</h3>



<p class="wp-block-paragraph">Este es sutil y muy valioso. A veces guardas una copia de un dato en el momento de una transacción, por ejemplo el nombre y el precio de un producto tal como estaban ese día, en lugar de depender siempre del registro vivo. Una prueba puede confirmar que si el registro original cambia después, la copia guardada conserva su valor:</p>



<pre class="wp-block-code"><code>public function test_la_linea_conserva_el_precio_del_momento_de_captura()
{
    $producto = Producto::factory()-&gt;create(&#091;'precio' =&gt; 100.00]);

    $linea = LineaDeDetalle::factory()-&gt;create(&#091;
        'id_producto' =&gt; $producto-&gt;id,
        'precio'      =&gt; $producto-&gt;precio,
    ]);

    $producto-&gt;update(&#091;'precio' =&gt; 250.00]);

    $this-&gt;assertSame('100.00', $linea-&gt;fresh()-&gt;precio);
    $this-&gt;assertSame('250.00', $linea-&gt;producto-&gt;fresh()-&gt;precio);
}
</code></pre>



<p class="wp-block-paragraph">Las dos aserciones juntas son las que dan el valor: confirman que la copia no se movió <em>y</em> que la relación hacia el registro vivo sigue funcionando. Es la diferencia entre un histórico confiable y uno que se reescribe solo.</p>



<p class="wp-block-paragraph">El <code>fresh()</code> aquí no es decorativo. Sin él estarías consultando el objeto que ya tienes en memoria, no lo que quedó guardado, y la prueba pasaría incluso si la base de datos dijera otra cosa.</p>



<h2 class="wp-block-heading">Cómo ejecutarlas</h2>



<p class="wp-block-paragraph">Las pruebas se ejecutan en la terminal, desde la raíz del proyecto. No hay nada que abrir en el navegador.</p>



<p class="wp-block-paragraph">Toda la suite:</p>



<pre class="wp-block-code"><code>php artisan test
</code></pre>



<p class="wp-block-paragraph">Solo tu archivo:</p>



<pre class="wp-block-code"><code>php artisan test tests/Feature/DocumentoRelacionesTest.php
</code></pre>



<p class="wp-block-paragraph">Un solo caso, por nombre:</p>



<pre class="wp-block-code"><code>php artisan test --filter=test_la_linea_conserva_el_precio_del_momento_de_captura
</code></pre>



<p class="wp-block-paragraph">Una salida exitosa se ve así:</p>



<pre class="wp-block-preformatted"> <code>  PASS  Tests\Feature\DocumentoRelacionesTest
  ✓ la linea llega a su documento                        0.34s
  ✓ el documento ve todas sus lineas                     0.08s
  ✓ la carga anticipada evita consultas por cada linea   0.11s
  ✓ al borrar el documento se borran sus lineas          0.09s
  ✓ la linea conserva el precio del momento de captura   0.10s

  Tests:    5 passed (12 assertions)
  Duration: 0.72s
</code></pre>



<p class="wp-block-paragraph">Cuando algo falla, la salida te dice qué esperaba y qué obtuvo:</p>



<pre class="wp-block-preformatted"> <code> ⨯ el documento ve todas sus lineas

  Failed asserting that actual size 0 matches expected size 3.

  at tests/Feature/DocumentoRelacionesTest.php:31
</code></pre>



<p class="wp-block-paragraph">Ese &#8220;size 0&#8221; es exactamente el síntoma del que hablábamos al principio: la relación devuelve vacío sin lanzar ningún error. La diferencia es que ahora aparece en tu terminal en menos de un segundo, y no en un reporte de producción dentro de tres semanas.</p>



<p class="wp-block-paragraph">Si prefieres no escribir el comando cada vez, puedes dejarlas corriendo en modo observador:</p>



<pre class="wp-block-code"><code>php artisan test --watch
</code></pre>



<h2 class="wp-block-heading">Un consejo que ahorra dolores de cabeza</h2>



<p class="wp-block-paragraph">Si tu base de datos no usa la convención de nombres por defecto del ORM, declara la clave foránea de forma explícita en cada relación. No dependas de que el framework la adivine:</p>



<pre class="wp-block-code"><code>public function lineas()
{
    return $this-&gt;hasMany(LineaDeDetalle::class, 'id_documento', 'id');
}
</code></pre>



<p class="wp-block-paragraph">Es un argumento más por relación y elimina de raíz toda una categoría de errores silenciosos.</p>



<h2 class="wp-block-heading">Una red de seguridad para refactorizaciones</h2>



<p class="wp-block-paragraph">El mayor valor de estas pruebas aparece cuando tienes que modificar la estructura de datos. Agregar tablas, normalizar información que antes estaba mezclada, cambiar cómo se conectan las entidades: son cambios que tocan muchas piezas a la vez y donde es fácil romper una relación sin darte cuenta.</p>



<p class="wp-block-paragraph">Con un conjunto de pruebas de relaciones en su lugar, cada cambio se valida solo. Si una modificación rompe la conexión entre dos modelos, lo sabes en segundos, no cuando un usuario reporta que un reporte sale vacío. Esa retroalimentación inmediata es lo que convierte una refactorización arriesgada en una rutina controlada.</p>



<h2 class="wp-block-heading">En resumen</h2>



<p class="wp-block-paragraph">Las relaciones entre modelos son la fontanería de una aplicación: invisibles cuando funcionan, catastróficas cuando fallan. Dedicarles pruebas propias no lleva mucho tiempo, sobre todo apoyándote en factories que preparen los datos, y a cambio obtienes una red de seguridad que atrapa una clase de errores que de otro modo llegarían hasta el usuario final.</p>



<p class="wp-block-paragraph">La próxima vez que definas una relación, considera escribir junto a ella una prueba breve que confirme que carga bien, en ambos sentidos. Tu yo del futuro, en medio de una refactorización, te lo agradecerá.</p>



<p class="wp-block-paragraph"></p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.mariatech.com.mx/blog/laravel-php/por-que-deberias-probar-las-relaciones-entre-tus-modelos-y-no-solo-la-logica-de-negocio/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Arquitectura de un proyecto de Laravel</title>
		<link>https://www.mariatech.com.mx/blog/laravel-php/arquitectura-de-un-proyecto-de-laravel/</link>
					<comments>https://www.mariatech.com.mx/blog/laravel-php/arquitectura-de-un-proyecto-de-laravel/#respond</comments>
		
		<dc:creator><![CDATA[Victor Santillan]]></dc:creator>
		<pubDate>Tue, 14 Jul 2026 23:15:07 +0000</pubDate>
				<category><![CDATA[Laravel / PHP]]></category>
		<guid isPermaLink="false">https://www.mariatech.com.mx/blog/?p=311</guid>

					<description><![CDATA[Conoce una buena práctica para separar tu código en proyectos de Laravel...]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Cuando un proyecto Laravel empieza a crecer, una de las primeras señales de desorden aparece en los controladores: métodos cada vez más largos, validaciones mezcladas con consultas, reglas de negocio dentro del mismo archivo y operaciones repetidas en varias partes del sistema.</p>



<p class="wp-block-paragraph">Al inicio puede parecer práctico resolver todo dentro del controller, pero con el tiempo esa decisión vuelve más difícil mantener, probar y escalar el proyecto. Por eso conviene separar responsabilidades entre <strong>Controllers</strong>, <strong>Form Requests</strong>, <strong>Services</strong> y <strong>Models</strong>.</p>



<p class="wp-block-paragraph">La idea no es complicar la arquitectura, sino poner cada pieza en el lugar correcto.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Resumen rápido</h2>



<p class="wp-block-paragraph">Una forma sencilla de entenderlo es esta:</p>



<figure class="wp-block-table"><table><thead><tr><th>Elemento</th><th>Responsabilidad principal</th></tr></thead><tbody><tr><td>Controller</td><td>Controla el flujo HTTP: recibe la petición, llama lo necesario y responde.</td></tr><tr><td>Form Request</td><td>Valida y prepara los datos antes de llegar al controller.</td></tr><tr><td>Service</td><td>Ejecuta la lógica de negocio o procesos importantes del sistema.</td></tr><tr><td>Model</td><td>Representa una tabla, relaciones, atributos, scopes y comportamiento simple de la entidad.</td></tr></tbody></table></figure>



<p class="wp-block-paragraph">En otras palabras:</p>



<pre class="wp-block-code"><code>Request
  ↓
Form Request
  ↓
Controller
  ↓
Service
  ↓
Model / Base de datos
  ↓
Response</code></pre>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Qué es un Controller en Laravel</h2>



<p class="wp-block-paragraph">Un controller agrupa la lógica que responde a peticiones HTTP relacionadas con un recurso. Laravel recomienda usarlos para organizar el manejo de solicitudes que, de otra forma, terminarían como closures dentro de las rutas.</p>



<p class="wp-block-paragraph">El controller debe actuar como coordinador. No debería cargar toda la lógica de negocio del sistema.</p>



<h3 class="wp-block-heading">Responsabilidades recomendadas de un controller</h3>



<ul class="wp-block-list">
<li>Recibir la petición.</li>



<li>Usar un Form Request para validar.</li>



<li>Buscar un modelo cuando sea algo simple.</li>



<li>Llamar un Service cuando haya lógica de negocio.</li>



<li>Regresar una vista, redirect o respuesta JSON.</li>
</ul>



<h3 class="wp-block-heading">Ejemplo de controller limpio</h3>



<pre class="wp-block-code"><code>public function update(UpdateProductRequest $request, string $product)
{
    $product = Product::find($product);

    $product = $this-&gt;productService-&gt;update(
        $product,
        $request-&gt;validated()
    );

    return response()-&gt;json(&#91;
        'success' =&gt; true,
        'message' =&gt; 'Producto actualizado correctamente.',
        'product' =&gt; &#91;
            'id' =&gt; $product-&gt;id,
            'name' =&gt; $product-&gt;name,
        ],
    ]);
}</code></pre>



<p class="wp-block-paragraph">Este controller no sabe todos los detalles de cómo se actualiza el producto. Solo controla el flujo.</p>



<p class="wp-block-paragraph">Eso lo hace más fácil de leer, mantener y modificar.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Qué es un Form Request</h2>



<p class="wp-block-paragraph">Un Form Request es una clase especializada para validar y autorizar datos de entrada. Laravel permite crear estos archivos para encapsular validación y autorización en un lugar separado del controller.</p>



<p class="wp-block-paragraph">Esto evita que el controller se llene de reglas como <code>required</code>, <code>exists</code>, <code>max</code>, <code>email</code>, validaciones condicionales o preparación de datos.</p>



<h3 class="wp-block-heading">Responsabilidades recomendadas de un Form Request</h3>



<ul class="wp-block-list">
<li>Validar campos requeridos.</li>



<li>Validar formatos.</li>



<li>Validar existencia de registros en base de datos.</li>



<li>Preparar datos antes de validar.</li>



<li>Autorizar si el usuario puede ejecutar la acción.</li>
</ul>



<h3 class="wp-block-heading">Ejemplo de Form Request</h3>



<pre class="wp-block-code"><code>class UpdateProductRequest extends FormRequest
{
    public function rules(): array
    {
        return &#91;
            'product_id' =&gt; &#91;'required', 'integer', 'exists:products,id'],
            'name' =&gt; &#91;'required', 'string', 'max:255'],
            'price' =&gt; &#91;'required', 'numeric', 'min:0'],
        ];
    }

    public function authorize(): bool
    {
        return true;
    }
}</code></pre>



<p class="wp-block-paragraph">El controller ya no necesita preocuparse por esa preparación.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Qué es un Service</h2>



<p class="wp-block-paragraph">Un Service es una clase donde se concentra la lógica de negocio o los procesos importantes del sistema. Aunque Laravel no obliga a crear una carpeta <code>Services</code>, el framework sí facilita este patrón gracias a su Service Container y a la inyección de dependencias.</p>



<p class="wp-block-paragraph">La pregunta clave para decidir si algo va a un Service es:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">¿Esto solo controla una petición HTTP o representa una operación importante del negocio?</p>
</blockquote>



<p class="wp-block-paragraph">Si representa una operación del negocio, normalmente conviene moverlo a un Service.</p>



<h3 class="wp-block-heading">Responsabilidades recomendadas de un Service</h3>



<ul class="wp-block-list">
<li>Crear registros.</li>



<li>Actualizar registros.</li>



<li>Eliminar registros.</li>



<li>Ejecutar transacciones.</li>



<li>Aplicar reglas de negocio.</li>



<li>Coordinar varias tablas.</li>



<li>Calcular importes, descuentos, comisiones o estados.</li>



<li>Consumir APIs externas.</li>



<li>Disparar notificaciones, eventos o procesos relacionados.</li>
</ul>



<h3 class="wp-block-heading">Ejemplo de Service</h3>



<pre class="wp-block-code"><code>class ProductService
{
    public function create(array $data): Product
    {
        return DB::transaction(function () use ($data) {
            return Product::create(&#91;
                'name' =&gt; $data&#91;'name'],
                'description' =&gt; $data&#91;'description'] ?? null,
                'price' =&gt; $data&#91;'price'],
                'enabled' =&gt; $data&#91;'enabled'] ?? true,
            ]);
        });
    }

    public function update(Product $product, array $data): Product
    {
        return DB::transaction(function () use ($product, $data) {
            $product-&gt;name = $data&#91;'name'];
            $product-&gt;description = $data&#91;'description'] ?? null;
            $product-&gt;price = $data&#91;'price'];
            $product-&gt;enabled = $data&#91;'enabled'] ?? $product-&gt;enabled;
            $product-&gt;save();

            return $product-&gt;fresh();
        });
    }

    public function delete(Product $product): void
    {
        DB::transaction(function () use ($product) {
            $product-&gt;delete();
        });
    }
}</code></pre>



<p class="wp-block-paragraph">Aunque algunos métodos parezcan simples al inicio, tenerlos en un Service permite que crezcan ordenadamente. Hoy un <code>delete()</code> puede borrar un registro; mañana puede validar permisos internos, eliminar relaciones, registrar historial y disparar una notificación.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Qué es un Model en Laravel</h2>



<p class="wp-block-paragraph">Un Model representa una tabla de base de datos dentro de Eloquent. Laravel indica que cada tabla suele tener un modelo correspondiente, y que estos modelos permiten consultar, insertar, actualizar y eliminar registros.</p>



<p class="wp-block-paragraph">Pero eso no significa que toda la lógica de negocio deba vivir dentro del Model.</p>



<h3 class="wp-block-heading">Responsabilidades recomendadas de un Model</h3>



<ul class="wp-block-list">
<li>Definir la tabla si no sigue la convención.</li>



<li>Definir campos <code>fillable</code> o <code>guarded</code>.</li>



<li>Definir relaciones.</li>



<li>Definir casts.</li>



<li>Crear accessors y mutators.</li>



<li>Crear scopes reutilizables.</li>



<li>Agregar métodos simples propios de la entidad.</li>
</ul>



<h3 class="wp-block-heading">Ejemplo de Model</h3>



<pre class="wp-block-code"><code>class Product extends Model
{
    protected $fillable = &#91;
        'name',
        'description',
        'price',
        'enabled',
    ];

    protected function casts(): array
    {
        return &#91;
            'price' =&gt; 'decimal:2',
            'enabled' =&gt; 'boolean',
        ];
    }

    public function scopeEnabled($query)
    {
        return $query-&gt;where('enabled', true);
    }

    public function isAvailable(): bool
    {
        return $this-&gt;enabled === true;
    }
}</code></pre>



<p class="wp-block-paragraph">Este tipo de lógica sí pertenece al modelo porque describe el comportamiento directo de la entidad <code>Product</code>.</p>



<p class="wp-block-paragraph">En cambio, si el proceso implica cobrar, registrar historial, enviar notificaciones y actualizar varias tablas, eso ya debería ir a un Service.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Cuándo usar cada uno</h2>



<h3 class="wp-block-heading">Usa Controller cuando&#8230;</h3>



<ul class="wp-block-list">
<li>Necesitas recibir una petición HTTP.</li>



<li>Necesitas devolver una vista.</li>



<li>Necesitas devolver JSON.</li>



<li>Necesitas redireccionar.</li>



<li>La acción solo coordina otras piezas.</li>
</ul>



<pre class="wp-block-code"><code>public function index()
{
    $products = Product::enabled()
        -&gt;orderBy('name')
        -&gt;paginate(20);

    return view('products.index', compact('products'));
}</code></pre>



<p class="wp-block-paragraph">Una consulta sencilla para mostrar datos puede quedarse en el controller.</p>



<h3 class="wp-block-heading">Usa Form Request cuando&#8230;</h3>



<ul class="wp-block-list">
<li>Hay reglas de validación.</li>



<li>Hay autorización de la acción.</li>



<li>Necesitas preparar datos antes de validarlos.</li>



<li>Quieres evitar que el controller se llene de reglas.</li>
</ul>



<pre class="wp-block-code"><code>public function rules(): array
{
    return &#91;
        'name' =&gt; &#91;'required', 'string', 'max:255'],
        'email' =&gt; &#91;'required', 'email'],
    ];
}</code></pre>



<h3 class="wp-block-heading">Usa Service cuando&#8230;</h3>



<ul class="wp-block-list">
<li>La acción crea, actualiza o elimina información importante.</li>



<li>La acción toca varias tablas.</li>



<li>La lógica se reutilizará desde varios lugares.</li>



<li>Hay transacciones.</li>



<li>Hay integración con APIs externas.</li>



<li>Hay reglas de negocio.</li>
</ul>



<pre class="wp-block-code"><code>$order = $this-&gt;orderService-&gt;create($request-&gt;validated());</code></pre>



<h3 class="wp-block-heading">Usa Model cuando&#8230;</h3>



<ul class="wp-block-list">
<li>Necesitas definir relaciones.</li>



<li>Necesitas casts.</li>



<li>Necesitas scopes.</li>



<li>Necesitas accessors o mutators.</li>



<li>Necesitas métodos simples de la entidad.</li>
</ul>



<pre class="wp-block-code"><code>public function scopeEnabled($query)
{
    return $query-&gt;where('enabled', true);
}</code></pre>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Ejemplo completo: creación de una orden</h2>



<p class="wp-block-paragraph">Supongamos que tenemos un sistema donde un cliente puede crear una orden. La orden debe guardar productos, calcular total, registrar pagos pendientes y devolver una respuesta.</p>



<p class="wp-block-paragraph">Si todo se mete en el controller, el método puede volverse enorme. Una mejor separación sería:</p>



<h3 class="wp-block-heading">Ruta</h3>



<pre class="wp-block-code"><code>Route::post('/orders', &#91;OrderController::class, 'store'])
    -&gt;name('orders.store');</code></pre>



<h3 class="wp-block-heading">Controller</h3>



<pre class="wp-block-code"><code>class OrderController extends Controller
{
    public function __construct(
        private readonly OrderService $orderService
    ) {}

    public function store(StoreOrderRequest $request)
    {
        $order = $this-&gt;orderService-&gt;create($request-&gt;validated());

        return response()-&gt;json(&#91;
            'success' =&gt; true,
            'message' =&gt; 'Orden creada correctamente.',
            'order' =&gt; &#91;
                'id' =&gt; $order-&gt;id,
                'total' =&gt; $order-&gt;total,
            ],
        ]);
    }
}</code></pre>



<h3 class="wp-block-heading">Form Request</h3>



<pre class="wp-block-code"><code>class StoreOrderRequest extends FormRequest
{
    public function rules(): array
    {
        return &#91;
            'customer_name' =&gt; &#91;'required', 'string', 'max:255'],
            'items' =&gt; &#91;'required', 'array', 'min:1'],
            'items.*.product_id' =&gt; &#91;'required', 'integer', 'exists:products,id'],
            'items.*.quantity' =&gt; &#91;'required', 'integer', 'min:1'],
        ];
    }
}</code></pre>



<h3 class="wp-block-heading">Service</h3>



<pre class="wp-block-code"><code>class OrderService
{
    public function create(array $data): Order
    {
        return DB::transaction(function () use ($data) {
            $order = Order::create(&#91;
                'customer_name' =&gt; $data&#91;'customer_name'],
                'total' =&gt; 0,
                'status' =&gt; 'pending',
            ]);

            $total = 0;

            foreach ($data&#91;'items'] as $item) {
                $product = Product::findOrFail($item&#91;'product_id']);
                $subtotal = $product-&gt;price * $item&#91;'quantity'];

                $order-&gt;items()-&gt;create(&#91;
                    'product_id' =&gt; $product-&gt;id,
                    'quantity' =&gt; $item&#91;'quantity'],
                    'unit_price' =&gt; $product-&gt;price,
                    'subtotal' =&gt; $subtotal,
                ]);

                $total += $subtotal;
            }

            $order-&gt;update(&#91;
                'total' =&gt; $total,
            ]);

            return $order-&gt;fresh();
        });
    }
}</code></pre>



<h3 class="wp-block-heading">Model</h3>



<pre class="wp-block-code"><code>class Order extends Model
{
    protected $fillable = &#91;
        'customer_name',
        'total',
        'status',
    ];

    protected function casts(): array
    {
        return &#91;
            'total' =&gt; 'decimal:2',
        ];
    }

    public function items()
    {
        return $this-&gt;hasMany(OrderItem::class);
    }

    public function isPending(): bool
    {
        return $this-&gt;status === 'pending';
    }
}</code></pre>



<p class="wp-block-paragraph">Con esta estructura, cada clase tiene una responsabilidad clara.</p>



<p class="wp-block-paragraph">El controller no calcula totales. El Form Request no guarda datos. El Service no genera respuestas HTTP. El Model no se encarga de coordinar todo el proceso.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Errores comunes al separar responsabilidades</h2>



<h3 class="wp-block-heading">1. Crear Services para todo</h3>



<p class="wp-block-paragraph">No todas las acciones necesitan un Service. Si solo vas a mostrar una vista con una consulta sencilla, el controller puede hacerlo.</p>



<h3 class="wp-block-heading">2. Meter validaciones en el Service</h3>



<p class="wp-block-paragraph">La validación de entrada normalmente debe vivir en un Form Request. El Service debe recibir datos ya validados y ejecutar el proceso.</p>



<h3 class="wp-block-heading">3. Regresar respuestas HTTP desde el Service</h3>



<p class="wp-block-paragraph">El Service no debería regresar <code>response()-&gt;json()</code> ni redirects. Eso le corresponde al controller.</p>



<h3 class="wp-block-heading">4. Convertir el Model en una clase gigante</h3>



<p class="wp-block-paragraph">El Model debe representar la entidad, pero no coordinar procesos completos del sistema. Si una operación toca varias entidades o APIs externas, probablemente debe vivir en un Service.</p>



<h3 class="wp-block-heading">5. No usar transacciones en procesos críticos</h3>



<p class="wp-block-paragraph">Cuando una operación crea o actualiza varios registros relacionados, conviene usar transacciones para evitar datos incompletos si algo falla a mitad del proceso.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Regla práctica para proyectos Laravel</h2>



<p class="wp-block-paragraph">Una convención sencilla para mantener el orden es esta:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">Si una acción crea, actualiza o elimina información importante de la base de datos, o representa un proceso del negocio, la lógica principal debe ir en un Service.</p>
</blockquote>



<p class="wp-block-paragraph">Esto no significa que el controller nunca pueda consultar modelos. Para listados simples, vistas de edición o pantallas informativas, puede ser totalmente válido.</p>



<p class="wp-block-paragraph">Pero cuando el método empieza a tener transacciones, ciclos, cálculos, integraciones, actualizaciones en varias tablas o condiciones del negocio, es momento de mover esa lógica a un Service.</p>



<h3 class="wp-block-heading">Guía rápida</h3>



<figure class="wp-block-table"><table><thead><tr><th>Situación</th><th>Dónde colocarlo</th></tr></thead><tbody><tr><td>Mostrar una lista simple</td><td>Controller</td></tr><tr><td>Validar campos de formulario</td><td>Form Request</td></tr><tr><td>Crear un registro con reglas internas</td><td>Service</td></tr><tr><td>Actualizar varias tablas</td><td>Service</td></tr><tr><td>Consumir una API externa</td><td>Service</td></tr><tr><td>Definir relación belongsTo</td><td>Model</td></tr><tr><td>Crear accessor o cast</td><td>Model</td></tr><tr><td>Regresar JSON</td><td>Controller</td></tr><tr><td>Redireccionar a otra ruta</td><td>Controller</td></tr></tbody></table></figure>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Conclusión</h2>



<p class="wp-block-paragraph">Separar Controllers, Form Requests, Services y Models no es solo una cuestión de estilo. Es una forma de mantener proyectos Laravel más claros, escalables y fáciles de modificar.</p>



<p class="wp-block-paragraph">Un controller debe controlar el flujo de la petición. Un Form Request debe validar y preparar datos. Un Service debe ejecutar la lógica de negocio. Un Model debe representar la entidad y su relación con la base de datos.</p>



<p class="wp-block-paragraph">La meta no es crear más archivos sin razón, sino evitar que una sola clase tenga demasiadas responsabilidades.</p>



<p class="wp-block-paragraph">Cuando cada pieza cumple su función, el código se vuelve más fácil de leer, probar y mantener.</p>



<h2 class="wp-block-heading">Fuentes consultadas</h2>



<ul class="wp-block-list">
<li><a href="https://laravel.com/docs/13.x/controllers" target="_blank" rel="noreferrer noopener">Laravel Documentation: Controllers</a></li>



<li><a href="https://laravel.com/docs/13.x/validation#form-request-validation" target="_blank" rel="noreferrer noopener">Laravel Documentation: Form Request Validation</a></li>



<li><a href="https://laravel.com/docs/13.x/validation#preparing-input-for-validation" target="_blank" rel="noreferrer noopener">Laravel Documentation: Preparing Input for Validation</a></li>



<li><a href="https://laravel.com/docs/13.x/container" target="_blank" rel="noreferrer noopener">Laravel Documentation: Service Container</a></li>



<li><a href="https://laravel.com/docs/13.x/eloquent" target="_blank" rel="noreferrer noopener">Laravel Documentation: Eloquent ORM</a></li>
</ul>



<p class="wp-block-paragraph"></p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.mariatech.com.mx/blog/laravel-php/arquitectura-de-un-proyecto-de-laravel/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>GET vs POST: cuándo usar cada método en desarrollo web</title>
		<link>https://www.mariatech.com.mx/blog/laravel-php/get-vs-post-cuando-usar-cada-metodo-en-desarrollo-web/</link>
					<comments>https://www.mariatech.com.mx/blog/laravel-php/get-vs-post-cuando-usar-cada-metodo-en-desarrollo-web/#respond</comments>
		
		<dc:creator><![CDATA[Victor Santillan]]></dc:creator>
		<pubDate>Sun, 12 Jul 2026 00:34:29 +0000</pubDate>
				<category><![CDATA[Laravel / PHP]]></category>
		<guid isPermaLink="false">https://www.mariatech.com.mx/blog/?p=307</guid>

					<description><![CDATA[En este artículo veremos cuándo usar el método GET o POST y porque en el desarrollo web...]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Cuando desarrollamos una aplicación web, una de las decisiones más comunes es elegir qué método HTTP utilizar para una ruta: <code>GET</code> o <code>POST</code>. A simple vista puede parecer una decisión menor, sobre todo cuando no estamos enviando parámetros, pero elegir correctamente el método ayuda a mejorar la seguridad, mantener una arquitectura más clara y evitar comportamientos inesperados.</p>



<p class="wp-block-paragraph">En este artículo explicaremos de forma práctica cuándo usar <code>GET</code>, cuándo usar <code>POST</code> y por qué no todas las acciones deberían ejecutarse desde un simple enlace.</p>



<h2 class="wp-block-heading">La regla básica</h2>



<p class="wp-block-paragraph">Una forma sencilla de verlo es la siguiente:</p>



<pre class="wp-block-preformatted">GET  → consultar, mostrar o leer información.
POST → enviar información, ejecutar acciones o modificar el estado del sistema.</pre>



<p class="wp-block-paragraph">Esta diferencia es importante porque no todos los accesos a una URL deberían tener consecuencias dentro del sistema. Una ruta que solamente muestra información puede trabajar con <code>GET</code>. En cambio, una ruta que guarda, actualiza, elimina o dispara un proceso debería utilizar <code>POST</code> u otro método apropiado.</p>



<h2 class="wp-block-heading">Cuándo usar GET</h2>



<p class="wp-block-paragraph">El método <code>GET</code> se utiliza principalmente para consultar información. Es decir, cuando el usuario entra a una URL y espera ver datos, una página, un listado o un formulario, pero sin modificar nada en el sistema.</p>



<p class="wp-block-paragraph">Ejemplos comunes de rutas que sí pueden usar <code>GET</code>:</p>



<ul class="wp-block-list">
<li>Mostrar el listado de productos.</li>



<li>Ver el detalle de un pedido.</li>



<li>Abrir un formulario de registro.</li>



<li>Consultar reportes.</li>



<li>Filtrar información en una tabla.</li>



<li>Mostrar una pantalla de configuración.</li>
</ul>



<p class="wp-block-paragraph">Ejemplo genérico en Laravel:</p>



<pre class="wp-block-code"><code>Route::get('/admin/productos', &#91;ProductController::class, 'index'])
    -&gt;name('admin.products.index');</code></pre>



<p class="wp-block-paragraph">En este caso, la ruta solamente muestra información. No guarda datos, no elimina registros y no dispara procesos internos. Por eso <code>GET</code> es adecuado.</p>



<h2 class="wp-block-heading">Cuándo usar POST</h2>



<p class="wp-block-paragraph">El método <code>POST</code> se utiliza cuando el usuario va a enviar información o cuando una acción puede modificar el estado de la aplicación.</p>



<p class="wp-block-paragraph">Ejemplos comunes de acciones que deberían usar <code>POST</code>:</p>



<ul class="wp-block-list">
<li>Guardar un producto.</li>



<li>Crear un pedido.</li>



<li>Registrar un cliente.</li>



<li>Enviar un formulario.</li>



<li>Ejecutar una sincronización.</li>



<li>Procesar información externa.</li>



<li>Generar una acción administrativa.</li>
</ul>



<p class="wp-block-paragraph">Ejemplo genérico:</p>



<pre class="wp-block-code"><code>Route::post('/admin/productos', &#91;ProductController::class, 'store'])
    -&gt;name('admin.products.store');</code></pre>



<p class="wp-block-paragraph">Aquí la ruta recibe información y crea un nuevo registro. Por lo tanto, usar <code>POST</code> es lo correcto.</p>



<h2 class="wp-block-heading">¿Y si no estoy enviando parámetros?</h2>



<p class="wp-block-paragraph">Una duda común aparece cuando tenemos un botón que no envía datos visibles, pero sí ejecuta una acción. Por ejemplo:</p>



<ul class="wp-block-list">
<li>Sincronizar productos.</li>



<li>Procesar pedidos pendientes.</li>



<li>Actualizar inventario.</li>



<li>Ejecutar una tarea interna.</li>
</ul>



<p class="wp-block-paragraph">En estos casos podría parecer práctico usar un enlace con <code>href</code> y una ruta <code>GET</code>, porque no estamos enviando campos de formulario. Sin embargo, la decisión no depende solamente de si enviamos parámetros, sino de lo que ocurre cuando se visita la URL.</p>



<p class="wp-block-paragraph">Si al abrir la URL se ejecuta un proceso, se modifica información o se cambia el estado del sistema, entonces no debería ser <code>GET</code>. Debería ser <code>POST</code>.</p>



<h2 class="wp-block-heading">Ejemplo: ejecutar una sincronización</h2>



<p class="wp-block-paragraph">Supongamos que tenemos una aplicación administrativa y queremos agregar un botón para sincronizar información con un servicio externo.</p>



<p class="wp-block-paragraph">Una opción incorrecta sería hacerlo así:</p>



<pre class="wp-block-code"><code>&lt;a href="/admin/sincronizar-productos" class="btn btn-primary"&gt;
    Sincronizar productos
&lt;/a&gt;</code></pre>



<p class="wp-block-paragraph">Y definir la ruta como <code>GET</code>:</p>



<pre class="wp-block-code"><code>Route::get('/admin/sincronizar-productos', &#91;SyncController::class, 'syncProducts']);</code></pre>



<p class="wp-block-paragraph">El problema es que esa URL ejecuta un proceso. No solo muestra información. Por lo tanto, puede activarse accidentalmente si el usuario recarga la página, abre el enlace en otra pestaña, el navegador intenta previsualizarlo o incluso si algún rastreador accede a esa URL.</p>



<p class="wp-block-paragraph">La forma más adecuada sería usar una ruta <code>POST</code>:</p>



<pre class="wp-block-code"><code>Route::post('/admin/sincronizar-productos', &#91;SyncController::class, 'syncProducts'])
    -&gt;name('admin.products.sync');</code></pre>



<p class="wp-block-paragraph">Y ejecutarla desde un formulario:</p>



<pre class="wp-block-code"><code>&lt;form action="{{ route('admin.products.sync') }}" method="POST"&gt;
    @csrf

    &lt;button type="submit" class="btn btn-primary"&gt;
        Sincronizar productos
    &lt;/button&gt;
&lt;/form&gt;</code></pre>



<p class="wp-block-paragraph">Aunque el formulario no envíe campos adicionales, el uso de <code>POST</code> comunica correctamente que se trata de una acción, no de una simple consulta.</p>



<h2 class="wp-block-heading">Por qué evitar GET para acciones importantes</h2>



<p class="wp-block-paragraph">Usar <code>GET</code> para ejecutar acciones puede provocar varios problemas:</p>



<ul class="wp-block-list">
<li>La acción puede ejecutarse accidentalmente al recargar la página.</li>



<li>La URL puede compartirse o abrirse sin intención.</li>



<li>Un navegador puede intentar previsualizar o precargar el enlace.</li>



<li>Un bot o rastreador podría visitar la ruta.</li>



<li>No se aprovecha la protección CSRF de la misma manera que con formularios <code>POST</code>.</li>



<li>Se rompe la semántica de la aplicación, porque una consulta termina modificando el sistema.</li>
</ul>



<p class="wp-block-paragraph">Por eso, aunque usar <code>GET</code> pueda parecer más cómodo visualmente, no siempre es la opción correcta.</p>



<h2 class="wp-block-heading">La importancia del CSRF</h2>



<p class="wp-block-paragraph">En frameworks como Laravel, los formularios <code>POST</code> suelen incluir un token CSRF para validar que la petición realmente fue generada desde la aplicación y no desde un sitio externo.</p>



<p class="wp-block-paragraph">Ejemplo:</p>



<pre class="wp-block-code"><code>&lt;form action="{{ route('admin.products.sync') }}" method="POST"&gt;
    @csrf

    &lt;button type="submit"&gt;
        Ejecutar sincronización
    &lt;/button&gt;
&lt;/form&gt;</code></pre>



<p class="wp-block-paragraph">La directiva <code>@csrf</code> genera un token oculto que Laravel verifica al recibir la solicitud. Esto ayuda a proteger acciones sensibles como guardar información, eliminar registros o ejecutar procesos administrativos.</p>



<h2 class="wp-block-heading">¿Qué pasa si el formulario afecta el diseño?</h2>



<p class="wp-block-paragraph">En ocasiones se evita usar un formulario porque visualmente no encaja bien dentro de un grupo de botones o dentro de una interfaz administrativa. Sin embargo, ese es un problema de presentación, no de arquitectura.</p>



<p class="wp-block-paragraph">Podemos mantener el método correcto y ajustar el diseño con CSS:</p>



<pre class="wp-block-code"><code>&lt;div class="btn-group" role="group">
    &lt;a href="{{ route('admin.products.index') }}" class="btn btn-secondary">
        Regresar
    &lt;/a>

    &lt;form action="{{ route('admin.products.sync') }}" method="POST" class="m-0 d-inline">
        @csrf
        &lt;button type="submit" class="btn btn-primary">
            Sincronizar
        &lt;/button>
    &lt;/form>
&lt;/div></code></pre>



<p class="wp-block-paragraph">También puede resolverse con una petición AJAX usando <code>fetch</code>, manteniendo el método <code>POST</code> y enviando el token CSRF en los encabezados.</p>



<pre class="wp-block-code"><code>&lt;button type="button" id="btn-sync" class="btn btn-primary"&gt;
    Sincronizar
&lt;/button&gt;

&lt;script&gt;
document.getElementById('btn-sync').addEventListener('click', function () {
    fetch('/admin/sincronizar-productos', {
        method: 'POST',
        headers: {
            'X-CSRF-TOKEN': document.querySelector('meta&#91;name="csrf-token"]').content,
            'Accept': 'application/json'
        }
    });
});
&lt;/script&gt;</code></pre>



<p class="wp-block-paragraph">Lo importante es no cambiar a <code>GET</code> solo porque estéticamente es más cómodo usar un enlace.</p>



<h2 class="wp-block-heading">GET no debe tener efectos secundarios</h2>



<p class="wp-block-paragraph">Una buena práctica es que las rutas <code>GET</code> no tengan efectos secundarios importantes. Es decir, visitar una URL con <code>GET</code> no debería crear registros, eliminar información, actualizar inventario ni ejecutar procesos internos.</p>



<p class="wp-block-paragraph">Por ejemplo, estas rutas deberían evitarse:</p>



<pre class="wp-block-preformatted">GET /admin/eliminar-producto/10
GET /admin/procesar-pedidos
GET /admin/sincronizar-inventario
GET /admin/ejecutar-comando</pre>



<p class="wp-block-paragraph">Aunque técnicamente puedan funcionar, no son una buena práctica porque una simple visita a la URL puede cambiar el estado del sistema.</p>



<h2 class="wp-block-heading">Entonces, ¿cuándo usar cada uno?</h2>



<figure class="wp-block-table"><table><thead><tr><th>Situación</th><th>Método recomendado</th><th>Motivo</th></tr></thead><tbody><tr><td>Mostrar un listado</td><td>GET</td><td>Solo consulta información.</td></tr><tr><td>Ver detalle de un registro</td><td>GET</td><td>No modifica datos.</td></tr><tr><td>Mostrar un formulario</td><td>GET</td><td>Solo carga la interfaz.</td></tr><tr><td>Guardar un formulario</td><td>POST</td><td>Crea o modifica información.</td></tr><tr><td>Ejecutar una sincronización</td><td>POST</td><td>Dispara un proceso interno.</td></tr><tr><td>Eliminar un registro</td><td>DELETE o POST</td><td>Modifica el estado del sistema.</td></tr><tr><td>Actualizar información</td><td>PUT, PATCH o POST</td><td>Modifica datos existentes.</td></tr></tbody></table></figure>



<h2 class="wp-block-heading">Más allá de GET y POST</h2>



<p class="wp-block-paragraph">En aplicaciones modernas también es común utilizar otros métodos HTTP:</p>



<ul class="wp-block-list">
<li><code>PUT</code>: para actualizar completamente un recurso.</li>



<li><code>PATCH</code>: para actualizar parcialmente un recurso.</li>



<li><code>DELETE</code>: para eliminar un recurso.</li>
</ul>



<p class="wp-block-paragraph">Sin embargo, en formularios HTML tradicionales normalmente se trabaja con <code>GET</code> y <code>POST</code>. Frameworks como Laravel permiten simular métodos como <code>PUT</code>, <code>PATCH</code> o <code>DELETE</code> mediante campos ocultos.</p>



<pre class="wp-block-code"><code>&lt;form action="{{ route('admin.products.destroy', $product) }}" method="POST">
    @csrf
    @method('DELETE')
    &lt;button type="submit">
        Eliminar producto
    &lt;/button>
&lt;/form></code></pre>



<p class="wp-block-paragraph">Esto permite mantener una estructura más clara y alineada con las buenas prácticas de desarrollo web.</p>



<h2 class="wp-block-heading">Ejemplo práctico de flujo correcto</h2>



<p class="wp-block-paragraph">Supongamos que tenemos un módulo de pedidos y necesitamos estas acciones:</p>



<pre class="wp-block-preformatted">GET    /admin/pedidos              → Mostrar pedidos
GET    /admin/pedidos/15           → Ver detalle del pedido
GET    /admin/pedidos/create       → Mostrar formulario
POST   /admin/pedidos              → Guardar pedido
POST   /admin/pedidos/sincronizar  → Ejecutar sincronización
DELETE /admin/pedidos/15           → Eliminar pedido</pre>



<p class="wp-block-paragraph">Este tipo de estructura permite entender rápidamente qué hace cada ruta y qué impacto tiene dentro de la aplicación.</p>



<h2 class="wp-block-heading">Conclusión</h2>



<p class="wp-block-paragraph">Elegir entre <code>GET</code> y <code>POST</code> no debería depender únicamente de si enviamos parámetros o de qué opción se ve mejor en la interfaz. La decisión debe basarse en la intención de la ruta.</p>



<p class="wp-block-paragraph">Si la ruta solo consulta o muestra información, <code>GET</code> es correcto. Si la ruta guarda, modifica, elimina o ejecuta un proceso dentro del sistema, entonces debe usarse <code>POST</code> u otro método adecuado.</p>



<p class="wp-block-paragraph">En proyectos reales, esta diferencia ayuda a construir aplicaciones más seguras, ordenadas y fáciles de mantener. Además, evita que acciones importantes se ejecuten accidentalmente desde una URL que debería ser únicamente de consulta.</p>



<p class="wp-block-paragraph">La interfaz siempre se puede ajustar con CSS o JavaScript, pero la intención técnica de una ruta debe mantenerse clara desde el diseño de la aplicación.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.mariatech.com.mx/blog/laravel-php/get-vs-post-cuando-usar-cada-metodo-en-desarrollo-web/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Cómo sincronizar stock entre un sistema interno y una plataforma externa usando Laravel</title>
		<link>https://www.mariatech.com.mx/blog/laravel-php/como-sincronizar-stock-entre-un-sistema-interno-y-una-plataforma-externa-usando-laravel/</link>
					<comments>https://www.mariatech.com.mx/blog/laravel-php/como-sincronizar-stock-entre-un-sistema-interno-y-una-plataforma-externa-usando-laravel/#respond</comments>
		
		<dc:creator><![CDATA[Luis Duran]]></dc:creator>
		<pubDate>Fri, 26 Jun 2026 23:39:23 +0000</pubDate>
				<category><![CDATA[Laravel / PHP]]></category>
		<category><![CDATA[MySQL]]></category>
		<guid isPermaLink="false">https://www.mariatech.com.mx/blog/?p=304</guid>

					<description><![CDATA[En este artículo veremos un enfoque general para implementar una sincronización de stock usando Laravel, sin depender de una plataforma específica. La idea es explicar la arquitectura, las decisiones técnicas y las buenas prácticas que pueden aplicarse cuando un sistema interno necesita enviar información de inventario hacia un servicio externo. Mantener actualizado el inventario entre [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p class="has-text-align-left wp-block-paragraph">En este artículo veremos un enfoque general para implementar una sincronización de stock usando Laravel, sin depender de una plataforma específica. La idea es explicar la arquitectura, las decisiones técnicas y las buenas prácticas que pueden aplicarse cuando un sistema interno necesita enviar información de inventario hacia un servicio externo.</p>



<p class="has-text-align-left wp-block-paragraph">Mantener actualizado el inventario entre un sistema interno y una plataforma externa es una tarea clave para cualquier negocio que vende productos en distintos canales. Cuando el stock no se sincroniza correctamente, pueden aparecer problemas como ventas sin existencia, información desactualizada o diferencias entre lo que ve el cliente y lo que realmente existe en almacén.</p>



<h2 class="wp-block-heading">El reto de sincronizar inventario</h2>



<p class="has-text-align-left wp-block-paragraph">En muchos sistemas, el inventario principal se administra desde una aplicación interna. Esta aplicación puede controlar productos, almacenes, existencias, movimientos, pedidos y configuraciones propias del negocio.</p>



<p class="has-text-align-left wp-block-paragraph">Por otro lado, una plataforma externa puede funcionar como canal de venta, catálogo público o punto de contacto con los clientes. El problema aparece cuando el stock cambia dentro del sistema interno, pero esa actualización no llega de inmediato a la plataforma externa.</p>



<p class="has-text-align-left wp-block-paragraph">Esto puede provocar que un producto aparezca disponible cuando realmente ya no hay existencias, o que el equipo operativo tenga que corregir manualmente la información en más de un lugar.</p>



<p class="has-text-align-left wp-block-paragraph">Por eso es importante diseñar un proceso de sincronización que permita mantener ambos sistemas alineados.</p>



<h2 class="wp-block-heading">Definir una fuente principal de información</h2>



<p class="wp-block-paragraph">Antes de implementar la sincronización, es necesario definir qué sistema será la fuente principal del stock.</p>



<p class="wp-block-paragraph">En la mayoría de los casos, el sistema interno debe ser la fuente confiable, ya que ahí se registran los movimientos reales de inventario. La plataforma externa solo debería recibir el dato actualizado.</p>



<p class="wp-block-paragraph">Este principio evita conflictos como:</p>



<ul class="wp-block-list">
<li>Actualizar stock desde dos lugares diferentes.</li>



<li>Sobrescribir información correcta con datos desactualizados.</li>



<li>Perder control sobre qué sistema tiene la información válida.</li>



<li>Generar diferencias entre almacenes y publicaciones externas.</li>
</ul>



<p class="wp-block-paragraph">Cuando el sistema interno es la fuente principal, la sincronización se vuelve más clara: se calcula el stock disponible internamente y después se envía hacia la plataforma externa.</p>



<h2 class="wp-block-heading">Relacionar productos internos con elementos externos</h2>



<p class="wp-block-paragraph">Un paso fundamental es relacionar cada producto interno con su equivalente en la plataforma externa.</p>



<p class="wp-block-paragraph">No se debe enviar stock si no existe una relación clara entre ambos elementos. De lo contrario, el sistema podría actualizar el producto equivocado o enviar información incorrecta.</p>



<p class="wp-block-paragraph">Una relación puede contener datos generales como:</p>



<ul class="wp-block-list">
<li>Identificador del producto interno.</li>



<li>Identificador del elemento externo.</li>



<li>Estado de la relación.</li>



<li>Última fecha de sincronización.</li>



<li>Stock enviado más recientemente.</li>
</ul>



<p class="wp-block-paragraph">Esta relación permite saber qué elementos pueden sincronizarse y cuáles necesitan revisión antes de participar en el proceso.</p>



<h2 class="wp-block-heading">Obtener el stock correcto</h2>



<p class="wp-block-paragraph">Cuando el sistema interno maneja varios almacenes, no siempre conviene enviar el stock total de todos ellos. En algunos casos, la plataforma externa debe trabajar únicamente con un almacén específico.</p>



<p class="wp-block-paragraph">Por ejemplo, una empresa puede tener inventario en sucursales, almacenes físicos o puntos de distribución diferentes. La sincronización debe respetar la configuración del negocio y tomar el stock desde el almacén autorizado para ese canal.</p>



<p class="wp-block-paragraph">También es recomendable validar que el stock enviado nunca sea negativo. Si el sistema interno llegara a tener un valor menor que cero por algún ajuste o movimiento pendiente, lo más seguro es enviar cero a la plataforma externa.</p>



<h2 class="wp-block-heading">Separar la lógica usando Jobs</h2>



<p class="wp-block-paragraph">En Laravel, una buena práctica es separar este tipo de procesos en Jobs.</p>



<p class="wp-block-paragraph">Un Job permite ejecutar la sincronización sin cargar toda la lógica dentro del controlador. Esto hace que el código sea más ordenado, más fácil de mantener y más flexible.</p>



<p class="wp-block-paragraph">El flujo general puede verse así:</p>



<pre class="wp-block-code"><code>Usuario o tarea programada inicia la sincronización
↓
Laravel dispara un Job
↓
El Job busca productos relacionados
↓
Calcula el stock interno
↓
Envía la información al servicio externo
↓
Registra el resultado</code></pre>



<p class="wp-block-paragraph">Esta estructura permite ejecutar la misma lógica desde distintos puntos, como un botón manual, una tarea programada o un comando de consola.</p>



<h2 class="wp-block-heading">Usar un servicio para comunicarse con la plataforma externa</h2>



<p class="wp-block-paragraph">La comunicación con la API externa debería estar aislada en una clase de servicio.</p>



<p class="wp-block-paragraph">Esto evita que el controlador o el Job tengan demasiada responsabilidad. El servicio puede encargarse de preparar la petición, enviar el stock, interpretar la respuesta y devolver un resultado claro.</p>



<p class="wp-block-paragraph">De forma conceptual, el servicio debería realizar estas tareas:</p>



<ul class="wp-block-list">
<li>Validar que exista una configuración activa.</li>



<li>Obtener el token o credenciales necesarias.</li>



<li>Enviar la cantidad disponible a la plataforma externa.</li>



<li>Revisar si la respuesta fue exitosa.</li>



<li>Detectar errores conocidos.</li>



<li>Devolver el resultado al proceso principal.</li>
</ul>



<p class="wp-block-paragraph">Separar esta lógica facilita futuras modificaciones, especialmente si la plataforma externa cambia sus reglas, endpoints o formato de respuesta.</p>



<h2 class="wp-block-heading">Actualizar información local solo cuando la API responda correctamente</h2>



<p class="wp-block-paragraph">Una parte importante del proceso es no asumir que la sincronización fue exitosa solo porque se envió una petición.</p>



<p class="wp-block-paragraph">La plataforma externa puede rechazar el cambio por varias razones: credenciales inválidas, producto no encontrado, permisos insuficientes, reglas del canal o restricciones sobre ciertos campos.</p>



<p class="wp-block-paragraph">Por eso, el sistema interno solo debería actualizar su registro local de “último stock sincronizado” cuando la API confirme que la operación fue aceptada.</p>



<p class="wp-block-paragraph">Esto evita mostrar información incorrecta dentro del sistema administrativo.</p>



<h2 class="wp-block-heading">Registrar una bitácora de sincronización</h2>



<p class="wp-block-paragraph">Toda integración con servicios externos debería contar con una bitácora.</p>



<p class="wp-block-paragraph">La bitácora permite saber qué ocurrió en cada intento de sincronización. Esto es útil para auditoría, soporte y diagnóstico de errores.</p>



<p class="wp-block-paragraph">Una buena bitácora puede registrar información como:</p>



<ul class="wp-block-list">
<li>Fecha del intento.</li>



<li>Producto interno relacionado.</li>



<li>Elemento externo actualizado.</li>



<li>Stock calculado internamente.</li>



<li>Stock enviado a la plataforma externa.</li>



<li>Estado de la operación.</li>



<li>Código de respuesta.</li>



<li>Mensaje descriptivo del resultado.</li>
</ul>



<p class="wp-block-paragraph">Con esta información, el equipo puede saber si una actualización fue exitosa, si la plataforma externa rechazó el cambio o si ocurrió un problema de configuración.</p>



<h2 class="wp-block-heading">Mostrar la bitácora en el panel administrativo</h2>



<p class="wp-block-paragraph">Guardar la bitácora en la base de datos es útil, pero mostrarla en la interfaz administrativa mejora mucho la experiencia del usuario.</p>



<p class="wp-block-paragraph">Una pestaña de historial o bitácora permite revisar rápidamente los últimos intentos de sincronización sin entrar directamente a la base de datos.</p>



<p class="wp-block-paragraph">Esta sección puede mostrar columnas como:</p>



<ul class="wp-block-list">
<li>Fecha.</li>



<li>Producto.</li>



<li>Stock interno.</li>



<li>Stock enviado.</li>



<li>Estado.</li>



<li>Código de respuesta.</li>



<li>Mensaje.</li>
</ul>



<p class="wp-block-paragraph">De esta manera, el usuario puede identificar rápidamente si una sincronización fue exitosa o si necesita atención.</p>



<h2 class="wp-block-heading">Sincronización automática y sincronización manual</h2>



<p class="wp-block-paragraph">Una sincronización puede ejecutarse de forma automática mediante tareas programadas. Esto ayuda a mantener el inventario actualizado sin intervención constante del usuario.</p>



<p class="wp-block-paragraph">Sin embargo, también es útil contar con una opción manual desde el panel administrativo.</p>



<p class="wp-block-paragraph">El botón manual no reemplaza el proceso automático, sino que lo complementa. Puede servir cuando el usuario necesita actualizar el stock inmediatamente después de hacer cambios importantes en el inventario o en las relaciones con la plataforma externa.</p>



<p class="wp-block-paragraph">Para evitar ejecuciones accidentales, conviene mostrar una confirmación antes de iniciar el proceso.</p>



<h2 class="wp-block-heading">Manejo de errores</h2>



<p class="wp-block-paragraph">No todos los errores significan que la lógica interna esté mal. A veces la plataforma externa rechaza una actualización por sus propias reglas.</p>



<p class="wp-block-paragraph">Algunos casos comunes son:</p>



<ul class="wp-block-list">
<li>Token expirado.</li>



<li>Permisos insuficientes.</li>



<li>Producto externo no encontrado.</li>



<li>Campo no editable.</li>



<li>Restricciones por estado del producto.</li>



<li>Errores temporales del servicio externo.</li>
</ul>



<p class="wp-block-paragraph">Por eso es importante que el sistema muestre mensajes claros y registre la respuesta técnica para diagnóstico.</p>



<p class="wp-block-paragraph">Un mensaje útil para el usuario debe explicar el problema sin saturarlo con detalles técnicos innecesarios.</p>



<h2 class="wp-block-heading">Buenas prácticas</h2>



<p class="wp-block-paragraph">Al implementar sincronización de stock entre un sistema interno y una plataforma externa, conviene aplicar estas buenas prácticas:</p>



<ul class="wp-block-list">
<li>Definir claramente cuál sistema es la fuente principal del inventario.</li>



<li>Sincronizar solo productos correctamente relacionados.</li>



<li>Tomar el stock desde el almacén configurado.</li>



<li>Evitar enviar cantidades negativas.</li>



<li>Separar responsabilidades usando controladores, Jobs y servicios.</li>



<li>Registrar cada intento en una bitácora.</li>



<li>Actualizar datos locales solo cuando la API confirme éxito.</li>



<li>Mostrar resultados de sincronización en el panel administrativo.</li>



<li>Manejar errores de forma clara.</li>



<li>Mantener una opción manual como apoyo al proceso automático.</li>
</ul>



<h2 class="wp-block-heading">Conclusión</h2>



<p class="wp-block-paragraph">Sincronizar stock entre un sistema interno y una plataforma externa no consiste solamente en enviar una cantidad mediante una API. También implica validar relaciones, respetar configuraciones, manejar errores, registrar evidencia y mantener consistencia entre ambos sistemas.</p>



<p class="wp-block-paragraph">Laravel ofrece herramientas muy útiles para construir este tipo de procesos de forma ordenada, como Jobs, servicios, comandos de consola, tareas programadas y manejo de peticiones HTTP.</p>



<p class="wp-block-paragraph">Una implementación bien diseñada permite que el sistema interno conserve el control principal del inventario, mientras la plataforma externa recibe actualizaciones confiables y trazables.</p>



<p class="wp-block-paragraph">El resultado es una integración más segura, más fácil de mantener y más útil para los usuarios que necesitan operar productos en diferentes canales de venta.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.mariatech.com.mx/blog/laravel-php/como-sincronizar-stock-entre-un-sistema-interno-y-una-plataforma-externa-usando-laravel/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Corregir una rama creada desde la base incorrecta con git cherry-pick</title>
		<link>https://www.mariatech.com.mx/blog/git/corregir-una-rama-creada-desde-la-base-incorrecta-con-git-cherry-pick/</link>
					<comments>https://www.mariatech.com.mx/blog/git/corregir-una-rama-creada-desde-la-base-incorrecta-con-git-cherry-pick/#respond</comments>
		
		<dc:creator><![CDATA[Victor Santillan]]></dc:creator>
		<pubDate>Wed, 24 Jun 2026 19:05:30 +0000</pubDate>
				<category><![CDATA[Git]]></category>
		<guid isPermaLink="false">https://www.mariatech.com.mx/blog/?p=300</guid>

					<description><![CDATA[Algunas veces por equivocación creamos ramas desde otra que no es main y cuando queremos hacer el pull request tenemos commits que no deberían aplicarse, en este artículo veremos como resolverlo de una forma sencilla...]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">En un flujo de trabajo con Git es común crear ramas nuevas para desarrollar funcionalidades específicas. Esto permite mantener el código organizado, probar cambios sin afectar la rama principal y preparar Pull Requests más claros.</p>



<p class="wp-block-paragraph">Sin embargo, también puede pasar que una rama se cree desde una base incorrecta. Por ejemplo, en lugar de crear una nueva rama desde <code>main</code>, se crea desde otra rama de desarrollo que todavía no está lista para integrarse. Cuando esto ocurre, el Pull Request puede mostrar muchos commits adicionales que no pertenecen realmente al cambio que queremos subir.</p>



<h2 class="wp-block-heading">El problema</h2>



<p class="wp-block-paragraph">Supongamos que tenemos una rama principal:</p>



<pre class="wp-block-code"><code>main</code></pre>



<p class="wp-block-paragraph">También existe una rama de desarrollo para una integración grande:</p>



<pre class="wp-block-code"><code>feature/integracion-api</code></pre>



<p class="wp-block-paragraph">Después se crea una nueva rama para una funcionalidad más pequeña, por ejemplo el registro del costo de envío:</p>



<pre class="wp-block-code"><code>feature/integracion-envios</code></pre>



<p class="wp-block-paragraph">El detalle es que esta última rama fue creada accidentalmente desde <code>feature/integracion-api</code> y no desde <code>main</code>. Entonces, al intentar crear un Pull Request hacia <code>main</code>, GitHub muestra que se van a aplicar muchos commits adicionales.</p>



<p class="wp-block-paragraph">En ese punto la señal de alerta es clara: si la funcionalidad era pequeña, pero el Pull Request muestra demasiados commits o archivos que no corresponden, no conviene hacer el merge todavía.</p>



<h2 class="wp-block-heading">Por qué no era buena idea hacer el merge directo</h2>



<p class="wp-block-paragraph">Si se hacía el merge directo de <code>feature/integracion-envios</code> hacia <code>main</code>, también se podían integrar cambios incompletos de la rama <code>feature/integracion-api</code>. Esto habría mezclado funcionalidades distintas y pudo provocar errores en producción o en el ambiente de pruebas.</p>



<p class="wp-block-paragraph">El objetivo era integrar únicamente los commits relacionados con el registro del costo de envío, sin arrastrar el trabajo de otra rama.</p>



<h2 class="wp-block-heading">La solución: crear una rama limpia desde main</h2>



<p class="wp-block-paragraph">La solución fue crear una nueva rama partiendo correctamente desde <code>main</code> y después aplicar únicamente los commits necesarios usando <code>git cherry-pick</code>.</p>



<p class="wp-block-paragraph">El comando <code>git cherry-pick</code> permite tomar uno o varios commits específicos de otra rama y aplicarlos sobre la rama actual. Esto resulta útil cuando queremos rescatar cambios puntuales sin mezclar todo el historial de la rama original.</p>



<h2 class="wp-block-heading">1. Actualizar la información del repositorio</h2>



<p class="wp-block-paragraph">Primero se actualiza la información del repositorio remoto:</p>



<pre class="wp-block-code"><code>git fetch origin</code></pre>



<p class="wp-block-paragraph">Este comando descarga las referencias más recientes de las ramas remotas, sin modificar directamente el código de la rama actual.</p>



<h2 class="wp-block-heading">2. Cambiar a main y actualizarla</h2>



<p class="wp-block-paragraph">Después se cambia a la rama principal y se actualiza con los últimos cambios disponibles:</p>



<pre class="wp-block-code"><code>git checkout main
git pull origin main</code></pre>



<p class="wp-block-paragraph">Esto asegura que la nueva rama limpia se cree desde la versión más reciente de <code>main</code>.</p>



<h2 class="wp-block-heading">3. Crear una nueva rama limpia</h2>



<p class="wp-block-paragraph">Con <code>main</code> actualizada, se crea una nueva rama limpia para la funcionalidad:</p>



<pre class="wp-block-code"><code>git checkout -b feature/integracion-envios-clean</code></pre>



<p class="wp-block-paragraph">Esta nueva rama ya no tendrá como base la rama de integración grande, sino directamente <code>main</code>.</p>



<h2 class="wp-block-heading">4. Identificar los commits correctos</h2>



<p class="wp-block-paragraph">El siguiente paso es identificar los commits que realmente corresponden a la funcionalidad de envíos.</p>



<pre class="wp-block-code"><code>git log --oneline origin/feature/integracion-api..origin/feature/integracion-envios</code></pre>



<p class="wp-block-paragraph">Este comando ayuda a visualizar los commits que existen en la rama de envíos, pero que no forman parte de la rama base desde la que fue creada.</p>



<p class="wp-block-paragraph">El resultado puede ser algo como:</p>



<pre class="wp-block-code"><code>abc1234 Agrega campo de costo de envío al pedido
def5678 Ajusta cálculo total incluyendo envío</code></pre>



<p class="wp-block-paragraph">En este ejemplo, esos dos commits serían los únicos que realmente se necesitan aplicar en la rama limpia.</p>



<h2 class="wp-block-heading">5. Aplicar los commits con cherry-pick</h2>



<p class="wp-block-paragraph">Una vez identificados los commits correctos, se aplican sobre la rama limpia:</p>



<pre class="wp-block-code"><code>git cherry-pick abc1234 def5678</code></pre>



<p class="wp-block-paragraph">Con esto, Git toma los cambios introducidos por esos commits y los aplica sobre la rama actual, generando nuevos commits con esos mismos cambios.</p>



<p class="wp-block-paragraph">Si durante el proceso aparecen conflictos, se deben resolver manualmente, agregar los archivos corregidos y continuar el cherry-pick:</p>



<pre class="wp-block-code"><code>git add .
git cherry-pick --continue</code></pre>



<p class="wp-block-paragraph">Si fuera necesario cancelar el proceso, se puede usar:</p>



<pre class="wp-block-code"><code>git cherry-pick --abort</code></pre>



<h2 class="wp-block-heading">6. Revisar los cambios antes de subir la rama</h2>



<p class="wp-block-paragraph">Antes de subir la nueva rama, se revisa el estado del repositorio:</p>



<pre class="wp-block-code"><code>git status</code></pre>



<p class="wp-block-paragraph">También es recomendable revisar qué archivos quedaron modificados:</p>



<pre class="wp-block-code"><code>git diff --name-only main...feature/integracion-envios-clean</code></pre>



<p class="wp-block-paragraph">La idea es confirmar que la rama limpia solo contiene archivos relacionados con la funcionalidad que se quiere integrar.</p>



<h2 class="wp-block-heading">7. Subir la rama limpia al repositorio remoto</h2>



<p class="wp-block-paragraph">Una vez revisado todo, se sube la nueva rama a GitHub:</p>



<pre class="wp-block-code"><code>git push -u origin feature/integracion-envios-clean</code></pre>



<p class="wp-block-paragraph">Después de esto, ya es posible crear un nuevo Pull Request desde la rama limpia hacia <code>main</code>.</p>



<h2 class="wp-block-heading">8. Crear el Pull Request correcto</h2>



<p class="wp-block-paragraph">En GitHub, el nuevo Pull Request debe quedar configurado de esta forma:</p>



<pre class="wp-block-code"><code>base: main
compare: feature/integracion-envios-clean</code></pre>



<p class="wp-block-paragraph">Ahora el Pull Request mostrará únicamente los commits necesarios y los archivos relacionados con el registro del costo de envío.</p>



<p class="wp-block-paragraph">Esto permite hacer una revisión más clara, evitar mezclar funcionalidades y mantener la rama <code>main</code> limpia.</p>



<h2 class="wp-block-heading">Flujo completo de comandos</h2>



<p class="wp-block-paragraph">El flujo completo quedaría de la siguiente manera:</p>



<pre class="wp-block-code"><code>git fetch origin

git checkout main
git pull origin main

git checkout -b feature/integracion-envios-clean

git log --oneline origin/feature/integracion-api..origin/feature/integracion-envios

git cherry-pick abc1234 def5678

git status

git diff --name-only main...feature/integracion-envios-clean

git push -u origin feature/integracion-envios-clean</code></pre>



<h2 class="wp-block-heading">Buenas prácticas para evitar este problema</h2>



<p class="wp-block-paragraph">Antes de empezar una nueva funcionalidad, es recomendable seguir este flujo:</p>



<pre class="wp-block-code"><code>git checkout main
git pull origin main
git checkout -b feature/nueva-funcionalidad</code></pre>



<p class="wp-block-paragraph">También conviene revisar la rama actual antes de crear una nueva:</p>



<pre class="wp-block-code"><code>git branch --show-current</code></pre>



<p class="wp-block-paragraph">Con este pequeño hábito se evitan muchos problemas al momento de crear Pull Requests.</p>



<h2 class="wp-block-heading">Conclusión</h2>



<p class="wp-block-paragraph">Cuando una rama se crea desde la base incorrecta, no siempre es necesario rehacer todo el trabajo. Si los cambios están organizados en commits claros, podemos crear una rama limpia desde <code>main</code> y aplicar únicamente los commits necesarios con <code>git cherry-pick</code>.</p>



<p class="wp-block-paragraph">En este caso, la solución fue evitar el merge directo, crear una nueva rama limpia, aplicar los commits correctos y generar un Pull Request más claro y seguro. Esto permitió integrar la funcionalidad sin arrastrar cambios de otra rama que todavía no estaba lista.</p>



<p class="wp-block-paragraph">Este tipo de situaciones son comunes en proyectos reales, y aprender a resolverlas correctamente ayuda a mantener un historial limpio, reducir errores y mejorar el flujo de trabajo en equipo.</p>



<p class="wp-block-paragraph"></p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.mariatech.com.mx/blog/git/corregir-una-rama-creada-desde-la-base-incorrecta-con-git-cherry-pick/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Cómo corregir el bug de scroll causado por Iconify e IntersectionObserver v2.1.0</title>
		<link>https://www.mariatech.com.mx/blog/plugins/como-corregir-el-bug-de-scroll-causado-por-iconify-e-intersectionobserver-v2-1-0/</link>
					<comments>https://www.mariatech.com.mx/blog/plugins/como-corregir-el-bug-de-scroll-causado-por-iconify-e-intersectionobserver-v2-1-0/#respond</comments>
		
		<dc:creator><![CDATA[Victor Santillan]]></dc:creator>
		<pubDate>Thu, 18 Jun 2026 01:27:26 +0000</pubDate>
				<category><![CDATA[Plugins]]></category>
		<category><![CDATA[HTML]]></category>
		<category><![CDATA[JavaScript]]></category>
		<guid isPermaLink="false">https://www.mariatech.com.mx/blog/?p=297</guid>

					<description><![CDATA[Si estás usando Iconify en su versión 2.1.0 y tienes problemas al hacer scroll en páginas largas este artículo es para ti...]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">En interfaces administrativas con muchos iconos, formularios, tablas y modales, puede aparecer un comportamiento extraño: al hacer scroll en páginas extensas, la pantalla se congela por momentos o parece “rebotar”. Aunque visualmente se percibe como un salto, normalmente el problema viene de un ciclo de recalculado del navegador provocado por algún script que modifica el DOM mientras el usuario se desplaza.</p>



<p class="wp-block-paragraph">En este caso, el origen del problema estaba relacionado con <strong>Iconify</strong>, específicamente con el archivo <code>iconify-icon.js</code> y el uso de <code>IntersectionObserver</code> para renderizar o remover iconos dependiendo de si están visibles en pantalla.</p>



<h2 class="wp-block-heading">El síntoma</h2>



<p class="wp-block-paragraph">El problema aparece únicamente en páginas con altura considerable, donde era necesario hacer scroll para revisar el contenido. Al avanzar hacia abajo, llegaba un punto en el que la pantalla se quedaba &#8220;atorada&#8221; y comenzaba a &#8220;saltar&#8221;, como si intentara regresar a una posición anterior.</p>



<p class="wp-block-paragraph">Después de realizar un rastreo y de varias pruebas, el comportamiento desaparecía al quitar el archivo:</p>



<pre class="wp-block-code"><code>&lt;script src="/assets/js/iconify-icon.js"&gt;&lt;/script&gt;</code></pre>



<p class="wp-block-paragraph">Esto permitió confirmar que el problema no estaba en Bootstrap, DataTables, CSS personalizado ni en el layout principal, sino en la forma en que Iconify estaba administrando los iconos durante el scroll.</p>



<h2 class="wp-block-heading">Por qué sucede</h2>



<p class="wp-block-paragraph">Algunas versiones de <code>iconify-icon.js</code> usan <code>IntersectionObserver</code> para detectar si un icono está visible dentro del viewport. Cuando el icono sale de pantalla, el script puede remover el SVG renderizado; cuando vuelve a entrar, lo renderiza nuevamente.</p>



<p class="wp-block-paragraph">El comportamiento problemático se encuentra en una lógica similar a esta:</p>



<pre class="wp-block-code"><code><code>    this._observer = new IntersectionObserver((entries) => {
        const isVisible = entries.some((entry) => entry.isIntersecting);

        if (isVisible !== this._visible) {
            this._visible = isVisible;
            this._forceRender();
        }
    });
    </code>
</code></pre>



<p class="wp-block-paragraph">Y dentro de <code>_forceRender()</code> puede existir una lógica que elimina el icono del Shadow DOM cuando ya no está visible:</p>



<pre class="wp-block-code"><code><code>    _forceRender() {
        if (this._visible) {
            this._queueCheck();
        } else {
            const element = getRenderedIcon(this._shadowRoot);

            if (element) {
                this._shadowRoot.removeChild(element);
            }
        }
    }
</code>
</code></pre>



<p class="wp-block-paragraph">En teoría, esto busca optimizar el rendimiento. Pero en páginas largas, con muchos iconos y contenido dinámico, puede causar el efecto contrario: cada vez que el usuario hace scroll, el script modifica el DOM, el navegador recalcula layout, el observer vuelve a detectar cambios y se puede producir un ciclo de renderizado/recalculo.</p>



<h2 class="wp-block-heading">La solución aplicada</h2>



<p class="wp-block-paragraph">La solución fue desactivar ese comportamiento de observación de visibilidad y mantener los iconos renderizados de forma estable. Esto evita que Iconify quite y vuelva a insertar SVGs durante el scroll.</p>



<p class="wp-block-paragraph">Antes de modificar el archivo, es recomendable hacer un respaldo:</p>



<pre class="wp-block-code"><code>cp public/assets/js/iconify-icon.js public/assets/js/iconify-icon.backup.js</code></pre>



<p class="wp-block-paragraph">Después, se pueden aplicar los siguientes ajustes dentro del archivo <code>iconify-icon.js</code>.</p>



<h2 class="wp-block-heading">1. Ajustar <code>connectedCallback()</code></h2>



<p class="wp-block-paragraph">Busca una función similar a esta:</p>



<pre class="wp-block-code"><code>connectedCallback() {
    this._connected = true;
    this.startObserver();
}</code></pre>



<p class="wp-block-paragraph">Y cámbiala por:</p>



<pre class="wp-block-code"><code>connectedCallback() {
    this._connected = true;
    this._visible = true;
    this._queueCheck();
}</code></pre>



<p class="wp-block-paragraph">Con este cambio, al conectar el elemento al DOM, el icono se considera visible y se renderiza sin iniciar el observer.</p>



<h2 class="wp-block-heading">2. Corregir la lógica de <code>noobserver</code></h2>



<p class="wp-block-paragraph">En algunas versiones, el manejo del atributo <code>noobserver</code> puede estar invertido. Si encuentras algo similar a esto:</p>



<pre class="wp-block-code"><code>case "noobserver":
    this.hasAttribute("noobserver") ? this.startObserver() : this.stopObserver();
    break;</code></pre>



<p class="wp-block-paragraph">Cámbialo por:</p>



<pre class="wp-block-code"><code>case "noobserver":
    this.hasAttribute("noobserver") ? this.stopObserver() : this.startObserver();
    break;</code></pre>



<p class="wp-block-paragraph">Esto hace que, si el elemento tiene el atributo <code>noobserver</code>, el observer se detenga en lugar de iniciarse.</p>



<h2 class="wp-block-heading">3. Reemplazar <code>startObserver()</code></h2>



<p class="wp-block-paragraph">Busca el método <code>startObserver()</code>. Normalmente contiene una instancia de <code>IntersectionObserver</code>, similar a esta:</p>



<pre class="wp-block-code"><code>startObserver() {
    if (!this._observer &amp;&amp; !this.hasAttribute("noobserver")) {
        try {
            this._observer = new IntersectionObserver((entries) =&gt; {
                const isVisible = entries.some((entry) =&gt; entry.isIntersecting);

                if (isVisible !== this._visible) {
                    this._visible = isVisible;
                    this._forceRender();
                }
            });

            this._observer.observe(this);
        } catch (error) {
            if (this._observer) {
                try {
                    this._observer.disconnect();
                } catch (error) {}

                this._observer = null;
            }
        }
    }
}</code></pre>



<p class="wp-block-paragraph">Reemplázalo por una versión simple:</p>



<pre class="wp-block-code"><code>startObserver() {
    this._visible = true;
    this._queueCheck();
}</code></pre>



<p class="wp-block-paragraph">Este es el cambio principal. El icono se mantiene renderizado y ya no se elimina o reconstruye durante el scroll.</p>



<h2 class="wp-block-heading">4. Ajustar <code>_forceRender()</code></h2>



<p class="wp-block-paragraph">Busca una función similar a esta:</p>



<pre class="wp-block-code"><code>_forceRender() {
    if (this._visible) {
        this._queueCheck();
    } else {
        const element = getRenderedIcon(this._shadowRoot);

        if (element) {
            this._shadowRoot.removeChild(element);
        }
    }
}</code></pre>



<p class="wp-block-paragraph">Y cámbiala por:</p>



<pre class="wp-block-code"><code>_forceRender() {
    this._visible = true;
    this._queueCheck();
}</code></pre>



<p class="wp-block-paragraph">Con esto se evita que cualquier flujo interno vuelva a remover el SVG del Shadow DOM.</p>



<h2 class="wp-block-heading">Cómo evitar problemas de caché</h2>



<p class="wp-block-paragraph">Después de modificar el archivo, es importante forzar al navegador a cargar la nueva versión. Una forma sencilla es agregar un parámetro de versión al script:</p>



<pre class="wp-block-code"><code>&lt;script src="/assets/js/iconify-icon.js?v=1.0.2"&gt;&lt;/script&gt;</code></pre>



<p class="wp-block-paragraph">También puedes limpiar caché manualmente o compilar nuevamente los assets si el archivo forma parte de un proceso de build.</p>



<h2 class="wp-block-heading">Alternativa: usar un patch separado</h2>



<p class="wp-block-paragraph">Si no quieres modificar directamente la librería, puedes crear un archivo adicional que se cargue después de <code>iconify-icon.js</code>. Esta opción es útil cuando el archivo original pertenece a una plantilla o paquete de terceros.</p>



<pre class="wp-block-code"><code>&lt;script src="/assets/js/iconify-icon.js"&gt;&lt;/script&gt;
&lt;script src="/assets/js/iconify-icon-noobserver-patch.js"&gt;&lt;/script&gt;</code></pre>



<p class="wp-block-paragraph">Sin embargo, si el problema está dentro de la clase registrada como Web Component, modificar directamente el método dentro del archivo original suele ser más estable, siempre que se haga con respaldo y control de versiones.</p>



<h2 class="wp-block-heading">Cuándo conviene aplicar esta solución</h2>



<p class="wp-block-paragraph">Este ajuste puede ser útil cuando se cumplen varias de estas condiciones:</p>



<ul class="wp-block-list">
<li>La página tiene muchos iconos generados con <code>&lt;iconify-icon></code>.</li>



<li>El problema aparece únicamente en páginas largas con scroll.</li>



<li>El salto desaparece al quitar <code>iconify-icon.js</code>.</li>



<li>No existen errores claros en consola.</li>



<li>El navegador muestra actividad alta de renderizado o recalculado de estilos.</li>
</ul>



<h2 class="wp-block-heading">Recomendaciones finales</h2>



<p class="wp-block-paragraph">Antes de modificar una librería de terceros, conviene confirmar que realmente es la causa del problema. Para hacerlo puedes probar lo siguiente:</p>



<ul class="wp-block-list">
<li>Desactivar temporalmente el script sospechoso.</li>



<li>Probar la misma página con pocos iconos.</li>



<li>Revisar si el problema aparece solo al hacer scroll.</li>



<li>Validar que no existan scripts duplicados.</li>



<li>Hacer respaldo del archivo original antes de editarlo.</li>
</ul>



<p class="wp-block-paragraph">En este caso, la solución fue mantener los iconos renderizados y desactivar la observación de visibilidad con <code>IntersectionObserver</code>. Con ello se evita que el navegador entre en un ciclo de recalculado al hacer scroll y la interfaz vuelve a comportarse de forma estable.</p>



<h2 class="wp-block-heading">Conclusión</h2>



<p class="wp-block-paragraph">Los bugs de scroll pueden ser difíciles de detectar porque no siempre generan errores en consola. Muchas veces el problema no está en el CSS, sino en scripts que modifican el DOM mientras el usuario navega por la página.</p>



<p class="wp-block-paragraph">Cuando una librería intenta optimizar el renderizado quitando y agregando elementos durante el scroll, puede terminar provocando saltos visuales, congelamientos o recalculados constantes. En interfaces administrativas con muchos iconos, desactivar ese comportamiento puede ser una solución práctica y efectiva.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.mariatech.com.mx/blog/plugins/como-corregir-el-bug-de-scroll-causado-por-iconify-e-intersectionobserver-v2-1-0/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Cómo solucionar la discrepancia de protocolos (HTTP vs HTTPS) en integraciones OAuth 2.0</title>
		<link>https://www.mariatech.com.mx/blog/laravel-php/como-solucionar-la-discrepancia-de-protocolos-http-vs-https-en-integraciones-oauth-2-0/</link>
					<comments>https://www.mariatech.com.mx/blog/laravel-php/como-solucionar-la-discrepancia-de-protocolos-http-vs-https-en-integraciones-oauth-2-0/#respond</comments>
		
		<dc:creator><![CDATA[Luis Duran]]></dc:creator>
		<pubDate>Tue, 16 Jun 2026 00:12:41 +0000</pubDate>
				<category><![CDATA[Laravel / PHP]]></category>
		<category><![CDATA[Apps]]></category>
		<guid isPermaLink="false">https://www.mariatech.com.mx/blog/?p=294</guid>

					<description><![CDATA[Si estás integrando una API externa en tu aplicación web y te has topado con mensajes de error como &#8220;La aplicación no puede conectarse&#8221;, &#8220;Invalid redirect URI&#8221; o errores del navegador como ERR_CONNECTION_REFUSED al intentar autenticar usuarios mediante OAuth 2.0, no estás solo. Este es uno de los problemas más frecuentes durante el desarrollo local [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Si estás integrando una API externa en tu aplicación web y te has topado con mensajes de error como <strong>&#8220;La aplicación no puede conectarse&#8221;</strong>, <strong>&#8220;Invalid redirect URI&#8221;</strong> o errores del navegador como <code><strong>ERR_CONNECTION_REFUSED</strong></code> al intentar autenticar usuarios mediante OAuth 2.0, no estás solo.</p>



<p class="wp-block-paragraph">Este es uno de los problemas más frecuentes durante el desarrollo local y suele estar relacionado con una causa muy específica: <strong>la discrepancia de protocolos en la URI de redirección (</strong><code><strong>redirect_uri</strong></code><strong>)</strong>.</p>



<p class="wp-block-paragraph">A continuación, veremos por qué ocurre este problema y cómo solucionarlo para que tu flujo de autenticación funcione correctamente tanto en desarrollo como en producción.</p>



<h2 class="wp-block-heading">El origen del problema: la seguridad en OAuth 2.0</h2>



<p class="wp-block-paragraph">OAuth 2.0 es el estándar utilizado por numerosas plataformas y servicios para permitir que aplicaciones externas accedan a recursos protegidos de forma segura. Durante la configuración de una aplicación, normalmente se debe registrar una o más URI de redirección.</p>



<p class="wp-block-paragraph">Estas direcciones son utilizadas por el proveedor de autenticación para devolver al usuario a tu aplicación una vez que ha iniciado sesión y autorizado el acceso.</p>



<p class="wp-block-paragraph">El conflicto suele aparecer debido a la diferencia entre los requisitos de seguridad de la plataforma y las características de un entorno local de desarrollo:</p>



<ol start="1" class="wp-block-list">
<li><strong>Las plataformas exigen HTTPS:</strong> Muchos proveedores requieren que la <code>redirect_uri</code> utilice un protocolo seguro (<code>https://</code>) para proteger el intercambio de información sensible.</li>



<li><strong>Los entornos locales suelen usar HTTP:</strong> Herramientas de desarrollo como Laragon, XAMPP, WAMP o configuraciones locales de servidores web suelen funcionar inicialmente mediante <code>http://</code>.</li>
</ol>



<p class="wp-block-paragraph">Cuando la aplicación genera una URL con HTTPS pero el servidor local no está preparado para responder mediante ese protocolo, el navegador no puede establecer la conexión. Por otro lado, si se utiliza HTTP, muchos proveedores rechazarán la solicitud por razones de seguridad o porque la URL no coincide exactamente con la registrada.</p>



<h2 class="wp-block-heading">La solución en 3 pasos</h2>



<p class="wp-block-paragraph">La clave consiste en lograr que tu entorno local soporte HTTPS y que las URLs generadas por tu aplicación coincidan exactamente con las registradas en el proveedor de autenticación.</p>



<h3 class="wp-block-heading">Paso 1: Habilitar HTTPS en tu entorno local</h3>



<p class="wp-block-paragraph">Configura un certificado SSL local para que tu aplicación pueda responder mediante HTTPS.</p>



<p class="wp-block-paragraph">Algunas opciones comunes son:</p>



<ul class="wp-block-list">
<li><strong>Laragon:</strong> Permite habilitar certificados SSL locales de forma automática.</li>



<li><strong>Laravel Valet:</strong> Puede generar certificados locales seguros mediante comandos específicos.</li>



<li><strong>mkcert:</strong> Herramienta ampliamente utilizada para crear certificados confiables en entornos de desarrollo.</li>



<li><strong>Docker o servidores personalizados:</strong> Configura certificados SSL dentro del contenedor o servidor correspondiente.</li>
</ul>



<p class="wp-block-paragraph">Una vez configurado, tu aplicación debería ser accesible mediante una URL similar a:</p>



<pre class="wp-block-code"><code>https:&#047;&#047;miaplicacion.local</code></pre>



<h3 class="wp-block-heading">Paso 2: Generar URLs seguras desde la aplicación</h3>



<p class="wp-block-paragraph">Es importante que la URI de redirección se genere utilizando HTTPS para garantizar que coincida con la configuración registrada en el proveedor.</p>



<p class="wp-block-paragraph">Por ejemplo, en Laravel:</p>



<pre class="wp-block-code"><code>$redirect_uri = secure_url(route('oauth.callback', &#091;], false));</code></pre>



<p class="wp-block-paragraph">El objetivo no es utilizar una función específica, sino asegurarse de que la URL enviada al proveedor tenga exactamente el protocolo HTTPS esperado.</p>



<h3 class="wp-block-heading">Paso 3: Verificar la configuración del proveedor</h3>



<p class="wp-block-paragraph">La URI configurada en el panel del proveedor debe coincidir exactamente con la generada por tu aplicación.</p>



<p class="wp-block-paragraph">Ejemplos:</p>



<ul class="wp-block-list">
<li><strong>Correcto:</strong> <code>https://miaplicacion.local/oauth/callback</code></li>



<li><strong>Incorrecto:</strong> <code>http://miaplicacion.local/oauth/callback</code></li>



<li><strong>Incorrecto:</strong> <code>https://miaplicacion.local/oauth/callback/</code></li>



<li><strong>Incorrecto:</strong> <code>https://www.miaplicacion.local/oauth/callback</code> (si el dominio registrado no incluye <code>www</code>)</li>
</ul>



<p class="wp-block-paragraph">Las plataformas suelen realizar comparaciones estrictas, por lo que cualquier diferencia en protocolo, dominio, ruta, mayúsculas o barras finales puede provocar errores de autenticación.</p>



<h2 class="wp-block-heading">Buenas prácticas adicionales</h2>



<p class="wp-block-paragraph">Además de corregir el problema inmediato, es recomendable seguir estas prácticas:</p>



<ul class="wp-block-list">
<li>Mantener la misma estructura de URLs entre desarrollo, pruebas y producción.</li>



<li>Utilizar variables de entorno para configurar dominios y rutas de autenticación.</li>



<li>Evitar construir manualmente las URLs de redirección cuando el framework puede generarlas automáticamente.</li>



<li>Verificar periódicamente la configuración registrada en los proveedores externos.</li>



<li>Probar el flujo completo de autenticación antes de desplegar cambios en producción.</li>
</ul>



<h2 class="wp-block-heading">Conclusión</h2>



<p class="wp-block-paragraph">La mayoría de los problemas relacionados con OAuth 2.0 en entornos de desarrollo no provienen de errores en el código, sino de diferencias entre la configuración local y los requisitos de seguridad exigidos por los proveedores de autenticación.</p>



<p class="wp-block-paragraph">Al habilitar HTTPS localmente, generar URLs seguras desde la aplicación y asegurarte de que las URIs registradas coincidan exactamente con las utilizadas en el código, podrás evitar errores de autenticación y contar con un flujo de integración mucho más estable y seguro.</p>



<p class="wp-block-paragraph">Estos mismos principios aplican a cualquier integración basada en OAuth 2.0, independientemente del proveedor o servicio externo con el que estés trabajando.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.mariatech.com.mx/blog/laravel-php/como-solucionar-la-discrepancia-de-protocolos-http-vs-https-en-integraciones-oauth-2-0/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Cómo asegurar conexiones a APIs externas usando OAuth 2.0 en Laravel</title>
		<link>https://www.mariatech.com.mx/blog/laravel-php/como-asegurar-conexiones-a-apis-externas-usando-oauth-2-0-en-laravel/</link>
					<comments>https://www.mariatech.com.mx/blog/laravel-php/como-asegurar-conexiones-a-apis-externas-usando-oauth-2-0-en-laravel/#respond</comments>
		
		<dc:creator><![CDATA[Luis Duran]]></dc:creator>
		<pubDate>Tue, 09 Jun 2026 20:39:53 +0000</pubDate>
				<category><![CDATA[Laravel / PHP]]></category>
		<category><![CDATA[MySQL]]></category>
		<guid isPermaLink="false">https://www.mariatech.com.mx/blog/?p=291</guid>

					<description><![CDATA[Aprende a implementar OAuth 2.0 en Laravel para conectar tu aplicación o ERP con APIs externas de forma segura. Protege tus credenciales paso a paso.]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Si alguna vez has tenido que conectar un sistema local (como un ERP) con plataformas de terceros como Mercado Libre, Google o Facebook, sabes que la comunicación mediante APIs es el pan de cada día. Sin embargo, el mayor reto no es lograr que la conexión funcione, sino garantizar que <strong>funcione de manera segura</strong>.</p>



<p class="wp-block-paragraph">En este artículo, exploraremos las mejores prácticas para implementar OAuth 2.0 en Laravel, protegiendo las credenciales de tu aplicación y evitando vulnerabilidades críticas.</p>



<h2 class="wp-block-heading">¿Por qué no deberías guardar tokens en texto plano?</h2>



<p class="wp-block-paragraph">Cuando trabajas con integraciones de terceros, el flujo de OAuth 2.0 te entrega un <code>access_token</code> y, a menudo, un <code>refresh_token</code>. El error más común en desarrolladores junior es guardar estas cadenas directamente en la base de datos como texto plano.</p>



<p class="wp-block-paragraph">Si un atacante lograra vulnerar tu base de datos, tendría acceso total a las cuentas de tus clientes en esas plataformas externas. En Laravel, la solución es sumamente sencilla gracias a sus Facades de encriptación.</p>



<p class="wp-block-paragraph">Al guardar tus credenciales, asegúrate de utilizar la función nativa de Laravel para encriptar los datos sensibles, de manera que solo tu aplicación pueda interpretarlos usando la llave maestra (<code>APP_KEY</code>) de tu entorno.</p>



<figure class="wp-block-image size-full"><img fetchpriority="high" decoding="async" width="648" height="127" src="https://www.mariatech.com.mx/blog/wp-content/uploads/2026/06/Captura-de-pantalla-2026-06-09-130023.png" alt="Uso de la función encrypt en Laravel" class="wp-image-292" srcset="https://www.mariatech.com.mx/blog/wp-content/uploads/2026/06/Captura-de-pantalla-2026-06-09-130023.png 648w, https://www.mariatech.com.mx/blog/wp-content/uploads/2026/06/Captura-de-pantalla-2026-06-09-130023-300x59.png 300w" sizes="(max-width: 648px) 100vw, 648px" /></figure>



<h2 class="wp-block-heading">Creando una arquitectura segura en la Base de Datos</h2>



<p class="wp-block-paragraph">Otro aspecto vital es la separación de responsabilidades. No mezcles la configuración de tus APIs con la tabla general de usuarios de tu sistema.</p>



<p class="wp-block-paragraph">Lo ideal es diseñar una tabla específica (por ejemplo, <code>api_configurations</code>) que contenga únicamente los datos de autenticación: el <em>App ID</em>, el <em>Secret Key</em> y los <em>tokens</em>. Esto te permite aplicar reglas estrictas de acceso, implementar restricciones de unicidad para evitar duplicados en las configuraciones y aislar la información crítica del resto de la lógica de negocio.</p>



<h2 class="wp-block-heading">El flujo de autorización y el control de Callbacks</h2>



<p class="wp-block-paragraph">OAuth 2.0 requiere que el usuario sea redirigido a la plataforma externa para dar su consentimiento y luego regrese a tu aplicación a través de una URL de retorno (Callback).</p>



<p class="wp-block-paragraph">Un buen desarrollador debe blindar esta ruta de retorno. Nunca asumas que la redirección viene con los parámetros correctos. Es indispensable:</p>



<ul class="wp-block-list">
<li>Validar que el código de autorización realmente venga en la URL.</li>



<li>Implementar validaciones de estado para evitar peticiones redundantes.</li>



<li>Manejar bloqueos para que un usuario no pueda acceder a rutas de &#8220;desconexión&#8221; si la cuenta ya ha sido desvinculada previamente.</li>
</ul>



<h2 class="wp-block-heading">Conclusión: La seguridad no es opcional</h2>



<p class="wp-block-paragraph">Conectar una API es un logro técnico, pero asegurarla es lo que distingue a un desarrollo amateur de uno profesional. Laravel nos ofrece todas las herramientas necesarias —desde rutas nombradas hasta encriptación nativa y middlewares— para que nuestras integraciones sean robustas y a prueba de balas.</p>



<p class="wp-block-paragraph"></p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.mariatech.com.mx/blog/laravel-php/como-asegurar-conexiones-a-apis-externas-usando-oauth-2-0-en-laravel/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
