Preview:
#!/bin/bash

# Colores para la salida
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

# Funciones de mensajes
error() { echo -e "${RED}Error: $1${NC}" >&2; }
success() { echo -e "${GREEN}$1${NC}"; }
info() { echo -e "${BLUE}$1${NC}"; }
warning() { echo -e "${YELLOW}$1${NC}" >&2; }
process() { echo -e "${CYAN}$1${NC}"; }

# Variables globales
DOCKER_DIR="docker"
PROJECT_NAME=""
FRAMEWORK=""
PHP_VERSION=""
WEB_PORT=""
DB_PORT=""
CURRENT_DIR=$(pwd)

# Función para verificar y corregir permisos
check_and_fix_permissions() {
    local target_dir="$1"
    
    # Verificar si el directorio existe
    if [ ! -d "$target_dir" ]; then
        return 0
    fi
    
    process "Verificando permisos de $target_dir..."
    
    # Verificar si tenemos permisos de escritura
    if [ ! -w "$target_dir" ]; then
        warning "No tenemos permisos de escritura en $target_dir"
        process "Intentando corregir permisos..."
        
        # Intentar con sudo si está disponible
        if command -v sudo &> /dev/null; then
            if sudo chmod 755 "$target_dir" && sudo chown "$USER:$USER" "$target_dir" 2>/dev/null; then
                success "Permisos corregidos con sudo"
                return 0
            fi
        fi
        
        # Intentar sin sudo
        if chmod 755 "$target_dir" 2>/dev/null; then
            success "Permisos corregidos"
        else
            error "No se pueden corregir los permisos automáticamente"
            info "Por favor, ejecuta manualmente:"
            echo "  sudo chmod 755 \"$target_dir\""
            echo "  sudo chown \$USER:\$USER \"$target_dir\""
            return 1
        fi
    else
        success "Permisos correctos en $target_dir"
    fi
    
    return 0
}

# Función para limpiar directorio de forma segura
safe_clean_directory() {
    local dir_path="$1"
    
    if [ ! -d "$dir_path" ]; then
        return 0
    fi
    
    process "Limpiando directorio $dir_path..."
    
    # Verificar permisos primero
    if ! check_and_fix_permissions "$dir_path"; then
        return 1
    fi
    
    # Intentar eliminar contenido del directorio
    if [ -w "$dir_path" ]; then
        # Usar find para eliminar contenido de forma segura
        if find "$dir_path" -mindepth 1 -maxdepth 1 -exec rm -rf {} + 2>/dev/null; then
            success "Directorio limpiado correctamente"
            return 0
        else
            # Si falla, intentar con sudo
            if command -v sudo &> /dev/null; then
                if sudo rm -rf "${dir_path:?}/"* 2>/dev/null; then
                    success "Directorio limpiado con sudo"
                    return 0
                fi
            fi
        fi
    fi
    
    error "No se pudo limpiar el directorio $dir_path"
    return 1
}

# Verificar instalación de Docker y Docker Compose
check_docker_installation() {
    process "Verificando instalación de Docker..."
    if ! command -v docker &> /dev/null; then
        error "Docker no está instalado. Instala Docker primero."
        echo "Visita: https://docs.docker.com/get-docker/"
        exit 1
    fi
    success "Docker está instalado."

    process "Verificando Docker Compose..."
    if ! command -v docker-compose &> /dev/null && ! docker compose version &> /dev/null; then
        error "Docker Compose no está instalado."
        echo "Visita: https://docs.docker.com/compose/install/"
        exit 1
    fi
    success "Docker Compose está instalado."
}

# Verificar permisos de Docker
check_docker_permissions() {
    process "Verificando permisos de Docker..."
    if ! docker info &> /dev/null; then
        error "No tienes permisos para acceder a Docker."
        warning "Solución:"
        echo "  sudo usermod -aG docker \$USER"
        echo "  newgrp docker"
        exit 1
    fi
    success "Permisos de Docker verificados."
}

# Verificar conexión a internet
check_internet_connection() {
    process "Verificando conexión a internet..."
    if ! curl -s --connect-timeout 10 https://packagist.org > /dev/null; then
        error "No hay conexión a internet o Packagist no está disponible"
        warning "El script requiere conexión a internet para descargar dependencias"
        read -p "¿Continuar de todos modos? (y/N): " -n 1 -r
        echo
        if [[ ! $REPLY =~ ^[Yy]$ ]]; then
            exit 1
        fi
    else
        success "Conexión a internet verificada"
    fi
}

# Encontrar puertos disponibles
find_available_ports() {
    local web_port=8081
    local db_port=5433
    
    # Encontrar puerto web disponible
    while ss -tuln 2>/dev/null | grep -q ":${web_port} "; do
        web_port=$((web_port + 1))
    done
    
    # Encontrar puerto DB disponible (empezando desde 5433)
    while ss -tuln 2>/dev/null | grep -q ":${db_port} "; do
        db_port=$((db_port + 1))
    done
    
    WEB_PORT="$web_port"
    DB_PORT="$db_port"
    success "Puertos disponibles encontrados: Web → ${WEB_PORT}, DB → ${DB_PORT}"
}

# Seleccionar framework CON VERSIONES CORRECTAS
select_framework() {
    echo ""
    info "🎯 SELECCIÓN DE FRAMEWORK"
    echo "========================="
    echo ""
    echo "  1) Yii2 Basic - Framework tradicional (PHP 7.4)"
    echo "  2) Yii2 Advanced - Framework avanzado (PHP 7.4)"
    echo "  3) Yii3 - Framework moderno (PHP 8.4+)"  
    echo "  4) Laravel - Framework elegante (PHP 8.3+)"
    echo ""
    
    while true; do
        read -p "Selecciona el framework (1-4): " choice
        case $choice in
            1) 
                FRAMEWORK="yii2-basic"
                PHP_VERSION="7.4"
                success "Seleccionado: Yii2 Basic con PHP 7.4"
                break
                ;;
            2) 
                FRAMEWORK="yii2-advanced"
                PHP_VERSION="7.4"
                success "Seleccionado: Yii2 Advanced con PHP 7.4"
                break
                ;;
            3) 
                FRAMEWORK="yii3"
                PHP_VERSION="8.4"
                success "Seleccionado: Yii3 con PHP 8.4"
                break
                ;;
            4) 
                FRAMEWORK="laravel"
                PHP_VERSION="8.3"
                success "Seleccionado: Laravel con PHP 8.3"
                break
                ;;
            *) 
                error "Opción inválida. Por favor, selecciona 1, 2, 3 o 4"
                ;;
        esac
    done
}

# Validar nombre del proyecto
get_project_name() {
    echo ""
    info "📝 NOMBRE DEL PROYECTO"
    echo "======================"
    echo "Elige un nombre para tu proyecto:"
    echo "  - Debe comenzar con una letra"
    echo "  - Puede contener letras, números, guiones y guiones bajos"
    echo ""
    
    while true; do
        read -p "Ingresa el nombre del proyecto: " project_name
        
        if [ -z "$project_name" ]; then
            error "El nombre no puede estar vacío."
            continue
        fi
        
        if [[ ! "$project_name" =~ ^[a-zA-Z][a-zA-Z0-9_-]*$ ]]; then
            error "El nombre debe comenzar con una letra y solo puede contener letras, números, guiones y guiones bajos."
            continue
        fi
        
        PROJECT_NAME="$project_name"
        success "Nombre del proyecto validado: $PROJECT_NAME"
        break
    done
}

# Crear estructura de directorios MEJORADA con gestión de permisos
create_directory_structure() {
    process "Creando estructura de directorios en '$DOCKER_DIR'..."
    
    # Verificar y corregir permisos del directorio docker si existe
    if [ -d "$DOCKER_DIR" ]; then
        warning "El directorio $DOCKER_DIR ya existe. Verificando permisos..."
        
        if ! check_and_fix_permissions "$DOCKER_DIR"; then
            error "No se pueden corregir los permisos de $DOCKER_DIR"
            return 1
        fi
        
        # Verificar y corregir permisos de subdirectorios
        if [ -d "$DOCKER_DIR/src" ]; then
            if ! check_and_fix_permissions "$DOCKER_DIR/src"; then
                error "No se pueden corregir los permisos de $DOCKER_DIR/src"
                return 1
            fi
            
            # Limpiar el directorio src de forma segura
            if ! safe_clean_directory "$DOCKER_DIR/src"; then
                error "No se pudo limpiar el directorio $DOCKER_DIR/src"
                return 1
            fi
        else
            # Crear directorio src con permisos correctos
            if ! mkdir -p "$DOCKER_DIR/src"; then
                error "No se pudo crear el directorio $DOCKER_DIR/src"
                return 1
            fi
            chmod 755 "$DOCKER_DIR/src"
        fi
    else
        # Crear toda la estructura desde cero
        if ! mkdir -p "$DOCKER_DIR/src" "$DOCKER_DIR/apache-logs" "$DOCKER_DIR/postgres-backups"; then
            error "No se pudo crear la estructura de directorios"
            return 1
        fi
        # Establecer permisos correctos
        chmod 755 "$DOCKER_DIR" "$DOCKER_DIR/src" "$DOCKER_DIR/apache-logs" "$DOCKER_DIR/postgres-backups"
        success "Estructura de directorios creada en: $DOCKER_DIR/"
    fi
    
    # Crear subcarpeta con nombre del proyecto dentro de src
    process "Creando directorio del proyecto: $DOCKER_DIR/src/$PROJECT_NAME"
    if ! mkdir -p "$DOCKER_DIR/src/$PROJECT_NAME"; then
        error "No se pudo crear la subcarpeta del proyecto"
        return 1
    fi
    
    # Establecer permisos correctos para la carpeta del proyecto
    if ! chmod 755 "$DOCKER_DIR/src/$PROJECT_NAME"; then
        warning "No se pudieron establecer los permisos automáticamente en $DOCKER_DIR/src/$PROJECT_NAME"
        info "Puedes establecerlos manualmente después:"
        echo "  chmod 755 \"$DOCKER_DIR/src/$PROJECT_NAME\""
    fi
    
    success "Estructura de directorios creada: $DOCKER_DIR/src/$PROJECT_NAME/"
    return 0
}

# Crear archivo .env
create_env_file() {
    process "Creando archivo de configuración .env..."
    
    find_available_ports
    
    cat > "$DOCKER_DIR/.env" << EOF
# Configuración de la aplicación
APP_NAME=$PROJECT_NAME
APP_ENV=local
APP_DEBUG=true
APP_URL=http://localhost:${WEB_PORT}

# Configuración de base de datos
DB_CONNECTION=pgsql
DB_HOST=db
DB_PORT=5432  # Puerto INTERNO del contenedor (SIEMPRE 5432)
DB_DATABASE=${PROJECT_NAME}_db
DB_USERNAME=postgres
DB_PASSWORD=password

# Configuración de PostgreSQL
POSTGRES_DB=${PROJECT_NAME}_db
POSTGRES_USER=postgres
POSTGRES_PASSWORD=password

# Configuración de Docker
PHP_VERSION=${PHP_VERSION}
WEB_PORT=${WEB_PORT}
DB_EXTERNAL_PORT=${DB_PORT}  # Puerto EXPUESTO en el host

# Configuración específica del framework
FRAMEWORK=${FRAMEWORK}
PROJECT_NAME=${PROJECT_NAME}
EOF

    # Verificar que el archivo se creó y establecer permisos
    if [ ! -f "$DOCKER_DIR/.env" ]; then
        error "No se pudo crear el archivo .env"
        return 1
    fi
    
    chmod 644 "$DOCKER_DIR/.env"
    success "Archivo .env creado con puertos: Web → ${WEB_PORT}, DB Host → ${DB_PORT}"
    return 0
}

# Crear configuración de Apache
create_apache_config() {
    process "Creando configuración de Apache..."
    
    # Configuración diferente para Yii2 Advanced
    if [ "$FRAMEWORK" = "yii2-advanced" ]; then
        cat > "$DOCKER_DIR/000-default.conf" << EOF
<VirtualHost *:80>
    ServerAdmin webmaster@localhost
    DocumentRoot /var/www/html/frontend/web
    <Directory /var/www/html/frontend/web>
        AllowOverride All
        Require all granted
        Options Indexes FollowSymLinks
    </Directory>
    ErrorLog \${APACHE_LOG_DIR}/error.log
    CustomLog \${APACHE_LOG_DIR}/access.log combined
</VirtualHost>
EOF
    else
        cat > "$DOCKER_DIR/000-default.conf" << EOF
<VirtualHost *:80>
    ServerAdmin webmaster@localhost
    DocumentRoot /var/www/html/public
    <Directory /var/www/html>
        AllowOverride All
        Require all granted
        Options Indexes FollowSymLinks
    </Directory>
    ErrorLog \${APACHE_LOG_DIR}/error.log
    CustomLog \${APACHE_LOG_DIR}/access.log combined
</VirtualHost>
EOF
    fi

    if [ ! -f "$DOCKER_DIR/000-default.conf" ]; then
        error "No se pudo crear la configuración de Apache"
        return 1
    fi
    
    chmod 644 "$DOCKER_DIR/000-default.conf"
    success "Configuración de Apache creada"
    return 0
}

# Crear Dockerfile específico para cada framework - CORREGIDO
create_dockerfile() {
    process "Creando Dockerfile para PHP $PHP_VERSION..."
    
    case "$FRAMEWORK" in
        "yii2-basic"|"yii2-advanced")
            create_yii2_dockerfile
            ;;
        "yii3")
            create_yii3_dockerfile
            ;;
        "laravel")
            create_laravel_dockerfile
            ;;
    esac
    
    if [ ! -f "$DOCKER_DIR/Dockerfile" ]; then
        error "No se pudo crear el Dockerfile"
        return 1
    fi
    
    chmod 644 "$DOCKER_DIR/Dockerfile"
    return 0
}

create_yii2_dockerfile() {
    cat > "$DOCKER_DIR/Dockerfile" << 'EOF'
ARG PHP_VERSION=7.4
FROM php:${PHP_VERSION}-apache

# Actualizar lista de paquetes y instalar dependencias
RUN apt-get update && apt-get install -y \
    libpq-dev \
    libzip-dev \
    libpng-dev \
    libjpeg-dev \
    libfreetype6-dev \
    zip \
    unzip \
    git \
    curl \
    gnupg \
    ca-certificates \
    && rm -rf /var/lib/apt/lists/*

# Configurar extensiones de PHP para PHP 7.4 - CONFIGURACIÓN CORREGIDA
RUN docker-php-ext-configure gd --with-freetype --with-jpeg \
    && docker-php-ext-install -j$(nproc) \
        pdo \
        pdo_pgsql \
        pgsql \
        zip \
        gd \
        bcmath \
        opcache \
    && a2enmod rewrite

# Instalar Composer
RUN curl -sS https://getcomposer.org/installer | php -- \
    --install-dir=/usr/local/bin \
    --filename=composer

# Configurar Composer para evitar timeouts
RUN composer config -g process-timeout 1200 && \
    composer config -g repo.packagist composer https://packagist.org

# Instalar extensiones adicionales necesarias para Yii2
RUN docker-php-ext-install mysqli && docker-php-ext-enable mysqli

COPY 000-default.conf /etc/apache2/sites-available/000-default.conf
RUN a2ensite 000-default.conf
RUN chown -R www-data:www-data /var/www/html
WORKDIR /var/www/html

# Configuraciones PHP optimizadas para PHP 7.4
RUN echo "memory_limit = 512M" > /usr/local/etc/php/conf.d/memory.ini && \
    echo "upload_max_filesize = 64M" >> /usr/local/etc/php/conf.d/uploads.ini && \
    echo "post_max_size = 64M" >> /usr/local/etc/php/conf.d/uploads.ini && \
    echo "max_execution_time = 300" >> /usr/local/etc/php/conf.d/timeouts.ini && \
    echo "max_input_time = 120" >> /usr/local/etc/php/conf.d/timeouts.ini

EXPOSE 80
EOF
    success "Dockerfile para Yii2 creado"
}

create_yii3_dockerfile() {
    cat > "$DOCKER_DIR/Dockerfile" << 'EOF'
ARG PHP_VERSION=8.4
FROM php:${PHP_VERSION}-apache

# Actualizar lista de paquetes y instalar dependencias
RUN apt-get update && apt-get install -y \
    libpq-dev \
    libzip-dev \
    libpng-dev \
    libjpeg-dev \
    libfreetype6-dev \
    zip \
    unzip \
    git \
    curl \
    gnupg \
    ca-certificates \
    && rm -rf /var/lib/apt/lists/*

# Configurar extensiones de PHP - CONFIGURACIÓN CORREGIDA
RUN docker-php-ext-configure gd --with-freetype --with-jpeg \
    && docker-php-ext-install -j$(nproc) \
        pdo \
        pdo_pgsql \
        pgsql \
        zip \
        gd \
        bcmath \
        opcache \
    && a2enmod rewrite

# Instalar Composer
RUN curl -sS https://getcomposer.org/installer | php -- \
    --install-dir=/usr/local/bin \
    --filename=composer

# Configurar Composer para evitar timeouts
RUN composer config -g process-timeout 1200 && \
    composer config -g repo.packagist composer https://packagist.org

COPY 000-default.conf /etc/apache2/sites-available/000-default.conf
RUN a2ensite 000-default.conf
RUN chown -R www-data:www-data /var/www/html
WORKDIR /var/www/html

# Configuraciones PHP optimizadas
RUN echo "memory_limit = 512M" > /usr/local/etc/php/conf.d/memory.ini && \
    echo "upload_max_filesize = 128M" >> /usr/local/etc/php/conf.d/uploads.ini && \
    echo "post_max_size = 128M" >> /usr/local/etc/php/conf.d/uploads.ini && \
    echo "max_execution_time = 300" >> /usr/local/etc/php/conf.d/timeouts.ini && \
    echo "max_input_time = 120" >> /usr/local/etc/php/conf.d/timeouts.ini && \
    echo "opcache.enable=1" >> /usr/local/etc/php/conf.d/opcache.ini && \
    echo "opcache.memory_consumption=256" >> /usr/local/etc/php/conf.d/opcache.ini

EXPOSE 80
EOF
    success "Dockerfile para Yii3 creado"
}

create_laravel_dockerfile() {
    cat > "$DOCKER_DIR/Dockerfile" << 'EOF'
ARG PHP_VERSION=8.3
FROM php:${PHP_VERSION}-apache

# Actualizar lista de paquetes y instalar dependencias
RUN apt-get update && apt-get install -y \
    libpq-dev \
    libzip-dev \
    libpng-dev \
    libjpeg-dev \
    libfreetype6-dev \
    zip \
    unzip \
    git \
    curl \
    gnupg \
    ca-certificates \
    && rm -rf /var/lib/apt/lists/*

# Configurar extensiones de PHP - CONFIGURACIÓN CORREGIDA
RUN docker-php-ext-configure gd --with-freetype --with-jpeg \
    && docker-php-ext-install -j$(nproc) \
        pdo \
        pdo_pgsql \
        pgsql \
        zip \
        gd \
        bcmath \
        opcache \
    && a2enmod rewrite

# Instalar Composer
RUN curl -sS https://getcomposer.org/installer | php -- \
    --install-dir=/usr/local/bin \
    --filename=composer

# Configurar Composer para evitar timeouts
RUN composer config -g process-timeout 1200 && \
    composer config -g repo.packagist composer https://packagist.org

# Extensiones adicionales para Laravel
RUN docker-php-ext-install exif && docker-php-ext-enable exif

COPY 000-default.conf /etc/apache2/sites-available/000-default.conf
RUN a2ensite 000-default.conf
RUN chown -R www-data:www-data /var/www/html
WORKDIR /var/www/html

# Configuraciones PHP optimizadas
RUN echo "memory_limit = 512M" > /usr/local/etc/php/conf.d/memory.ini && \
    echo "upload_max_filesize = 128M" >> /usr/local/etc/php/conf.d/uploads.ini && \
    echo "post_max_size = 128M" >> /usr/local/etc/php/conf.d/uploads.ini && \
    echo "max_execution_time = 300" >> /usr/local/etc/php/conf.d/timeouts.ini && \
    echo "max_input_time = 120" >> /usr/local/etc/php/conf.d/timeouts.ini && \
    echo "opcache.enable=1" >> /usr/local/etc/php/conf.d/opcache.ini

EXPOSE 80
EOF
    success "Dockerfile para Laravel creado"
}

# Crear docker-compose.yml MEJORADO
create_docker_compose() {
    process "Creando docker-compose.yml..."
    
    # Configurar volumen según el tipo de proyecto
    local volume_config="./src/${PROJECT_NAME}:/var/www/html"
    
    cat > "$DOCKER_DIR/docker-compose.yml" << EOF
version: '3.8'

services:
  web:
    build:
      context: .
      dockerfile: Dockerfile
      args:
        PHP_VERSION: \${PHP_VERSION}
    ports:
      - "\${WEB_PORT}:80"
    volumes:
      - ${volume_config}
      - ./apache-logs:/var/log/apache2
    depends_on:
      db:
        condition: service_healthy
    networks:
      - backend-network
    env_file:
      - .env
    container_name: ${PROJECT_NAME}-web
    restart: unless-stopped
    dns:
      - 8.8.8.8
      - 8.8.4.4
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:80"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

  db:
    image: postgres:15
    environment:
      POSTGRES_DB: \${POSTGRES_DB}
      POSTGRES_USER: \${POSTGRES_USER}
      POSTGRES_PASSWORD: \${POSTGRES_PASSWORD}
    volumes:
      - pgdata:/var/lib/postgresql/data
      - ./postgres-backups:/backups
    networks:
      - backend-network
    container_name: ${PROJECT_NAME}-db
    restart: unless-stopped
    ports:
      - "\${DB_EXTERNAL_PORT}:5432"
    dns:
      - 8.8.8.8
      - 8.8.4.4
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U \${POSTGRES_USER} -d \${POSTGRES_DB}"]
      interval: 10s
      timeout: 5s
      retries: 10
      start_period: 30s

networks:
  backend-network:
    driver: bridge

volumes:
  pgdata:
    driver: local
EOF

    if [ ! -f "$DOCKER_DIR/docker-compose.yml" ]; then
        error "No se pudo crear el archivo docker-compose.yml"
        return 1
    fi
    
    chmod 644 "$DOCKER_DIR/docker-compose.yml"
    success "Archivo docker-compose.yml creado"
    return 0
}

# Función para ejecutar comandos en el directorio docker
run_in_docker_dir() {
    local cmd="$1"
    
    if [ ! -d "$DOCKER_DIR" ]; then
        error "Directorio $DOCKER_DIR no encontrado"
        return 1
    fi
    
    # Verificar permisos antes de continuar
    if ! check_and_fix_permissions "$DOCKER_DIR"; then
        error "No se puede acceder a $DOCKER_DIR por problemas de permisos"
        return 1
    fi
    
    cd "$DOCKER_DIR" || { error "No se puede acceder al directorio $DOCKER_DIR"; return 1; }
    eval "$cmd"
    local result=$?
    cd "$CURRENT_DIR" || return 1
    return $result
}

# Función MEJORADA para esperar a que PostgreSQL esté listo
wait_for_postgres() {
    process "Esperando a que PostgreSQL esté listo..."
    local max_attempts=30
    local attempt=1
    
    while [ $attempt -le $max_attempts ]; do
        if run_in_docker_dir "docker-compose exec -T db pg_isready -U postgres > /dev/null 2>&1"; then
            success "PostgreSQL está listo después de $attempt intentos"
            
            # Esperar un poco más para asegurar que esté completamente listo
            sleep 5
            
            # Verificar que la base de datos específica existe, si no crearla
            process "Verificando base de datos ${PROJECT_NAME}_db..."
            if ! run_in_docker_dir "docker-compose exec -T db psql -U postgres -d ${PROJECT_NAME}_db -c 'SELECT 1' > /dev/null 2>&1"; then
                process "Creando base de datos ${PROJECT_NAME}_db..."
                if run_in_docker_dir "docker-compose exec -T db createdb -U postgres ${PROJECT_NAME}_db"; then
                    success "Base de datos ${PROJECT_NAME}_db creada exitosamente"
                else
                    error "Error creando la base de datos ${PROJECT_NAME}_db"
                    return 1
                fi
            else
                success "Base de datos ${PROJECT_NAME}_db ya existe"
            fi
            
            sleep 2
            return 0
        fi
        
        if [ $attempt -eq 1 ]; then
            process "Esperando a que PostgreSQL se inicialice..."
        else
            process "Intento $attempt/$max_attempts - PostgreSQL aún no está listo..."
        fi
        
        sleep 3
        attempt=$((attempt + 1))
    done
    
    error "PostgreSQL no está respondiendo después de $max_attempts intentos"
    run_in_docker_dir 'docker-compose logs db'
    return 1
}

# Construir y levantar contenedores CON MEJOR MANEJO DE ERRORES
build_docker_containers() {
    process "Construyendo contenedores Docker..."
    
    if [ ! -d "$DOCKER_DIR" ]; then
        error "El directorio $DOCKER_DIR no existe. No se pueden construir los contenedores."
        return 1
    fi
    
    # Verificar permisos antes de continuar
    if ! check_and_fix_permissions "$DOCKER_DIR"; then
        error "No se pueden construir los contenedores por problemas de permisos"
        return 1
    fi
    
    # Parar contenedores existentes
    process "Deteniendo contenedores existentes..."
    if ! run_in_docker_dir 'docker-compose down 2>/dev/null'; then
        warning "No se pudieron detener contenedores existentes (puede ser normal si no hay contenedores)"
    fi
    
    # Construir imágenes con mejor manejo de errores
    process "Construyendo imágenes Docker..."
    
    # Primero intentar construir sin cache para detectar errores temprano
    if run_in_docker_dir "docker-compose build --no-cache --build-arg PHP_VERSION=${PHP_VERSION} 2>&1 | tee build.log"; then
        success "Imágenes construidas exitosamente sin cache"
    else
        error "Error construyendo las imágenes Docker sin cache"
        process "Analizando errores de construcción..."
        
        # Mostrar los últimos errores del log
        if [ -f "$DOCKER_DIR/build.log" ]; then
            error "Últimas líneas del log de construcción:"
            tail -20 "$DOCKER_DIR/build.log" | while read -r line; do
                echo "  $line"
            done
        fi
        
        # Intentar con cache como fallback
        warning "Intentando construcción con cache..."
        if ! run_in_docker_dir "docker-compose build --build-arg PHP_VERSION=${PHP_VERSION}"; then
            error "Error crítico en la construcción de imágenes Docker"
            return 1
        fi
    fi
    
    # Levantar contenedores
    process "Iniciando contenedores..."
    if ! run_in_docker_dir 'docker-compose up -d'; then
        error "Error iniciando contenedores"
        run_in_docker_dir 'docker-compose logs'
        return 1
    fi
    
    # Esperar a que PostgreSQL esté listo
    if ! wait_for_postgres; then
        error "La base de datos no se inicializó correctamente"
        return 1
    fi
    
    # Verificar estado de los contenedores
    process "Verificando estado de los contenedores..."
    if run_in_docker_dir 'docker-compose ps | grep -q "Up"'; then
        success "Contenedores Docker listos y ejecutándose"
        return 0
    else
        error "Los contenedores no se iniciaron correctamente"
        run_in_docker_dir 'docker-compose ps'
        return 1
    fi
}

# Ejecutar comandos en el contenedor web con reintentos MEJORADO
exec_in_web() {
    local cmd="$1"
    local max_attempts=3
    local attempt=1
    
    sleep 10
    
    while [ $attempt -le $max_attempts ]; do
        process "Ejecutando comando (intento $attempt/$max_attempts)..."
        
        if run_in_docker_dir "docker-compose exec -T web bash -c \"$cmd\""; then
            return 0
        fi
        
        if [ $attempt -lt $max_attempts ]; then
            warning "Intento $attempt falló. Reintentando en 10 segundos..."
            sleep 10
        fi
        
        attempt=$((attempt + 1))
    done
    
    error "Error ejecutando comando después de $max_attempts intentos: $cmd"
    return 1
}

# Función MEJORADA para crear proyectos con Composer
create_project_with_composer() {
    local framework_cmd="$1"
    local project_name="$2"
    local install_dir="$3"
    
    process "Descargando proyecto $framework_cmd..."
    
    exec_in_web "composer config -g process-timeout 1200"
    exec_in_web "composer config -g repo.packagist composer https://packagist.org"
    exec_in_web "composer clear-cache"
    
    # Opciones específicas por framework
    if [[ "$FRAMEWORK" == yii2* ]]; then
        local composer_options=(
            "--prefer-dist --no-progress --no-interaction"
            "--prefer-dist --no-dev --no-progress --no-interaction"
            "--prefer-source --no-progress --no-interaction"
        )
    else
        local composer_options=(
            "--prefer-dist --no-dev --no-progress --no-interaction"
            "--prefer-dist --no-progress --no-interaction"
            "--prefer-source --no-progress --no-interaction"
        )
    fi
    
    for options in "${composer_options[@]}"; do
        process "Intentando con opciones: $options"
        
        # Limpiar directorio temporal si existe
        exec_in_web "rm -rf /tmp/${project_name} 2>/dev/null || true"
        
        if exec_in_web "composer create-project $options $framework_cmd /tmp/${project_name}"; then
            # VERIFICAR que el proyecto se creó correctamente
            if exec_in_web "[ -d /tmp/${project_name} ] && [ -f /tmp/${project_name}/composer.json ]"; then
                success "Proyecto descargado correctamente"
                
                # Mover a la ubicación final
                exec_in_web "rm -rf ${install_dir}/* 2>/dev/null || true"
                exec_in_web "sh -c 'mv /tmp/${project_name}/* ${install_dir}/ 2>/dev/null || true'"
                exec_in_web "sh -c 'mv /tmp/${project_name}/.* ${install_dir}/ 2>/dev/null || true'"
                exec_in_web "rm -rf /tmp/${project_name}"
                
                # Instalar dependencias si es necesario
                if exec_in_web "[ ! -d ${install_dir}/vendor ] && [ -f ${install_dir}/composer.json ]"; then
                    process "Instalando dependencias..."
                    if exec_in_web "cd ${install_dir} && composer install --no-dev --no-progress --no-interaction"; then
                        success "Dependencias instaladas correctamente"
                    else
                        warning "No se pudieron instalar las dependencias automáticamente"
                    fi
                fi
                
                return 0
            fi
        fi
        
        warning "Intento falló con opciones: $options"
        exec_in_web "rm -rf /tmp/${project_name} 2>/dev/null || true"
    done
    
    return 1
}

# Crear proyecto Yii2 Basic
create_yii2_basic_project() {
    process "Creando proyecto Yii2 Basic: ${PROJECT_NAME}"
    
    if ! create_project_with_composer "yiisoft/yii2-app-basic" "${PROJECT_NAME}" "/var/www/html"; then
        error "Error al crear proyecto Yii2 Basic después de múltiples intentos"
        return 1
    fi
    
    # Configuraciones adicionales para Yii2 Basic
    exec_in_web "chown -R www-data:www-data /var/www/html"
    exec_in_web "chmod -R 755 /var/www/html/runtime"
    exec_in_web "chmod -R 755 /var/www/html/web/assets"
    
    success "Proyecto Yii2 Basic '${PROJECT_NAME}' creado correctamente"
    return 0
}

# Crear proyecto Yii2 Advanced
create_yii2_advanced_project() {
    process "Creando proyecto Yii2 Advanced: ${PROJECT_NAME}"
    
    if ! create_project_with_composer "yiisoft/yii2-app-advanced" "${PROJECT_NAME}" "/var/www/html"; then
        error "Error al crear proyecto Yii2 Advanced después de múltiples intentos"
        return 1
    fi
    
    # Configuraciones adicionales para Yii2 Advanced
    exec_in_web "chown -R www-data:www-data /var/www/html"
    exec_in_web "chmod -R 755 /var/www/html/frontend/runtime"
    exec_in_web "chmod -R 755 /var/www/html/backend/runtime"
    exec_in_web "chmod -R 755 /var/www/html/frontend/web/assets"
    exec_in_web "chmod -R 755 /var/www/html/backend/web/assets"
    
    # Inicializar el proyecto advanced
    process "Inicializando Yii2 Advanced..."
    if exec_in_web "cd /var/www/html && php init --env=Development --overwrite=All"; then
        success "Yii2 Advanced inicializado correctamente"
    else
        warning "No se pudo inicializar Yii2 Advanced automáticamente"
    fi
    
    success "Proyecto Yii2 Advanced '${PROJECT_NAME}' creado correctamente"
    return 0
}

# Crear proyecto Yii3
create_yii3_project() {
    process "Creando proyecto Yii3: ${PROJECT_NAME}"
    
    if ! create_project_with_composer "yiisoft/yii-project-template" "${PROJECT_NAME}" "/var/www/html"; then
        error "Error al crear proyecto Yii3 después de múltiples intentos"
        return 1
    fi
    
    exec_in_web "chown -R www-data:www-data /var/www/html"
    
    success "Proyecto Yii3 '${PROJECT_NAME}' creado correctamente"
    return 0
}

# Crear proyecto Laravel
create_laravel_project() {
    process "Creando proyecto Laravel: ${PROJECT_NAME}"
    
    if ! create_project_with_composer "laravel/laravel" "${PROJECT_NAME}" "/var/www/html"; then
        error "Error al crear proyecto Laravel después de múltiples intentos"
        return 1
    fi
    
    exec_in_web "chown -R www-data:www-data /var/www/html"
    exec_in_web "chmod -R 775 /var/www/html/storage"
    exec_in_web "chmod -R 775 /var/www/html/bootstrap/cache"
    
    # Configurar .env para Laravel
    process "Configurando Laravel..."
    exec_in_web "cd /var/www/html && cp .env.example .env 2>/dev/null || true"
    
    exec_in_web "cd /var/www/html && cat > .env << 'ENDFILE'
APP_NAME=${PROJECT_NAME}
APP_ENV=local
APP_KEY=
APP_DEBUG=true
APP_URL=http://localhost:${WEB_PORT}

DB_CONNECTION=pgsql
DB_HOST=db
DB_PORT=5432
DB_DATABASE=${PROJECT_NAME}_db
DB_USERNAME=postgres
DB_PASSWORD=password

LOG_CHANNEL=stack
LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug

QUEUE_CONNECTION=sync
SESSION_DRIVER=file
SESSION_LIFETIME=120

MEMCACHED_HOST=127.0.0.1

REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379

MAIL_MAILER=smtp
MAIL_HOST=mailhog
MAIL_PORT=1025
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS=null
MAIL_FROM_NAME=\"\${APP_NAME}\"
ENDFILE"
    
    exec_in_web "cd /var/www/html && php artisan key:generate"
    
    success "Proyecto Laravel '${PROJECT_NAME}' creado correctamente"
    return 0
}

# Crear el proyecto según el framework seleccionado
create_framework_project() {
    process "Iniciando creación del proyecto ${PROJECT_NAME} con ${FRAMEWORK}..."
    
    process "Probando conexión a internet desde el contenedor..."
    if ! exec_in_web "curl -s --connect-timeout 30 https://packagist.org > /dev/null"; then
        warning "El contenedor no puede conectarse a Packagist. Esto puede causar problemas."
        exec_in_web "cat /etc/resolv.conf"
    else
        success "Conexión a internet verificada desde el contenedor"
    fi
    
    case "$FRAMEWORK" in
        "yii2-basic")
            create_yii2_basic_project
            ;;
        "yii2-advanced")
            create_yii2_advanced_project
            ;;
        "yii3")
            create_yii3_project
            ;;
        "laravel")
            create_laravel_project
            ;;
        *)
            error "Framework no válido: $FRAMEWORK"
            return 1
            ;;
    esac
}

# Configuración MEJORADA de base de datos
configure_database() {
    process "Configurando base de datos para $FRAMEWORK..."
    
    sleep 10
    
    case "$FRAMEWORK" in
        "yii2-basic")
            configure_yii2_basic_db
            ;;
        "yii2-advanced")
            configure_yii2_advanced_db
            ;;
        "yii3")
            configure_yii3_db
            ;;
        "laravel")
            configure_laravel_db
            ;;
    esac
}

configure_laravel_db() {
    process "Configurando base de datos para Laravel..."
    
    process "Verificando conexión a la base de datos..."
    if exec_in_web "cd /var/www/html && php -r \"
try {
    \\\$pdo = new PDO('pgsql:host=db;port=5432;dbname=${PROJECT_NAME}_db', 'postgres', 'password');
    \\\$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    echo 'Conexión a BD exitosa' . PHP_EOL;
} catch (PDOException \\\$e) {
    echo 'Error de conexión: ' . \\\$e->getMessage() . PHP_EOL;
    exit(1);
}
\""; then
        success "Conexión a la base de datos verificada"
    else
        error "No se pudo conectar a la base de datos"
        return 1
    fi
    
    process "Ejecutando migraciones de Laravel..."
    if exec_in_web "cd /var/www/html && php artisan migrate --force"; then
        success "Migraciones de Laravel ejecutadas correctamente"
    else
        warning "Las migraciones de Laravel fallaron o no son necesarias"
    fi
    return 0
}

configure_yii3_db() {
    process "Configurando base de datos para Yii3..."
    if exec_in_web "cd /var/www/html && php ./yii migrate/up --interactive=0"; then
        success "Migraciones de Yii3 ejecutadas correctamente"
    else
        warning "Las migraciones de Yii3 fallaron o no son necesarias"
    fi
    return 0
}

# Configuración MEJORADA para Yii2 Basic
configure_yii2_basic_db() {
    process "Configurando base de datos para Yii2 Basic..."
    
    # Primero verificar que Yii2 esté instalado correctamente
    process "Verificando instalación de Yii2 Basic..."
    if ! exec_in_web "[ -f /var/www/html/vendor/yiisoft/yii2/Yii.php ]"; then
        error "Yii2 Basic no está instalado correctamente. No se puede configurar la base de datos."
        info "Instala las dependencias manualmente:"
        echo "  cd $DOCKER_DIR && docker-compose exec web composer install"
        return 1
    fi
    
    process "Configurando conexión a base de datos para Yii2 Basic..."
    exec_in_web "cd /var/www/html && cp config/db.php config/db.php.backup 2>/dev/null || true"
    
    exec_in_web "cd /var/www/html && cat > config/db.php << 'ENDFILE'
<?php

return [
    'class' => 'yii\db\Connection',
    'dsn' => 'pgsql:host=db;port=5432;dbname=${PROJECT_NAME}_db',
    'username' => 'postgres',
    'password' => 'password',
    'charset' => 'utf8',
];
ENDFILE"
    
    # Deshabilitar GII en producción o si no está instalado
    exec_in_web "cd /var/www/html && cp config/web.php config/web.php.backup 2>/dev/null || true"
    exec_in_web "cd /var/www/html && sed -i \"s/'modules' => \[/'modules' => \[\\n    /*\\n    'gii' => [\\n        'class' => 'yii\\\gii\\\Module',\\n    ],\\n    */\\n/g\" config/web.php 2>/dev/null || true"
    
    process "Intentando ejecutar migraciones de Yii2 Basic..."
    if exec_in_web "cd /var/www/html && php yii migrate/up --interactive=0"; then
        success "Migraciones de Yii2 Basic ejecutadas correctamente"
    else
        warning "Las migraciones de Yii2 Basic fallaron o no son necesarias"
    fi
    
    return 0
}

# Configuración para Yii2 Advanced
configure_yii2_advanced_db() {
    process "Configurando base de datos para Yii2 Advanced..."
    
    # Verificar instalación
    if ! exec_in_web "[ -f /var/www/html/vendor/yiisoft/yii2/Yii.php ]"; then
        error "Yii2 Advanced no está instalado correctamente."
        return 1
    fi
    
    # Configurar base de datos común
    exec_in_web "cd /var/www/html && cat > common/config/main-local.php << 'ENDFILE'
<?php
return [
    'components' => [
        'db' => [
            'class' => 'yii\db\Connection',
            'dsn' => 'pgsql:host=db;port=5432;dbname=${PROJECT_NAME}_db',
            'username' => 'postgres',
            'password' => 'password',
            'charset' => 'utf8',
        ],
    ],
];
ENDFILE"
    
    # Configurar base de datos para frontend y backend
    for app in frontend backend; do
        exec_in_web "cd /var/www/html && cat > ${app}/config/main-local.php << 'ENDFILE'
<?php
return [
    'components' => [
        'db' => [
            'class' => 'yii\db\Connection',
            'dsn' => 'pgsql:host=db;port=5432;dbname=${PROJECT_NAME}_db',
            'username' => 'postgres',
            'password' => 'password',
            'charset' => 'utf8',
        ],
    ],
];
ENDFILE"
    done
    
    process "Ejecutando migraciones de Yii2 Advanced..."
    if exec_in_web "cd /var/www/html && php yii migrate/up --interactive=0"; then
        success "Migraciones de Yii2 Advanced ejecutadas correctamente"
    else
        warning "Las migraciones de Yii2 Advanced fallaron o no son necesarias"
    fi
    
    return 0
}

# Mostrar resumen final MEJORADO
show_final_summary() {
    echo ""
    success "🎉 ¡PROYECTO CREADO EXITOSAMENTE!"
    echo "=================================="
    echo ""
    info "📊 RESUMEN FINAL:"
    echo "-----------------"
    echo "  📂 Proyecto: $PROJECT_NAME"
    echo "  🚀 Framework: $FRAMEWORK"
    echo "  🐘 PHP: $PHP_VERSION"
    echo "  🌐 URL: http://localhost:${WEB_PORT}"
    echo "  🗄️  Base de datos: localhost:${DB_PORT}"
    echo "  📁 Directorio: $DOCKER_DIR/src/$PROJECT_NAME/"
    echo ""
    
    # Información específica por framework
    case "$FRAMEWORK" in
        "yii2-advanced")
            info "🌐 URLs de Yii2 Advanced:"
            echo "  Frontend: http://localhost:${WEB_PORT}"
            echo "  Backend: http://localhost:${WEB_PORT}/backend/web"
            ;;
        *)
            info "🌐 URL de la aplicación:"
            echo "  http://localhost:${WEB_PORT}"
            ;;
    esac
    echo ""
    
    info "🚀 COMANDOS ÚTILES:"
    echo "------------------"
    echo "  Iniciar proyecto: cd $DOCKER_DIR && docker-compose up -d"
    echo "  Detener proyecto: cd $DOCKER_DIR && docker-compose down"
    echo "  Ver logs: cd $DOCKER_DIR && docker-compose logs"
    echo "  Acceder a la consola: cd $DOCKER_DIR && docker-compose exec web bash"
    echo ""
    
    info "🔗 CONEXIÓN A LA BASE DE DATOS:"
    echo "------------------------------"
    echo "  Host: localhost"
    echo "  Puerto: ${DB_PORT}"
    echo "  Base de datos: ${PROJECT_NAME}_db"
    echo "  Usuario: postgres"
    echo "  Contraseña: password"
    echo ""
    
    # Instrucciones específicas por framework
    case "$FRAMEWORK" in
        "yii2-basic"|"yii2-advanced")
            info "🔧 INSTRUCCIONES ESPECÍFICAS PARA YII2:"
            echo "-------------------------------------"
            echo "  Si hay problemas con dependencias:"
            echo "    cd $DOCKER_DIR && docker-compose exec web composer install"
            if [ "$FRAMEWORK" = "yii2-advanced" ]; then
                echo "  Para inicializar el proyecto:"
                echo "    cd $DOCKER_DIR && docker-compose exec web php init"
            fi
            echo "  Para ejecutar migraciones:"
            echo "    cd $DOCKER_DIR && docker-compose exec web php yii migrate/up --interactive=0"
            echo "  Para instalar módulos de desarrollo:"
            echo "    cd $DOCKER_DIR && docker-compose exec web composer require --dev yiisoft/yii2-gii"
            echo "    cd $DOCKER_DIR && docker-compose exec web composer require --dev yiisoft/yii2-debug"
            echo ""
            ;;
        "yii3")
            info "🔧 INSTRUCCIONES ESPECÍFICAS PARA YII3:"
            echo "-------------------------------------"
            echo "  Para ejecutar migraciones: cd $DOCKER_DIR && docker-compose exec web php ./yii migrate/up --interactive=0"
            echo ""
            ;;
        "laravel")
            info "🔧 INSTRUCCIONES ESPECÍFICAS PARA LARAVEL:"
            echo "----------------------------------------"
            echo "  Para ejecutar migraciones: cd $DOCKER_DIR && docker-compose exec web php artisan migrate"
            echo "  Para generar key: cd $DOCKER_DIR && docker-compose exec web php artisan key:generate"
            echo ""
            ;;
    esac
    
    info "📝 PRÓXIMOS PASOS:"
    echo "-----------------"
    echo "  1. Accede a la URL mostrada arriba para ver tu aplicación"
    echo "  2. Configura la base de datos si es necesario"
    echo "  3. ¡Comienza a desarrollar!"
    echo ""
}

# Función principal
main() {
    clear
    echo "========================================"
    echo "  GENERADOR DE PROYECTOS CON DOCKER v1.1"
    echo "========================================"
    echo ""
    
    check_docker_installation
    check_docker_permissions
    check_internet_connection
    
    select_framework
    
    get_project_name
    
    if ! create_directory_structure; then
        error "Error creando la estructura de directorios"
        exit 1
    fi
    
    if ! create_env_file; then
        error "Error creando el archivo .env"
        exit 1
    fi
    
    if ! create_apache_config; then
        error "Error creando la configuración de Apache"
        exit 1
    fi
    
    if ! create_dockerfile; then
        error "Error creando el Dockerfile"
        exit 1
    fi
    
    if ! create_docker_compose; then
        error "Error creando docker-compose.yml"
        exit 1
    fi
    
    if build_docker_containers; then
        process "Esperando inicialización completa de servicios..."
        sleep 20
        
        if create_framework_project; then
            process "Configurando la aplicación..."
            if configure_database; then
                show_final_summary
            else
                warning "Proyecto creado pero la configuración de BD tuvo problemas menores"
                show_final_summary
            fi
        else
            error "Error al crear el proyecto del framework"
            exit 1
        fi
    else
        error "Error al construir los contenedores Docker"
        exit 1
    fi
}

if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
    main "$@"
fi
downloadDownload PNG downloadDownload JPEG downloadDownload SVG

Tip: You can change the style, width & colours of the snippet with the inspect tool before clicking Download!

Click to optimize width for Twitter