Skip to content

La arquitectura de un modelo

Un modelo declarado en laraimport.json produce unos 58 archivos. Parecen muchos, pero siguen un solo patrón: cada pieza hace una cosa, y la lógica que escribes tiene un sitio fijo. Esta página es el mapa; el detalle de cada archivo está en Qué se genera y dónde va tu código.

Los ejemplos usan un modelo Product dentro de una aplicación. En un paquete, app/ es src/ y App\ es el namespace del paquete.

Una petición de principio a fin

Editar un producto desde el administrador:

PUT /api/app/product/update            nombre: api.app.product.update

  ├─ ProductController::update          auth:sanctum; no tiene lógica

  ├─ UpdateRequest
  │    authorize()  findOrFail(product_id) y can('update', $product)  → ProductPolicy
  │    rules()      product_id y las reglas del JSON
  │    handle()     $product->updateModel($this)                      → ProductStorage
  │                   update() con sólo $updatable, y las metas si las hay
  │                 new ProductResource($product)                     → incluye actions
  │                 event(new UpdateEvent(...))                        → listeners

  └─ JSON: el registro y sus actions, sin envoltorio data
  • El controlador sólo delega. Cada método llama al handle() de su request.
  • Cada acción tiene su request. Autoriza contra la política, valida y ejecuta.
  • El modelo es una fachada. Los métodos que trabajan viven en sus traits.
  • El Resource decide la forma de la respuesta y las acciones que la interfaz ofrece para esa fila.
  • Los efectos secundarios van en listeners, colgados del evento de la acción.

Las doce acciones

Sin la clave routes, un modelo tiene las doce. Cada una tiene su ruta, su método en el controlador, su request, su evento, su permiso en la política, su test y, si aplica, su pantalla. Quitar una con routes la quita en cascada de todos esos sitios.

AcciónMétodo HTTPURL (en una aplicación)Nombre de rutaPermiso de la política
policiesGETapi/app/product/policiesapi.app.product.policiesConsulta todos
policyGETapi/app/product/policyapi.app.product.policy
indexGETapi/app/product/indexapi.app.product.indexindex
showGETapi/app/product/showapi.app.product.showview
createPOSTapi/app/product/createapi.app.product.createcreate
updatePUTapi/app/product/updateapi.app.product.updateupdate
deleteDELETEapi/app/product/deleteapi.app.product.deletedelete
restorePOSTapi/app/product/restoreapi.app.product.restorerestore
forceDeleteDELETEapi/app/product/force-deleteapi.app.product.force.deleteforceDelete
exportPOSTapi/app/product/exportapi.app.product.exportexport
bulkUpdatePUTapi/app/product/bulk-updateapi.app.product.bulk.updateupdate, registro a registro
bulkDeleteDELETEapi/app/product/bulk-deleteapi.app.product.bulk.deletedelete, registro a registro

policies responde, para el usuario actual y un registro opcional, qué acciones puede hacer. Es lo que consulta la tabla antes de enseñar un botón.

Tres atajos cambian la forma del modelo sin tocar código:

  • routes con only o except elige qué acciones existen. Sin delete, restore ni forceDelete, el modelo pierde también el borrado lógico.
  • immutable: true quita la edición, el borrado y las masivas, y el modelo rechaza modificaciones hechas con Eloquent.
  • secret: true en una columna la saca de las respuestas, de la exportación y de la tabla.

Ver Rutas, inmutables, secretos y usuarios.

Las capas de un modelo

Dominio

ArchivoQué es
app/Models/Product.phpLa fachada. Sus listas ($fillable, $creatable, $updatable, $export_cols, $loadable_relations, $loadable_counts) y casts() salen del JSON.
Traits/Relations/ProductRelations.phpLas relaciones. Hueco.
Traits/Operations/ProductOperations.phpLa lógica de negocio. Hueco, y el sitio por defecto.
Traits/Storage/ProductStorage.phpCrear, actualizar y borrar, y el guardado de metas. Hueco para archivos.
Traits/Mutators/ProductMutators.phpAccessors y mutators. Hueco.
Traits/Assignments/ProductAssignment.phpSe genera vacío.
Filters/Product/ManagedFilter.phpQuién ve qué registros del índice. Hueco.
Filters/Product/{Id,Creation,Updated,EagerLoading}Filter.phpFiltros del índice, sobre search-surge.
app/Policies/ProductPolicy.phpAutorización por acción. Hueco. Nace cerrada.
app/Observers/ProductObserver.phpCiclo de vida del modelo. Hueco.
app/Models/ProductMeta.phpSólo con metas: true.

HTTP

ArchivoQué es
app/Http/Controllers/ProductController.phpDelega en los requests.
app/Http/Requests/Product/*Request.phpUno por acción. rules() es hueco, dentro del array.
app/Http/Resources/Models/ProductResource.phpLa forma de la respuesta y el array actions. Hueco.
app/Http/Events/Product/Events/*Un evento por acción.
app/Http/Events/Product/Listeners/*/*Efectos secundarios. Hueco.
routes/api/models/product.phpLas rutas. El nombre del archivo va en snake_case: order_line.php.

Exportación, datos y tests

ArchivoQué es
app/Exports/ProductsExports.phpLa exportación a Excel.
app/Notifications/Product/ExportNotification.phpEl aviso con el enlace de descarga.
database/migrations/*_create_products_table.phpLa tabla, y *_alter_products_table.php cuando cambian columnas.
database/factories/ProductFactory.phpDatos de prueba que ya insertan. Hueco.
tests/Feature/Models/ProductEndpointsTest.phpRecorre cada endpoint. Hueco.

Interfaz

Todo vive en resources/<ui>/src/models/product/, con la misma estructura en Vue y en React:

ArchivoQué es
index.jsEl contrato del modelo. Funciones puras y llamadas HTTP: API_ROUTE_PREFIX, crudActions(), bulkActions(), dataTableHead(), dataTableSort(), las funciones CRUD. Es el mismo archivo en Vue y en React.
store/index.jsPinia en Vue, Zustand en React, con la misma superficie.
routes/index.jsLas rutas AdminProducts, AdminCreateProduct, AdminShowProduct, AdminEditProduct.
forms/CreateForm, EditForm, FilterForm.
views/AdminView (la tabla), CreateView, ShowView, EditView.
widgets/DataTable, ModelCard, ModelProfile.

Y a nivel de módulo: resources/<ui>/index.js (la entrada), src/routes.js, src/i18n.js, src/locales/{en,es}.json, src/theme.js y src/components/{ActionMenu,Breadcrumbs}. En un paquete, además, package.json y vite.config.js.

Dónde va tu código

HuecoQué va ahí
Traits/OperationsLa lógica de negocio. Con metas, también la forma de payload en buildPayload().
Traits/RelationsRelaciones que el JSON no declara.
Traits/StorageSubida y borrado de archivos.
Traits/MutatorsAccessors y mutators.
ManagedFilter::canViewQuién ve qué. Sin esto, el índice lo devuelve todo.
PolíticaAutorización por acción. Nace cerrada: sólo pasa quien administra, y ni él borra para siempre hasta que saques forceDelete de $exceptAbilities.
Requests/*/rules()Reglas que no vienen del JSON, dentro del array.
ResourceLa forma exacta de la respuesta y su array actions.
Listeners y observerEfectos secundarios y ciclo de vida.
FactoryDatos de prueba realistas.
tests/FeatureEl comportamiento.

El modelo es una fachada, no un almacén de lógica. Un método público en Operations que orquesta, y el trabajo real en la clase que le corresponda.

Si una lógica no cabe en ninguno de estos sitios, falta algo en laraimport.json.

Lo que no se toca

  • El controlador y el archivo de rutas. Si falta un endpoint, falta en el JSON o en el generador.
  • Las listas del modelo ($fillable, $creatable, $updatable, $export_cols, $loadable_relations, $loadable_counts, $editable_metas, $protected_metas) y casts(): salen del JSON.
  • Lo que está fuera de los marcadores //RULES//, //IMPORTS// y //EDIT// en los archivos que los tienen.
  • models/<entidad>/index.js en uno solo de los dos frameworks: los separaría.

Escribir en estos sitios es exactamente la deriva que larapack:verify señala.

Regenerar sin destruir

.larapack/manifest.json registra cada archivo generado, la plantilla de la que salió y el hash de su contenido. Con eso:

  • larapack:import --force regenera los archivos cuyo hash no cambió y conserva los que editaste, avisando.
  • Un archivo que el manifiesto no conoce se respeta: el generador no toca lo que no escribió él.
  • Los traits de Relations, Storage y Operations sólo se crean si faltan, y los tests generados nunca se sobrescriben, ni con --force.
  • Una columna que cambia en una tabla existente produce una migración de alteración nueva, no una reescritura de la de creación.

larapack:verify lee el mismo manifiesto y marca lo que falta, lo editado, las rutas que no se declararon, las escrituras en un modelo inmutable y los secretos expuestos. Ver Verificar y auditar.

Siguiente: El contrato front ↔ back.