
# ============================================================
# DEPLOYMENT COMPLETO — BOLSA DE TRABAJO PHP
# Skills: senior-security · senior-architect · senior-fullstack
# ============================================================


# ============================================================
# 1. ESTRUCTURA FINAL DE DIRECTORIOS EN SERVIDOR
# ============================================================

# /var/www/bolsa-trabajo/           ← raíz del proyecto (fuera del webroot)
#   ├── app/                        ← código PHP
#   ├── config/
#   ├── resources/views/
#   ├── storage/                    ← CVs y Excel (chmod 750, no accesible via web)
#   │   ├── cvs/
#   │   └── interviews/
#   ├── vendor/
#   ├── cron/
#   ├── public/                     ← ÚNICO directorio apuntado por Apache/Nginx
#   │   ├── index.php
#   │   ├── assets/
#   │   └── .htaccess
#   ├── .env
#   └── composer.json

# PERMISOS (senior-security: mínimo privilegio)
# chmod 755 /var/www/bolsa-trabajo
# chmod -R 644 /var/www/bolsa-trabajo/app
# chmod -R 755 /var/www/bolsa-trabajo/app  (dirs necesitan +x)
# chmod -R 750 /var/www/bolsa-trabajo/storage   ← solo www-data puede leer/escribir
# chmod 600 /var/www/bolsa-trabajo/.env          ← solo root lee el .env
# chown -R www-data:www-data /var/www/bolsa-trabajo


# ============================================================
# 2. APACHE — VirtualHost (public/.htaccess ya generado antes)
# ============================================================

# /etc/apache2/sites-available/bolsa-trabajo.conf

<VirtualHost *:80>
    ServerName empleos.tuempresa.com
    Redirect permanent / https://empleos.tuempresa.com/
</VirtualHost>

<VirtualHost *:443>
    ServerName empleos.tuempresa.com

    DocumentRoot /var/www/bolsa-trabajo/public

    SSLEngine on
    SSLCertificateFile    /etc/letsencrypt/live/empleos.tuempresa.com/fullchain.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/empleos.tuempresa.com/privkey.pem

    # senior-security: headers de seguridad HTTP
    Header always set X-Frame-Options "DENY"
    Header always set X-Content-Type-Options "nosniff"
    Header always set X-XSS-Protection "1; mode=block"
    Header always set Referrer-Policy "strict-origin-when-cross-origin"
    Header always set Permissions-Policy "geolocation=(), microphone=(), camera=()"
    Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
    Header always set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' https://fonts.googleapis.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data:; object-src 'none'"

    <Directory /var/www/bolsa-trabajo/public>
        Options -Indexes -FollowSymLinks
        AllowOverride All
        Require all granted

        # Ocultar información del servidor
        ServerTokens Prod
        ServerSignature Off
    </Directory>

    # Denegar acceso a todo lo que está fuera de /public
    <Directory /var/www/bolsa-trabajo>
        Require all denied
    </Directory>
    <Directory /var/www/bolsa-trabajo/public>
        Require all granted
    </Directory>

    # Logs
    ErrorLog  /var/log/apache2/bolsa-error.log
    CustomLog /var/log/apache2/bolsa-access.log combined

    # PHP-FPM (recomendado sobre mod_php)
    <FilesMatch \.php$>
        SetHandler "proxy:unix:/run/php/php8.2-fpm.sock|fcgi://localhost"
    </FilesMatch>
</VirtualHost>

# Activar: a2ensite bolsa-trabajo.conf && systemctl reload apache2


# ============================================================
# 3. NGINX (alternativa a Apache)
# ============================================================

# /etc/nginx/sites-available/bolsa-trabajo

server {
    listen 80;
    server_name empleos.tuempresa.com;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name empleos.tuempresa.com;

    root /var/www/bolsa-trabajo/public;
    index index.php;

    ssl_certificate     /etc/letsencrypt/live/empleos.tuempresa.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/empleos.tuempresa.com/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers off;

    # senior-security: headers de seguridad
    add_header X-Frame-Options "DENY" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' https://fonts.googleapis.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com https://fonts.gstatic.com; font-src https://fonts.gstatic.com; img-src 'self' data:; object-src 'none'" always;

    # No mostrar versión de Nginx
    server_tokens off;

    # No listar directorios
    autoindex off;

    # Rewrite: todo va a index.php
    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    # PHP-FPM
    location ~ \.php$ {
        fastcgi_pass unix:/run/php/php8.2-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        include fastcgi_params;
        fastcgi_hide_header X-Powered-By;
    }

    # Denegar acceso a archivos sensibles
    location ~ /\.(env|git|htaccess) {
        deny all;
    }
    location ~ ^/(app|config|resources|storage|vendor|cron|database)/ {
        deny all;
    }

    # Caché de assets estáticos
    location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg|woff2?)$ {
        expires 30d;
        add_header Cache-Control "public, immutable";
        access_log off;
    }

    # Límite de tamaño de upload (igual que en PHP)
    client_max_body_size 11M;

    access_log /var/log/nginx/bolsa-access.log;
    error_log  /var/log/nginx/bolsa-error.log;
}

# Activar: ln -s /etc/nginx/sites-available/bolsa-trabajo /etc/nginx/sites-enabled/
# Verificar: nginx -t && systemctl reload nginx


# ============================================================
# 4. PHP-FPM — Configuración (php.ini de producción)
# ============================================================

# /etc/php/8.2/fpm/conf.d/99-bolsa-trabajo.ini

[PHP]
; senior-security: ocultar versión de PHP
expose_php = Off

; Límites de upload (deben coincidir con .env UPLOAD_MAX_MB)
upload_max_filesize = 10M
post_max_size = 11M
max_file_uploads = 5

; Timeouts
max_execution_time = 60
max_input_time = 60
memory_limit = 128M

; senior-security: desactivar funciones peligrosas
disable_functions = exec,passthru,shell_exec,system,proc_open,popen,curl_exec,curl_multi_exec,parse_ini_file,show_source

; Logs de errores (nunca en pantalla en producción)
display_errors = Off
display_startup_errors = Off
log_errors = On
error_log = /var/log/php/bolsa-errors.log
error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT

; Sesiones seguras
session.cookie_httponly = 1
session.cookie_secure = 1
session.cookie_samesite = Lax
session.use_strict_mode = 1
session.use_only_cookies = 1
session.gc_maxlifetime = 3600

; Opcache (rendimiento)
opcache.enable = 1
opcache.memory_consumption = 128
opcache.interned_strings_buffer = 8
opcache.max_accelerated_files = 4000
opcache.validate_timestamps = 0  ; 0 en producción, 1 en desarrollo


# ============================================================
# 5. .env DE PRODUCCIÓN
# ============================================================

APP_NAME="TalentoHoy"
APP_URL=https://empleos.tuempresa.com
APP_ENV=production
APP_DEBUG=false

DB_HOST=127.0.0.1
DB_PORT=3306
DB_NAME=bolsa_trabajo
DB_USER=bolsa_app
DB_PASS=<password_seguro_generado_con_openssl>

SMTP_HOST=smtp.sendgrid.net
SMTP_PORT=587
SMTP_USER=apikey
SMTP_PASS=<sendgrid_api_key>
SMTP_FROM_NAME="Bolsa de Empleo"
SMTP_ENCRYPTION=tls

UPLOAD_MAX_MB=10
STORAGE_PATH=/var/www/bolsa-trabajo/storage

SESSION_LIFETIME=3600
SESSION_NAME=bolsa_sess

PUBLIC_RATE_LIMIT=3

# Generar password: openssl rand -base64 32


# ============================================================
# 6. MYSQL — Usuario con mínimo privilegio (senior-security)
# ============================================================

-- Ejecutar como root en MySQL:
CREATE DATABASE bolsa_trabajo CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

-- Usuario solo con permisos necesarios (sin DROP, sin CREATE, sin GRANT)
CREATE USER 'bolsa_app'@'127.0.0.1' IDENTIFIED BY '<password_seguro>';
GRANT SELECT, INSERT, UPDATE, DELETE, EXECUTE ON bolsa_trabajo.* TO 'bolsa_app'@'127.0.0.1';

-- Usuario de backup (solo lectura)
CREATE USER 'bolsa_backup'@'127.0.0.1' IDENTIFIED BY '<password_backup>';
GRANT SELECT, LOCK TABLES ON bolsa_trabajo.* TO 'bolsa_backup'@'127.0.0.1';

FLUSH PRIVILEGES;


# ============================================================
# 7. CRONTAB COMPLETO
# ============================================================

# Editar como www-data: crontab -u www-data -e

# Cerrar puestos vencidos — todos los días a las 2:00 AM
0 2 * * * /usr/bin/php /var/www/bolsa-trabajo/cron/close-expired-jobs.php >> /var/log/bolsa-cron.log 2>&1

# Procesar cola de emails — cada 5 minutos
*/5 * * * * /usr/bin/php /var/www/bolsa-trabajo/cron/process-email-queue.php >> /var/log/bolsa-email.log 2>&1

# Limpiar rate_limit_log viejo — cada hora
0 * * * * /usr/bin/php /var/www/bolsa-trabajo/cron/cleanup-rate-limit.php >> /var/log/bolsa-cron.log 2>&1

# Backup de base de datos — todos los días a las 3:00 AM
0 3 * * * mysqldump -u bolsa_backup -p<password_backup> bolsa_trabajo | gzip > /var/backups/bolsa-$(date +\%Y\%m\%d).sql.gz 2>&1

# Rotar logs de cron — el 1ro de cada mes
0 0 1 * * truncate -s 0 /var/log/bolsa-cron.log /var/log/bolsa-email.log


# ============================================================
# 8. cron/cleanup-rate-limit.php (nuevo, no estaba antes)
# ============================================================

<?php
require_once __DIR__ . '/../vendor/autoload.php';
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__ . '/../');
$dotenv->load();

use App\Core\Database;

$pdo     = Database::getInstance();
$deleted = $pdo->exec("DELETE FROM rate_limit_log WHERE hit_at < DATE_SUB(NOW(), INTERVAL 2 HOUR)");
echo "[" . date('Y-m-d H:i:s') . "] rate_limit_log: {$deleted} registros eliminados.\n";


# ============================================================
# 9. SCRIPT DE DEPLOYMENT (deploy.sh)
# senior-fullstack: workflow de deploy reproducible
# ============================================================

#!/bin/bash
# deploy.sh — ejecutar desde la raíz del proyecto
set -e  # abortar si cualquier comando falla

echo "🚀 Iniciando deployment..."

# 1. Pull de cambios
git pull origin main

# 2. Instalar dependencias de producción (sin dev)
composer install --no-dev --optimize-autoloader

# 3. Verificar que .env existe
if [ ! -f .env ]; then
    echo "❌ Falta el archivo .env"
    exit 1
fi

# 4. Ejecutar migraciones pendientes (si se agregan en el futuro)
# php artisan migrate --force

# 5. Permisos correctos
chmod -R 750 storage/
chmod 600 .env
chown -R www-data:www-data storage/ public/

# 6. Limpiar opcache
php -r "opcache_reset();" 2>/dev/null || true

# 7. Verificar que la app responde
HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" https://empleos.tuempresa.com)
if [ "$HTTP_STATUS" != "200" ]; then
    echo "❌ La app no responde correctamente (HTTP $HTTP_STATUS)"
    exit 1
fi

echo "✅ Deployment completado correctamente."
echo "   HTTP Status: $HTTP_STATUS"
echo "   Hora: $(date)"


# ============================================================
# 10. CHECKLIST DE PRODUCCIÓN
# senior-security: verificación antes de go-live
# ============================================================

# [ ] APP_DEBUG=false en .env
# [ ] SSL/TLS activo y redirige HTTP → HTTPS
# [ ] Headers de seguridad activos (verificar con securityheaders.com)
# [ ] .env con chmod 600 y fuera del webroot
# [ ] storage/ con chmod 750, no accesible via web
# [ ] Opcache activado
# [ ] display_errors=Off en php.ini
# [ ] disable_functions configurado
# [ ] Usuario DB con mínimo privilegio (sin DROP, CREATE, GRANT)
# [ ] Crontab configurado y funcionando
# [ ] Backup automático de DB configurado
# [ ] Logs de PHP rotados (logrotate)
# [ ] Certificado SSL renovación automática (certbot renew --dry-run)
# [ ] Rate limiting funcionando (test desde IP)
# [ ] CSRF funcionando (test con herramienta)
# [ ] Formularios sin XSS (test con <script>alert(1)</script>)
# [ ] SQL injection prevenido (test con ' OR '1'='1)
# [ ] Subida de archivos solo acepta MIME válidos
# [ ] CVs no accesibles por URL directa
# [ ] Admin no accesible sin autenticación
# [ ] Roles funcionando (recruiter no puede acceder a settings)
# [ ] Emails llegando correctamente (test con Mailtrap primero)
# [ ] Exportación Excel y PDF funcionando
# [ ] Cron close-expired-jobs ejecutándose (verificar en audit_log)


# ============================================================
# 11. LOGROTATE — rotación de logs de la app
# ============================================================

# /etc/logrotate.d/bolsa-trabajo

/var/log/bolsa-*.log
/var/log/php/bolsa-errors.log
{
    daily
    rotate 30
    compress
    delaycompress
    missingok
    notifempty
    sharedscripts
    postrotate
        systemctl reload php8.2-fpm > /dev/null 2>&1 || true
    endscript
}
