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

# Macros de respuesta y contratos HTTP en paquetes

> Analiza ResponseFactory y Macroable de Laravel 13 para entender las respuestas JSON propias de un paquete, las colisiones de nombres, la vida útil del estado de registro y cómo probar y mantener contratos HTTP.

A veces quieres que los usuarios de tu paquete puedan devolver el mismo formato de JSON de error desde muchos controladores. Si publicas una macro de respuesta, puedes añadir una API de construcción como `response()->acmeFailure(...)` sin exigir que los controladores hereden de una clase base.

Esta página se basa en la documentación oficial de Laravel en la rama predeterminada `13.x` y en la implementación oficial de la última versión del Framework, `v13.40.0`, revisadas el 8 de noviembre de 2026. Partiendo de los fundamentos de [Respuestas](https://kawax.biz/es/responses.md) y del [trait Macroable](https://kawax.biz/es/advanced/macroable.md), trata los límites que debes tener en cuenta al mantener una macro como API pública de un paquete distribuido.

## Se registra en la Factory, no en la respuesta

El nombre `Response` designa tanto a una facade que solicita construir una respuesta como a la clase de la respuesta HTTP generada. Lo que extiende `Response::macro()` en la documentación oficial es `ResponseFactory`, la clase a la que se resuelve la facade.

| Destino | Función | Uso en esta página |
| - | - | - |
| `Illuminate\Support\Facades\Response` | Facade que resuelve y llama al contrato `ResponseFactory` | Registrar y llamar a la macro |
| `Illuminate\Routing\ResponseFactory` | APIs de construcción como `json()`, `view()` y `noContent()` | Clase que guarda la macro |
| `Illuminate\Http\Response` | Respuesta HTTP ya generada | Registro de macros separado de la Factory |
| `Illuminate\Http\JsonResponse` | Respuesta JSON | Valor de retorno de esta macro |

`response()` sin argumentos devuelve la Factory. En cambio, `response('cuerpo')` y `response(null)` devuelven una respuesta creada con `make()`, así que no llaman al mismo destino.

> [!WARNING]
>
> Registrar en `Illuminate\Http\Response::macro()` no crea una macro para `response()->acmeFailure()`. No confundas en tus sentencias `use` la facade con la clase de respuesta del mismo nombre.

## Publicar una API de construcción para JSON de error

### Registrarla en un service provider

Registra una macro con prefijo del paquete en el método `boot()` de `src/HttpServiceProvider.php`. Para saber cómo se carga el provider, consulta [Descubrimiento de paquetes](https://kawax.biz/es/advanced/package-discovery.md).

El contrato público de este ejemplo es un JSON con `error.code` y `error.message`, junto con un estado HTTP entre 400 y 599. Esta restricción de estado no es una limitación del mecanismo de macros de Laravel. La define el paquete como parte de una API exclusiva para errores.

```php theme={null}
<?php

namespace Acme\Http;

use Illuminate\Http\JsonResponse;
use Illuminate\Routing\ResponseFactory;
use Illuminate\Support\Facades\Response;
use Illuminate\Support\ServiceProvider;
use InvalidArgumentException;

final class HttpServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        Response::macro('acmeFailure', function (
            string $code,
            string $message,
            int $status = 422,
            array $headers = [],
        ): JsonResponse {
            if ($status < 400 || $status > 599) {
                throw new InvalidArgumentException(
                    'acmeFailure requires a status between 400 and 599.'
                );
            }

            /** @var ResponseFactory $this */
            return $this->json([
                'error' => [
                    'code' => $code,
                    'message' => $message,
                ],
            ], $status, $headers);
        });
    }
}
```

Este provider debe arrancar antes de que se use la macro. No pongas el registro en un archivo que carga rutas HTTP ni dentro de una condición `runningInConsole()` negada. Las pruebas de funcionalidad HTTP también se ejecutan desde la CLI.

Un provider cuya única tarea es este registro tampoco necesita ser un provider diferido que espera a que se resuelva un servicio. Para entender el registro diferido, consulta [Service providers diferidos](https://kawax.biz/es/advanced/deferred-provider.md).

### Llamarla desde un controlador

Después de que tu lógica de negocio detecte un conflicto, pasa el código de error elegido y un mensaje para el usuario.

```php theme={null}
return response()->acmeFailure(
    code: 'order_conflict',
    message: '注文を確定できませんでした。',
    status: 409,
    headers: ['X-Acme-Error' => 'order_conflict'],
);
```

El estado HTTP es `409` y el JSON tiene esta forma. Igual que con un `json()` normal, el `Content-Type` es `application/json`.

```json theme={null}
{
  "error": {
    "code": "order_conflict",
    "message": "注文を確定できませんでした。"
  }
}
```

También puedes llamar a la misma macro de la Factory mediante la facade.

```php theme={null}
use Illuminate\Support\Facades\Response;

return Response::acmeFailure(
    'order_conflict', '注文を確定できませんでした。', 409
);
```

> [!NOTE]
>
> La macro solo construye las respuestas donde la llamas. No convierte automáticamente las excepciones de validación ni los errores de autenticación a este formato. Tampoco añade ningún cambio entre HTML y JSON según la cabecera `Accept`.

## El `$this` de la Closure y la ruta de llamada

La Factory usa `Macroable`. Cuando llamas a un método no definido de una instancia, `__call()` busca la macro registrada y vuelve a vincular una Closure normal a la instancia sobre la que se llamó.

```mermaid theme={null}
flowchart TD
    A["response()->acmeFailure(...)<br>o Response::acmeFailure(...)"] --> B["Instancia de ResponseFactory"]
    B --> C["Macroable::__call()<br>Revincula el $this de la Closure"]
    C --> D["ResponseFactory::json()"]
    D --> E["Nueva JsonResponse"]
```

Aunque el código de registro esté dentro de un método del service provider, `$this` en tiempo de ejecución es la Factory, no el service provider. Por eso `$this->json()` en el ejemplo llama al método normal de la Factory.

* Define las macros que usan `$this` con una `function (...) { ... }` normal. Una `static function` no tiene `$this` de instancia.
* Si registras un objeto invocable, no se revincula como una Closure. Haz que ese objeto reciba sus dependencias por sí mismo, por ejemplo mediante su constructor.
* Los argumentos de la macro son los que pasa quien la llama. Aunque declares el tipo `Request` u otro similar, Macroable no lo inyecta automáticamente desde el contenedor.

La llamada a la facade `Response::acmeFailure()` parece estática, pero internamente se reenvía a la instancia de la Factory. En cambio, `Illuminate\Routing\ResponseFactory::acmeFailure()` sobre la clase concreta es una llamada estática real, y `__callStatic()` vincula la Closure sin instancia. Una macro como esta, que usa `$this->json()`, no admite llamadas estáticas sobre la clase concreta.

## Tratar las colisiones de nombres como un problema de compatibilidad

`macro()` solo asigna un valor en un array estático indexado por nombre. Registrar un nombre existente no genera ninguna advertencia.

| Situación | Comportamiento en la implementación revisada |
| - | - |
| Volver a registrar una macro con el mismo nombre | El registro posterior sobrescribe la macro anterior |
| Registrar un nombre igual a un método público que realmente existe en la clase, como `json()` | Se llama al método normal y nunca se llega a la macro |
| `hasMacro($name)` es `true` | Significa que el nombre está registrado. No garantiza que no haya colisión con un método real |
| `mixin($object, replace: false)` | Evita reemplazar macros ya registradas. No comprueba colisiones con métodos reales |

Elige un nombre que identifique tu paquete, como `acmeFailure`, en lugar de uno genérico como `failure`. Al actualizar Laravel, comprueba también las colisiones con métodos reales mediante `method_exists(ResponseFactory::class, 'acmeFailure')`.

Si compruebas `hasMacro()` y omites el registro sin condiciones, podrías usar la implementación de otro paquete como si fuera tu propia API. Solo cuando conviertas "la aplicación puede sobrescribirla registrándola antes" en parte de la especificación, publica esa prioridad junto con los argumentos y el valor de retorno necesarios. El ejemplo anterior sigue la política de registrar la definición propia del paquete cada vez que arranca el provider.

## Separar el estado de registro de la vida útil de la respuesta

El registro de macros es una **propiedad estática** de la Factory. Volver a crear la Factory o borrar la instancia resuelta de la facade no vacía este registro. El estado de registro también puede persistir en pruebas que vuelven a crear la aplicación dentro del mismo proceso PHP.

Por otro lado, la macro del ejemplo hace que `json()` cree una nueva `JsonResponse` en cada llamada. Lo que se comparte es la lógica de construcción, no el cuerpo ni las cabeceras de la respuesta.

Estos son los límites que debes respetar en workers de larga duración.

1. No captures la Request actual, el usuario autenticado ni el tenant con `use (...)` en el momento del registro. Pasa esos valores como argumentos al llamar.
2. No reutilices una respuesta creada en el momento del registro. Devuelve una respuesta nueva cada vez.
3. Si capturas configuración como valor en el momento del registro, incluye en la especificación que ese valor se conserva dentro del proceso.
4. Después de actualizar el paquete, reinicia los procesos de larga duración para que se aplique el nuevo código de registro.

[Diseño del ciclo de vida de los servicios de paquetes](https://kawax.biz/es/advanced/package-service-lifetimes.md) trata con más detalle el estado compartido del contenedor y el estado por petición.

> [!WARNING]
>
> `Response::flushMacros()` elimina **todas las macros** registradas en la Factory. También se pierden los registros de otros paquetes, así que no lo uses en el procesamiento de peticiones en producción ni como limpieza descuidada en un entorno de pruebas compartido. Verifica las colisiones y los reinicios en un proceso de pruebas aislado.

## Probar el contrato HTTP con valores de retorno reales

En una aplicación de pruebas de Laravel 13 que pueda cargar el paquete mediante Composer, añade el siguiente `tests/Feature/ResponseMacroTest.php`. Para `Tests\TestCase` y los fundamentos de las pruebas HTTP, consulta [Pruebas HTTP](https://kawax.biz/es/http-tests.md). Para preparar un entorno de paquete independiente, consulta [Pruebas de paquetes](https://kawax.biz/es/advanced/package-testing.md).

```php theme={null}
<?php

namespace Tests\Feature;

use Acme\Http\HttpServiceProvider;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Response;
use Illuminate\Support\Facades\Route;
use InvalidArgumentException;
use Tests\TestCase;

class ResponseMacroTest extends TestCase
{
    protected function setUp(): void
    {
        parent::setUp();

        $this->app->register(HttpServiceProvider::class);
    }

    public function test_http_response_keeps_the_public_contract(): void
    {
        Route::get('/_acme/failure', function (): JsonResponse {
            return response()->acmeFailure(
                'order_conflict',
                '注文を確定できませんでした。',
                409,
                ['X-Acme-Error' => 'order_conflict'],
            );
        });

        $this->getJson('/_acme/failure')
            ->assertStatus(409)
            ->assertHeader('Content-Type', 'application/json')
            ->assertHeader('X-Acme-Error', 'order_conflict')
            ->assertExactJson([
                'error' => [
                    'code' => 'order_conflict',
                    'message' => '注文を確定できませんでした。',
                ],
            ]);
    }

    public function test_facade_returns_a_fresh_json_response(): void
    {
        $first = Response::acmeFailure('first', '最初のエラー');
        $second = response()->acmeFailure('second', '次のエラー', 503);

        $this->assertInstanceOf(JsonResponse::class, $first);
        $this->assertNotSame($first, $second);
        $this->assertSame(422, $first->getStatusCode());

        $first->setData(['changed' => true]);

        $this->assertSame(503, $second->getStatusCode());
        $this->assertSame('second', $second->getData(true)['error']['code']);
    }

    public function test_success_status_is_rejected(): void
    {
        $this->expectException(InvalidArgumentException::class);

        response()->acmeFailure('invalid_status', '設定を確認してください。', 200);
    }
}
```

Si simulas la propia API de construcción, como con `Response::shouldReceive('acmeFailure')`, no se ejecutan el registro de la macro, la vinculación de la Closure ni la generación del JSON. Separa la verificación del número de llamadas y comprueba la respuesta HTTP real como se muestra arriba.

Además, trata el cuerpo, el estado y las cabeceras que reciben los usuarios como parte de la compatibilidad.

* Mantén `error.code` estable para la evaluación automática y distínguelo de los cambios en el mensaje mostrado.
* Los nombres de los argumentos se usan como argumentos con nombre, así que comprueba el impacto de cambiarlos igual que con los valores predeterminados o el orden.
* Devuelve las respuestas sin cuerpo, como `204`, con otra API como `noContent()`, no como JSON de error.
* Si permites extensiones que cambian el `Content-Type` mediante cabeceras propias, mantenlo coherente con el formato del cuerpo.
* Si tu objetivo es la serialización de datos o la paginación, considera también los [recursos de API de Eloquent](https://kawax.biz/es/eloquent-resources.md) en lugar de reimplementarlos solo con macros.

## Lista de comprobación para el mantenimiento a largo plazo

* [ ] En las combinaciones de Laravel 13 soportadas, el provider arranca y la macro se puede llamar tanto desde el helper como desde la facade.
* [ ] El nombre de la macro no colisiona con otros registros ni con métodos reales de la Factory.
* [ ] Hay pruebas de compatibilidad para las claves JSON, el estado HTTP, las cabeceras, los valores predeterminados y los argumentos con nombre.
* [ ] Las llamadas consecutivas no comparten respuestas y no se guarda información de la Request ni del usuario en el momento del registro.
* [ ] Las pruebas que reinician los registros están aisladas de otros paquetes.
* [ ] El reinicio de los procesos de larga duración forma parte del procedimiento de actualización.

## Fuentes primarias consultadas

* [Documentación oficial de Laravel: Response macros y JSON responses (13.x)](https://github.com/laravel/docs/blob/13.x/responses.md#response-macros)
* [Documentación oficial de Laravel: Package development (13.x)](https://github.com/laravel/docs/blob/13.x/packages.md)
* [La facade Response y su destino de resolución (v13.40.0)](https://github.com/laravel/framework/blob/v13.40.0/src/Illuminate/Support/Facades/Response.php)
* [Reenvío de la Facade a la instancia (v13.40.0)](https://github.com/laravel/framework/blob/v13.40.0/src/Illuminate/Support/Facades/Facade.php)
* [El helper response() (v13.40.0)](https://github.com/laravel/framework/blob/v13.40.0/src/Illuminate/Foundation/helpers.php)
* [Lógica de construcción de ResponseFactory (v13.40.0)](https://github.com/laravel/framework/blob/v13.40.0/src/Illuminate/Routing/ResponseFactory.php)
* [Registro, vinculación y reinicio en Macroable (v13.40.0)](https://github.com/laravel/framework/blob/v13.40.0/src/Illuminate/Macroable/Traits/Macroable.php)
* [Pruebas oficiales de Macroable (v13.40.0)](https://github.com/laravel/framework/blob/v13.40.0/tests/Support/SupportMacroableTest.php)


## Related topics

- [Trait Macroable](https://kawax.biz/es/advanced/macroable.md)
- [Temas avanzados](https://kawax.biz/es/advanced/index.md)
- [Stores de caché personalizados y contratos en paquetes](https://kawax.biz/es/advanced/package-cache-drivers.md)
- [Cliente HTTP y pruebas en paquetes](https://kawax.biz/es/advanced/package-http-client.md)
- [Drivers de sesión personalizados en paquetes](https://kawax.biz/es/advanced/package-session-drivers.md)


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