Apariencia
Deploy del backend a cPanel (develop)
Análogo al deploy del frontend — empaquetas en local con un script y descomprimes en el servidor con otro script. La diferencia clave: el backend solo se zipea el código de la app (sin vendor/, sin .env, sin storage/logs/); el vendor/ ya vive en el servidor y solo se reinstala si composer.lock cambió.
TL;DR — 3 comandos por deploy
bash
# 1. En tu Mac
cd humae_backend && composer deploy:zip
# 2. Subir el zip por File Manager a:
# /home2/humaecom/develop.backend-v1.humae.com.mx/
# 3. En SSH
bash ~/develop.backend-v1.humae.com.mx/deploy.shPor qué este flujo
A diferencia del frontend (donde Next standalone trae su propio node_modules), Laravel necesita vendor/ instalado en el servidor. Pero subir vendor/ cada deploy serían ~50 MB, hace lentísimo el upload por File Manager y arriesga corrupción. Mejor:
- Local: zip con todo MENOS
vendor/,.env*y caches → ~400 KB. - Server: preserva el
.envy elvendor/. Sicomposer.lockcambió → correcomposer install --no-devsolo cuando hace falta.
Hay dos scripts que automatizan todo:
| Script | Dónde corre | Qué hace |
|---|---|---|
humae_backend/scripts/build-deploy-zip.sh | Tu Mac | Empaqueta app/, bootstrap/, config/, database/, lang/, public/, resources/, routes/, storage/ (vacío), artisan, composer.json/lock, .htaccess. Excluye vendor/, .env*, node_modules/, tests/, .git/, storage/logs/*, caches. |
humae_backend/scripts/server-deploy.sh | SSH cPanel | Respalda .env, extrae el zip, restaura .env, detecta si composer.lock cambió y solo entonces reinstala vendor, limpia caches, re-cachea config/routes/views/events, reinicia queue, corre smoke test. |
Setup inicial (una sola vez)
1. Subir el script de deploy del server
Vía cPanel File Manager, sube humae_backend/scripts/server-deploy.sh a:
/home2/humaecom/develop.backend-v1.humae.com.mx/deploy.shDale permisos 755 desde Right click → Permissions.
2. Asegurarte de que .env y vendor/ ya estén en el server
Si es la primera vez, sigue el primer deploy del backend original — necesitas:
.enven~/develop.backend-v1.humae.com.mx/.envcon todos los secretsvendor/instalado:composer install --no-dev --optimize-autoloader- DB migrada:
php artisan migrate --force
A partir de ahí, los deploys subsecuentes son los 3 comandos del TL;DR.
3. Configurar los cronjobs
cPanel no tiene Supervisor, así que el cron de cPanel es lo que mantiene vivos tanto el Laravel Scheduler como el queue worker. Sin esto, jobs como ExpireMembershipsJob nunca corren y las notificaciones encoladas se quedan dormidas para siempre.
Necesitas dos entries de cron, ambas cada minuto:
cron
# 1) Laravel scheduler — dispara los Schedule::job() registrados en routes/console.php
* * * * * /opt/cpanel/ea-php83/root/usr/bin/php /home2/humaecom/develop.backend-v1.humae.com.mx/artisan schedule:run >> /dev/null 2>&1
# 2) Queue worker — procesa la cola y se autoapaga antes del próximo tick
* * * * * /opt/cpanel/ea-php83/root/usr/bin/php /home2/humaecom/develop.backend-v1.humae.com.mx/artisan queue:work --stop-when-empty --tries=3 --max-time=50 --sleep=3 >> /dev/null 2>&1Por qué el segundo así: --stop-when-empty hace que termine cuando se vacía la cola; --max-time=50 lo mata si pasa de 50 s (antes de que el cron lo dispare otra vez). Resultado: nunca corren dos workers simultáneos y no se necesita Supervisor.
Alternativa de una sola entry
Si prefieres una única línea de cron, registra el worker desde el Scheduler en routes/console.php:
php
Schedule::command('queue:work --stop-when-empty --max-time=50')
->everyMinute()
->withoutOverlapping()
->onOneServer();Y elimina la entry #2 del crontab. Solo te queda la del schedule:run. Requiere CACHE_STORE=redis o database para que onOneServer()/withoutOverlapping() tomen lock.
Cómo agregarlas en cPanel
Vía UI: Home → Advanced → Cron Jobs → "Add New Cron Job":
- Common Settings:
Every Minute (* * * * *) - Command: pega la línea completa (PHP path absoluto +
artisan+ flags) - Add New Cron Job → repite para la segunda
Vía SSH: crontab -e, pega ambas líneas, guarda. Verifica con crontab -l.
No uses php pelado
En cPanel php puede apuntar a una versión vieja (5.6, 7.x) según el alias de la cuenta. Siempre usa el path absoluto /opt/cpanel/ea-php83/root/usr/bin/php. Si la cuenta corre otra versión de PHP, ajusta ea-php83 — verifica con ls /opt/cpanel/ | grep ea-php. Una entry de cron con la versión equivocada falla silenciosa: el log de Laravel queda en blanco.
Verificar que están vivos
bash
# 1. El crontab tiene las dos entries
crontab -l | grep -E 'schedule:run|queue:work'
# 2. Laravel ve los schedules registrados
cd ~/develop.backend-v1.humae.com.mx
/opt/cpanel/ea-php83/root/usr/bin/php artisan schedule:list
# → debe listar al menos: memberships:expire 00:15 America/Mexico_City
# 3. Forzar un tick manual con verbose
/opt/cpanel/ea-php83/root/usr/bin/php artisan schedule:run -v
# 4. El worker está procesando — últimas líneas del log de hoy
tail -n 50 storage/logs/laravel-$(date +%F).log | grep -E 'Processing|Processed|Failed'
# 5. Jobs fallidos
/opt/cpanel/ea-php83/root/usr/bin/php artisan queue:failedSi schedule:list muestra los jobs pero ninguno ha corrido en 24 h, casi seguro el cron de cPanel no está disparando: revisa la entry, el path absoluto a PHP y que el cPanel no tenga el cron de la cuenta deshabilitado (Hosting → Cron Jobs muestra un warning si está apagado).
Para el catálogo completo de jobs (frecuencias, qué hace cada uno, troubleshooting específico de cada job) ver Cronjobs y tareas programadas.
Deploys subsecuentes (flujo rápido)
En tu Mac
bash
cd humae_backend
composer deploy:zipEl script imprime al final:
✅ Zip listo: /Users/.../Desktop/humae-backend.zip (375K)Sube el zip
cPanel File Manager → /home2/humaecom/develop.backend-v1.humae.com.mx/ → Upload → arrastra ~/Desktop/humae-backend.zip. Sobrescribe si ya hay uno.
En SSH
bash
bash ~/develop.backend-v1.humae.com.mx/deploy.shLo que hace:
- Verifica que el zip existe.
- Calcula MD5 de
composer.lockactual (antes de extraer). - Respalda
.enva~/_humae_backend_env_backup. - Extrae el zip — NO toca
vendor/,.env, nistorage/{logs,framework,app/public,app/private}. - Restaura
.env. - Aplica permisos
775astorage/ybootstrap/cache/. - Calcula MD5 de
composer.locknuevo. Si cambió → correcomposer install --no-dev --optimize-autoloader. Si no → mantiene elvendor/actual. view:clear,config:clear,route:clear,event:clear, luegoconfig:cache,route:cache,view:cache,event:cache.queue:restart.- Smoke test sobre 3 endpoints:
GET /up → 200
POST /api/v1/auth/login → 422 (esperado, body inválido)
POST /api/v1/webhooks/stripe (invalid sig) → 400 (esperado, firma mala)Si /up no devuelve 200, el script te muestra cómo ver los logs.
Qué incluye y qué NO incluye el zip
✅ Incluye
app/ Toda la lógica de la app (Controllers, Services, Models, etc.)
bootstrap/ Config bootstrap
config/ Configuración de Laravel
database/ Migraciones, factories, seeders
lang/ Traducciones (lang/es/, lang/es.json)
public/ Document root (index.php, .htaccess, /img/, /storage/)
resources/ Vistas, plantillas Blade (incluye plantillas de email customizadas)
routes/ Definición de rutas API/web/console
storage/ Estructura vacía con .gitkeep (no logs, no caches)
artisan CLI de Laravel
composer.json Lista de dependencias (para detectar cambios)
composer.lock Lock file (para `composer install` reproducible)
.htaccess Apache rewrite rules❌ NO incluye (y por qué)
| Excluido | Razón |
|---|---|
vendor/ | Ya está en el server. Solo se reinstala si composer.lock cambió. |
.env y .env.* | El .env de producción tiene secrets que NO deben pisarse en cada deploy. |
node_modules/ | Backend Laravel no usa Node en runtime. |
storage/logs/* | Los logs del server no deben sobrescribirse. |
storage/framework/{cache,sessions,views}/* | Caches que se regeneran solas. |
storage/app/{public,private}/* | Avatares y documentos subidos por usuarios. |
bootstrap/cache/*.php | Configs cacheadas, se regeneran con config:cache. |
tests/ | No se necesitan en producción. |
.git/, .phpunit.cache/ | Metadata de desarrollo. |
Estructura final en el server
/home2/humaecom/develop.backend-v1.humae.com.mx/
├── .env ← preservado entre deploys
├── .htaccess
├── deploy.sh ← script de deploy (subido una vez)
├── humae-backend.zip ← último zip subido
├── app/ ← reemplazado en cada deploy
├── bootstrap/
│ └── cache/ ← regenerado por config:cache
├── config/
├── database/
├── lang/
├── public/ ← document root del dominio
│ ├── index.php
│ ├── .htaccess
│ ├── img/
│ │ └── humae-logo.png
│ └── storage/ ← symlink a ../storage/app/public
├── resources/
├── routes/
├── storage/ ← preservado entre deploys
│ ├── app/
│ │ ├── public/ ← avatares (preservado)
│ │ └── private/ ← documentos (preservado)
│ ├── framework/ ← caches (preservado, se autoregeneran)
│ └── logs/ ← preservado
├── vendor/ ← solo se reinstala si composer.lock cambió
├── artisan
├── composer.json
└── composer.lockCuándo composer install se dispara
Solo cuando agregas/actualizas/quitas paquetes:
bash
# En local
composer require nuevo/paquete
# o
composer update vendor/paqueteEsto modifica composer.lock. Cuando hagas composer deploy:zip, el zip incluye el composer.lock nuevo. El script en server detecta que el hash MD5 cambió y corre:
composer install --no-dev --optimize-autoloader --no-interactionSi solo cambias código (Controllers, Services, Models, vistas, plantillas de email, traducciones), composer.lock no cambia y composer install se salta — el deploy es instantáneo.
Troubleshooting
| Síntoma | Causa | Fix |
|---|---|---|
/up da 503 después de deploy | DB inalcanzable o config corrupta | tail -n 100 storage/logs/laravel-$(date +%Y-%m-%d).log y revisar .env |
| Cambios no se reflejan | Config cacheado | El script ya hace config:clear && config:cache. Si persiste, borra manualmente bootstrap/cache/config.php y bootstrap/cache/routes-v7.php. |
composer install falla | Memoria insuficiente | Exportar COMPOSER_MEMORY_LIMIT=-1 antes de correr bash deploy.sh |
.env desapareció después de extract | El backup quedó en otro path | Restaurar desde ~/_humae_backend_env_backup (el script siempre genera este) |
| Plantillas de email no cambian | Vista cacheada | El script ya hace view:clear. Si persiste: rm -rf storage/framework/views/* |
| Webhook Stripe da "Invalid signature" después de deploy | Config cacheada con secret viejo | php artisan config:clear && php artisan config:cache && php artisan queue:restart |
| Permisos 500 en logo / storage | chmod no se aplicó | Re-ejecutar manualmente: chmod -R 775 storage bootstrap/cache |
Membresías no se marcan expired / correos encolados nunca llegan | Cron de cPanel no configurado, o usando php viejo en vez de /opt/cpanel/ea-php83/root/usr/bin/php | Ver §Configurar los cronjobs |
Apéndice: contenido de los scripts
build-deploy-zip.sh (corre en local)
Lo invocas con composer deploy:zip (registrado en composer.json). El script:
- Verifica que estás en el root de
humae_backend/. - Borra el zip anterior si existe.
- Empaqueta los directorios incluidos, excluyendo todo lo de la sección "NO incluye".
- Inyecta
.gitkeepen las carpetas vacías destorage/ybootstrap/cache/(porque zip no preserva carpetas vacías). - Imprime el path final y los siguientes pasos.
server-deploy.sh (renombrado a deploy.sh en el server)
Maneja todo el ciclo de extracción + restore + composer condicional + cache + restart + smoke test.
Idempotente: si lo corres dos veces sin un zip nuevo, simplemente vuelve a extraer el mismo zip y re-cachear (no rompe nada).
Referencias
- Frontend a cPanel (develop) — flujo análogo para el frontend Next.js
- Checklist maestro de deploy — pasos pre-producción
- Variables de entorno — lista completa de env vars
- Cronjobs y tareas programadas — catálogo completo de jobs, frecuencias y debugging
- Backend troubleshooting — problemas más amplios del backend

