Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 4 additions & 9 deletions .github/workflows/ci-cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,6 @@ on:
pull_request:
branches: [ main, develop ]

# Permite cancelar runs anteriores do mesmo workflow na mesma branch
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

env:
NODE_VERSION: '20'
FIREBASE_PROJECT_ID: pyexplorer-cd32d
Expand All @@ -34,7 +29,7 @@ jobs:
# ===========================================================================
build:
name: 🔨 Build & Lint
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
# Firebase credentials are now in .env file (committed to repo)
# Security is enforced via Firestore Rules, not secret API keys

Expand Down Expand Up @@ -94,7 +89,7 @@ jobs:
# ===========================================================================
deploy-production:
name: 🚀 Deploy to Production
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
needs: build
# Só faz deploy em push para main (não em PRs)
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
Expand Down Expand Up @@ -137,7 +132,7 @@ jobs:
# ===========================================================================
deploy-preview:
name: 👀 Deploy Preview
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
needs: build
# Só faz preview em PRs (exceto Dependabot que não tem acesso aos secrets)
if: github.event_name == 'pull_request' && github.actor != 'dependabot[bot]'
Expand Down Expand Up @@ -166,7 +161,7 @@ jobs:
# ===========================================================================
lighthouse:
name: 🔦 Lighthouse Audit
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
needs: deploy-production
if: github.event_name == 'push' && github.ref == 'refs/heads/main'

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/dependabot-auto-merge.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ permissions:
jobs:
dependabot-auto-merge:
name: 🤖 Auto-Merge Dependabot PRs
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
if: github.actor == 'dependabot[bot]'

steps:
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/security.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
# ===========================================================================
audit:
name: 🔒 Security Audit
runs-on: ubuntu-latest
runs-on: ubuntu-24.04

steps:
- name: 📥 Checkout repository
Expand Down Expand Up @@ -48,7 +48,7 @@ jobs:
# ===========================================================================
outdated:
name: 📦 Check Outdated Packages
runs-on: ubuntu-latest
runs-on: ubuntu-24.04

steps:
- name: 📥 Checkout repository
Expand Down
18 changes: 18 additions & 0 deletions src/data/questions/basic_commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -320,5 +320,23 @@ export const basicCommandsQuestions: QuestionDocument[] = [
],
explanationKidFriendly: 'O símbolo # faz o Python ignorar tudo que vem depois dele na mesma linha. É assim que fazemos comentários! 🤫',
points: 10,
},
{
id: 'basic_16',
type: 'multiple_choice',
world: 'basic_commands',
difficulty: 'easy',
ageMin: 8,
title: 'Linha em Branco',
prompt: 'Como fazemos para pular uma linha (mostrar uma linha vazia) usando print?',
options: [
'print()',
'print(" ")',
'print(0)',
'pular_linha()',
],
answerIndex: 0,
explanationKidFriendly: 'Se você usar `print()` sem nada dentro, ele mostra uma linha vazia! É ótimo para separar textos e organizar o que aparece na tela. 📄',
points: 10,
}
];
132 changes: 132 additions & 0 deletions src/data/questions/dictionaries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,5 +203,137 @@ export const dictionariesQuestions: QuestionDocument[] = [
],
explanationKidFriendly: 'Dicionários são fáceis de atualizar! Basta usar a chave ["poção"] e dar o novo valor. O valor antigo some e o novo assume! 💰',
points: 15,
},
{
id: 'dict_12',
type: 'multiple_choice',
world: 'dictionaries',
difficulty: 'medium',
ageMin: 10,
title: 'Tamanho do Dicionário',
prompt: 'Se tivermos `d = {"a": 1, "b": 2, "c": 3}`, o que `len(d)` retorna?',
options: [
'3 (conta as chaves)',
'6 (conta tudo)',
'1 (conta o dicionário)',
'0',
],
answerIndex: 0,
explanationKidFriendly: '`len()` conta quantas chaves (pares) existem no dicionário. Temos "a", "b" e "c", então são 3 itens! 📏',
points: 15,
},
{
id: 'dict_13',
type: 'fill_code',
world: 'dictionaries',
difficulty: 'hard',
ageMin: 11,
title: 'Apagando Chaves',
prompt: 'Use `del` para apagar a chave "idade" do dicionário:',
starterCode: 'pessoa = {"nome": "Ana", "idade": 10}\n___ pessoa["idade"]\nprint(pessoa)',
solutionTemplate: 'del pessoa["idade"]',
tests: [
{ input: null, expectedOutput: "{'nome': 'Ana'}" },
],
explanationKidFriendly: '`del` vem de "deletar". Ele remove a chave e o valor do dicionário para sempre! 🗑️',
points: 20,
},
{
id: 'dict_14',
type: 'multiple_choice',
world: 'dictionaries',
difficulty: 'medium',
ageMin: 10,
title: 'Só as Chaves',
prompt: 'Qual método usamos para ver todas as chaves (nomes) do dicionário?',
options: [
'.keys()',
'.values()',
'.items()',
'.names()',
],
answerIndex: 0,
explanationKidFriendly: '`keys()` retorna uma lista só com as chaves! É útil para saber quais informações temos guardadas. 🔑',
points: 15,
},
{
id: 'dict_15',
type: 'multiple_choice',
world: 'dictionaries',
difficulty: 'medium',
ageMin: 10,
title: 'Só os Valores',
prompt: 'Qual método usamos para ver todos os valores guardados?',
options: [
'.values()',
'.keys()',
'.data()',
'.get()',
],
answerIndex: 0,
explanationKidFriendly: '`values()` mostra só os valores, ignorando os nomes das chaves. 💎',
points: 15,
},
{
id: 'dict_16',
type: 'fill_code',
world: 'dictionaries',
difficulty: 'easy',
ageMin: 10,
title: 'Começando do Zero',
prompt: 'Crie um dicionário vazio chamado `mochila`:',
starterCode: 'mochila = ___\nmochila["água"] = 1\nprint(mochila)',
solutionTemplate: 'mochila = {}',
tests: [
{ input: null, expectedOutput: "{'água': 1}" },
],
explanationKidFriendly: 'Para criar um dicionário vazio, usamos chaves sem nada dentro `{}`. Depois podemos adicionar coisas! 🎒',
points: 10,
},
{
id: 'dict_17',
type: 'true_false',
world: 'dictionaries',
difficulty: 'medium',
ageMin: 10,
title: 'Misturando Tudo',
prompt: 'Um dicionário pode ter chaves de texto e valores numéricos ao mesmo tempo? Ex: `{"nome": "Ana", "idade": 10}`',
correctBool: true,
explanationKidFriendly: 'Sim! Essa é a melhor parte dos dicionários. Você pode guardar textos, números e até listas no mesmo lugar! 🥗',
points: 10,
},
{
id: 'dict_18',
type: 'fill_code',
world: 'dictionaries',
difficulty: 'hard',
ageMin: 11,
title: 'Dicionário dentro de Dicionário',
prompt: 'Acesse a nota de matemática ("mat") do aluno "joao":',
starterCode: 'notas = {"joao": {"mat": 10, "port": 8}}\nnota_mat = notas["joao"][___]\nprint(nota_mat)',
solutionTemplate: 'nota_mat = notas["joao"]["mat"]',
tests: [
{ input: null, expectedOutput: '10' },
],
explanationKidFriendly: 'Podemos ter dicionários dentro de dicionários! Primeiro abrimos a chave "joao", depois a chave "mat". 📦📦',
points: 25,
},
{
id: 'dict_19',
type: 'multiple_choice',
world: 'dictionaries',
difficulty: 'medium',
ageMin: 10,
title: 'Loop no Dicionário',
prompt: 'Quando fazemos `for x in dicionario:`, o que é o `x`?',
options: [
'A chave (nome)',
'O valor',
'Os dois juntos',
'O índice',
],
answerIndex: 0,
explanationKidFriendly: 'Por padrão, o loop percorre as chaves! Se quiser os valores, precisa usar `.values()`. 🗝️',
points: 15,
}
];
132 changes: 132 additions & 0 deletions src/data/questions/error_handling.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,5 +208,137 @@ export const errorHandlingQuestions: QuestionDocument[] = [
],
explanationKidFriendly: '`IndexError` acontece quando tentamos pegar um item que não existe na lista. Com `try/except`, evitamos que o programa quebre! 🛡️',
points: 20,
},
{
id: 'error_12',
type: 'multiple_choice',
world: 'error_handling',
difficulty: 'medium',
ageMin: 10,
title: 'Pegando o Erro Certo',
prompt: 'Para pegar SÓ o erro de divisão por zero, usamos:',
options: [
'except ZeroDivisionError:',
'except Error:',
'except:',
'except Zero:',
],
answerIndex: 0,
explanationKidFriendly: 'É melhor ser específico! `except ZeroDivisionError:` pega só erros de divisão, deixando outros erros passarem para você ver. 🎯',
points: 15,
},
{
id: 'error_13',
type: 'multiple_choice',
world: 'error_handling',
difficulty: 'medium',
ageMin: 10,
title: 'Chave Inexistente',
prompt: 'Qual erro acontece se tentarmos acessar `dic["chave"]` e a chave não existir?',
options: [
'KeyError',
'ValueError',
'NameError',
'TypeError',
],
answerIndex: 0,
explanationKidFriendly: 'KeyError significa "Erro de Chave". Acontece quando tentamos usar uma chave que não está no dicionário! 🔑❌',
points: 15,
},
{
id: 'error_14',
type: 'multiple_choice',
world: 'error_handling',
difficulty: 'medium',
ageMin: 11,
title: 'Sempre Executa',
prompt: 'Qual bloco do try/except roda SEMPRE, dando erro ou não?',
options: [
'finally',
'else',
'always',
'check',
],
answerIndex: 0,
explanationKidFriendly: '`finally` significa "finalmente". Ele roda no final de tudo, não importa o que aconteça! Ótimo para limpeza. 🧹',
points: 15,
},
{
id: 'error_15',
type: 'fill_code',
world: 'error_handling',
difficulty: 'hard',
ageMin: 11,
title: 'Vários Problemas',
prompt: 'Complete para tratar `ValueError` e `ZeroDivisionError` separadamente:',
starterCode: 'try:\n x = int(input()) / int(input())\nexcept ValueError:\n print("Erro de valor")\n___ ZeroDivisionError:\n print("Não divida por zero")',
solutionTemplate: 'except ZeroDivisionError:',
tests: [
{ input: null, expectedOutput: 'Não divida por zero' }, // This test implies the mock would trigger it, but for fill_code we check the syntax mainly
],
explanationKidFriendly: 'Você pode ter vários `except`! Um para cada tipo de problema. Assim você dá a mensagem certa para o erro certo! 👨‍⚕️',
points: 20,
},
{
id: 'error_16',
type: 'true_false',
world: 'error_handling',
difficulty: 'hard',
ageMin: 11,
title: 'Erro de Escrita',
prompt: 'O `try/except` consegue pegar erros de digitação (SyntaxError), como esquecer os dois pontos `:` ?',
correctBool: false,
explanationKidFriendly: 'Não! `SyntaxError` acontece ANTES do programa rodar. O Python nem começa se tiver erro de escrita, então o `try` não funciona! 🚫',
points: 15,
},
{
id: 'error_17',
type: 'multiple_choice',
world: 'error_handling',
difficulty: 'medium',
ageMin: 10,
title: 'Espaços Errados',
prompt: 'O que é um IndentationError?',
options: [
'Erro nos espaços/tabulação do código',
'Erro de matemática',
'Erro de nome de variável',
'Erro de importação',
],
answerIndex: 0,
explanationKidFriendly: 'Python é muito exigente com espaços! Se você não alinhar o código corretamente, ele dá `IndentationError`. 📏',
points: 15,
},
{
id: 'error_18',
type: 'fill_code',
world: 'error_handling',
difficulty: 'medium',
ageMin: 11,
title: 'Texto não é Número',
prompt: 'Trate o erro de converter "dez" para número (ValueError):',
starterCode: 'try:\n n = int("dez")\nexcept ___:\n print("Não é número")',
solutionTemplate: 'except ValueError:',
tests: [
{ input: null, expectedOutput: 'Não é número' },
],
explanationKidFriendly: '`ValueError` acontece quando o valor não faz sentido. Tentar transformar "dez" em número é um erro de valor! 🔠',
points: 20,
},
{
id: 'error_19',
type: 'fill_code',
world: 'error_handling',
difficulty: 'medium',
ageMin: 10,
title: 'Ignorando Erro',
prompt: 'Se der erro, não faça nada (use `pass`):',
starterCode: 'try:\n print(1/0)\nexcept:\n ___',
solutionTemplate: 'pass',
tests: [
{ input: null, expectedOutput: '' },
],
explanationKidFriendly: '`pass` significa "passar". É como dizer "deixa pra lá". O erro acontece, é capturado, e o programa segue sem fazer nada. 😶',
points: 15,
}
];
Loading
Loading