Apariencia
Infraestructura
Requisitos de servidor, base de datos, Redis, queue worker, scheduler y backups. Esta página es neutra al proveedor — funciona igual en VPS propio, Render, Railway, Laravel Forge o AWS.
Stack mínimo
| Componente | Versión | Memoria | Uso |
|---|---|---|---|
| PHP | 8.3+ | 512 MB | Backend Laravel |
| MySQL | 8.0+ | 1 GB | DB principal |
| Redis | 6.0+ | 256 MB | Cache + queue + sesiones |
| Node.js | 20+ | 256 MB | Build del frontend |
| Nginx | 1.22+ | 64 MB | Reverse proxy + static files |
Stack recomendado para MVP (hasta 1000 usuarios activos): 1 VPS de 2 vCPU + 4 GB RAM ($20–$40/mes en DigitalOcean, Linode, Hetzner).
Backend — servidor PHP
Instalación de PHP 8.3 (Ubuntu 24.04)
bash
sudo add-apt-repository ppa:ondrej/php -y
sudo apt update
sudo apt install -y \
php8.3 php8.3-fpm php8.3-cli \
php8.3-mysql php8.3-mbstring php8.3-xml \
php8.3-curl php8.3-zip php8.3-gd php8.3-bcmath \
php8.3-intl php8.3-redis php8.3-opcache
# Verificar
php -v # → PHP 8.3.xExtensiones requeridas
mbstring, intl, pdo_mysql, bcmath, zip, gd, curl, xml, opcache, redisComposer
bash
curl -sS https://getcomposer.org/installer | php
sudo mv composer.phar /usr/local/bin/composerDeploy del backend
bash
cd /var/www
sudo git clone https://github.com/tu-org/humae-backend humae_backend
cd humae_backend
# Composer
composer install --optimize-autoloader --no-dev
# Env
cp .env.example .env
# ... editar .env con los valores de producción ...
php artisan key:generate
# Migrar + seedear
php artisan migrate --force
php artisan db:seed --force
# Cachear config/rutas/views/events
php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan event:cache
# Permisos
sudo chown -R www-data:www-data storage bootstrap/cache
sudo chmod -R 775 storage bootstrap/cacheNginx para Laravel
nginx
server {
listen 443 ssl http2;
server_name api.humae.com.mx;
ssl_certificate /etc/letsencrypt/live/api.humae.com.mx/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/api.humae.com.mx/privkey.pem;
root /var/www/humae_backend/public;
index index.php;
client_max_body_size 12M; # 10MB docs + overhead
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
}
location ~ /\. { deny all; }
access_log /var/log/nginx/humae_api_access.log;
error_log /var/log/nginx/humae_api_error.log;
}PHP-FPM pool tuning
/etc/php/8.3/fpm/pool.d/www.conf:
ini
pm = dynamic
pm.max_children = 10 ; ~50 MB por child → 500 MB max
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 4
pm.max_requests = 500 ; reciclar workers para evitar leaksOPcache (obligatorio en prod)
/etc/php/8.3/fpm/conf.d/10-opcache.ini:
ini
opcache.enable=1
opcache.memory_consumption=256
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=20000
opcache.validate_timestamps=0 ; en prod: 0. En dev: 1.
opcache.revalidate_freq=0
opcache.fast_shutdown=1opcache.validate_timestamps=0 significa que tras cada deploy debes ejecutar sudo service php8.3-fpm reload para que OPcache lea el código nuevo.
Base de datos
MySQL 8
Instalar:
bash
sudo apt install mysql-server-8.0
sudo mysql_secure_installationCrear DB y usuario:
sql
CREATE DATABASE humae_production
CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
CREATE USER 'humae_app'@'localhost' IDENTIFIED BY 'password_fuerte_y_largo';
GRANT ALL PRIVILEGES ON humae_production.* TO 'humae_app'@'localhost';
FLUSH PRIVILEGES;Config mínima /etc/mysql/mysql.conf.d/mysqld.cnf
ini
[mysqld]
# Conexiones
max_connections = 150
max_allowed_packet = 64M
# InnoDB
innodb_buffer_pool_size = 1G # ~50% de la RAM del servidor
innodb_log_file_size = 256M
innodb_flush_log_at_trx_commit = 1
# Slow query log (útil para debugging)
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 1
# Character set
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ciOpciones managed (sin servidor propio)
- DigitalOcean Managed MySQL — $15/mes para 1 GB
- AWS RDS MySQL — desde $13/mes (db.t4g.micro)
- PlanetScale — free tier 5 GB (usa branches de DB, requiere planet-scale CLI)
Si usas managed, asegúrate de:
- Configurar allowlist de IP del servidor backend
- Activar SSL/TLS para la conexión
- Habilitar backups automáticos
Backups
bash
# Crear script /usr/local/bin/humae-db-backup.sh
#!/bin/bash
DATE=$(date +%F)
DUMP_FILE=/tmp/humae_$DATE.sql.gz
mysqldump -u humae_app -p"$DB_PASS" humae_production | gzip > $DUMP_FILE
aws s3 cp $DUMP_FILE s3://humae-backups/db/
rm $DUMP_FILEbash
# Crontab del root: daily 3am UTC
0 3 * * * /usr/local/bin/humae-db-backup.shRetención: 30 días en S3 con lifecycle rule, 7 días locales.
Redis
Self-hosted
bash
sudo apt install redis-server/etc/redis/redis.conf:
conf
bind 127.0.0.1 ::1 # solo localhost si el backend está en el mismo server
requirepass <password>
maxmemory 256mb
maxmemory-policy allkeys-lru
save 900 1 # persist snapshot cada 15 min
save 300 10
save 60 10000bash
sudo systemctl enable redis-server
sudo systemctl start redis-serverOpciones managed
- Upstash — free tier generoso (10k commands/día serverless)
- Redis Cloud — free 30 MB
Verificación
bash
redis-cli -h localhost -a $REDIS_PASSWORD ping
# → PONGAlmacenamiento local
Los archivos subidos por los usuarios (avatares + documentos) se guardan en humae_backend/storage/app. No se usa ningún blob storage externo.
Dimensionamiento
- ~2.5 MB por candidato activo (avatar WebP + 5 documentos PDF promedio).
- Provisión inicial ~50 GB en el volumen que hospeda
storage/app. - Alertar al 70 % de uso para escalar el volumen antes de llegar a 90 %.
Volumen persistente
Si se usa Docker / Kubernetes, montar storage/app como volumen persistente (no en el filesystem efímero del container).
En VPS tradicional basta con que storage/ esté en el disco principal y el disco tenga espacio.
Permisos
bash
sudo chown -R www-data:www-data /var/www/humae_backend/storage
sudo chmod -R 775 /var/www/humae_backend/storage
cd /var/www/humae_backend && sudo -u www-data php artisan storage:linkBackup offsite con restic
bash
sudo apt install -y restic
restic -r s3:s3.amazonaws.com/humae-storage-backup init
# Script /usr/local/bin/humae-storage-backup.sh
#!/bin/bash
export RESTIC_PASSWORD_FILE=/root/.restic-pass
export AWS_ACCESS_KEY_ID=...
export AWS_SECRET_ACCESS_KEY=...
restic -r s3:s3.amazonaws.com/humae-storage-backup backup /var/www/humae_backend/storage/app
restic -r s3:s3.amazonaws.com/humae-storage-backup forget --keep-daily 14 --keep-weekly 12 --keep-monthly 12 --prune# Cron: diario 4am UTC
0 4 * * * /usr/local/bin/humae-storage-backup.shRestauración de emergencia
bash
restic -r s3:s3.amazonaws.com/humae-storage-backup snapshots
restic -r s3:s3.amazonaws.com/humae-storage-backup restore <snapshot-id> --target /var/www/humae_backend
sudo chown -R www-data:www-data /var/www/humae_backend/storageVer contrato de storage en Storage local.
SMTP (Postfix)
Los correos transaccionales se envían desde un Postfix corriendo en el mismo servidor del backend. Laravel habla SMTP a 127.0.0.1:25; Postfix entrega saliente por puerto 25.
Instalación
bash
sudo apt install -y postfix opendkim opendkim-tools mailutils
# Setup: "Internet Site", mail name = mail.humae.com.mx/etc/postfix/main.cf mínimo
conf
myhostname = mail.humae.com.mx
myorigin = humae.com.mx
mydestination = $myhostname, localhost.$mydomain, localhost
inet_interfaces = loopback-only
mynetworks = 127.0.0.0/8 [::1]/128
smtp_tls_security_level = may
smtp_tls_CApath = /etc/ssl/certs
maximal_queue_lifetime = 1d
bounce_queue_lifetime = 1d
# Integración con OpenDKIM
milter_default_action = accept
smtpd_milters = local:/run/opendkim/opendkim.sock
non_smtpd_milters = local:/run/opendkim/opendkim.sockbash
sudo systemctl enable --now postfix opendkimDKIM con OpenDKIM
bash
sudo mkdir -p /etc/opendkim/keys/humae.com.mx
sudo opendkim-genkey -s default -d humae.com.mx -D /etc/opendkim/keys/humae.com.mx
sudo chown -R opendkim:opendkim /etc/opendkim/keys
cat /etc/opendkim/keys/humae.com.mx/default.txt
# → copiar el TXT a DNS: default._domainkey.humae.com.mxVerificación de deliverability
bash
# Que Postfix esté escuchando
sudo ss -lntp | grep :25
# Mandar un correo de prueba con Laravel
cd /var/www/humae_backend
php artisan tinker --execute="\Mail::raw('smoke',fn(\$m)=>\$m->to('tu@correo.com')->subject('smoke'));"
# Seguir el log
sudo tail -f /var/log/mail.logEl receptor debe ver dkim=pass y spf=pass en los headers.
Puerto 25 saliente
Muchos providers bloquean 25 outbound por default. Abrir ticket al provider antes de deploy, o elegir uno que no bloquee.
Monitoreo
bash
mailq # cola pendiente de Postfix (debe estar casi vacía)
grep 'status=bounced' /var/log/mail.log | tail # bounces recientesAlertas recomendadas: mailq > 50, /var/log/mail.log con ráfagas de status=deferred, reputation check periódico en mxtoolbox.com.
Ver detalles y DNS completo en SMTP local.
Queue y scheduler
Queue worker con Supervisor
Instalar:
bash
sudo apt install supervisor/etc/supervisor/conf.d/humae-queue.conf:
ini
[program:humae-queue]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/humae_backend/artisan queue:work redis --queue=default --tries=3 --max-time=3600 --timeout=90
autostart=true
autorestart=true
user=www-data
numprocs=2
redirect_stderr=true
stdout_logfile=/var/log/supervisor/humae-queue.log
stopwaitsecs=3600Activar:
bash
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start humae-queue:*Verificar:
bash
sudo supervisorctl status
# humae-queue:humae-queue_00 RUNNING pid 12345, uptime 0:05:12
# humae-queue:humae-queue_01 RUNNING pid 12346, uptime 0:05:12Scheduler con cron
sudo crontab -e -u www-data:
* * * * * cd /var/www/humae_backend && php artisan schedule:run >> /dev/null 2>&1Esto ejecuta los jobs programados. Para el catálogo completo de qué se ejecuta y cuándo, ver Cronjobs y tareas programadas.
Verificar:
bash
php artisan schedule:listFailed jobs
Laravel guarda jobs fallidos en la tabla failed_jobs. Revisar con:
bash
php artisan queue:failedReintentar:
bash
php artisan queue:retry allAdmin puede verlos desde /admin/jobs-fallidos (Fase 2).
Frontend — Next.js
Opción A — Vercel (recomendada)
- Conectar repo GitHub a Vercel.
- Framework preset: Next.js (auto-detect).
- Root directory:
humae_frontend. - Build command:
npm run build(default). - Output directory:
.next(default). - Env vars: agregar
NEXT_PUBLIC_API_URLetc. - Domain: conectar
humae.com.mx.
Deploy automático en cada push a main.
Opción B — Cloudflare Pages
Similar a Vercel. Build command: npm run build. Framework preset: Next.js.
Opción C — Self-hosted con PM2
En el mismo VPS (o uno dedicado al frontend):
bash
cd /var/www
git clone https://github.com/tu-org/humae-frontend humae_frontend
cd humae_frontend
npm install
npm run build
sudo npm install -g pm2
pm2 start npm --name humae-frontend -- start
pm2 save
pm2 startup # activar al bootNginx reverse proxy:
nginx
server {
listen 443 ssl http2;
server_name humae.com.mx www.humae.com.mx;
ssl_certificate /etc/letsencrypt/live/humae.com.mx/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/humae.com.mx/privkey.pem;
location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}Docs — este manual
Opción A — Cloudflare Pages
Framework preset: VitePress
Build command: npm run build
Output directory: .vitepress/dist
Root directory: humae_docsOpción B — Static file serving
bash
cd humae_docs && npm run build
# Contenido en .vitepress/dist/ se puede subir a cualquier static hostDeploy con Docker (alternativa)
HUMAE incluye docker-compose.yml listo para producción:
bash
cd "HUMAE NEW"
docker compose up -dServicios incluidos: mysql, redis, mailhog, backend, frontend.
Para prod real recomendamos agregar watchtower o similar para auto-update, y exponer solo los puertos necesarios detrás del reverse proxy.
Monitoreo operacional
Logs
bash
# Backend (Laravel)
tail -f /var/www/humae_backend/storage/logs/laravel-$(date +%F).log
# Nginx
tail -f /var/log/nginx/humae_api_error.log
# Queue worker
sudo supervisorctl tail -f humae-queue:humae-queue_00
# MySQL slow log
tail -f /var/log/mysql/slow.logRecursos del servidor
bash
# CPU + RAM
htop
# Disco
df -h
# Conexiones
ss -sHealth externo
Curl desde otra máquina:
bash
curl -i https://api.humae.com.mx/api/v1/healthDebe devolver 200 OK con {"success":true,"data":{"app":"ok",...}}.
Siguiente
Verificación end-to-end post-deploy: Verificación post-deploy →

