← Volver al listado de tecnologías

Proceso Completo de Publicación en App Store y Google Play

Por: Artiko
defoldpublishingapp-storegoogle-playasolanzamiento

Proceso Completo de Publicación en App Store y Google Play

Publicar tu juego en las tiendas móviles es un proceso complejo que requiere preparación cuidadosa. Esta guía te llevará paso a paso por todo el proceso.

Preparación Pre-Lanzamiento

1. Checklist de Preparación

Lista de Verificación Técnica

-- pre_launch_checklist.lua
local M = {}

M.TECHNICAL_CHECKLIST = {
    builds = {
        {item = "Build de release firmado correctamente", completed = false},
        {item = "Todas las extensiones nativas funcionando", completed = false},
        {item = "Sin referencias a desarrollo/debug", completed = false},
        {item = "Iconos en todas las resoluciones requeridas", completed = false},
        {item = "Screenshots de todas las pantallas", completed = false},
        {item = "Configuración de permisos correcta", completed = false}
    },

    compliance = {
        {item = "Política de privacidad implementada", completed = false},
        {item = "Términos de servicio creados", completed = false},
        {item = "Cumplimiento COPPA (menores)", completed = false},
        {item = "Cumplimiento GDPR (Europa)", completed = false},
        {item = "Certificados de edad/rating", completed = false}
    },

    monetization = {
        {item = "IAP configurados y testeados", completed = false},
        {item = "Ads integrados y funcionando", completed = false},
        {item = "Analytics implementado", completed = false},
        {item = "Crash reporting activo", completed = false},
        {item = "Remote config configurado", completed = false}
    },

    testing = {
        {item = "Testing en múltiples dispositivos", completed = false},
        {item = "Testing de performance", completed = false},
        {item = "Testing de memoria", completed = false},
        {item = "Testing de batería", completed = false},
        {item = "Testing de conectividad", completed = false}
    }
}

function M.check_build_requirements(self)
    local issues = {}

    -- Verificar configuración de build
    if not self:has_release_keystore() then
        table.insert(issues, "Falta keystore para release")
    end

    if not self:has_all_required_icons() then
        table.insert(issues, "Faltan iconos en algunas resoluciones")
    end

    if self:has_debug_code() then
        table.insert(issues, "Código de debug encontrado en release")
    end

    if not self:has_privacy_policy() then
        table.insert(issues, "Falta política de privacidad")
    end

    return issues
end

function M.generate_build_report(self)
    local report = {
        timestamp = os.time(),
        version = sys.get_config("project.version"),
        platform = sys.get_sys_info().system_name,
        issues = self:check_build_requirements(),
        file_sizes = self:get_build_sizes(),
        permissions = self:get_required_permissions()
    }

    return report
end

return M

2. Asset Preparation

Generador de Assets para Tiendas

-- store_assets_generator.lua
local M = {}

M.REQUIRED_ASSETS = {
    ios = {
        icons = {
            {size = "20x20", scale = "2x", filename = "Icon-40.png"},
            {size = "20x20", scale = "3x", filename = "Icon-60.png"},
            {size = "29x29", scale = "2x", filename = "Icon-58.png"},
            {size = "29x29", scale = "3x", filename = "Icon-87.png"},
            {size = "40x40", scale = "2x", filename = "Icon-80.png"},
            {size = "40x40", scale = "3x", filename = "Icon-120.png"},
            {size = "60x60", scale = "2x", filename = "Icon-120.png"},
            {size = "60x60", scale = "3x", filename = "Icon-180.png"},
            {size = "1024x1024", scale = "1x", filename = "Icon-1024.png"}
        },
        screenshots = {
            {device = "iPhone 6.5", size = "1242x2688", count = 3},
            {device = "iPhone 6.1", size = "828x1792", count = 3},
            {device = "iPhone 5.5", size = "1242x2208", count = 3},
            {device = "iPad Pro 12.9", size = "2048x2732", count = 3}
        }
    },

    android = {
        icons = {
            {density = "mdpi", size = "48x48", folder = "drawable-mdpi"},
            {density = "hdpi", size = "72x72", folder = "drawable-hdpi"},
            {density = "xhdpi", size = "96x96", folder = "drawable-xhdpi"},
            {density = "xxhdpi", size = "144x144", folder = "drawable-xxhdpi"},
            {density = "xxxhdpi", size = "192x192", folder = "drawable-xxxhdpi"}
        },
        screenshots = {
            {type = "phone", size = "1080x1920", count = 8},
            {type = "tablet_7", size = "1200x1920", count = 8},
            {type = "tablet_10", size = "1600x2560", count = 8}
        },
        feature_graphic = {
            size = "1024x500",
            format = "PNG or JPG"
        }
    }
}

function M.validate_assets(platform)
    local required = M.REQUIRED_ASSETS[platform]
    local missing = {}

    -- Verificar iconos
    for _, icon in ipairs(required.icons) do
        local path = self:get_icon_path(platform, icon)
        if not self:file_exists(path) then
            table.insert(missing, "Icon: " .. icon.filename or icon.size)
        end
    end

    -- Verificar screenshots
    for _, screenshot_type in ipairs(required.screenshots) do
        local screenshot_count = self:count_screenshots(platform, screenshot_type)
        if screenshot_count < screenshot_type.count then
            table.insert(missing, string.format("Screenshots %s: %d/%d",
                screenshot_type.device or screenshot_type.type,
                screenshot_count, screenshot_type.count))
        end
    end

    return missing
end

function M.generate_store_listing_template(platform, app_info)
    if platform == "ios" then
        return M.generate_ios_listing(app_info)
    elseif platform == "android" then
        return M.generate_android_listing(app_info)
    end
end

function M.generate_ios_listing(app_info)
    return {
        name = app_info.name,
        subtitle = app_info.subtitle,
        description = M.format_ios_description(app_info.description),
        keywords = table.concat(app_info.keywords, ","),
        support_url = app_info.support_url,
        marketing_url = app_info.marketing_url,
        privacy_policy_url = app_info.privacy_policy_url,
        category = app_info.category,
        age_rating = app_info.age_rating
    }
end

function M.generate_android_listing(app_info)
    return {
        title = app_info.name,
        short_description = app_info.short_description,
        full_description = M.format_android_description(app_info.description),
        category = app_info.category,
        content_rating = app_info.content_rating,
        website = app_info.website,
        email = app_info.contact_email,
        privacy_policy = app_info.privacy_policy_url
    }
end

return M

Configuración de App Store (iOS)

1. App Store Connect Setup

Configuración de Aplicación iOS

-- ios_store_config.lua
local M = {}

M.APP_STORE_CONFIG = {
    app_information = {
        name = "Mi Juego Increíble",
        bundle_id = "com.miestudio.mijuego",
        sku = "MIJUEGO2024",
        primary_language = "Spanish (Spain)",
        category = "Games",
        secondary_category = "Action"
    },

    pricing_and_availability = {
        price_tier = "Free",
        availability = {
            worldwide = true,
            specific_countries = {}
        },
        app_store_visibility = true,
        pre_order = false
    },

    version_information = {
        version = "1.0",
        copyright = "© 2024 Mi Estudio",
        age_rating = {
            violence = "None",
            profanity = "None",
            sexual_content = "None",
            horror = "None",
            gambling = "None"
        }
    },

    review_information = {
        contact_email = "[email protected]",
        contact_phone = "+1234567890",
        demo_account = {
            username = "[email protected]",
            password = "DemoPassword123"
        },
        notes = "Este juego incluye compras in-app opcionales."
    }
}

M.LOCALIZATIONS = {
    ["es-ES"] = {
        name = "Mi Juego Increíble",
        subtitle = "Aventura épica móvil",
        description = [[
¡Embárcate en una aventura épica en este emocionante juego de acción!

CARACTERÍSTICAS PRINCIPALES:
• Más de 100 niveles desafiantes
• Gráficos impresionantes y efectos visuales
• Sistema de progresión profundo
• Multijugador en línea
• Eventos especiales semanales

GAMEPLAY ADICTIVO:
Domina controles intuitivos mientras navegas por mundos peligrosos llenos de enemigos y tesoros. Mejora tu personaje, desbloquea nuevas habilidades y compite con jugadores de todo el mundo.

CARACTERÍSTICAS SOCIALES:
• Conecta con Game Center
• Compite en tablas de clasificación
• Desbloquea logros únicos
• Comparte tus mejores momentos

ACTUALIZACIONES REGULARES:
Agregamos constantemente nuevo contenido, niveles y características basadas en los comentarios de los jugadores.

¡Descarga ahora y únete a millones de jugadores en esta aventura épica!
        ]],
        keywords = "juego,acción,aventura,multijugador,móvil,gratis",
        release_notes = "¡Primera versión! Incluye 100 niveles, modo multijugador y características sociales."
    },

    ["en-US"] = {
        name = "My Amazing Game",
        subtitle = "Epic mobile adventure",
        description = "English description here...",
        keywords = "game,action,adventure,multiplayer,mobile,free",
        release_notes = "Initial release! Features 100 levels, multiplayer mode, and social features."
    }
}

function M.generate_app_store_metadata()
    local metadata = {
        config = M.APP_STORE_CONFIG,
        localizations = M.LOCALIZATIONS,
        build_info = {
            version = sys.get_config("project.version"),
            build_number = os.time(),
            min_ios_version = "12.0"
        }
    }

    return metadata
end

function M.validate_app_store_submission()
    local issues = {}

    -- Verificar información requerida
    if not M.APP_STORE_CONFIG.app_information.name then
        table.insert(issues, "App name is required")
    end

    if string.len(M.APP_STORE_CONFIG.app_information.name) > 50 then
        table.insert(issues, "App name exceeds 50 characters")
    end

    -- Verificar descripción
    local desc = M.LOCALIZATIONS["es-ES"].description
    if string.len(desc) > 4000 then
        table.insert(issues, "Description exceeds 4000 characters")
    end

    -- Verificar keywords
    local keywords = M.LOCALIZATIONS["es-ES"].keywords
    if string.len(keywords) > 100 then
        table.insert(issues, "Keywords exceed 100 characters")
    end

    return issues
end

return M

2. Build Signing y Provisioning

Configuración de Certificados

#!/bin/bash
# ios_signing_setup.sh

echo "Setting up iOS code signing..."

# Verificar herramientas
if ! command -v xcodebuild &> /dev/null; then
    echo "Error: Xcode command line tools not installed"
    exit 1
fi

# Variables
TEAM_ID="ABCD123456"
BUNDLE_ID="com.miestudio.mijuego"
APP_NAME="Mi Juego Increíble"

# Crear certificado de desarrollo
echo "Creating development certificate..."
security create-keypair -a 2048 -t RSA -f PKCS12 -k login.keychain

# Configurar provisioning profile
echo "Setting up provisioning profile..."
cat > ExportOptions.plist << EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>teamID</key>
    <string>$TEAM_ID</string>
    <key>method</key>
    <string>app-store</string>
    <key>destination</key>
    <string>export</string>
    <key>uploadBitcode</key>
    <true/>
    <key>uploadSymbols</key>
    <true/>
    <key>compileBitcode</key>
    <true/>
    <key>signingStyle</key>
    <string>automatic</string>
</dict>
</plist>
EOF

# Build para App Store
echo "Building for App Store..."
xcodebuild -workspace "YourGame.xcworkspace" \
           -scheme "YourGame" \
           -configuration Release \
           -destination generic/platform=iOS \
           -archivePath "build/YourGame.xcarchive" \
           archive

# Export para App Store
echo "Exporting for App Store..."
xcodebuild -exportArchive \
           -archivePath "build/YourGame.xcarchive" \
           -exportPath "build/AppStore" \
           -exportOptionsPlist ExportOptions.plist

echo "iOS build complete!"

Configuración de Google Play Store

1. Google Play Console Setup

Configuración de Aplicación Android

-- android_store_config.lua
local M = {}

M.PLAY_STORE_CONFIG = {
    store_listing = {
        title = "Mi Juego Increíble",
        short_description = "Aventura épica móvil con más de 100 niveles desafiantes",
        full_description = [[
¡Embárcate en una aventura épica en este emocionante juego de acción!

🎮 CARACTERÍSTICAS PRINCIPALES
• Más de 100 niveles únicos y desafiantes
• Gráficos 3D impresionantes optimizados para móviles
• Sistema de progresión profundo con mejoras de personaje
• Modo multijugador en tiempo real
• Eventos especiales y desafíos semanales
• Completamente GRATIS para jugar

⚡ GAMEPLAY ADICTIVO
Domina controles táctiles intuitivos mientras exploras mundos peligrosos llenos de enemigos, tesoros ocultos y secretos por descubrir. Mejora tu personaje, desbloquea habilidades poderosas y equipo épico.

🏆 CARACTERÍSTICAS SOCIALES
• Integración completa con Google Play Games
• Compite en tablas de clasificación globales
• Desbloquea más de 50 logros únicos
• Comparte capturas y momentos épicos
• Invita y desafía a tus amigos

🔄 ACTUALIZACIONES CONSTANTES
Agregamos regularmente:
• Nuevos niveles y mundos
• Personajes y habilidades
• Eventos temáticos
• Mejoras basadas en feedback de jugadores

📱 OPTIMIZADO PARA MÓVILES
• Funciona sin conexión a internet
• Batería optimizada para sesiones largas
• Compatible con dispositivos de gama baja
• Interfaz adaptable a cualquier pantalla

¡Únete a millones de jugadores y descarga GRATIS ahora!
        ]],
        category = "GAME_ACTION",
        tags = ["acción", "aventura", "multijugador", "gratis"],
        website = "https://miestudio.com/mijuego",
        email = "[email protected]",
        privacy_policy = "https://miestudio.com/privacy"
    },

    content_rating = {
        questionnaire = {
            violence = {
                cartoon_violence = false,
                realistic_violence = false,
                blood = false,
                sexual_violence = false
            },
            interactive_elements = {
                users_interact = true,
                shares_info = false,
                shares_location = false
            },
            substance_use = {
                alcohol = false,
                drugs = false,
                tobacco = false
            }
        }
    },

    pricing_distribution = {
        pricing = "free",
        countries = "all",
        device_categories = ["phone", "tablet"],
        exclude_countries = []
    },

    app_content = {
        target_audience = {
            age_group = "13_17",
            appeals_to_children = false
        },
        ads = {
            contains_ads = true,
            ad_networks = ["AdMob", "Unity Ads"]
        },
        data_safety = {
            collects_data = true,
            shares_data = false,
            data_types = [
                "device_identifiers",
                "app_activity",
                "app_info_performance"
            ]
        }
    }
}

M.RELEASE_NOTES = {
    ["es-ES"] = "¡Primera versión disponible! Incluye 100 niveles emocionantes, modo multijugador y características sociales completas.",
    ["en-US"] = "Initial release! Features 100 exciting levels, multiplayer mode, and full social features.",
    ["pt-BR"] = "Lançamento inicial! Apresenta 100 níveis emocionantes, modo multijogador e recursos sociais completos.",
    ["fr-FR"] = "Version initiale ! Comprend 100 niveaux passionnants, un mode multijoueur et des fonctionnalités sociales complètes."
}

function M.generate_play_store_metadata()
    local metadata = {
        config = M.PLAY_STORE_CONFIG,
        release_notes = M.RELEASE_NOTES,
        build_info = {
            version_name = sys.get_config("project.version"),
            version_code = tonumber(sys.get_config("android.version_code", "1")),
            min_sdk_version = tonumber(sys.get_config("android.minimum_sdk_version", "21")),
            target_sdk_version = tonumber(sys.get_config("android.target_sdk_version", "34"))
        }
    }

    return metadata
end

function M.validate_play_store_submission()
    local issues = {}

    -- Verificar título
    local title = M.PLAY_STORE_CONFIG.store_listing.title
    if string.len(title) > 50 then
        table.insert(issues, "Title exceeds 50 characters")
    end

    -- Verificar descripción corta
    local short_desc = M.PLAY_STORE_CONFIG.store_listing.short_description
    if string.len(short_desc) > 80 then
        table.insert(issues, "Short description exceeds 80 characters")
    end

    -- Verificar descripción completa
    local full_desc = M.PLAY_STORE_CONFIG.store_listing.full_description
    if string.len(full_desc) > 4000 then
        table.insert(issues, "Full description exceeds 4000 characters")
    end

    return issues
end

return M

2. Android App Bundle Generation

Generación de AAB

#!/bin/bash
# android_bundle_build.sh

echo "Building Android App Bundle..."

# Variables
APP_NAME="MiJuego"
PACKAGE_NAME="com.miestudio.mijuego"
VERSION_NAME="1.0"
VERSION_CODE="1"
KEYSTORE_PATH="release-key.keystore"
KEYSTORE_ALIAS="mijuego-key"

# Verificar herramientas
if ! command -v bundletool &> /dev/null; then
    echo "Error: bundletool not found. Download from: https://github.com/google/bundletool"
    exit 1
fi

# Limpiar builds anteriores
echo "Cleaning previous builds..."
rm -rf build/android/*

# Configurar variables de entorno
export JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64
export ANDROID_HOME=~/Android/Sdk

# Build con Defold
echo "Building with Defold..."
# Aquí usarías el build server de Defold o bob.jar
java -jar bob.jar --platform armv7-android,arm64-android \
    --bundle-output build/android/${APP_NAME}.aab \
    --variant release \
    resolve build bundle

# Verificar el bundle
echo "Verifying bundle..."
bundletool validate --bundle=build/android/${APP_NAME}.aab

# Generar APKs universales para testing
echo "Generating universal APK for testing..."
bundletool build-apks \
    --bundle=build/android/${APP_NAME}.aab \
    --output=build/android/${APP_NAME}.apks \
    --mode=universal

# Extract APK universal
bundletool extract-apks \
    --apks=build/android/${APP_NAME}.apks \
    --output-dir=build/android/extracted \
    --device-spec=universal.json

echo "Android App Bundle build complete!"
echo "AAB file: build/android/${APP_NAME}.aab"
echo "Test APK: build/android/extracted/universal.apk"

# Mostrar información del bundle
echo "Bundle information:"
bundletool dump manifest --bundle=build/android/${APP_NAME}.aab

ASO (App Store Optimization)

1. Keyword Research y Optimization

Sistema de ASO

-- aso_optimizer.lua
local M = {}

M.KEYWORD_RESEARCH = {
    primary_keywords = {
        {keyword = "juego acción", volume = 10000, difficulty = 8, relevance = 10},
        {keyword = "aventura móvil", volume = 5000, difficulty = 6, relevance = 9},
        {keyword = "multijugador", volume = 8000, difficulty = 7, relevance = 8},
        {keyword = "juego gratis", volume = 50000, difficulty = 9, relevance = 7}
    },

    secondary_keywords = {
        {keyword = "rpg móvil", volume = 3000, difficulty = 5, relevance = 6},
        {keyword = "juego offline", volume = 2000, difficulty = 4, relevance = 5},
        {keyword = "battle royale", volume = 15000, difficulty = 9, relevance = 4}
    },

    long_tail_keywords = {
        "mejor juego acción 2024",
        "juego aventura sin internet",
        "multijugador tiempo real móvil",
        "juego gratis sin anuncios"
    }
}

M.ASO_TEMPLATES = {
    ios_title = "Mi Juego - Aventura Épica",
    ios_subtitle = "Acción Multijugador Gratis",

    android_title = "Mi Juego: Aventura Acción",
    android_short_desc = "Épica aventura multijugador con 100+ niveles. ¡Gratis!",

    description_structure = {
        hook = "¡La aventura más épica te espera!",
        features = {
            "100+ niveles únicos",
            "Multijugador en tiempo real",
            "Gráficos impresionantes",
            "Completamente GRATIS"
        },
        social_proof = "Únete a millones de jugadores",
        call_to_action = "¡Descarga GRATIS ahora!"
    }
}

function M.optimize_title(platform, base_title, keywords)
    local optimized = base_title
    local char_limit = platform == "ios" and 50 or 50

    -- Añadir keywords más importantes que quepan
    for _, kw in ipairs(keywords) do
        local test_title = optimized .. " " .. kw.keyword
        if string.len(test_title) <= char_limit then
            if not string.find(string.lower(optimized), string.lower(kw.keyword)) then
                optimized = test_title
            end
        end
    end

    return optimized
end

function M.generate_description(template, keywords)
    local description = template.hook .. "\n\n"

    -- Sección de características
    description = description .. "🎮 CARACTERÍSTICAS:\n"
    for _, feature in ipairs(template.features) do
        description = description .. "• " .. feature .. "\n"
    end

    -- Insertar keywords naturalmente
    description = description .. "\n" .. self:insert_keywords_naturally(template, keywords)

    -- Call to action
    description = description .. "\n\n" .. template.call_to_action

    return description
end

function insert_keywords_naturally(self, template, keywords)
    local sections = {
        "Experimenta la mejor " .. keywords[1].keyword .. " en tu dispositivo móvil.",
        "Perfecto para fans de " .. keywords[2].keyword .. " y " .. keywords[3].keyword .. ".",
        "Este " .. keywords[4].keyword .. " redefine lo que esperas de los juegos móviles."
    }

    return table.concat(sections, " ")
end

function M.track_aso_performance()
    return {
        keyword_rankings = {
            {keyword = "juego acción", position = 15, change = "+3"},
            {keyword = "aventura móvil", position = 8, change = "+5"},
            {keyword = "multijugador", position = 25, change = "-2"}
        },
        conversion_metrics = {
            impressions = 10000,
            page_views = 1200,
            installs = 350,
            conversion_rate = 29.2
        }
    }
end

return M

2. Screenshot y Visual Optimization

Generador de Screenshots para Tiendas

-- screenshot_generator.lua
local M = {}

M.SCREENSHOT_TEMPLATES = {
    ios = {
        {
            title = "¡Aventura Épica!",
            subtitle = "100+ Niveles Únicos",
            gameplay_focus = "level_selection",
            device = "iPhone 12 Pro",
            size = "1170x2532"
        },
        {
            title = "Multijugador Épico",
            subtitle = "Compite en Tiempo Real",
            gameplay_focus = "multiplayer_battle",
            device = "iPhone 12 Pro",
            size = "1170x2532"
        },
        {
            title = "Gráficos Increíbles",
            subtitle = "Optimizado para Móviles",
            gameplay_focus = "gameplay_action",
            device = "iPhone 12 Pro",
            size = "1170x2532"
        }
    },

    android = {
        {
            title = "¡AVENTURA ÉPICA!",
            subtitle = "Más de 100 niveles únicos",
            features = ["Multijugador", "Gratis", "Sin Internet"],
            gameplay_focus = "level_overview"
        },
        {
            title = "MULTIJUGADOR",
            subtitle = "Batalla en tiempo real",
            features = ["PvP", "Rankings", "Torneos"],
            gameplay_focus = "multiplayer_action"
        }
    }
}

function M.generate_screenshot_config(platform, template)
    local config = {
        platform = platform,
        template = template,
        overlay_elements = {
            title = {
                text = template.title,
                font_size = platform == "ios" and 48 or 52,
                color = "#FFFFFF",
                stroke_color = "#000000",
                position = "top"
            },
            subtitle = {
                text = template.subtitle,
                font_size = platform == "ios" and 32 or 36,
                color = "#FFD700",
                position = "below_title"
            }
        },
        effects = {
            glow = true,
            gradient_overlay = true,
            device_frame = platform == "ios"
        }
    }

    if platform == "android" and template.features then
        config.overlay_elements.features = {
            items = template.features,
            style = "badge",
            position = "bottom"
        }
    end

    return config
end

function M.capture_gameplay_screenshot(scene_name, config)
    -- Configurar cámara para screenshot
    msg.post("main:/camera", "set_screenshot_mode", {
        resolution = config.size,
        scene = scene_name,
        ui_overlay = true
    })

    -- Capturar screenshot
    msg.post("@render:", "capture_screen", {
        path = "/screenshots/" .. scene_name .. "_raw.png"
    }, function(self, message_id, message)
        if message_id == hash("screenshot_captured") then
            -- Procesar screenshot con overlays
            M.process_screenshot(message.path, config)
        end
    end)
end

function M.process_screenshot(image_path, config)
    -- Esta función procesaría la imagen añadiendo overlays
    -- En una implementación real, usarías herramientas externas
    -- o una extensión nativa para edición de imágenes

    local processed_path = string.gsub(image_path, "_raw", "_store")

    print("Processing screenshot: " .. image_path)
    print("Output: " .. processed_path)
    print("Config: " .. json.encode(config))

    -- Simular procesamiento
    timer.delay(2.0, false, function()
        print("Screenshot processed successfully!")
        msg.post("main:/ui", "screenshot_ready", {
            path = processed_path,
            config = config
        })
    end)
end

return M

Release Management

1. Version Control y Release Pipeline

Sistema de Release Automation

#!/bin/bash
# release_pipeline.sh

set -e  # Exit on any error

# Configuration
VERSION=$1
BUILD_NUMBER=$2
RELEASE_TYPE=${3:-"patch"}  # patch, minor, major

if [ -z "$VERSION" ] || [ -z "$BUILD_NUMBER" ]; then
    echo "Usage: $0 <version> <build_number> [release_type]"
    echo "Example: $0 1.2.3 42 minor"
    exit 1
fi

echo "🚀 Starting release pipeline for version $VERSION build $BUILD_NUMBER"

# Step 1: Pre-flight checks
echo "📋 Running pre-flight checks..."
./scripts/pre_flight_check.sh

# Step 2: Update version numbers
echo "📝 Updating version numbers..."
sed -i.bak "s/version = .*/version = $VERSION/" game.project
sed -i.bak "s/version_code = .*/version_code = $BUILD_NUMBER/" game.project

# Step 3: Build for all platforms
echo "🔨 Building for all platforms..."

# iOS Build
echo "Building iOS..."
java -jar bob.jar \
    --platform arm64-ios \
    --bundle-output builds/ios/game_${VERSION}.ipa \
    --variant release \
    resolve build bundle

# Android Build
echo "Building Android..."
java -jar bob.jar \
    --platform armv7-android,arm64-android \
    --bundle-output builds/android/game_${VERSION}.aab \
    --variant release \
    resolve build bundle

# Step 4: Run automated tests
echo "🧪 Running automated tests..."
./scripts/run_tests.sh

# Step 5: Generate release notes
echo "📄 Generating release notes..."
./scripts/generate_release_notes.sh $VERSION > releases/release_notes_$VERSION.md

# Step 6: Upload to stores (if not dry-run)
if [ "$DRY_RUN" != "true" ]; then
    echo "📤 Uploading to stores..."

    # Upload to TestFlight
    xcrun altool --upload-app \
        -f builds/ios/game_${VERSION}.ipa \
        -t ios \
        -u "$APPLE_ID" \
        -p "$APPLE_PASSWORD"

    # Upload to Play Console (Internal Track)
    bundletool upload-bundle \
        --bundle builds/android/game_${VERSION}.aab \
        --track internal
fi

# Step 7: Git tagging
echo "🏷️  Creating git tag..."
git add .
git commit -m "Release version $VERSION build $BUILD_NUMBER"
git tag -a "v$VERSION" -m "Release version $VERSION"
git push origin main --tags

# Step 8: Notification
echo "📢 Sending notifications..."
./scripts/send_release_notification.sh $VERSION $BUILD_NUMBER

echo "✅ Release pipeline completed successfully!"
echo "📊 Build artifacts:"
echo "   iOS: builds/ios/game_${VERSION}.ipa"
echo "   Android: builds/android/game_${VERSION}.aab"
echo "   Release notes: releases/release_notes_$VERSION.md"

2. Phased Rollout Strategy

Sistema de Rollout Progresivo

-- rollout_manager.lua
local M = {}

M.ROLLOUT_PHASES = {
    {
        name = "Internal Testing",
        percentage = 0,
        duration_days = 3,
        criteria = {
            crash_rate_threshold = 0.1,
            anr_rate_threshold = 0.05,
            min_rating = 4.0
        },
        users = "internal_testers"
    },
    {
        name = "Closed Beta",
        percentage = 1,
        duration_days = 7,
        criteria = {
            crash_rate_threshold = 0.5,
            anr_rate_threshold = 0.2,
            min_rating = 3.8
        },
        users = "beta_testers"
    },
    {
        name = "Open Beta",
        percentage = 5,
        duration_days = 5,
        criteria = {
            crash_rate_threshold = 1.0,
            anr_rate_threshold = 0.5,
            min_rating = 3.5
        },
        users = "open_beta"
    },
    {
        name = "Gradual Rollout 20%",
        percentage = 20,
        duration_days = 3,
        criteria = {
            crash_rate_threshold = 2.0,
            anr_rate_threshold = 1.0,
            min_rating = 3.0
        },
        users = "production"
    },
    {
        name = "Full Rollout",
        percentage = 100,
        duration_days = 0,
        criteria = {},
        users = "production"
    }
}

function M.get_current_phase(version)
    -- En una implementación real, esto vendría de tu sistema de tracking
    return {
        phase_index = 3,
        start_date = os.time() - (2 * 24 * 60 * 60),  -- Hace 2 días
        metrics = {
            crash_rate = 0.8,
            anr_rate = 0.3,
            rating = 3.7,
            downloads = 1250,
            reviews = 45
        }
    }
end

function M.should_proceed_to_next_phase(current_phase_info)
    local phase = M.ROLLOUT_PHASES[current_phase_info.phase_index]
    local metrics = current_phase_info.metrics

    -- Verificar criterios de calidad
    if metrics.crash_rate > phase.criteria.crash_rate_threshold then
        return false, "Crash rate too high"
    end

    if metrics.anr_rate > phase.criteria.anr_rate_threshold then
        return false, "ANR rate too high"
    end

    if metrics.rating < phase.criteria.min_rating then
        return false, "Rating too low"
    end

    -- Verificar duración mínima
    local days_in_phase = (os.time() - current_phase_info.start_date) / (24 * 60 * 60)
    if days_in_phase < phase.duration_days then
        return false, "Minimum duration not met"
    end

    return true, "All criteria met"
end

function M.generate_rollout_report(version)
    local current_phase = M.get_current_phase(version)
    local can_proceed, reason = M.should_proceed_to_next_phase(current_phase)

    return {
        version = version,
        current_phase = M.ROLLOUT_PHASES[current_phase.phase_index],
        metrics = current_phase.metrics,
        can_proceed = can_proceed,
        proceed_reason = reason,
        recommendations = M.get_recommendations(current_phase)
    }
end

function M.get_recommendations(current_phase_info)
    local recommendations = {}
    local metrics = current_phase_info.metrics

    if metrics.crash_rate > 1.0 then
        table.insert(recommendations, "Consider hotfix for crash issues")
    end

    if metrics.rating < 3.5 then
        table.insert(recommendations, "Monitor user feedback closely")
    end

    if metrics.downloads < 1000 then
        table.insert(recommendations, "Consider increasing marketing efforts")
    end

    return recommendations
end

return M

Post-Launch Optimization

1. Performance Monitoring

Sistema de Monitoring Post-Launch

-- post_launch_monitor.lua
local M = {}

function init(self)
    self.metrics = {
        downloads = 0,
        active_users = 0,
        retention_day_1 = 0,
        retention_day_7 = 0,
        crash_rate = 0,
        rating = 0,
        revenue = 0
    }

    self.alerts = {
        crash_rate_threshold = 2.0,
        rating_threshold = 3.0,
        retention_threshold = 20.0
    }
end

function M.collect_store_metrics(self)
    -- En una implementación real, esto se conectaría a APIs de las tiendas
    return {
        app_store = {
            downloads_today = 150,
            rating = 4.2,
            reviews_count = 89,
            impressions = 5420,
            conversion_rate = 2.8
        },
        google_play = {
            downloads_today = 320,
            rating = 3.9,
            reviews_count = 156,
            impressions = 8950,
            conversion_rate = 3.6
        }
    }
end

function M.analyze_user_feedback(self, reviews)
    local sentiment_analysis = {
        positive = 0,
        neutral = 0,
        negative = 0,
        common_issues = {},
        feature_requests = {}
    }

    for _, review in ipairs(reviews) do
        -- Análisis básico de sentimiento
        local sentiment = self:analyze_sentiment(review.text)
        sentiment_analysis[sentiment] = sentiment_analysis[sentiment] + 1

        -- Extraer problemas comunes
        local issues = self:extract_issues(review.text)
        for _, issue in ipairs(issues) do
            sentiment_analysis.common_issues[issue] = (sentiment_analysis.common_issues[issue] or 0) + 1
        end
    end

    return sentiment_analysis
end

function analyze_sentiment(self, text)
    -- Análisis básico de sentimiento
    local positive_words = {"great", "amazing", "love", "excellent", "fantastic"}
    local negative_words = {"bad", "terrible", "hate", "awful", "worst"}

    local positive_count = 0
    local negative_count = 0

    local lower_text = string.lower(text)

    for _, word in ipairs(positive_words) do
        if string.find(lower_text, word) then
            positive_count = positive_count + 1
        end
    end

    for _, word in ipairs(negative_words) do
        if string.find(lower_text, word) then
            negative_count = negative_count + 1
        end
    end

    if positive_count > negative_count then
        return "positive"
    elseif negative_count > positive_count then
        return "negative"
    else
        return "neutral"
    end
end

function M.generate_health_report(self)
    local store_metrics = self:collect_store_metrics()
    local combined_metrics = {
        total_downloads = store_metrics.app_store.downloads_today + store_metrics.google_play.downloads_today,
        avg_rating = (store_metrics.app_store.rating + store_metrics.google_play.rating) / 2,
        total_reviews = store_metrics.app_store.reviews_count + store_metrics.google_play.reviews_count
    }

    local health_score = self:calculate_health_score(combined_metrics)

    return {
        timestamp = os.time(),
        metrics = combined_metrics,
        health_score = health_score,
        alerts = self:check_alerts(combined_metrics),
        recommendations = self:get_health_recommendations(combined_metrics)
    }
end

function calculate_health_score(self, metrics)
    local score = 0

    -- Rating score (0-40 points)
    score = score + (metrics.avg_rating / 5.0) * 40

    -- Download score (0-30 points)
    local download_score = math.min(metrics.total_downloads / 1000, 1.0) * 30
    score = score + download_score

    -- Review engagement score (0-30 points)
    local review_score = math.min(metrics.total_reviews / 100, 1.0) * 30
    score = score + review_score

    return math.floor(score)
end

function check_alerts(self, metrics)
    local alerts = {}

    if metrics.avg_rating < self.alerts.rating_threshold then
        table.insert(alerts, {
            level = "critical",
            message = "Average rating below threshold",
            value = metrics.avg_rating,
            threshold = self.alerts.rating_threshold
        })
    end

    return alerts
end

return M

2. Update Strategy

Sistema de Actualizaciones

-- update_strategy.lua
local M = {}

M.UPDATE_TYPES = {
    HOTFIX = {
        timeline = "1-3 days",
        review_time = "24 hours",
        scope = "Critical bugs only"
    },
    PATCH = {
        timeline = "1-2 weeks",
        review_time = "24-48 hours",
        scope = "Bug fixes and minor features"
    },
    MINOR = {
        timeline = "4-6 weeks",
        review_time = "2-7 days",
        scope = "New features and improvements"
    },
    MAJOR = {
        timeline = "3-6 months",
        review_time = "2-7 days",
        scope = "Major features and overhauls"
    }
}

function M.plan_update(issue_severity, user_impact, development_effort)
    local update_type = "PATCH"  -- Default

    if issue_severity == "critical" and user_impact == "high" then
        update_type = "HOTFIX"
    elseif development_effort == "high" or user_impact == "major" then
        update_type = "MINOR"
    end

    return {
        type = update_type,
        timeline = M.UPDATE_TYPES[update_type].timeline,
        expected_review_time = M.UPDATE_TYPES[update_type].review_time,
        scope = M.UPDATE_TYPES[update_type].scope
    }
end

function M.generate_update_changelog(version, changes)
    local changelog = "Version " .. version .. "\n\n"

    -- Agrupar cambios por tipo
    local grouped_changes = {
        new_features = {},
        improvements = {},
        bug_fixes = {},
        known_issues = {}
    }

    for _, change in ipairs(changes) do
        table.insert(grouped_changes[change.type], change.description)
    end

    -- Generar changelog formateado
    if #grouped_changes.new_features > 0 then
        changelog = changelog .. "🆕 NEW FEATURES:\n"
        for _, feature in ipairs(grouped_changes.new_features) do
            changelog = changelog .. "• " .. feature .. "\n"
        end
        changelog = changelog .. "\n"
    end

    if #grouped_changes.improvements > 0 then
        changelog = changelog .. "⚡ IMPROVEMENTS:\n"
        for _, improvement in ipairs(grouped_changes.improvements) do
            changelog = changelog .. "• " .. improvement .. "\n"
        end
        changelog = changelog .. "\n"
    end

    if #grouped_changes.bug_fixes > 0 then
        changelog = changelog .. "🐛 BUG FIXES:\n"
        for _, fix in ipairs(grouped_changes.bug_fixes) do
            changelog = changelog .. "• " .. fix .. "\n"
        end
        changelog = changelog .. "\n"
    end

    changelog = changelog .. "\n¡Gracias por jugar! Seguimos trabajando para mejorar tu experiencia."

    return changelog
end

return M

Compliance y Políticas

1. Privacy Policy Generator

Generador de Política de Privacidad

-- privacy_policy_generator.lua
local M = {}

M.PRIVACY_TEMPLATE = {
    app_name = "Mi Juego Increíble",
    company_name = "Mi Estudio",
    contact_email = "[email protected]",
    effective_date = "1 de enero de 2024",

    data_collected = {
        "Identificadores del dispositivo",
        "Información de rendimiento de la aplicación",
        "Datos de uso y actividad del juego",
        "Información de compras in-app"
    },

    data_usage = {
        "Mejorar la experiencia de juego",
        "Proporcionar soporte al cliente",
        "Analizar el rendimiento de la aplicación",
        "Personalizar contenido y ofertas"
    },

    third_party_services = {
        "Google Analytics",
        "Firebase Crashlytics",
        "AdMob",
        "Unity Ads"
    }
}

function M.generate_privacy_policy()
    local policy = string.format([[
POLÍTICA DE PRIVACIDAD

Última actualización: %s

Esta política de privacidad describe cómo %s ("nosotros", "nuestro" o "nos") recopila, usa y comparte información cuando utilizas nuestra aplicación móvil %s.

1. INFORMACIÓN QUE RECOPILAMOS

Recopilamos los siguientes tipos de información:
%s

2. CÓMO UTILIZAMOS LA INFORMACIÓN

Utilizamos la información recopilada para:
%s

3. SERVICIOS DE TERCEROS

Nuestra aplicación utiliza los siguientes servicios de terceros:
%s

4. SEGURIDAD DE LOS DATOS

Implementamos medidas de seguridad apropiadas para proteger tu información contra acceso no autorizado, alteración, divulgación o destrucción.

5. CONTACTO

Si tienes preguntas sobre esta política de privacidad, puedes contactarnos en:
Email: %s

Esta política puede actualizarse ocasionalmente. Te notificaremos sobre cambios significativos.
    ]],
    M.PRIVACY_TEMPLATE.effective_date,
    M.PRIVACY_TEMPLATE.company_name,
    M.PRIVACY_TEMPLATE.app_name,
    M.format_list(M.PRIVACY_TEMPLATE.data_collected),
    M.format_list(M.PRIVACY_TEMPLATE.data_usage),
    M.format_list(M.PRIVACY_TEMPLATE.third_party_services),
    M.PRIVACY_TEMPLATE.contact_email)

    return policy
end

function M.format_list(items)
    local formatted = ""
    for _, item in ipairs(items) do
        formatted = formatted .. "• " .. item .. "\n"
    end
    return formatted
end

return M

Mejores Prácticas

1. Launch Checklist

2. Store Optimization

3. Launch Strategy

4. Post-Launch

Esta guía completa te proporciona todo lo necesario para publicar exitosamente tu juego en las principales tiendas móviles y mantenerlo optimizado post-lanzamiento.