<?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>MySQL &#8211; Blog Maria Tech</title>
	<atom:link href="https://www.mariatech.com.mx/blog/categoria/mysql/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>Wed, 16 Sep 2026 01:35:22 +0000</lastBuildDate>
	<language>es</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.1.3</generator>

<image>
	<url>https://www.mariatech.com.mx/blog/wp-content/uploads/2024/05/cropped-icon_wordpress-32x32.png</url>
	<title>MySQL &#8211; Blog Maria Tech</title>
	<link>https://www.mariatech.com.mx/blog</link>
	<width>32</width>
	<height>32</height>
</image> 
	<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>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>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>
		<item>
		<title>Cómo saber si un registro está duplicado en MySQL</title>
		<link>https://www.mariatech.com.mx/blog/mysql/como-saber-si-un-registro-esta-duplicado-en-mysql/</link>
					<comments>https://www.mariatech.com.mx/blog/mysql/como-saber-si-un-registro-esta-duplicado-en-mysql/#respond</comments>
		
		<dc:creator><![CDATA[Victor Santillan]]></dc:creator>
		<pubDate>Wed, 15 May 2024 15:28:59 +0000</pubDate>
				<category><![CDATA[MySQL]]></category>
		<guid isPermaLink="false">https://www.mariatech.com.mx/blog/?p=140</guid>

					<description><![CDATA[En algunas ocasiones es necesario saber si tenemos registros duplicados en nuestra base de datos, en este artículo veremos como obtenerlos...]]></description>
										<content:encoded><![CDATA[<p>En algunas ocasiones es necesario saber si tenemos registros duplicados en nuestra base de datos, como lo pueden ser; correo electrónico, algún identificador, número de teléfono o cualquier otro dato.</p>
<p>Para hacer esto lo podemos hacer con una consulta utilizando los comandos de <em>COUNT</em>, <em>GROUP BY</em> y <em>HAVING</em>,  vamos a ver como sería nuestra consulta:</p>


<pre class="wp-block-code"><code>SELECT name,COUNT(*) FROM clients
GROUP BY name
HAVING COUNT(*) &gt; 1;</code></pre>



<p class="wp-block-paragraph">En esta consulta estamos solicitando de la tabla &#8220;clients&#8221; los registros donde el nombre se repita más de una vez.</p>



<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="168" src="https://www.mariatech.com.mx/blog/wp-content/uploads/2024/05/registro_dos-1024x168.png" alt="" class="wp-image-142" srcset="https://www.mariatech.com.mx/blog/wp-content/uploads/2024/05/registro_dos-1024x168.png 1024w, https://www.mariatech.com.mx/blog/wp-content/uploads/2024/05/registro_dos-300x49.png 300w, https://www.mariatech.com.mx/blog/wp-content/uploads/2024/05/registro_dos-768x126.png 768w, https://www.mariatech.com.mx/blog/wp-content/uploads/2024/05/registro_dos-1536x252.png 1536w, https://www.mariatech.com.mx/blog/wp-content/uploads/2024/05/registro_dos-2048x335.png 2048w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>


<p>Ahora vamos a explicar qué hace cada comando:</p>
<p><em>COUNT</em>: Regresa el número de registros.</p>
<p><em>GROUP BY</em>: Agrupa las filas que tiene el mismo valor y devuelve una única fila de cada agrupación realizada.</p>
<p><em>HAVING</em>: Específica la condición que se aplica a los grupos, cuándo tenemos el comando <em>GROUP BY</em> no podemos usar la clausula <em>WHERE</em>, así que ahí es donde usamos <em>HAVING.</em></p>
<p>Si quieres saber más sobre estos comandos puedes buscar en la documentación de MySQL:</p>


<p class="wp-block-paragraph"><a href="https://dev.mysql.com/doc">https://dev.mysql.com/doc</a></p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.mariatech.com.mx/blog/mysql/como-saber-si-un-registro-esta-duplicado-en-mysql/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
