Solución de problemas
Cada problema, con su síntoma, su causa y cómo se arregla. Muchos se deben a una versión antigua de un paquete: las versiones actuales están en Versiones y compatibilidad.
Sesión y acceso
Cada tabla del administrador responde 401 después de iniciar sesión
Síntoma. El login funciona, pero cada tabla del administrador responde 401 y te devuelve a la pantalla de acceso.
Causa. Sanctum no reconoce las peticiones como de la propia SPA y no acepta la cookie de sesión. Pasa cuando:
APP_URLno es la dirección con la que abres la aplicación, con su puerto;- la dirección no está en
SANCTUM_STATEFUL_DOMAINS, que por omisión cubrelocalhost,127.0.0.1:8000y el host deAPP_URL; SESSION_DOMAINno corresponde a esa dirección;- falta
$middleware->statefulApi()enbootstrap/app.php.
Solución.
# .env: la dirección exacta del navegador, con su puerto
APP_URL=http://127.0.0.1:8000// bootstrap/app.php
->withMiddleware(function (Middleware $middleware): void {
$middleware->statefulApi();
})php artisan config:clearLa aplicación base ya trae statefulApi(). En una aplicación propia, compruébalo.
Dos aplicaciones en local se cierran la sesión una a otra
Síntoma. Iniciar sesión en una aplicación cierra la sesión de la otra.
Causa. Las dos corren en 127.0.0.1 con puertos distintos y comparten las cookies.
Solución. Sirve la segunda en localhost, y pon esa dirección en su APP_URL:
php artisan serve --host=localhost --port=8001Las peticiones responden 419
Síntoma. Un POST, un PUT o un DELETE responde 419.
Causa. Falta la cookie CSRF o caducó. Desde axios 1.6, además, la cabecera X-XSRF-TOKEN no se envía sin withXSRFToken, ni siquiera al mismo origen.
Solución. La interfaz de la aplicación base pide otra cookie a /sanctum/csrf-cookie y repite la petición una vez. En un front propio:
axios.defaults.withCredentials = true
axios.defaults.withXSRFToken = true
await axios.get('/sanctum/csrf-cookie') // antes de iniciar sesiónSi sigue fallando, revisa SESSION_DOMAIN y SANCTUM_STATEFUL_DOMAINS: son la razón habitual de que la sesión «no se quede».
"Volver a mi cuenta" responde 405
Síntoma. Durante una suplantación, el botón para volver a la cuenta original responde 405, o no hace nada y la sesión sigue suplantada.
Causa. laravel-auth 6.1.0 sólo acepta POST en auth.revert.impersonate: por GET, otro sitio podía terminar la suplantación de un administrador con un <img>. Tu interfaz sigue llamando con GET. Pasa en las aplicaciones creadas con laravel-setup 7.0.0 y en cualquier front propio.
Solución. Llama con POST y el token CSRF. La interfaz de la aplicación es tuya desde que se instaló, así que actualizar laravel-setup no la cambia: edita el store de sesión.
const revertImpersonation = async () => {
await http.post(apiUrl(AUTH_ROUTES.revertImpersonation))
// …
}revertImpersonation: async () => {
const { data } = await http.post(resolve('auth.revert.impersonate'))
// …
},<form method="POST" action="{{ route('auth.revert.impersonate') }}">
@csrf
<button type="submit">Volver a mi cuenta</button>
</form>Ver laravel-auth.
Cambiar la contraseña cierra la sesión
Síntoma. Después de cambiar la contraseña, la siguiente petición a la API responde 401.
Causa. laravel-auth anterior a 6.0.3 no renovaba la huella de la contraseña en la sesión, y AuthenticateSession la daba por inválida.
Solución. composer update innoboxrr/laravel-auth a la 6.0.3 o posterior.
Aparece el aviso de verificar el correo a quien no puede verificarlo
Síntoma. La interfaz pide verificar el correo a usuarios cuyo modelo no implementa MustVerifyEmail.
Causa. laravel-auth anterior a 6.0.2 respondía verified: false a todo usuario sin correo verificado.
Solución. Actualiza laravel-auth a la 6.0.2 o posterior.
El administrador no ve la administración, o recibe 403
Síntoma. Con tu cuenta no aparece el grupo de administración del menú, una pantalla responde 403, o un modelo nuevo no sale en el menú.
Causa. Alguna de estas:
ADMIN_EMAILSestá vacío o no tiene tu correo, así queisAdmin()devuelvefalse;- la configuración está en caché y el cambio del
.envno se ha leído; - la ruta del modelo no tiene título, o tiene parámetros: el menú sólo lista las rutas de primer nivel con título y sin parámetros;
- la interfaz no se ha vuelto a compilar.
Solución.
# .env: separados por coma
ADMIN_EMAILS=tu@correo.com,otra@correo.comphp artisan config:clear
php artisan route:json
npm run buildPara restringir un modelo a los administradores, añade su ruta a adminOnly: el nombre en Vue, el id en React. Ver El administrador.
Un administrador recibe 403 al guardar las opciones del sitio
Síntoma. El editor del sitio no guarda: cada escritura responde 403.
Causa. laravel-options anterior a 2.1.0 nunca registraba su política.
Solución. Actualiza innoboxrr/laravel-options a la 2.1.0 o posterior.
El editor del .env falla con "Route [login] not defined"
Síntoma. Abrir /env-editor sin sesión lanza «Route [login] not defined».
Causa. El middleware auth redirige a los invitados a una ruta llamada login, y la aplicación no la tiene.
Solución. Di dónde está el acceso, como hace la aplicación base:
// bootstrap/app.php
->withMiddleware(function (Middleware $middleware): void {
$middleware->redirectGuestsTo('/auth/login');
})Rutas e interfaz
La tabla sale vacía aunque la API devuelve datos
Síntoma. La petición del índice responde 200 con registros y la tabla no pinta ninguno.
Causa. La tabla espera data, meta y links en la raíz de la respuesta. Sin JsonResource::withoutWrapping() llega un data de más.
Solución.
// app/Providers/AppServiceProvider.php
use Illuminate\Http\Resources\Json\JsonResource;
public function boot(): void
{
JsonResource::withoutWrapping();
}"Unknown backend route" o una petición a la página actual
Síntoma. La consola muestra Unknown backend route "x". Run php artisan route:json., o una petición va a la URL de la página en la que estás.
Causa. La ruta no está en routes.json: se añadió un endpoint y no se volvió a exportar, o routes-to-json escribe el archivo en otro sitio del que lee la interfaz.
Solución.
php artisan route:json
npm run buildComprueba path en config/routes-to-json.php (o JSON_ROUTES_FILE). La aplicación base usa resources/<ui>/routes.json; el valor por omisión del paquete es resources/vue/assets/json/routes.json. Para no olvidarlo:
"build": "php artisan route:json && vite build"El administrador sale sin estilos
Síntoma. El módulo generado se ve sin ningún estilo.
Causa. En un módulo de paquete generado antes de LaraPack 7.7.1, sideEffects de resources/<ui>/package.json no incluye src/theme.js, y Vite descarta el import del tema y con él la hoja de estilos.
Solución.
"sideEffects": ["*.css", "*.vue", "src/theme.js"]"sideEffects": ["*.css", "src/theme.js"]El módulo falla con vue-router, Pinia o React Router
Síntoma. Errores de inyección, dos routers o stores vacíos al montar un módulo.
Causa. npm install vue-router pinia sin versión instala majors que el módulo no declara (vue-router 5, Pinia 4). Y un módulo instalado desde una carpeta puede traer su propia copia de cada uno.
Solución.
npm install vue vue-router@4 pinia@3 @vitejs/plugin-vuenpm install react react-dom react-router-dom@7 zustand@5 @vitejs/plugin-react// vite.config.js
resolve: { dedupe: ['vue', 'vue-router', 'pinia'] },Los nombres del modelo y de sus campos salen en inglés
Síntoma. Los textos de LaraPack están en español, pero «Price», «Status» o «Products» salen tal cual.
Causa. Es a propósito. LaraPack no puede saber cómo se llaman tu modelo y tus campos en español: los deja con "" en src/locales/es.json, y mientras lo estén se ve la clave.
Solución. Tradúcelos en resources/<ui>/src/locales/es.json del módulo o en el archivo de idioma de la aplicación (resources/<ui>/app/lang/es.json en la aplicación base). Regenerar suma las claves que falten y nunca pisa una traducción escrita.
Arranque, migraciones y caché
php artisan migrate falla en una aplicación nueva con CACHE_STORE=database
Síntoma. En una aplicación Laravel 13 recién creada, migrate falla antes de crear la tabla de la caché.
Causa. Varios proveedores leían la caché al arrancar, y con CACHE_STORE=database —lo que trae Laravel 13— la tabla aún no existe. Además compartían las claves auth_policies y events_and_observers, así que un paquete recibía los listeners de otro.
Solución. Actualiza laravel-options 2.1.0, laravel-notifications 2.1.0, laravel-uploads 2.1.x, laravel-audit 2.1.x, aws-file-manager 2.0.0 y support 2.1.1, y regenera el proveedor de eventos de LaraPack (7.10 o posterior):
php vendor/bin/builder larapack:event-service-provider --dry-run
php vendor/bin/builder larapack:event-service-provider --forceSi lo editaste, --force lo conserva: quita a mano el Cache::remember('events_and_listeners', ...) y recorre discoverEvents() directamente.
Las rutas de la aplicación se cargan dos veces, o los correos de verificación llegan duplicados
Síntoma. Rutas registradas dos veces, o dos correos de verificación por registro.
Causa. Versiones antiguas de los paquetes tenían proveedores que heredaban de los RouteServiceProvider y EventServiceProvider de Laravel, y volvían a cargar las rutas y los eventos de la aplicación.
Solución. Actualiza a las versiones para Laravel 13: laravel-options, laravel-notifications, laravel-uploads, laravel-audit y laravel-env-editor 2.1.x, aws-file-manager 2.0.0, locale-generator y routes-to-json 2.1.0, support 2.1.1 y laravel-auth 6.0.1 o posterior.
"Table already exists", o la misma migración de creación dos veces
Síntoma. migrate falla porque la tabla ya existe, y hay dos create_<tabla>_table con horas distintas.
Causa. Antes de LaraPack 7.7.1, volver a importar duplicaba las migraciones de creación.
Solución. Borra la más nueva y su entrada en .larapack/manifest.json, y actualiza LaraPack: ya reutiliza las migraciones de creación, de metas y de pivotes.
Cambié columnas en el JSON y no se escribió la migración de alteración
Síntoma. Reimportas tras cambiar una columna y no aparece <fecha>_alter_<tabla>_table.php.
Causa. Alguna de estas:
- la migración de creación se editó a mano, y LaraPack no adivina lo escrito fuera del laraimport;
- el cambio es de una clave foránea, que sería quitarla y crearla con sus datos;
- la migración de creación no la generó LaraPack, como la de
usersde Laravel, y se omite; - en una versión anterior regeneraste con
--forcela migración de creación de una tabla ya migrada: esa diferencia nunca llegó a la base y LaraPack ya no la ve.
Solución. Escribe esa migración a mano, una vez:
php artisan make:migration alter_products_tableLa aplicación base ya añade payload y softDeletes a users con su propia migración. Ver Migraciones y cambios de esquema.
Generación
larapack:verify falla con route-not-declared
Síntoma. Después de quitar a mano acciones que sobraban, verify falla y la CI también.
Causa. Existe una ruta, un método, una request o una vista de una acción que el modelo no declara, o al revés: se borró a mano lo que el JSON sigue declarando.
Solución. Declara la forma real en laraimport.json y regenera:
{ "name": "AuditEvent", "immutable": true, "routes": { "only": ["policies", "index", "show"] } }php vendor/bin/builder larapack:import --dry-run
php vendor/bin/builder larapack:import --forceLa salida de larapack:import --format=json no se puede decodificar
Síntoma. Un agente o un script falla al leer el informe de la importación.
Causa. Antes de LaraPack 7.10.3, larapack:import --format=json escribía líneas de progreso («Processing model: …») delante del documento.
Solución. Actualiza LaraPack a la 7.10.3 o posterior. Desde entonces la salida es un único documento, también cuando algo falla.
Lo generado queda sin formatear
Síntoma. pint --test falla en la CI sobre archivos que nadie editó.
Causa. El proyecto no tiene Pint en vendor/laravel/pint (o LARAPACK_PINT apunta a un archivo que no existe): la importación lo avisa en texto y en JSON formatted vale null. Un fallo del propio Pint no detiene la generación ni se informa.
Solución.
composer require --dev laravel/pint
vendor/bin/pint--force no aplicó el cambio a un archivo
Síntoma. Regeneras con --force y un archivo sigue como estaba.
Causa. --force nunca sobrescribe un archivo editado a mano: lo conserva y lo dice (preserved). Además, los traits Relations, Storage y Operations sólo se crean si faltan, y los tests generados no se sobrescriben nunca.
Solución. Lleva el cambio a mano a cada archivo conservado. Si la edición no te interesa, borra el archivo y vuelve a importar.
En una aplicación, los tests generados fallan por namespaces o con 404
Síntoma. En una aplicación generada con LaraPack 7.10.0 fallan casi todos los tests generados.
Causa. Las factories se generaban en App\Database\Factories y los tests en App\Tests, que la aplicación no carga, y los tests llamaban a URIs sin app/ y sin sesión.
Solución.
- Factories y modelos:
larapack:import --dry-runy despuéslarapack:import --force. En los editados, cambiaApp\Database\FactoriesporDatabase\Factories. - Tests: si no los tocaste, borra
tests/Feature/Models/<Modelo>EndpointsTest.phpy vuelve a importar. Si los editaste, cambiaApp\TestsporTests, llama aroute('api.app.<modelo>.<acción>')e inicia sesión antes de cada llamada. - Si LaraPack 7.10.0 creó
tests/TestCase.phpextendiendoOrchestra\Testbench\TestCase, bórralo y vuelve a importar. - Registra el
EventServiceProviderenbootstrap/providers.php.
Ver la guía de actualización.
Exportaciones y archivos
La exportación falla con «No hint path defined for [app]»
Síntoma. En una aplicación generada con LaraPack 7.10.0 o 7.10.1, cada exportación falla al renderizar.
Causa. Pedía la vista app::excel.<modelo>, que nadie registra, y leía app.notification_via y app.export_disk de la configuración de Laravel.
Solución. Regenera la exportación y su notificación:
php artisan larapack:import --dry-run
php artisan larapack:import --forceSi las editaste, cambia config('app.excel_view', 'app::excel.') por config('larapack.excel_view', 'excel.'), y app.notification_via y app.export_disk por larapack.notification_via y larapack.export_disk. Lleva esas claves de config/app.php a config/larapack.php (larapack:config lo crea).
La exportación termina pero nadie recibe el aviso
Síntoma. La exportación no da error y el archivo nunca llega.
Causa. Alguna de estas:
- en una aplicación, el
EventServiceProviderno está enbootstrap/providers.php; notification_viaincluyedatabasey no existe la tabla de notificaciones;- con
notification_viaen['mail'], el valor por omisión, el aviso va por correo: la aplicación tiene que poder enviarlo.
Solución.
php artisan larapack:event-service-provider # y regístralo en bootstrap/providers.php
php artisan make:notifications-table
php artisan migrateY añade database a notification_via en config/larapack.php (o en la configuración del paquete) si quieres el aviso en la campana.
Subir un archivo responde 422 o falla
Síntoma. Subir un avatar u otro archivo responde 422, o falla al guardar.
Causa.
- El tipo no está permitido: laravel-uploads no admite SVG.
- Supera
max_size(LARAVEL_UPLOADS_MAX_SIZE, 10240 KB por omisión). - El disco por omisión de laravel-uploads es
s3, y S3 no está configurado.
Solución. Sube un tipo permitido, ajusta el límite o el disco:
LARAVEL_UPLOADS_DISK=public
LARAVEL_UPLOADS_MAX_SIZE=10240La aplicación base ya pone LARAVEL_UPLOADS_DISK=public.
aws-file-manager responde 503 o 422
Síntoma. La API del gestor de archivos responde 503 siempre, o 422 al hacer público un archivo.
Causa. Responde 503 hasta que el bucket y la región están configurados, y 422 al pedir visibilidad pública en un bucket sin ACL (AWS_FILE_MANAGER_USE_ACL=false).
Solución.
AWS_BUCKET=mi-bucket
AWS_DEFAULT_REGION=us-east-1
AWS_ACCESS_KEY_ID=…
AWS_SECRET_ACCESS_KEY=…
AWS_FILE_MANAGER_USE_ACL=falseWindows y Composer
Composer no resuelve paquetes para PHP 8.3, o resuelve distinto que la CI
Síntoma. En local, Composer no instala paquetes que piden PHP 8.3, o instala versiones que la CI rechaza.
Causa. El composer del PATH de Laragon corre con PHP 8.2 y es Composer 2.8.x: no instala paquetes sólo para PHP 8.3 y no bloquea los avisos de seguridad como la CI.
Solución. Ejecuta un composer.phar actualizado con el PHP 8.4:
"C:/laragon/bin/php/php-8.4/php.exe" composer.phar self-update
"C:/laragon/bin/php/php-8.4/php.exe" composer.phar update(ajusta la ruta a tu instalación). En la aplicación base, pásaselo a app:install: un .phar se ejecuta con el mismo PHP que Artisan.
php artisan app:install --composer=C:/ruta/composer.pharinstall:api no instaló Sanctum
Síntoma. Las rutas generadas fallan porque auth:sanctum no existe, aunque ejecutaste php artisan install:api.
Causa. install:api usa el composer del PATH y, si falla, no lo dice.
Solución.
composer show laravel/sanctum # ¿está?
composer require laravel/sanctum # con el Composer y el PHP correctosLa aplicación base no usa install:api: publica las migraciones de Sanctum en app:install y trabaja con statefulApi().
La suite falla en local con «could not find driver»
Síntoma. Los tests que usan la base de datos fallan en local y pasan en la CI.
Causa. El PHP local no tiene pdo_sqlite. La CI sí.
Solución. Sin tocar el php.ini:
php -d extension=pdo_sqlite -d extension=sqlite3 vendor/bin/phpunitOtros tropiezos en local
- Con poca memoria,
composer updatey PHPUnit de varios paquetes en paralelo se cortan. Ejecútalos de uno en uno. - Los heredocs de bash estropean
\\en los namespaces de PHP. Escribe los scripts PHP en un archivo con el editor. gh apiimprime el cuerpo de un 404 en la salida estándar. Comprueba el código de salida, no la salida.
CI
El workflow Release no arranca y no deja log
Síntoma. Release aparece como startup_failure, sin ningún paso ni log.
Causa. El release.yml no declara permissions: contents: write, y el permiso por defecto del repositorio es de sólo lectura.
Solución.
jobs:
release:
permissions:
contents: write
uses: innoboxrr/.github/.github/workflows/php-release.yml@mainLa CI no puede instalar orchestra/testbench ^9
Síntoma. composer update falla en la CI y en local funciona.
Causa. El Composer de la CI bloquea por omisión los paquetes con avisos de seguridad, y todas las versiones de Laravel 11 los tienen. El Composer local es más antiguo y resuelve.
Solución. Amplía la restricción:
"orchestra/testbench": "^9.0 || ^10.0 || ^11.0"Reprodúcelo con el log del job de la CI, no en local.
Tests falla en la auditoría y no se publica
Síntoma. El paso «Auditar contra la línea base del ecosistema» falla, y Release no publica.
Causa. Un hallazgo de nivel error: una restricción estrecha (^7.10.2 frente a ^7.10), version en composer.json, falta un workflow o no hay tests.
Solución. Reprodúcelo en local y corrige:
php vendor/innoboxrr/larapack-generator/builder larapack:audit .Para pedir un mínimo por encima de la línea base, usa conflict.
Arreglé el workflow compartido y la CI sigue fallando igual
Síntoma. Corregiste algo en innoboxrr/.github, relanzas la ejecución y falla con el error de antes.
Causa. gh run rerun reutiliza la versión del workflow reutilizable de la ejecución original.
Solución. Lanza una ejecución nueva: un push, o workflow_dispatch en los workflows que lo tienen, como Release.
gh run view --log-failed no muestra nada
Síntoma. El job falló y el comando no imprime log.
Causa. Los jobs de un workflow reutilizable anidado no salen en --log-failed.
Solución. Pide el log del job por la API:
gh run view <run-id> --json jobs --jq '.jobs[] | select(.conclusion == "failure") | .databaseId'
gh api repos/<owner>/<repo>/actions/jobs/<job-id>/logsUn paquete con dependencias privadas no instala en la CI
Síntoma. composer update no encuentra un paquete privado.
Causa. Hace falta COMPOSER_AUTH, y secrets: inherit no cruza organizaciones: un repositorio de otra organización que llama a un workflow de innoboxrr no le pasa el secreto. Además, Composer necesita cada repositorio privado declarado como vcs en la raíz, también los transitivos.
Solución.
jobs:
tests:
uses: innoboxrr/.github/.github/workflows/php-tests.yml@main
secrets:
COMPOSER_AUTH: ${{ secrets.COMPOSER_AUTH }}Faltan tests/ o .gitattributes en un paquete extraído
Síntoma. Un paquete extraído de otro repositorio llega sin tests.
Causa. git archive respeta export-ignore y deja fuera, sin avisar, lo que el .gitattributes marca.
Solución. No extraigas con git archive: copia el árbol de trabajo.
Dos paquetes de dominio forman un ciclo de instalación
Síntoma. Composer no puede instalar dos paquetes que se nombran el uno al otro.
Causa. Los dos se declaran en require mutuamente.
Solución. En require sólo va lo que hace falta para cargar una clase: un trait, una clase padre, una interfaz, una migración o un proveedor. Un modelo que sólo aparece en una relación o en un servicio va en suggest.
npm publish pide un código de un solo uso
Síntoma. El release de npm falla pidiendo OTP, justo después de firmar la provenance.
Causa. Alguna de estas:
- hay un
NODE_AUTH_TOKENy npm se autentica con él en vez de con OIDC; - el npm es anterior al soporte de OIDC;
- el trusted publisher no está configurado en npmjs.com para ese repositorio y
release.yml; - falta
id-token: write.
Solución. Quita el token, usa node-release.yml (ya actualiza npm y declara id-token: write) y configura el trusted publisher. Ver Publicar una versión.
npm install falla en la CI con Vitest 4 y Vite 8.3
Síntoma. npm install revienta en Node 20 y 22 y funciona en Node 24.
Causa. Vitest 4 con Vite 8.3 no se instala con npm 10, el que traen Node 20 y 22.
Solución. Los paquetes npm del ecosistema siguen en vite ^7.1 y vitest ^3. Ver Los workflows.
El release de npm terminó y npm view muestra la versión anterior
Síntoma. Release está en verde y el registro no lista la versión nueva.
Causa. El registro de npm puede tardar en listarla.
Solución. Espera, y comprueba en el log que el publish salió con provenance y que existe el tag v<versión>. No vuelvas a publicar la misma versión.
Pruebas
Los tests de jsdom se quedan colgados al abrir un menú
Síntoma. Un test que abre un menú de MenuComponent o de un datatable no termina.
Causa. @floating-ui/dom real se cuelga en jsdom.
Solución. Simúlalo, como hacen los propios paquetes:
vi.mock('@floating-ui/dom', () => ({
autoUpdate: vi.fn((reference, floating, update) => {
update()
return () => {}
}),
computePosition: vi.fn(() => Promise.resolve({ x: 0, y: 0 })),
flip: vi.fn(),
offset: vi.fn(),
shift: vi.fn(),
}))Un diálogo o un menú no se comporta igual en jsdom
Síntoma. En los tests, un diálogo no queda por encima o un popover no se abre como en el navegador.
Causa. jsdom no tiene showModal() ni la Popover API. Los componentes caen en los atributos open y hidden.
Solución. Comprueba esos atributos en los tests y el comportamiento real en un navegador.
Pruebas automatizadas en un navegador real
- Los elementos de un menú popover oculto no responden a clics lanzados por script. Usa clics reales del ratón.
- Varias tandas de acciones lanzadas a la vez pierden pulsaciones de teclado. Lánzalas de una en una.