innoboxrr / larapack-generator
Generador de scaffolding para paquetes y proyectos Laravel 13
Requires
- php: ^8.3
- doctrine/inflector: ^2.0
- illuminate/support: ^13.0
- innoboxrr/search-surge: ^3.0
- innoboxrr/traits: ^2.0
- opis/json-schema: ^2.6
- staudenmeir/belongs-to-through: ^2.5
- staudenmeir/eloquent-has-many-deep: ^1.7
- symfony/console: ^7.4 || ^8.0
Requires (Dev)
- orchestra/testbench: ^11.0
- phpunit/phpunit: ^12.0 || ^13.0
Suggests
- maatwebsite/excel: Necesario en el proyecto destino si se usan las clases de exportacion que genera este paquete (^3.1).
Provides
None
Conflicts
None
Replaces
None
- dev-master
- 7.0.0
- 6.0.1
- 6.0.0
- 5.x-dev
- 5.0.33
- 5.0.32
- 5.0.31
- 5.0.30
- 5.0.29
- 5.0.28
- 5.0.27
- 5.0.26
- 5.0.25
- 5.0.24
- 5.0.23
- 5.0.22
- 5.0.21
- 5.0.20
- 5.0.19
- 5.0.18
- 5.0.17
- 5.0.16
- 5.0.15
- 5.0.14
- 5.0.13
- 5.0.12
- 5.0.11
- 5.0.10
- 5.0.9
- 5.0.8
- 5.0.7
- 5.0.6
- 5.0.5
- 5.0.4
- 5.0.3
- 5.0.2
- 5.0.1
- 5.0.0
- 4.x-dev
- 4.0.4
- 4.0.3
- 4.0.2
- 4.0.1
- 4.0.0
- 3.3.5
- 3.3.4
- 3.3.3
- 3.3.2
- 3.3.1
- 3.3.0
- 3.2.1
- 3.2.0
- 3.1.2
- 3.1.1
- 3.1.0
- 3.0.5
- 3.0.4
- 3.0.3
- 3.0.2
- 3.0.1
- 3.0.0
- 2.0.9
- 2.0.8
- 2.0.7
- 2.0.6
- 2.0.5
- 2.0.4
- 2.0.3
- 2.0.2
- 2.0.1
- 2.0.0
- 1.5.1
- 1.5.0
- 1.4.2
- 1.4.1
- 1.4.0
- 1.3.4
- 1.3.3
- 1.3.2
- 1.3.1
- 1.3.0
- 1.2.2
- 1.2.1
- 1.2.0
- 1.1.8
- 1.1.7
- 1.1.6
- 1.1.5
- 1.1.4
- 1.1.3
- 1.1.2
- 1.1.1
- 1.1.0
- 1.0.11
- 1.0.10
- 1.0.9
- 1.0.8
- 1.0.7
- 1.0.6
- 1.0.5
- 1.0.4
- 1.0.3
- 1.0.2
- 1.0.1
- 1.0.0
- dev-unificacion-ecosistema
- dev-laravel-13
- dev-developer
This package is auto-updated.
Last update: 2026-09-08 06:23:19 UTC
README
Apoya Nuestro Trabajo 🙌
Desarrollamos estos paquetes para la comunidad de Laravel con el objetivo de hacer la vida de los desarrolladores más fácil. Si te sientes agradecido por nuestro trabajo y te gustaría apoyarnos, considera inscribirte en uno de nuestros cursos de pago. Nos ayudas a seguir manteniendo estos recursos y mejoras tus habilidades.
Te recomendamos especialmente el curso Desarrollo de Paquetes en Laravel. Aprenderás a crear tus propios paquetes de Laravel y PHP, optimizando tu productividad como desarrollador.
Gracias por tu apoyo, ¡apreciamos enormemente a nuestra comunidad!
Instalación
Para instalar el paquete, ejecuta el siguiente comando:
composer require innoboxrr/larapack-generator
Si tras la instalación no se añade el instalador en la raíz del directorio, ejecuta el siguiente comando:
cp vendor/<vendor>/<package>/builder.example builder
Requerimientos
| Requisito | Versión |
|---|---|
| PHP | ^8.3 |
| Laravel | ^13.0 |
| Symfony Console | ^7.4 || ^8.0 |
| PHPUnit (dev) | ^12.0 || ^13.0 |
| Orchestra Testbench (dev) | ^11.0 |
- El paquete supone que el proyecto Laravel tiene por lo menos el modelo
App\Models\User. - Se recomienda tener configurado AWS S3 para la exportación de archivos. Si no, modifica el parámetro de configuración
export_disk. - Verifica que el modelo
App\Models\Usertenga el métodoisAdmin(). Si no tienes un sistema de roles, puedes implementar el siguiente método básico:
public function isAdmin() { return $this->id === 1; }
- La estructura principal del paquete debe estar dentro del directorio
srcpara proyectos de paquetes yapppara aplicaciones Laravel.
Código generado (Laravel 13)
Los stubs producen código alineado con las convenciones de Laravel 11+/13:
- Modelos: los casts se declaran con el método
casts(): array, y el observer, la política y la factoría se enlazan con los atributos#[ObservedBy],#[UsePolicy]y#[UseFactory]en lugar de descubrirse por reflexión. - Controladores: implementan
Illuminate\Routing\Controllers\HasMiddlewarecon un método estáticomiddleware(), en vez de$this->middleware()en el constructor. El controlador base ya no extiendeIlluminate\Routing\Controller. - Rutas: usan callables
[FooController::class, 'accion'], por lo que elRouteServiceProviderya no declara un namespace de controladores. - Proveedores: extienden
Illuminate\Support\ServiceProviderdirectamente; ya no se usan las clases base deIlluminate\Foundation\Support\Providers. ElEventServiceProvidersólo descubre eventos y listeners. - Migraciones, requests, políticas y recursos: firmas con tipos de retorno
(
up(): void,rules(): array,toArray(Request $request): array, …). phpunit.xml: esquema de PHPUnit 12/13 concacheDirectoryy las variables de entorno de Laravel 13 (CACHE_STORE,APP_MAINTENANCE_DRIVER).
Comandos Disponibles
Los comandos viven en el espacio larapack: y estan disponibles de dos formas:
php artisan larapack:full-model Post # dentro de una app Laravel
php builder larapack:full-model Post # binario, tambien fuera de Laravel
El binario mantiene los nombres antiguos (make:*, json:importer) como alias.
En Artisan no se registran porque make:model, make:policy, make:factory y
make:observer son comandos del propio Laravel.
Todos aceptan --root=<ruta> para decir sobre que proyecto se trabaja. Sin
esa opcion la raiz se descubre subiendo directorios desde el paquete hasta dar
con un vendor/autoload.php: dentro de una aplicacion Laravel eso da la raiz
correcta, pero con el binario sobre un clon del generador da el propio
generador. Los generadores aceptan ademas --force y --dry-run, y todos
--format=json.
Importador JSON
Genera todo el entorno de varios modelos a partir de un archivo declarativo:
php artisan larapack:import /ruta/al/laraimport.json
php artisan larapack:import --vue --react
Sin ruta, busca laraimport.json en la raiz del proyecto. --vue y
--react no son excluyentes: los dos modulos consumen el mismo contrato.
El contrato: larapack:validate y larapack:schema
El laraimport.json no es un archivo de configuracion: es el contrato del que
sale toda la arquitectura. Esta descrito en un JSON Schema (draft 2020-12) en
schema/laraimport.schema.json, declarado tambien en
extra.larapack.schema del composer.json para que una herramienta externa
lo encuentre sin conocer la estructura del paquete.
php artisan larapack:validate # valida ./laraimport.json
php artisan larapack:validate ruta.json # valida el que le digas
php artisan larapack:validate --format=json # para un CI o un agente
php artisan larapack:schema # imprime el esquema
larapack:validate sale con codigo distinto de cero si el archivo no es
valido, asi que sirve de puerta antes de generar. Comprueba dos cosas
distintas:
Forma, contra el esquema: tipos de columna y componentes de formulario son
enums cerrados; form: true obliga a declarar form_component; foreignId
obliga a declarar constraint.
Coherencia, mirando el documento entero: modelos o columnas repetidos,
ciclos de claves foraneas (que no tienen orden de migracion posible), claves
foraneas a si mismo no anulables, reglas sobre campos que no son columnas,
relaciones que no resuelven contra ningun modelo, y UpdateRequest sin la
regla del identificador del que dependen authorize() y handle().
Los avisos no bloquean. Los errores si: generar con ellos produce codigo que no arranca.
El importador ejecuta esta misma validacion antes de escribir nada, de modo que un archivo incompleto ya no deja el proyecto a medio generar.
Resto de comandos
larapack:app-service-provider - Crea un proveedor de servicio de aplicacion.
larapack:auth-service-provider - Crea un proveedor de servicio de autenticacion.
larapack:config - Crea un archivo de configuracion.
larapack:controller - Crea un nuevo controlador.
larapack:event-service-provider - Crea un proveedor de servicio de eventos.
larapack:events - Crea eventos y listeners para el modelo.
larapack:excel - Crea una clase de Excel.
larapack:export - Crea una clase de exportacion.
larapack:export-notification - Crea una clase de notificacion de exportacion.
larapack:factory - Crea una nueva fabrica.
larapack:filters - Crea una clase de filtros.
larapack:full-model - Crea un entorno completo de modelo.
larapack:migration - Crea una nueva migracion.
larapack:model - Crea un nuevo modelo.
larapack:model-traits - Crea traits para el modelo.
larapack:model-view - Crea el modulo Vue del modelo.
larapack:react-view - Crea el modulo React del modelo.
larapack:observer - Crea un observer.
larapack:policy - Crea una nueva politica.
larapack:providers - Crea todos los proveedores de servicio.
larapack:requests - Crea una clase de requests.
larapack:resource - Crea una nueva clase de recurso.
larapack:route - Crea una nueva ruta.
larapack:route-service-provider - Crea un proveedor de servicio de rutas.
larapack:test - Crea una nueva clase de test.
larapack:import - Genera varios modelos desde un laraimport.
larapack:validate - Valida un laraimport sin generar nada.
larapack:schema - Emite el esquema del laraimport.
larapack:verify - Busca desviaciones respecto al manifiesto.
larapack:audit - Comprueba la linea base del ecosistema.
larapack:skill - Instala las instrucciones para un agente.
larapack:remove-full-model - Elimina todas las entidades de un modelo.
Regenerar sin destruir
El generador anota lo que produce en .larapack/manifest.json: cada archivo,
la plantilla de la que salió y el hash de su contenido en ese momento. Con eso
sabe distinguir lo que escribió él de lo que has escrito tú.
php artisan larapack:model Post --dry-run # dice qué haría, sin escribir
php artisan larapack:model Post --force # regenera
php artisan larapack:model Post --format=json
--force nunca sobrescribe un archivo cuyo hash haya cambiado. Si lo has
editado, se conserva y se te dice. Un archivo que el manifiesto no conoce se
respeta igual: el generador no toca lo que no escribió él.
El manifiesto debe versionarse con el proyecto. Sin él, el generador no puede distinguir tu código del suyo y se vuelve conservador con todo.
Verificar la arquitectura
php artisan larapack:verify
php artisan larapack:verify --strict # los avisos cuentan como fallos
php artisan larapack:verify --format=json
Comprueba cuatro cosas:
| Comprobación | Nivel | Qué detecta |
|---|---|---|
missing-file |
error | Algo que se generó y ya no está |
customised |
info | Editado a mano; no se podrá regenerar sin perderlo |
inconsistent-entity |
aviso | Una entidad sin un componente que todas las demás tienen |
route-prefix |
error | El API_ROUTE_PREFIX del módulo JS dejó de cuadrar con el ->as() del RouteServiceProvider |
Devuelve un código de salida distinto de cero si hay errores, así que sirve como puerta en CI. La salida en JSON está pensada para que un agente lea qué corregir en lugar de raspar texto:
{
"ok": false,
"errors": 1,
"warnings": 0,
"findings": [
{
"level": "error",
"check": "missing-file",
"model": "Post",
"file": "src/Policies/PostPolicy.php",
"message": "Se generó pero ya no existe. Regenéralo con --force o retíralo del manifiesto."
}
]
}
No intenta adivinar si una clase "tiene demasiada lógica": esa clase de comprobación produce falsos positivos y acaba desactivándose. Sólo verifica lo que es determinista.
La línea base del ecosistema
larapack:verify mira un paquete contra su propio manifiesto.
larapack:audit mira un paquete contra las versiones que rigen a todos:
php builder larapack:audit # el paquete actual
php builder larapack:audit packages --all # todos los de un directorio
php builder larapack:audit --format=json --strict
Esas versiones viven en un solo archivo, ecosystem.json, y no en veintitantos
composer.json que nunca coinciden: PHP, illuminate/*, orchestra/testbench,
las dependencias internas de Composer y las de npm que el módulo generado pide.
El audit comprueba además que el paquete tenga tests de verdad y los workflows
de publicación, y sale con código distinto de cero si algo no cumple.
Un paquete que no pase el audit no debería publicarse; por eso el tests.yml
del ecosistema lo ejecuta.
Ejemplo de laraimport.json
Solo models[].name, props[].name y props[].type son obligatorios. Todo
lo demas tiene un valor por defecto que el esquema declara, asi que el archivo
solo dice lo que decide de verdad:
{
"models": [
{
"name": "Category",
"props": [
{ "name": "name", "type": "string" }
]
}
]
}
Un modelo completo, con formulario, tabla, relaciones y validacion:
{
"models": [
{
"name": "Post",
"metas": true,
"props": [
{
"name": "title",
"type": "string",
"form": true,
"form_component": "TextInputComponent",
"form_submit": true,
"datatable": true
},
{
"name": "status",
"type": "string",
"cast": "string",
"form": true,
"form_component": "SelectInputComponent",
"form_submit": true,
"datatable": true,
"enum": {
"draft": "Borrador",
"published": "Publicado"
}
},
{
"name": "payload",
"type": "longText",
"nullable": true,
"cast": "json"
},
{
"name": "category_id",
"type": "foreignId",
"constraint": "categories",
"form_submit": true
},
{
"name": "user_id",
"type": "foreignId",
"constraint": "users",
"exports_cols": false,
"form_submit": true
}
],
"load_relations": [
{ "type": "belongsTo", "related": "Category", "name": "category" },
{ "type": "belongsTo", "related": "User", "name": "user", "namespace": "App\\Models" }
],
"load_counts": [],
"editable_metas": ["seo_title"],
"requests": [
{
"name": "Create",
"rules": {
"title": "required|string|max:255",
"status": ["required", "in:draft,published"],
"category_id": "required|exists:categories,id",
"user_id": "required|exists:users,id"
}
},
{
"name": "Update",
"rules": {
"post_id": "required|numeric",
"title": "nullable|string|max:255",
"status": ["nullable", "in:draft,published"],
"category_id": "nullable|exists:categories,id"
}
}
]
},
{
"name": "Category",
"props": [
{ "name": "name", "type": "string", "form": true, "form_component": "TextInputComponent", "form_submit": true, "datatable": true }
]
},
{
"name": "Tag",
"props": [
{ "name": "name", "type": "string", "form": true, "form_component": "TextInputComponent", "form_submit": true, "datatable": true }
]
}
],
"pivots": [
{
"name": "post_tag",
"props": [
{ "name": "post_id", "type": "foreignId", "constraint": "posts" },
{ "name": "tag_id", "type": "foreignId", "constraint": "tags" }
]
}
]
}
Tres detalles que el esquema resuelve solo, y que antes habia que acertar a mano:
- El orden de los modelos no importa.
Postdepende deCategorypor su clave foranea, asi que la migracion decategoriesse genera primero aunque el modelo este declarado despues. Categoryno llevanamespaceen la relacion. Como se declara en este mismo archivo, vive en el paquete y elusegenerado apunta ahi.Userno esta, asi que resuelve contraApp\Models.- Las reglas admiten array. Es la forma recomendada de Laravel para todo lo
que lleve
|dentro, como unregex:.
assignments y filters siguen aceptandose por compatibilidad, pero estan
marcadas como obsoletas en el esquema: el generador no las lee.
La interfaz: Vue y React
La UI vive dentro del paquete, junto al backend que consume, y se publica como su propio npm:
packages/<feature>/
composer.json -> innoboxrr/<feature> (Laravel API)
resources/vue/
package.json -> innoboxrr-<feature> (npm)
src/models/<entidad>/...
resources/react/
package.json -> innoboxrr-<feature>-react (npm)
src/models/<entidad>/...
Los dos modulos salen del mismo laraimport.json y tienen exactamente la
misma estructura. Y src/models/<entidad>/index.js es el mismo archivo en
los dos: no una copia, el mismo. Son funciones puras y llamadas HTTP, sin
nada de un framework de UI —API_ROUTE_PREFIX, crudActions(),
dataTableHead(), dataTableSort() y las funciones CRUD—. Ahi esta el punto
entero de la paridad: si cada framework tuviera su contrato, no habria un
contrato.
Lo que cambia es solo la capa de presentacion:
| Vue | React | |
|---|---|---|
| Componentes | <script setup>, .vue |
funciones, .jsx |
| Enlace de datos | v-model |
value + onChange(valor) |
| Store | Pinia | Zustand, con la misma superficie |
| Router | vue-router 4 | React Router 7 |
| Formularios | innoboxrr-form-elements |
innoboxrr-react-form-elements |
| Tabla | innoboxrr-vue-datatable |
innoboxrr-react-datatable |
Los dos paquetes de formularios exportan los mismos 30 nombres, asi que el
form_component del laraimport vale igual para los dos. Cada paquete tiene
un test que falla si uno se adelanta al otro.
El host descubre los modelos por import.meta.glob, asi que generar uno nuevo
no obliga a enumerarlo en ningun sitio.
El aspecto
El modulo generado no necesita ningun framework de CSS. No hay que cargar
UIkit, ni Tailwind, ni Font Awesome: todo sale de innoboxrr-form-core, y
resources/<ui>/src/theme.js lo importa una vez.
import 'innoboxrr-form-core/styles'
Colores, formas y densidad son variables CSS, asi que cambiar el aspecto de todo el paquete no obliga a tocar un solo componente:
:root { --fe-primary: #7c3aed; --fe-radius: 10px; --fe-density: 0.875; /* interfaz mas compacta */ }
El modo oscuro responde a la preferencia del sistema y a un data-theme="dark"
en la raiz. setTheme queda para el otro caso: apuntar un token a las clases de
otro sistema visual, si la aplicacion ya tiene el suyo.
Los iconos se piden por nombre semantico —plus, edit, delete, show,
actions— y el mapa decide de que coleccion salen. Son mas de 200.000 iconos de
mas de 150 colecciones bajo un solo esquema:
setIcons({ plus: 'lucide:plus', delete: 'lucide:trash-2' })
Un nombre de coleccion entero vale donde haga falta uno suelto
(<IconComponent name="mdi:home" />), y un Resource de Laravel emite tambien
el nombre semantico: 'icon' => 'show', no 'fa-eye'.
El helper route
No es Ziggy: es innoboxrr-route-resolver, y la cadena completa es
php artisan route:json (innoboxrr/routes-to-json)
-> resources/.../routes.json
-> setRoutes() en bootstrap.js
-> window.route
-> route(API_ROUTE_PREFIX + 'index')
Si anades un endpoint, hay que volver a exportar el routes.json.
La aplicacion anfitriona debe llamar a JsonResource::withoutWrapping() en
su AppServiceProvider; sin eso llega un data de mas y la tabla sale vacia.
Para un agente (Claude Code, Codex)
El problema mas grande de dejar que una IA escriba Laravel no es que escriba mal: es que cada modelo sale un poco distinto del anterior, y a los treinta modelos la aplicacion ya no tiene una arquitectura. Un generador determinista invierte esa dinamica — el agente escribe un JSON pequeno y verificable, y las ~58 piezas de cada modelo salen identicas siempre.
Para que eso funcione el agente necesita tres cosas, y las tres son comandos:
larapack:schema descubrir el contrato, en vez de deducirlo de un ejemplo
larapack:validate comprobar su JSON antes de generar nada
larapack:verify comprobar su propio trabajo despues
Los tres devuelven --format=json y codigo de salida distinto de cero, asi
que el agente puede iterar solo, sin preguntar y sin raspar texto.
Falta lo que le dice como usarlos. Eso vive en skill/SKILL.md dentro del
paquete y se instala en el proyecto:
php artisan larapack:skill # -> .claude/skills/larapack/SKILL.md
php artisan larapack:skill --print # emitelo por salida estandar
php artisan larapack:skill --path=AGENTS.md
Un paquete dentro de vendor/ no lo lee nadie, de ahi la copia. Y al vivir el
original en el paquete, actualizar el generador actualiza las instrucciones:
reejecuta larapack:skill despues de un composer update. Si el destino se
ha ampliado con reglas del proyecto, no se pisa sin --force.
El texto ensena la regla —la arquitectura se declara, no se escribe—, el flujo
completo, el mapa de lo que se genera, donde va la logica de negocio (los
huecos: Operations, ManagedFilter::canView, rules(), los listeners, el
Resource) y que no hay que tocar.
Notas Importantes
- Para el empleo de este paquete en el desarrollo de paquetes, se espera que el directorio que contenga la lógica principal tenga el nombre "src". En una aplicación de Laravel, la carpeta predeterminada es "app".