Se interpola directamente $request->search en whereRaw() sin parametrizar, permitiendo inyección SQL arbitraria por cualquier usuario autenticado.
Usar la interpolación parametrizada de Eloquent o el helper where() con LIKE y binding seguro.
Subscription::create($request->all()) sin que el modelo defina $fillable. Un atacante puede sobrescribir campos como status, price_override o is_admin inyectando parámetros extra en el POST.
Usar FormRequest para validar y añadir $fillable con los campos permitidos en el modelo.
El endpoint /admin/customers/export retorna todos los usuarios con email, nombre y datos de pago sin verificar que el usuario sea administrador. Cualquier usuario autenticado puede descargar el CSV completo.
Proteger con Gate o Policy, y limitar los campos exportados. Registrar en log de auditoría.
Se itera sobre $subscriptions accediendo a $sub->user->email dentro del bucle sin haber hecho with('user'). Con 500 suscripciones = 501 queries.
Añadir with('user') (o definir $with en el modelo si la relación se usa siempre).
PHP 8.3 permite tipos estrictos por archivo. Sin declare(strict_types=1) las conversiones implícitas pueden enmascarar bugs de tipo (ej. int "1" → 1 sin error).
Añadir en la primera línea tras el tag de apertura en todos los archivos que no sean vistas Blade.
El método store() tiene 74 líneas e incluye cálculo de precio, llamada a Stripe, creación del modelo y envío de email. Viola SRP, imposible de testear unitariamente.
Extraer a CreateSubscriptionAction (patrón Action de Laravel). El controlador solo orquesta: valida → action → responde.
La clave secreta de Stripe sk_live_… está directamente en el código fuente. Quedará expuesta en el historial de git para siempre.
Leer de config('services.stripe.secret') (que a su vez lee de .env). Rotar la clave comprometida inmediatamente.
User::with('subscriptions')->get() carga todos los usuarios en memoria. Con 50k registros el proceso PHP agotará memoria y el servidor caerá.
Usar cursor() o chunk() para streaming del CSV sin saturar memoria.
Dos llamadas a dd($response) y dump($this->attributes) quedaron sin eliminar. En producción interrumpen la respuesta HTTP y exponen datos internos.
Eliminar todas las llamadas. Configurar el linter para bloquear dd/dump/var_dump en pre-commit (ya soportado por Pint con la regla no_debug_backtrace).
if (count($subscriptions) > 0) cuando solo se quiere comprobar si hay elementos. Menos expresivo y lanza una query adicional en colecciones lazy.
Usar $subscriptions->isNotEmpty() para claridad semántica. Solo usar count() cuando el número exacto sea necesario.
Pint detecta 12 violaciones: imports mezclados (clases, interfaces, traits), espaciado alrededor de operadores y llaves de cierre sin línea en blanco.
Ejecutar ./vendor/bin/pint (no solo --test) para aplicar correcciones automáticamente. Añadir al pre-commit hook de CI.
starts_at/ends_at retornan strings en lugar de objetos Carbon. El campo status es un string sin enum, perdiendo type-safety.