<?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>Tue, 14 Jul 2026 23:15:07 +0000</lastBuildDate>
	<language>es</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.0.1</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>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>
		<item>
		<title>Cómo cambiar la autenticación de GitHub de Access Token a SSH en un proyecto Laravel</title>
		<link>https://www.mariatech.com.mx/blog/laravel-php/como-cambiar-la-autenticacion-de-github-de-access-token-a-ssh-en-un-proyecto-laravel/</link>
					<comments>https://www.mariatech.com.mx/blog/laravel-php/como-cambiar-la-autenticacion-de-github-de-access-token-a-ssh-en-un-proyecto-laravel/#respond</comments>
		
		<dc:creator><![CDATA[Victor Santillan]]></dc:creator>
		<pubDate>Sun, 31 May 2026 23:23:59 +0000</pubDate>
				<category><![CDATA[Laravel / PHP]]></category>
		<category><![CDATA[Git]]></category>
		<guid isPermaLink="false">https://www.mariatech.com.mx/blog/?p=278</guid>

					<description><![CDATA[En este artículo veremos cómo migrar la autenticación de un proyecto desde HTTPS con token hacia SSH, cómo validar que la conexión funcione...]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Cuando trabajamos con proyectos alojados en GitHub, es común que al principio usemos una URL HTTPS con un <strong>Personal Access Token</strong> para clonar, descargar o actualizar el repositorio. Sin embargo, estos tokens pueden vencer, ser revocados o simplemente dejar de funcionar por cambios de seguridad.</p>



<p class="wp-block-paragraph">En este artículo veremos cómo migrar la autenticación de un proyecto desde HTTPS con token hacia SSH, cómo validar que la conexión funcione correctamente y qué hacer cuando Git no permite cambiar de rama porque existen archivos locales modificados, como puede pasar con archivos de configuración en Laravel.</p>



<h2 class="wp-block-heading">El problema: el token de acceso ya venció</h2>



<p class="wp-block-paragraph">Supongamos que tenemos un proyecto Laravel en un servidor de pruebas o demo. Al intentar ejecutar comandos como <code>git fetch</code>, <code>git pull</code> o <code>git checkout</code>, GitHub responde con un error de autenticación porque el token que se usaba anteriormente ya venció.</p>



<p class="wp-block-paragraph">En muchos casos, aunque ya hayamos creado una llave SSH en el servidor, Git seguirá usando el token anterior si el repositorio todavía tiene configurada una URL remota con HTTPS.</p>



<p class="wp-block-paragraph">Es decir, crear una llave SSH no cambia automáticamente la forma en que Git se conecta al repositorio. También debemos actualizar la URL remota del proyecto.</p>



<h2 class="wp-block-heading">1. Revisar la URL remota actual</h2>



<p class="wp-block-paragraph">Primero debemos entrar a la carpeta del proyecto y revisar qué URL remota está usando Git.</p>



<pre class="wp-block-code"><code>cd /ruta/general/del/proyecto

git remote -v</code></pre>



<p class="wp-block-paragraph">Si la respuesta muestra una URL similar a esta:</p>



<pre class="wp-block-code"><code>origin  https://github.com/usuario/repositorio.git (fetch)
origin  https://github.com/usuario/repositorio.git (push)</code></pre>



<p class="wp-block-paragraph">significa que el proyecto todavía está usando HTTPS.</p>



<p class="wp-block-paragraph">En algunos casos, incluso puede aparecer un token dentro de la URL, algo parecido a esto:</p>



<pre class="wp-block-code"><code>origin  https://TOKEN@github.com/usuario/repositorio.git</code></pre>



<p class="wp-block-paragraph">Ese es el escenario que debemos corregir.</p>



<h2 class="wp-block-heading">2. Cambiar la URL remota de HTTPS a SSH</h2>



<p class="wp-block-paragraph">Para que Git use la llave SSH, debemos cambiar la URL remota del repositorio usando el comando <code>git remote set-url</code>.</p>



<pre class="wp-block-code"><code>git remote set-url origin git@github.com:usuario/repositorio.git</code></pre>



<p class="wp-block-paragraph">Este comando cambia el origen del repositorio para que Git deje de usar HTTPS y empiece a usar SSH.</p>



<p class="wp-block-paragraph">Después podemos verificar nuevamente la configuración:</p>



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



<p class="wp-block-paragraph">La salida esperada sería algo como:</p>



<pre class="wp-block-code"><code>origin  git@github.com:usuario/repositorio.git (fetch)
origin  git@github.com:usuario/repositorio.git (push)</code></pre>



<h2 class="wp-block-heading">3. Probar la conexión SSH con GitHub</h2>



<p class="wp-block-paragraph">Antes de intentar descargar ramas o actualizar el proyecto, conviene probar que la llave SSH realmente esté funcionando.</p>



<pre class="wp-block-code"><code>ssh -T git@github.com</code></pre>



<p class="wp-block-paragraph">Si la autenticación es correcta, GitHub responderá con un mensaje indicando que la conexión fue exitosa, aunque no permita acceso de shell. Esto es normal.</p>



<p class="wp-block-paragraph">Una respuesta esperada puede ser similar a:</p>



<pre class="wp-block-code"><code>Hi usuario! You've successfully authenticated, but GitHub does not provide shell access.</code></pre>



<p class="wp-block-paragraph">Con eso confirmamos que la autenticación SSH ya está funcionando.</p>



<h2 class="wp-block-heading">4. Descargar información actualizada del repositorio</h2>



<p class="wp-block-paragraph">Una vez configurada la URL SSH, podemos ejecutar:</p>



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



<p class="wp-block-paragraph">Este comando descarga la información actualizada de las ramas remotas sin mezclar cambios todavía en la rama actual.</p>



<h2 class="wp-block-heading">5. Cambiar a una rama remota</h2>



<p class="wp-block-paragraph">Si necesitamos crear una rama local a partir de una rama remota, podemos usar:</p>



<pre class="wp-block-code"><code>git checkout -b nombre-rama origin/nombre-rama</code></pre>



<p class="wp-block-paragraph">Sin embargo, en proyectos reales puede aparecer un error indicando que un archivo local tiene cambios y que Git necesita sobrescribirlo para cambiar de rama.</p>



<p class="wp-block-paragraph">Un caso común en Laravel puede ser un archivo como:</p>



<pre class="wp-block-code"><code>config/app.php</code></pre>



<h2 class="wp-block-heading">6. ¿Por qué pasa este error?</h2>



<p class="wp-block-paragraph">Git protege los cambios locales para evitar que se pierdan accidentalmente. Si tenemos modificaciones en un archivo y la rama a la que queremos cambiar también modifica ese archivo, Git detiene el proceso.</p>



<p class="wp-block-paragraph">A veces se intenta usar el comando <code>assume-unchanged</code> para que Git ignore temporalmente un archivo:</p>



<pre class="wp-block-code"><code>git update-index --assume-unchanged config/app.php</code></pre>



<p class="wp-block-paragraph">Este comando puede ayudar en algunos escenarios locales, pero no es una solución ideal para manejar archivos de configuración modificados en servidores o ambientes de demo.</p>



<p class="wp-block-paragraph">Además, aunque un archivo esté marcado como <code>assume-unchanged</code>, Git aún puede bloquear operaciones si necesita proteger cambios locales antes de cambiar de rama.</p>



<h2 class="wp-block-heading">7. Revisar si un archivo está marcado como assume-unchanged</h2>



<p class="wp-block-paragraph">Podemos revisar el estado del archivo con:</p>



<pre class="wp-block-code"><code>git ls-files -v config/app.php</code></pre>



<p class="wp-block-paragraph">Si el archivo aparece con una letra minúscula al inicio, significa que está marcado como <code>assume-unchanged</code>.</p>



<h2 class="wp-block-heading">8. Quitar assume-unchanged</h2>



<p class="wp-block-paragraph">Antes de resolver el conflicto, es recomendable quitar esa marca para que Git vuelva a tratar el archivo normalmente.</p>



<pre class="wp-block-code"><code>git update-index --no-assume-unchanged config/app.php</code></pre>



<h2 class="wp-block-heading">9. Opción segura: guardar temporalmente los cambios con stash</h2>



<p class="wp-block-paragraph">Si necesitamos conservar los cambios locales del archivo, podemos guardarlos temporalmente con <code>git stash</code>.</p>



<pre class="wp-block-code"><code>git stash push -m "Backup config app" -- config/app.php</code></pre>



<p class="wp-block-paragraph">Después ya podemos intentar cambiar de rama:</p>



<pre class="wp-block-code"><code>git checkout -b nombre-rama origin/nombre-rama</code></pre>



<p class="wp-block-paragraph">Y si necesitamos recuperar los cambios guardados:</p>



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



<p class="wp-block-paragraph">Si al recuperar los cambios aparecen conflictos, deberán resolverse manualmente.</p>



<h2 class="wp-block-heading">10. Opción rápida: descartar cambios locales</h2>



<p class="wp-block-paragraph">Si los cambios locales no son importantes, podemos descartarlos.</p>



<pre class="wp-block-code"><code>git restore config/app.php</code></pre>



<p class="wp-block-paragraph">Después intentamos nuevamente cambiar de rama:</p>



<pre class="wp-block-code"><code>git checkout -b nombre-rama origin/nombre-rama</code></pre>



<p class="wp-block-paragraph">Esta opción debe usarse con cuidado, porque elimina los cambios locales del archivo.</p>



<h2 class="wp-block-heading">11. Mejor práctica en Laravel: usar .env para configuración por ambiente</h2>



<p class="wp-block-paragraph">En Laravel, lo más recomendable es evitar modificar archivos como <code>config/app.php</code> directamente en cada servidor. Si una configuración cambia dependiendo del ambiente, lo ideal es manejarla desde el archivo <code>.env</code>.</p>



<p class="wp-block-paragraph">Por ejemplo, en lugar de dejar un valor fijo dentro de <code>config/app.php</code>, podemos leerlo desde una variable de entorno:</p>



<pre class="wp-block-code"><code>'custom_product' =&gt; env('APP_CUSTOM_PRODUCT', false),</code></pre>



<p class="wp-block-paragraph">Y en el archivo <code>.env</code> del servidor:</p>



<pre class="wp-block-code"><code>APP_CUSTOM_PRODUCT=true</code></pre>



<p class="wp-block-paragraph">De esta manera cada ambiente puede tener su propia configuración sin modificar archivos versionados en Git.</p>



<h2 class="wp-block-heading">12. Flujo recomendado completo</h2>



<p class="wp-block-paragraph">Un flujo seguro para este tipo de situación sería:</p>



<pre class="wp-block-code"><code>cd /ruta/general/del/proyecto

git remote -v

git remote set-url origin git@github.com:usuario/repositorio.git

git remote -v

ssh -T git@github.com

git fetch origin

git update-index --no-assume-unchanged config/app.php

git stash push -m "Backup config app" -- config/app.php

git checkout -b nombre-rama origin/nombre-rama</code></pre>



<p class="wp-block-paragraph">Si después necesitamos recuperar los cambios locales:</p>



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



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



<p class="wp-block-paragraph">Cuando un proyecto deja de autenticarse con GitHub porque venció un access token, una buena solución es migrar la conexión del repositorio a SSH. Para que esto funcione, no basta con crear la llave SSH: también debemos cambiar la URL remota del repositorio.</p>



<p class="wp-block-paragraph">Además, si Git bloquea un cambio de rama por archivos modificados, debemos decidir si queremos conservar esos cambios con <code>git stash</code> o descartarlos con <code>git restore</code>.</p>



<p class="wp-block-paragraph">En proyectos Laravel, la recomendación más limpia es mantener los archivos de configuración versionados sin modificaciones locales y mover los valores específicos de cada ambiente al archivo <code>.env</code>. Esto evita conflictos, mejora el flujo de despliegue y reduce errores al trabajar con ramas en Git.</p>



<p class="wp-block-paragraph">Si necesitas ayuda para crear un proyecto de software en <strong>Maria Tech </strong>contamos con los expertos para apoyarte.</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/como-cambiar-la-autenticacion-de-github-de-access-token-a-ssh-en-un-proyecto-laravel/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Cómo usar Form Request Validation para formularios enviados con Ajax</title>
		<link>https://www.mariatech.com.mx/blog/laravel-php/como-usar-form-request-validation-para-formularios-enviados-con-ajax/</link>
					<comments>https://www.mariatech.com.mx/blog/laravel-php/como-usar-form-request-validation-para-formularios-enviados-con-ajax/#respond</comments>
		
		<dc:creator><![CDATA[Victor Santillan]]></dc:creator>
		<pubDate>Sun, 31 May 2026 04:38:25 +0000</pubDate>
				<category><![CDATA[Laravel / PHP]]></category>
		<guid isPermaLink="false">https://www.mariatech.com.mx/blog/?p=275</guid>

					<description><![CDATA[En este artículo veremos una forma práctica de trabajar con Request validation con formularios Ajax...]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Cuando trabajamos con formularios en Laravel, lo más común es validar la información usando <strong>Form Requests</strong>. Esto permite mantener los controladores limpios y centralizar las reglas de validación en clases independientes.</p>



<p class="wp-block-paragraph">Sin embargo, cuando el formulario se envía por medio de <strong>Ajax</strong>, especialmente desde modales o componentes dinámicos, surge una duda común: ¿cómo devolvemos los errores de validación en formato JSON sin dejar de usar la estructura nativa de Laravel?</p>



<p class="wp-block-paragraph">En este artículo veremos una forma práctica de trabajar con <strong>Request validation</strong> con formularios Ajax, respuestas JSON y códigos HTTP correctos.</p>



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



<p class="wp-block-paragraph">En un formulario tradicional, si la validación falla, Laravel redirige automáticamente al usuario a la página anterior y muestra los errores en la vista.</p>



<p class="wp-block-paragraph">Pero cuando usamos Ajax, no queremos una redirección. Queremos una respuesta JSON que podamos procesar desde JavaScript para mostrar los errores dentro de un modal, un toast o una alerta.</p>



<p class="wp-block-paragraph">Una solución rápida suele ser hacer la validación directamente en el controlador:</p>



<pre class="wp-block-code"><code>$validator = Validator::make($request-&gt;all(), $rules, $messages);

if ($validator-&gt;fails()) {
    return response()-&gt;json(&#91;
        'success' =&gt; false,
        'errors' =&gt; $validator-&gt;errors()-&gt;all(),
    ], 422);
}</code></pre>



<p class="wp-block-paragraph">Aunque esto funciona, tiene una desventaja importante: la validación queda mezclada dentro del controlador. Conforme el proyecto crece, esto vuelve el código más difícil de mantener.</p>



<h2 class="wp-block-heading">La mejor opción: seguir usando Form Request</h2>



<p class="wp-block-paragraph">Laravel permite seguir usando Form Requests aun cuando el formulario se envía por Ajax. Lo único que necesitamos es personalizar la respuesta cuando la validación falla.</p>



<p class="wp-block-paragraph">Primero, podemos crear un Form Request:</p>



<pre class="wp-block-code"><code>php artisan make:request Admin/User/StoreUserRequest</code></pre>



<p class="wp-block-paragraph">Luego definimos las reglas de validación:</p>



<pre class="wp-block-code"><code>namespace App\Http\Requests\Admin\User;

use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;

class StoreUserRequest extends FormRequest
{
    public function authorize(): bool
    {
        return true;
    }

    public function rules(): array
    {
        return &#91;
            'name' => &#91;'required', 'string', 'max:255'],
            'last_name' => &#91;'required', 'string', 'max:255'],
            'email' => &#91;'required', 'email', 'max:255', Rule::unique('users', 'email')],
            'phone' => &#91;'nullable', 'digits:10'],
            'id_profile_type' => &#91;'required', 'exists:profile_types,id'],
            'password' => &#91;'required', 'string', 'min:8', 'confirmed'],
            'enabled' => &#91;'nullable', 'boolean'],
        ];
    }
}</code></pre>



<h2 class="wp-block-heading">Personalizar la respuesta JSON de validación</h2>



<p class="wp-block-paragraph">Por defecto, Laravel devuelve los errores agrupados por campo:</p>



<pre class="wp-block-code"><code>{
    "message": "The given data was invalid.",
    "errors": {
        "email": &#91;
            "Este correo electrónico ya está registrado."
        ],
        "password": &#91;
            "La contraseña debe tener al menos 8 caracteres."
        ]
    }
}</code></pre>



<p class="wp-block-paragraph">Este formato es útil cuando queremos marcar errores campo por campo. Pero si nuestra interfaz usa modales y queremos mostrar una lista general de errores, puede ser más cómodo devolver una lista plana.</p>



<p class="wp-block-paragraph">Para eso podemos sobrescribir el método <code>failedValidation</code> dentro del StoreUserRequest:</p>



<pre class="wp-block-code"><code>use Illuminate\Contracts\Validation\Validator;
use Illuminate\Http\Exceptions\HttpResponseException;

protected function failedValidation(Validator $validator): void
{
    throw new HttpResponseException(response()-&gt;json(&#91;
        'success' =&gt; false,
        'message' =&gt; 'Hay errores en el formulario.',
        'errors' =&gt; $validator-&gt;errors()-&gt;all(),
    ], 422));
}</code></pre>



<p class="wp-block-paragraph">Con esto, la respuesta quedará así:</p>



<pre class="wp-block-code"><code>{
    "success": false,
    "message": "Hay errores en el formulario.",
    "errors": &#91;
        "El nombre es obligatorio.",
        "Este correo electrónico ya está registrado.",
        "La contraseña debe tener al menos 8 caracteres."
    ]
}</code></pre>



<h2 class="wp-block-heading">Controlador limpio usando el Form Request</h2>



<p class="wp-block-paragraph">Una vez que el Form Request está listo, el controlador puede mantenerse limpio:</p>



<pre class="wp-block-code"><code>use App\Http\Requests\Admin\User\StoreUserRequest;
use App\Models\User;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Hash;
use Throwable;

public function store(StoreUserRequest $request): JsonResponse
{
    try {
        $validated = $request-&gt;validated();

        $user = User::create(&#91;
            'name' =&gt; $validated&#91;'name'],
            'last_name' =&gt; $validated&#91;'last_name'],
            'email' =&gt; $validated&#91;'email'],
            'phone' =&gt; $validated&#91;'phone'] ?? null,
            'id_profile_type' =&gt; $validated&#91;'id_profile_type'],
            'password' =&gt; Hash::make($validated&#91;'password']),
            'enabled' =&gt; $validated&#91;'enabled'] ?? 1,
        ]);

        return response()-&gt;json(&#91;
            'success' =&gt; true,
            'message' =&gt; 'Usuario registrado correctamente.',
            'user_id' =&gt; $user-&gt;id,
        ], 201);

    } catch (Throwable $e) {
        report($e);

        return response()-&gt;json(&#91;
            'success' =&gt; false,
            'message' =&gt; 'No fue posible registrar el usuario. Inténtalo nuevamente.',
        ], 500);
    }
}</code></pre>



<h2 class="wp-block-heading">Códigos HTTP recomendados</h2>



<p class="wp-block-paragraph">Es importante regresar códigos HTTP correctos para que el frontend pueda interpretar bien la respuesta.</p>



<ul class="wp-block-list">
<li><strong>200:</strong> operación correcta.</li>



<li><strong>201:</strong> registro creado correctamente.</li>



<li><strong>401:</strong> usuario no autenticado.</li>



<li><strong>403:</strong> usuario autenticado, pero sin permisos.</li>



<li><strong>404:</strong> recurso no encontrado.</li>



<li><strong>422:</strong> error de validación.</li>



<li><strong>500:</strong> error inesperado del servidor.</li>
</ul>



<p class="wp-block-paragraph">Para formularios Ajax, una convención práctica sería:</p>



<pre class="wp-block-code"><code>Validación fallida  → 422
Creación correcta   → 201
Actualización       → 200
Eliminación         → 200
No encontrado       → 404
Sin permisos        → 403
Error inesperado    → 500</code></pre>



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



<p class="wp-block-paragraph">Usar Form Requests con formularios Ajax es una forma limpia y profesional de validar datos en Laravel sin sacrificar la experiencia de usuario.</p>



<p class="wp-block-paragraph">La clave está en no llevar las reglas de validación al controlador, sino mantenerlas dentro del Request y personalizar la respuesta JSON cuando la validación falla.</p>



<p class="wp-block-paragraph">Con esta estructura logramos:</p>



<ul class="wp-block-list">
<li>Controladores más limpios.</li>



<li>Validaciones reutilizables.</li>



<li>Respuestas JSON consistentes.</li>



<li>Códigos HTTP correctos.</li>



<li>Mejor integración con formularios en modales.</li>



<li>Mejor experiencia de usuario usando Ajax.</li>
</ul>



<p class="wp-block-paragraph">Esta forma de trabajo es especialmente útil en paneles administrativos, sistemas internos, CRMs, ERPs y plataformas donde los formularios se muestran en modales o secciones dinámicas.</p>



<p class="wp-block-paragraph">Si necesitas ayuda para crear un proyecto de software en <strong>Maria Tech </strong>contamos con los expertos para apoyarte.</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>



<p class="wp-block-paragraph"></p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.mariatech.com.mx/blog/laravel-php/como-usar-form-request-validation-para-formularios-enviados-con-ajax/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Cómo implementar recuperación de contraseña para administradores y clientes en Laravel 13</title>
		<link>https://www.mariatech.com.mx/blog/laravel-php/como-implementar-recuperacion-de-contrasena-para-administradores-y-clientes-en-laravel-13/</link>
					<comments>https://www.mariatech.com.mx/blog/laravel-php/como-implementar-recuperacion-de-contrasena-para-administradores-y-clientes-en-laravel-13/#respond</comments>
		
		<dc:creator><![CDATA[Victor Santillan]]></dc:creator>
		<pubDate>Sun, 24 May 2026 02:56:48 +0000</pubDate>
				<category><![CDATA[Laravel / PHP]]></category>
		<guid isPermaLink="false">https://www.mariatech.com.mx/blog/?p=267</guid>

					<description><![CDATA[En este artículo veremos cómo estructurar un proceso de recuperación de contraseña en Laravel para dos tipos de usuarios: administradores y clientes.]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">En proyectos web con áreas separadas para administradores y clientes, es común que ambos tipos de usuarios necesiten iniciar sesión, cerrar sesión y recuperar su contraseña. El reto técnico aparece cuando cada tipo de usuario vive en una tabla diferente y debe tener su propio flujo de autenticación.</p>



<p class="wp-block-paragraph">En este artículo veremos cómo estructurar un proceso de recuperación de contraseña en Laravel 13 para dos tipos de usuarios: <strong>administradores</strong> y <strong>clientes</strong>. El objetivo es mantener una arquitectura limpia, segura y fácil de mantener.</p>



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



<p class="wp-block-paragraph">Laravel Breeze instala un sistema de autenticación funcional, pero por defecto está pensado para trabajar principalmente con una sola tabla de usuarios, normalmente <code>users</code>.</p>



<p class="wp-block-paragraph">Sin embargo, en proyectos más completos podemos necesitar algo como esto:</p>



<ul class="wp-block-list">
<li><strong>Administradores:</strong> usuarios internos del sistema, almacenados en la tabla <code>users</code>.</li>



<li><strong>Clientes:</strong> usuarios externos que acceden al sitio o portal del cliente, almacenados en la tabla <code>customers</code>.</li>
</ul>



<p class="wp-block-paragraph">En este escenario no conviene mezclar ambos tipos de usuario en la misma tabla, porque normalmente tienen permisos, vistas, reglas de negocio y flujos diferentes.</p>



<h2 class="wp-block-heading">Estructura recomendada</h2>



<pre class="wp-block-code"><code>app/
├── Http/
│   ├── Controllers/
│   │   ├── Admin/
│   │   │   └── Auth/
│   │   │       ├── PasswordResetLinkController.php
│   │   │       └── NewPasswordController.php
│   │   └── Customer/
│   │       └── Auth/
│   │           ├── PasswordResetLinkController.php
│   │           └── NewPasswordController.php

resources/
├── views/
│   ├── admin/
│   │   └── auth/
│   │       ├── forgot-password.blade.php
│   │       └── reset-password.blade.php
│   └── customer/
│       └── auth/
│           ├── forgot-password.blade.php
│           └── reset-password.blade.php

routes/
├── admin.php
└── customer.php</code></pre>



<p class="wp-block-paragraph">La idea es separar cada flujo para evitar dependencias innecesarias y tener rutas, controladores, vistas y brokers independientes.</p>



<h2 class="wp-block-heading">Configuración de autenticación</h2>



<p class="wp-block-paragraph">En <code>config/auth.php</code> podemos definir un guard y un provider para cada tipo de usuario.</p>



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

use App\Models\User;
use App\Models\Customer;

return &#91;

    'defaults' =&gt; &#91;
        'guard' =&gt; 'web',
        'passwords' =&gt; 'users',
    ],

    'guards' =&gt; &#91;
        'web' =&gt; &#91;
            'driver' =&gt; 'session',
            'provider' =&gt; 'users',
        ],

        'customer' =&gt; &#91;
            'driver' =&gt; 'session',
            'provider' =&gt; 'customers',
        ],
    ],

    'providers' =&gt; &#91;
        'users' =&gt; &#91;
            'driver' =&gt; 'eloquent',
            'model' =&gt; User::class,
        ],

        'customers' =&gt; &#91;
            'driver' =&gt; 'eloquent',
            'model' =&gt; Customer::class,
        ],
    ],

    'passwords' =&gt; &#91;
        'users' =&gt; &#91;
            'provider' =&gt; 'users',
            'table' =&gt; 'password_reset_tokens',
            'expire' =&gt; 60,
            'throttle' =&gt; 60,
        ],

        'customers' =&gt; &#91;
            'provider' =&gt; 'customers',
            'table' =&gt; 'customer_password_reset_tokens',
            'expire' =&gt; 60,
            'throttle' =&gt; 60,
        ],
    ],

];</code></pre>



<p class="wp-block-paragraph">El punto clave está en la sección <code>passwords</code>. Ahí definimos dos brokers: <code>users</code> para administradores y <code>customers</code> para clientes. Cada broker usa su propio provider y su propia tabla de tokens.</p>



<h2 class="wp-block-heading">Migración para tokens de clientes</h2>



<pre class="wp-block-code"><code>Schema::create('customer_password_reset_tokens', function (Blueprint $table) {
    $table-&gt;string('email')-&gt;primary();
    $table-&gt;string('token');
    $table-&gt;timestamp('created_at')-&gt;nullable();
});</code></pre>



<p class="wp-block-paragraph">Esto permite que el flujo de administradores y clientes quede completamente separado.</p>



<h2 class="wp-block-heading">Rutas para administradores</h2>



<pre class="wp-block-code"><code>use App\Http\Controllers\Admin\Auth\PasswordResetLinkController;
use App\Http\Controllers\Admin\Auth\NewPasswordController;
use Illuminate\Support\Facades\Route;

Route::middleware('admin.guest')->group(function () {
    Route::get('/forgot-password', &#91;PasswordResetLinkController::class, 'create'])
        ->name('password.request');

    Route::post('/forgot-password', &#91;PasswordResetLinkController::class, 'store'])
        ->name('password.email');

    Route::get('/reset-password/{token}', &#91;NewPasswordController::class, 'create'])
        ->name('password.reset');

    Route::post('/reset-password', &#91;NewPasswordController::class, 'store'])
        ->name('password.store');
});</code></pre>



<h2 class="wp-block-heading">Rutas para clientes</h2>



<pre class="wp-block-code"><code>use App\Http\Controllers\Customer\Auth\PasswordResetLinkController;
use App\Http\Controllers\Customer\Auth\NewPasswordController;
use Illuminate\Support\Facades\Route;

Route::middleware('customer.guest')->group(function () {
    Route::get('/forgot-password', &#91;PasswordResetLinkController::class, 'create'])
        ->name('password.request');

    Route::post('/forgot-password', &#91;PasswordResetLinkController::class, 'store'])
        ->name('password.email');

    Route::get('/reset-password/{token}', &#91;NewPasswordController::class, 'create'])
        ->name('password.reset');

    Route::post('/reset-password', &#91;NewPasswordController::class, 'store'])
        ->name('password.store');
});</code></pre>



<h2 class="wp-block-heading">Controlador para enviar el enlace de recuperación</h2>



<p class="wp-block-paragraph">El controlador encargado de enviar el enlace de recuperación debe usar el broker correcto.</p>



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



<pre class="wp-block-code"><code>namespace App\Http\Controllers\Admin\Auth;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Password;
use Illuminate\Validation\ValidationException;

class PasswordResetLinkController extends Controller
{
    public function create()
    {
        return view('admin.auth.forgot-password');
    }

    public function store(Request $request)
    {
        $request-&gt;validate(&#91;
            'email' =&gt; &#91;'required', 'email'],
        ]);

        $status = Password::broker('users')-&gt;sendResetLink(
            $request-&gt;only('email')
        );

        if ($status === Password::RESET_LINK_SENT) {
            return back()-&gt;with('status', __($status));
        }

        throw ValidationException::withMessages(&#91;
            'email' =&gt; __($status),
        ]);
    }
}</code></pre>



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



<pre class="wp-block-code"><code>namespace App\Http\Controllers\Customer\Auth;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Password;
use Illuminate\Validation\ValidationException;

class PasswordResetLinkController extends Controller
{
    public function create()
    {
        return view('customer.auth.forgot-password');
    }

    public function store(Request $request)
    {
        $request-&gt;validate(&#91;
            'email' =&gt; &#91;'required', 'email'],
        ]);

        $status = Password::broker('customers')-&gt;sendResetLink(
            $request-&gt;only('email')
        );

        if ($status === Password::RESET_LINK_SENT) {
            return back()-&gt;with('status', __($status));
        }

        throw ValidationException::withMessages(&#91;
            'email' =&gt; __($status),
        ]);
    }
}</code></pre>



<p class="wp-block-paragraph">El cambio central es el broker:</p>



<pre class="wp-block-code"><code>Password::broker('users')      // Administradores
Password::broker('customers')  // Clientes</code></pre>



<h2 class="wp-block-heading">Controlador para actualizar la contraseña</h2>



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



<pre class="wp-block-code"><code>namespace App\Http\Controllers\Admin\Auth;

use App\Http\Controllers\Controller;
use Illuminate\Auth\Events\PasswordReset;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Password;
use Illuminate\Support\Str;
use Illuminate\Validation\Rules;

class NewPasswordController extends Controller
{
    public function create(Request $request)
    {
        return view('admin.auth.reset-password', &#91;
            'request' =&gt; $request,
        ]);
    }

    public function store(Request $request)
    {
        $request-&gt;validate(&#91;
            'token' =&gt; &#91;'required'],
            'email' =&gt; &#91;'required', 'email'],
            'password' =&gt; &#91;'required', 'confirmed', Rules\Password::defaults()],
        ]);

        $status = Password::broker('users')-&gt;reset(
            $request-&gt;only('email', 'password', 'password_confirmation', 'token'),
            function ($user) use ($request) {
                $user-&gt;forceFill(&#91;
                    'password' =&gt; Hash::make($request-&gt;password),
                    'remember_token' =&gt; Str::random(60),
                ])-&gt;save();

                event(new PasswordReset($user));
            }
        );

        return $status === Password::PASSWORD_RESET
            ? redirect()-&gt;route('admin.login')-&gt;with('status', __($status))
            : back()-&gt;withInput($request-&gt;only('email'))
                -&gt;withErrors(&#91;'email' =&gt; __($status)]);
    }
}</code></pre>



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



<pre class="wp-block-code"><code>namespace App\Http\Controllers\Customer\Auth;

use App\Http\Controllers\Controller;
use Illuminate\Auth\Events\PasswordReset;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Password;
use Illuminate\Support\Str;
use Illuminate\Validation\Rules;

class NewPasswordController extends Controller
{
    public function create(Request $request)
    {
        return view('customer.auth.reset-password', &#91;
            'request' =&gt; $request,
        ]);
    }

    public function store(Request $request)
    {
        $request-&gt;validate(&#91;
            'token' =&gt; &#91;'required'],
            'email' =&gt; &#91;'required', 'email'],
            'password' =&gt; &#91;'required', 'confirmed', Rules\Password::defaults()],
        ]);

        $status = Password::broker('customers')-&gt;reset(
            $request-&gt;only('email', 'password', 'password_confirmation', 'token'),
            function ($customer) use ($request) {
                $customer-&gt;forceFill(&#91;
                    'password' =&gt; Hash::make($request-&gt;password),
                    'remember_token' =&gt; Str::random(60),
                ])-&gt;save();

                event(new PasswordReset($customer));
            }
        );

        return $status === Password::PASSWORD_RESET
            ? redirect()-&gt;route('customer.login')-&gt;with('status', __($status))
            : back()-&gt;withInput($request-&gt;only('email'))
                -&gt;withErrors(&#91;'email' =&gt; __($status)]);
    }
}</code></pre>



<h2 class="wp-block-heading">Personalizar la URL del correo de recuperación</h2>



<p class="wp-block-paragraph">Un detalle importante es que Laravel genera el enlace de recuperación usando una notificación. Si no personalizamos ese enlace, puede terminar enviando al usuario al flujo equivocado.</p>



<p class="wp-block-paragraph">Para resolverlo, podemos agregar la lógica en <code>app/Providers/AppServiceProvider.php</code>.</p>



<pre class="wp-block-code"><code>namespace App\Providers;

use App\Models\Customer;
use App\Models\User;
use Illuminate\Auth\Notifications\ResetPassword;
use Illuminate\Support\Facades\URL;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        //
    }

    public function boot(): void
    {
        ResetPassword::createUrlUsing(function (object $notifiable, string $token): string {
            if ($notifiable instanceof Customer) {
                return URL::route('customer.password.reset', &#91;
                    'token' =&gt; $token,
                    'email' =&gt; $notifiable-&gt;getEmailForPasswordReset(),
                ]);
            }

            if ($notifiable instanceof User) {
                return URL::route('admin.password.reset', &#91;
                    'token' =&gt; $token,
                    'email' =&gt; $notifiable-&gt;getEmailForPasswordReset(),
                ]);
            }

            return URL::route('customer.password.reset', &#91;
                'token' =&gt; $token,
                'email' =&gt; $notifiable-&gt;getEmailForPasswordReset(),
            ]);
        });
    }
}</code></pre>



<p class="wp-block-paragraph">Con esto, cuando el modelo sea <code>User</code>, Laravel enviará al flujo de administrador. Cuando el modelo sea <code>Customer</code>, enviará al flujo de cliente.</p>



<h2 class="wp-block-heading">Vistas de recuperación de contraseña</h2>



<p class="wp-block-paragraph">En cada formulario es importante apuntar a la ruta correcta.</p>



<h3 class="wp-block-heading">Formulario para solicitar enlace</h3>



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



<pre class="wp-block-code"><code>&lt;form method="POST" action="{{ route('admin.password.email') }}">
    @csrf
    &lt;input type="email" name="email" required>
    &lt;button type="submit">
        Enviar enlace
    &lt;/button>
&lt;/form></code></pre>



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



<pre class="wp-block-code"><code>&lt;form method="POST" action="{{ route('customer.password.email') }}">
    @csrf
    &lt;input type="email" name="email" required>
    &lt;button type="submit">
        Enviar enlace
    &lt;/button>
&lt;/form></code></pre>



<h3 class="wp-block-heading">Formulario para crear nueva contraseña</h3>



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



<pre class="wp-block-code"><code>&lt;form method="POST" action="{{ route('admin.password.store') }}">
    @csrf
    &lt;input type="hidden" name="token" value="{{ $request->route('token') }}">
    &lt;input type="email" name="email" value="{{ old('email', $request->email) }}" required>
    &lt;input type="password" name="password" required>
    &lt;input type="password" name="password_confirmation" required>
    &lt;button type="submit">
        Actualizar contraseña
    &lt;/button>
&lt;/form></code></pre>



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



<pre class="wp-block-code"><code>&lt;form method="POST" action="{{ route('customer.password.store') }}">
    @csrf
    &lt;input type="hidden" name="token" value="{{ $request->route('token') }}">
    &lt;input type="email" name="email" value="{{ old('email', $request->email) }}" required>
    &lt;input type="password" name="password" required>
    &lt;input type="password" name="password_confirmation" required>
    &lt;button type="submit">
        Actualizar contraseña
    &lt;/button>
&lt;/form></code></pre>



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



<ul class="wp-block-list">
<li>Usar modelos separados cuando los usuarios internos y externos tienen responsabilidades diferentes.</li>



<li>Separar rutas, vistas y controladores por tipo de usuario.</li>



<li>Usar un broker de contraseña diferente para cada tabla.</li>



<li>Personalizar la URL del correo para evitar que el usuario llegue al flujo equivocado.</li>



<li>No depender completamente de la estructura por defecto de Breeze cuando el proyecto requiere múltiples áreas de autenticación.</li>



<li>Mantener nombres claros como <code>Admin</code> y <code>Customer</code> para facilitar el mantenimiento.</li>
</ul>



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



<p class="wp-block-paragraph">La recuperación de contraseña para múltiples tipos de usuarios en Laravel requiere separar correctamente guards, providers, brokers, rutas, controladores y vistas. Aunque Breeze ofrece una buena base inicial, en proyectos con administradores y clientes conviene tomar el control del flujo para evitar cruces entre autenticaciones.</p>



<p class="wp-block-paragraph">La clave está en usar un broker distinto para cada tipo de usuario:</p>



<pre class="wp-block-code"><code>Password::broker('users')      // Administradores
Password::broker('customers')  // Clientes</code></pre>



<p class="wp-block-paragraph">Y personalizar el enlace generado por correo desde <code>AppServiceProvider</code>, para que cada usuario llegue al formulario correcto.</p>



<p class="wp-block-paragraph">Con esta estructura, el sistema queda más limpio, seguro y preparado para crecer.</p>



<p class="wp-block-paragraph">¡Si te ha servido de algo este artículo no olvides dejarnos un comentario!</p>



<p class="wp-block-paragraph"></p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.mariatech.com.mx/blog/laravel-php/como-implementar-recuperacion-de-contrasena-para-administradores-y-clientes-en-laravel-13/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
