Apariencia
Troubleshooting
Runbook de problemas comunes que se encuentran en dev y prod, con el diagnóstico y fix probado.
Logs primero
El 80% de los bugs se resuelven leyendo logs. Antes de cambiar código, siempre empieza por:
bash
# Laravel app logs
tail -f storage/logs/laravel-$(date +%F).log
# Nginx access
tail -f /var/log/nginx/humae_api_access.log
# Nginx errors
tail -f /var/log/nginx/humae_api_error.log
# Queue worker
sudo supervisorctl tail -f humae-queue:humae-queue_00
# MySQL slow queries
tail -f /var/log/mysql/slow.logFiltros útiles:
bash
# Ver solo errores del backend
tail -f storage/logs/laravel-*.log | grep -E "(ERROR|CRITICAL|ALERT)"
# Ver webhooks de Stripe
tail -f storage/logs/laravel-*.log | grep -i stripe
# Ver solo la app sin noise
tail -f storage/logs/laravel-*.log | grep -v "telescope\|scout\|debugbar"Problemas comunes
1 · "SQLSTATE[HY000] [2002] Connection refused" en migraciones
Causa: MySQL no está corriendo o DB_HOST es incorrecto.
Fix:
bash
# Verificar que MySQL esté activo
sudo systemctl status mysql
# Si no: sudo systemctl start mysql
# Verificar conexión
mysql -u $DB_USERNAME -p$DB_PASSWORD -h $DB_HOST $DB_DATABASE -e "SELECT 1;"
# Si usas Docker:
docker compose ps
# Si mysql no está running: docker compose up -d mysql2 · Error 419 CSRF token mismatch (frontend SPA)
Causa: la cookie XSRF-TOKEN no llega o el header X-XSRF-TOKEN no se envía.
Fix:
- El frontend debe llamar
GET /sanctum/csrf-cookieantes del primer POST. - Revisar
SANCTUM_STATEFUL_DOMAINS— debe incluir el dominio del frontend. - Revisar
SESSION_DOMAIN— debe cubrir ambos subdominios (ej..humae.com.mx). - Axios en el frontend debe tener
withCredentials: true.
3 · "Cannot redeclare function X" al correr tests
Causa: helpers de test definidos con mismo nombre en archivos distintos.
Fix: renombrar uno, o mover a una clase helper:
php
// Antes (colisiona):
function makeActiveCandidate(): CandidateProfile { /*...*/ } // en dos archivos
// Después:
function directoryTestMakeActiveCandidate(): CandidateProfile { /*...*/ }
function pipelineTestMakeActiveCandidate(): CandidateProfile { /*...*/ }Mejor opción: tests/Support/TestData.php con métodos estáticos.
4 · Migration falla con "Specified key was too long"
Causa: el nombre auto-generado del índice supera 64 chars (límite de MySQL).
Fix: asigna nombre explícito:
php
// Antes
$table->unique(['psychometric_attempt_id', 'psychometric_question_id']);
// Después
$table->unique(
['psychometric_attempt_id', 'psychometric_question_id'],
'uq_pa_pq',
);5 · Migration falla con "Invalid default value for 'birth_date'"
Causa: MySQL 8 con modo NO_ZERO_DATE rechaza timestamp default 0000-00-00.
Fix: usar datetime en vez de timestamp:
php
// Antes
$table->timestamp('birth_date')->nullable();
// Después
$table->datetime('birth_date')->nullable();6 · PHPStan nivel 8 falla con "chain of method calls on string"
Causa: faltan @property docblocks en el modelo, PHPStan infiere string para columnas.
Fix: agregar docblock completo:
php
/**
* @property int $id
* @property string $title
* @property VacancyState $state
* @property \Carbon\Carbon|null $published_at
* @property \Carbon\Carbon $created_at
* @property-read Company $company
*/
class Vacancy extends Model { /*...*/ }Alternativa: php artisan ide-helper:models -W auto-genera.
7 · PHPStan falla en relaciones HasMany<X, self>
Causa: Laravel 12 cambió covariance. El segundo type param debe ser $this no self.
Fix:
php
// Antes (Laravel 11 style)
/** @return HasMany<VacancyAssignment, self> */
public function assignments(): HasMany { /*...*/ }
// Después (Laravel 12 style)
/** @return HasMany<VacancyAssignment, $this> */
public function assignments(): HasMany { /*...*/ }Para reemplazar en todos los archivos de una:
bash
find app/Models -name "*.php" -exec sed -i '' 's/, self>/, \$this>/g' {} \;8 · Stripe webhook no llega al backend
Diagnóstico paso a paso:
bash
# 1. ¿El endpoint es accesible desde internet?
curl -i https://api.humae.com.mx/api/webhooks/stripe -X POST -d '{}'
# Debe devolver 400 (firma inválida), no 404 ni 502
# 2. ¿Stripe está disparando?
# Dashboard Stripe → Webhooks → tu endpoint → últimos eventos
# ¿Aparecen eventos con status "failed"? Ver error
# 3. ¿El signing secret es el correcto?
# En .env: echo $STRIPE_WEBHOOK_SECRET
# En Stripe dashboard: el "Signing secret" de tu endpoint
# Deben coincidir exactamente
# 4. ¿El backend recibe el POST?
tail -f storage/logs/laravel-*.log | grep -i webhookFix común: Cloudflare proxy rechazando el payload grande.
Solución: regla WAF bypass para POST /api/webhooks/*.
9 · "Field 'X' doesn't have a default value" al insertar
Causa: la columna es NOT NULL en DB pero el factory o create no la provee.
Fix: agregar al factory o pasar el valor:
php
// Factory
'level' => fake()->randomElement(['basico', 'intermedio', 'avanzado']),
// O en attach pivot:
$profile->skills()->attach([
$skillId => ['level' => 'avanzado'], // pivot columns
]);10 · /storage/avatars/... devuelve 404
Causa: el symlink public/storage → storage/app/public no existe (no se corrió php artisan storage:link).
Fix:
bash
cd /var/www/humae_backend
php artisan storage:link
sudo chown -R www-data:www-data storage bootstrap/cache public/storageSi el archivo físico está en storage/app/public/avatars/... pero igual devuelve 404, verificar que Nginx no esté rewriteando /storage/* al index.php y que el symlink sea legible por www-data.
11 · Emails no llegan al inbox
Diagnóstico:
bash
# 1. ¿Laravel intenta enviar?
tail -f storage/logs/laravel-*.log | grep -i mail
# 2. ¿Postfix recibe la conexión SMTP de Laravel?
sudo tail -f /var/log/mail.log
# 3. ¿Hay mensajes en la cola local?
mailq
# 4. ¿El MTA destino aceptó?
# - status=sent → OK
# - status=deferred → reintentará
# - status=bounced → revisar motivo (rechazo por reputación, dominio inexistente, etc.)Fix comunes:
connect to 127.0.0.1:25: connection refused→ Postfix no está corriendo.sudo systemctl start postfix.- Todo termina en spam → falta PTR, DKIM o SPF. Verificar con
dig +short txt humae.com.mxy con el headerAuthentication-Resultsdel correo recibido (debe decirdkim=pass spf=pass). status=deferred (network is unreachable)→ el provider cloud bloquea 25 saliente. Pedir desbloqueo o migrar de provider.- Queue no procesa →
supervisorctl status humae-queue:*.
12 · Queue worker no procesa jobs
Diagnóstico:
bash
# Ver estado
sudo supervisorctl status humae-queue:*
# Debe decir RUNNING
# Ver logs del worker
sudo supervisorctl tail -f humae-queue:humae-queue_00
# Ver jobs encolados
redis-cli -a $REDIS_PASSWORD LLEN queues:default
# > 100 puede indicar que el worker no está procesando
# Ver failed jobs
php artisan queue:failedFix comunes:
- Worker crasheado →
supervisorctl restart humae-queue:*. - Job demasiado grande (memory leak) → ajustar
memory_limiten PHP. - Job >
$timeout→ aumentar el timeout del job o del worker. - Redis caído →
systemctl status redis-server.
13 · "Target class [XYZ] does not exist" al resolver DI
Causa: namespace incorrecto, autoload no actualizado, o binding mal registrado.
Fix:
bash
composer dump-autoload
# Si es un binding custom, verificar AppServiceProvider::register()
php artisan config:clear
php artisan cache:clear14 · OPcache muestra código viejo tras deploy
Causa: opcache.validate_timestamps=0 en prod (recomendado), pero no hiciste reload tras deploy.
Fix:
bash
sudo service php8.3-fpm reload
# O graceful:
sudo systemctl reload php8.3-fpmAutomatizar en el script de deploy.
15 · "Route [X] not defined" al llamar route('X')
Causa: php artisan route:cache cachea rutas viejas o la ruta no existe.
Fix:
bash
php artisan route:clear
php artisan route:list | grep <nombre>
# verificar que existe
# En prod: después de cambios en rutas
php artisan route:cache16 · Tests pasan local pero fallan en CI
Causas comunes:
- Timezone diferente → fijar
APP_TIMEZONEen tests. - Dependencias de orden (tests que comparten estado) → revisar
beforeEachyRefreshDatabase. - Random data → usar seeds fijas con
fake()->seed(1)o fixtures. - DB driver diferente → MySQL vs SQLite tienen comportamiento sutil distinto (JSON columns, date parsing).
Debug local de CI:
bash
# Replicar CI env
APP_ENV=testing php artisan test17 · Frontend CORS error
Síntoma: Access to XMLHttpRequest at 'https://api.humae.com.mx/...' from origin 'https://humae.com.mx' has been blocked by CORS policy
Fix:
Verificar
FRONTEND_URLen.envdel backend.Verificar
config/cors.phpinclude el origen.Limpiar cache:
php artisan config:clear.Si tienes múltiples orígenes (staging), separar con coma:
envFRONTEND_URL=https://humae.com.mx,https://staging.humae.com.mxY en
config/cors.php:php'allowed_origins' => array_map('trim', explode(',', env('FRONTEND_URL', ''))),
18 · "Class 'Redis' not found"
Causa: extensión PHP redis no instalada.
Fix:
bash
sudo apt install php8.3-redis
sudo systemctl restart php8.3-fpmO usar el cliente PHP puro:
env
REDIS_CLIENT=predisY en composer:
bash
composer require predis/predisPerformance diagnóstico
Queries lentas
sql
-- Activar slow log (my.cnf)
slow_query_log = 1
long_query_time = 1
-- Ver slow queries
tail -f /var/log/mysql/slow.logCorrer EXPLAIN en queries lentas. Si falta índice, agregar migration.
N+1 detection
Usar spatie/laravel-ray o [barryvdh/laravel-debugbar] en dev.
En prod: revisar logs de slow queries con SQL repetido.
Memory leaks en queue workers
Workers PHP acumulan memoria con el tiempo. Solución: --max-time=3600 (reciclar worker cada hora).
Debugging con Tinker
bash
php artisan tinkerÚtil para:
php
// Ver estado actual
>>> \App\Models\User::count()
>>> \App\Models\Vacancy::where('state', 'activa')->count()
// Inspeccionar un modelo
>>> $u = \App\Models\User::find(1);
>>> $u->roles
>>> $u->candidateProfile
// Probar un service
>>> $service = app(\App\Services\MembershipService::class);
>>> $service->expireStale();
// Disparar una notification manualmente
>>> $u->notify(new \App\Notifications\WelcomeNotification());
// Ejecutar código arbitrario
>>> \DB::select('SELECT COUNT(*) as n FROM users');Telescope en dev
Si el proyecto tiene Laravel Telescope instalado:
bash
php artisan telescope:install
php artisan migrateAcceder en /telescope (solo dev). Muestra:
- Todas las requests con payload, response, duration
- Queries ejecutadas (detecta N+1)
- Jobs dispatched + results
- Notifications enviadas
- Cache hits/misses
- Exceptions
Nunca habilitar en prod — expone datos sensibles.
Cuándo hacer rollback
Solo si:
- Un endpoint crítico está caído (auth, checkout, webhooks).
- Corrupción de datos en curso.
- Falla de seguridad expuesta.
Cómo:
Identificar el commit bueno:
git log.Deploy del commit anterior:
bash# Si usas GitOps (Vercel, Render auto-deploy) git revert <bad-commit> git push # Si es manual ssh prod cd /var/www/humae_backend git checkout <good-sha> composer install --no-dev php artisan migrate:rollback # SOLO si la migration causa el problema sudo service php8.3-fpm reloadRestaurar DB desde backup si hubo corrupción.
No hacer rollback de migraciones sin pensar — pueden perder datos. Es mejor hacer una migration forward que revierta el comportamiento.
Contacto
- Email:
dev@humae.com.mx - Slack interno:
#humae-backend - Issues en GitHub:
github.com/tu-org/humae-backend/issues
Manual terminado. Siguiente recurso: API docs con Scribe o el checklist de deploy.

