🎓 Initial commit: Math2 Platform - Plataforma de Álgebra Lineal PRO
Some checks failed
Test Suite / test-backend (push) Has been cancelled
Test Suite / test-frontend (push) Has been cancelled
Test Suite / e2e-tests (push) Has been cancelled
Test Suite / coverage-check (push) Has been cancelled

 Características:
- 45 ejercicios universitarios (Basic → Advanced)
- Renderizado LaTeX profesional
- IA generativa (Z.ai/DashScope)
- Docker 9 servicios
- Tests 123/123 pasando
- Seguridad enterprise (JWT, XSS, Rate limiting)

🐳 Infraestructura:
- Next.js 14 + Node.js 20
- PostgreSQL 15 + Redis 7
- Docker Compose completo
- Nginx + SSL ready

📚 Documentación:
- 5 informes técnicos completos
- README profesional
- Scripts de deployment automatizados

Estado: Producción lista 
This commit is contained in:
Renato
2026-03-31 11:27:11 -03:00
commit bc43c9e772
309 changed files with 84845 additions and 0 deletions

238
scripts/clean.sh Executable file
View File

@@ -0,0 +1,238 @@
#!/bin/bash
#
# Script de limpieza de artefactos generados
# Uso: ./scripts/clean.sh [-a|--all] [-h|--help]
#
# Elimina:
# - Build outputs (.next/, dist/, build/)
# - Dependencias (node_modules/)
# - Logs, coverage, archivos temporales
#
set -euo pipefail
# Colores
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Flags
ALL=false
DRY_RUN=false
# Función para mostrar ayuda
show_help() {
cat << EOF
Uso: ./scripts/clean.sh [OPCIONES]
Limpia artefactos generados del proyecto.
OPCIONES:
-a, --all Limpia TODO incluyendo node_modules
-d, --dry-run Muestra qué se eliminaría sin eliminarlo
-h, --help Muestra esta ayuda
Ejemplos:
./scripts/clean.sh # Limpia builds, logs, coverage
./scripts/clean.sh -a # Limpia TODO incluyendo node_modules
./scripts/clean.sh -d # Muestra qué se eliminaría
EOF
}
# Parsear argumentos
while [[ $# -gt 0 ]]; do
case $1 in
-a|--all)
ALL=true
shift
;;
-d|--dry-run)
DRY_RUN=true
shift
;;
-h|--help)
show_help
exit 0
;;
*)
echo -e "${RED}Error: Opción desconocida: $1${NC}"
echo "Usa -h o --help para ver opciones disponibles"
exit 1
;;
esac
done
# Función para eliminar directorios
remove_dir() {
local path=$1
if [[ -d "$path" ]]; then
if [[ "$DRY_RUN" == true ]]; then
echo -e "${YELLOW}[DRY RUN] Se eliminaría:${NC} $path"
return 0
fi
# Calcular tamaño
local size
size=$(du -sh "$path" 2>/dev/null | cut -f1) || size="?"
rm -rf "$path"
echo -e "${GREEN}${NC} Eliminado: $path (${size})"
return 0
fi
return 1
}
# Función para eliminar archivos
remove_file() {
local pattern=$1
if [[ "$DRY_RUN" == true ]]; then
find . -name "$pattern" -type f 2>/dev/null | while read -r file; do
echo -e "${YELLOW}[DRY RUN] Se eliminaría:${NC} $file"
done
return 0
fi
local count=0
while IFS= read -r -d '' file; do
rm -f "$file"
((count++))
done < <(find . -name "$pattern" -type f -print0 2>/dev/null)
if [[ $count -gt 0 ]]; then
echo -e "${GREEN}${NC} Eliminados $count archivos: $pattern"
fi
}
# Header
echo -e "${BLUE}========================================${NC}"
echo -e "${BLUE} Limpieza de Artefactos Generados ${NC}"
echo -e "${BLUE}========================================${NC}"
echo ""
if [[ "$DRY_RUN" == true ]]; then
echo -e "${YELLOW}MODO DRY RUN - No se eliminará nada${NC}"
echo ""
fi
# Contador de eliminados
declare -i TOTAL_SIZE=0
# ============================================
# FRONTEND - Build outputs
# ============================================
echo -e "${BLUE}Frontend:${NC}"
remove_dir "frontend/.next"
remove_dir "frontend/out"
remove_dir "frontend/build"
remove_dir "frontend/dist"
# ============================================
# BACKEND - Build outputs
# ============================================
echo ""
echo -e "${BLUE}Backend:${NC}"
remove_dir "backend/dist"
remove_dir "backend/build"
remove_dir "backend/coverage"
remove_dir "backend/.nyc_output"
# ============================================
# ROOT - Build outputs
# ============================================
echo ""
echo -e "${BLUE}Root:${NC}"
remove_dir "coverage"
remove_dir ".nyc_output"
remove_dir ".cache"
remove_dir ".temp"
remove_dir ".tmp"
# ============================================
# LOGS
# ============================================
echo ""
echo -e "${BLUE}Logs:${NC}"
remove_file "*.log"
remove_file "npm-debug.log*"
remove_file "yarn-debug.log*"
remove_file "yarn-error.log*"
# ============================================
# COVERAGE REPORTS
# ============================================
echo ""
echo -e "${BLUE}Coverage:${NC}"
remove_dir "coverage"
remove_file "*.lcov"
# ============================================
# NODE_MODULES (solo si --all)
# ============================================
if [[ "$ALL" == true ]]; then
echo ""
echo -e "${BLUE}Dependencias (node_modules):${NC}"
remove_dir "frontend/node_modules"
remove_dir "backend/node_modules"
remove_dir "node_modules"
echo ""
echo -e "${YELLOW}Para reinstalar dependencias:${NC}"
echo " npm install"
echo " cd frontend && npm install"
echo " cd ../backend && npm install"
fi
# ============================================
# DOCKER DATA (opcional)
# ============================================
echo ""
echo -e "${BLUE}Docker data (si existen):${NC}"
remove_dir "docker-data"
remove_dir "postgres_data"
remove_dir "redis_data"
# ============================================
# OTROS ARTEFACTOS
# ============================================
echo ""
echo -e "${BLUE}Otros artefactos:${NC}"
remove_file ".DS_Store"
remove_file "Thumbs.db"
remove_file "*.swp"
remove_file "*.swo"
remove_file "*~"
remove_dir "uploads" # Excepto .gitkeep
# Preservar .gitkeep en uploads
if [[ -d "uploads" && "$DRY_RUN" == false ]]; then
mkdir -p uploads/pdfs
touch uploads/pdfs/.gitkeep
echo -e "${GREEN}${NC} Preservado uploads/pdfs/.gitkeep"
fi
# ============================================
# RESUMEN
# ============================================
echo ""
echo -e "${BLUE}========================================${NC}"
echo -e "${GREEN}Limpieza completada!${NC}"
echo -e "${BLUE}========================================${NC}"
if [[ "$DRY_RUN" == true ]]; then
echo ""
echo -e "${YELLOW}Nota:${NC} Esto fue un DRY RUN. Nada fue eliminado."
echo "Ejecuta sin --dry-run para limpiar realmente."
fi
if [[ "$ALL" == true ]]; then
echo ""
echo -e "${YELLOW}Nota:${NC} Se eliminaron node_modules."
echo "Ejecuta 'npm install' para reinstalar dependencias."
fi
echo ""
echo -e "${BLUE}Para reinstalar dependencias:${NC}"
echo " npm install"
echo " cd frontend && npm install && cd ../backend && npm install"

359
scripts/deploy.sh Executable file
View File

@@ -0,0 +1,359 @@
#!/bin/bash
# ========================================
# MATH PLATFORM - PRODUCTION DEPLOYMENT
# Enterprise Grade Deployment Script
# ========================================
set -euo pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Configuration
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
BACKUP_DIR="$PROJECT_DIR/backups"
LOG_DIR="$PROJECT_DIR/logs"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
# Logging functions
log_info() {
echo -e "${BLUE}[INFO]${NC} $1"
}
log_success() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
}
log_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# Error handler
error_handler() {
log_error "Deployment failed at line $1"
log_info "Rolling back to previous state..."
rollback_deployment
exit 1
}
trap 'error_handler $LINENO' ERR
# ========================================
# PRE-DEPLOYMENT CHECKS
# ========================================
check_prerequisites() {
log_info "Checking prerequisites..."
# Check if running as root (should NOT)
if [[ $EUID -eq 0 ]]; then
log_error "Do not run this script as root"
exit 1
fi
# Check required commands
local required_commands=("docker" "docker-compose" "curl" "wget")
for cmd in "${required_commands[@]}"; do
if ! command -v "$cmd" &> /dev/null; then
log_error "$cmd is required but not installed"
exit 1
fi
done
# Check Docker is running
if ! docker info &> /dev/null; then
log_error "Docker daemon is not running"
exit 1
fi
log_success "Prerequisites check passed"
}
check_environment() {
log_info "Checking environment variables..."
local required_vars=(
"DATABASE_URL"
"REDIS_PASSWORD"
"JWT_SECRET"
)
local missing_vars=()
for var in "${required_vars[@]}"; do
if [[ -z "${!var:-}" ]]; then
missing_vars+=("$var")
fi
done
if [[ ${#missing_vars[@]} -gt 0 ]]; then
log_error "Missing required environment variables:"
printf '%s\n' "${missing_vars[@]}"
exit 1
fi
log_success "Environment variables check passed"
}
# ========================================
# DATABASE OPERATIONS
# ========================================
create_backup() {
log_info "Creating database backup..."
mkdir -p "$BACKUP_DIR"
local backup_file="$BACKUP_DIR/backup_$TIMESTAMP.sql"
# Extract database info from DATABASE_URL
local db_container="math-postgres-prod"
if docker ps | grep -q "$db_container"; then
if docker exec "$db_container" pg_dump -U mathuser mathdb > "$backup_file"; then
log_success "Database backup created: $backup_file"
# Compress backup
gzip "$backup_file"
log_success "Backup compressed: ${backup_file}.gz"
# Keep only last 7 backups
ls -t "$BACKUP_DIR"/backup_*.sql.gz | tail -n +8 | xargs -r rm
else
log_warning "Failed to create database backup, continuing anyway..."
fi
else
log_warning "Database container not running, skipping backup"
fi
}
run_migrations() {
log_info "Running database migrations..."
cd "$PROJECT_DIR"
# Create temporary migration container
docker-compose -f docker-compose.prod.yml run --rm backend npx prisma migrate deploy
log_success "Database migrations completed"
}
# ========================================
# DEPLOYMENT OPERATIONS
# ========================================
build_images() {
log_info "Building Docker images..."
cd "$PROJECT_DIR"
# Export version for builds
export VERSION=${VERSION:-1.0.0}
# Build all images
docker-compose -f docker-compose.prod.yml build --no-cache
log_success "Docker images built successfully"
}
zero_downtime_deploy() {
log_info "Starting zero-downtime deployment..."
cd "$PROJECT_DIR"
# Scale up new instances
log_info "Scaling up backend instances..."
docker-compose -f docker-compose.prod.yml up -d --no-deps --scale backend=3 backend
# Wait for health checks
log_info "Waiting for health checks (30s)..."
sleep 30
# Check health
if ! check_service_health "backend"; then
log_error "Backend health check failed after scaling"
return 1
fi
# Scale down to normal
log_info "Scaling down to normal capacity..."
docker-compose -f docker-compose.prod.yml up -d --no-deps --scale backend=2 backend
log_success "Zero-downtime deployment completed"
}
deploy_services() {
log_info "Deploying services..."
cd "$PROJECT_DIR"
# Pull latest images if using registry
if [[ "${PULL_IMAGES:-false}" == "true" ]]; then
log_info "Pulling latest images from registry..."
docker-compose -f docker-compose.prod.yml pull
fi
# Deploy all services
docker-compose -f docker-compose.prod.yml up -d --remove-orphans
log_success "Services deployed successfully"
}
# ========================================
# HEALTH CHECKS
# ========================================
check_service_health() {
local service=$1
local max_attempts=${2:-10}
local attempt=1
log_info "Checking health for service: $service"
while [[ $attempt -le $max_attempts ]]; do
if docker-compose -f docker-compose.prod.yml ps "$service" | grep -q "healthy"; then
log_success "$service is healthy"
return 0
fi
log_info "Health check attempt $attempt/$max_attempts..."
sleep 5
((attempt++))
done
log_error "$service failed health check after $max_attempts attempts"
return 1
}
run_health_checks() {
log_info "Running comprehensive health checks..."
local services=("postgres" "redis" "backend" "frontend" "nginx")
local failed_services=()
for service in "${services[@]}"; do
if ! check_service_health "$service"; then
failed_services+=("$service")
fi
done
if [[ ${#failed_services[@]} -gt 0 ]]; then
log_error "Health checks failed for services:"
printf '%s\n' "${failed_services[@]}"
return 1
fi
# Check main health endpoint
local health_url="http://localhost/health"
if curl -sf "$health_url" &> /dev/null; then
log_success "Main health endpoint is responding"
else
log_warning "Main health endpoint not responding (might need SSL)"
fi
log_success "All health checks passed"
}
# ========================================
# ROLLBACK
# ========================================
rollback_deployment() {
log_warning "Initiating rollback..."
cd "$PROJECT_DIR"
# Get last successful version (if available)
local last_version=${LAST_VERSION:-"latest"}
# Revert to previous images
export VERSION="$last_version"
docker-compose -f docker-compose.prod.yml up -d
log_warning "Rollback completed"
}
# ========================================
# CLEANUP
# ========================================
cleanup() {
log_info "Running cleanup..."
# Remove old images
docker image prune -af --filter "until=24h" || true
# Remove unused volumes
docker volume prune -f || true
# Clean old logs
find "$LOG_DIR" -name "*.log" -type f -mtime +7 -delete 2>/dev/null || true
log_success "Cleanup completed"
}
# ========================================
# MAIN
# ========================================
main() {
log_info "🚀 Starting Math Platform Production Deployment"
log_info "Timestamp: $(date)"
log_info "Version: ${VERSION:-1.0.0}"
# Pre-deployment
check_prerequisites
check_environment
# Backup
create_backup
# Database
run_migrations
# Build and deploy
build_images
deploy_services
# Zero-downtime for backend
zero_downtime_deploy
# Verification
run_health_checks
# Cleanup
cleanup
log_success "✅ Deployment completed successfully!"
log_info "Services are running at:"
log_info " - Frontend: https://localhost"
log_info " - API: https://localhost/api"
log_info " - Grafana: http://localhost:3001 (if monitoring enabled)"
}
# Parse command line arguments
case "${1:-deploy}" in
"backup")
create_backup
;;
"migrate")
check_environment
run_migrations
;;
"rollback")
rollback_deployment
;;
"health")
run_health_checks
;;
"deploy"|*)
main
;;
esac

172
scripts/health-check.sh Executable file
View File

@@ -0,0 +1,172 @@
#!/bin/bash
# ================================================
# Math Platform - Health Check Script
# ================================================
set -e
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
echo -e "${BLUE}============================================${NC}"
echo -e "${BLUE}Math Platform - Health Check${NC}"
echo -e "${BLUE}============================================${NC}"
# Function to check service health
check_service() {
local service_name=$1
local url=$2
local expected_code=${3:-200}
echo -n "Checking $service_name... "
local response=$(curl -s -o /dev/null -w "%{http_code}" "$url" 2>/dev/null || echo "000")
if [ "$response" = "$expected_code" ]; then
echo -e "${GREEN}OK${NC} (HTTP $response)"
return 0
else
echo -e "${RED}FAILED${NC} (HTTP $response, expected $expected_code)"
return 1
fi
}
# Function to check Docker container
check_container() {
local container_name=$1
echo -n "Checking container $container_name... "
if docker ps | grep -q "$container_name"; then
local status=$(docker inspect --format='{{.State.Health.Status}}' "$container_name" 2>/dev/null || echo "no-health-check")
if [ "$status" = "healthy" ] || [ "$status" = "no-health-check" ]; then
echo -e "${GREEN}RUNNING${NC} ($status)"
return 0
else
echo -e "${YELLOW}UNHEALTHY${NC} ($status)"
return 1
fi
else
echo -e "${RED}NOT RUNNING${NC}"
return 1
fi
}
# Function to check disk space
check_disk_space() {
echo -n "Checking disk space... "
local usage=$(df -h / | awk 'NR==2 {print $5}' | sed 's/%//')
if [ "$usage" -lt 80 ]; then
echo -e "${GREEN}OK${NC} (${usage}% used)"
return 0
elif [ "$usage" -lt 90 ]; then
echo -e "${YELLOW}WARNING${NC} (${usage}% used)"
return 1
else
echo -e "${RED}CRITICAL${NC} (${usage}% used)"
return 1
fi
}
# Function to check memory usage
check_memory() {
echo -n "Checking memory usage... "
local mem_usage=$(free | awk 'NR==2{printf "%.0f", $3*100/$2 }')
if [ "$mem_usage" -lt 80 ]; then
echo -e "${GREEN}OK${NC} (${mem_usage}% used)"
return 0
elif [ "$mem_usage" -lt 90 ]; then
echo -e "${YELLOW}WARNING${NC} (${mem_usage}% used)"
return 1
else
echo -e "${RED}CRITICAL${NC} (${mem_usage}% used)"
return 1
fi
}
# Function to check Docker resources
check_docker_resources() {
echo -n "Checking Docker resources... "
local available=$(docker info 2>/dev/null | grep "CPUs" | head -1 | awk '{print $2}')
if [ -n "$available" ]; then
echo -e "${GREEN}OK${NC} ($available CPUs available)"
return 0
else
echo -e "${RED}FAILED${NC} (Docker not available)"
return 1
fi
}
# Main health check
main() {
local failed=0
echo -e "\n${BLUE}Container Status${NC}"
echo "----------------------------------------"
check_container "math-postgres" || failed=$((failed + 1))
check_container "math-redis" || failed=$((failed + 1))
check_container "math-backend" || failed=$((failed + 1))
check_container "math-frontend" || failed=$((failed + 1))
check_container "math-nginx" || failed=$((failed + 1))
echo -e "\n${BLUE}Service Health${NC}"
echo "----------------------------------------"
check_service "Backend API" "http://localhost:3001/health" "200" || failed=$((failed + 1))
check_service "Frontend" "http://localhost:3000" "200" || failed=$((failed + 1))
check_service "Nginx" "http://localhost/" "200" || failed=$((failed + 1))
echo -e "\n${BLUE}System Resources${NC}"
echo "----------------------------------------"
check_disk_space || failed=$((failed + 1))
check_memory || failed=$((failed + 1))
check_docker_resources || failed=$((failed + 1))
echo -e "\n${BLUE}Database Connection${NC}"
echo "----------------------------------------"
echo -n "Checking PostgreSQL connection... "
if docker exec math-postgres pg_isready -U mathuser -d mathdb &>/dev/null; then
echo -e "${GREEN}OK${NC}"
else
echo -e "${RED}FAILED${NC}"
failed=$((failed + 1))
fi
echo -n "Checking Redis connection... "
if docker exec math-redis redis-cli -a "${REDIS_PASSWORD:-redis_secure_password_2024}" ping &>/dev/null; then
echo -e "${GREEN}OK${NC}"
else
echo -e "${RED}FAILED${NC}"
failed=$((failed + 1))
fi
# Summary
echo -e "\n${BLUE}============================================${NC}"
echo -e "${BLUE}Summary${NC}"
echo -e "${BLUE}============================================${NC}"
if [ $failed -eq 0 ]; then
echo -e "${GREEN}All checks passed!${NC}\n"
exit 0
else
echo -e "${RED}$failed check(s) failed!${NC}\n"
exit 1
fi
}
# Run main function
main

113
scripts/monitor.sh Executable file
View File

@@ -0,0 +1,113 @@
#!/bin/bash
# ================================================
# Math Platform - Monitoring Script
# ================================================
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
NC='\033[0m' # No Color
clear
while true; do
clear
echo -e "${CYAN}╔═══════════════════════════════════════════════════════╗${NC}"
echo -e "${CYAN}║ Math Platform - Real-time Monitor ║${NC}"
echo -e "${CYAN}$(date '+%Y-%m-%d %H:%M:%S')${NC}"
echo -e "${CYAN}╚═══════════════════════════════════════════════════════╝${NC}"
# System Resources
echo -e "\n${BLUE}System Resources${NC}"
echo "----------------------------------------"
CPU_USAGE=$(top -bn1 | grep "Cpu(s)" | awk '{print $2}' | cut -d'%' -f1)
MEM_USAGE=$(free | awk 'NR==2{printf "%.1f%%", $3*100/$2}')
DISK_USAGE=$(df -h / | awk 'NR==2 {print $5}')
echo -e "CPU: ${CPU_USAGE}%"
echo -e "Memory: ${MEM_USAGE}"
echo -e "Disk: ${DISK_USAGE}"
# Docker Containers
echo -e "\n${BLUE}Container Status${NC}"
echo "----------------------------------------"
containers=("math-postgres" "math-redis" "math-backend" "math-frontend" "math-nginx" "math-pdf-worker" "math-exercise-worker" "math-notification-worker")
for container in "${containers[@]}"; do
if docker ps | grep -q "$container"; then
status=$(docker inspect --format='{{.State.Status}}' "$container" 2>/dev/null)
health=$(docker inspect --format='{{.State.Health.Status}}' "$container" 2>/dev/null || echo "no-check")
if [ "$health" = "healthy" ] || [ "$health" = "no-check" ]; then
echo -e "${GREEN}${NC} $container (${status})"
else
echo -e "${YELLOW}${NC} $container (${health})"
fi
else
echo -e "${RED}${NC} $container (not running)"
fi
done
# Resource Usage per Container
echo -e "\n${BLUE}Container Resource Usage${NC}"
echo "----------------------------------------"
echo -e "Container\tCPU\tMemory\tNet I/O"
echo -e "---------\t---\t------\t-------"
docker stats --no-stream --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.NetIO}}" 2>/dev/null | grep math- | head -7
# Service Health
echo -e "\n${BLUE}Service Health${NC}"
echo "----------------------------------------"
# Backend
if curl -s http://localhost:3001/health > /dev/null 2>&1; then
echo -e "${GREEN}${NC} Backend API (http://localhost:3001)"
else
echo -e "${RED}${NC} Backend API (http://localhost:3001)"
fi
# Frontend
if curl -s http://localhost:3000 > /dev/null 2>&1; then
echo -e "${GREEN}${NC} Frontend (http://localhost:3000)"
else
echo -e "${RED}${NC} Frontend (http://localhost:3000)"
fi
# Nginx
if curl -s http://localhost:80 > /dev/null 2>&1; then
echo -e "${GREEN}${NC} Nginx (http://localhost:80)"
else
echo -e "${RED}${NC} Nginx (http://localhost:80)"
fi
# Recent Logs (last 5 lines)
echo -e "\n${BLUE}Recent Backend Logs${NC}"
echo "----------------------------------------"
docker logs --tail=5 math-backend 2>&1 | grep -v "^$" || echo "No logs available"
# Database Stats
echo -e "\n${BLUE}Database Stats${NC}"
echo "----------------------------------------"
DB_SIZE=$(docker exec math-postgres psql -U mathuser -d mathdb -t -c "SELECT pg_size_pretty(pg_database_size('mathdb'));" 2>/dev/null | xargs || echo "N/A")
DB_CONNS=$(docker exec math-postgres psql -U mathuser -d mathdb -t -c "SELECT count(*) FROM pg_stat_activity;" 2>/dev/null | xargs || echo "N/A")
echo -e "Database Size: ${DB_SIZE}"
echo -e "Active Connections: ${DB_CONNS}"
# Queue Stats
echo -e "\n${BLUE}Queue Stats (Redis)${NC}"
echo "----------------------------------------"
QUEUE_INFO=$(docker exec math-redis redis-cli -a "${REDIS_PASSWORD:-redis_secure_password_2024}" INFO stats 2>/dev/null | grep -E "total_connections_received|total_commands_processed" | cut -d: -f2 | xargs || echo "N/A")
echo -e "Connections: $(echo "$QUEUE_INFO" | head -1)"
echo -e "Commands: $(echo "$QUEUE_INFO" | tail -1)"
# Instructions
echo -e "\n${YELLOW}Press Ctrl+C to exit${NC}"
echo -e "${YELLOW}Refreshing in 5 seconds...${NC}"
sleep 5
done

106
scripts/setup-secrets.sh Executable file
View File

@@ -0,0 +1,106 @@
#!/bin/bash
# ==================================================
# SECRETS SETUP SCRIPT
# Script para configurar secrets localmente de forma segura
# ==================================================
set -e
echo "=================================================="
echo " Configuración de Secrets - Math Platform"
echo "=================================================="
echo ""
# Colores para output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Crear directorio de secrets
SECRETS_DIR="./secrets"
mkdir -p "$SECRETS_DIR"
echo -e "${YELLOW}Este script creará archivos de secrets en $SECRETS_DIR/${NC}"
echo -e "${YELLOW}Estos archivos están en .gitignore y NO deben commitearse${NC}"
echo ""
# Función para solicitar secret
request_secret() {
local name=$1
local description=$2
local file="$SECRETS_DIR/$name.txt"
echo "--------------------------------------------------"
echo -e "${GREEN}$description${NC}"
if [ -f "$file" ]; then
echo -e "${YELLOW}⚠️ Ya existe: $file${NC}"
read -p "¿Desea sobrescribir? (s/N): " overwrite
if [[ ! "$overwrite" =~ ^[Ss]$ ]]; then
echo " → Saltando..."
return
fi
fi
read -s -p "Ingrese el valor (presione Enter para omitir): " value
echo ""
if [ -n "$value" ]; then
echo "$value" > "$file"
chmod 600 "$file"
echo -e "${GREEN} ✓ Creado: $file${NC}"
else
echo -e "${YELLOW} → Omitido${NC}"
fi
}
# Solicitar cada secret
request_secret "db_password" "1. Contraseña de la Base de Datos PostgreSQL"
request_secret "redis_password" "2. Contraseña de Redis"
request_secret "jwt_secret" "3. JWT Secret (mínimo 32 caracteres recomendado)"
request_secret "session_secret" "4. Session Secret"
request_secret "ai_api_key" "5. AI API Key (DashScope/Aliyun)"
request_secret "telegram_token" "6. Telegram Bot Token"
request_secret "telegram_chat_id" "7. Telegram Admin Chat ID"
request_secret "monitor_db_password" "8. Contraseña del usuario de monitoreo"
echo ""
echo "=================================================="
echo -e "${GREEN}Configuración completada${NC}"
echo "=================================================="
echo ""
# Verificar permisos
echo "Verificando permisos de seguridad..."
chmod 600 "$SECRETS_DIR"/*.txt 2>/dev/null || true
echo -e "${GREEN}✓ Permisos establecidos: 600 (solo lectura/escritura owner)${NC}"
echo ""
# Listar secrets creados
echo "Secrets configurados:"
ls -la "$SECRETS_DIR/"
echo ""
echo "=================================================="
echo " INSTRUCCIONES DE USO"
echo "=================================================="
echo ""
echo "1. Para desarrollo local con secrets:"
echo " docker-compose -f docker-compose.secrets.yml up -d"
echo ""
echo "2. Para producción con Docker Swarm:"
echo " docker secret create db_password secrets/db_password.txt"
echo " docker stack deploy -c docker-compose.secrets.yml math-platform"
echo ""
echo "3. Para rotar credenciales:"
echo " - Generar nuevos valores"
echo " - Actualizar archivos en secrets/"
echo " - Reiniciar servicios: docker-compose -f docker-compose.secrets.yml restart"
echo ""
echo -e "${RED}⚠️ IMPORTANTE:${NC}"
echo -e "${RED} - NUNCA commitear archivos de secrets${NC}"
echo -e "${RED} - Hacer backup de secrets en un lugar seguro${NC}"
echo -e "${RED} - Rotar credenciales expuestas inmediatamente${NC}"
echo ""
echo "=================================================="

212
scripts/start-production.sh Executable file
View File

@@ -0,0 +1,212 @@
#!/bin/bash
# Script para iniciar el stack completo en modo producción
# Math2 Platform - Production-Grade Deployment
set -e # Exit on error
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Configuration
COMPOSE_FILE="docker-compose.prod.yml"
BACKEND_DIR="backend"
HEALTH_TIMEOUT=60
echo -e "${BLUE}🚀 Iniciando Math2 Platform en modo producción...${NC}"
echo "================================================"
# 1. Verificar que existe el archivo docker-compose.prod.yml
if [ ! -f "$COMPOSE_FILE" ]; then
echo -e "${RED}❌ No se encuentra $COMPOSE_FILE${NC}"
exit 1
fi
# 2. Verificar variables de entorno críticas
echo -e "${BLUE}🔍 Verificando variables de entorno...${NC}"
REQUIRED_VARS=(
"DATABASE_URL"
"DB_PASSWORD"
"REDIS_PASSWORD"
"JWT_SECRET"
)
MISSING_VARS=()
for var in "${REQUIRED_VARS[@]}"; do
if [ -z "${!var}" ]; then
MISSING_VARS+=($var)
fi
done
if [ ${#MISSING_VARS[@]} -ne 0 ]; then
echo -e "${RED}❌ Variables de entorno faltantes:${NC}"
for var in "${MISSING_VARS[@]}"; do
echo " - $var"
done
echo ""
echo -e "${YELLOW} Copia .env.example a .env y configura los valores${NC}"
exit 1
fi
echo -e "${GREEN}✅ Variables de entorno verificadas${NC}"
# 3. Verificar que TypeScript compila
echo -e "${BLUE}🔍 Verificando compilación TypeScript...${NC}"
cd $BACKEND_DIR
if ! npm run type-check > /tmp/type-check.log 2>&1; then
echo -e "${RED}❌ TypeScript tiene errores:${NC}"
cat /tmp/type-check.log
echo ""
echo -e "${YELLOW}⚠️ Corrige los errores antes de continuar${NC}"
exit 1
fi
echo -e "${GREEN}✅ TypeScript compila correctamente${NC}"
# 4. Generar Prisma Client
echo -e "${BLUE}📦 Generando Prisma Client...${NC}"
npx prisma generate --generator client > /tmp/prisma-generate.log 2>&1
if [ $? -ne 0 ]; then
echo -e "${RED}❌ Error generando Prisma Client${NC}"
cat /tmp/prisma-generate.log
exit 1
fi
echo -e "${GREEN}✅ Prisma Client generado${NC}"
# 5. Construir imágenes Docker
echo -e "${BLUE}🐳 Construyendo imágenes Docker...${NC}"
cd ..
export VERSION=${VERSION:-1.0.0}
docker-compose -f $COMPOSE_FILE build --parallel > /tmp/docker-build.log 2>&1
if [ $? -ne 0 ]; then
echo -e "${RED}❌ Error construyendo imágenes Docker${NC}"
tail -50 /tmp/docker-build.log
exit 1
fi
echo -e "${GREEN}✅ Imágenes Docker construidas (versión: $VERSION)${NC}"
# 6. Verificar volúmenes
echo -e "${BLUE}💾 Verificando volúmenes...${NC}"
docker volume ls | grep -q "postgres_data\|redis_data" || echo -e "${YELLOW}⚠️ Creando volúmenes por primera vez${NC}"
# 7. Iniciar servicios (con DB primero)
echo -e "${BLUE}🚀 Iniciando servicios (PostgreSQL y Redis primero)...${NC}"
docker-compose -f $COMPOSE_FILE up -d postgres redis
# Esperar a que DB esté lista
echo -e "${BLUE}⏳ Esperando a que PostgreSQL esté listo...${NC}"
for i in $(seq 1 30); do
if docker-compose -f $COMPOSE_FILE exec -T postgres pg_isready -U ${DB_USER:-mathuser} -d ${DB_NAME:-mathdb} > /dev/null 2>&1; then
echo -e "${GREEN}✅ PostgreSQL listo${NC}"
break
fi
if [ $i -eq 30 ]; then
echo -e "${RED}❌ Timeout esperando PostgreSQL${NC}"
exit 1
fi
sleep 2
done
# 8. Aplicar migraciones
echo -e "${BLUE}🔄 Aplicando migraciones de base de datos...${NC}"
cd $BACKEND_DIR
export DATABASE_URL="postgresql://${DB_USER:-mathuser}:${DB_PASSWORD}@localhost:5432/${DB_NAME:-mathdb}"
npx prisma migrate deploy > /tmp/migrate.log 2>&1
if [ $? -ne 0 ]; then
echo -e "${RED}❌ Error aplicando migraciones${NC}"
cat /tmp/migrate.log
exit 1
fi
echo -e "${GREEN}✅ Migraciones aplicadas${NC}"
# 9. Seed si es necesario (solo si la tabla User está vacía)
echo -e "${BLUE}🌱 Verificando si se necesita seed...${NC}"
SEED_COUNT=$(npx prisma db execute --stdin <<EOF 2>/dev/null | head -1
SELECT COUNT(*) FROM "User";
EOF
)
if [ "$SEED_COUNT" = "0" ] || [ -z "$SEED_COUNT" ]; then
echo -e "${BLUE}🌱 Ejecutando seed de base de datos...${NC}"
npx prisma db seed > /tmp/seed.log 2>&1
if [ $? -ne 0 ]; then
echo -e "${YELLOW}⚠️ Seed no completado (puede ser normal en primer despliegue)${NC}"
else
echo -e "${GREEN}✅ Seed completado${NC}"
fi
else
echo -e "${GREEN}✅ Datos existentes encontrados, omitiendo seed${NC}"
fi
cd ..
# 10. Iniciar todos los servicios
echo -e "${BLUE}🚀 Iniciando todos los servicios...${NC}"
docker-compose -f $COMPOSE_FILE up -d
# 11. Health checks
echo -e "${BLUE}⏳ Esperando servicios...${NC}"
sleep 10
echo -e "${BLUE}🏥 Ejecutando health checks...${NC}"
# Backend health check
echo -n " Backend: "
for i in $(seq 1 $HEALTH_TIMEOUT); do
if curl -sf http://localhost:3001/health > /dev/null 2>&1; then
echo -e "${GREEN}✅ OK${NC}"
break
fi
if [ $i -eq $HEALTH_TIMEOUT ]; then
echo -e "${RED}❌ FAIL (timeout)${NC}"
fi
sleep 1
done
# Frontend/Nginx health check
echo -n " Frontend: "
for i in $(seq 1 $HEALTH_TIMEOUT); do
if curl -sf http://localhost > /dev/null 2>&1 || curl -sf http://localhost:80 > /dev/null 2>&1; then
echo -e "${GREEN}✅ OK${NC}"
break
fi
if [ $i -eq $HEALTH_TIMEOUT ]; then
echo -e "${RED}❌ FAIL (timeout)${NC}"
fi
sleep 1
done
# Verificar workers (opcional, no crítico)
echo -n " Workers: "
WORKER_COUNT=$(docker-compose -f $COMPOSE_FILE ps -q pdf-worker exercise-worker notification-worker 2>/dev/null | wc -l)
if [ "$WORKER_COUNT" -ge 3 ]; then
echo -e "${GREEN}✅ OK ($WORKER_COUNT workers)${NC}"
else
echo -e "${YELLOW}⚠️ Parcial ($WORKER_COUNT/3 workers)${NC}"
fi
# 12. Mostrar resumen
echo ""
echo -e "${GREEN}================================================${NC}"
echo -e "${GREEN}✅ Math2 Platform iniciada en modo producción${NC}"
echo -e "${GREEN}================================================${NC}"
echo ""
echo -e "${BLUE}📊 Accesos:${NC}"
echo " • Dashboard: http://localhost"
echo " • API: http://localhost:3001"
echo " • Health Check: http://localhost:3001/health"
echo ""
echo -e "${BLUE}🔧 Comandos útiles:${NC}"
echo " • Ver logs: docker-compose -f $COMPOSE_FILE logs -f"
echo " • Detener: docker-compose -f $COMPOSE_FILE down"
echo " • Status: docker-compose -f $COMPOSE_FILE ps"
echo " • Verificar: ./scripts/verify-production.sh"
echo ""
echo -e "${YELLOW}⚠️ IMPORTANTE: Este es un entorno local de producción${NC}"
echo -e "${YELLOW} Para deployment real, configura SSL y dominio${NC}"
echo ""

195
scripts/system-check.sh Executable file
View File

@@ -0,0 +1,195 @@
#!/bin/bash
# ================================================
# Math Platform - Complete System Verification
# ================================================
set -e
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
NC='\033[0m' # No Color
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
echo -e "${CYAN}╔════════════════════════════════════════════════════════════╗${NC}"
echo -e "${CYAN}║ Math Platform - Complete System Verification ║${NC}"
echo -e "${CYAN}$(date '+%Y-%m-%d %H:%M:%S')${NC}"
echo -e "${CYAN}╚════════════════════════════════════════════════════════════╝${NC}"
total_checks=0
passed_checks=0
failed_checks=0
warned_checks=0
# Function to perform check
check() {
local description=$1
local command=$2
local severity=${3:-ERROR} # ERROR or WARN
total_checks=$((total_checks + 1))
echo -n "[$total_checks] $description... "
if eval "$command" > /dev/null 2>&1; then
echo -e "${GREEN}PASS${NC}"
passed_checks=$((passed_checks + 1))
return 0
else
if [ "$severity" = "WARN" ]; then
echo -e "${YELLOW}WARN${NC}"
warned_checks=$((warned_checks + 1))
else
echo -e "${RED}FAIL${NC}"
failed_checks=$((failed_checks + 1))
fi
return 1
fi
}
# Section 1: Docker Environment
echo -e "\n${BLUE}═══ Docker Environment ═══${NC}"
check "Docker daemon running" "docker info"
check "Docker Compose available" "command -v docker-compose || docker compose version"
# Section 2: Configuration Files
echo -e "\n${BLUE}═══ Configuration Files ═══${NC}"
check "docker-compose.yml exists" "test -f $PROJECT_ROOT/docker-compose.yml"
check ".env file exists" "test -f $PROJECT_ROOT/.env"
check "Backend Dockerfile exists" "test -f $PROJECT_ROOT/docker/Dockerfile.backend"
check "Frontend Dockerfile exists" "test -f $PROJECT_ROOT/docker/Dockerfile.frontend"
check "Worker Dockerfile exists" "test -f $PROJECT_ROOT/docker/Dockerfile.worker"
check "Nginx config exists" "test -f $PROJECT_ROOT/docker/nginx.conf"
# Section 3: Application Structure
echo -e "\n${BLUE}═══ Application Structure ═══${NC}"
check "Backend package.json exists" "test -f $PROJECT_ROOT/backend/package.json"
check "Frontend package.json exists" "test -f $PROJECT_ROOT/frontend/package.json"
check "Prisma schema exists" "test -f $PROJECT_ROOT/backend/prisma/schema.prisma"
check "Next.js config exists" "test -f $PROJECT_ROOT/frontend/next.config.js"
check "Backend src directory exists" "test -d $PROJECT_ROOT/backend/src"
check "Frontend src directory exists" "test -d $PROJECT_ROOT/frontend/src"
# Section 4: Scripts
echo -e "\n${BLUE}═══ Management Scripts ═══${NC}"
check "start.sh is executable" "test -x $PROJECT_ROOT/docker/start.sh"
check "stop.sh is executable" "test -x $PROJECT_ROOT/docker/stop.sh"
check "backup.sh is executable" "test -x $PROJECT_ROOT/docker/backup.sh"
check "test-e2e.sh is executable" "test -x $PROJECT_ROOT/scripts/test-e2e.sh"
check "health-check.sh is executable" "test -x $PROJECT_ROOT/scripts/health-check.sh"
check "validate-deployment.sh is executable" "test -x $PROJECT_ROOT/scripts/validate-deployment.sh"
# Section 5: Directories
echo -e "\n${BLUE}═══ Required Directories ═══${NC}"
check "docker/data directory exists" "test -d $PROJECT_ROOT/docker/data"
check "docker/logs directory exists" "test -d $PROJECT_ROOT/docker/logs"
check "docker/init-scripts directory exists" "test -d $PROJECT_ROOT/docker/init-scripts"
check "pdfs directory exists" "test -d $PROJECT_ROOT/pdfs"
check "scripts directory exists" "test -d $PROJECT_ROOT/scripts"
# Section 6: Environment Variables (if services are running)
echo -e "\n${BLUE}═══ Environment Variables ═══${NC}"
if [ -f "$PROJECT_ROOT/.env" ]; then
source "$PROJECT_ROOT/.env"
check "DB_PASSWORD is set" "test -n '$DB_PASSWORD'" "WARN"
check "REDIS_PASSWORD is set" "test -n '$REDIS_PASSWORD'" "WARN"
check "JWT_SECRET is set" "test -n '$JWT_SECRET'" "WARN"
check "AI_API_KEY is set" "test -n '$AI_API_KEY'" "WARN"
else
echo -e "${YELLOW}.env file not found, skipping variable checks${NC}"
fi
# Section 7: Services (if running)
echo -e "\n${BLUE}═══ Running Services ═══${NC}"
if docker info > /dev/null 2>&1; then
check "PostgreSQL container running" "docker ps | grep -q math-postgres" "WARN"
check "Redis container running" "docker ps | grep -q math-redis" "WARN"
check "Backend container running" "docker ps | grep -q math-backend" "WARN"
check "Frontend container running" "docker ps | grep -q math-frontend" "WARN"
check "Nginx container running" "docker ps | grep -q math-nginx" "WARN"
else
echo -e "${YELLOW}Docker not running, skipping service checks${NC}"
fi
# Section 8: Service Health (if running)
echo -e "\n${BLUE}═══ Service Health Endpoints ═══${NC}"
if curl -s http://localhost:3001/health > /dev/null 2>&1; then
check "Backend API health endpoint" "curl -s -f http://localhost:3001/health" "WARN"
else
echo -e "${YELLOW}Backend not accessible, skipping health checks${NC}"
fi
if curl -s http://localhost:3000 > /dev/null 2>&1; then
check "Frontend web server" "curl -s -f http://localhost:3000" "WARN"
else
echo -e "${YELLOW}Frontend not accessible, skipping health checks${NC}"
fi
# Section 9: Port Availability (if services not running)
echo -e "\n${BLUE}═══ Port Availability ═══${NC}"
if ! docker ps | grep -q math-postgres; then
check "Port 5432 available" "! netstat -tuln 2>/dev/null | grep -q ':5432 ' || ! ss -tuln 2>/dev/null | grep -q ':5432 '" "WARN"
else
echo -e "${YELLOW}PostgreSQL running, skipping port check${NC}"
fi
if ! docker ps | grep -q math-redis; then
check "Port 6379 available" "! netstat -tuln 2>/dev/null | grep -q ':6379 ' || ! ss -tuln 2>/dev/null | grep -q ':6379 '" "WARN"
else
echo -e "${YELLOW}Redis running, skipping port check${NC}"
fi
# Section 10: System Resources
echo -e "\n${BLUE}═══ System Resources ═══${NC}"
DISK_USAGE=$(df -h "$PROJECT_ROOT" | awk 'NR==2 {print $5}' | sed 's/%//')
if [ "$DISK_USAGE" -lt 80 ]; then
check "Disk space OK (using ${DISK_USAGE}%)" "true"
else
check "Disk space low (using ${DISK_USAGE}%)" "false" "WARN"
fi
MEM_USAGE=$(free | awk 'NR==2{printf "%.0f", $3*100/$2}')
if [ "$MEM_USAGE" -lt 90 ]; then
check "Memory OK (using ${MEM_USAGE}%)" "true"
else
check "Memory high (using ${MEM_USAGE}%)" "false" "WARN"
fi
# Final Summary
echo -e "\n${CYAN}╔════════════════════════════════════════════════════════════╗${NC}"
echo -e "${CYAN}║ Verification Summary ║${NC}"
echo -e "${CYAN}╠════════════════════════════════════════════════════════════╣${NC}"
printf "${CYAN}║ Total Checks: %-3d ║${NC}\n" $total_checks
printf "${GREEN}║ Passed: %-3d ║${NC}\n" $passed_checks
printf "${RED}║ Failed: %-3d ║${NC}\n" $failed_checks
printf "${YELLOW}║ Warnings: %-3d ║${NC}\n" $warned_checks
echo -e "${CYAN}╚════════════════════════════════════════════════════════════╝${NC}"
if [ $failed_checks -eq 0 ]; then
echo -e "\n${GREEN}✓ All critical checks passed!${NC}\n"
if [ $warned_checks -gt 0 ]; then
echo -e "${YELLOW}⚠ Some warnings detected. Review above for details.${NC}\n"
fi
echo -e "${BLUE}Next steps:${NC}"
echo -e " 1. Start services: ${CYAN}./docker/start.sh${NC}"
echo -e " 2. Run health check: ${CYAN}./scripts/health-check.sh${NC}"
echo -e " 3. Run E2E tests: ${CYAN}./scripts/test-e2e.sh${NC}"
echo -e " 4. View documentation: ${CYAN}cat DEPLOY.md${NC}\n"
exit 0
else
echo -e "\n${RED}✗ Some critical checks failed!${NC}"
echo -e "${RED} Please resolve the issues above before proceeding.${NC}\n"
exit 1
fi

436
scripts/test-e2e.sh Executable file
View File

@@ -0,0 +1,436 @@
#!/bin/bash
# ================================================
# Math Platform - End-to-End Testing Script
# ================================================
set -e
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Configuration
API_BASE_URL="http://localhost:3001"
FRONTEND_URL="http://localhost:3000"
TEST_USER_EMAIL="test_$(date +%s)@example.com"
TEST_USER_PASSWORD="Test123456!"
TEST_USER_NAME="Test User"
# Test results
TESTS_PASSED=0
TESTS_FAILED=0
TESTS_TOTAL=0
# Helper functions
print_header() {
echo -e "\n${BLUE}============================================${NC}"
echo -e "${BLUE}$1${NC}"
echo -e "${BLUE}============================================${NC}"
}
print_test() {
echo -e "\n${YELLOW}TEST: $1${NC}"
TESTS_TOTAL=$((TESTS_TOTAL + 1))
}
print_success() {
echo -e "${GREEN}✓ PASSED: $1${NC}"
TESTS_PASSED=$((TESTS_PASSED + 1))
}
print_error() {
echo -e "${RED}✗ FAILED: $1${NC}"
TESTS_FAILED=$((TESTS_FAILED + 1))
}
print_info() {
echo -e "${BLUE} $1${NC}"
}
# Function to wait for service
wait_for_service() {
local url=$1
local max_attempts=30
local attempt=1
print_info "Waiting for $url..."
while [ $attempt -le $max_attempts ]; do
if curl -s -f "$url" > /dev/null 2>&1; then
print_success "Service is ready"
return 0
fi
sleep 2
attempt=$((attempt + 1))
done
print_error "Service not ready after $max_attempts attempts"
return 1
}
# Function to make API request
api_request() {
local method=$1
local endpoint=$2
local data=$3
local token=$4
local url="${API_BASE_URL}${endpoint}"
local headers="-H 'Content-Type: application/json'"
if [ -n "$token" ]; then
headers="$headers -H 'Authorization: Bearer $token'"
fi
if [ "$method" = "GET" ]; then
curl -s -X GET "$url" $headers
elif [ "$method" = "POST" ]; then
curl -s -X POST "$url" $headers -d "$data"
elif [ "$method" = "PUT" ]; then
curl -s -X PUT "$url" $headers -d "$data"
elif [ "$method" = "DELETE" ]; then
curl -s -X DELETE "$url" $headers
fi
}
# Function to test user registration
test_registration() {
print_test "User Registration"
local response=$(api_request "POST" "/api/auth/register" "{\"email\":\"$TEST_USER_EMAIL\",\"password\":\"$TEST_USER_PASSWORD\",\"name\":\"$TEST_USER_NAME\"}")
if echo "$response" | grep -q "token"; then
USER_TOKEN=$(echo "$response" | grep -o '"token":"[^"]*' | cut -d'"' -f4)
USER_ID=$(echo "$response" | grep -o '"id":"[^"]*' | cut -d'"' -f4)
print_success "User registered successfully"
print_info "User ID: $USER_ID"
print_info "Email: $TEST_USER_EMAIL"
return 0
else
print_error "Registration failed"
print_info "Response: $response"
return 1
fi
}
# Function to test user login
test_login() {
print_test "User Login"
local response=$(api_request "POST" "/api/auth/login" "{\"email\":\"$TEST_USER_EMAIL\",\"password\":\"$TEST_USER_PASSWORD\"}")
if echo "$response" | grep -q "token"; then
USER_TOKEN=$(echo "$response" | grep -o '"token":"[^"]*' | cut -d'"' -f4)
print_success "User logged in successfully"
return 0
else
print_error "Login failed"
print_info "Response: $response"
return 1
fi
}
# Function to test getting modules
test_get_modules() {
print_test "Get Learning Modules"
local response=$(api_request "GET" "/api/modules" "" "$USER_TOKEN")
if echo "$response" | grep -q "modules"; then
MODULE_COUNT=$(echo "$response" | grep -o '"id"' | wc -l)
print_success "Retrieved $MODULE_COUNT modules"
# Get first module ID
if [ $MODULE_COUNT -gt 0 ]; then
MODULE_ID=$(echo "$response" | grep -o '"id":"[^"]*' | head -1 | cut -d'"' -f4)
print_info "First Module ID: $MODULE_ID"
fi
return 0
else
print_error "Failed to retrieve modules"
print_info "Response: $response"
return 1
fi
}
# Function to test accessing a specific module
test_access_module() {
print_test "Access Module Content"
if [ -z "$MODULE_ID" ]; then
print_error "No module ID available"
return 1
fi
local response=$(api_request "GET" "/api/modules/$MODULE_ID" "" "$USER_TOKEN")
if echo "$response" | grep -q "title"; then
MODULE_TITLE=$(echo "$response" | grep -o '"title":"[^"]*' | cut -d'"' -f4)
print_success "Accessed module: $MODULE_TITLE"
return 0
else
print_error "Failed to access module"
print_info "Response: $response"
return 1
fi
}
# Function to test getting exercises
test_get_exercises() {
print_test "Get Exercises for Module"
if [ -z "$MODULE_ID" ]; then
print_error "No module ID available"
return 1
fi
local response=$(api_request "GET" "/api/exercises?moduleId=$MODULE_ID" "" "$USER_TOKEN")
if echo "$response" | grep -q "exercises"; then
EXERCISE_COUNT=$(echo "$response" | grep -o '"id"' | wc -l)
print_success "Retrieved $EXERCISE_COUNT exercises"
# Get first exercise ID
if [ $EXERCISE_COUNT -gt 0 ]; then
EXERCISE_ID=$(echo "$response" | grep -o '"id":"[^"]*' | head -1 | cut -d'"' -f4)
print_info "First Exercise ID: $EXERCISE_ID"
fi
return 0
else
print_error "Failed to retrieve exercises"
print_info "Response: $response"
return 1
fi
}
# Function to test submitting an exercise
test_submit_exercise() {
print_test "Submit Exercise Answer"
if [ -z "$EXERCISE_ID" ]; then
print_error "No exercise ID available"
return 1
fi
# Submit a test answer
local response=$(api_request "POST" "/api/exercises/$EXERCISE_ID/submit" "{\"answer\":\"42\",\"timeSpent\":60}" "$USER_TOKEN")
if echo "$response" | grep -q "correct"; then
print_success "Exercise submitted successfully"
return 0
else
print_error "Failed to submit exercise"
print_info "Response: $response"
return 1
fi
}
# Function to test getting user progress
test_get_progress() {
print_test "Get User Progress"
local response=$(api_request "GET" "/api/users/progress" "" "$USER_TOKEN")
if echo "$response" | grep -q "progress"; then
print_success "Retrieved user progress"
return 0
else
print_error "Failed to retrieve progress"
print_info "Response: $response"
return 1
fi
}
# Function to test getting ranking
test_get_ranking() {
print_test "Get User Ranking"
local response=$(api_request "GET" "/api/users/ranking?limit=10" "" "$USER_TOKEN")
if echo "$response" | grep -q "ranking"; then
print_success "Retrieved user ranking"
return 0
else
print_error "Failed to retrieve ranking"
print_info "Response: $response"
return 1
fi
}
# Function to test getting achievements
test_get_achievements() {
print_test "Get User Achievements"
local response=$(api_request "GET" "/api/users/achievements" "" "$USER_TOKEN")
if echo "$response" | grep -q "achievements"; then
ACHIEVEMENT_COUNT=$(echo "$response" | grep -o '"id"' | wc -l)
print_success "Retrieved $ACHIEVEMENT_COUNT achievements"
return 0
else
print_error "Failed to retrieve achievements"
print_info "Response: $response"
return 1
fi
}
# Function to test updating user profile
test_update_profile() {
print_test "Update User Profile"
local new_name="Updated Test User"
local response=$(api_request "PUT" "/api/users/profile" "{\"name\":\"$new_name\"}" "$USER_TOKEN")
if echo "$response" | grep -q "name"; then
print_success "Profile updated successfully"
return 0
else
print_error "Failed to update profile"
print_info "Response: $response"
return 1
fi
}
# Function to test health endpoint
test_health() {
print_test "Health Check Endpoint"
local response=$(curl -s "$API_BASE_URL/health")
if echo "$response" | grep -q "status.*ok\|healthy"; then
print_success "Health check passed"
return 0
else
print_error "Health check failed"
print_info "Response: $response"
return 1
fi
}
# Function to test frontend accessibility
test_frontend() {
print_test "Frontend Accessibility"
local response=$(curl -s -o /dev/null -w "%{http_code}" "$FRONTEND_URL")
if [ "$response" = "200" ]; then
print_success "Frontend is accessible"
return 0
else
print_error "Frontend returned status code: $response"
return 1
fi
}
# Function to run all tests
run_all_tests() {
print_header "Math Platform E2E Test Suite"
# Check if services are running
print_info "Checking service availability..."
if ! wait_for_service "$API_BASE_URL/health"; then
print_error "Backend service is not available. Please start the services first."
print_info "Run: ./docker/start.sh"
exit 1
fi
if ! wait_for_service "$FRONTEND_URL"; then
print_error "Frontend service is not available. Please start the services first."
print_info "Run: ./docker/start.sh"
exit 1
fi
echo -e "\n${GREEN}All services are running! Starting tests...${NC}\n"
# Run tests
test_health
test_frontend
test_registration
test_login
test_get_modules
test_access_module
test_get_exercises
test_submit_exercise
test_get_progress
test_get_ranking
test_get_achievements
test_update_profile
# Print summary
print_header "Test Summary"
echo -e "Total Tests: $TESTS_TOTAL"
echo -e "${GREEN}Passed: $TESTS_PASSED${NC}"
echo -e "${RED}Failed: $TESTS_FAILED${NC}"
if [ $TESTS_FAILED -eq 0 ]; then
echo -e "\n${GREEN}All tests passed!${NC}\n"
exit 0
else
echo -e "\n${RED}Some tests failed!${NC}\n"
exit 1
fi
}
# Function to run specific test
run_specific_test() {
local test_name=$1
case $test_name in
"health")
test_health
;;
"frontend")
test_frontend
;;
"register")
test_registration
;;
"login")
test_login
;;
"modules")
test_get_modules
;;
"module")
test_access_module
;;
"exercises")
test_get_exercises
;;
"submit")
test_submit_exercise
;;
"progress")
test_get_progress
;;
"ranking")
test_get_ranking
;;
"achievements")
test_get_achievements
;;
"profile")
test_update_profile
;;
*)
print_error "Unknown test: $test_name"
echo "Available tests: health, frontend, register, login, modules, module, exercises, submit, progress, ranking, achievements, profile"
exit 1
;;
esac
}
# Main execution
main() {
if [ $# -eq 0 ]; then
run_all_tests
else
run_specific_test "$1"
fi
}
# Run main function
main "$@"

249
scripts/validate-deployment.sh Executable file
View File

@@ -0,0 +1,249 @@
#!/bin/bash
# ================================================
# Math Platform - Pre-Deployment Validation
# ================================================
set -e
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
ERRORS=0
WARNINGS=0
echo -e "${BLUE}============================================${NC}"
echo -e "${BLUE}Math Platform - Pre-Deployment Validation${NC}"
echo -e "${BLUE}============================================${NC}"
# Function to print check result
print_result() {
local status=$1
local message=$2
if [ "$status" = "OK" ]; then
echo -e "${GREEN}${NC} $message"
elif [ "$status" = "WARN" ]; then
echo -e "${YELLOW}${NC} $message"
WARNINGS=$((WARNINGS + 1))
else
echo -e "${RED}${NC} $message"
ERRORS=$((ERRORS + 1))
fi
}
# Check 1: Docker Installation
echo -e "\n${BLUE}Checking Prerequisites${NC}"
echo "----------------------------------------"
if command -v docker &> /dev/null; then
DOCKER_VERSION=$(docker --version | awk '{print $3}' | sed 's/,//')
print_result "OK" "Docker installed: $DOCKER_VERSION"
else
print_result "FAIL" "Docker not installed"
fi
if command -v docker-compose &> /dev/null || docker compose version &> /dev/null; then
print_result "OK" "Docker Compose available"
else
print_result "FAIL" "Docker Compose not installed"
fi
# Check 2: Environment File
echo -e "\n${BLUE}Checking Environment Configuration${NC}"
echo "----------------------------------------"
if [ -f "$PROJECT_ROOT/.env" ]; then
print_result "OK" ".env file exists"
# Check critical variables
source "$PROJECT_ROOT/.env"
if [ -n "$DB_PASSWORD" ] && [ ${#DB_PASSWORD} -ge 16 ]; then
print_result "OK" "DB_PASSWORD configured (length: ${#DB_PASSWORD})"
else
print_result "WARN" "DB_PASSWORD not set or too short (min 16 chars)"
fi
if [ -n "$REDIS_PASSWORD" ] && [ ${#REDIS_PASSWORD} -ge 16 ]; then
print_result "OK" "REDIS_PASSWORD configured (length: ${#REDIS_PASSWORD})"
else
print_result "WARN" "REDIS_PASSWORD not set or too short (min 16 chars)"
fi
if [ -n "$JWT_SECRET" ] && [ ${#JWT_SECRET} -ge 32 ]; then
print_result "OK" "JWT_SECRET configured (length: ${#JWT_SECRET})"
else
print_result "FAIL" "JWT_SECRET not set or too short (min 32 chars)"
fi
if [ -n "$AI_API_KEY" ]; then
print_result "OK" "AI_API_KEY configured"
else
print_result "WARN" "AI_API_KEY not set (AI features won't work)"
fi
else
print_result "FAIL" ".env file not found"
fi
# Check 3: Required Files
echo -e "\n${BLUE}Checking Required Files${NC}"
echo "----------------------------------------"
files=(
"docker-compose.yml:docker-compose.yml"
"Dockerfile (Backend):docker/Dockerfile.backend"
"Dockerfile (Frontend):docker/Dockerfile.frontend"
"Dockerfile (Worker):docker/Dockerfile.worker"
"Nginx config:docker/nginx.conf"
"Backend package.json:backend/package.json"
"Frontend package.json:frontend/package.json"
"Prisma schema:backend/prisma/schema.prisma"
"Next.js config:frontend/next.config.js"
)
for file_info in "${files[@]}"; do
file="${file_info##*:}"
name="${file_info%:*}"
if [ -f "$PROJECT_ROOT/$file" ]; then
print_result "OK" "$name exists"
else
print_result "FAIL" "$name missing: $file"
fi
done
# Check 4: Required Directories
echo -e "\n${BLUE}Checking Directories${NC}"
echo "----------------------------------------"
dirs=(
"Backend source:backend/src"
"Frontend source:frontend/src"
"Docker data:docker/data"
"Docker logs:docker/logs"
"Init scripts:docker/init-scripts"
"Scripts:scripts"
"PDF storage:pdfs"
)
for dir_info in "${dirs[@]}"; do
dir="${dir_info##*:}"
name="${dir_info%:*}"
if [ -d "$PROJECT_ROOT/$dir" ]; then
print_result "OK" "$name exists"
else
print_result "WARN" "$name missing: $dir"
fi
done
# Check 5: Script Permissions
echo -e "\n${BLUE}Checking Script Permissions${NC}"
echo "----------------------------------------"
scripts=(
"docker/start.sh"
"docker/stop.sh"
"docker/backup.sh"
"scripts/test-e2e.sh"
"scripts/health-check.sh"
"scripts/monitor.sh"
)
for script in "${scripts[@]}"; do
if [ -f "$PROJECT_ROOT/$script" ]; then
if [ -x "$PROJECT_ROOT/$script" ]; then
print_result "OK" "$script is executable"
else
print_result "WARN" "$script is not executable"
fi
else
print_result "WARN" "$script not found"
fi
done
# Check 6: Docker Resources
echo -e "\n${BLUE}Checking Docker Resources${NC}"
echo "----------------------------------------"
if docker info &> /dev/null; then
# Check available disk space
DISK_AVAILABLE=$(df -h "$PROJECT_ROOT" | awk 'NR==2 {print $4}')
DISK_USAGE_PERCENT=$(df -h "$PROJECT_ROOT" | awk 'NR==2 {print $5}' | sed 's/%//')
if [ "$DISK_USAGE_PERCENT" -lt 80 ]; then
print_result "OK" "Disk space available: $DISK_AVAILABLE"
else
print_result "WARN" "Disk space low: $DISK_AVAILABLE available"
fi
# Check Docker memory
DOCKER_MEMORY=$(docker info 2>/dev/null | grep "Total Memory" | awk '{print $3}' || echo "N/A")
if [ "$DOCKER_MEMORY" != "N/A" ]; then
print_result "OK" "Docker memory: $DOCKER_MEMORY"
fi
# Check for running containers
RUNNING_CONTAINERS=$(docker ps -q | wc -l)
print_result "OK" "Running containers: $RUNNING_CONTAINERS"
else
print_result "WARN" "Docker not running"
fi
# Check 7: Port Availability
echo -e "\n${BLUE}Checking Port Availability${NC}"
echo "----------------------------------------"
ports=("80:HTTP" "3000:Frontend" "3001:Backend" "5432:PostgreSQL" "6379:Redis")
for port_info in "${ports[@]}"; do
port="${port_info%%:*}"
name="${port_info##*:}"
if netstat -tuln 2>/dev/null | grep -q ":$port " || ss -tuln 2>/dev/null | grep -q ":$port "; then
print_result "WARN" "Port $port ($name) already in use"
else
print_result "OK" "Port $port ($name) available"
fi
done
# Check 8: File Permissions
echo -e "\n${BLUE}Checking File Permissions${NC}"
echo "----------------------------------------"
if [ -d "$PROJECT_ROOT/docker/data" ]; then
if [ -w "$PROJECT_ROOT/docker/data" ]; then
print_result "OK" "docker/data is writable"
else
print_result "FAIL" "docker/data is not writable"
fi
fi
if [ -d "$PROJECT_ROOT/docker/logs" ]; then
if [ -w "$PROJECT_ROOT/docker/logs" ]; then
print_result "OK" "docker/logs is writable"
else
print_result "FAIL" "docker/logs is not writable"
fi
fi
# Summary
echo -e "\n${BLUE}============================================${NC}"
echo -e "${BLUE}Validation Summary${NC}"
echo -e "${BLUE}============================================${NC}"
if [ $ERRORS -eq 0 ] && [ $WARNINGS -eq 0 ]; then
echo -e "${GREEN}All checks passed! Ready for deployment.${NC}\n"
exit 0
elif [ $ERRORS -eq 0 ]; then
echo -e "${YELLOW}Validation passed with $WARNINGS warning(s).${NC}\n"
exit 0
else
echo -e "${RED}Validation failed with $ERRORS error(s) and $WARNINGS warning(s).${NC}\n"
exit 1
fi

187
scripts/verify-production.sh Executable file
View File

@@ -0,0 +1,187 @@
#!/bin/bash
# Verificar que el stack de producción está funcionando correctamente
# Math2 Platform - Production Verification
set -e
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
COMPOSE_FILE="docker-compose.prod.yml"
VERIFICATION_LOG="/tmp/verify-production-$(date +%Y%m%d-%H%M%S).log"
echo -e "${BLUE}🔍 Verificando estado de producción...${NC}"
echo "================================================" | tee -a $VERIFICATION_LOG
date | tee -a $VERIFICATION_LOG
echo "" | tee -a $VERIFICATION_LOG
# Función para verificar estado de contenedores
check_containers() {
echo -e "${BLUE}📦 Estado de contenedores:${NC}" | tee -a $VERIFICATION_LOG
docker-compose -f $COMPOSE_FILE ps 2>&1 | tee -a $VERIFICATION_LOG
echo "" | tee -a $VERIFICATION_LOG
}
# Función para verificar health de cada servicio
check_service_health() {
local service=$1
local port=$2
local endpoint=$3
echo -n " $service: " | tee -a $VERIFICATION_LOG
# Verificar si el contenedor está corriendo
if ! docker-compose -f $COMPOSE_FILE ps | grep -q "$service.*Up"; then
echo -e "${RED}❌ CONTAINER DOWN${NC}" | tee -a $VERIFICATION_LOG
return 1
fi
# Verificar health endpoint si aplica
if [ -n "$endpoint" ]; then
if curl -sf http://localhost:$port$endpoint > /dev/null 2>&1; then
echo -e "${GREEN}✅ HEALTHY${NC}" | tee -a $VERIFICATION_LOG
return 0
else
echo -e "${YELLOW}⚠️ RUNNING (No health endpoint)${NC}" | tee -a $VERIFICATION_LOG
return 0
fi
else
echo -e "${GREEN}✅ RUNNING${NC}" | tee -a $VERIFICATION_LOG
return 0
fi
}
# Verificar estado de servicios
echo -e "${BLUE}🏥 Health Checks de Servicios:${NC}" | tee -a $VERIFICATION_LOG
SERVICES=(
"postgres:5432:"
"redis:6379:"
"backend:3001:/health"
"frontend:3000:"
"nginx:80:"
"pdf-worker:3002:/health"
"exercise-worker:3003:/health"
"notification-worker:3004:/health"
)
FAILED_SERVICES=0
for service_info in "${SERVICES[@]}"; do
IFS=':' read -r service port endpoint <<< "$service_info"
if ! check_service_health "$service" "$port" "$endpoint"; then
((FAILED_SERVICES++))
fi
done
echo "" | tee -a $VERIFICATION_LOG
# Verificar logs recientes (solo errores)
echo -e "${BLUE}📋 Logs recientes (últimos errores):${NC}" | tee -a $VERIFICATION_LOG
for service in backend frontend pdf-worker exercise-worker notification-worker; do
echo -e "${YELLOW} [$service] Errores:${NC}" | tee -a $VERIFICATION_LOG
docker-compose -f $COMPOSE_FILE logs --tail=20 $service 2>&1 | grep -E "(ERROR|FATAL|Exception|Traceback)" | head -5 | tee -a $VERIFICATION_LOG || echo " No hay errores recientes" | tee -a $VERIFICATION_LOG
echo "" | tee -a $VERIFICATION_LOG
done
# Verificar recursos
echo -e "${BLUE}💾 Uso de recursos:${NC}" | tee -a $VERIFICATION_LOG
docker stats --no-stream --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.NetIO}}\t{{.BlockIO}}" 2>/dev/null | grep -E "(math-|postgres|redis)" | tee -a $VERIFICATION_LOG || echo " No se pueden obtener estadísticas" | tee -a $VERIFICATION_LOG
echo "" | tee -a $VERIFICATION_LOG
# Verificar base de datos
echo -e "${BLUE}🗄️ Estado de Base de Datos:${NC}" | tee -a $VERIFICATION_LOG
cd backend
# Verificar conexión a PostgreSQL
if npx prisma db execute --stdin <<EOF 2>/dev/null | tee -a $VERIFICATION_LOG
SELECT current_database(), current_user, version();
EOF
then
echo -e "${GREEN}✅ Conexión a PostgreSQL OK${NC}" | tee -a $VERIFICATION_LOG
else
echo -e "${RED}❌ Error conectando a PostgreSQL${NC}" | tee -a $VERIFICATION_LOG
((FAILED_SERVICES++))
fi
# Verificar estado de migraciones
echo -e "${BLUE}🔄 Estado de migraciones:${NC}" | tee -a $VERIFICATION_LOG
if npx prisma migrate status 2>&1 | tee -a $VERIFICATION_LOG | grep -q "Database schema is up to date"; then
echo -e "${GREEN}✅ Migraciones al día${NC}" | tee -a $VERIFICATION_LOG
else
echo -e "${YELLOW}⚠️ Verificar estado de migraciones${NC}" | tee -a $VERIFICATION_LOG
fi
cd ..
# Verificar Redis
echo -e "${BLUE}📊 Estado de Redis:${NC}" | tee -a $VERIFICATION_LOG
if docker-compose -f $COMPOSE_FILE exec -T redis redis-cli -a ${REDIS_PASSWORD} info stats 2>/dev/null | grep -E "(total_connections_received|total_commands_processed)" | tee -a $VERIFICATION_LOG; then
echo -e "${GREEN}✅ Redis respondiendo${NC}" | tee -a $VERIFICATION_LOG
else
echo -e "${YELLOW}⚠️ Redis no responde a comandos${NC}" | tee -a $VERIFICATION_LOG
fi
echo "" | tee -a $VERIFICATION_LOG
# Verificar volúmenes
echo -e "${BLUE}💾 Estado de volúmenes:${NC}" | tee -a $VERIFICATION_LOG
docker volume ls | grep -E "math|postgres_data|redis_data" | tee -a $VERIFICATION_LOG
echo "" | tee -a $VERIFICATION_LOG
# Tests de endpoints HTTP
echo -e "${BLUE}🌐 Tests de Endpoints:${NC}" | tee -a $VERIFICATION_LOG
# Backend health
echo -n " Backend (/health): " | tee -a $VERIFICATION_LOG
if curl -sf http://localhost:3001/health > /dev/null 2>&1; then
echo -e "${GREEN}✅ OK${NC}" | tee -a $VERIFICATION_LOG
else
echo -e "${RED}❌ FAIL${NC}" | tee -a $VERIFICATION_LOG
((FAILED_SERVICES++))
fi
# Frontend
echo -n " Frontend (/: " | tee -a $VERIFICATION_LOG
if curl -sf http://localhost > /dev/null 2>&1; then
echo -e "${GREEN}✅ OK${NC}" | tee -a $VERIFICATION_LOG
else
echo -e "${RED}❌ FAIL${NC}" | tee -a $VERIFICATION_LOG
((FAILED_SERVICES++))
fi
# API endpoints adicionales
echo -n " API (/api/status): " | tee -a $VERIFICATION_LOG
if curl -sf http://localhost:3001/api/status > /dev/null 2>&1 || curl -sf http://localhost/api/status > /dev/null 2>&1; then
echo -e "${GREEN}✅ OK${NC}" | tee -a $VERIFICATION_LOG
else
echo -e "${YELLOW}⚠️ No disponible${NC}" | tee -a $VERIFICATION_LOG
fi
echo "" | tee -a $VERIFICATION_LOG
# Resumen final
echo -e "${BLUE}================================================${NC}" | tee -a $VERIFICATION_LOG
echo -e "${BLUE}📊 RESUMEN DE VERIFICACIÓN${NC}" | tee -a $VERIFICATION_LOG
echo -e "${BLUE}================================================${NC}" | tee -a $VERIFICATION_LOG
if [ $FAILED_SERVICES -eq 0 ]; then
echo -e "${GREEN}✅ TODOS LOS SERVICIOS FUNCIONAN CORRECTAMENTE${NC}" | tee -a $VERIFICATION_LOG
else
echo -e "${RED}$FAILED_SERVICES SERVICIO(S) CON PROBLEMAS${NC}" | tee -a $VERIFICATION_LOG
fi
echo "" | tee -a $VERIFICATION_LOG
echo -e "${BLUE}📁 Log guardado en:${NC} $VERIFICATION_LOG" | tee -a $VERIFICATION_LOG
echo "" | tee -a $VERIFICATION_LOG
# Recomendaciones
if [ $FAILED_SERVICES -gt 0 ]; then
echo -e "${YELLOW}⚠️ Recomendaciones:${NC}" | tee -a $VERIFICATION_LOG
echo " 1. Revisa los logs: docker-compose -f $COMPOSE_FILE logs -f [servicio]" | tee -a $VERIFICATION_LOG
echo " 2. Verifica variables de entorno en .env" | tee -a $VERIFICATION_LOG
echo " 3. Reinicia servicios: docker-compose -f $COMPOSE_FILE restart" | tee -a $VERIFICATION_LOG
echo " 4. Si persiste: docker-compose -f $COMPOSE_FILE down && ./scripts/start-production.sh" | tee -a $VERIFICATION_LOG
fi
exit $FAILED_SERVICES