📚 Pré-requisitos Teóricos: este projeto aplica conceitos ensinados em Módulo 02: Lógica de Programação e Algoritmos. Recomendado revisar antes de começar.
Trilha de Especialização — Nível Introdutório (Curso 25: Sistemas Desktop com Python)
- Visão Geral dos 10 Miniprojetos: 🌱 Guia Pedagógico de Miniprojetos
- Catálogo Geral: 🏗️ Catálogo de Projetos
- ➡️ Implementação Prática: Miniprojeto 09 (Código Completo em 4 Degraus)
Na gestão predial de condomínios, prédios públicos e escolas municipais, o controle dos reparos de infraestrutura (elétrica, hidráulica, pintura e alvenaria) exige acompanhamento rigoroso de prioridades e pareceres técnicos de conclusão.
Neste projeto, o estudante aprende como:
.json.Chamado encapsulando o ciclo de vida (iniciar_atendimento(), concluir(parecer)).CHECK para prioridades e status, calculando agregações de produtividade (GROUP BY).flowchart LR
D1["🪜 DEGRAU 1\nImperativo & JSON\nMenu while + chamados.json\ncom ciclo de vida básico"] --> D2["🪜 DEGRAU 2\nPrimeira Classe POO\nclass Chamado\n(iniciar, concluir, parecer)"]
D2 --> D3["🪜 DEGRAU 3\nSQLite com CHECK\nTabela chamados e\nagregações GROUP BY"]
D3 --> D4["🪜 DEGRAU 4\nInterface Web Streamlit\nQuadro Kanban, métricas\ne parecer técnico"]
style D1 fill:#fffde7,stroke:#fbc02d,stroke-width:2px
style D2 fill:#e3f2fd,stroke:#1976d2,stroke-width:2px
style D3 fill:#e8f5e9,stroke:#388e3c,stroke-width:2px
style D4 fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px
sistemas_python_intro_09_manutencao_predial/
├── degrau_1_imperativo_json/
│ ├── main.py # Script imperativo com menu e arquivo chamados.json
│ └── test_degrau_1.py # Testes automatizados de abertura e conclusão
├── degrau_2_primeira_classe/
│ ├── main.py # Classes POO (class Chamado e class GerenciadorManutencao)
│ └── test_degrau_2.py # Testes unitários de transições de status
├── degrau_3_sqlite/
│ ├── main.py # SQLite3 com CHECK constraints e contagem por status
│ └── test_degrau_3.py # Testes unitários de persistência e pareceres técnicos
├── degrau_4_interface_streamlit/
│ ├── app.py # Painel Web em Python puro com Streamlit (Kanban)
│ └── main.py # Launcher do servidor Web Streamlit
├── main.py # Hub Central / Launcher interativo
└── index.md # Este guia didático autoguiado
No VS Code / PyCharm, abra a pasta deste projeto (File > Open Folder) ou navegue via terminal:
cd sistemas_python_intro_09_manutencao_predial
# Executar o Hub Central (Menu interativo com todos os degraus):
python main.py
# Ou executar os degraus de terminal (1 a 3):
python degrau_1_imperativo_json/main.py
python degrau_2_primeira_classe/main.py
python degrau_3_sqlite/main.py
# Executar a aplicação Web (Degrau 4 com Streamlit):
streamlit run degrau_4_interface_streamlit/app.py
[!TIP] Dica para execução a partir da raiz do repositório: Se você abriu o repositório completo no VS Code, basta executar:
python catalogo_projetos/sistemas_python_intro_09_manutencao_predial/main.py
.json)No primeiro degrau, estruturamos os chamados em formato JSON, registrando data de abertura, local e status.
degrau_1_imperativo_json/main.py):import json
import os
from datetime import datetime
ARQUIVO_PADRAO = "chamados.json"
def abrir_chamado(chamados, local, categoria, descricao, prioridade="MEDIA"):
novo = {
"id": len(chamados) + 1,
"local": local.strip(),
"categoria": categoria.strip(),
"descricao": descricao.strip(),
"prioridade": prioridade.upper(),
"status": "ABERTO",
"parecer_tecnico": "",
"data_abertura": datetime.now().strftime("%d/%m/%Y %H:%M"),
"data_conclusao": ""
}
chamados.append(novo)
return novo
def atualizar_status(chamados, chamado_id, novo_status, parecer=""):
for ch in chamados:
if ch["id"] == chamado_id:
ch["status"] = novo_status
if parecer:
ch["parecer_tecnico"] = parecer.strip()
if novo_status == "CONCLUIDO":
ch["data_conclusao"] = datetime.now().strftime("%d/%m/%Y %H:%M")
return True
return False
degrau_1_imperativo_json/test_degrau_1.py):"""
Testes Unitários para o Degrau 1 do Projeto 09 (Imperativo & JSON)
"""
import os
import unittest
from main import (
abrir_chamado,
atualizar_status,
carregar_chamados,
salvar_chamados
)
ARQUIVO_TESTE = "teste_chamados.json"
class TestDegrau1Manutencao(unittest.TestCase):
def setUp(self):
if os.path.exists(ARQUIVO_TESTE):
os.remove(ARQUIVO_TESTE)
def tearDown(self):
if os.path.exists(ARQUIVO_TESTE):
os.remove(ARQUIVO_TESTE)
def test_ciclo_de_vida_chamado(self):
chamados = []
ch = abrir_chamado(chamados, "Bloco B - Apto 302", "Hidráulica", "Vazamento no sifão da pia", "ALTA")
self.assertIsNotNone(ch)
self.assertEqual(ch["status"], "ABERTO")
self.assertEqual(ch["prioridade"], "ALTA")
# Iniciar atendimento
ok1 = atualizar_status(chamados, 1, "EM_ANDAMENTO")
self.assertTrue(ok1)
self.assertEqual(chamados[0]["status"], "EM_ANDAMENTO")
# Conclusão com parecer
ok2 = atualizar_status(chamados, 1, "CONCLUIDO", "Sifão substituído por novo modelo flexível.")
self.assertTrue(ok2)
self.assertEqual(chamados[0]["status"], "CONCLUIDO")
self.assertEqual(chamados[0]["parecer_tecnico"], "Sifão substituído por novo modelo flexível.")
# Persistência
salvar_chamados(chamados, caminho=ARQUIVO_TESTE)
carregados = carregar_chamados(caminho=ARQUIVO_TESTE)
self.assertEqual(len(carregados), 1)
self.assertEqual(carregados[0]["status"], "CONCLUIDO")
if __name__ == "__main__":
unittest.main()
Chamado e GerenciadorManutencao)No segundo degrau, a classe Chamado encapsula seu próprio ciclo de atendimento através dos métodos iniciar_atendimento() e concluir(parecer).
degrau_2_primeira_classe/main.py):class Chamado:
def __init__(self, id_chamado, local, categoria, descricao, prioridade="MEDIA", status="ABERTO", parecer="", data_abertura=None, data_conclusao=""):
self.id = int(id_chamado)
self.local = local.strip()
self.categoria = categoria.strip()
self.descricao = descricao.strip()
self.prioridade = prioridade.upper()
self.status = status.upper()
self.parecer = parecer.strip()
self.data_abertura = data_abertura or datetime.now().strftime("%d/%m/%Y %H:%M")
self.data_conclusao = data_conclusao
def iniciar_atendimento(self):
if self.status == "ABERTO":
self.status = "EM_ANDAMENTO"
return True
return False
def concluir(self, parecer=""):
self.status = "CONCLUIDO"
self.parecer = parecer.strip()
self.data_conclusao = datetime.now().strftime("%d/%m/%Y %H:%M")
return True
degrau_2_primeira_classe/test_degrau_2.py):"""
Testes Unitários para o Degrau 2 do Projeto 09 (Primeira Classe POO)
"""
import os
import unittest
from main import Chamado, GerenciadorManutencao
ARQUIVO_TESTE = "teste_chamados_poo.json"
class TestDegrau2Manutencao(unittest.TestCase):
def setUp(self):
if os.path.exists(ARQUIVO_TESTE):
os.remove(ARQUIVO_TESTE)
def tearDown(self):
if os.path.exists(ARQUIVO_TESTE):
os.remove(ARQUIVO_TESTE)
def test_classe_chamado_transicoes(self):
ch = Chamado(1, "Sala dos Professores", "Elétrica", "Tomada 220V em curto", "URGENTE")
self.assertTrue(ch.esta_pendente())
self.assertEqual(ch.status, "ABERTO")
self.assertTrue(ch.iniciar_atendimento())
self.assertEqual(ch.status, "EM_ANDAMENTO")
self.assertTrue(ch.esta_pendente())
ch.concluir("Disjuntor rearmado e fiação refeita.")
self.assertEqual(ch.status, "CONCLUIDO")
self.assertFalse(ch.esta_pendente())
self.assertEqual(ch.parecer, "Disjuntor rearmado e fiação refeita.")
def test_gerenciador_persistencia(self):
ger1 = GerenciadorManutencao()
c = ger1.abrir_chamado("Portaria Principal", "Segurança", "Interfone mudo", "ALTA")
c.iniciar_atendimento()
ger1.salvar(caminho=ARQUIVO_TESTE)
ger2 = GerenciadorManutencao()
ger2.carregar(caminho=ARQUIVO_TESTE)
self.assertEqual(len(ger2.chamados), 1)
self.assertEqual(ger2.chamados[0].local, "Portaria Principal")
self.assertEqual(ger2.chamados[0].status, "EM_ANDAMENTO")
if __name__ == "__main__":
unittest.main()
No terceiro degrau, criamos a tabela no SQLite com validações de integridade e consultas agrupadas por status.
degrau_3_sqlite/main.py):import sqlite3
def inicializar_tabelas(caminho="manutencao.db"):
conn = sqlite3.connect(caminho)
cursor = conn.cursor()
cursor.execute("""
CREATE TABLE IF NOT EXISTS chamados (
id INTEGER PRIMARY KEY AUTOINCREMENT,
local TEXT NOT NULL,
categoria TEXT NOT NULL,
descricao TEXT NOT NULL,
prioridade TEXT NOT NULL CHECK(prioridade IN ('BAIXA', 'MEDIA', 'ALTA', 'URGENTE')),
status TEXT DEFAULT 'ABERTO' CHECK(status IN ('ABERTO', 'EM_ANDAMENTO', 'CONCLUIDO')),
parecer_tecnico TEXT,
data_abertura TEXT DEFAULT (datetime('now', 'localtime')),
data_conclusao TEXT
)
""")
conn.commit()
conn.close()
def obter_metricas_status(caminho="manutencao.db"):
conn = sqlite3.connect(caminho)
cursor = conn.cursor()
cursor.execute("SELECT status, COUNT(*) FROM chamados GROUP BY status")
contagens = dict(cursor.fetchall())
conn.close()
return contagens
degrau_3_sqlite/test_degrau_3.py):"""
Testes Unitários para o Degrau 3 do Projeto 09 (SQLite3 com Métricas e Transições)
"""
import os
import unittest
from main import (
abrir_chamado,
atualizar_status,
inicializar_tabelas,
listar_chamados,
obter_metricas_status
)
BANCO_TESTE = "teste_manutencao_degrau3.db"
class TestDegrau3SQLite(unittest.TestCase):
def setUp(self):
if os.path.exists(BANCO_TESTE):
os.remove(BANCO_TESTE)
inicializar_tabelas(caminho=BANCO_TESTE)
def tearDown(self):
if os.path.exists(BANCO_TESTE):
os.remove(BANCO_TESTE)
def test_abertura_e_transicao_sqlite(self):
# 1. Abrir Chamado
ch_id = abrir_chamado("Biblioteca Central", "Climatização", "Ar-condicionado pingando água", "MEDIA", caminho=BANCO_TESTE)
self.assertIsNotNone(ch_id)
# 2. Métricas iniciais
m1 = obter_metricas_status(caminho=BANCO_TESTE)
self.assertEqual(m1["TOTAL"], 1)
self.assertEqual(m1["ABERTO"], 1)
self.assertEqual(m1["EM_ANDAMENTO"], 0)
self.assertEqual(m1["CONCLUIDO"], 0)
# 3. Transição para EM_ANDAMENTO
ok1 = atualizar_status(ch_id, "EM_ANDAMENTO", caminho=BANCO_TESTE)
self.assertTrue(ok1)
m2 = obter_metricas_status(caminho=BANCO_TESTE)
self.assertEqual(m2["EM_ANDAMENTO"], 1)
# 4. Conclusão com Parecer
ok2 = atualizar_status(ch_id, "CONCLUIDO", "Dreno desobstruído e filtro higienizado.", caminho=BANCO_TESTE)
self.assertTrue(ok2)
m3 = obter_metricas_status(caminho=BANCO_TESTE)
self.assertEqual(m3["CONCLUIDO"], 1)
self.assertEqual(m3["ABERTO"], 0)
# 5. Consulta por status
concluidos = listar_chamados("CONCLUIDO", caminho=BANCO_TESTE)
self.assertEqual(len(concluidos), 1)
self.assertEqual(concluidos[0][6], "Dreno desobstruído e filtro higienizado.")
if __name__ == "__main__":
unittest.main()
No quarto degrau, o aluno constrói um painel interativo que divide os chamados em colunas visuais por status, facilitando a alteração e inserção de pareceres.
degrau_4_interface_streamlit/app.py):import streamlit as st
def main():
st.set_page_config(page_title="Chamados de Manutenção Predial", page_icon="🏢", layout="wide")
st.title("🏢 Gestão de Chamados — Manutenção Predial & Zeladoria")
# Métricas
col1, col2, col3, col4 = st.columns(4)
col1.metric("📋 Total de Chamados", "12")
col2.metric("🟡 Abertos", "4")
col3.metric("🔵 Em Andamento", "3")
col4.metric("🟢 Concluídos", "5")
degrau_4_interface_streamlit/main.py):"""
Launcher para o Degrau 4 (Streamlit Web)
Verifica se o pacote streamlit está instalado e inicia a aplicação no navegador.
"""
import os
import subprocess
import sys
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
APP_FILE = os.path.join(BASE_DIR, "app.py")
def main():
print("\n========================================================")
print("🌐 INICIANDO SISTEMA DE MANUTENÇÃO PREDIAL (STREAMLIT)")
print("========================================================\n")
try:
import streamlit # noqa: F401
print("✅ Streamlit detectado! Abrindo a aplicacao no seu navegador padrao...\n")
cmd = [sys.executable, "-m", "streamlit", "run", APP_FILE]
subprocess.run(cmd, cwd=BASE_DIR)
except ImportError:
print("⚠️ O pacote 'streamlit' nao foi encontrado no ambiente Python atual.")
print("\nPara instalar e rodar, execute no seu terminal:")
print(" pip install streamlit")
print(f" streamlit run \"{APP_FILE}\"\n")
if __name__ == "__main__":
main()
main.py)O arquivo main.py na raiz do projeto funciona como um painel de controle: permite executar qualquer um dos 4 degraus (ou toda a suíte de testes) a partir de um único menu no terminal, sem precisar navegar manualmente entre as pastas.
main.py):"""
Hub Central / Launcher — Miniprojeto 09: Chamados de Manutenção Predial
Permite executar de forma autoguiada qualquer um dos 4 degraus pedagógicos ou rodar a suíte de testes.
"""
import os
import subprocess
import sys
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
def rodar_script(caminho_relativo):
caminho_absoluto = os.path.join(BASE_DIR, caminho_relativo)
diretorio = os.path.dirname(caminho_absoluto)
print(f"\n🚀 Executando: {caminho_relativo}...\n")
try:
subprocess.run([sys.executable, caminho_absoluto], cwd=diretorio, check=True)
except KeyboardInterrupt:
print("\n\n⏹️ Execucao interrompida pelo usuario.")
except Exception as e:
print(f"\n❌ Erro durante a execucao: {e}")
def rodar_todos_os_testes():
print("\n🧪 ========================================================")
print(" RODANDO A SUÍTE DE TESTES DOS DEGRAUS 1, 2 E 3")
print("========================================================\n")
testes = [
("Degrau 1 (Imperativo & Arquivo JSON)", "degrau_1_imperativo_json/test_degrau_1.py"),
("Degrau 2 (Primeira Classe POO)", "degrau_2_primeira_classe/test_degrau_2.py"),
("Degrau 3 (Banco SQLite3 Relacional)", "degrau_3_sqlite/test_degrau_3.py"),
]
for nome, arq in testes:
print(f"▶️ Testando {nome}...")
caminho_abs = os.path.join(BASE_DIR, arq)
diretorio = os.path.dirname(caminho_abs)
res = subprocess.run([sys.executable, caminho_abs], cwd=diretorio)
if res.returncode == 0:
print(f"✅ {nome}: PASSOU!\n")
else:
print(f"❌ {nome}: FALHOU!\n")
def menu_hub():
while True:
print("\n========================================================")
print("🏢 MINIPROJETO 09: MANUTENÇÃO PREDIAL (HUB)")
print("========================================================")
print("1. 🪜 Executar Degrau 1 (Imperativo & Arquivo .json)")
print("2. 🪜 Executar Degrau 2 (Primeira Classe POO: class Chamado)")
print("3. 🪜 Executar Degrau 3 (Banco de Dados SQLite3 com Transições)")
print("4. 🪜 Executar Degrau 4 (Interface Web com Streamlit)")
print("5. 🧪 Rodar Todos os Testes Automatizados (Degraus 1, 2 e 3)")
print("6. 🚪 Sair")
print("========================================================")
opcao = input("Escolha o degrau que deseja executar (1-6): ").strip()
if opcao == "1":
rodar_script("degrau_1_imperativo_json/main.py")
elif opcao == "2":
rodar_script("degrau_2_primeira_classe/main.py")
elif opcao == "3":
rodar_script("degrau_3_sqlite/main.py")
elif opcao == "4":
rodar_script("degrau_4_interface_streamlit/main.py")
elif opcao == "5":
rodar_todos_os_testes()
elif opcao == "6":
print("👋 Encerrando o launcher. Bons estudos!")
break
else:
print("❌ Opcao invalida. Digite de 1 a 6.")
if __name__ == "__main__":
menu_hub()
# Executar a suíte de testes do Miniprojeto 09:
python -c "import subprocess, sys; [subprocess.run([sys.executable, p], check=True) for p in ['catalogo_projetos/sistemas_python_intro_09_manutencao_predial/degrau_1_imperativo_json/test_degrau_1.py', 'catalogo_projetos/sistemas_python_intro_09_manutencao_predial/degrau_2_primeira_classe/test_degrau_2.py', 'catalogo_projetos/sistemas_python_intro_09_manutencao_predial/degrau_3_sqlite/test_degrau_3.py']]"
# Ou, de forma ainda mais simples, use o Hub Central (opção 5 do menu):
python main.py
Resultado obtido:
Ran 1 test in 0.003s OK (Degrau 1)
Ran 2 tests in 0.005s OK (Degrau 2)
Ran 1 test in 0.038s OK (Degrau 3)
A imagem abaixo ilustra a esteira evolutiva dos 4 degraus didáticos do miniprojeto:
flowchart TD
subgraph S1["🪜 Degrau 1: Script Imperativo"]
A1["Menu Terminal while"] --> A2["Arquivo de Texto/JSON"]
end
subgraph S2["🪜 Degrau 2: Orientação a Objetos"]
B1["Modelo de Domínio POO"] --> B2["Encapsulamento de Regras"]
end
subgraph S3["🪜 Degrau 3: Banco Relacional"]
C1["SQLite3 Local"] --> C2["Transações ACID & Agregações"]
end
subgraph S4["🪜 Degrau 4: Interface Visual"]
D1["Aplicação Desktop / Web"] --> D2["UX Interativa Final"]
end
S1 --> S2
S2 --> S3
S3 --> S4
style S1 fill:#fffde7,stroke:#fbc02d,stroke-width:1.5px
style S2 fill:#e3f2fd,stroke:#1976d2,stroke-width:1.5px
style S3 fill:#e8f5e9,stroke:#388e3c,stroke-width:1.5px
style S4 fill:#f3e5f5,stroke:#7b1fa2,stroke-width:1.5px
| ID | Persona | História de Usuário | Critério de Aceite | Prioridade |
|---|---|---|---|---|
| US-01 | Síndico / Morador | Desejo abrir chamados de manutenção predial com local e gravidade em JSON | Persistir chamados com protocolo único em chamados.json | Alta |
| US-02 | Zelador / Técnico | Desejo atualizar o estado do chamado através de métodos da classe POO | Classe Chamado com transições de status (Aberto -> Em Atendimento -> Concluído) | Alta |
| US-03 | Administradora de Condomínio | Desejo histórico relacional no SQLite com SLA e custos de reparo | Tabelas de condomínios, unidades e manutenções com índices por status | Alta |
| US-04 | Gestor Predial | Desejo dashboard Streamlit com painel Kanban de chamados e métricas | Painel web interativo com filtros por bloco, gravidade e tempo de resposta | Alta |
| US-05 | Auditor Condominial | Desejo verificar que chamados fechados registrem a data de resolução | Testes unitários garantindo regras de finalização e consistência de status | Média |
| ID | Descrição do Requisito | Criticidade |
|---|---|---|
| RF-01 | Geração e persistência de ordens de serviço prediais em arquivo JSON | Alta |
| RF-02 | Máquina de estados finitos encapsulada na classe Chamado para controle de SLA | Alta |
| RF-03 | Estrutura relacional SQLite com consultas agrupadas por urgência e custo | Alta |
| RF-04 | Visualizador Web Streamlit com quadros de acompanhamento e formulários | Alta |
| RF-05 | Launcher de execução unificado e validações com unittest | Média |
| ID | Especificação Arquitetural | Criticidade |
|---|---|---|
| RNF-01 | Integridade transacional no SQLite com rollback em falhas de escrita | Alta |
| RNF-02 | Tempo de resposta imediato na atualização de status de chamados | Média |
| RNF-03 | Interface visual moderna e acessível para equipes de zeladoria | Média |
| RNF-04 | Conformidade com padrões PEP 8 e documentação semântica | Alta |
| RNF-05 | Isolamento independente de código entre os 4 degraus didáticos | Alta |
| User Story | Requisito Funcional | Requisito Não-Funcional | Módulo / Artefato de Código | Status de Homologação |
|---|---|---|---|---|
| US-01 | RF-01 |
RNF-01, RNF-04 |
degrau_1_imperativo_json/main.py |
Testado |
| US-02 | RF-02 |
RNF-04, RNF-05 |
degrau_2_primeira_classe/main.py |
Testado |
| US-03 | RF-03 |
RNF-01, RNF-02 |
degrau_3_sqlite/main.py |
Testado |
| US-04 | RF-04 |
RNF-02, RNF-03 |
degrau_4_interface_streamlit/app.py |
Testado |
| US-05 | RF-05 |
RNF-01, RNF-05 |
main.py & test_degrau_*.py |
Testado |
| Checkpoint | Escopo da Validação | Artefato Verificado | Status |
|---|---|---|---|
| CP-01 | Script Imperativo & Persistência Inicial | degrau_1_* |
PASS ✅ |
| CP-02 | Modelagem Orientada a Objetos | degrau_2_* |
PASS ✅ |
| CP-03 | Integridade Relacional no SQLite | degrau_3_* |
PASS ✅ |
| CP-04 | Interface Visual & Usabilidade | degrau_4_* |
PASS ✅ |
| CP-05 | Hub Launcher Central & Suíte de Testes | main.py + test_*.py |
PASS ✅ |