Aplicación web full-stack (Laravel 12 + Inertia 2 + Vue 3 + TypeScript) que sustituye las tarjetas de fidelización físicas de cartón (N sellos → 1 producto gratis) por tarjetas digitales, para negocios de mascotas: residencias, tiendas y servicios caninos. Incluye panel de administración para el mostrador y portal de cliente mobile-first, con escáner QR por cámara, alta por invitación email, auditoría de dominio y branding configurable por despliegue.
Suite Pest 4 (unit + feature) en verde. Gate de calidad: Pint, PHPStan (level 5, baseline vacío), PHPMD, ESLint y Prettier. Ver
CHANGELOG.md.
Documentación:
docs/PROJECT_OVERVIEW.md— dominio, reglas de negocio, flujos y pantallas.docs/PROJECT_TECHNICAL_OVERVIEW.md— dirección técnica recomendada.
- Arquitectura por capas con Actions de dominio. La lógica de negocio vive en actions explícitas
y testeables (
app/Actions/Loyalty/AddStampToLoyaltyCard,VoidStamp,CompleteLoyaltyCard,GenerateReward,RedeemReward,CancelLoyaltyCard…). Los controllers son finos: autorizan, validan vía Form Requests y delegan. Reglas codificadas y blindadas con tests:required_stampscongelado en la tarjeta al crearla, firma bloqueada en tarjetas no activas, premio modelado como entidad separada, discrepancia de producto como aviso (no bloqueo). - Auditoría propia. Tabla
audit_eventsalimentada porRecordAuditEventdesde las actions críticas (firma añadida/anulada, tarjeta completada/cancelada, premio generado/redimido, invitación enviada…), para reconstruir qué pasó ante cualquier disputa. - Operativa de mostrador sin fricción. Buscador global con debounce, atajos de teclado
(
/,Ctrl+K, flechas,Enter) y escáner QR desde la cámara; compatible también con lector USB de códigos de barras. La pantalla estrella es el detalle de tarjeta: tarjeta visual grande, botón gigante "Añadir firma", historial cronológico y anulación con motivo. - Portal de cliente mobile-first. Tarjetas activas con QR escaneable, banner de premios
pendientes, historial, y ajustes propios (perfil, contraseña, tema claro/oscuro/sistema, eliminar
cuenta sin afectar al
Customerdel admin). - Alta por invitación email. Sin registro público: el admin crea la cuenta y, en el mismo gesto,
invita al portal. El cliente recibe un email, fija su contraseña (
portal/setup) y entra con auto-login. Flujo endurecido contra accesos directos sin token. - Multi-instancia por configuración.
config/branding.phplee deenv('BRAND_*'): cada despliegue aplica su propia paleta, logos y favicon sin tocar código, gracias a tokens semánticos de marca (bg-brand-primary,bg-brand-reward…) que resuelven a variables CSS.
- Laravel 12 · PHP 8.2+
- Inertia 2 + Vue 3 + TypeScript
- Tailwind 3 + tokens semánticos de marca
- Pest 4 (Unit + Feature)
- SQLite (por defecto, también para tests en memoria); MariaDB soportado
qr-scanner(cámara) +qrcode(renderizado)- Calidad: Pint (formato), PHPStan + larastan (level 5, baseline vacío), PHPMD; ESLint + Prettier
en el frontend; cabeceras de modelos con
barryvdh/laravel-ide-helper
docs/ Documentación de producto y técnica
src/ Proyecto Laravel completo
Requiere PHP 8.2+, Composer y Node 20+. Por defecto usa SQLite (sin servidor de BD); para MariaDB,
ajusta DB_* en .env.
cd src
composer install
npm install
cp .env.example .env
php artisan key:generate
php artisan migrate --seed # admin + 2 clientes + tarjetas en distintos estados
npm run dev # frontend en modo dev (HMR)La aplicación no tiene registro público: el admin crea las cuentas y, opcionalmente, invita al portal en el mismo gesto.
Login de desarrollo (sembrado con php artisan db:seed):
| Password | Rol | |
|---|---|---|
admin@local |
password |
admin |
[email protected] |
password |
customer |
config/branding.php lee de env. Para reaprovechar la codebase con otro negocio basta con un
.env distinto en su despliegue:
BRAND_NAME="Nombre del negocio"
BRAND_TAGLINE="Tu eslogan aquí"
BRAND_COLOR_PRIMARY_DARK="#1a4f5e"
BRAND_LOGO_HORIZONTAL=/branding/cliente-horizontal.svg
BRAND_FAVICON=/branding/cliente-favicon.svgLos SVG personalizados se colocan en public/branding/.
cd src
./vendor/bin/pest # suite Pest (SQLite en memoria; no toca la BD de desarrollo)
npm run build # build de producción
php artisan migrate:fresh --seed # reset completo de la BD de desarrollocd src
composer lint # Pint en modo --test (formato)
composer analyse # PHPStan (larastan, level 5)
composer mess # PHPMD (ruleset pragmático)
composer quality # los tres anteriores en cadena
npm run lint # ESLint (check)
npm run format:check # Prettier (check)PHPStan arranca con baseline vacío (
phpstan-baseline.neon); PHPMD mantiene un baseline acotado al ruido del seeder de demo. La idea es empezar en verde y ratchetear a futuro.
- Capa de dominio: actions explícitas en
app/Actions/...; controllers finos (autorizan, validan, delegan). - Estados: enums PHP backed by string, almacenados como
VARCHARpara compatibilidad SQLite/MariaDB. - Auditoría: tabla
audit_eventspropia,RecordAuditEventinvocada desde las actions críticas. - Branding: nunca clases Tailwind "duras" tipo
bg-amber-500para conceptos de marca; usar tokens semánticos (bg-brand-reward,bg-brand-primary-soft, etc.) que resuelven a variables CSS configurables. - Tests: Pest 4 con sintaxis closure; feature tests con
RefreshDatabase. Necesitan un build previo (npm run build) porque las páginas Inertia renderizan@vite.
MIT © 2026 mdps
Un proyecto de mdps · 2026 · desarrollado en Murcia.