> ## Documentation Index
> Fetch the complete documentation index at: https://docs.stockshift.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Laravel

> Usa la API de StockShift desde una app de Laravel. El paquete se basa en el SDK de PHP de StockShift. Agrega configuración, un facade, consultas que devuelven colecciones, una ruta de webhook que dispara eventos de Laravel, llamadas en cola, un fake de prueba y comandos de Artisan.

Requiere PHP 8.2 o posterior y Laravel 12 o 13.

## Instalación

```bash theme={null}
composer require stockshift/laravel
php artisan stockshift:install
```

`stockshift:install` publica `config/stockshift.php`, agrega `STOCKSHIFT_TOKEN` y `STOCKSHIFT_WEBHOOK_SECRET` a `.env` y `.env.example` y te pide sus valores. Crea una clave de API en StockShift, en Configuración > Acceso > Claves de API. Luego compruébala:

```bash theme={null}
php artisan stockshift:test
```

## Inicio rápido

```php theme={null}
use StockShift\Laravel\Facades\StockShift;

StockShift::catalog()->createItem(['sku' => 'BOLT-M4', 'name' => 'M4 hex bolt', 'type' => 'stocked', 'make_or_buy' => 'buy']);

$low = StockShift::stock()
    ->where('status', 'available')
    ->orderBy('available_to_promise')
    ->lazy();

foreach ($low as $row) {
    echo $row['sku'], ' ', $row['available_to_promise'], "\n";
}
```

## Hacer llamadas

El facade tiene todas las áreas de la API, como `catalog()` o `inventory()`, y cada área tiene un método por cada operación. Son los métodos del SDK, con los mismos argumentos y tipos de PHPStan:

```php theme={null}
$item = StockShift::catalog()->getItem('01J8QZ4M...');
echo $item['data']['sku'];
```

También puedes llamar a una ruta directamente con `get()`, `post()`, `put()`, `patch()`, `delete()` o `request()`:

```php theme={null}
StockShift::get('/catalog/items/{item}', ['path' => ['item' => '01J8QZ4M...']]);
```

El facade, un `StockShift\Laravel\Connection` inyectado y un `StockShift\Sdk\StockShift` inyectado usan todos la conexión predeterminada.

## Consultas

Cada lista de la API tiene un constructor de consultas. `where()` envía los filtros propios de la API y los resultados llegan como colecciones de Laravel:

```php theme={null}
$bolts = StockShift::items()
    ->search('bolt')
    ->where('status', 'active')
    ->select('sku', 'name')
    ->get();                  // Collection

$first = StockShift::items()->where('type', 'stocked')->first();
$count = StockShift::purchaseOrders()->where('status', 'sent')->count();
```

`lazy()` devuelve una `LazyCollection` que obtiene la página siguiente solo cuando llegas a ella. Con `limit()` o `take()`, las páginas más allá del límite nunca se obtienen:

```php theme={null}
StockShift::movements()->where('item', 'BOLT-M4')->lazy()->take(50)->each(function (array $movement) {
    // ...
});
```

La API devuelve cada lista en su propio orden fijo. `orderBy()` y `orderByDesc()` ordenan aquí en su lugar, así que leen primero la lista completa. Los campos pueden usar notación de puntos.

Los constructores del facade son `items()`, `stock()`, `movements()`, `counts()`, `transfers()`, `locations()`, `suppliers()`, `purchaseOrders()`, `customers()`, `salesOrders()`, `quotes()`, `invoices()`, `returns()`, `builds()`, `boms()` y `workCenters()`. Cualquier otra lista funciona con su ruta:

```php theme={null}
StockShift::query('/sales/price-lists/{priceList}', ['priceList' => $id])->get();
```

Otros métodos:

| Método | Qué hace |
| - | - |
| `when($condition, fn ($query) => ...)` | Agrega a la consulta solo cuando se cumple la condición. |
| `after($cursor)` | Empieza después de un cursor que guardaste. |
| `page($cursor = null)` | Una página, con `records` y `nextCursor`, para paginar a mano. |
| `remember($ttl = null)` | Guarda en caché los resultados de `get()`, `first()` y `count()`. |
| `toQuery()` | La cadena de consulta que recibirá la API. |

Los enums se envían como su valor y las fechas en ISO 8601.

## Conexiones

Una conexión es un token, el secreto de webhook que le corresponde y su configuración. Agrega más en `config/stockshift.php` para llamar a varias organizaciones:

```php theme={null}
'connections' => [
    'default' => [/* ... */],
    'eu' => [
        'token' => env('STOCKSHIFT_EU_TOKEN'),
        'webhook_secret' => env('STOCKSHIFT_EU_WEBHOOK_SECRET'),
    ],
],
```

```php theme={null}
StockShift::connection('eu')->items()->get();
```

Para inyectar una, marca el parámetro:

```php theme={null}
use StockShift\Laravel\Attributes\StockShiftConnection;
use StockShift\Laravel\Connection;

public function __construct(#[StockShiftConnection('eu')] private Connection $stockshift) {}
```

Cada conexión tiene esta configuración:

| Clave | Valor predeterminado | Significado |
| - | - | - |
| `token` | `STOCKSHIFT_TOKEN` | Una clave de API o un token de acceso OAuth. |
| `webhook_secret` | `STOCKSHIFT_WEBHOOK_SECRET` | El secreto de firma, o varios separados por comas mientras lo rotas. |
| `base_url` | `https://app.stockshift.io/api/v1` | Dónde está la API. |
| `timeout`, `connect_timeout` | `30`, `10` | Segundos. |
| `max_retries` | `2` | Reintentos para una llamada que se puede reintentar. |
| `max_retry_after` | `60` | El `Retry-After` más largo, en segundos, que vale la pena esperar. |
| `rate_limit` | `null` | Llamadas por minuto que hace esta app con un token. |
| `headers` | `[]` | Encabezados que se envían en cada llamada. |

### Un token para cada tenant

En una app donde cada tenant conecta su propia organización de StockShift, busca el token antes de cada llamada:

```php theme={null}
// In a service provider's boot method
StockShift::resolveTokenUsing(fn (string $connection) => tenant()->stockshift_token);
```

O crea una conexión para un solo token donde la necesites:

```php theme={null}
StockShift::withToken($team->stockshift_token)->items()->get();
```

Los resultados de consulta guardados en caché y los límites de solicitudes se mantienen separados para cada token.

## Errores

Una llamada fallida lanza la `ApiException` del SDK, que contiene el cuerpo de detalles del problema de RFC 9457. Decide según `errorCode`, que es estable:

```php theme={null}
use StockShift\Sdk\Exception\ApiException;

try {
    StockShift::catalog()->createItem($item);
} catch (ApiException $error) {
    if ($error->errorCode === 'validation_failed') {
        return back()->withErrors($error->errors());
    }

    throw $error;
}
```

Una llamada que no recibe respuesta lanza una `ConnectionException`. Ambas extienden `StockShiftException`.

## Reintentos, claves de idempotencia y llamadas en cola

El SDK reintenta un `429` después del tiempo que indica `Retry-After`. Reintenta un `5xx` o una falla de red para `GET`, `PUT` y `DELETE`, y para un `POST` o `PATCH` solo cuando tiene una clave de idempotencia:

```php theme={null}
StockShift::integrations()->pushStockNow($connectionId, ['idempotencyKey' => (string) Str::uuid()]);
```

`later()` envía una llamada desde un job en cola. Un `POST` o `PATCH` recibe una clave de idempotencia cuando se crea el job, así que los reintentos del propio job envían la misma clave y la API hace el cambio una sola vez:

```php theme={null}
StockShift::later('POST', '/catalog/items', ['body' => $item])->onQueue('stockshift');
```

El job es `StockShift\Laravel\Jobs\SendRequest`. Lo intenta cinco veces. Una respuesta `4xx` distinta de `429` lo hace fallar de inmediato, porque volver a enviarla da la misma respuesta. Un token de `withToken()` viaja con el job. El job se cifra en la cola.

## Límites de solicitudes

Define `rate_limit` en una conexión para limitar las llamadas por minuto que tu app hace con un token. Todos los procesos que comparten el almacén de caché comparten el conteo. Una llamada que superaría el límite espera hasta que se pueda enviar. Déjalo en `null` para depender del límite de la API, que el cliente ya espera.

Las solicitudes pasan por el cliente HTTP de Laravel, así que `Http::fake()`, los eventos `RequestSending` y `ResponseReceived` y herramientas como Telescope las ven.

## Webhooks

El paquete agrega una ruta `POST /stockshift/webhook`. Dale su URL completa a StockShift cuando agregues un endpoint de webhook y pon en `STOCKSHIFT_WEBHOOK_SECRET` el secreto de firma que te muestra StockShift.

Cada entrega se revisa contra el encabezado `StockShift-Signature`. Una que falla recibe un `400` y no avanza más. Una que pasa dispara dos eventos de Laravel: `WebhookReceived` para cada entrega y luego la clase propia del evento, como `InventoryStockMoved`:

```php theme={null}
use Illuminate\Support\Facades\Event;
use StockShift\Laravel\Events\InventoryStockMoved;

Event::listen(function (InventoryStockMoved $event) {
    // $event->id, $event->type, $event->created, $event->data['item_id'], $event->connection
});
```

Cada tipo de evento de la referencia de la API tiene una clase en `StockShift\Laravel\Events`, con `$data` tipado para PHPStan. `WebhookReceived` también se dispara para los tipos que esta versión del paquete no tiene como clase.

### Escuchar con un atributo

Marca una clase o un método con `#[OnStockShiftWebhook]`:

```php theme={null}
namespace App\Listeners;

use StockShift\Laravel\Attributes\OnStockShiftWebhook;
use StockShift\Laravel\Events\InventoryStockMoved;

#[OnStockShiftWebhook('inventory.stock.moved')]
class UpdateShelfLabels
{
    public function handle(InventoryStockMoved $event): void
    {
        // ...
    }
}
```

En una clase llama a `handle()` o `__invoke()`. En un método llama a ese método. Indica varios tipos, o termina uno con `*` para coincidir con muchos, como `sales.order.*`. Tipa el parámetro como `WebhookEvent` cuando pueda recibir más de una clase. `*` solo escucha `WebhookReceived`.

Se escanean las clases de `app/Listeners`. Cambia `webhooks.discover` en la configuración para escanear otras carpetas. Un tipo que no coincide con ningún evento falla al arrancar con una pista, para que un error de escritura no pase inadvertido. Un listener que el descubrimiento de eventos propio de Laravel ya encontró por su type hint no se agrega dos veces. Los listeners que implementan `ShouldQueue` se ponen en cola como de costumbre.

`php artisan optimize` guarda en caché los listeners que encuentra. `stockshift:cache` y `stockshift:clear` hacen lo mismo por su cuenta.

### Reintentos y colas

StockShift reintenta una entrega que no recibe un `2xx`, y cada reintento conserva el `id` del evento. El paquete recuerda los ids durante un día y dispara los eventos una sola vez. Si un listener lanza una excepción, el id se olvida y la entrega recibe un `500`, así que se procesa el reintento. Cambia `webhooks.deduplicate_for` para conservar los ids más tiempo, o ponlo en `0` para no conservar ninguno.

Define `STOCKSHIFT_WEBHOOK_QUEUE` para responder a StockShift de inmediato y disparar los eventos desde un job en cola. `STOCKSHIFT_WEBHOOK_QUEUE_CONNECTION` elige la conexión de la cola.

### Otras rutas

Pon `webhooks.path` en `null` para omitir la ruta y colocarla tú mismo. Una ruta para otra conexión revisa el secreto de esa conexión:

```php theme={null}
Route::stockshiftWebhooks('webhooks/stockshift-eu', 'eu');
```

El middleware también funciona por separado, como `stockshift.webhook` o `stockshift.webhook:eu`. Pone el evento verificado en el atributo de solicitud `stockshift.event`. Las rutas de webhook no necesitan una excepción de CSRF porque no están en el grupo `web`. Para agregar middleware, como `throttle:120,1`, inclúyelo en `webhooks.middleware`.

## Llamar a la API a mano

`Http::stockshift()` es una solicitud HTTP de Laravel con la URL base, el token y los tiempos de espera de la conexión. No reintenta ni lanza excepciones por errores:

```php theme={null}
$response = Http::stockshift()->get('catalog/items', ['search' => 'bolt']);
$response->json('data.items');

Http::stockshift('eu')->post('catalog/items', $item);
```

## Comandos de Artisan

| Comando | Qué hace |
| - | - |
| `stockshift:install` | Publica la configuración y agrega los ajustes a `.env`. |
| `stockshift:test` | Comprueba el token y muestra a quién pertenece. |
| `stockshift:webhooks` | Lista los endpoints de webhook de la organización. `--events` lista los tipos de evento a los que puede suscribirse un endpoint. |
| `stockshift:cache`, `stockshift:clear` | Guardan en caché o borran los listeners de atributo. |

Cada uno acepta `--connection` donde llama a la API. `php artisan about` muestra la conexión predeterminada y si los listeners están en caché.

## Pruebas

`StockShift::fake()` responde a las llamadas con las respuestas que le das y registra cada una. Nada llega a la API y no hace falta un token:

```php theme={null}
use StockShift\Laravel\Facades\StockShift;
use StockShift\Laravel\Testing\RecordedRequest;

$fake = StockShift::fake([
    'GET /catalog/items' => StockShift::page([['sku' => 'BOLT-M4']]),
    'GET /catalog/items/*' => ['data' => ['sku' => 'BOLT-M4']],
    'POST /catalog/items' => StockShift::response(['data' => ['id' => '01J9']], 201),
    'DELETE /catalog/items/*' => 204,
    'POST /purchasing/orders' => StockShift::problem(422, 'validation_failed', 'The supplier is archived.'),
]);

// Run your code, then:
StockShift::assertSent('POST /catalog/items', fn (RecordedRequest $request) => $request->body['sku'] === 'BOLT-M4');
StockShift::assertNotSent('DELETE /catalog/items/*');
StockShift::assertSentCount(3);
```

Una clave es un método y una ruta después de la URL base. `*` coincide con cualquier cosa. Un valor es un cuerpo JSON, un código de estado, una respuesta PSR-7 o un closure que recibe el `RecordedRequest` y devuelve uno de esos. `StockShift::page()` construye una página de una lista con los registros bajo el nombre que usa la lista. Una llamada sin respuesta lanza `UnexpectedRequest`. Si llamas a `fake()` otra vez, agrega respuestas al mismo fake.

`RecordedRequest` tiene `connection`, `method`, `path`, `query`, `body`, `header()` e `idempotencyKey()`. `recorded()` los devuelve todos, o los que coinciden con una ruta. `assertNothingSent()` comprueba que no se hizo ninguna llamada.

Para probar tus listeners de webhook, envía una entrega firmada a través de la ruta:

```php theme={null}
StockShift::fakeWebhook('inventory.stock.moved', [
    'item_id' => '01J8QZ4M...',
    'quantity' => '2.000000',
])->send()->assertNoContent();
```

`dispatch()` dispara los eventos sin la solicitud HTTP. `payload()` y `signature()` te dan el cuerpo y el encabezado si prefieres enviarlo tú mismo.

Como las llamadas pasan por el cliente HTTP de Laravel, `Http::fake()` también funciona, si prefieres simular a nivel de HTTP.

## Versiones

El paquete sigue el versionado semántico. Es compatible con las versiones de Laravel que reciben correcciones de seguridad y con las versiones de PHP que admite cada una. Una nueva versión mayor del SDK llega con una nueva versión mayor del paquete.


## Related topics

- [Webhooks](https://docs.stockshift.io/es/guides/webhooks.md)
- [SDK](https://docs.stockshift.io/es/sdks/index.md)
- [StockShift para desarrolladores](https://docs.stockshift.io/es/index.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.