El panel de administración
Qué trae /admin, quién entra y cómo agregar una pantalla propia.
El panel de administración es la trastienda de tu proyecto: usuarios, roles, registros y los módulos opcionales, cada uno como un CRUD generado a partir de una clase PHP corta. Esta guía cubre lo que trae el panel y cómo registrar un recurso propio con Laravel Front, el paquete que lo dibuja.
Cómo llegar
El panel vive en /admin. Sus rutas están en routes/admin.php, y bootstrap/app.php envuelve ese archivo completo en los middleware web, auth, security y role:admin, bajo el prefijo de nombre de ruta admin.. Quien tiene sesión y el rol de administrador ve la entrada Go To Admin Panel en el menú de usuario, arriba a la derecha de la aplicación.
De esa pila de middleware se siguen tres cosas:
- Necesitas sesión. A un visitante se le manda a la página de inicio de sesión.
securityaplica las mismas revisiones que el resto del área de cuenta: una cuenta bloqueada pierde la sesión, y una cuenta marcada para cambiar contraseña va a la pantalla de contraseña antes que a ninguna otra.- Necesitas el rol llamado
admin. El middleware lleva ese nombre literal, así que la claveadmin_roledeconfig/app.phpdebe seguir siendoadminpara que el panel abra.
Los superadministradores no son un rol. config/app.php → sudo es una lista de correos, y php artisan db:seed (a través de AdminSeeder) crea una cuenta por cada uno y le asigna el rol de administrador. Ese rol es el que abre la puerta; estar además en la lista sudo desbloquea la Dev Zone y el registro de actividad. Mira /help/sudo-and-super-administrators.
Ya dentro, cada pantalla revisa su propia política, y la política revisa un permiso con la forma {acción} {recurso}. El rol de administrador sembrado tiene todos los permisos, así que una instalación nueva lo muestra todo. Un rol que crees muestra sólo lo que le concedas, y un recurso que su rol no puede leer desaparece del menú. Mira /help/roles-and-permissions.
La disposición
Barra lateral. resources/views/layouts/sidebars/admin.blade.php renderiza el componente <x-sidebar-menu> (App\View\Components\SidebarMenu) con dos tipos de entradas: los enlaces manuales que declara (Dashboard en /admin y Dev Zone en /admin/dev, visible sólo para cuentas sudo) y cada recurso registrado que devuelve true en showOnMenu() y cuyo modelo pasa la revisión viewAny de su política para el usuario actual. Los recursos se agrupan por su $menu_group; uno sin grupo queda en el bloque sin encabezado de arriba. La barra declara el orden de sus grupos (Logs, Plans, Users); un grupo que no nombra va después de esos, en orden alfabético. Cada grupo recuerda en localStorage si lo dejaste abierto.
Búsqueda. Cuando el modelo del recurso usa el trait Searchable, la barra superior muestra un cuadro de búsqueda que filtra el índice por las columnas $searchable del modelo. Usuarios, roles y avisos se pueden buscar de fábrica.
Dashboard. /admin es un componente Livewire (App\Livewire\Admin\Dashboard) con un rango de fechas (por omisión, los últimos 30 días) y un selector de métrica. Las métricas siempre disponibles son New Registered Users, Total Accumulated Users, Activity Log Events, Daily Logged Users y Users by Country. Tracking Events aparece cuando FEATURE_TRACKING_ENABLED está encendida, Referral Subscriptions cuando FEATURE_REFERRALS_ENABLED está encendida, y el paquete de cobros agrega las suyas cuando está instalado. La métrica elegida se guarda en la URL, así que una vista del dashboard se puede guardar como marcador. Cada métrica está descrita en /help/dashboard-metrics-and-visitor-tracking.
Lo que trae el panel
Cada clase en app/Front/Resources/ es un recurso. Todas extienden la clase local App\Front\Resources\Resource, que fija los valores por omisión $section = 'admin', $showOnMenu = true y el icono circle-stack.
| Recurso | URL | Grupo del menú | Qué administra |
|---|---|---|---|
User |
/admin/users |
Users | Cuentas: nombre, las identidades habilitadas en config/auth.php (correo, teléfono, usuario), PIN cuando auth.enable_pin está encendido, contraseña con botón generador, roles, verificación, ubicación detectada, fecha de bloqueo, registros de comunicación. Filtro por plan cuando los cobros están activos. Acciones: Act as, Block User, Reset Pin. |
Role |
/admin/roles |
Users | Roles y su matriz de permisos. Acción: Clone. |
Permission |
/admin/permissions |
oculto | Lista de permisos de sólo lectura; showOnMenu es false y su política niega crear, actualizar y eliminar. Existe para que la pantalla de roles pueda cargar la lista. |
Announcement |
/admin/announcements |
ninguno | Avisos que ven todos los usuarios. Visible sólo cuando FEATURE_ANNOUNCEMENTS está encendida. Mira /help/announcements. |
Activity |
/admin/activities |
Logs | El registro de actividad que escriben los modelos (evento, sujeto, causante, propiedades JSON). Sólo índice y detalle. Su política exige el rol de administrador y un correo sudo. |
CommunicationLog |
/admin/communication_logs |
Logs | Cada correo y SMS que la aplicación envió o no pudo enviar: destinatario, clase de notificación, asunto, cuerpo HTML. Filtro por tipo y estado. Sin crear ni editar. |
TrackingEventType |
/admin/tracking-event-types |
Tracking | Los eventos con nombre que envía el módulo de seguimiento. Sólo cuando FEATURE_TRACKING_ENABLED está encendida. |
TrackingSession |
/admin/tracking-sessions |
Tracking | Sesiones de visitantes con campaña, IP, identificadores de Facebook y sus eventos. Sólo índice y detalle. Misma bandera. |
TrackingEvent |
/admin/tracking-events |
Tracking | Eventos individuales con su carga. Filtro por tipo, rango de fechas y sesión. Misma bandera. |
Tres pantallas son componentes Livewire normales registrados en el mismo archivo:
| URL | Componente | Qué hace |
|---|---|---|
/admin/dev |
Admin\DevZone |
Comandos de despliegue, editor de .env, botones de prueba. Aborta con 405 salvo que la cuenta sea sudo. Mira /help/the-devzone. |
/admin/webhook-events |
Admin\WebhookEvents |
Webhooks recibidos con carga, respuesta y error, filtrados por origen y tipo de evento. Mira /help/audit-trails-and-webhook-events. |
/admin/categories/{type} |
Shared\Category |
Un editor en árbol para las categorías de un tipo dado, abierto desde la acción Categories de un recurso que las use. Mira /help/models-casts-and-building-blocks. |
Registra un recurso propio
El ejemplo de abajo agrega un recurso Plan. Cambia el nombre por el tuyo.
1. Genera la clase
php artisan front:resource Plan -a
composer dump-autoload
-a crea también App\Models\Plan, su migración y App\Policies\PlanPolicy. Usa -m para sólo el modelo y la migración, -p para sólo la política, o ninguna bandera cuando el modelo ya existe. El comando escribe app/Front/Resources/Plan.php desde una plantilla con $base_url = '/admin/plans'; la URL es el plural en snake case del nombre de la clase bajo el default_base_url (/admin) de la configuración del paquete.
composer dump-autoload importa: el seeder de permisos descubre los recursos en el mapa de clases de Composer, y el ajuste optimize-autoloader del proyecto sólo refresca ese mapa cuando lo vuelcas.
2. Describe la pantalla
<?php
namespace App\Front\Resources;
use App\Front\Filters;
use App\Models\Plan as Model;
use WeblaborMx\Front\Inputs;
class Plan extends Resource
{
public $base_url = '/admin/plans';
public $model = Model::class;
public $title = 'name';
public $icon = 'credit-card';
public $menu_group = 'Plans';
public $menu_order = 1;
public function indexQuery($query)
{
return $query->latest();
}
public function fields()
{
return [
Inputs\ID::make(),
Inputs\Text::make('Name')->rules(['required', 'string', 'max:255']),
Inputs\Money::make('Price')->rules(['required', 'numeric', 'min:0']),
Inputs\Boolean::make('Active')->default(true),
Inputs\Textarea::make('Description')->hideFromIndex(),
Inputs\DateTime::make('Created At')->onlyOnDetail(),
];
}
public function filters()
{
return [
(new Filters\BooleanFilter)->setTitle('Active'),
(new Filters\TextFilter)->setTitle('Name'),
];
}
}
Las propiedades que entiende el recurso:
| Propiedad | Por omisión | Qué hace |
|---|---|---|
$base_url |
obligatoria | La URL del índice. Su último segmento se vuelve el prefijo y el nombre de las rutas. |
$model |
obligatoria | El modelo Eloquent detrás de la pantalla. |
$title |
'name' |
La columna que se muestra como etiqueta del registro en enlaces, migas de pan y relaciones. |
$search_title |
$title |
La columna que compara el cuadro de búsqueda y los campos de autocompletar. |
$icon |
'circle-stack' |
Un nombre de Heroicons para la barra lateral. |
$menu_group |
null |
El encabezado de la barra lateral bajo el que va la entrada. null la pone en el bloque sin grupo. |
$menu_order |
null |
Posición dentro del grupo. Las entradas sin uno van después de las ordenadas. |
$showOnMenu |
true |
Ponlo en false para que la pantalla siga accesible pero fuera de la barra lateral. Sobrescribe showOnMenu() para decidirlo en tiempo de ejecución, como hacen los recursos de seguimiento con su bandera. |
$section |
'admin' |
La barra lateral a la que pertenece el recurso; debe coincidir con el primer segmento de la URL. |
$actions |
las siete | Qué operaciones CRUD existen: index, create, store, show, edit, update, destroy. ['index', 'show'] produce un registro de sólo lectura. |
$pagination |
50 |
Filas por página. |
$default_sort / $default_sort_direction |
null / 'desc' |
Orden inicial del índice. |
$enable_index_sorting |
true |
Clic en el encabezado de una columna para ordenar. |
$enable_column_preferences |
true |
Cada usuario puede ocultar, mostrar y reordenar las columnas del índice; la elección se recuerda. |
$enable_export / $enable_import |
true |
Exportar el índice a una hoja de cálculo, e importar filas desde una en {base_url}/import. |
$show_create_button_on_index |
true |
El botón Create del índice. |
Campos. fields() devuelve inputs de WeblaborMx\Front\Inputs. El primer argumento es la etiqueta; la columna es su snake case salvo que pases un segundo argumento (Inputs\Text::make('Sent At', 'created_at')). Inputs disponibles: Autocomplete, BelongsTo, BelongsToMany, Boolean, Check, Checkboxes, Code, Date, DateTime, Disabled, File, HasMany, HasOneThrough, Hidden, ID, Image, ImageCropper, ImagePointer, Images, InputGroup, Money, MorphMany, MorphTo, MorphToMany, Number, Password, Percentage, Select, Text, Textarea, Time, ToastEditor (Markdown), Trix. El proyecto agrega los suyos bajo App\Front\Inputs: Password con botón generador, Pin, PermissionSelector y Categories.
Encadena esto en cualquier input:
| Método | Efecto |
|---|---|
rules(), creationRules(), updateRules() |
Validación para ambos formularios, sólo al crear, sólo al editar. |
hideFromIndex(), hideFromDetail(), hideWhenCreating(), hideWhenUpdating() |
Quita el campo de un lugar. |
onlyOnIndex(), onlyOnDetail(), onlyOnForms(), onlyOnCreate(), onlyOnEdit(), exceptOnForms() |
Deja el campo en un solo lugar. |
show($bool) |
Muestra u oculta según una condición en tiempo de ejecución, como una bandera. |
default($value) |
Valor inicial del formulario. |
help($text) |
Una pista bajo el input. |
placeholder($text) |
El placeholder del input. |
options($array) |
Las opciones de un Select o Checkboxes. |
sortable() / unsortable() |
Si la columna del índice ordena. |
exportable() / unexportable(), importable() / unimportable() |
Si el campo participa en la exportación y la importación. |
hideWhenValuesSet() |
Oculta el input cuando el valor llega en la URL, como al crear desde una relación. |
conditional("type=='normal'") |
Muestra el input sólo cuando otro input tiene cierto valor. |
Filtros. filters() devuelve instancias de las clases de app/Front/Filters/: TextFilter, SelectFilter (->options([...]), ->multiple()), BooleanFilter (->setTrueValue(...)) y DateFilter. setTitle($title, $field, $slug) nombra el filtro y, opcionalmente, la columna y la clave del query string; setOperator('>=') cambia la comparación y setScope('byPlan') delega en un scope del modelo. Cuando el modelo se puede buscar, el filtro de búsqueda se agrega solo.
Acciones. actions() devuelve clases que extienden App\Front\Actions\Action, un botón por registro. Define $title e $icon, pon $show_on_index = true para ofrecer el botón también en la lista y no sólo en el detalle, devuelve un booleano en hasPermissions($object) y haz el trabajo en handle($object). Devuelve una redirección o nada. Cuando fields() devuelve inputs, la acción muestra primero un formulario y recibe sus datos en $this->data. index_actions() hace lo mismo con clases que extienden App\Front\Actions\IndexAction, que actúan sobre la lista y no sobre un registro. ActingAs, BlockUser, ResetPin y CloneRole en app/Front/Actions/ son ejemplos que funcionan. GoToCategories es una acción lista cuyo handle() no recibe registro: redirige a la pantalla de categorías del tipo que le des con (new Actions\GoToCategories)->setTitle('Tags')->setType('tag').
Tarjetas. cards() devuelve tarjetas que se dibujan sobre la tabla del índice; el paquete trae WeblaborMx\Front\Cards\NumericCard, que extiendes con un método value() y que guarda su número en caché cinco minutos.
Páginas. Una pantalla que no es un CRUD — un reporte, un formulario de ajustes — extiende App\Front\Pages\Page, declara fields() para lo que muestra y post() para lo que guarda, y se registra con Route::page('Settings', 'settings'), que monta App\Front\Pages\Settings en /admin/settings para GET, POST, PUT y DELETE.
Ganchos. Sobrescribe estos para ejecutar código alrededor del CRUD: indexQuery($query) para cambiar la consulta del listado, indexResult($result) para cambiar la colección, processDataBeforeSaving($data) para editar la entrada antes de escribirla, beforeUpdate($object, $request), store($object, $request) y update($object, $request) después de guardar, processAfterSave($object, $request) después de cualquiera de los dos, destroy($object) antes de eliminar (devuelve false para impedirlo), y beforeRequest() para secuestrar cualquier petición después de la autorización.
Los ganchos de layout del paquete se configuran en config/front.php. El kit pone scripts_stack en scripts, el stack de Blade que el layout de la aplicación ya imprime, así que un input que necesita JavaScript lo empuja ahí.
3. Agrega la ruta
// routes/admin.php
Route::front('Plan');
Route::front registra el CRUD completo en una línea. Dentro de routes/admin.php las rutas toman la familia de nombres admin.front.plans y el prefijo /admin/plans:
| Nombre de ruta | Método y URL |
|---|---|
admin.front.plans |
GET /admin/plans |
admin.front.plans.create |
GET /admin/plans/create |
admin.front.plans.store |
POST /admin/plans |
admin.front.plans.show |
GET /admin/plans/{id} |
admin.front.plans.edit |
GET /admin/plans/{id}/edit |
admin.front.plans.update |
PUT /admin/plans/{id} |
admin.front.plans.destroy |
DELETE /admin/plans/{id} |
admin.front.plans.import |
GET /admin/plans/import |
admin.front.plans.restore, .force-delete |
Recuperación de borrados suaves cuando el modelo usa SoftDeletes. |
admin.front.plans.index_action, .show_action |
Las pantallas de acciones, por slug de la acción. |
Un recurso registrado pero sin enlace desde ningún lado sigue siendo accesible por URL y sigue protegido por su política.
4. Escribe la política
<?php
namespace App\Policies;
class PlanPolicy extends BasePolicy
{
protected string $name = 'plan';
protected $requiredConfig = 'features.plans_enabled';
}
$name es el singular de la tabla del modelo, y es la segunda palabra de cada permiso que el recurso necesita. $requiredConfig es opcional: cuando la clave de configuración que nombra es falsa, toda revisión falla, la pantalla se oculta del menú y sus rutas devuelven 403. Laravel encuentra App\Policies\PlanPolicy para App\Models\Plan por sí solo; sólo un modelo fuera de App\Models necesita Gate::policy() en AppServiceProvider::boot(), que es como se conecta ActivityPolicy.
5. Crea los permisos
php artisan db:seed --class=PermissionSeeder
php artisan db:seed --class=RoleSeeder
El primer comando crea create plan, retrieve plan, update plan y delete plan; el segundo se los concede al rol de administrador. Hasta que corras ambos, incluso el administrador ve un 403 en la pantalla nueva. Las reglas completas están en /help/roles-and-permissions.
6. Ponlo en el menú
Nada que hacer: la barra lateral lee $menu_group, $menu_order e $icon de la clase, y oculta la entrada a quien su rol no tenga retrieve plan. Para darle una posición fija a un grupo nuevo, agrégalo a la lista :groups de resources/views/layouts/sidebars/admin.blade.php; para agregar un enlace que no es un recurso, agrégalo a :items con name, url, icon, un menu_group opcional, order y un booleano show.