Panel administrativo con el ciclo completo de gestión de usuarios: el administrador da de alta, edita y elimina usuarios; el usuario creado recibe un correo de bienvenida y el administrador un resumen con el conteo de usuarios por país.
Es un proyecto pequeño a propósito. Lo que muestra no es el CRUD, sino cómo está resuelto alrededor: autorización por política, validación en Form Requests, efectos secundarios desacoplados mediante eventos y correo fuera del ciclo de la petición.
Con Docker (no necesita PHP ni MySQL instalados):
git clone https://github.com/briend17/user-app.git && cd user-app
cp .env.example .env
docker compose up -d
docker compose exec app composer install
docker compose exec app php artisan key:generate
docker compose exec app php artisan migrate --seed
docker compose exec app npm install && docker compose exec app npm run devLa aplicación queda en http://localhost:8000 y los correos capturados por Mailhog en http://localhost:8025.
Instalación local sin Docker
Requiere PHP 8.0+, Composer, Node 16+ y MySQL 8.
cp .env.example .env
composer install
npm install && npm run dev
php artisan key:generate
php artisan migrate --seed
php artisan serve| Perfil | Correo | Clave |
|---|---|---|
| Administrador | admin@userapp.com.co |
12345678 |
Los usuarios cliente los crea el administrador desde el panel.
Los envíos van en cola (ShouldQueue). En local, con QUEUE_CONNECTION=sync se
procesan al vuelo; en producción hace falta un worker:
php artisan queue:workCon Docker, Mailhog captura todo el correo saliente sin configuración extra.
php artisan test # o: docker compose exec app php artisan testLa suite corre sobre SQLite en memoria: no hace falta base de datos externa ni conexión a internet. GitHub Actions la ejecuta en cada push y pull request.
El panel es de administración y lo dice el código. Las rutas estaban tras
auth + verified, pero cualquier usuario autenticado podía listar, editar o
borrar a cualquier otro. UserPolicy (aplicada con authorizeResource) restringe
el panel al perfil administrador e impide que un administrador se borre a sí mismo
y deje el sistema sin acceso.
Solo entra lo validado. User::create($request->all()) dejaba pasar cualquier
campo del formulario a Eloquent, incluido password en texto plano. Ahora se usa
validated(), y las reglas comprueban lo que dicen comprobar: celular valida
diez dígitos con digits:10, no un rango numérico como hacía min:10|max:10.
El correo no bloquea la petición. Los listeners implementan ShouldQueue. El
resumen por país se calcula con withCount, que sustituye a cargar en memoria
todos los usuarios de todos los países y lanzar además un COUNT por fila desde
la vista.
Los datos de arranque son deterministas. El seeder de países consultaba una
API externa con la verificación TLS desactivada: migrate --seed fallaba sin
conexión o en CI, y los identificadores fijos del seeder de usuarios dependían del
orden de esa respuesta. Ahora la lista es local y las relaciones se resuelven por
nombre.
Los errores se registran, no se tragan. El manejo original hacía
catch (Exception $e) dentro de un namespace, que resuelve a App\...\Exception y
por tanto no capturaba nada: la transacción nunca se revertía y el usuario recibía
una redirección de éxito aunque la operación hubiera fallado.
Laravel 8 · PHP 8 · MySQL 8 · Vue 3 + Inertia.js · Tailwind CSS · PHPUnit · Docker Compose · GitHub Actions