🎓 Portal do Curso: FATEC - GTI - DSM

🎓 Portal FATEC - GTI - DSM
Tecnólogo
📅 Semestre 2026/2 ✅ Atualizado: 2026 🏫 FATEC 📊 Avaliação: (P1+P2+A1+A2)/4
📚
4 Eixos Curriculares
📖
40 Capítulos Teóricos
📝
40 Atividades Práticas
🏛️
10 Projetos Setoriais
🧪
10 Quizzes Formativos

📂 Acesse seu conteúdo

🔗 Atalhos Rápidos

💡 Como navegar: Clique nos cards para acesso direto a cada módulo, ou use o menu lateral esquerdo para navegar capítulo a capítulo. A busca (S) funciona em todo o conteúdo do portal.
📊 Sistema de Avaliação: a nota final segue o modelo Fatec — Média Final = (P1 + P2 + A1 + A2) / 4, com aprovação direta a partir de 6,0. Cada página de Cronograma traz as semanas de prova, rubricas do Projeto Integrador e regras de Exame de Recuperação.

🛠️ Ferramentas Recomendadas (2023.1)

Para um melhor aproveitamento das disciplinas técnicas, reunimos abaixo as principais ferramentas, linguagens e utilitários utilizados no mercado e nas aulas. 🛡️


🌐 Editores Online

NomeLinguagem/TecnologiaDescriçãoLink
DB FiddleSQLAmbiente online para executar e testar comandos SQL diretamente no navegador.Acessar
Python FiddlePythonEditor e ambiente online para escrever, executar e testar códigos Python sem instalação local.Acessar

💻 IDEs e Editores de Código 2023.1

NomeVersãoDescriçãoLink
VS CodeAtualEditor de código-fonte leve e extensível.Download
IntelliJ IDEA Community2023.1IDE focada em desenvolvimento Java e Kotlin.Download
PyCharm Community2023.1IDE focada em desenvolvimento Python profissional.Download
Android Studio2023 (Hedgehog)IDE oficial para aplicativos Android.Download
Code::Blocks20.03IDE para C/C++ (inclui compilador MinGW).Download
Notepad++AtualEditor de texto avançado e versátil.Download

🧩 Extensões Essenciais (VS Code)

NomeVersãoDescriçãoLink
Java Extension PackExtensãoConjunto de extensões para desenvolvimento Java.Marketplace
Spring Boot Extension PackExtensãoSuporte para Spring Boot e Microserviços.Marketplace
Python ExtensionExtensãoSuporte completo para a linguagem Python.Marketplace
Node.js ExtensionsExtensãoDocumentação e ferramentas para Node.js.Link

🐘 Banco de Dados

NomeVersãoDescriçãoLink
MySQL Community8.0.x (LTS)Banco de Dados Relacional.Download
MySQL Workbench8.0.xFerramenta visual para design e gestão de MySQL.Download
PostgreSQL17.xBanco de Dados Relacional.Download
pgAdmin 4v8.14Gestão administrativa para PostgreSQL.Download
MongoDB Community7.0Banco de Dados NoSQL (Orientado a Documentos).Download
MongoDB Compass1.40.xInterface visual para MongoDB.Download
DBeaver CE23.xFerramenta universal de banco de dados e SQL.Download
Cassandra-Banco de Dados NoSQL Colunar.Download

☕ Linguagens e Ambientes

NomeVersãoDescriçãoLink
Java JDK17 (LTS)Kit de desenvolvimento Java.Download
Node.js22 (LTS)Ambiente de execução JavaScript server-side.Download
Python3.11 (LTS)Linguagem de programação de alto nível.Download
Docker DesktopAtualGestão de containers e microserviços.Download

⛓️ Controle de Versão e Gestão

NomeVersãoDescriçãoLink
Git SCM2.53Sistema de controle de versão distribuído.Download
GitHub DesktopAtualInterface gráfica para Git e GitHub.Download

📐 Modelagem e Design

NomeVersãoDescriçãoLink
Astah UMLCommunityFerramenta profissional para diagramas UML.Download
draw.io-Ferramenta de diagramação online/desktop.Download
FigmaDest/WebDesign de interfaces e prototipagem.Download
CanvaOnlinePlataforma de design gráfico online.Link
Astah 64bit-Instalador 64 bits (Google Drive).Drive
Astah 32bit-Instalador 32 bits (Google Drive).Drive

🧪 Aprendizado e Algoritmos

NomeVersãoDescriçãoLink
VisualG3.0Interpretador de algoritmos e pseudocódigo.Download
Portugol StudioAtualAmbiente para aprender programação em PT.Download
ScratchAtualProgramação visual por blocos.Download

🎨 Multimídia e Imagem

NomeVersãoDescriçãoLink
GIMPAtualEditor de imagens (Alternativa Photoshop).Download
InkscapeAtualGráficos vetoriais (Alternativa Corel/Illustrator).Download
KritaAtualPintura digital e ilustração.Download
AffinityV2Suíte de design profissional (Photo/Designer).Download

🎬 Áudio e Vídeo

NomeVersãoDescriçãoLink
KdenliveAtualEditor de vídeo não-linear profissional.Download
ShotcutAtualEditor de vídeo gratuito e open-source.Download
AudacityAtualEditor e gravador de áudio multi-faixa.Download
OBS StudioAtualGravação de tela e transmissão ao vivo.Download

📂 Utilitários Sistêmicos

NomeVersãoDescriçãoLink
7-ZipAtualCompactador e descompactador de arquivos.Download
Sumatra PDFAtualLeitor de arquivos PDF, ePub e Mobi leve.Download

[!IMPORTANT] 📗 Dica de Uso: Para os bancos de dados (MySQL e PostgreSQL), recomendamos desativar o serviço no boot automático para economizar RAM, iniciando-os manualmente apenas quando necessário.


🛠️ Ferramentas Recomendadas (2024.1)

Programas e Scripts

🔗 Acessar Programas e Scripts

🩺 GUIA DE TROUBLESHOOTING E FAQ DE ENGENHARIA

Durante o desenvolvimento de software e persistência de dados, encontrar erros e exceções no terminal faz parte natural da jornada de aprendizado.

Este guia reúne os erros mais comuns enfrentados pelos estudantes, explicando a causa raiz, o que a mensagem técnica significa e o passo a passo exato para resolver o problema em segundos. 🛡️⚡


🧭 Índice Rápido de Diagnóstico

flowchart TD
    E["🚨 Encontrou um Erro?"] --> API["🌐 1. Erros de API, Flask & Pydantic (400, 404, 405, Porta 5000)"]
    E --> ORM["🛢️ 2. Erros de Banco & SQLAlchemy (IntegrityError, Lock)"]
    E --> ENV["🐳 3. Erros de Docker, Portas & Venv (Porta 5432 Ocupada)"]
    E --> MIG["🔄 4. Erros de Alembic & Migrations (Multiple Heads)"]
    E --> JINJA["🎨 5. Erros de Template Jinja2 (TemplateNotFound)"]

    style E fill:#fee2e2,stroke:#ef4444
    style API fill:#e0f2fe,stroke:#0284c7
    style ORM fill:#fef3c7,stroke:#d97706
    style ENV fill:#dcfce7,stroke:#16a34a
    style MIG fill:#f3e8ff,stroke:#9333ea
    style JINJA fill:#fce7f3,stroke:#db2777

🌐 1. Erros de API, Flask 3.x e Pydantic

❌ Erro 1.1: Porta 5000 já em uso (OSError: [WinError 10048])

  • Sintoma: Ao executar python -m app.main, o terminal exibe OSError: [WinError 10048] Only one usage of each socket address is normally permitted.
  • Causa Raiz: Uma instância anterior do servidor Flask ainda está rodando em segundo plano no Windows e segurando a porta 5000.
  • Como Resolver: Abra o PowerShell e encerre os processos Python ativos:
Get-Process python* | Stop-Process -Force

❌ Erro 1.2: 400 Bad Request ao validar payload com Pydantic

  • Sintoma: Ao enviar uma requisição POST ou PUT, a API retorna 400 Bad Request com o detalhe do erro do Pydantic ou request.get_json() retorna None.
  • Causa Raiz: O cliente não enviou o cabeçalho Content-Type: application/json ou faltou algum campo obrigatório exigido pelo Schema.
  • Como Resolver:
    1. No PowerShell com Invoke-RestMethod, garanta o parâmetro -ContentType "application/json".
    2. Verifique se o JSON contém todas as chaves exigidas pelo schema Pydantic.
    3. No handler do Flask, utilize sempre tratamento com bloco try/except:
dados = request.get_json() or {}
try:
    payload = MeuSchema(**dados)
except Exception as err:
    return jsonify({"detail": str(err)}), 400

❌ Erro 1.3: 405 Method Not Allowed

  • Sintoma: Ao submeter um formulário ou chamar um endpoint, o navegador exibe 405 Method Not Allowed.
  • Causa Raiz: O endpoint foi registrado para um verbo HTTP (ex: @app.get("/usuarios")), mas a requisição foi feita usando outro método (ex: POST).
  • Como Resolver:
    1. No formulário HTML, confira a tag: <form action="/usuarios" method="POST">.
    2. No arquivo do router Flask, certifique-se de que o método HTTP coincide:
@router.route("/usuarios", methods=["POST"])
# ou:
@router.post("/usuarios")

❌ Erro 1.4: Erro de CORS (Blocked by CORS Policy)

  • Sintoma: O frontend não consegue se comunicar com a API e o console do navegador exibe: Access to XMLHttpRequest at 'http://localhost:5000/api' has been blocked by CORS policy.
  • Causa Raiz: Por segurança, navegadores bloqueiam requisições JavaScript vindas de origens/portas diferentes.
  • Como Resolver: Adicione os headers CORS ou utilize flask-cors:
@app.after_request
def add_cors_headers(response):
    response.headers["Access-Control-Allow-Origin"] = "*"
    response.headers["Access-Control-Allow-Headers"] = "Content-Type,Authorization"
    response.headers["Access-Control-Allow-Methods"] = "GET,POST,PUT,DELETE,OPTIONS"
    return response

🛢️ 2. Erros de Banco de Dados & SQLAlchemy 2.0

❌ Erro 2.1: sqlalchemy.exc.IntegrityError: NOT NULL constraint failed

  • Sintoma: Falha ao tentar salvar um registro com session.commit().
  • Causa Raiz: O modelo possui uma coluna com nullable=False (ou Mapped[str] sem | None), mas o objeto foi instanciado sem preencher esse valor.
  • Como Resolver: Garanta que todos os campos obrigatórios sejam passados no construtor do modelo antes de executar session.add():
# ❌ Incorreto:
novo_cliente = Cliente(email="carlos@email.com") # Faltou o nome!

# ✅ Correto:
novo_cliente = Cliente(nome="Carlos Silva", email="carlos@email.com")

❌ Erro 2.2: sqlite3.OperationalError: database is locked

  • Sintoma: O SQLite trava e emite erro informando que o arquivo de banco de dados está bloqueado.
  • Causa Raiz: Uma sessão anterior abriu uma transação de escrita e não foi encerrada com session.commit(), session.rollback() ou session.close(), ou o banco está aberto para edição concorrente em outra ferramenta (como DB Browser).
  • Como Resolver:
    1. Garanta que a função get_db() utilize yield e finally: db.close().
    2. Feche conexões manuais abertas em ferramentas externas.
    3. No database.py, configure timeout para o SQLite:
engine = create_engine(
    "sqlite:///dev.db",
    connect_args={"timeout": 30} # Aguarda até 30s antes de falhar
)

❌ Erro 2.3: sqlalchemy.orm.exc.DetachedInstanceError

  • Sintoma: Erro ao acessar um relacionamento (ex: usuario.pedidos) no template Jinja2 ou após sair da função de rota.
  • Causa Raiz: O SQLAlchemy usa carregamento preguiçoso (Lazy Loading) por padrão. Quando a sessão do banco foi fechada pelo get_db(), o objeto perdeu a conexão ativa necessária para buscar os dados filhos.
  • Como Resolver: Utilize joinedload() ou selectinload() na consulta para trazer os relacionamentos de forma ansiosa (Eager Loading):
from sqlalchemy.orm import selectinload

stmt = select(Usuario).options(selectinload(Usuario.pedidos)).where(Usuario.id == 1)
usuario = session.scalars(stmt).first()

🐳 3. Erros de Docker, Portas e Ambiente Virtual

❌ Erro 3.1: Bind for 0.0.0.0:5432 failed: port is already allocated

  • Sintoma: Ao rodar docker compose up -d, o terminal acusa que a porta 5432 já está em uso.
  • Causa Raiz: O serviço do PostgreSQL nativo do Windows já está rodando em segundo plano e ocupando a porta 5432 da máquina física.
  • Como Resolver (Opção A — Parar o serviço do Windows):
    • Abra o PowerShell como Administrador e execute:
Stop-Service postgresql*
  • Como Resolver (Opção B — Mudar a porta no docker-compose.yml):
    • Mude o mapeamento para usar a porta 5433 no seu computador:
ports:
  - "5433:5432" # 5433 no Windows -> 5432 dentro do container
  • Atualize seu .env: DATABASE_URL=postgresql://postgres:senha@localhost:5433/meu_db.

❌ Erro 3.2: Erro de Permissão ao Ativar o Venv no PowerShell (Script Execution Policy)

  • Sintoma: Ao rodar .\venv\Scripts\Activate.ps1, o PowerShell exibe: não pode ser carregado porque a execução de scripts foi desabilitada neste sistema.
  • Causa Raiz: Política de segurança padrão do Windows para scripts locais.
  • Como Resolver: Abra o PowerShell e libere para o seu usuário atual:
Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned

🔄 4. Erros de Migrações com Alembic

❌ Erro 4.1: alembic.util.exc.CommandError: Target database is not up to date

  • Sintoma: Erro ao tentar rodar alembic revision --autogenerate.
  • Causa Raiz: Existem migrações anteriores no histórico que ainda não foram aplicadas no banco de dados atual.
  • Como Resolver: Aplique todas as migrações pendentes antes de gerar uma nova:
alembic upgrade head
alembic revision --autogenerate -m "nova_alteracao"
alembic upgrade head

❌ Erro 4.2: Conflito de Versões (Multiple head revisions are present)

  • Sintoma: Conflito após fazer git pull de alterações de outro colega de grupo no Projeto Integrador.
  • Causa Raiz: Dois alunos criaram migrações simultaneamente em branches separadas.
  • Como Resolver: Crie uma migração de merge para unir as duas pontas:
alembic merge heads -m "merge_conflitos"
alembic upgrade head

🎨 5. Erros de Template e Renderização Jinja2

❌ Erro 5.1: jinja2.exceptions.TemplateNotFound: index.html

  • Sintoma: A aplicação Flask falha com erro de template não encontrado ao tentar renderizar a view.
  • Causa Raiz: O caminho informado no template_folder do Flask está incorreto ou a pasta templates não está no mesmo nível do diretório app/.
  • Como Resolver: Configure o caminho absoluto baseado na raiz do diretório app/:
from pathlib import Path
from flask import Flask, render_template

BASE_DIR = Path(__file__).resolve().parent
app = Flask(__name__, template_folder=str(BASE_DIR / "templates"))

❌ Erro 5.2: jinja2.exceptions.UndefinedError: 'item' is undefined

  • Sintoma: A página quebra durante a renderização no navegador.
  • Causa Raiz: O template .html tenta acessar uma variável {{ item.nome }}, mas ela não foi passada nos argumentos do render_template(...).
  • Como Resolver: Verifique a rota no main.py e certifique-se de passar o objeto nomeado no retorno:
@app.route("/detalhes/<int:id>")
def detalhes(id: int):
    item_do_banco = ...
    return render_template("detalhes.html", item=item_do_banco)

💡 Regra de Ouro do Engenheiro de Software

Não tenha medo dos erros: Leia atentamente a última linha do rastreamento (Traceback). Mais de 90% dos problemas de desenvolvimento são resolvidos conferindo tipos de dados, caminhos de arquivo e conexões ativas! 🚀🛡️

📑 CHEATSHEETS DE ENGENHARIA E BANCO DE DADOS

Este guia reúne as sintaxes e comandos mais utilizados no dia a dia do desenvolvimento backend corporativo, servindo como material oficial de consulta rápida para laboratórios práticos, avaliações e desenvolvimento de Projetos Integradores. ⚡🛡️


🧱 1. SQLAlchemy 2.0 (CRUD & Consultas Modernas)

flowchart LR
    S["select(Model)"] --> W[".where(...)"]
    W --> J[".join(...)"]
    J --> O[".order_by(...)"]
    E["session.scalars(...).all()"]
    
    S --> W
    W --> J
    J --> O
    O --> E
    
    style S fill:#e3f2fd,stroke:#1565c0
    style W fill:#fff8e1,stroke:#f57f17
    style J fill:#e8f5e9,stroke:#2e7d32
    style O fill:#f3e5f5,stroke:#7b1fa2
    style E fill:#e0f2f1,stroke:#00695c

📋 Mapeamento Declarativo de Modelos:

from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship
from sqlalchemy import String, Integer, ForeignKey, Numeric
from decimal import Decimal

class Base(DeclarativeBase):
    pass

class Cliente(Base):
    __tablename__ = "clientes"
    id_cliente: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    nome: Mapped[str] = mapped_column(String(100), nullable=False)
    limite_credito: Mapped[Decimal] = mapped_column(Numeric(10, 2), default=Decimal("0.00"))
    pedidos: Mapped[list["Pedido"]] = relationship(back_populates="cliente", cascade="all, delete-orphan")

🔍 Operações de CRUD:

from sqlalchemy import select, func
from sqlalchemy.orm import selectinload

# 1. READ (Buscar por ID):
cliente = session.get(Cliente, 1)

# 2. READ (Filtrar múltiplos com WHERE e ORDER BY):
stmt = select(Cliente).where(Cliente.limite_credito > 1000).order_by(Cliente.nome.asc())
clientes = session.scalars(stmt).all()

# 3. READ (Com Eager Loading de Relacionamento):
stmt = select(Cliente).options(selectinload(Cliente.pedidos)).where(Cliente.id_cliente == 1)
cliente_com_pedidos = session.scalars(stmt).first()

# 4. CREATE (Inserir novo registro):
novo = Cliente(nome="Transportadora VIP", limite_credito=Decimal("5000.00"))
session.add(novo)
session.commit()
session.refresh(novo) # Popula o ID gerado

# 5. UPDATE (Atualizar campos):
cliente.limite_credito = Decimal("7500.00")
session.commit()

# 6. DELETE (Excluir registro):
session.delete(cliente)
session.commit()

# 7. AGGREGATE (Contagem e Média):
stmt = select(func.count(Cliente.id_cliente), func.avg(Cliente.limite_credito))
total, media = session.execute(stmt).one()

🌐 2. Flask 3.x & SQLAlchemy 2.0 (Rotas, Blueprints e JSON)

from flask import Flask, Blueprint, request, jsonify
from pydantic import BaseModel, Field
from sqlalchemy.orm import Session
from sqlalchemy import select

app = Flask(__name__)
bp = Blueprint("clientes", __name__, url_prefix="/api")

# Schema Pydantic de Entrada (Validação de Tipos)
class ClienteCreate(BaseModel):
    nome: str = Field(..., min_length=3, max_length=100)
    limite_credito: float = Field(0.0, ge=0.0)

# Rota POST (Criar registro com Validação Pydantic)
@bp.post("/clientes")
def criar_cliente():
    dados = request.get_json() or {}
    try:
        payload = ClienteCreate(**dados)
    except Exception as err:
        return jsonify({"detail": str(err)}), 400

    with SessionLocal() as session:
        cliente = Cliente(**payload.model_dump())
        session.add(cliente)
        session.commit()
        session.refresh(cliente)
        return jsonify({
            "id_cliente": cliente.id_cliente,
            "nome": cliente.nome,
            "limite_credito": float(cliente.limite_credito)
        }), 201

# Rota GET (Buscar por ID com Tratamento de Erro 404)
@bp.get("/clientes/<int:id_cliente>")
def buscar_cliente(id_cliente: int):
    with SessionLocal() as session:
        cliente = session.get(Cliente, id_cliente)
        if not cliente:
            return jsonify({"detail": "Cliente não encontrado."}), 404
        return jsonify({
            "id_cliente": cliente.id_cliente,
            "nome": cliente.nome,
            "limite_credito": float(cliente.limite_credito)
        }), 200

app.register_blueprint(bp)

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5000, debug=True)

🛢️ 3. SQL Corporativo (DML, DQL e Relatórios)

📋 Ordem Obrigatória de Execução da Consulta:

  1. FROM & JOIN (Localiza as tabelas)
  2. WHERE (Filtra linhas individuais)
  3. GROUP BY (Agrupa dados)
  4. HAVING (Filtra grupos calculados)
  5. SELECT (Projeta colunas e funções)
  6. ORDER BY (Ordena o resultado)
  7. LIMIT & OFFSET (Pagina os dados)

💻 Queries de Alto Desempenho:

-- 1. INNER JOIN (Relatório com 2 tabelas):
SELECT p.id_pedido, c.nome, p.valor_total
FROM pedido p
INNER JOIN cliente c ON p.id_cliente = c.id_cliente
WHERE p.status = 'CONCLUIDO';

-- 2. LEFT JOIN (Encontrar clientes que NUNCA compraram):
SELECT c.id_cliente, c.nome
FROM cliente c
LEFT JOIN pedido p ON c.id_cliente = p.id_cliente
WHERE p.id_pedido IS NULL;

-- 3. GROUP BY + HAVING (Categorias com faturamento > R$ 10.000):
SELECT categoria, COUNT(*) AS total_itens, SUM(preco) AS faturamento
FROM produto
GROUP BY categoria
HAVING SUM(preco) > 10000
ORDER BY faturamento DESC;

-- 4. Subquery com EXISTS (Mais rápida que IN):
SELECT nome FROM fornecedor f
WHERE EXISTS (
    SELECT 1 FROM materia_prima mp 
    WHERE mp.id_fornecedor = f.id_fornecedor AND mp.estoque_atual < 10
);

-- 5. INSERT com RETURNING (Retorna ID gerado sem segundo SELECT):
INSERT INTO clientes (nome, limite_credito) 
VALUES ('Logística Express', 3000.00) 
RETURNING id_cliente, nome;

🐳 4. Git & Docker Compose (Terminal do Desenvolvedor)

OperaçãoComando no TerminalO que faz?
Ativar Venv (Windows).\venv\Scripts\Activate.ps1Ativa o ambiente virtual isolado.
Ativar Venv (Linux/Mac)source venv/bin/activateAtiva o ambiente virtual no terminal Unix.
Instalar Dependênciaspip install -r requirements.txtInstala pacotes do projeto.
Executar Servidor Flaskpython -m app.mainInicia o servidor web Flask na porta 5000.
Executar Testes (Pytest)pytest -vRoda a suíte completa de testes unitários.
Subir Banco (Docker)docker compose up -dInicia o PostgreSQL em segundo plano.
Parar Banco (Docker)docker compose downEncerra os contêineres liberando memória.
Ver Logs do Bancodocker compose logs -f dbAcompanha logs do PostgreSQL em tempo real.
Status do Gitgit statusExibe arquivos modificados e prontos.
Criar Nova Branchgit checkout -b feature/minha-telaCria e entra em uma branch de trabalho.
Salvar Alteraçõesgit commit -m "feat: minha feature"Registra o commit no histórico local.
Enviar ao GitHubgit push origin feature/minha-telaPublica o branch no repositório remoto.

🧰 CAPÍTULO 00: KIT DE SOBREVIVÊNCIA — FUNDAMENTOS COMUNS


📖 Para quem é este capítulo

Este capítulo é autoguiado e sem nota. Ele reúne o que as Atividades 01 das duas trilhas (Engenharia de Software e Banco de Dados) já pressupõem que você sabe fazer desde a Semana 1, mas que nenhum capítulo ensina isoladamente: usar o terminal, versionar código com Git, ler e escrever Python orientado a objetos, e entender JSON/HTTP o suficiente para copiar um curl e saber o que ele está fazendo.

Se você já programa em Python e já usa Git no dia a dia, pode pular direto para o capítulo 00 da sua trilha:
👉 Capítulo 00 — Engenharia de Software · 👉 Capítulo 00 — Banco de Dados

🎯 Objetivos de Aprendizagem

Estimativa de dedicação: 3 horas de estudo autoguiado. Ao final deste capítulo, você será capaz de:

  • 🔹 Navegar pastas e executar scripts pelo terminal do VS Code.
  • 🔹 Versionar um projeto com Git e enviá-lo para o GitHub (clone, add, commit, push).
  • 🔹 Ler e escrever classes Python com @dataclass, @property e validação de dados.
  • 🔹 Interpretar um payload JSON e montar/entender um comando curl contra uma API REST.

🏢 Por que isso importa (Cenário TecProExpress)

Todas as atividades práticas do curso — Engenharia de Software e Banco de Dados — giram em torno da TecProExpress, uma empresa fictícia de logística. Desde a primeira semana, os roteiros já pedem para você "clonar o repositório", "rodar python script.py" ou "testar com curl" sem parar para explicar esses três movimentos. Este capítulo existe para que, quando isso acontecer, você já tenha feito esse gesto pelo menos uma vez.


🧠 1. Terminal e VS Code

Um terminal é apenas um jeito de conversar com o computador por texto em vez de cliques. Os três comandos que você vai repetir centenas de vezes neste curso:

ComandoO que fazExemplo
cdEntra em uma pastacd atividades-banco-de-dados
dir (Windows) / ls (Linux/Mac)Lista o conteúdo da pasta atualdir
python arquivo.pyExecuta um script Pythonpython app.py

🎯 Abrindo o terminal certo no VS Code

No VS Code, use o atalho Ctrl + ` (crase) para abrir o terminal integrado — ele já abre na pasta do projeto que você tem aberta, sem precisar navegar manualmente.

Instale também as extensões recomendadas do projeto: abra a pasta do repositório no VS Code e aceite a notificação para instalar o pacote definido em .vscode/extensions.json (Python, SQLTools, Docker, Draw.io).


🧠 2. Git e GitHub do Zero

Git guarda o histórico do seu código no seu computador. GitHub guarda uma cópia desse histórico na nuvem, para o professor avaliar e para você nunca perder o trabalho.

flowchart LR
    A["📁 Pasta local"] -->|"git init / git clone"| B["📦 Repositório Git local"]
    B -->|"git add ."| C["🗂️ Área de Stage"]
    C -->|"git commit -m '...'"| D["📌 Commit (snapshot local)"]
    D -->|"git push"| E["☁️ GitHub (remoto)"]

    style A fill:#e3f2fd,stroke:#1e88e5
    style D fill:#fff8e1,stroke:#fbc02d
    style E fill:#e8f5e9,stroke:#43a047

🛠️ O ciclo que você vai repetir toda semana

# 1. Clone o repositório de atividades (uma única vez)
git clone https://github.com/SEU-USUARIO/atividades-banco-de-dados.git
cd atividades-banco-de-dados

# 2. Depois de editar/criar arquivos, veja o que mudou
git status

# 3. Selecione o que vai entrar no commit
git add .

# 4. Registre um snapshot com uma mensagem clara
git commit -m "feat: adiciona script da atividade 01"

# 5. Envie para o GitHub
git push

🔍 Detalhamento do Comando:

  • git status: mostra o que mudou desde o último commit — rode antes de qualquer outro comando, sempre.
  • git add .: coloca todos os arquivos modificados na fila do próximo commit.
  • git commit -m "...": a mensagem deve dizer o quê e por quê (ex: fix: corrige cálculo de frete, não mudanças).
  • git push: só depois disso o professor consegue ver seu trabalho no GitHub.

⚠️ Erro clássico

Se git push recusar com "rejected", normalmente é porque o GitHub tem uma versão mais nova do que a sua. Rode git pull primeiro para trazer as mudanças remotas, resolva qualquer conflito, e só então git push de novo.


🧠 3. Python Essencial e POO

Praticamente todo capítulo teórico do curso (Engenharia de Software e Banco de Dados) usa classes Python para modelar entidades do mundo real. Os quatro ingredientes que você precisa reconhecer:

IngredientePara que serve
class NomeDaClasse:Define um "molde" para criar objetos (ex: um Produto, um Cliente).
selfDentro da classe, representa "este objeto específico".
@dataclassGera automaticamente o construtor (__init__) a partir dos atributos declarados.
@propertyTransforma um método em um atributo "calculado" (chamado sem parênteses).

💻 Código Completo e Autocontido (produto.py)

"""
Módulo: produto.py
Domínio: Representação de um produto do catálogo da TecProExpress.
Execução: python produto.py
"""
from dataclasses import dataclass, field

@dataclass
class Produto:
    nome: str
    preco: float
    tags: list[str] = field(default_factory=list)

    @property
    def preco_formatado(self) -> str:
        """Atributo calculado: formata o preço como moeda brasileira."""
        return f"R$ {self.preco:.2f}"

    def aplicar_desconto(self, percentual: float) -> None:
        if not 0 <= percentual <= 100:
            raise ValueError("Percentual de desconto deve estar entre 0 e 100.")
        self.preco -= self.preco * (percentual / 100)


if __name__ == "__main__":
    produto = Produto(nome="Notebook TecPro X1", preco=3500.00, tags=["eletronico", "oferta"])
    print(f"Antes do desconto: {produto.preco_formatado}")

    produto.aplicar_desconto(10)
    print(f"Depois de 10% de desconto: {produto.preco_formatado}")

🚀 Como Executar

python produto.py

🖥️ Saída Esperada no Terminal

Antes do desconto: R$ 3500.00
Depois de 10% de desconto: R$ 3150.00

🔍 Detalhamento do Código:

  • field(default_factory=list): evita o erro clássico de usar uma lista vazia ([]) diretamente como valor padrão em Python.
  • aplicar_desconto: valida o percentual antes de aplicar — o objeto nunca fica em um estado inconsistente (preço negativo, por exemplo).
  • @property: repare que chamamos produto.preco_formatado, sem parênteses — para quem usa a classe, parece um atributo comum.

💡 Onde isso aparece de novo

Esse mesmo padrão (@dataclass + @property + validação no construtor) é a base do Capítulo 01 de Engenharia de Software e de praticamente todo mini-projeto SQLAlchemy dos capítulos de Banco de Dados. Se este código fez sentido, você está pronto para a Semana 1.


🧠 4. JSON e HTTP/REST na Prática

Desde a Atividade 06 (Engenharia de Software) e a Atividade 06 (Banco de Dados), os roteiros pedem para testar uma API com curl. Aqui está o que você precisa para não ficar copiando sem entender.

JSON é só um dicionário Python escrito como texto:

{
  "nome": "Notebook TecPro X1",
  "preco": 3500.00,
  "tags": ["eletronico", "oferta"]
}

HTTP é o "idioma" que o navegador (ou o curl) usa para conversar com um servidor:

Verbo HTTPUso típicoStatus de sucesso comum
GETBuscar um recurso200 OK
POSTCriar um recurso novo201 Created
PUTAtualizar um recurso inteiro200 OK
DELETERemover um recurso204 No Content
Status HTTPSignificado
200 / 201Sucesso
400O cliente mandou dados inválidos
404O recurso não existe
500Erro interno do servidor

🔍 Anatomia de um curl

curl -X POST "http://127.0.0.1:5000/api/pacotes" \
     -H "Content-Type: application/json" \
     -d '{"codigo": "BR123456", "peso_kg": 2.5}'
  • -X POST: qual verbo HTTP usar.
  • "http://127.0.0.1:5000/...": o endereço do servidor — 127.0.0.1 é "meu próprio computador"; 5000 é a porta onde o servidor está escutando.
  • -H "Content-Type: application/json": avisa ao servidor que o corpo da requisição é JSON.
  • -d '{...}': o corpo (body) da requisição — os dados que você está enviando.

💡 Checkpoint de Lógica

Importante

Reflexão Profissional: Por que o Git guarda um histórico de commits em vez de simplesmente sobrescrever o arquivo mais recente? (Resposta: porque em equipe, várias pessoas mexem no mesmo código ao mesmo tempo — o histórico permite entender quem mudou o quê e quando, reverter um erro específico sem perder o resto do trabalho, e resolver conflitos quando duas pessoas editam a mesma linha.) 🧠🛡️


🧪 Quiz de Fixação e Autoavaliação

🧪 Quiz de Autoavaliação — Capítulo 00 (Fundamentos Comuns)

1. Qual comando envia seus commits locais para o GitHub?

  • A) git commit
  • B) git add .
  • C) git push
  • D) git status
💡 Ver Resposta e Justificativa

Resposta Correta: C
Justificativa: git commit só registra o snapshot localmente. É o git push que envia esse histórico para o repositório remoto no GitHub.


2. Em Python, o que o decorador @dataclass gera automaticamente para a classe?

  • A) Um servidor Flask
  • B) O método __init__ (construtor), a partir dos atributos declarados
  • C) Uma tabela SQL
  • D) Um arquivo JSON
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: @dataclass elimina a necessidade de escrever __init__ manualmente — ele é gerado a partir dos atributos anotados na classe.


3. Ao rodar curl -X POST .../api/pacotes -d '{"peso_kg": -5}' e o servidor responder com status 400, o que isso indica?

  • A) O servidor caiu.
  • B) O recurso foi criado com sucesso.
  • C) O cliente enviou dados inválidos e o servidor recusou a requisição.
  • D) O recurso não existe.
💡 Ver Resposta e Justificativa

Resposta Correta: C
Justificativa: A faixa 4xx de status HTTP indica erro do lado do cliente — aqui, provavelmente um peso negativo reprovado por uma validação como a que vimos em aplicar_desconto.


🛠️ Ponte para a Sua Trilha

🎯 Próximo Passo

Agora escolha o capítulo 00 específico da sua disciplina — ele assume tudo o que você acabou de ver aqui e foca no que só aquela trilha exige logo na Semana 1:

👉 Capítulo 00 — Engenharia de Software: UML, Flask e pytest 👉 Capítulo 00 — Banco de Dados: SQL, ER e Docker


📌 Resumo Executivo & Key Takeaways

  • Terminal: cd para navegar, python arquivo.py para executar — é só isso que você precisa para começar.
  • Git: o ciclo status → add → commit -m "..." → push se repete em toda entrega do curso.
  • POO em Python: @dataclass gera o construtor; @property cria atributos calculados; valide dados no construtor para evitar objetos inconsistentes.
  • HTTP/REST: verbo (GET/POST/...) + endereço + corpo JSON é a receita de toda requisição que você vai testar com curl neste curso.

Engenharia de Software e Aplicações

📋 PLANO DE ENSINO ATUALIZADO

Carga Horária: 80 aulas presenciais + 40 atividades autônomas
Curso: Tecnólogo - FATEC

🎯 OBJETIVO GERAL

Capacitar o aluno a aplicar princípios da Engenharia de Software no desenvolvimento de sistemas, utilizando metodologias, técnicas e ferramentas modernas para análise, projeto, implementação e validação de software.

🧪 METODOLOGIA

  • Aulas expositivas + práticas.
  • Aprendizado baseado em projetos (PBL).
  • Estudos de caso reais.
  • Uso de ferramentas do mercado (Git, Docker, Jira, Figma).

📊 AVALIAÇÃO

TipoPeso
Exercícios e atividades20%
Trabalhos práticos30%
Projeto final40%
Participação10%

🚀 DIFERENCIAL TECNOLÓGICO

Integração de stacks modernas para o projeto final:

  • Backend: Spring Boot (Java)
  • Frontend: Angular
  • Banco: PostgreSQL
  • DevOps: Docker & Git

🧭 MÓDULOS DO CURSO

MóduloDescrição / Capítulos Relacionados
1Introdução à Engenharia de Software
Cap 01: Introdução e Natureza do Software
2Ciclo de Vida de Software
Cap 02: Modelos de Processo de Software
Cap 03: As Atividades do Processo
3Metodologias Ágeis
Cap 04: Metodologias Ágeis
4Engenharia de Requisitos
Cap 05: Fundamentos de Requisitos
Cap 06: Elicitação e Levantamento
Cap 07: Especificação de Requisitos (ERS)
Cap 08: Validação e Gestão
5Fundamentos de Modelagem
Cap 09: Fundamentos da Modelagem
6Diagrama de Casos de Uso
Cap 10: Diagrama de Casos de Uso (Conceitos)
Cap 11: Casos de Uso (Prática e Relações)
7Diagrama de Classes (UML)
Cap 12: Diagrama de Classes (Conceitos)
Cap 13: Herança e Polimorfismo
8Diagrama de Sequência
Cap 14: Diagrama de Sequência
9Diagramas Dinâmicos
Cap 15: Diagramas Dinâmicos (Estados e Atividades)
10Qualidade de Software (SQA)
Cap 16: Qualidade de Software (SQA)
11Estratégias de Teste
Cap 17: Estratégias de Teste
12Manutenção e Configuração (SCM)
Cap 18: Manutenção e Evolução
Cap 19: Gerência de Configuração (SCM)
13🚀 Conclusão e Próximos Passos
Cap 20: Conclusão e Próximos Passos

📖 BIBLIOGRAFIA BÁSICA

  • PILONE, Dan; MILES, Russell. Use A Cabeça - Desenvolvimento de Software.
  • PRESSMAN, R. S. Engenharia de Software.
  • SOMERVILLE, I. Engenharia de Software.

📖 BIBLIOGRAFIA COMPLEMENTAR

  • GUEDES, G. UML 2 – Uma Abordagem Prática.
  • YOURDON, E. Análise Estruturada Moderna.

🧰 CAPÍTULO 00: KIT DE SOBREVIVÊNCIA — ENGENHARIA DE SOFTWARE


📖 Pré-requisito

Este capítulo assume que você já passou pelo Capítulo 00 — Fundamentos Comuns (terminal, Git, POO em Python, JSON/HTTP). Se ainda não viu, comece por lá.

🎯 Objetivos de Aprendizagem

Estimativa de dedicação: 2 horas de estudo autoguiado. Ao final deste capítulo, você será capaz de:

  • 🔹 Ler (não desenhar) os três diagramas UML mais usados no curso: Casos de Uso, Classes e Sequência.
  • 🔹 Subir seu primeiro servidor Flask e responder a uma requisição real.
  • 🔹 Escrever e rodar seu primeiro teste automatizado com pytest.

🏢 Por que isso importa

A Atividade 05 já pede um Diagrama de Casos de Uso, a Atividade 06 já pede para rodar um servidor Flask real e testá-lo com curl, e a Atividade 09 já pede testes com pytest — tudo isso antes da teoria completa de cada tópico ter sido consolidada. Este capítulo adianta o mínimo necessário para você não travar nesses pontos.


🧠 1. Como Ler um Diagrama UML

Você não precisa saber desenhar UML perfeitamente ainda — precisa saber ler os três tipos que mais aparecem no curso.

📌 Caso de Uso: "o que o sistema faz, por quem"

Um boneco (ator) se conecta a ações (óvalos). Uma seta pontilhada com <<include>> significa "esse caso de uso sempre aciona o outro".

flowchart LR
    Cliente(("👤 Cliente"))
    UC1(["Rastrear Pacote"])
    UC2(["Cadastrar Pacote"])
    UC3(["Validar CEP"])

    Cliente --> UC1
    Cliente --> UC2
    UC2 -.->|"《include》"| UC3

    style Cliente fill:#e3f2fd,stroke:#1e88e5
    style UC3 fill:#fff8e1,stroke:#fbc02d

Leitura: o Cliente pode Rastrear ou Cadastrar um Pacote; cadastrar sempre inclui validar o CEP.

📌 Classes: "quais entidades existem e como se relacionam"

Cada caixa é uma classe (atributos em cima, métodos embaixo). O número perto da linha é a multiplicidade (quantos de cada lado).

classDiagram
    class Cliente {
        -nome: str
        -email: str
    }
    class Pacote {
        -codigo: str
        -peso_kg: float
        +calcular_frete() float
    }
    Cliente "1" --> "*" Pacote : envia

Leitura: 1 Cliente pode enviar * (zero ou muitos) Pacotes.

📌 Sequência: "quem chama quem, em que ordem, ao longo do tempo"

Cada linha vertical é um participante. As setas horizontais são mensagens, lidas de cima para baixo.

sequenceDiagram
    actor Cliente
    participant App as App Web
    participant API as API Flask

    Cliente->>App: Rastrear pacote BR123456
    App->>API: GET /api/pacotes/BR123456
    activate API
    API-->>App: 200 OK + status JSON
    deactivate API
    App-->>Cliente: Mostra status na tela

Leitura: o Cliente pede à tela, a tela pede à API, a API responde, a tela mostra o resultado — nessa ordem exata.

🎯 Ferramenta oficial de desenho

Para desenhar seus próprios diagramas (não só ler), o curso usa o draw.io (extensão hediet.vscode-drawio já no .vscode/extensions.json). O Mermaid acima é só para você aprender a ler rápido, direto no navegador.


🧠 2. Flask: Seu Primeiro "Hello World"

A partir da Atividade 06, você vai rodar um servidor Flask de verdade. Vamos fazer isso uma vez, com calma.

📋 Instalação

pip install flask

💻 Código Completo (app.py)

"""
Módulo: app.py
Primeiro servidor Flask da jornada TecProExpress.
Execução: python app.py
"""
from flask import Flask, request, jsonify

app = Flask(__name__)

@app.route("/")
def index():
    return "🚚 TecProExpress API no ar!"

@app.route("/api/saudacao", methods=["POST"])
def saudacao():
    dados = request.get_json() or {}
    nome = dados.get("nome", "visitante")
    return jsonify({"mensagem": f"Olá, {nome}! Bem-vindo à TecProExpress."}), 200

if __name__ == "__main__":
    print("🚀 Servidor Flask ativo em: http://127.0.0.1:5000")
    app.run(debug=True, port=5000)

🚀 Como Executar

python app.py

🌐 Testando com curl (em outro terminal)

curl -X POST http://127.0.0.1:5000/api/saudacao \
     -H "Content-Type: application/json" \
     -d "{\"nome\": \"Ana\"}"

🖥️ Resposta Esperada

{"mensagem": "Olá, Ana! Bem-vindo à TecProExpress."}

🧩 Alternativa ao curl: extensão de navegador

Se preferir uma interface gráfica em vez de digitar comandos no terminal, use a extensão Yet Another REST Client (YARC) no Chrome (yet-another-rest-client.com):

  1. Instale a extensão na Chrome Web Store e abra-a.
  2. Configure o método POST e a URL http://127.0.0.1:5000/api/saudacao.
  3. Na aba de corpo (Body), selecione JSON e cole {"nome": "Ana"}.
  4. Clique em Send e confira a mesma resposta mostrada acima.

Funciona tão bem quanto o curl — use o que for mais confortável para você.

🔍 Detalhamento do Código:

  • @app.route("/api/saudacao", methods=["POST"]): registra a rota e o verbo HTTP aceito.
  • request.get_json(): lê o corpo JSON enviado no -d do curl.
  • jsonify(...): converte o dicionário Python de volta em uma resposta JSON válida.
  • app.run(debug=True, port=5000): debug=True recarrega o servidor sozinho a cada alteração salva — ótimo durante o desenvolvimento.

⚠️ Porta ocupada?

Se a porta 5000 já estiver em uso no seu computador, troque para app.run(debug=True, port=5001) — e lembre de usar a mesma porta no curl. É exatamente esse tipo de divergência (porta do servidor ≠ porta do curl) que mais trava quem está começando.


🧠 3. pytest em 10 Minutos

📋 Instalação

pip install pytest

💻 Código Completo (test_saudacao.py)

Salve no mesmo diretório do app.py criado acima:

"""
Módulo: test_saudacao.py
Execução: pytest test_saudacao.py -v
"""
import pytest
from app import app


@pytest.fixture
def client():
    app.config["TESTING"] = True
    with app.test_client() as client:
        yield client


def test_saudacao_com_nome(client):
    resposta = client.post("/api/saudacao", json={"nome": "Ana"})
    assert resposta.status_code == 200
    assert "Ana" in resposta.get_json()["mensagem"]


def test_saudacao_sem_nome(client):
    resposta = client.post("/api/saudacao", json={})
    assert resposta.status_code == 200
    assert "visitante" in resposta.get_json()["mensagem"]

🚀 Como Executar

pytest test_saudacao.py -v

🖥️ Saída Esperada no Terminal

test_saudacao.py::test_saudacao_com_nome PASSED                        [ 50%]
test_saudacao.py::test_saudacao_sem_nome PASSED                        [100%]

============================== 2 passed in 0.08s ===============================

🔍 Detalhamento do Código:

  • @pytest.fixture: prepara algo reutilizável (aqui, um cliente de testes) para cada função de teste que o receber como parâmetro.
  • app.test_client(): simula requisições HTTP contra o Flask sem precisar subir o servidor de verdade — mais rápido e mais confiável.
  • assert: se a condição for falsa, o teste falha e o pytest mostra exatamente qual valor era esperado.

💡 Checkpoint de Lógica

Importante

Reflexão Profissional: Por que testar com app.test_client() é melhor do que abrir o navegador e clicar manualmente toda vez que você muda uma linha de código? (Resposta: um teste automatizado roda em milissegundos, não esquece nenhum caso, e pode rodar sozinho num pipeline de CI/CD a cada push — o que você fará na Atividade 16.) 🧠🛡️


🧪 Quiz de Fixação e Autoavaliação

🧪 Quiz de Autoavaliação — Capítulo 00 (Engenharia de Software)

1. No diagrama de classes, o que a multiplicidade "1" --> "*" entre Cliente e Pacote significa?

  • A) Todo Pacote pertence a exatamente 1 Cliente e todo Cliente tem exatamente 1 Pacote.
  • B) Um Cliente pode enviar zero ou muitos Pacotes.
  • C) Um Pacote pode ter muitos Clientes.
  • D) É um erro de sintaxe.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: "1" do lado de Cliente e "*" do lado de Pacote significa "1 Cliente para 0..N Pacotes" — a leitura sempre começa do lado com "1".


2. No Flask, qual função converte um dicionário Python em uma resposta JSON válida?

  • A) request.get_json()
  • B) jsonify()
  • C) app.route()
  • D) json.dumps() é obrigatório, jsonify() não existe no Flask.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: jsonify() é a função nativa do Flask para serializar dados Python em uma resposta HTTP com Content-Type: application/json correto.


3. Por que usar app.test_client() em vez de rodar o servidor de verdade nos testes?

  • A) É mais rápido e não depende de uma porta de rede estar livre.
  • B) É a única forma de testar rotas POST.
  • C) Não é possível testar Flask com pytest.
  • D) test_client() substitui o curl em produção.
💡 Ver Resposta e Justificativa

Resposta Correta: A
Justificativa: test_client() simula requisições HTTP em memória, sem abrir uma porta de rede real — os testes rodam em milissegundos e não conflitam entre si.


🛠️ Ponte para a Ação

🎯 Próximo Passo

Você está pronto para a jornada. Siga para: 👉 CAPÍTULO 01: INTRODUÇÃO E NATUREZA DO SOFTWARE 👉 ATIVIDADE 01: ESCOPO E PERSONAS


📌 Resumo Executivo & Key Takeaways

  • UML: Casos de Uso mostram quem faz o quê; Classes mostram quais entidades existem; Sequência mostra a ordem das chamadas ao longo do tempo.
  • Flask: uma rota é uma função decorada com @app.route; request.get_json() lê a entrada, jsonify() formata a saída.
  • pytest: app.test_client() testa rotas Flask sem precisar de um servidor real rodando — é assim que a Atividade 09 e o pipeline de CI/CD da Atividade 16 funcionam.

📚 CAPÍTULO 01: INTRODUÇÃO E NATUREZA DO SOFTWARE


🎯 1. Objetivos de Aprendizagem & Competências

Estimativa de Dedicação: 2 horas de estudo autoguiado.
Ao final deste capítulo, você será capaz de:

  • 🔹 Compreender a evolução histórica da Engenharia de Software, a crise do software de 1968 e os princípios da Conferência da OTAN.
  • 🔹 Diferenciar software como produto de engenharia (projetado e evolutivo) versus produtos de manufatura física (sujeitos a desgaste de peças).
  • 🔹 Analisar a arquitetura de tipos de dados, mutabilidade e integridade no ecossistema moderno em Python 3.11+.
  • 🔹 Implementar classes com encapsulamento defensivo e validação de integridade para serviços corporativos.

🏢 2. Cenário Corporativo & Estudo de Caso (TecProExpress)

Imagine que você acaba de ingressar como Engenheiro de Software Júnior na TecProExpress, uma empresa nacional de logística expressa que opera 50.000 encomendas/dia. A diretoria herdou um sistema monolítico legado de 15 anos que se tornou frágil, lento e difícil de manter.

O Desafio: Seu objetivo não é apenas escrever linhas de código, mas transformar a prática empírica em uma disciplina de engenharia rigorosa, garantindo que novos módulos sejam construídos com baixo acoplamento, alta coesão e prontidão para evolução sustentável nos próximos anos.


🧠 3. Fundamentação Teórica & Modelos Visuais

3.1. A Natureza Singular do Software

Ao contrário de pontes ou automóveis, o software não se desgasta pelo atrito físico. Ele se degrada por alterações mal projetadas, acúmulo de débito técnico e divergência entre a especificação e as regras de negócio.

flowchart TD
    subgraph "PRODUTOS FÍSICOS VS SOFTWARE"
        subgraph Hardware ["🏭 Manufatura Física"]
            H1["Projeto Inicial"] --> H2["Linha de Montagem"]
            H2 --> H3["Desgaste Físico por Atrito"]
            H3 --> H4["Substituição de Peças"]
        end
        subgraph Software ["💻 Engenharia de Software"]
            S1["Elicitação & Design"] --> S2["Construção Lógica"]
            S2 --> S3["Degradação por Complexidade"]
            S3 --> S4["Refatoração & Evolução Contínua"]
        end
    end
    style Hardware fill:#fff3e0,stroke:#e65100
    style Software fill:#e1f5fe,stroke:#0277bd

3.2. Os 4 Pilares da Engenharia de Software

A Engenharia de Software estrutura-se na integração equilibrada entre Pessoas, Processos, Métodos e Ferramentas:

flowchart LR
    P["👥 Pessoas<br>(Stakeholders & Times)"] --> PR["⚙️ Processos<br>(Ágil, Scrum, Kanban)"]
    PR --> M["📐 Métodos<br>(UML, DDD, Testes)"]
    M --> F["🛠️ Ferramentas<br>(Python, Git, CI/CD, Docker)"]
    
    style P fill:#e8f5e9,stroke:#2e7d32
    style PR fill:#ede7f6,stroke:#4527a0
    style M fill:#e3f2fd,stroke:#1565c0
    style F fill:#fbe9e7,stroke:#d84315

💻 4. Aplicação Prática & Código Executável (Python 3.11+)

No código corporativo moderno, a aplicação dos princípios de orientação a objetos e auto-defesa de integridade impede que estados inconsistentes sejam criados na memória.

📋 Pré-requisitos e Instalação

Este módulo utiliza exclusivamente recursos nativos do Python 3.11+ (dataclasses e decimal). Nenhuma instalação externa é necessária:

# Ambiente padrão Python 3.11+ (sem dependências externas)

💻 Código Completo e Autocontido (tecpro_frete.py)

Crie o arquivo tecpro_frete.py e insira o código abaixo:

"""
Módulo: tecpro_frete.py
Domínio: Validação e cálculo defensivo de encomendas corporativas.
Stack: Python 3.11+
"""
from dataclasses import dataclass
from decimal import Decimal

@dataclass(frozen=True)
class Encomenda:
    codigo_rastreio: str
    peso_kg: Decimal
    valor_declarado: Decimal
    destino_uf: str

    def __post_init__(self):
        if not self.codigo_rastreio or len(self.codigo_rastreio) < 8:
            raise ValueError("Código de rastreio inválido (mínimo 8 caracteres).")
        if self.peso_kg <= Decimal("0.0"):
            raise ValueError("O peso da encomenda deve ser estritamente positivo.")
        if self.valor_declarado < Decimal("0.0"):
            raise ValueError("O valor declarado não pode ser negativo.")

    def calcular_seguro(self, taxa_percentual: Decimal = Decimal("0.01")) -> Decimal:
        """Calcula a taxa de seguro proporcional ao valor declarado."""
        return self.valor_declarado * taxa_percentual

if __name__ == "__main__":
    try:
        pacote = Encomenda(
            codigo_rastreio="BR-SP-2026-001",
            peso_kg=Decimal("2.450"),
            valor_declarado=Decimal("1500.00"),
            destino_uf="SP"
        )
        seguro = pacote.calcular_seguro()
        print("✅ Encomenda criada com sucesso!")
        print(f"📦 Pacote: {pacote.codigo_rastreio} | Seguro: R$ {seguro:.2f}")
    except ValueError as erro:
        print(f"❌ Falha de integridade: {erro}")

🚀 Como Executar

Execute o script no terminal:

python tecpro_frete.py

🖥️ Saída Esperada no Terminal

✅ Encomenda criada com sucesso!
📦 Pacote: BR-SP-2026-001 | Seguro: R$ 15.00

💡 5. Checkpoint de Engenharia & Boas Práticas

Boas Práticas & Anti-Patterns

  • Princípio SFB (Safe From Bugs): Centralize regras de validação nos construtores ou métodos de fábrica. Nunca permita que um objeto transite pela aplicação em estado inconsistente.
  • Anti-Pattern Primitive Obsession: Evite passar múltiplos tipos primitivos soltos (str, float) entre funções. Agrupe-os em estruturas semânticas ricas (Value Objects ou dataclasses).

🔗 6. Conexão com os Projetos Integradores

Projeto IntegradorComo o conceito deste capítulo é aplicado no PI
PI-01: ManuTrackMapeamento de ativos industriais com blindagem de integridade e horas de operação.
PI-04: ShopFlowCriação de modelos de Pedido e Itens de Carrinho com validação imutável de valores.

🧪 7. Quiz de Fixação e Autoavaliação (Formative Assessment)

🧪 Quiz de Autoavaliação — Capítulo 01

1. Qual das alternativas abaixo melhor descreve uma característica única do software em relação a produtos físicos (hardware)?

  • A) O software se desgasta fisicamente com o uso contínuo pelo atrito mecânico.
  • B) O custo principal do software concentra-se na reprodução fabril de cópias.
  • C) O custo do software concentra-se no design e arquitetura; ele não se desgasta fisicamente, mas degrada por acúmulo de complexidade e débito técnico.
  • D) O software é manufaturado em linhas de montagem industriais automatizadas.
💡 Ver Resposta e Justificativa

Resposta Correta: C
Justificativa: O software não é manufaturado — ele é projetado. Seu custo principal reside na engenharia e arquitetura. Ele não sofre desgaste de peças, mas degrada se não for continuamente refatorado e mantido.


2. Em que ano ocorreu a Conferência da OTAN que marcou o nascimento formal da disciplina de 'Engenharia de Software'?

  • A) 1975
  • B) 1995
  • C) 1968
  • D) 2001
💡 Ver Resposta e Justificativa

Resposta Correta: C
Justificativa: Em 1968, em Garmisch (Alemanha), a conferência da OTAN formalizou a crise do software e estabeleceu a necessidade de aplicar o rigor da engenharia ao desenvolvimento.


3. Como são denominados os sistemas antigos que sustentam operações corporativas vitais, mas possuem arquiteturas rígidas e alto acoplamento?

  • A) Sistemas Serverless
  • B) Sistemas Legados
  • C) Microsserviços Reativos
  • D) Sistemas de Alta Disponibilidade
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: Sistemas legados são ativos corporativos essenciais que continuam em operação comercial, mas exigem cuidados especiais de migração e manutenção devido à arquitetura legada.


🛠️ 8. Ponte para a Ação: Laboratório Prático

🎯 Próximo Passo Prático

Coloque esta teoria em prática executando o roteiro de laboratório autoguiado:
👉 ATIVIDADE 01: ESCOPO E PERSONAS


📌 9. Resumo Executivo & Key Takeaways

  • Engenharia vs Programação: Programar é escrever código funcional; Engenharia de Software é construir sistemas sustentáveis, testáveis e escaláveis ao longo de décadas.
  • Degradação de Software: O software degrada por alterações desordenadas e falta de testes, não por desgaste de peças.
  • Pilares de Excelência: Pessoas, Processos, Métodos e Ferramentas trabalhando de forma sinérgica.
  • Auto-defesa de Dados: Validar dados no momento da instanciação impede a propagação silenciosa de bugs na aplicação.

🔄 CAPÍTULO 02: MODELOS DE PROCESSO DE SOFTWARE


🎯 1. Objetivos de Aprendizagem & Competências

Estimativa de Dedicação: 2 horas de estudo autoguiado.
Ao final deste capítulo, você será capaz de:

  • 🔹 Compreender as características, forças e limitações dos modelos Cascata, Prototipação, Espiral e Incremental.
  • 🔹 Avaliar e selecionar o modelo de ciclo de vida ideal com base na volatilidade de requisitos, criticidade de segurança e prazos.
  • 🔹 Analisar o impacto da gestão de riscos e feedback contínuo no custo de alteração de software.
  • 🔹 Implementar um sistema de máquina de estados em Python 3.11+ para rastrear o ciclo de vida de transição de fases.

🏢 2. Cenário Corporativo & Estudo de Caso (TecProExpress)

Na TecProExpress, a diretoria de TI está iniciando dois novos projetos simultâneos:

  1. Módulo de Telemetria e Frenagem Crítica: Integrado diretamente ao computador de bordo da frota pesada (risco de vida em caso de falha).
  2. App de Chat do Motorista com IA: Ferramenta experimental para interação em tempo real com o suporte (requisitos muito voláteis).

O Desafio: A equipe técnica não pode aplicar a mesma estratégia de processo para ambos os produtos. Você deve estruturar a governança de processos, aplicando o modelo preditivo formal para o módulo crítico e o modelo incremental adaptativo para o app experimental.


🧠 3. Fundamentação Teórica & Modelos Visuais

3.1. Panorama dos Modelos de Ciclo de Vida

Um modelo de processo de software prescreve a ordem das atividades, os critérios de entrada/saída de cada fase e as estratégias de validação com os clientes.

flowchart TD
    subgraph PROCESSOS ["PARADIGMAS DE PROCESSO"]
        W["🌊 Cascata (Waterfall)<br>Sequencial & Preditivo"]
        I["🔄 Incremental / Iterativo<br>Entregas Parciais & Evolutivas"]
        P["🎨 Prototipação<br>Validação de UI & Descoberta"]
        E["🌀 Espiral (Boehm)<br>Orientado a Riscos & Ciclos"]
    end
    
    style W fill:#e1f5fe,stroke:#0284c7
    style I fill:#dcfce7,stroke:#16a34a
    style P fill:#fef3c7,stroke:#d97706
    style E fill:#f3e5f5,stroke:#7b1fa2

3.2. Cascata vs. Incremental: Análise de Custo da Mudança

No modelo Cascata, o custo de corrigir um requisito mal compreendido na fase de testes pode ser até 100× maior do que na fase inicial de elicitação. No modelo Incremental, o feedback frequente reduz o retrabalho.

flowchart LR
    subgraph Cascata ["🌊 Modelo Cascata"]
        R1["Requisitos"] --> D1["Design"] --> C1["Código"] --> T1["Testes"] --> O1["Operação"]
    end
    
    subgraph Incremental ["🚀 Modelo Incremental"]
        direction TB
        Inc1["Incremento 1: MVP Core (4 semanas)"]
        Inc2["Incremento 2: Pagamentos & API (3 semanas)"]
        Inc3["Incremento 3: Relatórios & BI (3 semanas)"]
        Inc1 --> Inc2 --> Inc3
    end
    
    style Cascata fill:#fee2e2,stroke:#ef4444
    style Incremental fill:#dcfce7,stroke:#16a34a

💻 4. Aplicação Prática & Código Executável (Python 3.11+)

Implementação de um motor de ciclo de vida de fases de projeto com validação estrita de transições.

📋 Pré-requisitos e Instalação

Este módulo utiliza exclusivamente recursos nativos do Python 3.11+ (enum). Nenhuma instalação externa é necessária:

# Ambiente padrão Python 3.11+ (sem dependências externas)

💻 Código Completo e Autocontido (ciclo_vida_processo.py)

Crie o arquivo ciclo_vida_processo.py e insira o código abaixo:

"""
Módulo: ciclo_vida_processo.py
Domínio: Automação e rastreamento de estágios do processo de software.
Stack: Python 3.11+
"""
from enum import Enum, auto

class FaseProcesso(Enum):
    CONCEPCAO = auto()
    REQUISITOS = auto()
    DESENHO_ARQUITETURAL = auto()
    IMPLEMENTACAO = auto()
    TESTES_INTEGRACAO = auto()
    DEPLOY_PRODUCAO = auto()

class CicloVidaProjeto:
    def __init__(self, nome_projeto: str, modelo: str = "INCREMENTAL"):
        self.nome_projeto = nome_projeto
        self.modelo = modelo
        self.fase_atual = FaseProcesso.CONCEPCAO
        self.historico_fases: list[FaseProcesso] = [self.fase_atual]

    def transicionar(self, proxima_fase: FaseProcesso, criterios_aceite_atendidos: bool):
        """Valida se os critérios de qualidade da fase atual foram satisfeitos antes de avançar."""
        if not criterios_aceite_atendidos:
            raise PermissionError(
                f"❌ Transição bloqueada: Critérios de aceite incompletos para sair de {self.fase_atual.name}."
            )
        self.fase_atual = proxima_fase
        self.historico_fases.append(self.fase_atual)
        print(f"🚀 [{self.nome_projeto}] Avançou com sucesso para a fase: {self.fase_atual.name}")

if __name__ == "__main__":
    projeto_core = CicloVidaProjeto("TecPro-Telemetria", modelo="CASCATA")

    # 1. Avançando com critérios satisfeitos
    projeto_core.transicionar(FaseProcesso.REQUISITOS, criterios_aceite_atendidos=True)
    projeto_core.transicionar(FaseProcesso.DESENHO_ARQUITETURAL, criterios_aceite_atendidos=True)

    # 2. Tentativa de avanço com pendências de qualidade
    try:
        projeto_core.transicionar(FaseProcesso.IMPLEMENTACAO, criterios_aceite_atendidos=False)
    except PermissionError as err:
        print(err)

🚀 Como Executar

Execute o script no terminal:

python ciclo_vida_processo.py

🖥️ Saída Esperada no Terminal

🚀 [TecPro-Telemetria] Avançou com sucesso para a fase: REQUISITOS
🚀 [TecPro-Telemetria] Avançou com sucesso para a fase: DESENHO_ARQUITETURAL
❌ Transição bloqueada: Critérios de aceite incompletos para sair de DESENHO_ARQUITETURAL.

💡 5. Checkpoint de Engenharia & Boas Práticas

Boas Práticas & Anti-Patterns

  • Critério de Seleção: Use modelos sequenciais (Cascata) apenas quando os requisitos forem 100% estáveis, conhecidos e as tecnologias consolidadas.
  • Anti-Pattern Big Bang Release: Nunca acumule 6 meses de desenvolvimento sem entregas intermediárias ao usuário final. Entregue valor em pequenos incrementos funcionais (sprints de 2 a 3 semanas).

🔗 6. Conexão com os Projetos Integradores

Projeto IntegradorComo o conceito deste capítulo é aplicado no PI
PI-02: StockFlowDefinição do processo de desenvolvimento em 2 marcos (MVP no Marco 1 e PCP avançado no Marco 2).
PI-05: ParkFlowPrototipação rápida do fluxo de check-in antes da codificação das tabelas de tarifação.

🧪 7. Quiz de Fixação e Autoavaliação (Formative Assessment)

🧪 Quiz de Autoavaliação — Capítulo 02

1. Em qual dos seguintes cenários o modelo em Cascata tradicional é mais indicado?

  • A) Sistema de rede social com funcionalidades inovadoras e público jovem indefinido.
  • B) Aplicativo mobile para startups onde o modelo de negócio muda a cada semana.
  • C) Sistema embarcado de controle de freios aeronáuticos com especificações rigorosas e imutáveis da autoridade reguladora.
  • D) Portal de e-commerce cujo design visual será testado por testes A/B em tempo real.
💡 Ver Resposta e Justificativa

Resposta Correta: C
Justificativa: O modelo Cascata funciona perfeitamente quando os requisitos são totalmente claros, previsíveis e fixados por normativas rígidas, sem necessidade de alterações constantes de escopo.


2. Qual é a principal característica distintiva do Modelo Espiral proposto por Barry Boehm?

  • A) Eliminação total de documentação escrita.
  • B) Ausência de testes de integração.
  • C) Avaliação e mitigação explícita de riscos em cada ciclo de desenvolvimento.
  • D) Uso obrigatório de metodologias NoSQL.
💡 Ver Resposta e Justificativa

Resposta Correta: C
Justificativa: O modelo Espiral é centrado na análise contínua e sistemática de riscos a cada volta da espiral, antes de comprometer recursos com desenvolvimento pesado.


3. O que define a abordagem de Desenvolvimento Incremental?

  • A) O sistema é entregue apenas quando 100% das telas estiverem concluídas.
  • B) O sistema é dividido em pequenos subconjuntos funcionais (incrementos), permitindo validação antecipada com os usuários.
  • C) Todo o código é escrito antes de qualquer fase de requisitos.
  • D) O cliente só testa o software após 12 meses de implantação.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: O desenvolvimento incremental entrega versões parciais e funcionais a cada ciclo, permitindo colher feedback real dos usuários e refinar o produto continuamente.


🛠️ 8. Ponte para a Ação: Laboratório Prático

🎯 Próximo Passo Prático

Coloque esta teoria em prática executando o roteiro de laboratório autoguiado:
👉 ATIVIDADE 02: PROCESSOS DE SOFTWARE


📌 9. Resumo Executivo & Key Takeaways

  • Processos são Guias: Um processo de software estrutura as atividades da equipe para transformar requisitos de negócio em valor de produção com previsibilidade.
  • Modelo Cascata: Adequado para domínios altamente regulados e com requisitos imutáveis; vulnerável a mudanças tardias.
  • Modelo Incremental: Base das metodologias ágeis modernas; reduz o risco de construir o produto errado ao colher feedback contínuo.
  • Modelo Espiral: Abordagem de alta maturidade com ênfase na identificação e neutralização antecipada de riscos técnicos e de negócio.

⚙️ CAPÍTULO 03: AS ATIVIDADES DO PROCESSO


🎯 1. Objetivos de Aprendizagem & Competências

Estimativa de Dedicação: 2 horas de estudo autoguiado.
Ao final deste capítulo, você será capaz de:

  • 🔹 Compreender as quatro atividades universais de qualquer processo de software: Especificação, Projeto & Implementação, Validação e Evolução (Sommerville/Pressman).
  • 🔹 Mapear o fluxo de rastreabilidade bidirecional entre requisitos de negócio, arquitetura, código-fonte e suíte de testes.
  • 🔹 Distinguir verificação ("Estamos construindo o produto corretamente?") de validação ("Estamos construindo o produto correto?").
  • 🔹 Implementar uma esteira de validação em Python 3.11+ para rastreamento de artefatos de entrega.

🏢 2. Cenário Corporativo & Estudo de Caso (TecProExpress)

Na TecProExpress, um novo sistema de Rastreamento de Cargas e Frotas em Tempo Real foi aprovado para produção. Durante a primeira auditoria de governança, identificou-se que muitas funcionalidades codificadas não possuíam requisitos documentados e não eram cobertas por testes automatizados.

O Desafio: Seu objetivo é instituir a disciplina das 4 atividades universais do processo, criando uma esteira onde nenhum código chega a produção sem especificação prévia, implementação alinhada e validação automatizada.


🧠 3. Fundamentação Teórica & Modelos Visuais

3.1. As Quatro Atividades Universais

Independentemente de a equipe utilizar métodos ágeis (Scrum/Kanban) ou abordagens tradicionais, todo processo de software executa quatro atividades fundamentais:

flowchart TD
    subgraph ATIVIDADES ["AS 4 ATIVIDADES UNIVERSAIS DO PROCESSO (SOMMERVILLE)"]
        direction LR
        E["📋 1. ESPECIFICAÇÃO<br>O que o sistema deve fazer<br>(Requisitos & Regras)"] 
        --> D["📐 2. PROJETO & CÓDIGO<br>Como o sistema será estruturado<br>(Arquitetura & Implementação)"]
        --> V["🧪 3. VALIDAÇÃO<br>Garantir que funciona e atende<br>(Testes Unitários & E2E)"]
        --> EV["🚀 4. EVOLUÇÃO<br>Adaptação a novas demandas<br>(Refatoração & Manutenção)"]
        EV -.-> E
    end
    
    style E fill:#e0f2fe,stroke:#0284c7
    style D fill:#ede7f6,stroke:#5e35b1
    style V fill:#dcfce7,stroke:#16a34a
    style EV fill:#fef3c7,stroke:#d97706

3.2. Matriz de Rastreabilidade de Engenharia

A rastreabilidade garante que cada requisito tenha correspondência direta em classes de código e casos de teste:

flowchart LR
    RF["Requisito Funcional<br>RF01: Check-in de Carga"] --> UC["Caso de Uso / Endpoint<br>POST /api/cargas/"]
    UC --> CL["Classe de Domínio<br>Carga (models.py)"]
    CL --> UT["Teste Automatizado<br>test_checkin_carga()"]
    
    style RF fill:#e3f2fd,stroke:#1565c0
    style UC fill:#f3e5f5,stroke:#7b1fa2
    style CL fill:#fff8e1,stroke:#f57f17
    style UT fill:#dcfce7,stroke:#16a34a

💻 4. Aplicação Prática & Código Executável (Python 3.11+)

Implementação de uma estrutura de rastreamento de artefatos de processo com validação de cobertura.

📋 Pré-requisitos e Instalação

Este módulo utiliza exclusivamente recursos nativos do Python 3.11+ (dataclasses). Nenhuma instalação externa é necessária:

# Ambiente padrão Python 3.11+ (sem dependências externas)

💻 Código Completo e Autocontido (rastreabilidade_processo.py)

Crie o arquivo rastreabilidade_processo.py e insira o código abaixo:

"""
Módulo: rastreabilidade_processo.py
Domínio: Auditoria de rastreabilidade entre Requisitos, Código e Testes.
Stack: Python 3.11+
"""
from dataclasses import dataclass, field

@dataclass
class ItemRastreabilidade:
    id_requisito: str
    descricao_negocio: str
    endpoint_rest: str
    testes_associados: list[str] = field(default_factory=list)

    @property
    def coberto_por_testes(self) -> bool:
        return len(self.testes_associados) > 0

class AuditoriaProcesso:
    def __init__(self):
        self.itens: dict[str, ItemRastreabilidade] = {}

    def registrar_item(self, item: ItemRastreabilidade):
        self.itens[item.id_requisito] = item

    def relatorio_cobertura(self):
        total = len(self.itens)
        cobertos = sum(1 for item in self.itens.values() if item.coberto_por_testes)
        print("\n📊 RELATÓRIO DE RASTREABILIDADE DO PROCESSO:")
        print(f"Total de Requisitos Mapeados: {total}")
        print(f"Requisitos com Testes Automatizados: {cobertos}/{total} ({(cobertos/total)*100:.1f}%)")
        for item in self.itens.values():
            status = "✅ COBERTO" if item.coberto_por_testes else "❌ SEM TESTES"
            print(f" - [{item.id_requisito}] {item.descricao_negocio} -> {status}")

if __name__ == "__main__":
    auditoria = AuditoriaProcesso()
    auditoria.registrar_item(ItemRastreabilidade(
        id_requisito="RF-01",
        descricao_negocio="Registro de Entrada de Veículo",
        endpoint_rest="POST /api/checkin/",
        testes_associados=["test_checkin_sucesso", "test_checkin_vaga_ocupada"]
    ))
    auditoria.registrar_item(ItemRastreabilidade(
        id_requisito="RF-02",
        descricao_negocio="Emissão de Relatório Financeiro",
        endpoint_rest="GET /api/relatorios/faturamento",
        testes_associados=[]
    ))
    auditoria.relatorio_cobertura()

🚀 Como Executar

Execute o script no terminal:

python rastreabilidade_processo.py

🖥️ Saída Esperada no Terminal

📊 RELATÓRIO DE RASTREABILIDADE DO PROCESSO:
Total de Requisitos Mapeados: 2
Requisitos com Testes Automatizados: 1/2 (50.0%)
 - [RF-01] Registro de Entrada de Veículo -> ✅ COBERTO
 - [RF-02] Emissão de Relatório Financeiro -> ❌ SEM TESTES

💡 5. Checkpoint de Engenharia & Boas Práticas

Boas Práticas & Anti-Patterns

  • Verificação vs. Validação (Boehm):
    • Verificação: O software atende às especificações técnicas? (Ausência de erros de código e bugs lógicos).
    • Validação: O software resolve o problema real do usuário? (Adequação ao propósito de negócio).
  • Anti-Pattern Code & Fix: Nunca inicie o desenvolvimento sem um alinhamento prévio do requisito, nem entregue sem uma suíte de testes de validação.

🔗 6. Conexão com os Projetos Integradores

Projeto IntegradorComo o conceito deste capítulo é aplicado no PI
PI-03: PDVLiteMapeamento direto de RF01 (Cupom Fiscal) para o endpoint POST /api/vendas/ e testes com Pytest.
PI-06: ServiceFlowTransição das atividades de especificação de SLA para testes de estouro de prazo limite.

🧪 7. Quiz de Fixação e Autoavaliação (Formative Assessment)

🧪 Quiz de Autoavaliação — Capítulo 03

1. Qual é a principal diferença entre as atividades de 'Verificação' e 'Validação' de software?

  • A) Verificação é feita pelo cliente e Validação é feita pelo compilador.
  • B) Verificação avalia se o software foi construído de acordo com a especificação; Validação avalia se o software atende às necessidades reais do usuário.
  • C) Verificação ocorre na nuvem e Validação ocorre no computador local.
  • D) Não há diferença prática; são termos sinônimos no manifesto ágil.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: Como sintetizou Barry Boehm: Verificação é "Estamos construindo o produto corretamente?" (conformidade técnica), enquanto Validação é "Estamos construindo o produto correto?" (satisfação do usuário).


2. As quatro atividades fundamentais de qualquer processo de software segundo Ian Sommerville são:

  • A) Brainstorming, Codificação, Publicação e Venda.
  • B) Especificação, Projeto/Implementação, Validação e Evolução.
  • C) Contratação, Treinamento, Compilação e Deploy.
  • D) Elicitação, Prototipação, Documentação e Encerramento.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: Todo processo de engenharia de software articula-se em torno de Especificar o que fazer, Projetar/Implementar o sistema, Validar a qualidade e Evoluir continuamente.


3. O que uma 'Matriz de Rastreabilidade de Requisitos' permite aos engenheiros de software?

  • A) Aumentar o uso de memória RAM do servidor.
  • B) Rastrear a origem de cada requisito e verificar quais classes de código e casos de teste o cobrem.
  • C) Impedir o versionamento no Git.
  • D) Substituir a necessidade de testes automatizados.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: A matriz de rastreabilidade conecta o requisito de negócio aos seus artefatos técnicos correspondentes (modelos, rotas e testes), garantindo cobertura total e facilitando a análise de impacto em mudanças.


🛠️ 8. Ponte para a Ação: Laboratório Prático

🎯 Próximo Passo Prático

Coloque esta teoria em prática executando o roteiro de laboratório autoguiado:
👉 ATIVIDADE 03: ENGENHARIA DE REQUISITOS


📌 9. Resumo Executivo & Key Takeaways

  • 4 Atividades Canônicas: Especificação (O quê), Projeto & Código (Como), Validação (Testes) e Evolução (Melhoria Contínua).
  • Rastreabilidade Bidirecional: Cada linha de código corporativo deve ser justificada por um requisito e validada por ao menos um teste automatizado.
  • Verificação vs Validação: Construir o software sem bugs (Verificação) e garantir que ele resolve o problema certo (Validação).
  • Evolução Contínua: Mais de 60% do custo total de um software ao longo do seu ciclo de vida é gasto na fase de evolução e manutenção.

⚡ CAPÍTULO 04: METODOLOGIAS ÁGEIS (SCRUM, KANBAN E XP)


🎯 1. Objetivos de Aprendizagem & Competências

Estimativa de Dedicação: 2 horas de estudo autoguiado.
Ao final deste capítulo, você será capaz de:

  • 🔹 Compreender os 4 valores e os 12 princípios do Manifesto Ágil de 2001 e sua aplicação na engenharia moderna.
  • 🔹 Dominar a tríade de papéis (Product Owner, Scrum Master, Developers), eventos e artefatos do framework Scrum.
  • 🔹 Aplicar os princípios de fluxo puxado, visibilidade e limites de trabalho em progresso (WIP) do método Kanban.
  • 🔹 Implementar práticas de engenharia de software do Extreme Programming (XP), incluindo Test-Driven Development (TDD) e Refatoração contínua.

🏢 2. Cenário Corporativo & Estudo de Caso (TecProExpress)

Na TecProExpress, a diretoria precisa lançar o novo portal de Autoatendimento e Rastreamento de Encomendas em apenas 8 semanas para atender à demanda da Black Friday. O modelo tradicional em cascata levaria 6 meses apenas na fase de especificação.

O Desafio: Sua equipe foi organizada em um Squad Ágil Multidisciplinar. Você aplicará Scrum para a governança de entregas em Sprints quinzenais, Kanban para controlar o fluxo de tarefas no board e práticas de XP (TDD com Pytest) para garantir código sustentável e sem regressões.


🧠 3. Fundamentação Teórica & Modelos Visuais

3.1. Os 4 Valores do Manifesto Ágil (2001)

Em 2001, 17 líderes de software reuniram-se em Utah e sintetizaram os pilares da agilidade:

flowchart TD
    subgraph MANIFESTO ["OS 4 VALORES DO MANIFESTO ÁGIL"]
        V1["👥 Indivíduos e interações<br>MAIS QUE processos e ferramentas"]
        V2["💻 Software em funcionamento<br>MAIS QUE documentação abrangente"]
        V3["🤝 Colaboração com o cliente<br>MAIS QUE negociação de contratos"]
        V4["🔄 Responder a mudanças<br>MAIS QUE seguir um plano rígido"]
    end
    
    style MANIFESTO fill:#f0fdf4,stroke:#16a34a
    style V1 fill:#dcfce7,stroke:#15803d
    style V2 fill:#dcfce7,stroke:#15803d
    style V3 fill:#dcfce7,stroke:#15803d
    style V4 fill:#dcfce7,stroke:#15803d

3.2. Ciclo de Vida do Scrum

O Scrum estrutura o desenvolvimento em iterações curtas chamadas Sprints (1 a 4 semanas), gerando um incremento de produto potencialmente publicável:

flowchart LR
    PB["📋 Product Backlog<br>(Priorizado pelo PO)"] --> SP["🎯 Sprint Planning<br>(Seleção do Time)"]
    SP --> SB["📝 Sprint Backlog"]
    SB --> SPRINT["🏃 Sprint (2 Semanas)<br>Daily Scrum (15 min)"]
    SPRINT --> INC["🚀 Incremento de Produto<br>(Definition of Done)"]
    INC --> REV["🔍 Sprint Review<br>(Feedback do Cliente)"]
    REV --> RET["💡 Retrospectiva<br>(Melhoria Contínua)"]
    RET --> SP
    
    style PB fill:#e0f2fe,stroke:#0284c7
    style SP fill:#ede7f6,stroke:#5e35b1
    style SPRINT fill:#fef3c7,stroke:#d97706
    style INC fill:#dcfce7,stroke:#16a34a
    style REV fill:#fce4ec,stroke:#c2185b
    style RET fill:#f3e5f5,stroke:#7b1fa2

3.3. Kanban e Limites de WIP (Work in Progress)

O Kanban foca em otimizar o fluxo de entrega visualizando o trabalho e limitando o número de tarefas simultâneas:

flowchart LR
    subgraph KANBAN ["QUADRO KANBAN COM LIMITES DE WIP"]
        direction LR
        B["Backlog<br>(Sem limite)"] --> A["A Fazer<br>(WIP: 5)"]
        A --> DEV["Em Dev<br>(WIP: 3)"]
        DEV --> TEST["Em Testes<br>(WIP: 2)"]
        TEST --> DONE["Concluído<br>(Done)"]
    end
    style KANBAN fill:#f8fafc,stroke:#64748b
    style DEV fill:#fee2e2,stroke:#ef4444
    style TEST fill:#fef3c7,stroke:#d97706
    style DONE fill:#dcfce7,stroke:#16a34a

💻 4. Aplicação Prática & Código Executável (Python 3.11+)

Implementação do ciclo TDD (Test-Driven Development) do Extreme Programming (Red ➔ Green ➔ Refactor).

📋 Pré-requisitos e Instalação

No terminal do seu ambiente virtual, instale o executor de testes Pytest:

pip install pytest

💻 Código Completo e Autocontido (calculador_frete_tdd.py)

Crie o arquivo calculador_frete_tdd.py e insira o código abaixo integralmente:

"""
Módulo: calculador_frete_tdd.py
Prática XP: Desenvolvimento guiado por testes com Pytest.
Stack: Python 3.11+ | Pytest
"""
from decimal import Decimal
import pytest

# --- PASSO 1: RED -> GREEN (Implementação que atende a especificação) ---
def calcular_taxa_urgencia(peso_kg: Decimal, expressa: bool) -> Decimal:
    """Calcula taxa adicional para fretes prioritários."""
    if peso_kg <= Decimal("0.0"):
        raise ValueError("O peso deve ser maior que zero.")

    taxa_base = peso_kg * Decimal("5.00")
    if expressa:
        return taxa_base * Decimal("1.50")
    return taxa_base

# --- SUÍTE DE TESTES UNITÁRIOS COM PYTEST ---
def test_frete_convencional():
    resultado = calcular_taxa_urgencia(peso_kg=Decimal("2.0"), expressa=False)
    assert resultado == Decimal("10.00")

def test_frete_expresso_com_adicional():
    resultado = calcular_taxa_urgencia(peso_kg=Decimal("2.0"), expressa=True)
    assert resultado == Decimal("15.00")

def test_peso_invalido_deve_lancar_excecao():
    with pytest.raises(ValueError, match="maior que zero"):
        calcular_taxa_urgencia(peso_kg=Decimal("-1.0"), expressa=True)

if __name__ == "__main__":
    print("🚀 Executando lógica de frete demonstrativa:")
    val = calcular_taxa_urgencia(Decimal("4.0"), True)
    print(f"Taxa calculada: R$ {val:.2f}")

    print("\n🧪 Disparando suíte automatizada do Pytest:")
    pytest.main(["-v", __file__])

🚀 Como Executar

Você pode executar diretamente com o interpretador Python ou através do runner do Pytest:

# Execução direta com Python (dispara a lógica e a suíte interna)
python calculador_frete_tdd.py

# Ou execução formal pelo CLI do Pytest
pytest -v calculador_frete_tdd.py

🖥️ Saída Esperada no Terminal

🚀 Executando lógica de frete demonstrativa:
Taxa calculada: R$ 30.00

🧪 Disparando suíte automatizada do Pytest:
============================= test session starts =============================
calculador_frete_tdd.py::test_frete_convencional PASSED                  [ 33%]
calculador_frete_tdd.py::test_frete_expresso_com_adicional PASSED        [ 66%]
calculador_frete_tdd.py::test_peso_invalido_deve_lancar_excecao PASSED   [100%]
============================== 3 passed in 0.05s ==============================

💡 5. Checkpoint de Engenharia & Boas Práticas

Boas Práticas & Anti-Patterns

  • Definition of Done (DoD): Uma história de usuário só está pronta quando o código foi revisado (Code Review), os testes automatizados passaram no CI e a documentação técnica foi atualizada.
  • Anti-Pattern Zombie Scrum: Realizar as reuniões (Daily, Review) de forma mecânica sem de fato empoderar o time para tomar decisões ou adaptar o plano de release.

🔗 6. Conexão com os Projetos Integradores

Projeto IntegradorComo o conceito deste capítulo é aplicado no PI
PI-04: ShopFlowOrganização das User Stories do checkout no backlog do produto e entrega em 2 marcos.
PI-06: ServiceFlowAplicação de quadro Kanban para acompanhar o fluxo de atendimento das ordens de serviço.

🧪 7. Quiz de Fixação e Autoavaliação (Formative Assessment)

🧪 Quiz de Autoavaliação — Capítulo 04

1. Qual é a responsabilidade central do papel de Product Owner (PO) no framework Scrum?

  • A) Definir a arquitetura técnica de microsserviços e o banco de dados.
  • B) Maximizar o valor do produto e gerenciar ativamente o Product Backlog priorizado.
  • C) Conduzir diariamente as reuniões de 15 minutos e remover impedimentos operacionais.
  • D) Escrever os testes unitários da aplicação.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: O Product Owner é o responsável por representar os stakeholders, definir a visão do produto e garantir que o backlog esteja priorizado pelo maior valor de negócio.


2. No método Kanban, por que é fundamental estabelecer limites de Trabalho em Progresso (WIP - Work in Progress)?

  • A) Para impedir que os desenvolvedores façam pausas durante o dia.
  • B) Para evitar gargalos, reduzir a sobrecarga e acelerar o tempo de ciclo (Lead Time) das entregas.
  • C) Para forçar a contratação de novos gerentes de projeto.
  • D) Para desativar a esteira de integração contínua (CI).
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: Limitar o WIP força a equipe a terminar tarefas em andamento antes de iniciar novas ("Pare de começar, comece a terminar"), reduzindo gargalos e melhorando a previsibilidade do fluxo.


3. Qual é a sequência correta do ciclo TDD (Test-Driven Development) preconizado pelo Extreme Programming (XP)?

  • A) Refactor ➔ Green ➔ Red
  • B) Red (Escrever teste que falha) ➔ Green (Escrever código mínimo para passar) ➔ Refactor (Melhorar o design do código)
  • C) Deploy ➔ Teste Manual ➔ Correção de Bugs
  • D) Documentação ➔ Compilação ➔ Teste de Carga
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: O ciclo clássico do TDD é Red-Green-Refactor: primeiro escreve-se o teste que falha, implementa-se a solução mais simples para torná-lo verde e, por fim, refatora-se o código mantendo os testes passando.


🛠️ 8. Ponte para a Ação: Laboratório Prático

🎯 Próximo Passo Prático

Coloque esta teoria em prática executando o roteiro de laboratório autoguiado:
👉 ATIVIDADE 04: USER STORIES E BACKLOG


📌 9. Resumo Executivo & Key Takeaways

  • Manifesto Ágil: Valoriza pessoas, software funcional, colaboração e resposta a mudanças acima de planos inflexíveis.
  • Scrum: Framework iterativo-incremental baseado em papéis (PO, SM, Devs), eventos (Planning, Daily, Review, Retrospective) e artefatos.
  • Kanban: Gestão visual do fluxo de trabalho com limites explícitos de WIP para evitar sobrecarga e eliminar gargalos.
  • XP & TDD: Excelência técnica com testes automatizados escritos antes do código de produção (Red-Green-Refactor).

📋 CAPÍTULO 05: FUNDAMENTOS DE REQUISITOS


🎯 1. Objetivos de Aprendizagem & Competências

Estimativa de Dedicação: 2 horas de estudo autoguiado.
Ao final deste capítulo, você será capaz de:

  • 🔹 Diferenciar rigorosamente Requisitos Funcionais (RF), Requisitos Não-Funcionais (RNF) e Regras de Negócio (RN).
  • 🔹 Classificar RNFs segundo o modelo de qualidade FURPS+ (Functionality, Usability, Reliability, Performance, Supportability).
  • 🔹 Formular requisitos mensuráveis, claros e sem ambiguidades ("a tela deve ser rápida" ➔ "latência p95 < 200ms").
  • 🔹 Implementar contratos de dados e validações semânticas em Python 3.11+ utilizando Pydantic v2.

🏢 2. Cenário Corporativo & Estudo de Caso (TecProExpress)

Na TecProExpress, o departamento jurídico e o time de infraestrutura emitiram diretrizes mandatórias: o novo portal corporativo deve atender à LGPD (anonimização de dados de motoristas) e garantir tempo de resposta inferior a 500ms durante o pico de tráfego de 10.000 requisições/minuto.

O Desafio: Como Analista de Requisitos e Arquiteto de Software, você deve traduzir as dores dos usuários e as imposições legais em especificações técnicas precisas, separando o que é comportamento funcional (RF) do que é atributo arquitetural de qualidade (RNF).


🧠 3. Fundamentação Teórica & Modelos Visuais

3.1. A Tríade dos Requisitos de Software

Um requisito é uma condição ou capacidade necessária que o software deve possuir para resolver um problema real:

flowchart TD
    subgraph TRIADE ["A TRÍADE DE ESPECIFICAÇÃO DE SOFTWARE"]
        RF["⚡ REQUISITOS FUNCIONAIS (RF)<br>O QUE o sistema faz<br>Ex: Cadastrar motorista, Emitir cupom"]
        RNF["🛡️ REQUISITOS NÃO-FUNCIONAIS (RNF)<br>COMO o sistema opera (Qualidade)<br>Ex: Tempo de resposta < 200ms, Criptografia AES-256"]
        RN["⚖️ REGRAS DE NEGÓCIO (RN)<br>DIRETRIZES corporativas e leis<br>Ex: Desconto de 10% para compras acima de R$ 500,00"]
    end
    
    style RF fill:#e0f2fe,stroke:#0284c7
    style RNF fill:#ede7f6,stroke:#5e35b1
    style RN fill:#fef3c7,stroke:#d97706

3.2. Classificação de RNFs pelo Modelo FURPS+

O modelo FURPS+ (Hewlett-Packard / Rational) categoriza os requisitos de qualidade em 5 dimensões essenciais:

flowchart TD
    root["🛡️ Modelo FURPS+ (Dimensões de Qualidade)"]
    
    root --> F["1. Functionality (Funcionalidade)<br>• Capacidades e Segurança<br>• Conformidade de Negócio"]
    root --> U["2. Usability (Usabilidade)<br>• Fatores Humanos e UX<br>• Consistência de Interface"]
    root --> R["3. Reliability (Confiabilidade)<br>• MTBF e Tolerância a Falhas<br>• Recuperabilidade"]
    root --> P["4. Performance (Desempenho)<br>• Throughput e Tempo de Resposta<br>• Consumo de Memória"]
    root --> S["5. Supportability (Suportabilidade)<br>• Manutenibilidade e Testabilidade<br>• Portabilidade e Configuração"]
    
    style root fill:#eff6ff,stroke:#2563eb,stroke-width:2px
    style F fill:#f0fdf4,stroke:#16a34a
    style U fill:#fffbeb,stroke:#d97706
    style R fill:#fee2e2,stroke:#ef4444
    style P fill:#ede7f6,stroke:#7c3aed
    style S fill:#e0f2fe,stroke:#0284c7

💻 4. Aplicação Prática & Código Executável (Python 3.11+)

Implementação de validação de requisitos funcionais e regras de negócio com Pydantic v2:

📋 Pré-requisitos e Instalação

O esquema utiliza o validador EmailStr do Pydantic, que requer a biblioteca email-validator:

pip install "pydantic[email]"

💻 Código Completo e Autocontido (requisitos_contrato.py)

Crie o arquivo requisitos_contrato.py e insira o código abaixo integralmente:

"""
Módulo: requisitos_contrato.py
Domínio: Validação estrita de contratos de dados (Pydantic v2).
Stack: Python 3.11+ | Pydantic v2
"""
from decimal import Decimal
from pydantic import BaseModel, Field, EmailStr, field_validator

class RequisitoMotoristaSchema(BaseModel):
    # RF: Cadastro de motorista parceiro
    nome_completo: str = Field(..., min_length=3, max_length=100, description="Nome do motorista")
    cpf: str = Field(..., pattern=r"^\d{3}\.\d{3}\.\d{3}-\d{2}$", description="CPF formatado")
    email: EmailStr
    cnh_categoria: str = Field(..., description="Categoria da CNH (B, C, D, E)")
    score_avaliacao: Decimal = Field(default=Decimal("5.0"), ge=0, le=5)

    # RN: Regra de Negócio - Motoristas de carga pesada exigem CNH C, D ou E
    @field_validator("cnh_categoria")
    @classmethod
    def validar_cnh_valida(cls, v: str) -> str:
        categorias_permitidas = {"B", "C", "D", "E"}
        v_upper = v.upper()
        if v_upper not in categorias_permitidas:
            raise ValueError(f"Categoria CNH inválida. Permitidas: {categorias_permitidas}")
        return v_upper

if __name__ == "__main__":
    try:
        motorista_valido = RequisitoMotoristaSchema(
            nome_completo="Carlos Eduardo Silva",
            cpf="123.456.789-00",
            email="carlos.motorista@email.com",
            cnh_categoria="D"
        )
        print("✅ Requisito Funcional atendido com dados válidos:")
        print(motorista_valido.model_dump_json(indent=2))
    except Exception as err:
        print(f"❌ Violação de Requisito: {err}")

🚀 Como Executar

Execute o script no terminal:

python requisitos_contrato.py

🖥️ Saída Esperada no Terminal

✅ Requisito Funcional atendido com dados válidos:
{
  "nome_completo": "Carlos Eduardo Silva",
  "cpf": "123.456.789-00",
  "email": "carlos.motorista@email.com",
  "cnh_categoria": "D",
  "score_avaliacao": "5.0"
}

💡 5. Checkpoint de Engenharia & Boas Práticas

Boas Práticas & Anti-Patterns

  • Requisitos Mensuráveis: Nunca escreva "o sistema deve ser rápido" ou "fácil de usar". Escreva: "O tempo de carregamento da listagem de entregas deve ser inferior a 1,5s no percentil 95 sob carga de 500 usuários simultâneos".
  • Anti-Pattern Gold Plating: Evite adicionar funcionalidades complexas não solicitadas pelo cliente acreditando que "seria legal ter". Foque no valor de negócio acordado.

🔗 6. Conexão com os Projetos Integradores

Projeto IntegradorComo o conceito deste capítulo é aplicado no PI
PI-07: AgroSafeRN estrita: defensivos agrícolas não podem ser liberados para aplicação sem ART válida do agrônomo.
PI-09: FinLiteRNF de Confiabilidade: operações de baixa de títulos bancários devem ser atômicas (ACID).

🧪 7. Quiz de Fixação e Autoavaliação (Formative Assessment)

🧪 Quiz de Autoavaliação — Capítulo 05

1. A declaração 'A senha de todos os usuários deve ser armazenada com hash seguro bcrypt e salt de no mínimo 12 rounds' classifica-se como:

  • A) Requisito Funcional de Interface.
  • B) Requisito Não-Funcional de Segurança (RNF).
  • C) Caso de Uso Estendido.
  • D) Requisito de Escopo Descartável.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: O requisito impõe uma restrição de segurança técnica sobre como as senhas devem ser persistidas, qualificando-se como um Requisito Não-Funcional (RNF) de Segurança.


2. O que caracteriza uma 'Regra de Negócio' (RN) em contraste com um Requisito Funcional (RF)?

  • A) A RN só existe depois que o software é compilado.
  • B) A RN define uma política, cálculo ou restrição corporativa que existe independentemente do software (ex: cálculo de juros por atraso).
  • C) A RN é um comando do sistema operacional.
  • D) A RN só pode ser modelada em diagramas de hardware.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: Regras de negócio derivam de leis, políticas da empresa ou regulamentações de mercado. Elas existiriam mesmo que a empresa operasse no papel.


3. No modelo de qualidade FURPS+, a letra 'P' refere-se a:

  • A) Portability (Portabilidade de hardware).
  • B) Performance (Desempenho, tempo de resposta, consumo de memória e throughput).
  • C) Programming (Linguagem de programação escolhida).
  • D) Payment (Formas de pagamento suportadas).
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: No acrônimo FURPS+, o 'P' representa Performance (Desempenho), abrangendo métricas de latência, taxa de transferência de dados e uso de recursos do servidor.


🛠️ 8. Ponte para a Ação: Laboratório Prático

🎯 Próximo Passo Prático

Coloque esta teoria em prática executando o roteiro de laboratório autoguiado:
👉 ATIVIDADE 03: ENGENHARIA DE REQUISITOS


📌 9. Resumo Executivo & Key Takeaways

  • RF vs RNF vs RN: RF define o comportamento funcional (O quê); RNF define os critérios de qualidade (Como); RN define as políticas do negócio.
  • FURPS+: Framework consolidado de engenharia para elicitação abrangente de requisitos de qualidade.
  • Mensurabilidade: Requisitos não-funcionais devem conter métricas quantificáveis para permitir testes de aceitação automatizados.
  • Validação por Contrato: Modelos Pydantic v2 garantem que as regras de integridade dos requisitos sejam respeitadas em tempo de execução.

🔍 CAPÍTULO 06: ELICITAÇÃO E LEVANTAMENTO DE REQUISITOS


🎯 1. Objetivos de Aprendizagem & Competências

Estimativa de Dedicação: 2 horas de estudo autoguiado.
Ao final deste capítulo, você será capaz de:

  • 🔹 Compreender as principais técnicas de elicitação: Entrevistas estruturadas/semiestruturadas, Workshops (JAD), Questionários, Etnografia (Observação) e Prototipagem.
  • 🔹 Identificar e gerenciar conflitos de interesses entre diferentes perfis de stakeholders corporativos.
  • 🔹 Construir Personas e Mapas de Empatia para desvendar dores reais e comportamentos dos usuários finais.
  • 🔹 Implementar scripts em Python 3.11+ para processamento e estruturação de dados colhidos em questionários de elicitação.

🏢 2. Cenário Corporativo & Estudo de Caso (TecProExpress)

Na TecProExpress, a diretoria de operações quer automatizar o processo de triagem de pacotes nos centros de distribuição. No entanto, os operadores de galpão usam luvas grossas e trabalham em ritmo acelerado, enquanto os gerentes de logística querem relatórios complexos com dezenas de campos para preenchimento.

O Desafio: Se o analista de requisitos apenas entrevistar a diretoria em uma sala com ar-condicionado, o aplicativo será um fracasso no chão de fábrica. Você deve aplicar técnicas de observação direta (etnografia) e prototipação rápida para elicitar as restrições ergonômicas e funcionais reais.


🧠 3. Fundamentação Teórica & Modelos Visuais

3.1. O Processo de Elicitação de Requisitos

Elicitar não é apenas "coletar" (como se os requisitos estivessem prontos em uma gaveta), mas sim descobrir, articular e negociar as necessidades com os stakeholders:

flowchart LR
    D["🔍 Descoberta<br>(Entrevistas & Etnografia)"] --> C["📊 Classificação & Organização<br>(Agrupamento por Domínio)"]
    C --> N["🤝 Negociação & Priorização<br>(MoSCoW & Trade-offs)"]
    N --> DOC["📝 Especificação Preliminar<br>(User Stories / Casos de Uso)"]
    DOC -.-> D
    
    style D fill:#e0f2fe,stroke:#0284c7
    style C fill:#ede7f6,stroke:#5e35b1
    style N fill:#fef3c7,stroke:#d97706
    style DOC fill:#dcfce7,stroke:#16a34a

3.2. Comparativo de Técnicas de Elicitação

Técnica de ElicitaçãoQuando UtilizarVantagensDesafios
Entrevistas IndividuaisDetalhamento profundo de processos com especialistas.Riqueza de contexto e respostas abertas.Consome muito tempo; risco de viés individual.
Workshops (JAD / Design Sprint)Alinhamento de visões divergentes entre departamentos.Consenso rápido e decisão coletiva.Dificuldade de reunir agendas de executivos.
Questionários / SurveysPopulação grande e geograficamente dispersa (> 1000 usuários).Coleta quantitativa e análise estatística.Respostas superficiais; impossível aprofundar dúvidas.
Observação Etnográfica (Shadowing)Tarefas operacionais complexas e rotinas de campo.Revela regras tácitas que os usuários esquecem de falar.Pode alterar o comportamento natural do operador.
Prototipação RápidaUsuários têm dificuldade de visualizar a solução em texto.Feedback visual imediato e validação de usabilidade.Cliente pode confundir protótipo com sistema final pronto.

💻 4. Aplicação Prática & Código Executável (Python 3.11+)

Script para estruturação de dados de entrevistas de elicitação e extração de requisitos preliminares.

📋 Pré-requisitos e Instalação

Este módulo utiliza exclusivamente recursos nativos do Python 3.11+ (dataclasses). Nenhuma instalação externa é necessária:

# Ambiente padrão Python 3.11+ (sem dependências externas)

💻 Código Completo e Autocontido (analise_elicitacao.py)

Crie o arquivo analise_elicitacao.py e insira o código abaixo:

"""
Módulo: analise_elicitacao.py
Domínio: Consolidação de respostas de stakeholders e mapeamento de dores.
Stack: Python 3.11+
"""
from dataclasses import dataclass

@dataclass
class RespostaEntrevista:
    stakeholder_id: str
    papel: str  # Operador, Gerente, Cliente
    dor_relatada: str
    funcionalidade_desejada: str
    impacto_estimado: int  # 1 a 5 (5 = Crítico)

def processar_requisitos_prioritarios(respostas: list[RespostaEntrevista], corte_impacto: int = 4) -> list[str]:
    """Filtra e consolida as funcionalidades com maior impacto para compor o MVP."""
    prioritarias = [
        f"[{r.papel.upper()}] {r.funcionalidade_desejada} (Motivação: {r.dor_relatada})"
        for r in respostas if r.impacto_estimado >= corte_impacto
    ]
    return prioritarias

if __name__ == "__main__":
    dados_campo = [
        RespostaEntrevista("STK-01", "Operador", "Leitor de código de barras falha com pouca luz", "Modo lanterna automática no app", 5),
        RespostaEntrevista("STK-02", "Gerente", "Falta de visão dos pacotes atrasados no dia", "Dashboard de alertas em tempo real", 5),
        RespostaEntrevista("STK-03", "Cliente", "Desejo escolher a cor do tema da tela de rastreio", "Tema escuro personalizado", 2)
    ]

    mvp_items = processar_requisitos_prioritarios(dados_campo)
    print("📋 ITENS ELICITADOS PRIORITÁRIOS PARA O BACKLOG:")
    for item in mvp_items:
        print(f" ✔️ {item}")

🚀 Como Executar

Execute o script no terminal:

python analise_elicitacao.py

🖥️ Saída Esperada no Terminal

📋 ITENS ELICITADOS PRIORITÁRIOS PARA O BACKLOG:
 ✔️ [OPERADOR] Modo lanterna automática no app (Motivação: Leitor de código de barras falha com pouca luz)
 ✔️ [GERENTE] Dashboard de alertas em tempo real (Motivação: Falta de visão dos pacotes atrasados no dia)

💡 5. Checkpoint de Engenharia & Boas Práticas

Boas Práticas & Anti-Patterns

  • Escuta Ativa & Perguntas Abertas: Em entrevistas, nunca faça perguntas indutivas como "Você não acha que seria melhor ter um botão vermelho aqui?". Pergunte: "Como você realiza essa tarefa hoje e quais são suas maiores dificuldades?".
  • Anti-Pattern The Customer is Always Right on Solutions: O cliente é especialista no problema dele, mas não necessariamente na melhor solução técnica. Seu papel é entender a dor profunda e projetar a arquitetura ideal.

🔗 6. Conexão com os Projetos Integradores

Projeto IntegradorComo o conceito deste capítulo é aplicado no PI
PI-03: PDVLiteElicitação ergonômica: frente de caixa deve permitir fechar venda apenas com atalhos de teclado (sem mouse).
PI-08: CattleFlowObservação no curral: necessidade de leitura de brinco eletrônico e pesagem rápida sem digitação manual.

🧪 7. Quiz de Fixação e Autoavaliação (Formative Assessment)

🧪 Quiz de Autoavaliação — Capítulo 06

1. Qual técnica de elicitação é mais eficiente para descobrir regras de negócio tácitas que os usuários executam no piloto automático mas esquecem de verbalizar em reuniões?

  • A) Questionários com perguntas de múltipla escolha enviadas por e-mail.
  • B) Observação Etnográfica (Shadowing / Acompanhamento no local de trabalho).
  • C) Análise de concorrência na internet.
  • D) Leitura da documentação do banco de dados.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: A observação direta permite ao engenheiro ver o operador trabalhando no seu ambiente real, revelando atalhos manuais, exceções e problemas que nunca seriam lembrados em uma entrevista formal.


2. O que representa uma 'Persona' no contexto da Engenharia de Requisitos e Design Centrado no Usuário?

  • A) Um pseudônimo fictício para ocultar o nome dos programadores do projeto.
  • B) Um arquétipo realista construído com base em dados reais de pesquisa que sintetiza as características, metas, dores e comportamentos do usuário-alvo.
  • C) O diagrama de classes de autenticação de usuários.
  • D) Um robô de inteligência artificial de testes.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: Personas personificam os usuários reais para os quais o sistema está sendo projetado, mantendo a equipe focada em resolver as dores daquele perfil específico ao priorizar histórias.


3. Durante uma sessão de JAD (Joint Application Design / Workshop), o papel do facilitador é:

  • A) Escrever o código-fonte dos endpoints da API.
  • B) Mediar discussões, neutralizar conflitos e garantir que todos os stakeholders colaborem para atingir o consenso de escopo.
  • C) Decidir sozinho todas as regras de negócio sem consultar os usuários.
  • D) Registrar o domínio na internet.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: O facilitador atua de forma neutra conduzindo a dinâmica do workshop para evitar que participantes mais extrovertidos dominem a reunião e garantindo o alinhamento de escopo.


🛠️ 8. Ponte para a Ação: Laboratório Prático

🎯 Próximo Passo Prático

Coloque esta teoria em prática executando o roteiro de laboratório autoguiado:
👉 ATIVIDADE 01: ESCOPO E PERSONAS


📌 9. Resumo Executivo & Key Takeaways

  • Elicitação é Investigação: Requisitos são descobertos através da combinação de entrevistas, observação e prototipação.
  • Diversidade de Fontes: Ouvir apenas a liderança gera sistemas que a operação rejeita; ouvir apenas a operação gera sistemas fora da estratégia da empresa.
  • Etnografia e Contexto: O chão de fábrica e o campo impõem restrições que nenhuma reunião de escritório revela.
  • Personas: Ferramenta poderosa para manter o time focado no valor real do usuário durante todo o ciclo de vida do software.

📝 CAPÍTULO 07: ESPECIFICAÇÃO DE REQUISITOS (ERS E USER STORIES)


🎯 1. Objetivos de Aprendizagem & Competências

Estimativa de Dedicação: 2 horas de estudo autoguiado.
Ao final deste capítulo, você será capaz de:

  • 🔹 Compreender a estrutura de uma Especificação de Requisitos de Software (ERS / SRS) segundo as normas IEEE 830 e ISO/IEC/IEEE 29148.
  • 🔹 Formular Histórias de Usuário (User Stories) segundo o acrônimo INVEST (Independent, Negotiable, Valuable, Estimable, Small, Testable).
  • 🔹 Escrever Critérios de Aceite estruturados utilizando a sintaxe Gherkin / BDD (Dado / Quando / Então).
  • 🔹 Implementar testes automatizados em Python 3.11+ com pytest que validam diretamente os cenários descritos em Gherkin.

🏢 2. Cenário Corporativo & Estudo de Caso (TecProExpress)

Na TecProExpress, um cliente corporativo de grande porte assinou um contrato para integração de API de cálculo de fretes em tempo real. No entanto, o desenvolvedor backend implementou a resposta retornando prazos em dias úteis, enquanto o cliente esperava dias corridos.

O Desafio: A ausência de critérios de aceite inequívocos na ERS gerou 3 semanas de retrabalho e desgaste comercial. Como Engenheiro de Software, você deve formalizar a especificação através de User Stories claras e cenários executáveis em Gherkin que sirvam como contrato técnico e base de testes automatizados.


🧠 3. Fundamentação Teórica & Modelos Visuais

3.1. ERS Tradicional (IEEE 830) vs. User Stories Ágeis (INVEST)

A documentação de requisitos evoluiu de especificações volumosas para artefatos enxutos e orientados a valor de negócio:

flowchart TD
    subgraph MODELOS_ESPECIFICACAO ["EVOLUÇÃO DA ESPECIFICAÇÃO"]
        direction LR
        ERS["📄 ERS Tradicional (IEEE 830)<br>• Seções exaustivas<br>• Contrato formal rígido<br>• Ideal para sistemas críticos"]
        US["📋 User Story Ágil (INVEST)<br>• Como [ator] Quero [meta] Para [benefício]<br>• Critérios de Aceite em Gherkin<br>• Conversação contínua"]
    end
    style ERS fill:#e1f5fe,stroke:#0284c7
    style US fill:#dcfce7,stroke:#16a34a

3.2. A Tríade da User Story: Os 3 Cs de Ron Jeffries

Uma História de Usuário completa é composta por três elementos:

  1. Cartão (Card): A descrição sintética no formato "Como... Quero... Para que...".
  2. Conversa (Conversation): A discussão entre o Product Owner e a equipe técnica para detalhar nuances.
  3. Confirmação (Confirmation): Os Critérios de Aceite que determinam quando a história está concluída.
flowchart LR
    C1["📇 Cartão (Card)<br>Como [Ator] Quero [Ação] Para [Benefício]"] --> C2["🗣️ Conversa (Conversation)<br>Alinhamento técnico & regras"]
    C2 --> C3["✅ Confirmação (Confirmation)<br>Cenários BDD / Critérios de Aceite"]
    
    style C1 fill:#e0f2fe,stroke:#0284c7
    style C2 fill:#ede7f6,stroke:#5e35b1
    style C3 fill:#dcfce7,stroke:#16a34a

3.3. Sintaxe BDD / Gherkin (Dado - Quando - Então)

Cenário: Cálculo de frete expresso para capitais
  Dado que o cliente está autenticado na API
  E o endereço de destino pertence à capital "São Paulo"
  Quando o cliente solicita o frete de um pacote de 2.0 kg
  Então o sistema deve retornar o valor de R$ 15.00
  E o prazo de entrega deve ser de "1 dia útil"

💻 4. Aplicação Prática & Código Executável (Python 3.11+)

Implementação do teste automatizado com Pytest traduzindo os cenários Gherkin (BDD).

📋 Pré-requisitos e Instalação

No terminal do seu ambiente virtual, instale o executor de testes Pytest:

pip install pytest

💻 Código Completo e Autocontido (test_calculo_frete_bdd.py)

Crie o arquivo test_calculo_frete_bdd.py e insira o código abaixo integralmente:

"""
Módulo: test_calculo_frete_bdd.py
Domínio: Testes de aceitação automatizados espelhando cenários BDD.
Stack: Python 3.11+ | Pytest
"""
from decimal import Decimal
import pytest

# Implementação do serviço de frete
def calcular_frete_entrega(peso_kg: Decimal, eh_capital: bool, expresso: bool) -> tuple[Decimal, int]:
    """Retorna (valor_reais, prazo_dias_uteis)."""
    if peso_kg <= Decimal("0.0"):
        raise ValueError("Peso inválido.")

    valor_base = Decimal("10.00") + (peso_kg * Decimal("2.50"))
    prazo = 3

    if eh_capital:
        prazo -= 1
    if expresso:
        valor_base *= Decimal("1.30")
        prazo = 1

    return (valor_base.quantize(Decimal("0.01")), prazo)

# --- CENÁRIOS BDD IMPLEMENTADOS COM PYTEST ---
def test_cenario_frete_expresso_capital():
    # Dado: Pacote de 2.0 kg para Capital em modo Expresso
    peso = Decimal("2.0")

    # Quando: Solicita o frete
    valor, prazo = calcular_frete_entrega(peso_kg=peso, eh_capital=True, expresso=True)

    # Então: Valida valor e prazo
    assert valor == Decimal("19.50")  # (10 + 5) * 1.30 = 19.50
    assert prazo == 1

def test_cenario_frete_convencional_interior():
    valor, prazo = calcular_frete_entrega(peso_kg=Decimal("1.0"), eh_capital=False, expresso=False)
    assert valor == Decimal("12.50")
    assert prazo == 3

if __name__ == "__main__":
    v, p = calcular_frete_entrega(Decimal("2.0"), True, True)
    print(f"✅ Cenário Validado: Frete R$ {v:.2f} em {p} dia(s) útil(eis)")

    print("\n🧪 Disparando suíte automatizada de cenários BDD com Pytest:")
    pytest.main(["-v", __file__])

🚀 Como Executar

Execute diretamente com Python ou pelo CLI do Pytest:

# Execução direta com Python (executa lógica e testes)
python test_calculo_frete_bdd.py

# Ou execução formal pelo Pytest
pytest -v test_calculo_frete_bdd.py

🖥️ Saída Esperada no Terminal

✅ Cenário Validado: Frete R$ 19.50 em 1 dia(s) útil(eis)

🧪 Disparando suíte automatizada de cenários BDD com Pytest:
============================= test session starts =============================
test_calculo_frete_bdd.py::test_cenario_frete_expresso_capital PASSED    [ 50%]
test_calculo_frete_bdd.py::test_cenario_frete_convencional_interior PASSED [100%]
============================== 2 passed in 0.04s ==============================

💡 5. Checkpoint de Engenharia & Boas Práticas

Boas Práticas & Anti-Patterns

  • Critério INVEST: Uma boa User Story deve ser Independente, Negociável, Valiosa, Estimável, Pequena (Small) e Testável.
  • Anti-Pattern Requisitos Técnicos como User Story: Evite escrever "Como desenvolvedor quero criar uma tabela no PostgreSQL". O formato de User Story existe para expressar o valor do usuário final (ex: "Como motorista quero consultar minhas rotas do dia para otimizar meu tempo").

🔗 6. Conexão com os Projetos Integradores

Projeto IntegradorComo o conceito deste capítulo é aplicado no PI
PI-01: ManuTrackEspecificação de RF01 a RF04 no formato de contratos com critérios de aceite e esquemas Pydantic.
PI-10: ImobiFlowCenários Gherkin para validação de retenção de taxa de administração e repasse a donos.

🧪 7. Quiz de Fixação e Autoavaliação (Formative Assessment)

🧪 Quiz de Autoavaliação — Capítulo 07

1. No acrônimo INVEST para elaboração de User Stories, a letra 'T' significa:

  • A) Temporal (Com prazo fixo de entrega).
  • B) Transactional (Com suporte a transações ACID).
  • C) Testable (Testável, possuindo critérios de aceite claros que permitam verificar sua conclusão).
  • D) Technological (Focada em ferramentas de tecnologia).
💡 Ver Resposta e Justificativa

Resposta Correta: C
Justificativa: Testable (Testável) assegura que a história possui critérios de aceite inequívocos pelos quais a equipe e o cliente podem comprovar se a funcionalidade foi atendida.


2. Na sintaxe Gherkin / BDD, a cláusula 'Dado' (Given) tem a função de:

  • A) Executar a ação do usuário no sistema.
  • B) Estabelecer o contexto inicial e as pré-condições necessárias antes da ação.
  • C) Verificar o resultado final esperado.
  • D) Encerrar o programa com código de saída 0.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: O 'Dado' (Given) descreve o estado inicial do sistema e as pré-condições (ex: usuário autenticado, saldo em conta) antes de executar o gatilho ('Quando').


3. Quais são os '3 Cs' de uma User Story segundo Ron Jeffries?

  • A) Código, Compilação e Commit.
  • B) Cartão (Card), Conversa (Conversation) e Confirmação (Confirmation).
  • C) Conectividade, Cloud e Concorrência.
  • D) Custo, Cronograma e Complexidade.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: Os 3 Cs representam: o Cartão com o texto síntese, a Conversa contínua entre o time e o PO, e a Confirmação expressa nos critérios de aceite.


🛠️ 8. Ponte para a Ação: Laboratório Prático

🎯 Próximo Passo Prático

Coloque esta teoria em prática executando o roteiro de laboratório autoguiado:
👉 ATIVIDADE 04: USER STORIES E BACKLOG


📌 9. Resumo Executivo & Key Takeaways

  • ERS vs User Story: A ERS tradicional documenta o contrato exaustivo (IEEE 830); as User Stories documentam a intenção de valor de forma incremental.
  • INVEST: Guia de excelência para granularidade e qualidade de histórias de backlog.
  • BDD & Gherkin: Linguagem comum entre desenvolvedores, testadores e analistas de negócio (Dado / Quando / Então).
  • Especificação Executável: Testes com Pytest transformam critérios de aceite em garantias automáticas contra regressão.

🛡️ CAPÍTULO 08: VALIDAÇÃO E GESTÃO DE REQUISITOS


🎯 1. Objetivos de Aprendizagem & Competências

Estimativa de Dedicação: 2 horas de estudo autoguiado.
Ao final deste capítulo, você será capaz de:

  • 🔹 Avaliar a qualidade de requisitos aplicando os 5 critérios de Sommerville (Validade, Consistência, Completeza, Realismo e Verificabilidade).
  • 🔹 Compreender a economia do erro e a regra de custo exponencial de correção (Regra 1:10:100).
  • 🔹 Construir e manter Matrizes de Rastreabilidade de Requisitos (RTM) para análise de impacto em mudanças.
  • 🔹 Implementar um sistema de controle de solicitações de mudança (Change Requests) em Python 3.11+.

🏢 2. Cenário Corporativo & Estudo de Caso (TecProExpress)

Na TecProExpress, a equipe de desenvolvimento identificou um grave conflito durante o sprint: o Requisito RF-02 (Cadastro) determinava que o CPF de clientes poderia ser editado a qualquer momento, enquanto o Requisito RF-45 (Segurança e Auditoria Fiscal) proibia qualquer alteração de chaves primárias de identificação fiscal.

O Desafio: Como Analista de Requisitos Sênior, você deve realizar a auditoria cruzada de requisitos, identificar ambiguidades e contradições antes que as tabelas de banco de dados e APIs sejam construídas, formalizando um comitê de controle de mudanças (Change Control Board - CCB).


🧠 3. Fundamentação Teórica & Modelos Visuais

3.1. Os 5 Pilares de Validação de Requisitos (Sommerville)

A validação assegura que a especificação reflete a intenção real do negócio sem contradições lógicas:

flowchart TD
    subgraph PILARES ["5 CRITÉRIOS DE VALIDAÇÃO DE REQUISITOS"]
        V["1. VALIDADE: O software resolve a necessidade real?"]
        C["2. CONSISTÊNCIA: Existem requisitos contraditórios?"]
        CP["3. COMPLETEZA: Todas as exceções e fluxos alternativos foram previstos?"]
        R["4. REALISMO: É viável implementar dentro do prazo e orçamento?"]
        VF["5. VERIFICABILIDADE: É possível escrever um teste automatizado para comprovar?"]
    end
    
    style PILARES fill:#f8fafc,stroke:#475569
    style V fill:#e0f2fe,stroke:#0284c7
    style C fill:#ede7f6,stroke:#5e35b1
    style CP fill:#dcfce7,stroke:#16a34a
    style R fill:#fef3c7,stroke:#d97706
    style VF fill:#fee2e2,stroke:#ef4444

3.2. Fluxo de Gestão de Mudanças (Change Management)

Requisitos de software mudam continuamente devido a novas leis, estratégias de mercado e tecnologias:

flowchart LR
    REQ["📢 Solicitação de Mudança<br>(Change Request)"] --> IMP["🔍 Análise de Impacto<br>(Custo, Prazo & RTM)"]
    IMP --> CCB["⚖️ Comitê CCB<br>(Aprovação/Rejeição)"]
    CCB -->|Aprovado| UPD["📝 Atualização de ERS<br>& Backlog Sprint"]
    CCB -->|Rejeitado| LOG["📁 Arquivamento Justificado"]
    
    style REQ fill:#e0f2fe,stroke:#0284c7
    style IMP fill:#ede7f6,stroke:#5e35b1
    style CCB fill:#fef3c7,stroke:#d97706
    style UPD fill:#dcfce7,stroke:#16a34a
    style LOG fill:#fee2e2,stroke:#ef4444

💻 4. Aplicação Prática & Código Executável (Python 3.11+)

Implementação de um motor de gestão de mudanças e análise de impacto.

📋 Pré-requisitos e Instalação

Este módulo utiliza exclusivamente recursos nativos do Python 3.11+ (dataclasses e enum). Nenhuma instalação externa é necessária:

# Ambiente padrão Python 3.11+ (sem dependências externas)

💻 Código Completo e Autocontido (gestao_mudancas_ccb.py)

Crie o arquivo gestao_mudancas_ccb.py e insira o código abaixo integralmente:

"""
Módulo: gestao_mudancas_ccb.py
Domínio: Análise de impacto e aprovação de Change Requests (CR).
Stack: Python 3.11+
"""
from dataclasses import dataclass, field
from enum import Enum, auto

class StatusMudanca(Enum):
    SUBMETIDA = auto()
    EM_ANALISE_IMPACTO = auto()
    APROVADA_CCB = auto()
    REJEITADA = auto()

@dataclass
class SolicitacaoMudanca:
    id_cr: str
    descricao: str
    requisitos_impactados: list[str]
    horas_estimadas: int
    custo_adicional: float
    status: StatusMudanca = StatusMudanca.SUBMETIDA

class ComiteControleMudancas:
    def __init__(self, limite_orcamento_livre: float = 5000.00):
        self.limite_orcamento_livre = limite_orcamento_livre
        self.solicitacoes: dict[str, SolicitacaoMudanca] = {}

    def registrar_cr(self, cr: SolicitacaoMudanca):
        self.solicitacoes[cr.id_cr] = cr

    def deliberar(self, id_cr: str) -> str:
        cr = self.solicitacoes.get(id_cr)
        if not cr:
            raise KeyError("CR não encontrada.")

        if cr.custo_adicional <= self.limite_orcamento_livre and cr.horas_estimadas <= 40:
            cr.status = StatusMudanca.APROVADA_CCB
            return f"✅ [{cr.id_cr}] APROVADA: Impacto de {len(cr.requisitos_impactados)} requisito(s) dentro do limite."
        else:
            cr.status = StatusMudanca.EM_ANALISE_IMPACTO
            return f"⚠️ [{cr.id_cr}] RETIDA: Requer aprovação da Diretoria Executiva (Custo: R$ {cr.custo_adicional:.2f})."

if __name__ == "__main__":
    ccb = ComiteControleMudancas()
    cr1 = SolicitacaoMudanca("CR-001", "Adição de PIX no Checkout", ["RF-08", "RF-09"], 16, 2400.00)
    cr2 = SolicitacaoMudanca("CR-002", "Migração completa de banco legado", ["RF-01", "RF-02", "RNF-01"], 120, 18000.00)

    ccb.registrar_cr(cr1)
    ccb.registrar_cr(cr2)

    print(ccb.deliberar("CR-001"))
    print(ccb.deliberar("CR-002"))

🚀 Como Executar

Execute o script no terminal:

python gestao_mudancas_ccb.py

🖥️ Saída Esperada no Terminal

✅ [CR-001] APROVADA: Impacto de 2 requisito(s) dentro do limite.
⚠️ [CR-002] RETIDA: Requer aprovação da Diretoria Executiva (Custo: R$ 18000.00).

💡 5. Checkpoint de Engenharia & Boas Práticas

Boas Práticas & Anti-Patterns

  • Regra 1:10:100 (Pressman / Boehm): O custo de corrigir um defeito no documento de requisitos custa 1×; durante o desenvolvimento custa 10×; após o deploy em produção custa 100×. Invista em validação precoce!
  • Anti-Pattern Scope Creep: A expansão desordenada de escopo sem análise de impacto técnico e financeiro é a principal causa de estouro de orçamento em projetos de TI.

🔗 6. Conexão com os Projetos Integradores

Projeto IntegradorComo o conceito deste capítulo é aplicado no PI
PI-02: StockFlowValidação de consistência: movimentações de almoxarifado nunca podem permitir saldo negativo de estoque.
PI-09: FinLiteRTM conectando os títulos a pagar/receber com as baixas financeiras e saldos das contas bancárias.

🧪 7. Quiz de Fixação e Autoavaliação (Formative Assessment)

🧪 Quiz de Autoavaliação — Capítulo 08

1. De acordo com a 'Regra 1:10:100' da Engenharia de Software, por que a validação de requisitos é considerada uma atividade de altíssimo retorno financeiro?

  • A) Porque ela substitui a necessidade de contratar programadores.
  • B) Porque detectar e corrigir um erro conceitual na fase de requisitos custa até 100 vezes menos do que corrigi-lo após o sistema estar em produção.
  • C) Porque ela elimina a necessidade de infraestrutura na nuvem.
  • D) Porque ela gera lucros automáticos no mercado de ações.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: O custo de retrabalho cresce exponencialmente. Um erro corrigido no documento exige apenas alterar um texto; em produção, exige refatorar código, migrar bancos de dados, retestar e reparar danos aos usuários.


2. Qual é o papel principal de um Comitê de Controle de Mudanças (CCB - Change Control Board)?

  • A) Escrever o código backend das APIs em Flask.
  • B) Avaliar o impacto técnico, de prazo e de custo de solicitações de alteração de escopo, aprovando ou rejeitando mudanças formalmente.
  • C) Comprar os computadores e licenças da equipe.
  • D) Conduzir testes manuais de interface.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: O CCB evita o 'Scope Creep', garantindo que toda modificação de escopo seja tecnicamente justificada, orçada e acordada por todas as partes antes da implementação.


3. O critério de validação de 'Verificabilidade' estabelece que:

  • A) O sistema deve rodar apenas no sistema operacional Windows.
  • B) Todo requisito deve ser formulado de maneira que permita criar um teste objetivo e mensurável para comprovar se ele foi satisfeito.
  • C) O código deve ser escrito sem uso de frameworks.
  • D) O software não pode conter bancos de dados relacionais.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: Se não for possível escrever um teste automatizado ou um procedimento claro para auditar o atendimento de um requisito, ele é ambíguo e não-verificável.


🛠️ 8. Ponte para a Ação: Laboratório Prático

🎯 Próximo Passo Prático

Coloque esta teoria em prática executando o roteiro de laboratório autoguiado:
👉 ATIVIDADE 03: ENGENHARIA DE REQUISITOS


📌 9. Resumo Executivo & Key Takeaways

  • Validação Precoce: Validar requisitos no papel é a forma mais barata de garantir a qualidade e a sustentabilidade de um software.
  • 5 Pilares de Qualidade: Validade, Consistência, Completeza, Realismo e Verificabilidade.
  • Gestão de Mudanças (CCB): Mudanças são inevitáveis no ciclo de vida de TI, mas devem ser formalmente auditadas em custo e prazo.
  • Matriz de Rastreabilidade: Permite avaliar em segundos quais módulos e testes serão impactados quando um requisito mudar.

📐 CAPÍTULO 09: FUNDAMENTOS DA MODELAGEM E UML


🎯 1. Objetivos de Aprendizagem & Competências

Estimativa de Dedicação: 2 horas de estudo autoguiado.
Ao final deste capítulo, você será capaz de:

  • 🔹 Compreender o papel da UML (Unified Modeling Language - OMG) como linguagem visual padrão da indústria para especificação e design de sistemas.
  • 🔹 Dominar o Modelo 4+1 Visões de Arquitetura de Philippe Kruchten (Lógica, Implementação, Processo, Implantação e Casos de Uso).
  • 🔹 Classificar a taxonomia da UML 2.5 entre Diagramas Estruturais (estáticos) e Comportamentais (dinâmicos).
  • 🔹 Construir diagramas arquiteturais profissionais utilizando a abordagem Diagrams as Code com Mermaid e Draw.io.

🏢 2. Cenário Corporativo & Estudo de Caso (TecProExpress)

Na TecProExpress, a primeira versão do sistema de Gestão de Hubs Logísticos falhou antes do deploy em produção. Cada desenvolvedor implementou as entidades de banco com nomes diferentes (Cliente, Usuario, Destinatario), gerando incompatibilidade total entre os microsserviços.

O Desafio: Como Arquiteto de Software, você deve instituir a cultura de Modeling First (Diagrams as Code). Nenhuma linha de código ou tabela relacional deve ser criada sem que a planta baixa arquitetural e as interfaces de classes tenham sido previamente modeladas e validadas pela equipe.


🧠 3. Fundamentação Teórica & Modelos Visuais

3.1. O Modelo 4+1 Visões de Philippe Kruchten

Para atender às necessidades de diferentes públicos (desenvolvedores, clientes, operadores e analistas), a arquitetura é organizada em 5 visões integradas:

flowchart TD
    subgraph KRUCHTEN ["MODELO 4+1 VISÕES DE ARQUITETURA (KRUCHTEN)"]
        UC["🎯 5. VISÃO DE CASOS DE USO<br>(Usuários Finais & Requisitos de Negócio)"]
        
        V1["🧠 1. Visão Lógica<br>(Classes, Pacotes & DER)<br>Público: Desenvolvedores"]
        V2["⚡ 2. Visão de Processos<br>(Threads, Concorrência & SLA)<br>Público: Integradores"]
        V3["📦 3. Visão de Implementação<br>(Módulos, Bibliotecas & Git)<br>Público: Programadores"]
        V4["🌐 4. Visão de Implantação<br>(Servidores, Docker, Nuvem & Redes)<br>Público: DevOps & SRE"]
        
        UC --> V1
        UC --> V2
        UC --> V3
        UC --> V4
    end
    
    style KRUCHTEN fill:#f8fafc,stroke:#475569
    style UC fill:#fef3c7,stroke:#d97706
    style V1 fill:#e0f2fe,stroke:#0284c7
    style V2 fill:#ede7f6,stroke:#5e35b1
    style V3 fill:#dcfce7,stroke:#16a34a
    style V4 fill:#fee2e2,stroke:#ef4444

3.2. Taxonomia dos Diagramas da UML 2.5

A UML 2.5 padroniza 14 diagramas divididos em duas grandes famílias:

flowchart TD
    subgraph UML ["TAXONOMIA DA UML 2.5"]
        ESTRUTURA["🧱 DIAGRAMAS ESTRUTURAIS (ESTÁTICOS)<br>• Diagrama de Classes<br>• Diagrama de Componentes<br>• Diagrama de Implantação<br>• Diagrama de Pacotes<br>• Diagrama de Objetos"]
        COMPORTAMENTO["⚡ DIAGRAMAS COMPORTAMENTAIS (DINÂMICOS)<br>• Diagrama de Casos de Uso<br>• Diagrama de Sequência<br>• Diagrama de Atividades<br>• Diagrama de Transição de Estados<br>• Diagrama de Comunicação"]
    end
    
    style ESTRUTURA fill:#e0f2fe,stroke:#0284c7
    style COMPORTAMENTO fill:#dcfce7,stroke:#16a34a

💻 4. Aplicação Prática & Código Executável (Python 3.11+)

Implementação da planta de classes modelada em código Python com tipagem, relacionamentos estruturados e exportação Diagrams as Code (Mermaid).

📋 Pré-requisitos e Instalação

Este módulo utiliza exclusivamente recursos nativos do Python 3.11+ (dataclasses e decimal). Nenhuma instalação externa é necessária:

# Ambiente padrão Python 3.11+ (sem dependências externas)

💻 Código Completo e Autocontido (modelo_logistica_classes.py)

Crie o arquivo modelo_logistica_classes.py e insira o código abaixo integralmente:

"""
Módulo: modelo_logistica_classes.py
Domínio: Transposição de diagrama de classes UML (Kruchten 4+1) e exportação Diagrams as Code.
Stack: Python 3.11+
"""
from dataclasses import dataclass, field
from decimal import Decimal

@dataclass
class PontoParada:
    id_ponto: int
    endereco: str
    concluido: bool = False

@dataclass
class RotaEntrega:
    id_rota: str
    motorista_responsavel: str
    pontos: list[PontoParada] = field(default_factory=list)
    quilometragem_total: Decimal = Decimal("0.0")

    def adicionar_parada(self, parada: PontoParada) -> None:
        self.pontos.append(parada)

    def calcular_progresso(self) -> float:
        if not self.pontos:
            return 0.0
        concluidos = sum(1 for p in self.pontos if p.concluido)
        return (concluidos / len(self.pontos)) * 100.0

    def gerar_mermaid_class_diagram(self) -> str:
        """Exporta a estrutura da planta lógica em notação Diagrams as Code (Mermaid)."""
        return (
            "classDiagram\n"
            "    class RotaEntrega {\n"
            "        +String id_rota\n"
            "        +String motorista_responsavel\n"
            "        +Decimal quilometragem_total\n"
            "        +adicionar_parada(PontoParada)\n"
            "        +calcular_progresso() float\n"
            "    }\n"
            "    class PontoParada {\n"
            "        +Int id_ponto\n"
            "        +String endereco\n"
            "        +Boolean concluido\n"
            "    }\n"
            '    RotaEntrega "1" *-- "many" PontoParada : contem'
        )

if __name__ == "__main__":
    rota = RotaEntrega("ROTA-2026-SP", "Marcos Antunes", quilometragem_total=Decimal("45.8"))
    rota.adicionar_parada(PontoParada(1, "Av. Paulista, 1000", concluido=True))
    rota.adicionar_parada(PontoParada(2, "Rua Augusta, 500", concluido=False))

    print(f"📦 Rota: {rota.id_rota} | Motorista: {rota.motorista_responsavel}")
    print(f"📊 Progresso da Rota: {rota.calcular_progresso():.1f}%")
    print("\n📐 DIAGRAMA MERMAID GERADO (DIAGRAMS AS CODE):")
    print(rota.gerar_mermaid_class_diagram())

🚀 Como Executar

Execute o script no terminal:

python modelo_logistica_classes.py

🖥️ Saída Esperada no Terminal

📦 Rota: ROTA-2026-SP | Motorista: Marcos Antunes
📊 Progresso da Rota: 50.0%

📐 DIAGRAMA MERMAID GERADO (DIAGRAMS AS CODE):
classDiagram
    class RotaEntrega {
        +String id_rota
        +String motorista_responsavel
        +Decimal quilometragem_total
        +adicionar_parada(PontoParada)
        +calcular_progresso() float
    }
    class PontoParada {
        +Int id_ponto
        +String endereco
        +Boolean concluido
    }
    RotaEntrega "1" *-- "many" PontoParada : contem

💡 5. Checkpoint de Engenharia & Boas Práticas

Boas Práticas & Anti-Patterns

  • Diagrams as Code (Mermaid): Mantenha os diagramas UML dentro do próprio repositório Git em arquivos Markdown ou .mmd. Assim, a documentação visual é versionada junto com os commits de código.
  • Anti-Pattern Modelagem Exaustiva (BDUF - Big Design Up Front): Não tente modelar 100% de cada atributo antes de programar. Modele a arquitetura essencial, as entidades chave e as interações críticas em ciclos iterativos.

🔗 6. Conexão com os Projetos Integradores

Projeto IntegradorComo o conceito deste capítulo é aplicado no PI
PI-05: ParkFlowModelagem do diagrama estrutural de classes relacionando Setores, Vagas, Veículos e Tickets.
PI-10: ImobiFlowVisão Lógica integrando Proprietários, Contratos, Faturas e Vistorias de Imóveis.

🧪 7. Quiz de Fixação e Autoavaliação (Formative Assessment)

🧪 Quiz de Autoavaliação — Capítulo 09

1. Qual é a principal finalidade do 'Modelo 4+1 Visões' proposto por Philippe Kruchten?

  • A) Substituir o banco de dados relacional por NoSQL.
  • B) Descrever a arquitetura de software a partir de múltiplas perspectivas integradas para atender a diferentes públicos (desenvolvedores, operadores, usuários e analistas).
  • C) Forçar a escrita de testes manuais em planilhas Excel.
  • D) Dividir o projeto em exatamente 5 semanas de desenvolvimento.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: O modelo 4+1 organiza a complexidade da arquitetura em 4 visões especializadas (Lógica, Processos, Implementação e Implantação), amarradas pela visão central de Casos de Uso.


2. Na UML 2.5, qual dos seguintes diagramas pertence à categoria de Diagramas Estruturais (Estáticos)?

  • A) Diagrama de Sequência.
  • B) Diagrama de Atividades.
  • C) Diagrama de Classes.
  • D) Diagrama de Transição de Estados.
💡 Ver Resposta e Justificativa

Resposta Correta: C
Justificativa: O Diagrama de Classes descreve a estrutura estática do sistema (classes, atributos, métodos e associações), sem representar a passagem do tempo ou mensagens dinâmicas.


3. O que define o conceito de 'Diagrams as Code' (como no Mermaid e PlantUML)?

  • A) Desenhar diagramas à mão em papel sulfite.
  • B) Escrever diagramas utilizando texto declarativo legível, permitindo versionamento, diff e integração contínua no Git.
  • C) Usar programas pesados que exportam apenas arquivos binários proprietários.
  • D) Proibir o uso de comentários no código.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: Diagrams as Code permite criar, versionar e revisar diagramas em texto plano (Markdown/Mermaid) diretamente na esteira de desenvolvimento do repositório.


🛠️ 8. Ponte para a Ação: Laboratório Prático

🎯 Próximo Passo Prático

Coloque esta teoria em prática executando o roteiro de laboratório autoguiado:
👉 ATIVIDADE 05: CASOS DE USO (UML)


📌 9. Resumo Executivo & Key Takeaways

  • UML é o Padrão da Indústria: Permite comunicação visual inequívoca entre analistas, arquitetos e desenvolvedores.
  • Modelo 4+1: Estrutura o sistema nas visões Lógica, Processo, Implementação e Implantação em torno dos Casos de Uso.
  • Estático vs Dinâmico: Diagramas Estruturais mostram o que o sistema é; Diagramas Comportamentais mostram o que o sistema faz ao longo do tempo.
  • Diagrams as Code: Garante que a arquitetura visual seja versionada no Git lado a lado com o código-fonte.

🎭 CAPÍTULO 10: DIAGRAMA DE CASOS DE USO (CONCEITOS)


🎯 1. Objetivos de Aprendizagem & Competências

Estimativa de Dedicação: 2 horas de estudo autoguiado.
Ao final deste capítulo, você será capaz de:

  • 🔹 Compreender a finalidade do Diagrama de Casos de Uso na captura de requisitos funcionais sob a perspectiva do usuário (Ivar Jacobson / UML).
  • 🔹 Identificar Atores Primários, Atores Secundários (Sistemas Externos/APIs) e o Limite do Sistema (System Boundary).
  • 🔹 Modelar diagramas de casos de uso profissionais com sintaxe declarativa Mermaid e Draw.io.
  • 🔹 Evitar o anti-pattern de desenhar sequências temporais e fluxogramas dentro do diagrama de casos de uso.

🏢 2. Cenário Corporativo & Estudo de Caso (TecProExpress)

Na TecProExpress, a diretoria executiva solicitou uma visão consolidada de todas as operações do novo Módulo de Gestão de Fretes e Frotas. Eles não desejam ver tabelas de banco de dados ou detalhes de infraestrutura, mas precisam entender com clareza quais ações podem ser executadas pelo Motorista, pelo Operador de Logística e pelo Gateway Bancário.

O Desafio: Seu objetivo como Analista de Sistemas é construir a "foto panorâmica" de escopo funcional do sistema através do Diagrama de Casos de Uso, estabelecendo com precisão o que está dentro do escopo do software (System Boundary) e quais entidades externas interagem com ele.


🧠 3. Fundamentação Teórica & Modelos Visuais

3.1. A Tríade do Diagrama de Casos de Uso

O diagrama organiza-se em torno de 3 elementos gráficos padronizados pela UML:

  1. Atores (Actors): Entidades externas que interagem com o sistema (usuários, sensores ou sistemas legados).
  2. Casos de Uso (Use Cases - Elipses): Conjuntos de ações realizadas pelo sistema que geram um resultado de valor observável para o ator.
  3. Limite do Sistema (Subject Boundary - Retângulo): Delimita o que pertence ao software que será construído versus o que é externo.
flowchart LR
    subgraph SIS ["🏢 SISTEMA TECPROEXPRESS (SYSTEM BOUNDARY)"]
        direction TB
        UC1(["📦 Registrar Pacote"])
        UC2(["🗺️ Rastrear Entrega"])
        UC3(["💳 Processar Pagamento"])
    end
    
    A1["👤 Operador Logístico"] --> UC1
    A2["👤 Cliente Final"] --> UC2
    UC3 --> S1["🏛️ Gateway de Pagamento (API)"]
    
    style SIS fill:#f8fafc,stroke:#475569
    style UC1 fill:#e0f2fe,stroke:#0284c7
    style UC2 fill:#e0f2fe,stroke:#0284c7
    style UC3 fill:#e0f2fe,stroke:#0284c7
    style A1 fill:#dcfce7,stroke:#16a34a
    style A2 fill:#dcfce7,stroke:#16a34a
    style S1 fill:#ede7f6,stroke:#5e35b1

3.2. Atores Primários vs. Atores Secundários

  • Ator Primário: Inicia a interação com o sistema para atingir uma meta pessoal de negócio (ex: o Cliente buscando rastrear seu pacote).
  • Ator Secundário (Ator de Suporte): É acionado pelo sistema para fornecer serviços auxiliares (ex: a API de Envio de SMS ou o Serviço de Cobrança Bancária).

💻 4. Aplicação Prática & Código Executável (Python 3.11+)

Mapeamento de casos de uso para controladores REST com Flask 3.x em Python 3.11+.

📋 Pré-requisitos e Instalação

No terminal do seu ambiente virtual, instale o microframework Flask:

pip install flask

💻 Código Completo e Autocontido (rotas_casos_uso.py)

Crie o arquivo rotas_casos_uso.py e insira o código abaixo integralmente:

"""
Módulo: rotas_casos_uso.py
Domínio: Transposição de casos de uso UML para endpoints REST com Flask 3.x.
Stack: Python 3.11+ | Flask 3.x
"""
from flask import Flask, request, jsonify

app = Flask(__name__)

# Base de dados em memória para simulação operacional
PACOTES_DB = {
    "BR123456": {"codigo": "BR123456", "status": "EM TRANSITO", "localizacao": "Hub São Paulo"}
}

# Caso de Uso 1: Registrar Pacote (Ator: Operador Logístico)
@app.post("/api/pacotes/")
def uc_registrar_pacote():
    """UC01: Permite ao Operador Logístico cadastrar um novo pacote no sistema."""
    dados = request.get_json()
    if not dados or "codigo" not in dados or "peso_kg" not in dados:
        return jsonify({"status": "ERRO", "mensagem": "Campos 'codigo' e 'peso_kg' são obrigatórios."}), 400

    PACOTES_DB[dados["codigo"]] = {
        "codigo": dados["codigo"],
        "peso_kg": dados["peso_kg"],
        "destino": dados.get("destino", "NÃO INFORMADO"),
        "status": "REGISTRADO"
    }
    return jsonify({"status": "SUCESSO", "mensagem": f"Pacote {dados['codigo']} registrado com sucesso."}), 201

# Caso de Uso 2: Rastrear Entrega (Ator: Cliente Final)
@app.get("/api/pacotes/<codigo>/rastreio")
def uc_rastrear_entrega(codigo: str):
    """UC02: Permite ao Cliente Final consultar o status em tempo real do pacote."""
    pacote = PACOTES_DB.get(codigo)
    if not pacote:
        return jsonify({"status": "ERRO", "detalhe": f"Pacote '{codigo}' não localizado."}), 404
    return jsonify(pacote), 200

# Ponto de Entrada Executável
if __name__ == "__main__":
    print("🚀 TESTE DE CONTROLADORES REST FLASK (CASOS DE USO UML)")
    print("=" * 65)

    # Execução de testes de integração automáticos via Flask Test Client nativo
    client = app.test_client()

    # 1. Simulação do Caso de Uso 1 (Operador cadastra pacote)
    print("1. [UC01 - Operador] Registrando pacote:")
    resp_post = client.post("/api/pacotes/", json={"codigo": "BR999888", "peso_kg": 3.5, "destino": "Curitiba/PR"})
    print(f"   Status HTTP: {resp_post.status_code}")
    print(f"   Resposta: {resp_post.get_json()}\n")

    # 2. Simulação do Caso de Uso 2 (Cliente rastreia pacote)
    print("2. [UC02 - Cliente] Rastreando entrega cadastrada:")
    resp_get = client.get("/api/pacotes/BR123456/rastreio")
    print(f"   Status HTTP: {resp_get.status_code}")
    print(f"   Resposta: {resp_get.get_json()}\n")

    # 3. Cenário de Falha (Pacote não encontrado)
    print("3. [UC02 - Exceção] Consultando código inexistente:")
    resp_404 = client.get("/api/pacotes/XX000000/rastreio")
    print(f"   Status HTTP: {resp_404.status_code}")
    print(f"   Resposta: {resp_404.get_json()}")
    print("=" * 65)
    print("💡 Dica: Para iniciar o servidor HTTP interativo na porta 5000, descomente: # app.run(port=5000, debug=True)")
    # app.run(port=5000, debug=True)

🚀 Como Executar

Execute o script no terminal:

python rotas_casos_uso.py

🖥️ Saída Esperada no Terminal

🚀 TESTE DE CONTROLADORES REST FLASK (CASOS DE USO UML)
=================================================================
1. [UC01 - Operador] Registrando pacote:
   Status HTTP: 201
   Resposta: {'mensagem': 'Pacote BR999888 registrado com sucesso.', 'status': 'SUCESSO'}

2. [UC02 - Cliente] Rastreando entrega cadastrada:
   Status HTTP: 200
   Resposta: {'codigo': 'BR123456', 'localizacao': 'Hub São Paulo', 'status': 'EM TRANSITO'}

3. [UC02 - Exceção] Consultando código inexistente:
   Status HTTP: 404
   Resposta: {'detalhe': "Pacote 'XX000000' não localizado.", 'status': 'ERRO'}
=================================================================
💡 Dica: Para iniciar o servidor HTTP interativo na porta 5000, descomente: # app.run(port=5000, debug=True)

💡 5. Checkpoint de Engenharia & Boas Práticas

Boas Práticas & Anti-Patterns

  • Verbo no Infinitivo: Nomeie casos de uso sempre iniciando com verbos de ação que expressem valor de negócio ("Emitir Boleto", "Cadastrar Imóvel" e não "Boleto" ou "Tela 02").
  • Anti-Pattern Decomposição Funcional (Fluxograma): O Diagrama de Casos de Uso não é um fluxograma. Não ligue elipses em sequência linear ("Digitar Senha" ➔ "Validar Usuário" ➔ "Abrir Menu"). Use Diagramas de Atividades para fluxos internos!

🔗 6. Conexão com os Projetos Integradores

Projeto IntegradorComo o conceito deste capítulo é aplicado no PI
PI-01: ManuTrackMapeamento dos casos de uso dos Atores: Técnico de Manutenção e Gestor de Produção.
PI-07: AgroSafeAtor Secundário: Sistema do MAPA/CREA integrado para validação de receituário agronômico.

🧪 7. Quiz de Fixação e Autoavaliação (Formative Assessment)

🧪 Quiz de Autoavaliação — Capítulo 10

1. Em um Diagrama de Casos de Uso, qual é a representação e significado do 'Limite do Sistema' (System Boundary)?

  • A) É um círculo que representa a internet.
  • B) É um retângulo que envolve os casos de uso, delimitando com precisão o que está dentro do escopo de desenvolvimento do software.
  • C) É a tabela de banco de dados principal.
  • D) É o servidor físico onde o software roda.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: O System Boundary separa o sistema (casos de uso internos) do mundo exterior (atores externos), estabelecendo com clareza a fronteira do projeto.


2. Qual das seguintes opções representa uma boa prática de nomenclatura para um Caso de Uso?

  • A) TELA_DE_LOGIN_V2
  • B) Banco de Dados PostgreSQL
  • C) Consultar Histórico de Vistorias
  • D) Verificar se o campo não é nulo
💡 Ver Resposta e Justificativa

Resposta Correta: C
Justificativa: Casos de uso devem ser nomeados com um verbo no infinitivo seguido de um substantivo, representando uma meta completa com valor para o ator.


3. Um sistema externo (como uma API bancária ou o serviço de CEP dos Correios) é modelado no Diagrama de Casos de Uso como:

  • A) Uma classe abstrata.
  • B) Um caso de uso interno.
  • C) Um Ator Secundário (ou Ator de Suporte).
  • D) Uma chave estrangeira de banco de dados.
💡 Ver Resposta e Justificativa

Resposta Correta: C
Justificativa: Sistemas e APIs externos que trocam informações com o software são modelados como atores secundários situados fora da fronteira do sistema.


🛠️ 8. Ponte para a Ação: Laboratório Prático

🎯 Próximo Passo Prático

Coloque esta teoria em prática executando o roteiro de laboratório autoguiado:
👉 ATIVIDADE 05: CASOS DE USO (UML)


📌 9. Resumo Executivo & Key Takeaways

  • Visão de Escopo: O Diagrama de Casos de Uso apresenta as capacidades do sistema sob a ótica do usuário e de sistemas externos.
  • Atores: Papéis que interagem com o sistema (não confunda com pessoas individuais).
  • Fronteira do Sistema: Delimita a responsabilidade da equipe técnica de desenvolvimento.
  • Nomenclatura Semântica: Sempre use [Verbo no Infinitivo] + [Objeto] expressando uma meta real de negócio.

🎭 CAPÍTULO 11: CASOS DE USO (PRÁTICA E RELAÇÕES)


🎯 1. Objetivos de Aprendizagem & Competências

Estimativa de Dedicação: 2 horas de estudo autoguiado.
Ao final deste capítulo, você será capaz de:

  • 🔹 Distinguir e aplicar com precisão os três relacionamentos da UML entre casos de uso: «include» (inclusão obrigatória), «extend» (extensão condicional) e Generalização/Especialização.
  • 🔹 Estruturar especificações textuais completas de casos de uso contendo Fluxo Principal (Caminho Feliz), Fluxos Alternativos e Fluxos de Exceção.
  • 🔹 Modelar diagramas com pontos de extensão (Extension Points) claros no Mermaid e Draw.io.
  • 🔹 Implementar o padrão de handlers condicionais e exceções em Python 3.11+ refletindo os fluxos modelados.

🏢 2. Cenário Corporativo & Estudo de Caso (TecProExpress)

Na TecProExpress, a equipe de desenvolvimento modelou um caso de uso para "Emitir Frete". No entanto, quando o cliente solicita entrega expressa aos domingos, uma taxa especial de seguro adicional precisa ser contratada condicionalmente. Além disso, toda emissão de frete exige obrigatoriamente a validação do token JWT de autenticação.

O Desafio: Você deve modelar corretamente esses comportamentos utilizando as relações «include» para a autenticação obrigatória e «extend» com ponto de extensão condicional para o seguro especial de domingo, evitando duplicação de lógica e garantindo clareza técnica.


🧠 3. Fundamentação Teórica & Modelos Visuais

3.1. As Relações entre Casos de Uso na UML

flowchart TD
    subgraph RELACOES_UC ["RELAÇÕES NA UML ENTRE CASOS DE USO"]
        direction TB
        UC_BASE(["📦 Emitir Frete (Base)"])
        
        UC_INC(["🔐 Validar Token JWT<br>(Obrigatório)"])
        UC_EXT(["🛡️ Contratar Seguro Especial<br>(Opcional / Condicional)"])
        
        UC_BASE -.->|"«include»"| UC_INC
        UC_EXT -.->|"«extend» [Se for Domingo]"| UC_BASE
    end
    
    style UC_BASE fill:#e0f2fe,stroke:#0284c7
    style UC_INC fill:#ede7f6,stroke:#5e35b1
    style UC_EXT fill:#fef3c7,stroke:#d97706

3.2. Comparativo: «include» vs. «extend» vs. Generalização

Relação UMLDireção da SetaQuando UsarExemplo Real
«include»Caso Base ➔ Caso IncluídoO caso de uso base sempre e obrigatoriamente executa o comportamento incluído para ser concluído.Efetuar Pagamento inclui Validar Saldo.
«extend»Caso Extensor ➔ Caso BaseO comportamento é adicionado apenas se uma condição específica for satisfeita no ponto de extensão.Calcular Frete Especial estende Emitir Pedido (se entrega for internacional).
GeneralizaçãoFilho ➔ Pai (Seta com triângulo vazio)O caso de uso filho herda e especializa o comportamento do pai.Pagar via PIX e Pagar via Cartão especializam Pagar Fatura.

💻 4. Aplicação Prática & Código Executável (Python 3.11+)

Implementação dos fluxos principal, de extensão («extend») e de inclusão obrigatória («include») em Python 3.11+.

📋 Pré-requisitos e Instalação

Este módulo utiliza exclusivamente recursos nativos do Python 3.11+ (dataclasses e decimal). Nenhuma instalação externa é necessária:

# Ambiente padrão Python 3.11+ (sem dependências externas)

💻 Código Completo e Autocontido (emissoes_frete_regras.py)

Crie o arquivo emissoes_frete_regras.py e insira o código abaixo integralmente:

"""
Módulo: emissoes_frete_regras.py
Domínio: Aplicação das relações de caso de uso (Include, Extend e Exceções).
Stack: Python 3.11+
"""
from dataclasses import dataclass
from decimal import Decimal

class TokenInvalidoException(Exception):
    pass

@dataclass
class SolicitacaoFrete:
    token_jwt: str
    peso_kg: Decimal
    entrega_domingo: bool

# Caso de Uso Incluído («include» obrigatório)
def validar_token_jwt(token: str):
    if not token or token != "TOKEN_SEGURO_2026":
        raise TokenInvalidoException("❌ Acesso negado: Token JWT ausente ou inválido.")

# Caso de Uso Extensor («extend» condicional)
def aplicar_seguro_domingo(valor_base: Decimal) -> Decimal:
    print(" 🛡️ [«extend»] Ponto de Extensão Ativado: Adicionando Seguro Especial de Domingo (+15%)")
    return valor_base * Decimal("1.15")

# Caso de Uso Base
def emitir_frete(solicitacao: SolicitacaoFrete) -> Decimal:
    # 1. Executa o «include» obrigatório
    validar_token_jwt(solicitacao.token_jwt)

    # 2. Fluxo Principal
    valor_base = solicitacao.peso_kg * Decimal("12.00")

    # 3. Ponto de Extensão («extend»)
    if solicitacao.entrega_domingo:
        valor_base = aplicar_seguro_domingo(valor_base)

    return valor_base

if __name__ == "__main__":
    try:
        sol = SolicitacaoFrete("TOKEN_SEGURO_2026", Decimal("5.0"), entrega_domingo=True)
        valor_final = emitir_frete(sol)
        print(f"✅ Frete emitido com sucesso: R$ {valor_final:.2f}")
    except TokenInvalidoException as erro:
        print(erro)

🚀 Como Executar

Execute o script no terminal:

python emissoes_frete_regras.py

🖥️ Saída Esperada no Terminal

 🛡️ [«extend»] Ponto de Extensão Ativado: Adicionando Seguro Especial de Domingo (+15%)
✅ Frete emitido com sucesso: R$ 69.00

💡 5. Checkpoint de Engenharia & Boas Práticas

Boas Práticas & Anti-Patterns

  • Atenção à Direção da Seta no «extend»: No relacionamento «extend», a seta pontilhada aponta do caso de uso extensor PARA o caso de uso base (pois o caso base não tem conhecimento prévio da extensão).
  • Anti-Pattern Uso Excessivo de Include/Extend: Evite fragmentar o diagrama em dezenas de pequenos casos de uso para passos triviais (ex: "Digitar CPF"). Use «include» apenas para lógicas reaproveitadas em múltiplos casos de uso.

🔗 6. Conexão com os Projetos Integradores

Projeto IntegradorComo o conceito deste capítulo é aplicado no PI
PI-04: ShopFlowO caso de uso Finalizar Compra inclui obrigatoriamente Reservar Estoque e pode estender Aplicar Cupom de Desconto.
PI-06: ServiceFlowEncerrar OS inclui Calcular Peças e Mão de Obra e estende Notificar Cliente por WhatsApp.

🧪 7. Quiz de Fixação e Autoavaliação (Formative Assessment)

🧪 Quiz de Autoavaliação — Capítulo 11

1. Qual é a principal diferença entre os relacionamentos «include» e «extend» na UML?

  • A) O «include» é opcional e o «extend» é obrigatório.
  • B) O «include» representa uma execução obrigatória e incondicional do comportamento incluído; o «extend» representa uma execução opcional que ocorre apenas sob determinadas condições.
  • C) Ambos possuem exatamente a mesma semântica e podem ser usados de forma intercambiável.
  • D) O «extend» só pode ser usado entre atores humanos.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: O «include» é mandatório para a conclusão do caso base (fração essencial de lógica reaproveitada), enquanto o «extend» só é acionado se a condição do ponto de extensão for verdadeira.


2. No diagrama de casos de uso da UML, para onde aponta a seta pontilhada do relacionamento «extend»?

  • A) Do Caso de Uso Base para o Ator.
  • B) Do Caso de Uso Extensor PARA o Caso de Uso Base.
  • C) Do Caso de Uso Base para o Caso de Uso Extensor.
  • D) De um Ator para outro Ator.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: A convenção estrita da UML define que o caso de uso que estende (extensor) aponta para o caso de uso que está sendo estendido (base), pois o caso base independe da extensão.


3. O que é um 'Fluxo de Exceção' na especificação textual de um caso de uso?

  • A) O caminho ideal quando tudo dá certo na primeira tentativa.
  • B) O conjunto de passos executados quando ocorre um erro, validação inválida ou falha que impede a conclusão normal da meta (ex: cartão sem limite).
  • C) A lista de linguagens de programação usadas no backend.
  • D) O orçamento financeiro da sprint.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: Fluxos de exceção tratam condições de erro, falhas e cancelamentos, garantindo que o sistema responda de forma graciosa e segura.


🛠️ 8. Ponte para a Ação: Laboratório Prático

🎯 Próximo Passo Prático

Coloque esta teoria em prática executando o roteiro de laboratório autoguiado:
👉 ATIVIDADE 05: CASOS DE USO (UML)


📌 9. Resumo Executivo & Key Takeaways

  • «include»: Comportamento compartilhado e obrigatório para o término do caso base.
  • «extend»: Comportamento modular e condicional acionado em pontos de extensão pré-definidos.
  • Direção das Setas: Base ➔ «include» ➔ Compartilhado | Extensor ➔ «extend» ➔ Base.
  • Especificação Textual: Caminho Feliz (Principal), Alternativos e Exceções devem ser detalhados para guiar o time de testes e desenvolvimento.

🏛️ CAPÍTULO 12: DIAGRAMA DE CLASSES (CONCEITOS)


🎯 1. Objetivos de Aprendizagem & Competências

Estimativa de Dedicação: 2 horas de estudo autoguiado.
Ao final deste capítulo, você será capaz de:

  • 🔹 Compreender a estrutura do Diagrama de Classes da UML como pilar fundamental da modelagem estática e da Programação Orientada a Objetos (POO).
  • 🔹 Dominar a representação dos três compartimentos canônicos: Nome da Classe, Atributos Tipados e Métodos com Assinaturas de Retorno.
  • 🔹 Aplicar os modificadores de visibilidade da UML: Público (+), Privado (-), Protegido (#) e Pacote (~).
  • 🔹 Mapear classes UML para código Python 3.11+ utilizando propriedades protegidas (@property) e modelos do SQLAlchemy 2.0.

🏢 2. Cenário Corporativo & Estudo de Caso (TecProExpress)

Na TecProExpress, a equipe financeira identificou um bug crítico: funções espalhadas pelo sistema alteravam diretamente a variável saldo de motoristas parceiros sem registrar o log de auditoria e sem validar se o valor ficaria negativo.

O Desafio: Como Arquiteto de Software, você deve redesenhar o domínio aplicando o princípio do Encapsulamento. A classe CarteiraDigital deve manter o saldo estritamente privado (-), expondo apenas métodos públicos de depósito e saque com validação defensiva de integridade.


🧠 3. Fundamentação Teórica & Modelos Visuais

3.1. Anatomia da Classe na UML 2.5

Uma classe é representada na UML por um retângulo com três compartimentos horizontais:

classDiagram
    class CarteiraDigital {
        -String idCarteira
        -Decimal saldoPrivado
        #String idTitular
        +depositar(Decimal valor) bool
        +sacar(Decimal valor) bool
        +consultarSaldo() Decimal
    }

3.2. Modificadores de Visibilidade e Encapsulamento

Símbolo UMLVisibilidadeSemântica de EngenhariaEquivalente em Python 3.11+
+Público (Public)Acessível por qualquer classe ou módulo da aplicação.def consultar_saldo(self):
-Privado (Private)Acessível única e exclusivamente por métodos da própria classe.self.__saldo = saldo (Name Mangling)
#Protegido (Protected)Acessível pela própria classe e por suas subclasses derivadas.self._titular = titular (Convenção)
~Pacote (Package)Acessível por classes pertencentes ao mesmo pacote/módulo.Módulos internos (_internal.py)

💻 4. Aplicação Prática & Código Executável (Python 3.11+)

📋 Pré-requisitos e Instalação

Este exemplo utiliza exclusivamente módulos da biblioteca padrão do Python (decimal), não necessitando de bibliotecas de terceiros:

python --version  # Requer Python 3.11 ou superior

💻 Código Completo e Autocontido (carteira_digital.py)

Transposição da classe modelada na UML para código Python com encapsulamento defensivo e validações de invariantes de negócio:

"""
Módulo: carteira_digital.py
Domínio: Encapsulamento e proteção de saldo financeiro.
"""
from decimal import Decimal

class CarteiraDigital:
    def __init__(self, id_carteira: str, titular: str, saldo_inicial: Decimal = Decimal("0.00")):
        self.id_carteira: str = id_carteira          # + Público
        self._titular: str = titular                 # # Protegido
        self.__saldo: Decimal = saldo_inicial        # - Privado

    def depositar(self, valor: Decimal) -> bool:
        """+ Método Público para entrada de recursos com validação."""
        if valor <= Decimal("0.00"):
            raise ValueError("O valor de depósito deve ser estritamente positivo.")
        self.__saldo += valor
        return True

    def sacar(self, valor: Decimal) -> bool:
        """+ Método Público com trava de saldo insuficiente."""
        if valor <= Decimal("0.00"):
            raise ValueError("Valor de saque inválido.")
        if valor > self.__saldo:
            raise PermissionError("❌ Saldo insuficiente na carteira.")
        self.__saldo -= valor
        return True

    @property
    def saldo(self) -> Decimal:
        """+ Getter público controlado."""
        return self.__saldo

if __name__ == "__main__":
    print("--- Teste de Encapsulamento da Carteira Digital ---")
    carteira = CarteiraDigital("CRT-2026-99", "Marcos Silva", Decimal("500.00"))
    carteira.depositar(Decimal("250.00"))
    carteira.sacar(Decimal("100.00"))
    print(f"Titular: {carteira._titular}")
    print(f"✅ Saldo da Carteira {carteira.id_carteira}: R$ {carteira.saldo:.2f}")

    # Teste de violação de regra de negócio
    try:
        carteira.sacar(Decimal("1000.00"))
    except PermissionError as e:
        print(f"Validação defensiva disparada: {e}")

🚀 Como Executar

Execute o script diretamente no terminal:

python carteira_digital.py

🖥️ Saída Esperada no Terminal

--- Teste de Encapsulamento da Carteira Digital ---
Titular: Marcos Silva
✅ Saldo da Carteira CRT-2026-99: R$ 650.00
Validação defensiva disparada: ❌ Saldo insuficiente na carteira.

💡 5. Checkpoint de Engenharia & Boas Práticas

Boas Práticas & Anti-Patterns

  • Encapsulamento é Segurança: Nunca deixe atributos críticos (preço, saldo, estoque) acessíveis diretamente de fora da classe. Use métodos de negócio (adicionar_item(), processar_baixa()) para garantir que as regras e invariantes sejam sempre validadas.
  • Anti-Pattern Anemic Domain Model: Evite criar classes que são apenas "sacos de getters e setters" sem qualquer lógica de negócio, espalhando cálculos e regras em funções utilitárias desordenadas.

🔗 6. Conexão com os Projetos Integradores

Projeto IntegradorComo o conceito deste capítulo é aplicado no PI
PI-03: PDVLiteClasse Venda com encapsulamento do cálculo do total de itens e aplicação de desconto fiscal.
PI-09: FinLiteClasse ContaBancaria protegendo o saldo contábil contra baixas não autorizadas.

🧪 7. Quiz de Fixação e Autoavaliação (Formative Assessment)

🧪 Quiz de Autoavaliação — Capítulo 12

1. No diagrama de classes da UML, qual símbolo gráfico representa que um atributo possui visibilidade 'Privada' (Private)?

  • A) + (Mais)
  • B) - (Menos)
  • C) # (Cerquilha/Hash)
  • D) ~ (Til)
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: O símbolo - representa visibilidade Privada (Private), garantindo que apenas métodos internos da própria classe possam acessar e modificar o atributo.


2. O que caracteriza o princípio da Orientação a Objetos conhecido como 'Encapsulamento'?

  • A) Permitir que qualquer script altere variáveis globais sem restrições.
  • B) Ocultar os detalhes internos de implementação e proteger o estado do objeto, permitindo acesso apenas através de uma interface pública validada.
  • C) Transformar todo o banco de dados em arquivos CSV.
  • D) Executar múltiplos programas na mesma máquina virtual.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: O encapsulamento blinda o estado interno do objeto contra manipulações externas indevidas, mantendo a integridade e coesão das regras de negócio.


3. Na representação de um método na UML + calcularTotal(desconto: Decimal): Decimal, o tipo após os dois pontos no final indica:

  • A) O nome da tabela do banco de dados.
  • B) O tipo de dado do primeiro parâmetro.
  • C) O tipo de dado do valor de retorno do método.
  • D) A versão do compilador.
💡 Ver Resposta e Justificativa

Resposta Correta: C
Justificativa: Na sintaxe da UML, o tipo após os dois pontos da assinatura do método especifica o tipo de retorno retornado pela função.


🛠️ 8. Ponte para a Ação: Laboratório Prático

🎯 Próximo Passo Prático

Coloque esta teoria em prática executando o roteiro de laboratório autoguiado:
👉 ATIVIDADE 07: DIAGRAMA DE CLASSES (UML)


📌 9. Resumo Executivo & Key Takeaways

  • Planta Estática: O Diagrama de Classes define a estrutura de dados e as operações fundamentais do sistema.
  • 3 Compartimentos: Nome da Classe, Atributos Tipados e Métodos com assinaturas.
  • Encapsulamento Estrito: Atributos devem ser privados (-) ou protegidos (#); métodos de negócio compõem a interface pública (+).
  • Mapeamento ORM: Classes UML conectam-se diretamente às tabelas relacionais do SQLAlchemy e schemas do Pydantic.

🧬 CAPÍTULO 13: HERANÇA, POLIMORFISMO E ASSOCIAÇÕES


🎯 1. Objetivos de Aprendizagem & Competências

Estimativa de Dedicação: 2 horas de estudo autoguiado.
Ao final deste capítulo, você será capaz de:

  • 🔹 Diferenciar e aplicar os 4 relacionamentos essenciais da UML: Associação Simples, Agregação (losango vazio), Composição (losango preenchido) e Generalização/Herança (triângulo vazio).
  • 🔹 Distinguir Agregação (relação fraca 'tem-um', ciclo de vida independente) de Composição (relação forte 'parte-todo', ciclo de vida dependente e exclusão em cascata).
  • 🔹 Aplicar Polimorfismo e Classes Abstratas (abc.ABC) para garantir desacoplamento e extensibilidade.
  • 🔹 Implementar relacionamentos no SQLAlchemy 2.0 com relationship() e cascade="all, delete-orphan".

🏢 2. Cenário Corporativo & Estudo de Caso (TecProExpress)

Na TecProExpress, a plataforma de e-commerce e faturamento processa pedidos compostos por múltiplos itens. Se um Pedido for cancelado ou excluído do banco de dados, todos os seus ItensPedido associados devem ser automaticamente eliminados (Composição). No entanto, o Produto do catálogo continua existindo para outras vendas (Agregação).

O Desafio: Como Arquiteto de Software, você deve modelar as relações entre as classes com as cardinalidades corretas, garantindo que o ciclo de vida dos objetos reflita as regras de negócio e a integridade referencial do banco de dados.


🧠 3. Fundamentação Teórica & Modelos Visuais

3.1. Panorama dos Relacionamentos entre Classes na UML

classDiagram
    class Pedido {
        +int idPedido
        +date dataEmissao
        +calcularTotal() Decimal
    }
    class ItemPedido {
        +int quantidade
        +Decimal precoUnitario
        +subtotal() Decimal
    }
    class Produto {
        +int idProduto
        +string nome
        +Decimal preco
    }
    class Cliente {
        +int idCliente
        +string nome
    }
    
    Cliente "1" --> "0..*" Pedido : Associação
    Pedido "1" *-- "1..*" ItemPedido : Composição (Todo-Parte Forte)
    ItemPedido "0..*" o-- "1" Produto : Agregação (Parte Fraca)

3.2. Comparativo: Agregação vs. Composição vs. Herança

RelacionamentoSímbolo UMLCiclo de VidaExemplo Prático
AssociaçãoSeta Simples -->Independente; uma classe utiliza métodos ou dados de outra.Motorista conduz Caminhão.
AgregaçãoLosango Vazio o--Fraco ('tem-um'); a parte pode existir sem o todo.Departamento agrega Professores.
ComposiçãoLosango Cheio *--Forte ('parte-todo'); a parte morre junto com o todo.NotaFiscal compõe ItensNotaFiscal.
Herança / GeneralizaçãoTriângulo Vazio `<--`Compartilhamento de atributos e métodos da superclasse ('é-um').

💻 4. Aplicação Prática & Código Executável (Python 3.11+)

📋 Pré-requisitos e Instalação

Este exemplo utiliza os módulos padrão abc, decimal e dataclasses, sem necessidade de pacotes externos:

python --version  # Requer Python 3.11 ou superior

💻 Código Completo e Autocontido (modelo_pedido_composicao.py)

Implementação de Composição Forte e Polimorfismo de Estratégia de Desconto em Python 3.11+:

"""
Módulo: modelo_pedido_composicao.py
Domínio: Composição forte e polimorfismo de regras de frete e desconto.
"""
from abc import ABC, abstractmethod
from decimal import Decimal
from dataclasses import dataclass

# --- POLIMORFISMO DE ESTRATÉGIA DE DESCONTO ---
class RegraDesconto(ABC):
    @abstractmethod
    def calcular_desconto(self, valor_bruto: Decimal) -> Decimal:
        pass

class DescontoClientePadrao(RegraDesconto):
    def calcular_desconto(self, valor_bruto: Decimal) -> Decimal:
        return Decimal("0.00")

class DescontoClienteVIP(RegraDesconto):
    def calcular_desconto(self, valor_bruto: Decimal) -> Decimal:
        return valor_bruto * Decimal("0.10") # 10% OFF

# --- COMPOSIÇÃO: Pedido e ItemPedido ---
@dataclass
class ItemPedido:
    descricao: str
    quantidade: int
    preco_unitario: Decimal

    def subtotal(self) -> Decimal:
        return self.quantidade * self.preco_unitario

class Pedido:
    def __init__(self, id_pedido: int, estrategia_desconto: RegraDesconto):
        self.id_pedido = id_pedido
        self.estrategia_desconto = estrategia_desconto
        self.itens: list[ItemPedido] = [] # Composição: itens pertencem ao ciclo deste pedido

    def adicionar_item(self, descricao: str, quantidade: int, preco: Decimal):
        self.itens.append(ItemPedido(descricao, quantidade, preco))

    def calcular_total_bruto(self) -> Decimal:
        return sum((item.subtotal() for item in self.itens), Decimal("0.00"))

    def calcular_total_liquido(self) -> Decimal:
        bruto = self.calcular_total_bruto()
        desconto = self.estrategia_desconto.calcular_desconto(bruto)
        return bruto - desconto

if __name__ == "__main__":
    print("--- Simulação de Pedidos com Composição e Polimorfismo ---")
    
    # Pedido 1: Cliente Padrão (Sem Desconto)
    pedido_padrao = Pedido(1001, DescontoClientePadrao())
    pedido_padrao.adicionar_item("Teclado Mecânico", 1, Decimal("350.00"))
    print(f"📦 Pedido #{pedido_padrao.id_pedido} (Padrão):")
    print(f"   Bruto: R$ {pedido_padrao.calcular_total_bruto():.2f} | Líquido: R$ {pedido_padrao.calcular_total_liquido():.2f}")

    # Pedido 2: Cliente VIP (10% OFF)
    pedido_vip = Pedido(1002, DescontoClienteVIP())
    pedido_vip.adicionar_item("Monitor Dell 27", 2, Decimal("1200.00"))
    pedido_vip.adicionar_item("Teclado Mecânico", 1, Decimal("350.00"))
    print(f"🌟 Pedido #{pedido_vip.id_pedido} (VIP 10% OFF):")
    print(f"   Bruto: R$ {pedido_vip.calcular_total_bruto():.2f} | Líquido: R$ {pedido_vip.calcular_total_liquido():.2f}")

🚀 Como Executar

Execute o script diretamente no terminal:

python modelo_pedido_composicao.py

🖥️ Saída Esperada no Terminal

--- Simulação de Pedidos com Composição e Polimorfismo ---
📦 Pedido #1001 (Padrão):
   Bruto: R$ 350.00 | Líquido: R$ 350.00
🌟 Pedido #1002 (VIP 10% OFF):
   Bruto: R$ 2750.00 | Líquido: R$ 2475.00

💡 5. Checkpoint de Engenharia & Boas Práticas

Boas Práticas & Anti-Patterns

  • Prefira Composição à Herança (Gang of Four): A herança cria acoplamento rígido entre superclasse e subclasse. Sempre que possível, monte comportamentos combinando objetos menores através de composição e interfaces polimórficas.
  • Anti-Pattern Cascading Delete Acidental: Em relacionamentos de Agregação, nunca marque cascade="all, delete-orphan", pois isso apagará entidades compartilhadas acidentalmente.

🔗 6. Conexão com os Projetos Integradores

Projeto IntegradorComo o conceito deste capítulo é aplicado no PI
PI-04: ShopFlowComposição PedidoItemPedido com exclusão em cascata no SQLAlchemy ORM.
PI-08: CattleFlowAgregação entre PastoPiquete e AnimalBovino (o animal é rotacionado de pasto sem ser excluído).

🧪 7. Quiz de Fixação e Autoavaliação (Formative Assessment)

🧪 Quiz de Autoavaliação — Capítulo 13

1. Qual é a principal diferença estrutural entre 'Agregação' e 'Composição' na UML?

  • A) Agregação usa losango preto e Composição usa triângulo.
  • B) Na Composição, a parte possui dependência existencial do todo (se o todo for destruído, as partes deixam de existir); na Agregação, as partes podem existir de forma independente do todo.
  • C) Na Agregação, a parte não pode pertencer a nenhuma outra classe.
  • D) Não há diferença prática; são termos intercambiáveis na UML 2.5.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: A Composição representa um vínculo 'todo-parte' forte com ciclo de vida coincidente (ex: Nota Fiscal e seus Itens). A Agregação é um vínculo fraco onde as partes sobrevivem à destruição do objeto agregador (ex: Time e Jogadores).


2. No diagrama de classes da UML, o símbolo gráfico do relacionamento de 'Composição' é:

  • A) Uma linha pontilhada com seta aberta.
  • B) Um losango preenchido (losango preto/sólido) no lado do objeto 'Todo'.
  • C) Um triângulo vazio no lado da superclasse.
  • D) Um círculo com uma cruz dentro.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: O losango sólido (preenchido) indica Composição forte no lado da classe que atua como proprietária do ciclo de vida das partes.


3. O princípio da POO 'Polimorfismo' permite:

  • A) Gravar dados em arquivos texto sem formatação.
  • B) Tratar objetos de diferentes classes derivadas através de uma mesma interface comum, executando comportamentos específicos em tempo de execução.
  • C) Impedir o uso de funções assíncronas.
  • D) Criar tabelas sem chaves primárias.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: O polimorfismo possibilita invocar o mesmo método em objetos de diferentes tipos, com cada objeto respondendo de acordo com sua própria implementação especializada.


🛠️ 8. Ponte para a Ação: Laboratório Prático

🎯 Próximo Passo Prático

Coloque esta teoria em prática executando o roteiro de laboratório autoguiado:
👉 ATIVIDADE 07: DIAGRAMA DE CLASSES (UML)


📌 9. Resumo Executivo & Key Takeaways

  • Associação: Relação genérica de conhecimento entre duas classes.
  • Agregação (Losango Vazio): Vínculo fraco; as partes continuam existindo de forma independente.
  • Composição (Losango Cheio): Vínculo forte de posse existencial; exclusão em cascata obrigatória.
  • Polimorfismo: Desacoplamento através de classes base abstratas (abc.ABC), permitindo trocar algoritmos sem quebrar os clientes.

🔀 CAPÍTULO 14: DIAGRAMA DE SEQUÊNCIA E APIS REST


🎯 1. Objetivos de Aprendizagem & Competências

Estimativa de Dedicação: 2 horas de estudo autoguiado.
Ao final deste capítulo, você será capaz de:

  • 🔹 Compreender a representação temporal de troca de mensagens entre objetos no Diagrama de Sequência da UML 2.5.
  • 🔹 Dominar a sintaxe visual de Linhas de Vida (Lifelines), Barras de Ativação (Execution Occurrences), Mensagens Síncronas (->>), Assíncronas (-)) e Retornos (-->>).
  • 🔹 Modelar fluxos completos de requisições HTTP REST (Request-Response Cycle) conectando Cliente, Gateway, Serviços e Banco de Dados.
  • 🔹 Implementar rotas Flask integradas com serviços de domínio espelhando rigorosamente a ordem do diagrama de sequência.

🏢 2. Cenário Corporativo & Estudo de Caso (TecProExpress)

Na TecProExpress, a equipe de engenharia identificou uma latência excessiva durante o processo de Checkout e Emissão de Etiqueta. O time de frontend culpava o banco de dados, o DBA culpava a rede e a equipe backend culpava a API externa de CEP.

O Desafio: Como Arquiteto de Software, você deve desenhar o Diagrama de Sequência da UML para rastrear a linha do tempo exata das chamadas de rede e processamento síncrono/assíncrono, identificando gargalos e estabelecendo contratos REST claros e determinísticos em Flask.


🧠 3. Fundamentação Teórica & Modelos Visuais

3.1. Anatomia do Diagrama de Sequência da UML

O tempo flui verticalmente de cima para baixo. Cada participante possui uma linha de vida vertical com barras que indicam períodos em que o objeto está executando operações ativas:

sequenceDiagram
    autonumber
    actor C as 📱 App do Motorista
    participant API as 🌐 Flask (Gateway)
    participant SVC as 🧠 FreteService
    participant DB as 🛢️ PostgreSQL
    
    C->>+API: POST /api/fretes/calcular (distancia_km, peso_kg, expresso)
    API->>+SVC: processar_calculo(distancia, peso, expresso)
    SVC->>+DB: SELECT taxa_km FROM tabelas_frete...
    DB-->>-SVC: taxa_km = 2.50
    SVC-->>-API: { valor_total: 316.25, prazo_horas: 24 }
    API-->>-C: 200 OK { valor_total: 316.25, prazo_horas: 24 }

3.2. Notação de Mensagens na UML 2.5

Notação MermaidTipo de Mensagem UMLSignificado de Engenharia
->> (Seta sólida preenchida)Mensagem SíncronaO chamador bloqueia a execução aguardando o retorno da resposta.
-) (Seta fina aberta)Mensagem AssíncronaO chamador envia o evento e prossegue sem esperar (ex: disparo de fila RabbitMQ/Kafka).
-->> (Seta pontilhada)Mensagem de RetornoRetorno de dados ou confirmação de conclusão da operação.
par / and / endFragmento ParaleloExecução concorrente de múltiplos fluxos simultâneos.
alt / else / endFragmento CondicionalEstrutura de desvio if / else na sequência.

💻 4. Aplicação Prática & Código Executável (Python 3.11+)

📋 Pré-requisitos e Instalação

Instale o micro-framework Flask no seu ambiente virtual:

pip install flask

💻 Código Completo e Autocontido (sequence_api_frete.py)

Implementação do fluxo síncrono de cálculo de frete com Flask 3.x, refletindo a interação modelada no Diagrama de Sequência:

"""
Módulo: sequence_api_frete.py
Domínio: Endpoint REST Flask refletindo a sequência síncrona de cálculo de frete.
"""
from decimal import Decimal
import sys
from flask import Flask, request, jsonify

app = Flask(__name__)

# 1. Camada de Serviço (Service Layer)
class FreteService:
    @staticmethod
    def processar_calculo(distancia_km: Decimal, peso_kg: Decimal, expresso: bool = False) -> dict:
        taxa_base_km = Decimal("2.50")
        valor = (distancia_km * taxa_base_km) + (peso_kg * Decimal("3.00"))
        prazo = 24
        
        if expresso:
            valor *= Decimal("1.25")
            prazo = 8
            
        return {
            "valor_total": float(valor.quantize(Decimal("0.01"))),
            "prazo_horas": prazo,
            "status": "CALCULADO_COM_SUCESSO"
        }

# 2. Rota REST (Controller / Gateway)
@app.post("/api/fretes/calcular")
def endpoint_calcular_frete():
    """Reflete o Passo 1 do Diagrama de Sequência: POST /api/fretes/calcular."""
    dados = request.get_json()
    if not dados:
        return jsonify({"erro": "Payload JSON ausente ou inválido"}), 400

    try:
        distancia = Decimal(str(dados.get("distancia_km", 0)))
        peso = Decimal(str(dados.get("peso_kg", 0)))
        expresso = bool(dados.get("expresso", False))

        if distancia <= Decimal("0.00") or peso <= Decimal("0.00"):
            return jsonify({"erro": "distancia_km e peso_kg devem ser maiores que zero"}), 400

        resultado = FreteService.processar_calculo(distancia, peso, expresso)
        return jsonify(resultado), 200

    except Exception as ex:
        return jsonify({"erro": f"Falha no processamento: {str(ex)}"}), 400

if __name__ == "__main__":
    if "--server" in sys.argv:
        print("🚀 Iniciando servidor Flask em http://127.0.0.1:5000...")
        app.run(port=5000, debug=True)
    else:
        print("--- Simulação de Requisições HTTP com Flask test_client() ---")
        with app.test_client() as client:
            # Cenário 1: Frete Padrão (120.5 km, 5 kg) -> (120.5*2.50) + (5*3.00) = 301.25 + 15 = 316.25
            res1 = client.post("/api/fretes/calcular", json={
                "distancia_km": 120.5,
                "peso_kg": 5.0,
                "expresso": False
            })
            print(f"Cenário 1 (Frete Padrão) [Status {res1.status_code}]: {res1.get_json()}")

            # Cenário 2: Frete Expresso (120.5 km, 5 kg, expresso) -> 316.25 * 1.25 = 395.31
            res2 = client.post("/api/fretes/calcular", json={
                "distancia_km": 120.5,
                "peso_kg": 5.0,
                "expresso": True
            })
            print(f"Cenário 2 (Frete Expresso) [Status {res2.status_code}]: {res2.get_json()}")

            # Cenário 3: Validação de Payload com Erro
            res3 = client.post("/api/fretes/calcular", json={
                "distancia_km": -10.0,
                "peso_kg": 5.0
            })
            print(f"Cenário 3 (Validação Defensiva) [Status {res3.status_code}]: {res3.get_json()}")

🚀 Como Executar

Execute o script de teste automatizado diretamente no terminal:

python sequence_api_frete.py

Para iniciar o servidor HTTP e testar com ferramentas como Postman, cURL ou VS Code REST Client:

python sequence_api_frete.py --server

🖥️ Saída Esperada no Terminal

--- Simulação de Requisições HTTP com Flask test_client() ---
Cenário 1 (Frete Padrão) [Status 200]: {'prazo_horas': 24, 'status': 'CALCULADO_COM_SUCESSO', 'valor_total': 316.25}
Cenário 2 (Frete Expresso) [Status 200]: {'prazo_horas': 8, 'status': 'CALCULADO_COM_SUCESSO', 'valor_total': 395.31}
Cenário 3 (Validação Defensiva) [Status 400]: {'erro': 'distancia_km e peso_kg devem ser maiores que zero'}

💡 5. Checkpoint de Engenharia & Boas Práticas

Boas Práticas & Anti-Patterns

  • Contratos REST Claros e Validados: Toda API REST deve validar rigorosamente os payloads de entrada e retornar códigos HTTP semânticos (200 para sucesso, 400 para erro de validação do cliente, 500 para falha interna), com payloads JSON estruturados e previsíveis.
  • Anti-Pattern Excesso de Chamadas Síncronas (Chatty API): Evite criar sequências onde a API faz múltiplas consultas síncronas sequenciais desnecessárias. Agrupe chamadas ou adote processamento assíncrono para operações de longa duração.

🔗 6. Conexão com os Projetos Integradores

Projeto IntegradorComo o conceito deste capítulo é aplicado no PI
PI-05: ParkFlowDiagrama de Sequência do checkout do estacionamento (leitura de ticket ➔ tarifação ➔ baixa).
PI-10: ImobiFlowSequência de geração de fatura de aluguel e cálculo do repasse financeiro líquido ao proprietário.

🧪 7. Quiz de Fixação e Autoavaliação (Formative Assessment)

🧪 Quiz de Autoavaliação — Capítulo 14

1. No Diagrama de Sequência da UML, o que representa a 'Linha de Vida' (Lifeline) de um objeto?

  • A) O número de bytes que o objeto ocupa na memória RAM.
  • B) Uma linha vertical pontilhada que representa a existência e a passagem do tempo daquele participante durante a interação.
  • C) O cabo de rede que conecta os servidores.
  • D) A chave estrangeira que conecta duas tabelas no banco de dados.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: A linha de vida estende-se verticalmente para baixo a partir do retângulo do participante, representando a passagem cronológica do tempo na interação.


2. Qual é a diferença fundamental entre uma 'Mensagem Síncrona' e uma 'Mensagem Assíncrona' no diagrama de sequência?

  • A) A síncrona usa JavaScript e a assíncrona usa Python.
  • B) Na síncrona, o remetente pausa e aguarda a conclusão da resposta antes de continuar; na assíncrona, o remetente envia a mensagem e continua seu processamento imediatamente sem bloquear.
  • C) A mensagem assíncrona não pode conter parâmetros.
  • D) Não há diferença; a UML 2.5 unificou as duas mensagens.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: Mensagens síncronas bloqueiam o fluxo do chamador até a resposta (ex: chamada de função comum), enquanto mensagens assíncronas disparam eventos em segundo plano (ex: postagem em fila).


3. O que define o fragmento de interação combinado 'alt' (Alternative) no diagrama de sequência?

  • A) Um loop que repete a operação infinitamente.
  • B) Uma bifurcação condicional com guardas booleanas (equivalente a uma estrutura if / else).
  • C) Uma falha irrecuperável do sistema operacional.
  • D) A exclusão física de um registro no banco de dados.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: O operador 'alt' modela caminhos mutuamente exclusivos baseados em condições de guarda (ex: [Saldo Suficiente] vs [Saldo Insuficiente]).


🛠️ 8. Ponte para a Ação: Laboratório Prático

🎯 Próximo Passo Prático

Coloque esta teoria em prática executando o roteiro de laboratório autoguiado:
👉 ATIVIDADE 08: DIAGRAMA DE SEQUÊNCIA (UML)


📌 9. Resumo Executivo & Key Takeaways

  • Visão Temporal: O Diagrama de Sequência é o modelo comportamental mais utilizado para mapear a ordem cronológica de APIs REST e microsserviços.
  • Linhas de Vida & Barras de Ativação: Indicam os participantes e os momentos exatos de processamento ativo.
  • Mensagens Síncronas vs Assíncronas: Diferenciam chamadas diretas de eventos desacoplados em filas e mensageria.
  • Endpoints em Flask: Implementam de forma limpa e síncrona as transições modeladas no diagrama de sequência, facilitando testes com test_client().

🔄 CAPÍTULO 15: DIAGRAMAS DINÂMICOS (ESTADOS E ATIVIDADES)


🎯 1. Objetivos de Aprendizagem & Competências

Estimativa de Dedicação: 2 horas de estudo autoguiado.
Ao final deste capítulo, você será capaz de:

  • 🔹 Modelar o ciclo de vida completo de entidades reativas utilizando o Diagrama de Máquina de Estados da UML (David Harel).
  • 🔹 Dominar a sintaxe formal de transições: Evento [Condição de Guarda] / Ação Executada.
  • 🔹 Construir Diagramas de Atividades com particionamento em Raias (Swimlanes), bifurcações de decisão e nós de sincronização paralela (Fork e Join).
  • 🔹 Implementar o padrão comportamental State Pattern e máquinas de estados finitos (FSM) em Python 3.11+.

🏢 2. Cenário Corporativo & Estudo de Caso (TecProExpress)

Na TecProExpress, os motoristas estavam conseguindo alterar o status de um pacote diretamente de "Aguardando Coleta" para "Entregue ao Destinatário", pulando a etapa obrigatória de "Em Rota de Entrega" e sem coletar a assinatura digital do cliente.

O Desafio: Como Engenheiro de Software, você deve modelar formalmente a Máquina de Estados Finita do pacote e o Diagrama de Atividades com Raias do processo de entrega, garantindo que o backend bloqueie qualquer transição de status ilegal ou desordenada.


🧠 3. Fundamentação Teórica & Modelos Visuais

3.1. Diagrama de Máquina de Estados da UML

Mapeia os estados discretos pelos quais um objeto passa durante sua existência em resposta a eventos:

stateDiagram-v2
    [*] --> CRIADO : Criação do Pacote
    CRIADO --> COLETADO : Motorista Coleta no Hub
    COLETADO --> EM_TRANSITO : Saída para Entrega [Veículo Alocado]
    EM_TRANSITO --> TENTATIVA_FALHA : Cliente Ausente / Notificar SAC
    TENTATIVA_FALHA --> EM_TRANSITO : Nova Tentativa [Tentativas < 3]
    TENTATIVA_FALHA --> DEVOLVIDO_HUB : Excedeu Limite [Tentativas >= 3]
    EM_TRANSITO --> ENTREGUE : Assinatura Coletada / Enviar Recibo
    ENTREGUE --> [*]
    DEVOLVIDO_HUB --> [*]

3.2. Diagrama de Atividades com Raias (Swimlanes) e Fork / Join

Organiza o fluxo de trabalho distribuindo as responsabilidades entre os diferentes departamentos/atores:

flowchart TD
    subgraph CLIENTE ["👤 Cliente"]
        A1["Fazer Pedido"] --> A2["Efetuar Pagamento"]
    end
    
    subgraph GATEWAY ["🏛️ Gateway Pagamento"]
        A2 --> B1{"Pagamento Aprovado?"}
    end
    
    subgraph LOGISTICA ["🚚 Centro Logístico"]
        B1 -- Sim --> FORK["Barra Fork (Paralelo)"]
        FORK --> C1["Separar Estoque"]
        FORK --> C2["Emitir Nota Fiscal"]
        C1 --> JOIN["Barra Join (Sincronização)"]
        C2 --> JOIN
        JOIN --> C3["Despachar Encomenda"]
    end
    
    B1 -- Não --> A3["Notificar Erro Pagamento"]
    
    style FORK fill:#fef3c7,stroke:#d97706
    style JOIN fill:#fef3c7,stroke:#d97706
    style C3 fill:#dcfce7,stroke:#16a34a

💻 4. Aplicação Prática & Código Executável (Python 3.11+)

📋 Pré-requisitos e Instalação

Este exemplo utiliza os módulos padrão enum do Python, sem necessidade de pacotes externos:

python --version  # Requer Python 3.11 ou superior

💻 Código Completo e Autocontido (maquina_estados_pacote.py)

Implementação de uma Máquina de Estados Finitos (FSM) com validação estrita de transições de ciclo de vida:

"""
Módulo: maquina_estados_pacote.py
Domínio: Validação de transições de ciclo de vida de encomendas com FSM.
"""
from enum import Enum, auto

class EstadoPacote(Enum):
    CRIADO = auto()
    COLETADO = auto()
    EM_TRANSITO = auto()
    ENTREGUE = auto()
    CANCELADO = auto()

class PacoteFSM:
    # Tabela de transições permitidas: EstadoAtual -> Lista de Próximos Estados Legais
    TRANSIÇÕES_VALIDAS = {
        EstadoPacote.CRIADO: {EstadoPacote.COLETADO, EstadoPacote.CANCELADO},
        EstadoPacote.COLETADO: {EstadoPacote.EM_TRANSITO, EstadoPacote.CANCELADO},
        EstadoPacote.EM_TRANSITO: {EstadoPacote.ENTREGUE, EstadoPacote.COLETADO},
        EstadoPacote.ENTREGUE: set(), # Estado Final
        EstadoPacote.CANCELADO: set()  # Estado Final
    }

    def __init__(self, codigo_rastreio: str):
        self.codigo_rastreio = codigo_rastreio
        self.estado_atual = EstadoPacote.CRIADO

    def transicionar_para(self, novo_estado: EstadoPacote):
        permitidos = self.TRANSIÇÕES_VALIDAS.get(self.estado_atual, set())
        if novo_estado not in permitidos:
            raise ValueError(
                f"❌ Transição Ilegal: Não é permitido mudar de {self.estado_atual.name} para {novo_estado.name}."
            )
        self.estado_atual = novo_estado
        print(f"📦 Pacote [{self.codigo_rastreio}] transicionou com sucesso para: {self.estado_atual.name}")

if __name__ == "__main__":
    print("--- Simulação da Máquina de Estados (FSM) de Logística ---")
    pacote = PacoteFSM("BR-2026-99")
    pacote.transicionar_para(EstadoPacote.COLETADO)
    pacote.transicionar_para(EstadoPacote.EM_TRANSITO)
    pacote.transicionar_para(EstadoPacote.ENTREGUE)
    
    # Tentativa ilegal de transição a partir de um estado final
    try:
        pacote.transicionar_para(EstadoPacote.CRIADO)
    except ValueError as err:
        print(err)

🚀 Como Executar

Execute o script diretamente no terminal:

python maquina_estados_pacote.py

🖥️ Saída Esperada no Terminal

--- Simulação da Máquina de Estados (FSM) de Logística ---
📦 Pacote [BR-2026-99] transicionou com sucesso para: COLETADO
📦 Pacote [BR-2026-99] transicionou com sucesso para: EM_TRANSITO
📦 Pacote [BR-2026-99] transicionou com sucesso para: ENTREGUE
❌ Transição Ilegal: Não é permitido mudar de ENTREGUE para CRIADO.

💡 5. Checkpoint de Engenharia & Boas Práticas

Boas Práticas & Anti-Patterns

  • Condições de Guarda Robustas: Em transições de estado, use guardas booleanas inequívocas (ex: [Saldo >= Valor]) para impedir que objetos entrem em estados inválidos em produção.
  • Anti-Pattern Deadlocks em Fork/Join: No Diagrama de Atividades, todo fluxo aberto por um nó de Fork (bifurcação paralela) deve convergir para um nó de Join (sincronização) compatível para evitar travamentos de processos concorrentes.

🔗 6. Conexão com os Projetos Integradores

Projeto IntegradorComo o conceito deste capítulo é aplicado no PI
PI-06: ServiceFlowMáquina de Estados da Ordem de Serviço (ABERTAEM_ANALISEAPROVADACONCLUIDA).
PI-07: AgroSafeDiagrama de Atividades do fluxo de receituário agronômico (Emissão ART ➔ Separação ➔ Aplicação).

🧪 7. Quiz de Fixação e Autoavaliação (Formative Assessment)

🧪 Quiz de Autoavaliação — Capítulo 15

1. No Diagrama de Transição de Estados da UML, o que representa a sintaxe Evento [Guarda] / Ação?

  • A) O nome do banco de dados, a tabela e o registro excluído.
  • B) O gatilho que dispara a transição (Evento), a condição booleana que deve ser verdadeira para permitir a passagem ([Guarda]) e a operação executada durante a mudança (/ Ação).
  • C) O caminho da URL da API REST.
  • D) Um comentário de código descartável.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: Essa é a notação formal da UML: a transição só ocorre se o Evento acontecer E a Condição de Guarda for satisfeita, disparando a Ação associada.


2. No Diagrama de Atividades da UML, qual é a função das 'Barras de Sincronização' (Fork e Join)?

  • A) Desenhar botões de login na tela.
  • B) O nó 'Fork' divide um fluxo sequencial em múltiplos fluxos executados em paralelo; o nó 'Join' sincroniza e aguarda a conclusão de todos os fluxos paralelos antes de prosseguir.
  • C) Excluir arquivos temporários do servidor.
  • D) Cancelar a execução do programa em caso de erro.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: Fork e Join modelam concorrência e paralelismo: Fork bifurca o fluxo em threads simultâneas e Join aguarda todas terminarem para unificar o fluxo.


3. O que são as 'Raias' (Swimlanes) em um Diagrama de Atividades?

  • A) Linhas de código escritas em HTML.
  • B) Divisões visuais (colunas ou faixas horizontais) que particionam as atividades indicando qual ator, departamento ou sistema é responsável por executá-las.
  • C) Tipos primitivos de variáveis float.
  • D) Erros de compilação da IDE.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: As raias organizam o fluxo de trabalho atribuindo cada atividade ao seu responsável direto (ex: Cliente, Financeiro, Logística), facilitando a auditoria de processos.


🛠️ 8. Ponte para a Ação: Laboratório Prático

🎯 Próximo Passo Prático

Coloque esta teoria em prática executando o roteiro de laboratório autoguiado:
👉 ATIVIDADE 11: DIAGRAMA DE ATIVIDADES (UML)


📌 9. Resumo Executivo & Key Takeaways

  • Diagrama de Estados: Foca nas mudanças de situação interna de uma única entidade ao longo do tempo.
  • Sintaxe de Transição: Evento [Guarda Booleana] / Ação Executada.
  • Diagrama de Atividades: Modela o fluxo de trabalho global, decisões lógicas e sincronização paralela (Fork/Join).
  • Raias (Swimlanes): Atribuem responsabilidades claras para cada etapa do processo corporativo.

🧪 CAPÍTULO 16: QUALIDADE DE SOFTWARE (SQA)


🎯 1. Objetivos de Aprendizagem & Competências

Estimativa de Dedicação: 2 horas de estudo autoguiado.
Ao final deste capítulo, você será capaz de:

  • 🔹 Compreender os fundamentos de Software Quality Assurance (SQA) e as 8 características de qualidade da norma ISO/IEC 25010.
  • 🔹 Avaliar a Complexidade Ciclomática de McCabe e o índice de manutenibilidade de bases de código.
  • 🔹 Executar ferramentas de análise estática de código (Linters, Flake8, Ruff e Radon) no ecossistema Python 3.11+.
  • 🔹 Instituir Quality Gates e métricas de governança em esteiras de integração contínua.

🏢 2. Cenário Corporativo & Estudo de Caso (TecProExpress)

Na TecProExpress, após 1 ano de entregas aceleradas, o sistema central tornou-se lento e qualquer nova alteração gerava bugs inesperados em outros módulos não relacionados. O tempo médio para corrigir uma falha simples saltou de 2 horas para 4 dias úteis.

O Desafio: Como Engenheiro de Qualidade de Software (SQA Lead), você deve auditar a base de código utilizando a norma ISO/IEC 25010 e ferramentas de análise estática, estabelecendo métricas de complexidade e bloqueando deploys de código com alto débito técnico.


🧠 3. Fundamentação Teórica & Modelos Visuais

3.1. As 8 Características de Qualidade da Norma ISO/IEC 25010

A norma internacional ISO/IEC 25010 estrutura a qualidade de produto de software em 8 dimensões complementares:

flowchart TD
    root["🌐 ISO/IEC 25010: Qualidade de Produto de Software"]
    
    root --> C1["1. Adequação Funcional<br>(Completeza, Correção)"]
    root --> C2["2. Eficiência de Performance<br>(Tempo, Recursos, Capacidade)"]
    root --> C3["3. Compatibilidade<br>(Coexistência, Interoperabilidade)"]
    root --> C4["4. Usabilidade<br>(Operabilidade, Acessibilidade)"]
    root --> C5["5. Confiabilidade<br>(Maturidade, Tolerância a Falhas)"]
    root --> C6["6. Segurança<br>(Confidencialidade, Integridade)"]
    root --> C7["7. Manutenibilidade<br>(Modularidade, Reusabilidade, Testabilidade)"]
    root --> C8["8. Portabilidade<br>(Adaptabilidade, Instalabilidade)"]
    
    style root fill:#eff6ff,stroke:#2563eb,stroke-width:2px
    style C1 fill:#f0fdf4,stroke:#16a34a
    style C2 fill:#f0fdf4,stroke:#16a34a
    style C3 fill:#fffbeb,stroke:#d97706
    style C4 fill:#fffbeb,stroke:#d97706
    style C5 fill:#fee2e2,stroke:#ef4444
    style C6 fill:#fee2e2,stroke:#ef4444
    style C7 fill:#ede7f6,stroke:#7c3aed
    style C8 fill:#ede7f6,stroke:#7c3aed

3.2. Complexidade Ciclomática de Thomas McCabe

Mede o número de caminhos linearmente independentes no fluxo de controle de um método: $$\text{Complexidade } V(G) = E - N + 2P$$ (Onde $E$ = arestas, $N$ = nós de decisão e $P$ = componentes conexos).

flowchart TD
    subgraph MCCABE ["COMPLEXIDADE CICLOMÁTICA E RISCO"]
        R1["1 a 10: Código Simples (Baixo Risco)"]
        R2["11 a 20: Complexidade Moderada (Risco Médio)"]
        R3["21 a 50: Alta Complexidade (Alto Risco / Difícil Testar)"]
        R4["> 50: Código Não-Testável (Risco Crítico / Refatorar Urgente)"]
    end
    style R1 fill:#dcfce7,stroke:#16a34a
    style R2 fill:#fef3c7,stroke:#d97706
    style R3 fill:#fee2e2,stroke:#ef4444
    style R4 fill:#b91c1c,stroke:#7f1d1d,color:#fff

💻 4. Aplicação Prática & Código Executável (Python 3.11+)

📋 Pré-requisitos e Instalação

Este exemplo utiliza o módulo ast (Abstract Syntax Trees) nativo da biblioteca padrão do Python:

python --version  # Requer Python 3.11 ou superior

💻 Código Completo e Autocontido (sqa_analisador_qualidade.py)

Implementação de analisador estático de complexidade ciclomática de McCabe $V(G)$ via AST em Python 3.11+:

"""
Módulo: sqa_analisador_qualidade.py
Domínio: Auditoria estática de código e cálculo de complexidade ciclomática.
"""
import ast

class AnalisadorComplexidade(ast.NodeVisitor):
    def __init__(self):
        self.complexidade = 1 # Caminho base linear

    def visit_If(self, node):
        self.complexidade += 1
        self.generic_visit(node)

    def visit_For(self, node):
        self.complexidade += 1
        self.generic_visit(node)

    def visit_While(self, node):
        self.complexidade += 1
        self.generic_visit(node)

def calcular_complexidade_codigo(codigo_fonte: str) -> int:
    arvore_sintatica = ast.parse(codigo_fonte)
    analisador = AnalisadorComplexidade()
    analisador.visit(arvore_sintatica)
    return analisador.complexidade

if __name__ == "__main__":
    print("--- Auditoria Estática de Complexidade Ciclomática (SQA) ---")
    
    codigo_exemplo = """
def calcular_frete_complicado(peso, distancia, expresso, vip):
    if peso > 10:
        if distancia > 100:
            if expresso:
                return 150.0
            elif vip:
                return 120.0
    return 50.0
"""
    v_g = calcular_complexidade_codigo(codigo_exemplo)
    print(f"📊 Complexidade Ciclomática V(G) do método: {v_g}")
    if v_g <= 10:
        print("✅ Qualidade Aprovada: Método com baixa complexidade e alta testabilidade.")
    else:
        print("⚠️ Alerta SQA: Método com excesso de bifurcações aninhadas. Refatore!")

🚀 Como Executar

Execute o script diretamente no terminal:

python sqa_analisador_qualidade.py

🖥️ Saída Esperada no Terminal

--- Auditoria Estática de Complexidade Ciclomática (SQA) ---
📊 Complexidade Ciclomática V(G) do método: 5
✅ Qualidade Aprovada: Método com baixa complexidade e alta testabilidade.

💡 5. Checkpoint de Engenharia & Boas Práticas

Boas Práticas & Anti-Patterns

  • Quality Gates no CI: Configure ferramentas como Ruff, Flake8 e Pytest no GitHub Actions. Se a complexidade ultrapassar o limite ou a cobertura cair abaixo de 80%, a esteira deve abortar o pull request.
  • Anti-Pattern Arrow Anti-Pattern: Evite métodos com múltiplos níveis de if/else aninhados em formato de seta (>) que elevam a complexidade ciclomática. Use Guard Clauses (retornos antecipados).

🔗 6. Conexão com os Projetos Integradores

Projeto IntegradorComo o conceito deste capítulo é aplicado no PI
PI-01: ManuTrackAplicação de SQA e testes unitários garantindo que o cálculo de OEE seja matematicamente exato.
PI-05: ParkFlowQualidade de Confiabilidade: tarifação temporal testada para frações de hora e viradas de dia.

🧪 7. Quiz de Fixação e Autoavaliação (Formative Assessment)

🧪 Quiz de Autoavaliação — Capítulo 16

1. De acordo com a norma ISO/IEC 25010, qual característica de qualidade avalia a facilidade com que o software pode ser modificado, corrigido e adaptado a novas demandas?

  • A) Portabilidade.
  • B) Manutenibilidade (Maintainability).
  • C) Usabilidade.
  • D) Tolerância a Falhas.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: Manutenibilidade compreende a modularidade, reusabilidade, analisabilidade, modificabilidade e testabilidade do código-fonte ao longo do seu ciclo de vida.


2. A métrica de 'Complexidade Ciclomática' de McCabe é utilizada principalmente para:

  • A) Medir o preço de licença de um software na nuvem.
  • B) Quantificar o número de caminhos de execução independentes no código, indicando o risco e o número mínimo de testes necessários.
  • C) Contar a quantidade de bytes gravados no disco rígido.
  • D) Calcular a velocidade da memória RAM.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: Quanto maior a complexidade ciclomática (mais if, while, for), mais difícil é entender, manter e testar o código, exigindo maior número de casos de teste.


3. O que é um 'Quality Gate' em uma esteira moderna de DevOps / CI?

  • A) Uma catraca física na porta da sala dos desenvolvedores.
  • B) Um conjunto automatizado de critérios e limites de qualidade (cobertura de testes, ausência de vulnerabilidades, linter) que um código deve satisfazer para ser aprovado no merge.
  • C) Um firewall de rede local.
  • D) Um contrato assinado pelo cliente.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: Quality Gates impedem que código com problemas de conformidade, falhas de segurança ou sem testes automatizados chegue aos branches principais e ao ambiente de produção.


🛠️ 8. Ponte para a Ação: Laboratório Prático

🎯 Próximo Passo Prático

Coloque esta teoria em prática executando o roteiro de laboratório autoguiado:
👉 ATIVIDADE 09: QUALIDADE E TESTES COM PYTEST


📌 9. Resumo Executivo & Key Takeaways

  • SQA Sistêmico: A qualidade deve ser construída e garantida em todas as fases do processo, não apenas testada no final.
  • ISO/IEC 25010: Modelo internacional com 8 características canônicas para auditoria de software.
  • Complexidade Ciclomática: Métodos com $V(G) \le 10$ são sustentáveis; métodos acima de 20 devem ser refatorados imediatamente.
  • Quality Gates: Automação com linters e testes bloqueando commits de baixa qualidade na esteira de CI.

🧪 CAPÍTULO 17: ESTRATÉGIAS DE TESTE E AUTOMAÇÃO


🎯 1. Objetivos de Aprendizagem & Competências

Estimativa de Dedicação: 2 horas de estudo autoguiado.
Ao final deste capítulo, você será capaz de:

  • 🔹 Dominar a Pirâmide de Testes de Mike Cohn: Testes Unitários (base rápida), Testes de Integração (meio com banco em memória) e Testes E2E (topo).
  • 🔹 Diferenciar Testes de Caixa-Preta (baseados em requisitos/comportamento) de Testes de Caixa-Branca (baseados na cobertura estrutural de código e branches).
  • 🔹 Aplicar técnicas de Mocking, Test Fixtures e injeção de dependências no pytest.
  • 🔹 Implementar testes automatizados de endpoints HTTP com o test_client nativo do Flask e Pytest.

🏢 2. Cenário Corporativo & Estudo de Caso (TecProExpress)

Na TecProExpress, após uma atualização na rotina de cálculo de cubagem de fretes, o faturamento gerou R$ 45.000,00 em cobranças a menor antes que alguém percebesse o erro em produção. A equipe realizava apenas testes manuais clicando na interface do sistema.

O Desafio: Como Engenheiro de Testes de Software, você deve abolir testes manuais em rotinas críticas, construindo uma suíte automatizada com Pytest que execute 100% dos testes unitários e de integração em menos de 10 segundos a cada commit.


🧠 3. Fundamentação Teórica & Modelos Visuais

3.1. A Pirâmide de Testes de Mike Cohn

Estrutura recomendada para maximizar a velocidade de feedback e minimizar o custo de manutenção:

flowchart TD
    subgraph PIRAMIDE ["PIRÂMIDE DE TESTES DE SOFTWARE"]
        E2E["🌐 1. Testes Ponta a Ponta (E2E / UI)<br>Lentos, caros, poucos (Topo)"]
        INT["⚙️ 2. Testes de Integração (APIs & DB em memória)<br>Velocidade média, validam contratos (Meio)"]
        UNIT["🧪 3. Testes Unitários (Funções, Classes & Regras Puras)<br>Milissegundos, baratos, milhares (Base da Pirâmide)"]
        
        E2E --> INT --> UNIT
    end
    
    style E2E fill:#fee2e2,stroke:#ef4444
    style INT fill:#fef3c7,stroke:#d97706
    style UNIT fill:#dcfce7,stroke:#16a34a

3.2. Caixa-Preta vs. Caixa-Branca

CritérioTeste de Caixa-Preta (Funcional)Teste de Caixa-Branca (Estrutural)
Conhecimento InternoNão conhece o código-fonte (testa entradas e saídas).Analisa o código-fonte, branches e loops.
Técnicas ComunsParticionamento em Classes de Equivalência, Análise de Valor Limite.Cobertura de Sentenças (Statement Coverage), Cobertura de Decisão/Branch.
FocoO sistema atende aos Requisitos Funcionais acordados?Todos os caminhos lógicos do algoritmo foram executados?

💻 4. Aplicação Prática & Código Executável (Python 3.11+)

📋 Pré-requisitos e Instalação

Instale o Flask e o Pytest no seu ambiente virtual:

pip install flask pytest

Dica do Especialista

O test_client() do Flask é nativo e não requer clientes HTTP adicionais (como httpx), tornando a suíte de testes extremamente rápida, isolada e sem dependências assíncronas.

💻 Código Completo e Autocontido (test_api_integracao.py)

Implementação de testes de integração com o test_client nativo do Flask e fixtures do Pytest:

"""
Módulo: test_api_integracao.py
Domínio: Testes de integração de endpoints REST com Flask test_client e Pytest.
"""
import pytest
from flask import Flask, request, jsonify

app = Flask(__name__)

BANCO_DADOS_MOCK = {}

@app.post("/itens/")
def criar_item():
    dados = request.get_json()
    if not dados:
        return jsonify({"detail": "Payload JSON ausente"}), 400

    nome = dados.get("nome")
    preco = dados.get("preco", 0.0)

    if not nome:
        return jsonify({"detail": "Nome é obrigatório"}), 400
    if preco <= 0:
        return jsonify({"detail": "Preço deve ser positivo."}), 400

    BANCO_DADOS_MOCK[nome] = preco
    return jsonify({"status": "CRIADO", "item": nome, "preco": preco}), 201

@app.get("/itens/<nome>")
def consultar_item(nome: str):
    if nome not in BANCO_DADOS_MOCK:
        return jsonify({"detail": "Item não encontrado."}), 404
    return jsonify({"item": nome, "preco": BANCO_DADOS_MOCK[nome]}), 200

# --- SUÍTE PYTEST ---
@pytest.fixture
def client():
    """Fixture Pytest que fornece o cliente de testes isolado do Flask."""
    app.config["TESTING"] = True
    BANCO_DADOS_MOCK.clear()
    with app.test_client() as client:
        yield client

def test_criar_item_com_sucesso(client):
    resposta = client.post("/itens/", json={"nome": "Notebook", "preco": 3500.00})
    assert resposta.status_code == 201
    assert resposta.get_json()["item"] == "Notebook"

def test_criar_item_preco_invalido_deve_retornar_400(client):
    resposta = client.post("/itens/", json={"nome": "Mouse", "preco": -10.00})
    assert resposta.status_code == 400
    assert "Preço deve ser positivo" in resposta.get_json()["detail"]

def test_consultar_item_inexistente_deve_retornar_404(client):
    resposta = client.get("/itens/ItemFantasma")
    assert resposta.status_code == 404

def test_fluxo_completo_cadastro_e_consulta(client):
    post_res = client.post("/itens/", json={"nome": "Teclado", "preco": 250.00})
    assert post_res.status_code == 201
    
    get_res = client.get("/itens/Teclado")
    assert get_res.status_code == 200
    assert get_res.get_json()["preco"] == 250.00

if __name__ == "__main__":
    print("🧪 Executando suíte com Pytest...")
    pytest.main(["-v", __file__])

🚀 Como Executar

Você pode executar o arquivo diretamente com o interpretador Python ou através do runner do Pytest:

# Execução direta via Python
python test_api_integracao.py

# Ou via runner padrão do Pytest
pytest -v test_api_integracao.py

🖥️ Saída Esperada no Terminal

🧪 Executando suíte com Pytest...
============================= test session starts =============================
rootdir: C:\Temp\portal_mdbook
collected 4 items

test_api_integracao.py::test_criar_item_com_sucesso PASSED               [ 25%]
test_api_integracao.py::test_criar_item_preco_invalido_deve_retornar_400 PASSED [ 50%]
test_api_integracao.py::test_consultar_item_inexistente_deve_retornar_404 PASSED [ 75%]
test_api_integracao.py::test_fluxo_completo_cadastro_e_consulta PASSED   [100%]

============================== 4 passed in 0.05s ==============================

💡 5. Checkpoint de Engenharia & Boas Práticas

Boas Práticas & Anti-Patterns

  • Testes Independentes (FIRST): Os testes devem ser Fast (rápidos), Isolated (independentes da ordem de execução), Repeatable (repetíveis em qualquer ambiente), Self-validating (passa/falha sem inspeção manual) e Timely (escritos no momento certo).
  • Anti-Pattern Cone de Sorvete (Ice Cream Cone): Ter milhares de testes manuais/E2E lentos e frágeis no topo e quase nenhum teste unitário na base. Inverta para o formato da Pirâmide!

🔗 6. Conexão com os Projetos Integradores

Projeto IntegradorComo o conceito deste capítulo é aplicado no PI
PI-01 a PI-10 (Todos os 10 PIs)Cada um dos 10 PIs possui uma suíte test_*.py completa com pytest e app.test_client() do Flask executando sobre SQLite em memória.

🧪 7. Quiz de Fixação e Autoavaliação (Formative Assessment)

🧪 Quiz de Autoavaliação — Capítulo 17

1. Na Pirâmide de Testes de Mike Cohn, por que os testes unitários devem compor a maior base da suíte de testes?

  • A) Porque eles são os únicos testes aceitos pela nuvem da AWS.
  • B) Porque são extremamente rápidos (executam em milissegundos), baratos de manter e apontam com precisão exata a linha e função onde o bug ocorreu.
  • C) Porque dispensam a escrita de código-fonte.
  • D) Porque substituem a necessidade de requisitos de negócio.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: Testes unitários isolam unidades mínimas de código, fornecendo feedback instantâneo ao desenvolvedor durante a digitação e custando uma fração do tempo de testes E2E.


2. A técnica de teste de Caixa-Preta conhecida como 'Análise de Valor Limite' (Boundary Value Analysis) orienta a testar:

  • A) Apenas números pares e inteiros positivos.
  • B) Os valores nas fronteiras exatas das classes de equivalência (ex: se a regra aceita idades de 18 a 65 anos, testar 17, 18, 19, 64, 65 e 66).
  • C) O consumo máximo de memória do servidor.
  • D) A cor dos botões na interface gráfica.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: A maioria dos erros lógicos de programação ocorre nas bordas das condições relacionais (< vs <=), tornando o teste dos valores limites altamente eficaz.


3. O que é um 'Mock' no contexto de testes automatizados?

  • A) Um bug insolúvel que deve ser ignorado.
  • B) Um objeto simulado que reproduz o comportamento de uma dependência externa (como um banco de dados real ou API externa de pagamentos) de forma controlada e sem chamadas de rede reais.
  • C) Uma falha de sintaxe do Python.
  • D) Um tipo de licença de software livre.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: Mocks isolam a unidade sob teste, permitindo simular respostas de sucesso, lentidão ou erros de serviços externos sem custos e sem instabilidades de rede.


🛠️ 8. Ponte para a Ação: Laboratório Prático

🎯 Próximo Passo Prático

Coloque esta teoria em prática executando o roteiro de laboratório autoguiado:
👉 ATIVIDADE 09: QUALIDADE E TESTES COM PYTEST


📌 9. Resumo Executivo & Key Takeaways

  • Pirâmide de Testes: Base ampla de Testes Unitários + Camada intermediária de Integração + Topo enxuto de E2E.
  • Caixa-Preta vs Caixa-Branca: Foco em comportamento funcional e valores limites versus cobertura estrutural de branches.
  • FIRST Principles: Testes devem ser rápidos, isolados, repetíveis, auto-validáveis e tempestivos.
  • Automação no CI: Nenhum código deve ser aceito em produção sem que 100% da suíte de testes automatizados esteja verde.

🔧 CAPÍTULO 18: MANUTENÇÃO E EVOLUÇÃO DE SOFTWARE


🎯 1. Objetivos de Aprendizagem & Competências

Estimativa de Dedicação: 2 horas de estudo autoguiado.
Ao final deste capítulo, você será capaz de:

  • 🔹 Classificar as 4 modalidades canônicas de manutenção segundo a norma ISO/IEC 14764: Corretiva, Adaptativa, Perfectiva e Preventiva.
  • 🔹 Compreender as Leis de Evolução de Software de Manny Lehman (Mudança Contínua, Complexidade Crescente, Declínio de Qualidade).
  • 🔹 Calcular e gerenciar o Débito Técnico (Technical Debt) utilizando a metáfora financeira de Ward Cunningham.
  • 🔹 Aplicar técnicas estruturadas de Refatoração de Código (Refactoring) catalogadas por Martin Fowler com a segurança de testes automatizados.

🏢 2. Cenário Corporativo & Estudo de Caso (TecProExpress)

Na TecProExpress, o módulo central de roteirização de entregas completou 4 anos em produção. No início, cada sprint entregava 10 novas funcionalidades; hoje, a equipe mal consegue entregar 2, gastando 80% do tempo apagando incêndios causados por bugs em código acoplado e sem documentação.

O Desafio: Como Engenheiro de Manutenção e Arquiteto de Software, você deve instituir uma política contínua de pagamento de débito técnico, reservando 20% da capacidade de cada sprint para refatoração preventiva e perfectiva (Boy Scout Rule: "Deixe o acampamento mais limpo do que você o encontrou").


🧠 3. Fundamentação Teórica & Modelos Visuais

3.1. As 4 Modalidades de Manutenção de Software (ISO/IEC 14764)

flowchart TD
    subgraph MANUTENCAO ["OS 4 TIPOS DE MANUTENÇÃO DE SOFTWARE"]
        C["🐛 1. MANUTENÇÃO CORRETIVA (Reativa)<br>Correção de bugs e falhas em produção."]
        A["🔄 2. MANUTENÇÃO ADAPTATIVA (Proativa/Reativa)<br>Adaptação a novos ambientes (migração de SO, nuvem, leis fiscais)."]
        P["✨ 3. MANUTENÇÃO PERFECTIVA (Proativa)<br>Melhoria de desempenho, refatoração de código e novas funcionalidades."]
        PR["🛡️ 4. MANUTENÇÃO PREVENTIVA (Proativa)<br>Correção de problemas latentes antes que se tornem incidentes reais."]
    end
    
    style C fill:#fee2e2,stroke:#ef4444
    style A fill:#e0f2fe,stroke:#0284c7
    style P fill:#dcfce7,stroke:#16a34a
    style PR fill:#fef3c7,stroke:#d97706

3.2. A Metáfora do Débito Técnico (Ward Cunningham)

Fazer alterações rápidas e "gambiarras" para bater prazos é como contrair um empréstimo financeiro: traz liquidez no curto prazo, mas acumula juros na forma de complexidade. Se o principal não for pago através de refatoração, a taxa de entrega da equipe cai a zero.

flowchart LR
    ATALHO["⚡ Atalho de Código / Sem Testes<br>(Empréstimo Rápido)"] --> DUIDA["📈 Débito Técnico Acumulado"]
    DUIDA --> JUROS["💸 Juros Mensais:<br>Bugs Frequentes & Entregas Lentas"]
    JUROS --> FALENCIA["⛔ Falência do Software / Reescrever do Zero"]
    
    DUIDA -.->|Refatoração Contínua| PAGO["✅ Débito Pago & Código Sustentável"]
    
    style ATALHO fill:#fef3c7,stroke:#d97706
    style DUIDA fill:#fee2e2,stroke:#ef4444
    style FALENCIA fill:#b91c1c,stroke:#7f1d1d,color:#fff
    style PAGO fill:#dcfce7,stroke:#16a34a

💻 4. Aplicação Prática & Código Executável (Python 3.11+)

📋 Pré-requisitos e Instalação

Este exemplo utiliza os recursos nativos do Python (decimal), sem necessidade de pacotes externos:

python --version  # Requer Python 3.11 ou superior

💻 Código Completo e Autocontido (refatoracao_exemplo.py)

Exemplo prático de Refatoração Perfectiva aplicando Guard Clauses, eliminação de aninhamento excessivo (Arrow Anti-Pattern) e tipagem estrita:

"""
Módulo: refatoracao_exemplo.py
Domínio: Aplicação do padrão Guard Clauses e eliminação de duplicação.
"""
from decimal import Decimal

# --- CÓDIGO ANTES DA REFATORAÇÃO (Espaguete / Alta Complexidade) ---
def calcular_desconto_legado(cliente_tipo, valor_total, dias_atraso):
    resultado = 0
    if valor_total > 100:
        if dias_atraso == 0:
            if cliente_tipo == "VIP":
                resultado = valor_total * 0.15
            else:
                resultado = valor_total * 0.05
        else:
            resultado = 0
    else:
        resultado = 0
    return resultado

# --- CÓDIGO APÓS A REFATORAÇÃO PERFECTIVA (Clean Code / Baixa Complexidade) ---
def calcular_desconto_refatorado(cliente_tipo: str, valor_total: Decimal, dias_atraso: int) -> Decimal:
    """Refatoração com Guard Clauses e tipagem estrita."""
    if valor_total <= Decimal("100.00") or dias_atraso > 0:
        return Decimal("0.00")
    
    taxa = Decimal("0.15") if cliente_tipo.upper() == "VIP" else Decimal("0.05")
    return (valor_total * taxa).quantize(Decimal("0.01"))

if __name__ == "__main__":
    print("--- Comparativo de Refatoração: Legado vs. Guard Clauses ---")
    
    # Caso 1: VIP adimplente > R$ 100
    v_antes = calcular_desconto_legado("VIP", 200, 0)
    v_depois = calcular_desconto_refatorado("VIP", Decimal("200.00"), 0)
    print(f"Caso 1 [VIP, R$ 200, sem atraso]: Legado = R$ {v_antes:.2f} | Refatorado = R$ {v_depois:.2f}")
    assert v_antes == float(v_depois)

    # Caso 2: Padrão adimplente > R$ 100
    v_antes_padrao = calcular_desconto_legado("PADRAO", 200, 0)
    v_depois_padrao = calcular_desconto_refatorado("PADRAO", Decimal("200.00"), 0)
    print(f"Caso 2 [Padrão, R$ 200, sem atraso]: Legado = R$ {v_antes_padrao:.2f} | Refatorado = R$ {v_depois_padrao:.2f}")
    assert v_antes_padrao == float(v_depois_padrao)

    # Caso 3: Cliente com atraso
    v_atraso = calcular_desconto_refatorado("VIP", Decimal("500.00"), 3)
    print(f"Caso 3 [VIP, com 3 dias de atraso]: Desconto = R$ {v_atraso:.2f}")
    assert v_atraso == Decimal("0.00")

    print("✅ Todos os comportamentos preservados com sucesso após a refatoração!")

🚀 Como Executar

Execute o script diretamente no terminal:

python refatoracao_exemplo.py

🖥️ Saída Esperada no Terminal

--- Comparativo de Refatoração: Legado vs. Guard Clauses ---
Caso 1 [VIP, R$ 200, sem atraso]: Legado = R$ 30.00 | Refatorado = R$ 30.00
Caso 2 [Padrão, R$ 200, sem atraso]: Legado = R$ 10.00 | Refatorado = R$ 10.00
Caso 3 [VIP, com 3 dias de atraso]: Desconto = R$ 0.00
✅ Todos os comportamentos preservados com sucesso após a refatoração!

💡 5. Checkpoint de Engenharia & Boas Práticas

Boas Práticas & Anti-Patterns

  • Refatoração com Rede de Segurança: Nunca refatore código que não possui testes automatizados. Primeiro crie os testes de caracterização para garantir que o comportamento atual não mude; depois limpe o código.
  • Anti-Pattern Reescrever do Zero (Second-System Effect): Evite a tentação de "jogar tudo fora e fazer de novo". Sistemas novos descartam anos de correções de casos de borda já resolvidos no legado. Refatore progressivamente (Strangler Fig Pattern).

🔗 6. Conexão com os Projetos Integradores

Projeto IntegradorComo o conceito deste capítulo é aplicado no PI
PI-02: StockFlowManutenção Adaptativa: migração transparente do motor de banco de dados SQLite (Dev) para PostgreSQL (Prod).
PI-06: ServiceFlowRefatoração Perfectiva: extração do cálculo de SLA para um serviço desacoplado e reutilizável.

🧪 7. Quiz de Fixação e Autoavaliação (Formative Assessment)

🧪 Quiz de Autoavaliação — Capítulo 18

1. A atividade de modificar o código interno de um software para torná-lo mais legível, modular e manutenível, SEM alterar em nada o seu comportamento externo observável, é denominada:

  • A) Manutenção Corretiva de Emergência.
  • B) Refatoração de Código (Refactoring / Manutenção Perfectiva).
  • C) Engenharia Reversa Ilícita.
  • D) Deploy Contínuo.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: Como definido por Martin Fowler, refatoração é a melhoria disciplinada do design interno do software sem alterar suas funcionalidades externas perceptíveis.


2. A 2ª Lei de Evolução de Software de Lehman (Complexidade Crescente) afirma que:

  • A) O custo dos servidores cai pela metade a cada 18 meses.
  • B) À medida que um sistema evolui, sua complexidade aumenta continuamente, a menos que esforços ativos de refatoração e simplificação sejam realizados para mantê-la sob controle.
  • C) O número de desenvolvedores deve dobrar a cada ano.
  • D) O software nunca deve receber novas funcionalidades.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: Lehman demonstrou que a adição de código sem refatoração degrada a estrutura do software com o tempo (entropia crescente), exigindo manutenção preventiva constante.


3. Uma alteração realizada no software para compatibilizá-lo com uma nova versão do PostgreSQL ou com uma nova lei tributária é classificada como:

  • A) Manutenção Corretiva.
  • B) Manutenção Adaptativa.
  • C) Manutenção Destrutiva.
  • D) Manutenção Estática.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: A manutenção adaptativa modifica o sistema para que ele continue operando corretamente frente a alterações no ambiente tecnológico ou regulatório externo.


🛠️ 8. Ponte para a Ação: Laboratório Prático

🎯 Próximo Passo Prático

Coloque esta teoria em prática executando o roteiro de laboratório autoguiado:
👉 ATIVIDADE 13: ARQUITETURA DE SOFTWARE E PADRÕES


📌 9. Resumo Executivo & Key Takeaways

  • 4 Tipos de Manutenção: Corretiva (Bugs), Adaptativa (Ambiente), Perfectiva (Qualidade/Refatoração) e Preventiva (Previsão de Falhas).
  • Débito Técnico: Metáfora que quantifica o custo oculto de atalhos e decisões de engenharia de baixa qualidade.
  • Leis de Lehman: O software degrada naturalmente a menos que receba investimentos contínuos de refatoração.
  • Boy Scout Rule: Manter o hábito diário de limpar e melhorar pequenos trechos de código em cada commit.

🌿 CAPÍTULO 19: GERÊNCIA DE CONFIGURAÇÃO (SCM) E GIT


🎯 1. Objetivos de Aprendizagem & Competências

Estimativa de Dedicação: 2 horas de estudo autoguiado.
Ao final deste capítulo, você será capaz de:

  • 🔹 Dominar os fundamentos de Software Configuration Management (SCM), Linhas de Base (Baselines), Itens de Configuração (CIs) e Auditoria de Configuração (IEEE 828).
  • 🔹 Aplicar estratégias avançadas de ramificação com Git (GitFlow: main, develop, feature/*, release/*, hotfix/*).
  • 🔹 Padronizar o histórico de commits utilizando o padrão da indústria Conventional Commits (feat:, fix:, refactor:, test:).
  • 🔹 Estruturar releases segundo o Versionamento Semântico (SemVer: MAJOR.MINOR.PATCH).

🏢 2. Cenário Corporativo & Estudo de Caso (TecProExpress)

Na TecProExpress, mais de 40 desenvolvedores atuam simultaneamente no repositório do sistema de fretes. Sem regras de governança, alterações incompletas eram enviadas diretamente para a branch main, quebrando o ambiente de homologação e sobrescrevendo códigos de outros colegas de time.

O Desafio: Como Engenheiro de Release e SCM, você deve instituir a política de governança de código: proteção de branch main, fluxo GitFlow estrito, Conventional Commits e pipelines de validação com pre-commit hooks que bloqueiam código sem formatação ou com testes quebrados.


🧠 3. Fundamentação Teórica & Modelos Visuais

3.1. O Fluxo de Trabalho GitFlow

Estratégia consolidada de isolamento de branches para times multidisciplinares:

gitGraph
    commit id: "v1.0.0" tag: "v1.0.0"
    branch develop
    checkout develop
    commit id: "Setup Inicial"
    branch feature/checkout
    checkout feature/checkout
    commit id: "feat: add pix"
    commit id: "test: pix handler"
    checkout develop
    merge feature/checkout id: "Merge feature"
    branch release/1.1.0
    checkout release/1.1.0
    commit id: "docs: changelog"
    checkout main
    merge release/1.1.0 id: "Merge release" tag: "v1.1.0"
    checkout develop
    merge release/1.1.0 id: "Sync develop"

3.2. Versionamento Semântico 2.0.0 (SemVer)

O formato MAJOR.MINOR.PATCH comunica a compatibilidade das atualizações:

flowchart TD
    subgraph SEMVER ["VERSIONAMENTO SEMÂNTICO (SemVer)"]
        M["🔴 MAJOR (ex: 2.0.0)<br>Alterações incompatíveis com versões anteriores (Breaking Changes)"]
        MI["🟡 MINOR (ex: 1.2.0)<br>Adição de novas funcionalidades mantendo total compatibilidade regressiva"]
        P["🟢 PATCH (ex: 1.1.3)<br>Correções de bugs e patches de segurança sem alteração de API"]
    end
    style M fill:#fee2e2,stroke:#ef4444
    style MI fill:#fef3c7,stroke:#d97706
    style P fill:#dcfce7,stroke:#16a34a

💻 4. Aplicação Prática & Código Executável (Python 3.11+)

📋 Pré-requisitos e Instalação

Este exemplo utiliza os módulos padrão re e sys do Python, sem necessidade de pacotes externos:

python --version  # Requer Python 3.11 ou superior

💻 Código Completo e Autocontido (validar_conventional_commits.py)

Script em Python para validação automatizada de mensagens de commit (Pre-commit Hook) seguindo a especificação Conventional Commits:

"""
Módulo: validar_conventional_commits.py
Domínio: Auditoria de mensagens de commit segundo a convenção da indústria.
"""
import re
import sys

# Padrão: tipo(escopo opcional): descrição
PADRAO_COMMIT = r"^(feat|fix|docs|style|refactor|test|chore|ci)(\([a-z0-9_-]+\))?:\s[a-z0-9].{5,72}$"

def validar_mensagem_commit(mensagem: str) -> bool:
    mensagem = mensagem.strip()
    if not re.match(PADRAO_COMMIT, mensagem):
        print(f"❌ FORMATO DE COMMIT INVÁLIDO: '{mensagem}'")
        print("   Exemplos corretos:")
        print("   - feat(fretes): adicionar calculo de cubagem por volume")
        print("   - fix(auth): corrigir expiracao do token jwt")
        print("   - test(pedidos): adicionar testes de integracao com pytest")
        return False
    print(f"✅ Commit válido: '{mensagem}'")
    return True

if __name__ == "__main__":
    print("--- Auditoria de Mensagens de Commit (Conventional Commits) ---")
    
    # 1. Caso válido
    msg_correta = "feat(rastreio): integrar websocket de notificacao em tempo real"
    ok1 = validar_mensagem_commit(msg_correta)
    assert ok1 is True

    # 2. Caso inválido (sem tipo e formato fora do padrão)
    msg_errada = "arrumando bug no login"
    ok2 = validar_mensagem_commit(msg_errada)
    assert ok2 is False

🚀 Como Executar

Execute o script diretamente no terminal:

python validar_conventional_commits.py

🖥️ Saída Esperada no Terminal

--- Auditoria de Mensagens de Commit (Conventional Commits) ---
✅ Commit válido: 'feat(rastreio): integrar websocket de notificacao em tempo real'
❌ FORMATO DE COMMIT INVÁLIDO: 'arrumando bug no login'
   Exemplos corretos:
   - feat(fretes): adicionar calculo de cubagem por volume
   - fix(auth): corrigir expiracao do token jwt
   - test(pedidos): adicionar testes de integracao com pytest

💡 5. Checkpoint de Engenharia & Boas Práticas

Boas Práticas & Anti-Patterns

  • Commits Atômicos: Cada commit deve representar uma unidade lógica de trabalho isolada e completa (ex: uma correção de bug ou uma nova rota). Evite commits gigantescos com centenas de arquivos misturando refatoração e novas features.
  • Anti-Pattern Direct-to-Main: Nunca permita commits diretos na branch main. Todo código deve obrigatoriamente passar por um Pull Request / Merge Request revisado por ao menos um colega (Peer Review).

🔗 6. Conexão com os Projetos Integradores

Projeto IntegradorComo o conceito deste capítulo é aplicado no PI
PI-01 a PI-10 (Todos os 10 PIs)Aplicação de pre-commit hooks e Conventional Commits (feat(pi):, test:, docs:) em cada entrega dos 10 PIs no Git.

🧪 7. Quiz de Fixação e Autoavaliação (Formative Assessment)

🧪 Quiz de Autoavaliação — Capítulo 19

1. No Versionamento Semântico (SemVer: MAJOR.MINOR.PATCH), quando se deve incrementar o número 'MAJOR' (ex: de 1.4.2 para 2.0.0)?

  • A) Quando corrigimos um bug de digitação na documentação.
  • B) Quando introduzimos mudanças incompatíveis com versões anteriores da API (Breaking Changes), exigindo que os clientes atualizem seu código.
  • C) A cada virada de ano civil.
  • D) Quando o time troca de linguagem de programação.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: O número MAJOR é reservado para quebras de compatibilidade reversa (Breaking Changes), alertando os consumidores da biblioteca/API sobre a necessidade de adaptação.


2. No fluxo GitFlow, qual branch é criada para realizar correções emergenciais críticas diretamente sobre a versão em produção?

  • A) feature/correcao
  • B) develop
  • C) hotfix/*
  • D) experimental
💡 Ver Resposta e Justificativa

Resposta Correta: C
Justificativa: Branches hotfix/* derivam diretamente da main para sanar um incidente crítico de produção e, ao término, são mescladas de volta na main (com nova tag) e na develop.


3. O que é uma 'Linha de Base' (Baseline) na Gerência de Configuração de Software (SCM)?

  • A) Uma linha divisória no código Python.
  • B) Uma versão formalmente revisada e aprovada de um conjunto de artefatos de software (como uma Tag no Git) que serve como base imutável para futuros desenvolvimentos.
  • C) O salário mínimo dos desenvolvedores.
  • D) Um cabo de rede conectado ao servidor principal.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: Baselines representam marcos estáveis e rastreáveis no histórico do projeto, permitindo auditorias e rollbacks confiáveis caso ocorra alguma falha em produção.


🛠️ 8. Ponte para a Ação: Laboratório Prático

🎯 Próximo Passo Prático

Coloque esta teoria em prática executando o roteiro de laboratório autoguiado:
👉 ATIVIDADE 15: GITFLOW E TRABALHO COLABORATIVO


📌 9. Resumo Executivo & Key Takeaways

  • SCM é Governança: Controla a evolução ordenada e rastreável de todos os artefatos de código, modelos e configurações.
  • GitFlow: Isola o trabalho em branches dedicadas (feature, develop, release, hotfix, main).
  • Conventional Commits: Cria um histórico de commits legível por humanos e por ferramentas de automação de changelog.
  • SemVer: Regra global de transparência (MAJOR.MINOR.PATCH) para evolução de bibliotecas e APIs.

🚀 CAPÍTULO 20: CONCLUSÃO, DEVSECOPS E MÉTRICAS DORA


🎯 1. Objetivos de Aprendizagem & Competências

Estimativa de Dedicação: 2 horas de estudo autoguiado.
Ao final deste capítulo, você será capaz de:

  • 🔹 Integrar todas as disciplinas de Engenharia de Software (Processos, Requisitos, Modelagem UML, Qualidade e SCM) sob a cultura DevSecOps.
  • 🔹 Dominar as 4 Métricas DORA (DevOps Research and Assessment): Deployment Frequency, Lead Time for Changes, Change Failure Rate e Failed Deployment Recovery Time / MTTR.
  • 🔹 Compreender o princípio de Shift-Left Security (Segurança desde as primeiras etapas do ciclo de vida).
  • 🔹 Consolidar a entrega do seu Projeto Integrador conectando a arquitetura ao deploy contínuo em produção.

🏢 2. Cenário Corporativo & Estudo de Caso (TecProExpress)

Na TecProExpress, a alta liderança instituiu uma meta de transformação digital: transformar a empresa em uma organização de alta performance de engenharia (Elite Performer). Para isso, o tempo entre a concepção de uma funcionalidade e sua entrada em produção deve ser reduzido de 3 meses para menos de 1 dia, com taxa de falha de mudança inferior a 5%.

O Desafio: Como Líder Técnico de Engenharia de Software e DevSecOps, você deve orquestrar a cultura, as ferramentas de automação e os testes contínuos para garantir que os 10 Projetos Integradores operem com estabilidade, segurança e observabilidade contínua.


🧠 3. Fundamentação Teórica & Modelos Visuais

3.1. O Ciclo Infinito de DevSecOps

A segurança e a qualidade deixam de ser uma etapa final isolada e passam a permear cada fase do ciclo de entrega:

flowchart LR
    subgraph DEV ["💻 DESENVOLVIMENTO (DEV)"]
        direction LR
        P["Plan"] --> C["Code"] --> B["Build"] --> T["Test"]
    end
    
    subgraph SEC ["🛡️ SEGURANÇA (SEC)"]
        direction TB
        SAST["SAST / DAST"]
        SCA["SCA (Dependências)"]
        AUDIT["Auditoria de Acessos"]
    end
    
    subgraph OPS ["⚙️ OPERAÇÕES (OPS)"]
        direction LR
        R["Release"] --> D["Deploy"] --> O["Operate"] --> M["Monitor"]
    end
    
    T --> SEC
    SEC --> R
    M -.-> P
    
    style DEV fill:#e0f2fe,stroke:#0284c7
    style SEC fill:#fef3c7,stroke:#d97706
    style OPS fill:#dcfce7,stroke:#16a34a

3.2. As 4 Métricas DORA da Engenharia de Alta Performance

Métrica DORAO que mede?Nível Elite (Google / DORA)Nível Baixo (Tradicional)
1. Deployment FrequencyFrequência com que o código chega a produção.Múltiplos deploys/dia sob demanda1 deploy a cada 1 a 6 meses
2. Lead Time for ChangesTempo desde o primeiro commit até o código rodar em produção.Menos de 1 horaEntre 1 a 6 meses
3. Change Failure Rate (CFR)Percentual de deploys que causam incidentes em produção.0% a 5%Acima de 45%
4. Time to Restore Service (MTTR)Tempo médio para restaurar o sistema após uma falha crítica.Menos de 1 horaSuperior a 1 semana

💻 4. Aplicação Prática & Código Executável (Python 3.11+)

📋 Pré-requisitos e Instalação

Este exemplo utiliza os módulos padrão dataclasses do Python, sem necessidade de pacotes externos:

python --version  # Requer Python 3.11 ou superior

💻 Código Completo e Autocontido (calculador_metricas_dora.py)

Implementação de um motor de avaliação contínua de maturidade DevOps baseado nas 4 Métricas DORA do Google:

"""
Módulo: calculador_metricas_dora.py
Domínio: Avaliação contínua de performance e maturidade de engenharia.
"""
from dataclasses import dataclass

@dataclass
class MetricasDORA:
    squad_nome: str
    deploys_por_mes: int
    lead_time_horas: float
    taxa_falha_percentual: float
    tempo_recuperacao_horas: float

    def classificar_maturidade(self) -> str:
        if (self.deploys_por_mes >= 30 and 
            self.lead_time_horas <= 24 and 
            self.taxa_falha_percentual <= 5.0 and 
            self.tempo_recuperacao_horas <= 1.0):
            return "🏆 ELITE PERFORMER (Nível Google / Amazon / Netflix)"
        elif self.deploys_por_mes >= 4 and self.lead_time_horas <= 168:
            return "🥈 HIGH / MEDIUM PERFORMER (Boa maturidade ágil)"
        else:
            return "⚠️ LOW PERFORMER (Alerta: Alto risco e processos lentos)"

if __name__ == "__main__":
    print("--- Auditoria de Métricas DORA de Engenharia de Software ---")
    
    # Squad A: Práticas Modernas de CI/CD e Testes Automatizados
    squad_alpha = MetricasDORA(
        squad_nome="Squad Alpha (Logística Core)",
        deploys_por_mes=45,
        lead_time_horas=3.5,
        taxa_falha_percentual=2.1,
        tempo_recuperacao_horas=0.5
    )
    
    # Squad B: Processos Manuais e Deploys Esporádicos
    squad_beta = MetricasDORA(
        squad_nome="Squad Beta (Legado)",
        deploys_por_mes=1,
        lead_time_horas=720.0,
        taxa_falha_percentual=48.0,
        tempo_recuperacao_horas=48.0
    )

    for squad in [squad_alpha, squad_beta]:
        print(f"\n📊 {squad.squad_nome}:")
        print(f"   Deploys/mês: {squad.deploys_por_mes} | Lead Time: {squad.lead_time_horas}h")
        print(f"   Taxa Falha: {squad.taxa_falha_percentual}% | MTTR: {squad.tempo_recuperacao_horas}h")
        print(f"   Classificação: {squad.classificar_maturidade()}")

🚀 Como Executar

Execute o script diretamente no terminal:

python calculador_metricas_dora.py

🖥️ Saída Esperada no Terminal

--- Auditoria de Métricas DORA de Engenharia de Software ---

📊 Squad Alpha (Logística Core):
   Deploys/mês: 45 | Lead Time: 3.5h
   Taxa Falha: 2.1% | MTTR: 0.5h
   Classificação: 🏆 ELITE PERFORMER (Nível Google / Amazon / Netflix)

📊 Squad Beta (Legado):
   Deploys/mês: 1 | Lead Time: 720.0h
   Taxa Falha: 48.0% | MTTR: 48.0h
   Classificação: ⚠️ LOW PERFORMER (Alerta: Alto risco e processos lentos)

💡 5. Checkpoint de Engenharia & Boas Práticas

Boas Práticas & Anti-Patterns

  • Shift-Left Security: Encontre vulnerabilidades o mais cedo possível. Analise bibliotecas e dependências desatualizadas no próprio VS Code e no pre-commit hook antes mesmo de enviar o pull request.
  • Anti-Pattern Deploy de Sexta-Feira sem Automação: Nunca realize deploys manuais com comandos manuais em servidores de produção. O deploy deve ser automatizado, reprodutível e com capacidade de Rollback em 1 clique.

🔗 6. Conexão com os Projetos Integradores

Projeto IntegradorComo o conceito deste capítulo é aplicado no PI
PI-01 a PI-10 (Todos os 10 PIs)Conclusão e entrega final dos 10 PIs com dual-database, suítes de testes automatizados com pytest, Flask REST API e 100 desafios SQL aprovados.

🧪 7. Quiz de Fixação e Autoavaliação (Formative Assessment)

🧪 Quiz de Autoavaliação — Capítulo 20

1. Quais são as quatro métricas fundamentais de performance de engenharia estabelecidas pelo programa DORA (DevOps Research and Assessment)?

  • A) Linhas de Código, Número de Telas, Preço do Servidor e Memória RAM.
  • B) Deployment Frequency, Lead Time for Changes, Change Failure Rate e Time to Restore Service.
  • C) Quantidade de Reuniões, Número de E-mails, Horas Extras e Bugs Reportados.
  • D) Número de Classes, Quantidade de Comentários, Versão do Git e Sistema Operacional.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: As métricas DORA são o padrão ouro global da indústria para medir a velocidade de entrega e a estabilidade operacional de equipes de software.


2. O que preconiza o princípio de 'Shift-Left Security' na cultura DevSecOps?

  • A) Contratar guardas de segurança para vigiar a sala dos servidores.
  • B) Mover as verificações de segurança para as etapas mais iniciais do desenvolvimento (planejamento, codificação e testes locais), em vez de realizá-las apenas antes do deploy.
  • C) Usar senhas contendo apenas letras minúsculas.
  • D) Proibir o uso de internet pelos programadores.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: 'Shift-Left' significa antecipar as análises de vulnerabilidade para a esquerda da linha do tempo do projeto (IDE e commits), reduzindo o custo e o impacto de falhas de segurança.


3. O que define uma organização de engenharia de software classificada como 'Elite Performer'?

  • A) Realiza deploys apenas uma vez ao ano durante a madrugada de Natal.
  • B) Realiza múltiplos deploys automatizados por dia sob demanda, com Lead Time menor que 1 hora e MTTR de recuperação inferior a 1 hora.
  • C) Não realiza testes automatizados de nenhuma espécie.
  • D) Desenvolve softwares sem documentação ou repositório Git.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: Times de elite combinam alta velocidade de release com altíssima estabilidade operacional através de automação, CI/CD, testes rigorosos e observabilidade contínua.


🛠️ 8. Ponte para a Ação: Laboratório Prático

🎯 Próximo Passo Prático

Consolide todas as disciplinas concluindo a entrega avançada do seu projeto:
👉 ATIVIDADE 20: MARCO 2 — ENTREGA FINAL DO PROJETO INTEGRADOR


📌 9. Resumo Executivo & Key Takeaways

  • Engenharia de Ponta a Ponta: O ciclo de vida do software é contínuo, integrando pessoas, processos, requisitos, arquitetura e qualidade.
  • DevSecOps: A segurança e os testes automatizados são responsabilidade de todos do time desde o primeiro dia.
  • Métricas DORA: A excelência de um squad mede-se pela frequência de entrega, tempo de ciclo, baixa taxa de falha e recuperação rápida.
  • Profissional do Futuro: O Engenheiro de Software moderno é um solucionador de problemas de negócio guiado por rigor técnico e boas práticas.

📅 CRONOGRAMA — PROJETOS II (ENGENHARIA DE SOFTWARE)

Planejamento de 20 semanas acadêmicas com atividades práticas lineares e autoguiadas, cobrindo todo o ciclo de vida moderno de engenharia e operações de software. 🛡️🧩


SemanaTópico de Aula (Referência)Atividade Prática de Engenharia
1Introdução à Engenharia de SoftwareAtv 01: Escopo e Personas
2Modelos de Processos e Metodologias ÁgeisAtv 02: Processos de Software
3Elicitação e Engenharia de RequisitosAtv 03: Requisitos (FR/NFR)
4Decomposição de Escopo e Gestão de BacklogAtv 04: User Stories e Backlog
5Modelagem de Casos de UsoAtv 05: Diagrama de Casos de Uso (UML)
6Design de Interfaces e PrototipagemAtv 06: Prototipagem (Wireframes)
7Modelagem Estrutural: ClassesAtv 07: Diagrama de Classes (UML)
8Modelagem Dinâmica: Sequência
📝 P1 — prova objetiva (Semanas 1–8: Requisitos, Processos e Modelagem UML)
Atv 08: Diagrama de Sequência (UML)
9Conceitos de Testes e Plano de TestesAtv 09: Qualidade e Testes
10Fase I: Consolidação de RequisitosAtv 10: Projeto Integrador Final (Fase I)
11Modelagem de Processos ComplexosAtv 11: Diagrama de Atividades (UML)
12Modelagem de Comportamento DinâmicoAtv 12: Diagrama de Transição de Estados (UML)
13Arquitetura de Software e Padrões de DesignAtv 13: Arquitetura de Software e Padrões
14Integração de Sistemas e Design de APIsAtv 14: Modelagem de APIs REST e Swagger
15Gerência de Configuração e GitFlow
📝 P2 — prova objetiva (Semanas 9–15: Arquitetura, APIs, GitFlow e CI)
Atv 15: GitFlow e Trabalho Colaborativo
16Integração Contínua (CI)Atv 16: Pipelines de CI/CD com GitHub Actions
17Containerização de Ambientes de ExecuçãoAtv 17: Containerização com Docker
18Segurança no Ciclo de Vida (DevSecOps)Atv 18: Segurança e OWASP Top 10
19Gestão e Métricas de EngenhariaAtv 19: Métricas de Software, Estimativas e DORA
20Fase II: Dossiê de Engenharia Moderno🏆 Atv 20: Projeto Final Avançado (Fase II)

📈 Composição da Nota

A disciplina segue o modelo de avaliação da Fatec (Regulamento Geral dos Cursos Superiores de Graduação das Fatecs, Deliberação CEETEPS nº 106/2025). Sem pesos ou pontuações fracionadas por atividade — apenas média aritmética simples. Detalhamento completo, regra do Exame e exemplos de cálculo em docs/institucional/SISTEMA_AVALIACAO_FATEC.md (repositório do projeto).

Período 1 (Atividades 01–08)Período 2 (Atividades 09–20)
P1 — prova objetiva, nota de 0 a 10 (quantidade de questões a critério do professor), aplicada na Semana 8P2 — prova objetiva, nota de 0 a 10, aplicada na Semana 15
Atv 01 a 08, cada uma avaliada de 0 a 10 pelo professor, controladas em planilha própriaAtv 09 a 20, mesma lógica
A1 = média aritmética simples das notas de Atv 01 a 08A2 = média aritmética simples das notas de Atv 09 a 20

As semanas de P1 (8) e P2 (15) seguem o calendário oficial do 2º semestre de 2026 (docs/institucional/Calendario-Fatec-Assis-2026.pdf e docs/institucional/FATEC-Calendario 2026-2.jpeg: P1 em 21–25/09, P2 em 13–19/11), considerando que a turma de ES tem aula às sextas-feiras — por isso o corte de período não cai num meio redondo (8 e 12 atividades, não 10 e 10). Note que a semana de P2 é diferente da usada em Banco de Dados (semana 16), porque BD tem aula às terças e ES às sextas — a mesma janela oficial de prova cai em semanas de curso distintas para cada turma. As datas mudam a cada semestre e precisam ser conferidas no calendário vigente; a quantidade de questões da prova também é livre para o professor definir. O que não muda é o cálculo: prova 0–10 com peso igual entre questões; A1/A2 como média aritmética simples das notas do período.

Média Final = (P1 + P2 + A1 + A2) / 4

Aprovação: Média Final ≥ 6,0, com frequência mínima de 75%.

Direito ao Exame (Art. 34 do Regulamento): aluno reprovado por nota (Média Final < 6,0) que cumpriu a frequência mínima de 75% — sem piso mínimo de nota. A forma exata de cálculo da nota pós-exame é proposta pela Coordenação de Curso; a convenção adotada neste componente ((Média Final + Exame) / 2 ≥ 6,0) está pendente de validação formal e detalhada em docs/institucional/SISTEMA_AVALIACAO_FATEC.md.

💡 Nota: o Projeto Integrador Final (Atv 10, Fase I) e o Projeto Final Avançado (Atv 20, Fase II) permanecem como as últimas atividades de cada período — entram na média A1/A2 como qualquer outra atividade, sem peso extra.

⚠️ Esta metodologia está sujeita a alteração conforme decisão da Unidade/Coordenação de Curso. Qualquer mudança será informada previamente aos alunos, antes de entrar em vigor.

🚀 Guia do Aluno & Manual de Início Rápido do Projeto Integrador

Bem-vindo(a) ao Projeto Integrador Interdisciplinar (GTI - FATEC)! Este manual foi criado para orientar você e sua equipe na construção de uma aplicação web corporativa funcional ponta a ponta, integrando os conhecimentos de Engenharia de Software, Banco de Dados e Programação Orientada a Objetos em Python. 🛡️🧩


🎯 1. O que é o Projeto Integrador (PI)?

O Projeto Integrador simula o ambiente profissional de uma Software House ou time de TI corporativo. Você e seu time atuarão como Analistas de Sistemas, percorrendo todo o ciclo de vida de desenvolvimento:

graph LR
    E1["1. Elicitação<br/>(User Stories & Trello)"] --> E2["2. Modelagem<br/>(UML & DER no Draw.io)"]
    E2 --> E3["3. Backend & API<br/>(Flask & SQLAlchemy)"]
    E3 --> E4["4. Frontend Web<br/>(Jinja2 & Bootstrap 5)"]
    E4 --> E5["5. Banco Dual & CI/CD<br/>(SQLite / Docker Postgres)"]

    style E1 fill:#e3f2fd,stroke:#1e88e5,stroke-width:2px
    style E2 fill:#fff8e1,stroke:#fbc02d,stroke-width:2px
    style E3 fill:#e8f5e9,stroke:#43a047,stroke-width:2px
    style E4 fill:#f3e5f5,stroke:#8e24aa,stroke-width:2px
    style E5 fill:#ffebee,stroke:#e53935,stroke-width:2px

👥 2. Formação da Equipe & Papéis Ágeis (2 a 4 Alunos)

Cada integrante assume um papel principal no projeto (com atuação colaborativa em código):

PapelResponsabilidades PrincipaisEntregáveis Chave
👔 Product Owner (PO)Elicitação de requisitos, regras de negócio e priorização do Backlog.Documento de Visão, User Stories e Quadro Kanban no Trello/GitHub.
📐 Engenheiro de SoftwareArquitetura do sistema, diagramas UML e fluxos de interação.Casos de Uso, Diagrama de Classes e Diagrama de Atividades/Estados.
🛢️ DBA / ModeladorProjeto conceitual e lógico do banco de dados e normalização 3FN.Diagrama DER, Script DDL (Postgres/SQLite) e Mapeamento ORM.
💻 Desenvolvedor Full-Stack / QAConstrução das rotas Flask, templates Jinja2 e testes automatizados.Código-fonte modularizado, telas Bootstrap e suíte de testes pytest.

🧭 3. Como Escolher o Tema na Galeria (Matriz 5 Setores × 2 Níveis)

A galeria conta com 10 Projetos Oficiais organizados nos 5 Grandes Setores da Economia Brasileira:

┌──────────────────────────────────────────────────────────────────────────────────┐
│ 🏭 SETOR 1: INDÚSTRIA & PRODUÇÃO                                                 │
│    🟢 Nível 1: PI 01 — ManuTrack (Manutenção Preventiva de Máquinas)             │
│    🔴 Nível 2: PI 02 — StockFlow (Almoxarifado Industrial, Lotes & Curva ABC)    │
├──────────────────────────────────────────────────────────────────────────────────┤
│ 🛒 SETOR 2: COMÉRCIO & VAREJO                                                    │
│    🟢 Nível 1: PI 03 — PDVLite (Frente de Caixa & Balcão de Vendas)              │
│    🔴 Nível 2: PI 04 — ShopFlow (E-commerce B2C, Catálogo & Carrinho)            │
├──────────────────────────────────────────────────────────────────────────────────┤
│ 🛠️ SETOR 3: SERVIÇOS & TECNOLOGIA                                                │
│    🟢 Nível 1: PI 05 — ParkFlow (Estacionamentos & Tarifação)                    │
│    🔴 Nível 2: PI 06 — ServiceFlow (Ordens de Serviço & Gestão de SLA)           │
├──────────────────────────────────────────────────────────────────────────────────┤
│ 🌾 SETOR 4: AGROPECUÁRIA & AGRONEGÓCIO                                           │
│    🟢 Nível 1: PI 07 — AgroSafe (Insumos Agrícolas & Defensivos)                 │
│    🔴 Nível 2: PI 08 — CattleFlow (Pecuária, Rastreabilidade & Vacinação)        │
├──────────────────────────────────────────────────────────────────────────────────┤
│ 🏢 SETOR 5: FINANCEIRO E IMOBILIÁRIO                                             │
│    🟢 Nível 1: PI 09 — FinLite (Contas a Pagar/Receber & Fluxo de Caixa)         │
│    🔴 Nível 2: PI 10 — ImobiFlow (Gestão Imobiliária, Contratos & Locação)       │
└──────────────────────────────────────────────────────────────────────────────────┘

[!TIP] Como Escolher:

  • Grupos em Consolidação de Base: Escolham projetos de Nível 1 (🟢 Essencial / 3 a 4 Tabelas). Vocês entregarão um sistema 100% funcional com relacionamentos 1:N diretos.
  • Grupos Avançados / Desafiadores: Escolham projetos de Nível 2 (🔴 Intermediário / 5 a 7 Tabelas), implementando relacionamentos N:N, transações ACID e relatórios analíticos.

⚡ 4. Setup do Ambiente em 3 Minutos no VS Code

O curso disponibiliza um template oficial pronto para clonar na pasta:
📂 examples/template_projeto_integrador/

Passo a Passo de Execução:

  1. Instale as Extensões Recomendadas no VS Code: Abra o projeto e aceite a instalação do pacote .vscode/extensions.json (Python, Jinja, SQLTools, Docker, Draw.io).

  2. Crie e Ative seu Ambiente Virtual:

    python -m venv venv
    # No Windows (PowerShell):
    .\venv\Scripts\Activate.ps1
    # No Linux/Mac:
    source venv/bin/activate
    
  3. Instale as Dependências:

    pip install -r requirements.txt
    
  4. Execute o Sistema com SQLite (Zero Configuração no Dia 1):

    python main.py
    
    • 🌐 Aplicação Web & API: http://localhost:5000
  5. Execute a Suíte de Testes Automatizados:

    pytest
    

📅 5. Cronograma de Entregas e Rubricas de Avaliação

timeline
    title Linha do Tempo do Projeto Integrador (20 Semanas)
    Semana 04 : Entrega 1 - Concepção e Escopo : Documento de Visão, Personas e User Stories no Trello
    Semana 08 : Entrega 2 - Modelagem de Engenharia : Casos de Uso, Diagrama de Classes e DER 3FN no Draw.io
    Semana 14 : Entrega 3 - Backend e Persistência : API Flask, Modelos SQLAlchemy e Banco Dual
    Semana 20 : Entrega 4 - Entrega Final : Aplicação Web Jinja2, Testes Pytest, Docker e Apresentação

Rubrica Oficial de Pontuação:

Marco de EntregaPrazoPesoCritérios Avaliados
Entrega 1: Escopo e RequisitosSemana 0420%Documento de Visão claro, 8+ Requisitos Funcionais, 3+ RNF e Backlog estruturado no Trello/GitHub.
Entrega 2: Modelagem VisualSemana 0825%Diagrama de Casos de Uso com <<include>>/<<extend>>, Diagrama de Classes tipado e DER normalizado (3FN) no Draw.io.
Entrega 3: Backend & Banco DualSemana 1425%Rotas Flask funcionando e testadas, persistência SQLAlchemy com SQLite local e PostgreSQL no Docker.
Entrega 4: Sistema Completo & QASemana 2030%Interface web responsiva com Bootstrap 5, suíte de testes pytest aprovada, README completo e apresentação oral.

🛡️ 6. Regras de Ouro para o Sucesso da Equipe

  1. A "Regra dos Três Mundos": Certifique-se de que cada tabela SQL tenha sua classe correspondente em Python e seu bloco no Diagrama de Classes UML.
  2. Commits Frequentes: Não deixem para subir código no último dia. Usem Git e branches de funcionalidade (feature/nova-tela).
  3. Qualidade desde o Dia 1: Escrevam testes simples com pytest para garantir que o que funciona hoje continue funcionando amanhã.
  4. Comunicação Clara: O software profissional é aquele que qualquer desenvolvedor consegue clonar, rodar e entender em menos de 5 minutos! 🚀🛡️

🗺️ JORNADA DO DESENVOLVEDOR BACKEND: DOS FUNDAMENTOS AO PROJETO INTEGRADOR

Bem-vindo(a) ao roteiro prático e estruturado de desenvolvimento web do curso de Gestão da Tecnologia da Informação (GTI - FATEC).

Este guia foi desenhado em 7 projetos progressivos e autoguiados, permitindo que você construa aplicações reais passo a passo, evoluindo do primeiro endpoint HTTP até uma arquitetura corporativa completa com banco de dados dual (SQLite no desenvolvimento local e PostgreSQL em produção). 🛡️🚀


🧭 O Mapa de Bordo da Sua Jornada

flowchart TD
    subgraph FASE1 ["🟢 FASE 1: FUNDAMENTOS & HTTP (Em Memória)"]
        P1["🏆 Projeto 1: API Simples (Flask + JSON)"]
    end

    subgraph FASE2 ["🟡 FASE 2: PERSISTÊNCIA BÁSICA & WEB SSR"]
        P2["🏆 Projeto 2: To-Do List (Flask + Jinja2 + SQLite + SQLAlchemy)"]
        P3["🏆 Projeto 3: Biblioteca Acadêmica (Relacionamento 1:N & JOINs)"]
    end

    subgraph FASE3 ["🟠 FASE 3: REGRAS DE NEGÓCIO & SEGURANÇA"]
        P4["🏆 Projeto 4: Vendas & Estoque (Relacionamento N:N & Transações ACID)"]
        P5["🏆 Projeto 5: Sistema com Autenticação (Bcrypt + Sessões Flask)"]
    end

    subgraph FASE4 ["🟣 FASE 4: TRANSIÇÃO DUAL & PRODUÇÃO"]
        P6["🏆 Projeto 6: Migrações com Alembic & PostgreSQL no Docker"]
        P7["🏆 Projeto 7: Projeto Integrador Oficial (10 Temas Setoriais)"]
    end

    FASE1 ==> FASE2 ==> FASE3 ==> FASE4

    style FASE1 fill:#e3f2fd,stroke:#1565c0
    style FASE2 fill:#e8f5e9,stroke:#2e7d32
    style FASE3 fill:#fff8e1,stroke:#f57f17
    style FASE4 fill:#f3e5f5,stroke:#7b1fa2

🏛️ A Filosofia do Banco Dual: SQLite (Dev) ➔ PostgreSQL (Prod)

[!IMPORTANT] A Regra de Ouro da Arquitetura de Dados: O SQLite não é um banco "descartável" ou "de brinquedo". Ele é o banco de dados oficial de desenvolvimento e aprendizado, pois permite executar aplicações completas em qualquer computador através de um único arquivo .db, com zero atrito de configuração e instalação.

Quando sua aplicação for para homologação ou produção, a camada do SQLAlchemy 2.0 permite trocar para o PostgreSQL 17 no Docker apenas alterando a variável DATABASE_URL no arquivo .env, sem reescrever uma única linha de lógica do sistema!

flowchart LR
    APP["🐍 Sua Aplicação Flask"] ==> ORM["🧱 SQLAlchemy 2.0 (Camada Abstrata)"]
    
    ORM -->|DATABASE_URL=sqlite:///dev.db| SQLITE["📁 SQLite Local (.db)<br/>• Setup instantâneo<br/>• Ideal para testes locais"]
    ORM -->|DATABASE_URL=postgresql://...| POSTGRES["🐘 PostgreSQL 17 (Docker)<br/>• Alta concorrência<br/>• Produção corporativa"]

    style APP fill:#e3f2fd,stroke:#1565c0
    style ORM fill:#fff8e1,stroke:#f57f17
    style SQLITE fill:#f1f8e9,stroke:#558b2f
    style POSTGRES fill:#e0f2f1,stroke:#00695c

🔨 PROJETO 1: API Simples em Memória (Flask + JSON)

  • Objetivo: Compreender o ciclo de Request/Response HTTP, verbos (GET, POST), parâmetros de rota e serialização JSON sem complexidade de banco de dados.
  • Conceitos: Flask, request.get_json(), jsonify(), Rotas e Métodos HTTP, Status Codes.
sequenceDiagram
    autonumber
    actor Cliente as 👤 Aluno / Navegador
    participant API as 🌐 Flask Router
    participant Schema as 📋 Validação de Entrada
    
    Cliente->>API: GET /api/v1/saudacao?nome=Carlos
    API->>Schema: Valida parâmetro 'nome'
    Schema-->>API: Parâmetro presente e válido
    API-->>Cliente: HTTP 200 OK {"mensagem": "Olá, Carlos!", "status": "online"}

💻 Código do Projeto 1 (projeto1_api_simples.py):

# projeto1_api_simples.py
import sys
from flask import Flask, request, jsonify

app = Flask(__name__)

# Banco de dados temporário em memória (Lista Python de dicionários)
banco_memoria = [
    {"id": 1, "titulo": "Estudar Engenharia de Software", "concluida": True},
    {"id": 2, "titulo": "Instalar VS Code e Python 3.11", "concluida": False}
]

@app.get("/tarefas")
def listar_todas():
    return jsonify(banco_memoria), 200

@app.post("/tarefas")
def criar_tarefa():
    dados = request.get_json()
    if not dados or "id" not in dados or "titulo" not in dados:
        return jsonify({"erro": "Campos 'id' e 'titulo' são obrigatórios"}), 400

    for t in banco_memoria:
        if t["id"] == dados["id"]:
            return jsonify({"erro": "ID já existente."}), 400

    nova_tarefa = {
        "id": dados["id"],
        "titulo": dados["titulo"],
        "concluida": bool(dados.get("concluida", False))
    }
    banco_memoria.append(nova_tarefa)
    return jsonify(nova_tarefa), 201

if __name__ == "__main__":
    if "--server" in sys.argv:
        print("🚀 Servidor Flask do Projeto 1 rodando em http://127.0.0.1:5000")
        app.run(port=5000, debug=True)
    else:
        print("--- Teste Automatizado com Flask test_client() ---")
        with app.test_client() as client:
            res_get = client.get("/tarefas")
            print(f"GET /tarefas [Status {res_get.status_code}]: {res_get.get_json()}")
            
            res_post = client.post("/tarefas", json={"id": 3, "titulo": "Aprender Flask"})
            print(f"POST /tarefas [Status {res_post.status_code}]: {res_post.get_json()}")

🔨 PROJETO 2: To-Do List SSR (Flask + Jinja2 + SQLite + SQLAlchemy)

  • Objetivo: Construir a primeira aplicação Web completa com formulários HTML, renderização Server-Side (SSR) e persistência em arquivo SQLite real.
  • Conceitos: Jinja2 Templates (render_template), DeclarativeBase, Mapped, mapped_column, SessionLocal.
flowchart LR
    BROWSER["🌐 Browser (Formulário HTML)"] -->|POST /tarefas/nova| FLASK["🌐 Flask"]
    FLASK -->|Insere Registro| ORM["🧱 SQLAlchemy 2.0"]
    ORM -->|Grava em Disco| SQLITE[("📁 todo.db (SQLite)")]
    FLASK -->|Renderiza HTML com Dados| JINJA["🎨 Jinja2 Template"]
    JINJA -->|HTTP 302 / Redireciona| BROWSER

🔨 PROJETO 3: Biblioteca Acadêmica (Relacionamento 1:N & JOINs)

  • Objetivo: Modelar e consultar entidades com integridade referencial e chaves estrangeiras.
  • Conceitos: ForeignKey, relationship(), cascade="all, delete-orphan", select().join().
erDiagram
    CATEGORIA ||--o{ LIVRO : "1 categoria possui N livros"
    CATEGORIA {
        int id_categoria PK
        string nome
    }
    LIVRO {
        int id_livro PK
        string titulo
        string isbn
        int id_categoria FK
    }

🔨 PROJETO 4: Vendas & Estoque (Relacionamento N:N & Transações ACID)

  • Objetivo: Implementar tabelas associativas com atributos próprios (preço histórico e quantidade) e controle rigoroso de transações bancárias/comerciais (commit e rollback).
  • Conceitos: Tabela Associativa N:N, Transaction boundary, session.rollback().
erDiagram
    PEDIDO ||--o{ ITEM_PEDIDO : "contém"
    PRODUTO ||--o{ ITEM_PEDIDO : "está presente em"
    PEDIDO {
        int id_pedido PK
        datetime data_hora
        string status
    }
    ITEM_PEDIDO {
        int id_pedido PK, FK
        int id_produto PK, FK
        int quantidade
        decimal preco_unitario
    }
    PRODUTO {
        int id_produto PK
        string nome
        decimal preco_atual
        int estoque_atual
    }

🔨 PROJETO 5: Sistema com Autenticação e Segurança

  • Objetivo: Proteger recursos com hash seguro de senhas, sessões de usuário e proteção contra vulnerabilidades OWASP (SQL Injection, XSS, CSRF).
  • Conceitos: Passlib (Bcrypt), OAuth2PasswordBearer, JWT (JSON Web Tokens), HTTPOnly Cookies.
sequenceDiagram
    autonumber
    actor Usuario as 👤 Usuário
    participant App as 🌐 Flask Auth
    participant DB as 🛢️ Banco de Dados (SQLite/Postgres)

    Usuario->>App: POST /login (email, senha_plana)
    App->>DB: Busca usuário por email
    DB-->>App: Retorna hash_senha do banco
    App->>App: Valida bcrypt.verify(senha_plana, hash_senha)
    App-->>Usuario: Retorna Sessão / Cookie HTTP-Only

🔨 PROJETO 6: Transição Dual-Database com Alembic e PostgreSQL

  • Objetivo: Migrar a aplicação do ambiente local para produção no Docker usando migrações de schema automatizadas sem perda de dados.
  • Conceitos: Alembic, alembic revision --autogenerate, alembic upgrade head, docker-compose.yml.
flowchart TD
    M["🧱 SQLAlchemy Models (Python)"] --> A["🔄 Alembic CLI"]
    A -->|Gera Script de Migração| V["📄 versions/001_initial.py"]
    V -->|Executa no SQLite Local| S[("📁 SQLite (dev.db)")]
    V -->|Executa no PostgreSQL Docker| P[("🐘 PostgreSQL (portal_db)")]

🏆 PROJETO 7: O Projeto Integrador Oficial (Scaffold Full-Stack)

  • Objetivo: Desenvolver o projeto temático do grupo (dentre os 10 Temas Setoriais) com o Scaffold Oficial Padronizado:
examples/template_projeto_integrador/
├── 🐍 main.py                      (Flask com rotas Web SSR e REST API)
├── 🛢️ database.py                  (Dual Database transparente com SessionLocal)
├── 🧱 models.py                    (SQLAlchemy 2.0 com Mapped Type Hints)
├── 🌐 templates/                   (Jinja2 Server-Side Rendering)
├── 🧪 tests/                       (Pytest + Flask test_client em memória)
├── 🐳 docker-compose.yml           (PostgreSQL 17 multi-container)
└── 📄 README.md                    (Guia de execução rápida)

🎯 Próximo Passo:

👉 Acesse a Galeria dos 10 Projetos Integradores para escolher o tema do seu grupo e iniciar o desenvolvimento!

🎯 ATIVIDADE 01: ESCOPO E PERSONAS

📖 Fundamentação Teórica

Para realizar este laboratório com sucesso, certifique-se de ter compreendido os conceitos apresentados no:
👉 CAPÍTULO 01: INTRODUÇÃO E NATUREZA DO SOFTWARE

Bem-vindo a mais uma etapa da sua jornada no curso de Gestão de TI / Desenvolvimento de Sistemas. Hoje vamos mergulhar em conceitos que conectam a teoria técnica diretamente com o padrão de excelência da indústria, definindo as bases de um projeto real de Engenharia de Software. 🛡️🧩


🎯 Objetivos de Aprendizagem do Laboratório

Ao final deste laboratório prático (estimativa: 4 horas presenciais / autoguiadas), você será capaz de:

  • Definir um Escopo claro de projeto, diferenciando o que o sistema fará (In) do que não fará (Out).
  • Criar Personas que representem usuários reais, mapeando suas dores e necessidades.
  • Documentar a ideia central de um software profissional de ponta a ponta.

🏢 O Cenário Prático (Seu Desafio)

Imagine que você é o Líder de Produto (Product Owner) da TecProExpress. A diretoria identificou que os motoristas terceirizados estão com dificuldades para encontrar as entregas do dia, e o SAC está lotado de clientes perguntando "Onde está meu pacote?". O desafio desta semana é:

"Você precisa documentar a proposta de um novo App de Rastreamento. Sua primeira missão é definir o Escopo do projeto (para não estourar o orçamento da empresa) e descrever exatamente quem são as Personas (O Motorista e O Cliente) que vão usar a ferramenta."


🧠 Fundamentos: A Teoria Traduzida

Na Engenharia de Software, não saímos programando no primeiro dia. Precisamos alinhar as expectativas com os stakeholders (pessoas interessadas).

Escopo e Personas

  • Escopo (Scope): É o limite do projeto. Se o cliente pede um carro, o escopo é o carro; um avião está fora do escopo.
  • Persona: É uma representação semi-fictícia do seu cliente ideal baseada em dados reais e suposições educadas. Ela tem nome, dores e objetivos.

📊 Visualizando a Lógica

Dica

Em projetos ágeis, um escopo mal definido leva ao "Scope Creep" (quando o projeto cresce descontroladamente e nunca termina). Delimite desde o dia 1!

```mermaid flowchart TD A[Dor do Cliente] --> B{Análise de Escopo} B -->|"Dentro (In)"| C[Desenvolver Funcionalidade] B -->|"Fora (Out)"| D[Registrar para o Futuro] ```

📖 Exemplo Guiado

Abaixo, veja como aplicar a teoria passo a passo no cenário da TecProExpress.

PassoAção de EngenhariaResultado Esperado
01Definir o ProblemaMotoristas e Clientes perdidos sem saber onde está o pacote.
02Mapear Escopo In/OutO que o App fará e o que ele vai deixar de fora.
03Criar PersonaFicha técnica do usuário alvo.

📊 Delimitação Visual do MVP (Escopo In/Out)

flowchart TD
    subgraph IN [Escopo Ativo - MVP]
        direction TB
        F1["Chave de Ignição: Login Seguro"]
        F2["Painel do Motorista: Lista de Entregas"]
        F3["Status da Carga (Pendente / Trânsito / Entregue)"]
    end
    subgraph OUT [Escopo Futuro - Fora]
        direction TB
        NF1["Gestão Financeira & Comissões"]
        NF2["Algoritmo Avançado de Inteligência Artificial para Rotas"]
    end
    IN -.-> OUT
    style IN fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px
    style OUT fill:#ffebee,stroke:#c62828,stroke-width:2px

🛠️ Exemplo de Documentação

# Projeto: Rastreio Express

**Escopo (In):**
- Login do Motorista.
- Atualização de status da entrega (Pendente, Em Trânsito, Entregue).

**Escopo (Out):**
- Pagamento de comissão do motorista (Fora do escopo atual).

**Persona:**
*Nome:* Roberto "Beto", 45 anos.
*Ocupação:* Motorista Terceirizado.
*Dor:* "O aplicativo antigo trava muito e eu perco tempo ligando pro cliente."
*Objetivo:* Um botão gigante de "Cheguei no local".

🔍 Detalhamento da Documentação:

  • Escopo (In): É o MVP (Mínimo Produto Viável).
  • Escopo (Out): Protege a equipe de desenvolvimento contra pedidos abusivos que atrasam a entrega.
  • Persona: Vai muito além de "homens de 40 a 50 anos". O Beto tem uma Dor e a nossa interface tem que curar essa dor.

🛠️ Prática Obrigatória 1: Definição do Escopo

Cenário: O projeto semestral que você vai desenvolver. Você pode escolher um sistema de Delivery, Gestão Escolar, PetShop ou criar o seu próprio.

  1. Dê um Título para o seu sistema.
  2. Escreva 1 parágrafo descrevendo "O Problema" que ele resolve.
  3. Liste 3 funcionalidades que estão Dentro (In) do escopo.
  4. Liste 2 funcionalidades que estão Fora (Out) do escopo.

🏁 Resultado Esperado (Para sua Referência)

Você deverá gerar um documento (Markdown ou PDF) contendo essas definições claras, sem jargões excessivos, provando que você sabe limitar o tamanho do seu projeto.


💻 Validação Prática & Resultado Esperado no Terminal

Para apoiar a análise automatizada do seu escopo, utilize o script de validação de requisitos e personas em Python:

# validar_escopo.py
import json

projeto = {
    "nome": "TecProExpress - App de Rastreamento",
    "mvp_in": ["Autenticação de Motorista", "Lista de Entregas do Dia", "Atualização de Status"],
    "mvp_out": ["Módulo Financeiro de Comissões", "Algoritmo de Roteirização por IA"],
    "personas": [
        {"nome": "Roberto 'Beto'", "papel": "Motorista Terceirizado", "dor": "App antigo trava e exige ligações"},
        {"nome": "Mariana Lima", "papel": "Cliente Destinatária", "dor": "Incerteza sobre horário de entrega"}
    ]
}

print("=" * 60)
print(f"🚀 VALIDADOR DE ESCOPO: {projeto['nome']}")
print("=" * 60)
print(f"✅ Itens no Escopo (IN): {len(projeto['mvp_in'])} funcionalidades")
for item in projeto["mvp_in"]:
    print(f"   • [IN]  {item}")

print(f"\n⛔ Itens Fora do Escopo (OUT): {len(projeto['mvp_out'])} funcionalidades")
for item in projeto["mvp_out"]:
    print(f"   • [OUT] {item}")

print(f"\n👥 Personas Mapeadas: {len(projeto['personas'])}")
for p in projeto["personas"]:
    print(f"   • {p['nome']} ({p['papel']}) -> Dor: \"{p['dor']}\"")
print("=" * 60)

🖥️ Saída Esperada no Terminal:

============================================================
🚀 VALIDADOR DE ESCOPO: TecProExpress - App de Rastreamento
============================================================
✅ Itens no Escopo (IN): 3 funcionalidades
   • [IN]  Autenticação de Motorista
   • [IN]  Lista de Entregas do Dia
   • [IN]  Atualização de Status

⛔ Itens Fora do Escopo (OUT): 2 funcionalidades
   • [OUT] Módulo Financeiro de Comissões
   • [OUT] Algoritmo de Roteirização por IA

👥 Personas Mapeadas: 2
   • Roberto 'Beto' (Motorista Terceirizado) -> Dor: "App antigo trava e exige ligações"
   • Mariana Lima (Cliente Destinatária) -> Dor: "Incerteza sobre horário de entrega"
============================================================

🌐 Exemplo de Payload JSON para Cadastro de Personas (Swagger /docs)

{
  "nome": "Roberto 'Beto'",
  "idade": 45,
  "ocupacao": "Motorista Terceirizado",
  "dores": [
    "Aplicativo antigo trava com frequência",
    "Perda de tempo ligando para o cliente para confirmar endereço"
  ],
  "objetivos": [
    "Confirmar entrega com apenas 1 toque na tela",
    "Visualizar rota consolidada do dia"
  ]
}

📤 Instruções de Entrega (Microsoft Teams)

Após validar as suas definições de engenharia:

  1. Salve o arquivo de documentação com o nome Atividade_01.md na pasta es-atv-01-escopo/ do seu repositório GitHub.
  2. Certifique-se de fazer o commit e push para o repositório público.
  3. Submeta o link do seu repositório no Microsoft Teams para avaliação do professor.

💡 Checkpoint de Lógica

Importante

Reflexão Profissional: Por que é tão difícil dizer "Não" para o cliente na fase de escopo? (Resposta: Porque queremos agradar, mas como engenheiros, nossa função é garantir a entrega. Cada "Sim" para uma ideia fora do escopo aumenta o risco de falha do projeto inteiro). 🧠🛡️

---

🔥 Desafio de Fixação (Opcional)

Nível: Expert 🏆

Se você terminou rápido, expanda o seu escopo criando a "Matriz de Priorização (MoSCoW)":

  • Must have (Deve ter)
  • Should have (Deveria ter)
  • Could have (Poderia ter)
  • Won't have (Não vai ter nesta versão)

Classifique as funcionalidades do seu projeto nessas 4 categorias e anexe ao documento final.


📊 Rubrica Formativa de Avaliação

Critério de Avaliação Insuficiente (0% - 40%) Regular (41% - 70%) Excelente (71% - 100%)
Delimitação de Escopo (In/Out) Não delimita o escopo ou confunde funcionalidades internas com externas. Define In/Out mas sem justificativas claras ou com itens ambíguos. Mapeia perfeitamente os limites do software com ao menos 3 itens In e 2 Out detalhados.
Mapeamento de Personas Personas genéricas ou incompletas, sem dores e objetivos. Cria 2 personas mas com perfil superficial. Personas ricas com nome, idade, ocupação, dores e objetivos realistas de mercado.
Entrega no GitHub Entrega fora do prazo, em pasta incorreta ou repositório privado. Entregue no repositório mas sem a estrutura Markdown adequada. Arquivo `Atividade_01.md` publicado em `es-atv-01-escopo/` com Markdown impecável.

🎯 ATIVIDADE 02: MODELOS DE PROCESSO DE SOFTWARE

📖 Fundamentação Teórica

Para realizar este laboratório com sucesso, certifique-se de ter compreendido os conceitos apresentados no:
👉 CAPÍTULO 02: MODELOS DE PROCESSO DE SOFTWARE

Bem-vindo a mais uma etapa da sua jornada no curso de Gestão de TI / Desenvolvimento de Sistemas. Hoje vamos mergulhar em conceitos que conectam a teoria técnica diretamente com o padrão de excelência da indústria, definindo as engrenagens de como a sua equipe vai trabalhar. 🛡️🧩


🎯 Objetivos de Aprendizagem do Laboratório

Ao final deste laboratório prático (estimativa: 4 horas presenciais / autoguiadas), você será capaz de:

  • Diferenciar modelos Preditivos (Cascata/Waterfall) de modelos Adaptativos (Ágeis, Scrum, Kanban).
  • Justificar tecnicamente a escolha de um processo de software com base no nível de incerteza do escopo.
  • Definir papéis gerenciais e operacionais (ex: Product Owner, Scrum Master, Dev Team).

🏢 O Cenário Prático (Seu Desafio)

A diretoria da TecProExpress aprovou o escopo do seu "App de Rastreamento" (Atividade 01). Agora, o Gerente de Projetos quer saber: "Como vocês vão construir isso?"

Um diretor mais antigo sugeriu o uso do Modelo Cascata, exigindo que a equipe entregue o software completo apenas daqui a 6 meses. O seu time de desenvolvimento, no entanto, acredita que o mercado é muito volátil e prefere o Scrum para fazer entregas mensais.

"Seu desafio é vestir a camisa de Scrum Master / Agile Coach, escolher oficialmente qual modelo o projeto da sua equipe (criado na Atividade 01) vai seguir, e apresentar uma justificativa técnica blindada contra argumentos antigos."


🧠 Fundamentos: A Teoria Traduzida

Não existe um "melhor" modelo de processo de software. Existe o modelo que melhor lida com o nível de risco do seu projeto.

Preditivo vs Adaptativo

  • Cascata (Preditivo): Você tem que saber 100% do que o cliente quer no dia 1. Se o cliente mudar de ideia no meio, o projeto falha. Ideal para: Softwares de aviões ou satélites.
  • Ágil/Scrum (Adaptativo): Você assume que o cliente não sabe o que quer e vai mudar de ideia. O software é feito em "fatias" (Sprints) e o cliente testa mensalmente. Ideal para: Apps, Sistemas Web, Startups.

📊 Visualizando a Lógica

Dica

Em projetos acadêmicos e startups, a incerteza é gigante. Escolher metodologias ágeis é quase um instinto de sobrevivência!

```mermaid flowchart LR subgraph Cascata A1["Requisitos"] --> B1["Design"] --> C1["Código"] --> D1["Testes"] --> E1["Morte se o cliente mudar de ideia"] end
subgraph Scrum
A2(("Sprint 1")) --> B2(("Sprint 2")) --> C2(("Sprint 3"))
end

---

## 📖 Exemplo Guiado
Abaixo, veja como estruturar a escolha de um processo.

| Passo | Ação de Engenharia | Resultado Esperado |
| :--- | :--- | :--- |
| 01 | Analisar a Incerteza | Alta incerteza (O motorista do App não sabe bem o que quer). |
| 02 | Escolher o Modelo | Scrum. |
| 03 | Definir Papéis | Quem é o Dono do Produto (PO)? Quem programa? |

#### 📊 Jornada de uma Tarefa no Kanban Ágil
```mermaid
flowchart LR
    B["Backlog (Ideias)"] --> TD["To Do (Sprint)"]
    TD --> IP["In Progress"]
    IP --> CR["Code Review"]
    CR --> QA["Testing/QA"]
    QA --> DN["Done (Produção)"]

    style B fill:#eceff1,stroke:#607d8b
    style TD fill:#e1f5fe,stroke:#0288d1
    style IP fill:#fffde7,stroke:#fbc02d
    style CR fill:#f3e5f5,stroke:#8e24aa
    style QA fill:#e8f5e9,stroke:#2e7d32
    style DN fill:#e0f2f1,stroke:#004d40,stroke-width:2px

🛠️ Exemplo de Documentação

# Processo Escolhido: Scrum

**Justificativa:** 
Optamos pelo Scrum porque o aplicativo de rastreio tem alta incerteza. Como não sabemos se os motoristas terceirizados vão se adaptar à interface, precisamos de feedback constante (Sprints de 2 semanas) para corrigir a rota antes do orçamento acabar, coisa que o Cascata não permitiria.

**Papéis no Scrum:**
* **Product Owner (PO):** O Gerente de Logística (Dono do processo).
* **Scrum Master:** O Líder Técnico do projeto.
* **Dev Team:** Os programadores e designers.

🔍 Detalhamento da Documentação:

  • A Justificativa não é "porque é mais moderno". É uma defesa baseada na Incerteza e no Custo do Erro.
  • O PO (Product Owner) não é um programador. É a pessoa que entende de logística e sabe o que dá dinheiro para a empresa.

🛠️ Prática Obrigatória 1: Escolha e Justificativa

Cenário: O projeto semestral da sua equipe.

  1. Declare qual modelo seu projeto vai seguir: Scrum, Kanban ou Cascata.
  2. Escreva um parágrafo de no mínimo 4 linhas justificando tecnicamente sua escolha, usando as palavras "Incerteza" e "Feedback".

🏁 Resultado Esperado (Para sua Referência)

Texto argumentativo profissional validando o modelo de trabalho escolhido.


💻 Simulador de Sprint & Burndown em Python

Para vivenciar a cadência de uma Sprint ágil, execute o simulador em Python abaixo:

# simulador_sprint.py
class SprintScrum:
    def __init__(self, nome_sprint: str, duracao_dias: int, total_story_points: int) -> None:
        self.nome_sprint = nome_sprint
        self.duracao_dias = duracao_dias
        self.pontos_restantes = total_story_points
        self.historico = [total_story_points]

    def queimar_pontos_dia(self, dia: int, pontos_entregues: int) -> None:
        self.pontos_restantes = max(0, self.pontos_restantes - pontos_entregues)
        self.historico.append(self.pontos_restantes)
        print(f"📅 Dia {dia:02d}: Entregues {pontos_entregues:02d} pts | Restam: {self.pontos_restantes:02d} pts")

if __name__ == "__main__":
    print("=" * 60)
    print("🏃 SIMULADOR DE BURNDOWN DE SPRINT (SCRUM) - TECPROEXPRESS")
    print("=" * 60)

    sprint = SprintScrum(nome_sprint="Sprint 01 - Autenticação & MVP", duracao_dias=10, total_story_points=34)
    print(f"Meta: {sprint.nome_sprint} | Duração: {sprint.duracao_dias} dias | Backlog: 34 pts\n")

    sprint.queimar_pontos_dia(dia=2, pontos_entregues=5)
    sprint.queimar_pontos_dia(dia=4, pontos_entregues=8)
    sprint.queimar_pontos_dia(dia=7, pontos_entregues=13)
    sprint.queimar_pontos_dia(dia=10, pontos_entregues=8)

    print("-" * 60)
    if sprint.pontos_restantes == 0:
        print("🎉 SPRINT CONCLUÍDA COM 100% DOS PONTOS ENTREGUES!")
    print("=" * 60)

🖥️ Saída Esperada no Terminal:

============================================================
🏃 SIMULADOR DE BURNDOWN DE SPRINT (SCRUM) - TECPROEXPRESS
============================================================
Meta: Sprint 01 - Autenticação & MVP | Duração: 10 dias | Backlog: 34 pts

📅 Dia 02: Entregues 05 pts | Restam: 29 pts
📅 Dia 04: Entregues 08 pts | Restam: 21 pts
📅 Dia 07: Entregues 13 pts | Restam: 08 pts
📅 Dia 10: Entregues 08 pts | Restam: 00 pts
------------------------------------------------------------
🎉 SPRINT CONCLUÍDA COM 100% DOS PONTOS ENTREGUES!
============================================================

🌐 Exemplo de Payload JSON para Criação de Sprint (Swagger /docs)

{
  "nome": "Sprint 01 - Autenticação e Gestão de Entregas",
  "data_inicio": "2026-03-01",
  "data_fim": "2026-03-14",
  "story_points_alvo": 34,
  "product_owner": "Carlos Eduardo (Gerente de Logística)",
  "scrum_master": "Mariana Silva (Líder Técnica)",
  "itens_backlog": [
    {"id": "US-01", "titulo": "Login do Motorista via Token JWT", "pontos": 5},
    {"id": "US-02", "titulo": "Lista de Pacotes Atribuídos", "pontos": 8},
    {"id": "US-03", "titulo": "Atualização de Status de Entrega", "pontos": 13}
  ]
}

📤 Instruções de Entrega (Microsoft Teams)

Após validar suas documentações técnicas:

  1. Salve o arquivo de documentação com o nome Atividade_02.md na pasta es-atv-02-processos/ do seu repositório GitHub.
  2. Certifique-se de fazer o commit e push para o repositório público.
  3. Submeta o link do seu repositório no Microsoft Teams para avaliação do professor.

💡 Checkpoint de Lógica

Importante

Reflexão Profissional: Se a sua equipe (Dev Team) atrasar uma entrega importante e reclamar que foi por causa de uma interrupção não programada, quem deveria intervir e protegê-los de distrações externas? O Product Owner ou o Scrum Master? (Resposta: O Scrum Master é o escudo do time contra o caos corporativo). 🧠🛡️

---

📊 Rubrica Formativa de Avaliação

Critério de Avaliação Insuficiente (0% - 40%) Regular (41% - 70%) Excelente (71% - 100%)
Escolha do Processo & Justificativa Escolhe uma metodologia sem justificativa ou com explicação incoerente. Justifica brevemente mas sem relacionar com incerteza ou feedback. Argumentação técnica sólida de no mínimo 4 linhas usando 'Incerteza' e 'Feedback'.
Divisão de Papéis e Cadência Confunde os papéis de PO, Scrum Master e Dev Team. Define os papéis mas sem definir cadência de Sprints/reuniões. Mapeia os papéis do time com clareza e estabelece cadência realista para o semestre.
Entrega no GitHub Entrega fora da pasta `es-atv-02-processos/` ou com arquivo ausente. Arquivo entregue mas com formatação Markdown inconsistente. Arquivo `Atividade_02.md` publicado no repositório com formatação limpa e validada.

🎯 ATIVIDADE 03: ENGENHARIA DE REQUISITOS (FR E NFR)

📖 Fundamentação Teórica

Para realizar este laboratório com sucesso, certifique-se de ter compreendido os conceitos apresentados no:
👉 CAPÍTULO 03: AS ATIVIDADES DO PROCESSO

Bem-vindo a mais uma etapa da sua jornada no curso de Gestão de TI / Desenvolvimento de Sistemas. Hoje vamos mergulhar em conceitos que conectam a teoria técnica diretamente com o padrão de excelência da indústria, aprendendo a redigir o contrato técnico que guiará todos os programadores. 🛡️🧩


🎯 Objetivos de Aprendizagem do Laboratório

Ao final deste laboratório prático (estimativa: 4 horas presenciais / autoguiadas), você será capaz de:

  • Diferenciar Requisitos Funcionais (RF) (O que o sistema faz) de Requisitos Não-Funcionais (RNF) (Como o sistema se comporta).
  • Redigir requisitos com alto rigor técnico utilizando o padrão de escrita: "O sistema deve...".
  • Aplicar uma matriz de priorização de requisitos (Essencial, Importante, Desejável).

🏢 O Cenário Prático (Seu Desafio)

Na TecProExpress, após o seu time decidir utilizar a metodologia ágil (Atividade 02), a diretoria começou a bombardear a equipe com ideias para o novo App de Rastreamento. O diretor de Vendas quer que o App tenha "um mapa bem bonito e rápido". O diretor de Logística quer "um botão de pânico para o motorista".

Ouvindo essas demandas soltas, os desenvolvedores não sabem o que programar primeiro, nem como medir se o mapa é "bonito e rápido" o suficiente.

"Seu desafio é atuar como Engenheiro de Requisitos. Você deve traduzir as falas confusas e abstratas da diretoria em Requisitos Funcionais e Não-Funcionais matemáticos, testáveis e priorizados, criando a lista mestra que o time de desenvolvimento vai usar para trabalhar."


🧠 Fundamentos: A Teoria Traduzida

A Engenharia de Requisitos é a arte de extrair a verdade. Se o requisito for mal escrito, o programador criará o código errado.

Funcional vs Não-Funcional

  • Funcional (RF): É uma ação. É um botão que você clica, uma tela que abre, um cálculo que é feito. (Ex: "O sistema deve calcular o frete").
  • Não-Funcional (RNF): É a infraestrutura, a performance, a segurança. Você não clica num RNF, você o sente. (Ex: "O cálculo do frete deve ser respondido em menos de 2 segundos").

📊 Visualizando a Lógica

Dica

Use sempre o padrão "O sistema deve [AÇÃO] quando [CONDIÇÃO]". Nunca use adjetivos (bonito, rápido, seguro), use métricas exatas!

```mermaid flowchart LR A["Fica abstrata do cliente:
'O app tem que ser seguro'"] --> B{"Filtro do Engenheiro"} B --> C["RNF01: O sistema deve exigir
uma senha de 8 caracteres
alfanuméricos no login."] ```

📖 Exemplo Guiado

Abaixo, veja como categorizar e escrever requisitos de forma profissional.

PassoFala do ClienteTradução da Engenharia
01"Quero cadastrar meus clientes."RF01: O sistema deve permitir o cadastro de clientes contendo Nome, CPF e Endereço.
02"O mapa tem que carregar rápido."RNF01: O mapa de rastreio deve ser renderizado em no máximo 3 segundos após o clique.
03"Tem que rodar no celular de todo mundo."RNF02: O sistema deve ser responsivo e suportar os sistemas Android 10+ e iOS 14+.

📊 Priorização MoSCoW dos Requisitos da TecProExpress

flowchart TD
    subgraph MUST [Must Have - Essencial]
        M1["RF01: Login do Motorista"]
        M2["RF02: Listar Entregas do Dia"]
    end
    subgraph SHOULD [Should Have - Importante]
        S1["RF03: Roteirização Automática"]
    end
    subgraph COULD [Could Have - Desejável]
        C1["RF04: Som ao atribuir carga"]
    end
    MUST --> SHOULD --> COULD

    style MUST fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px
    style SHOULD fill:#e1f5fe,stroke:#0288d1,stroke-width:2px
    style COULD fill:#fffde7,stroke:#fbc02d,stroke-width:2px

🛠️ Estrutura de Documentação de Requisitos

# Lista de Requisitos (App Rastreio Express)

## Requisitos Funcionais (RF)
| ID | Descrição | Prioridade |
| :--- | :--- | :--- |
| RF01 | O sistema deve permitir que o motorista faça login via CPF e Senha. | Essencial |
| RF02 | O sistema deve listar as entregas do dia em ordem de distância. | Importante |
| RF03 | O sistema deve emitir um som quando um novo pacote for atribuído. | Desejável |

## Requisitos Não-Funcionais (RNF)
| ID | Categoria | Descrição |
| :--- | :--- | :--- |
| RNF01 | Performance | A listagem de entregas deve carregar em < 2 segundos. |
| RNF02 | Segurança | As senhas devem ser criptografadas em BCrypt no banco. |

🔍 Detalhamento da Documentação:

  • A Prioridade salva projetos. Se o prazo apertar, o time corta os requisitos "Desejáveis" para garantir que o "Essencial" suba para produção.
  • Categorias de RNF: Podem ser de Performance, Segurança, Usabilidade ou Tecnológicas.

💻 Matriz de Rastreabilidade & Validador de Requisitos em Python

Para auditar se seus requisitos possuem métricas verificáveis e priorização definida, execute o validador:

# validador_requisitos.py
requisitos_funcionais = [
    {"id": "RF01", "desc": "Autenticação do motorista via CPF e Senha", "prioridade": "Essencial"},
    {"id": "RF02", "desc": "Listagem de entregas ordenadas por distância", "prioridade": "Importante"},
    {"id": "RF03", "desc": "Notificação sonoro-visual ao atribuir nova entrega", "prioridade": "Desejável"}
]

requisitos_nao_funcionais = [
    {"id": "RNF01", "categoria": "Performance", "criterio": "Tempo de resposta < 2.0s sob 500 req/s"},
    {"id": "RNF02", "categoria": "Segurança", "criterio": "Criptografia de senhas com algoritmo BCrypt (salt >= 12)"}
]

print("=" * 65)
print("📋 MATRIZ DE RASTREABILIDADE DE REQUISITOS - TECPROEXPRESS")
print("=" * 65)

print(f"🔹 Requisitos Funcionais ({len(requisitos_funcionais)} catalogados):")
for rf in requisitos_funcionais:
    print(f"   • [{rf['id']}] [{rf['prioridade']:<10}] {rf['desc']}")

print(f"\n🔸 Requisitos Não-Funcionais ({len(requisitos_nao_funcionais)} catalogados):")
for rnf in requisitos_nao_funcionais:
    print(f"   • [{rnf['id']}] [{rnf['categoria']:<11}] Critério: {rnf['criterio']}")

print("=" * 65)

🖥️ Saída Esperada no Terminal:

=================================================================
📋 MATRIZ DE RASTREABILIDADE DE REQUISITOS - TECPROEXPRESS
=================================================================
🔹 Requisitos Funcionais (3 catalogados):
   • [RF01] [Essencial ] Autenticação do motorista via CPF e Senha
   • [RF02] [Importante] Listagem de entregas ordenadas por distância
   • [RF03] [Desejável ] Notificação sonoro-visual ao atribuir nova entrega

🔸 Requisitos Não-Funcionais (2 catalogados):
   • [RNF01] [Performance] Critério: Tempo de resposta < 2.0s sob 500 req/s
   • [RNF02] [Segurança  ] Critério: Criptografia de senhas com algoritmo BCrypt (salt >= 12)
=================================================================

🌐 Exemplo de Payload JSON para Catálogo de Requisitos (Swagger /docs)

{
  "codigo": "RF01",
  "modulo": "Autenticação",
  "descricao": "O sistema deve permitir que o motorista faça login via CPF e Senha",
  "prioridade": "Essencial",
  "criterios_aceite": [
    "Retornar token JWT válido por 8 horas",
    "Bloquear conta temporariamente após 5 tentativas inválidas"
  ],
  "rastreabilidade_testes": [
    "tests/test_auth.py::test_login_sucesso",
    "tests/test_auth.py::test_login_bloqueio"
  ]
}

📤 Instruções de Entrega (Microsoft Teams)

Após validar suas documentações técnicas:

  1. Salve o arquivo de documentação com o nome Atividade_03.md na pasta es-atv-03-requisitos/ do seu repositório GitHub.
  2. Certifique-se de fazer o commit e push para o repositório público.
  3. Submeta o link do seu repositório no Microsoft Teams para avaliação do professor.

💡 Checkpoint de Lógica

Importante

Reflexão Profissional: Imagine que você escreveu o seguinte requisito: "RF04: O sistema deve ter uma tela de relatórios legais". Durante a entrega, o cliente recusou o sistema dizendo que o relatório não era "legal" o suficiente. Como engenheiro, onde esteve o erro e como você reescreveria esse requisito para se proteger de recusas futuras? 🧠🛡️

---

📊 Rubrica Formativa de Avaliação

Critério de Avaliação Insuficiente (0% - 40%) Regular (41% - 70%) Excelente (71% - 100%)
Requisitos Funcionais (RF) Menos de 10 RFs ou formulados de maneira ambígua. 10 RFs formulados mas com falhas na padronização 'O sistema deve'. Pelo menos 10 RFs perfeitamente numerados (RF01..RF10) com priorização (Essencial/Importante/Desejável).
Requisitos Não-Funcionais (RNF) Menos de 5 RNFs ou sem distinção entre qualidade/desempenho. 5 RNFs definidos com medição subjetiva. Pelo menos 5 RNFs numerados (RNF01..RNF05) abrangendo Performance, Segurança e Usabilidade.
Entrega no GitHub Entrega fora da pasta ou repositório inacessível. Arquivo entregue mas sem tabela Markdown adequada. `Atividade_03.md` em `es-atv-03-requisitos/` com Markdown impecável.

📝 ATIVIDADE 04: USER STORIES E BACKLOG

📖 Fundamentação Teórica

Para realizar este laboratório com sucesso, certifique-se de ter compreendido os conceitos apresentados no:
👉 CAPÍTULO 04: METODOLOGIAS ÁGEIS

Bem-vindo a mais uma etapa da sua jornada no curso de Gestão de TI / Desenvolvimento de Sistemas. Hoje vamos mergulhar em conceitos que conectam a teoria técnica diretamente com o padrão de excelência da indústria ágil, mudando o foco do "O Quê" para o "Por Quê". 🛡️🧩


🎯 Objetivos de Aprendizagem do Laboratório

Ao final deste laboratório prático (estimativa: 4 horas presenciais / autoguiadas), você será capaz de:

  • Converter Requisitos Funcionais tradicionais em User Stories (Histórias de Usuário).
  • Redigir histórias no padrão internacional da agilidade: "Como [Persona], Eu quero [Ação], Para que [Valor]".
  • Criar Critérios de Aceite testáveis para definir o que significa uma tarefa "Pronta" (DoD).
  • Priorizar e organizar um Product Backlog.

🏢 O Cenário Prático (Seu Desafio)

Na TecProExpress, você entregou a lista de Requisitos Funcionais (Atividade 03) para a equipe de programadores. Um dos requisitos era: "RF01: O sistema deve ter uma barra de busca na tela inicial".

Os programadores criaram uma barra de busca linda, mas ela só buscava clientes pelo CPF. Quando os motoristas tentaram usar, ficaram furiosos, pois eles queriam buscar o endereço do cliente, não o CPF! O requisito foi cumprido tecnicamente, mas falhou comercialmente por falta de empatia e contexto.

"Seu desafio como Product Owner / Analista é impedir que isso aconteça novamente. Você deve pegar seus requisitos frios e reescrevê-los como User Stories, colocando o desenvolvedor na 'pele' da Persona, e escrevendo Critérios de Aceite que não deixem margem para dúvidas sobre como a funcionalidade deve se comportar."


🧠 Fundamentos: A Teoria Traduzida

Na agilidade, nós não escrevemos ordens genéricas, escrevemos "histórias" curtas que justificam o esforço do programador.

O Padrão da User Story

Uma boa história deve caber em um post-it. Ela tem 3 partes:

  1. Como [Papel/Persona]: Quem precisa disso?
  2. Eu quero [Ação]: O que o sistema tem que fazer?
  3. Para que [Valor/Benefício]: Por que isso é importante? Qual a dor que resolve?

📊 Visualizando a Lógica

Dica

Uma User Story sem Critérios de Aceite é apenas um desejo abstrato. O Critério de Aceite é o "Contrato" que o desenvolvedor assina dizendo: "Se o sistema passar nestes testes, a história está pronta".

```mermaid flowchart TD A["RF01: O sistema deve ter uma busca"] --> B{"Lente Ágil"} B --> C["História:
COMO Motorista...
QUERO buscar por endereço...
PARA evitar usar outro GPS."] C --> D["Critério de Aceite 1:
A busca deve aceitar CEP."] C --> E["Critério de Aceite 2:
Deve exibir mapa ao clicar."] ```

📖 Exemplo Guiado

Abaixo, veja como aplicar a técnica de User Stories e Critérios de Aceite no contexto da TecProExpress.

PassoEstruturaAplicação Real
01Como (Quem)Como Dona Silvana (Dona da loja),
02Quero (Ação)Quero ver os pedidos atrasados destacados em vermelho na tela,
03Para (Valor)Para que eu saiba rapidamente quem eu preciso cobrar primeiro.

📊 Decomposição Ágil: Do Épico aos Critérios de Aceite

flowchart TD
    EP["Épico: Logística Last-Mile"] --> F1["Feature: Rastreamento em Tempo Real"]
    EP --> F2["Feature: Painel de Gestão Comercial"]
    F1 --> US1["US01: Atualização de Status da Carga"]
    F1 --> US2["US02: Geolocalização de Motoristas"]
    US1 --> CA1["Critério 1: Status 'Entregue' exige comprovante"]
    US1 --> CA2["Critério 2: Notificar cliente em < 5s"]

    style EP fill:#eceff1,stroke:#607d8b,stroke-width:2px
    style F1 fill:#e1f5fe,stroke:#0288d1,stroke-width:2px
    style F2 fill:#e1f5fe,stroke:#0288d1,stroke-width:2px
    style US1 fill:#fffde7,stroke:#fbc02d,stroke-width:2px
    style US2 fill:#fffde7,stroke:#fbc02d,stroke-width:2px

🛠️ Estrutura do Backlog

# Product Backlog (Priorizado)

## US01: Destaque de Pedidos Atrasados
**Como** Dona Silvana,
**Quero** ver os pedidos com prazo vencido destacados na cor vermelha,
**Para que** eu possa focar nas cobranças mais urgentes assim que abrir o sistema.

**Critérios de Aceite:**
1. Pedidos com data de vencimento < Data de Hoje devem ter a cor de fundo #FF0000.
2. A tela deve apresentar um botão no topo "Filtrar apenas atrasados".
3. Se não houver pedidos atrasados, exibir mensagem: "Tudo em dia!".

## US02: ...

🔍 Detalhamento da Documentação:

  • Ao ler a US01, o programador entende a dor da Dona Silvana. Ele não fará apenas um "IF", ele entenderá o valor do negócio.
  • Os Critérios de Aceite são o roteiro que o testador de QA (Quality Assurance) usará para aprovar ou reprovar a entrega da equipe.
  • O documento completo forma o Product Backlog, que deve sempre estar organizado de cima para baixo (do mais importante para o menos importante).

💻 Parser e Validador de User Stories (INVEST) em Python

Para auditar se suas histórias de usuário seguem as boas práticas ágeis (modelo INVEST), execute o analisador:

# validador_backlog.py
user_stories = [
    {
        "id": "US01",
        "persona": "Dona Silvana (Gerente Financeira)",
        "acao": "ver os pedidos com prazo vencido destacados em vermelho",
        "beneficio": "focar nas cobranças mais urgentes logo ao abrir o painel",
        "criterios": [
            "Pedidos com vencimento < Hoje exibem badge vermelho #FF0000",
            "Filtro rápido 'Apenas Atrasados' no topo da listagem"
        ],
        "story_points": 5
    },
    {
        "id": "US02",
        "persona": "Roberto Beto (Motorista)",
        "acao": "confirmar entrega com leitura de QR Code",
        "beneficio": "evitar preenchimento manual de formulários durante a rota",
        "criterios": [
            "Validação criptográfica do QR Code da nota fiscal",
            "Registro offline caso não haja conexão 4G imediata"
        ],
        "story_points": 8
    }
]

print("=" * 65)
print("📝 BACKLOG ÁGIL & AUDITORIA DE USER STORIES - TECPROEXPRESS")
print("=" * 65)

for us in user_stories:
    print(f"\n🏷️  [{us['id']}] - Estimativa: {us['story_points']} Story Points")
    print(f"   • Como: {us['persona']}")
    print(f"   • Quero: {us['acao']}")
    print(f"   • Para que: {us['beneficio']}")
    print("   • Critérios de Aceite (DoD):")
    for crit in us["criterios"]:
        print(f"     [✓] {crit}")

print("=" * 65)

🖥️ Saída Esperada no Terminal:

=================================================================
📝 BACKLOG ÁGIL & AUDITORIA DE USER STORIES - TECPROEXPRESS
=================================================================

🏷️  [US01] - Estimativa: 5 Story Points
   • Como: Dona Silvana (Gerente Financeira)
   • Quero: ver os pedidos com prazo vencido destacados em vermelho
   • Para que: focar nas cobranças mais urgentes logo ao abrir o painel
   • Critérios de Aceite (DoD):
     [✓] Pedidos com vencimento < Hoje exibem badge vermelho #FF0000
     [✓] Filtro rápido 'Apenas Atrasados' no topo da listagem

🏷️  [US02] - Estimativa: 8 Story Points
   • Como: Roberto Beto (Motorista)
   • Quero: confirmar entrega com leitura de QR Code
   • Para que: evitar preenchimento manual de formulários durante a rota
   • Critérios de Aceite (DoD):
     [✓] Validação criptográfica do QR Code da nota fiscal
     [✓] Registro offline caso não haja conexão 4G imediata
=================================================================

🌐 Exemplo de Payload JSON para Item do Backlog (Swagger /docs)

{
  "id_historia": "US01",
  "titulo": "Destaque Visual de Pedidos Vencidos",
  "persona": "Dona Silvana (Financeiro)",
  "quero": "visualizar pedidos atrasados com badge vermelho",
  "para_que": "priorizar cobranças e renegociações críticas",
  "story_points": 5,
  "sprint_alvo": 1,
  "criterios_aceite": [
    "Badge #FF0000 visível quando data_vencimento < data_atual",
    "Mensagem 'Tudo em dia!' quando lista atrasada estiver vazia"
  ]
}

📤 Instruções de Entrega (Microsoft Teams)

Após validar seu Backlog Ágil:

  1. Salve o arquivo de documentação com o nome Atividade_04.md na pasta es-atv-04-backlog/ do seu repositório GitHub.
  2. Certifique-se de fazer o commit e push para o repositório público.
  3. Submeta o link do seu repositório no Microsoft Teams para avaliação do professor.

💡 Checkpoint de Lógica

Importante

Reflexão Profissional: Imagine uma história que diz: "Como usuário, quero me cadastrar no sistema, para acessar minha conta". Há dois erros gravíssimos nessa história. O primeiro é escrever "Como usuário" (um termo vago que destrói o propósito da Persona). O segundo é o "Para acessar minha conta" (Isso não é o benefício real do cadastro, é apenas uma obviedade do sistema. O real benefício de se cadastrar na Netflix, por exemplo, é 'salvar meus filmes favoritos'). Nunca escreva obviedades, descubra o valor de negócio! 🧠🛡️

---

📊 Rubrica Formativa de Avaliação

Critério de Avaliação Insuficiente (0% - 40%) Regular (41% - 70%) Excelente (71% - 100%)
Formulação das User Stories Usa termos vagos ("Como usuário") ou omite o valor de negócio (Para). Cria 5 User Stories no padrão Como/Quero/Para mas com benefícios óbvios. 5 User Stories perfeitamente vinculadas às Personas com valor de negócio real.
Critérios de Aceite Sem critérios de aceite ou com critérios genéricos e não testáveis. Apenas 1 critério de aceite por história. Pelo menos 2 critérios de aceite objetivos e testáveis abaixo de cada User Story.
Entrega no GitHub Entrega fora da pasta `es-atv-04-backlog/`. Arquivo entregue mas sem ordem de priorização de Backlog. `Atividade_04.md` entregue com Backlog priorizado de cima para baixo.

🎭 ATIVIDADE 05: CASOS DE USO (UML)

📖 Fundamentação Teórica

Para realizar este laboratório com sucesso, certifique-se de ter compreendido os conceitos apresentados no:
👉 CAPÍTULO 05: FUNDAMENTOS DE REQUISITOS

Bem-vindo a mais uma etapa da sua jornada no curso de Gestão de TI / Desenvolvimento de Sistemas. Hoje vamos mergulhar em conceitos que conectam a teoria técnica diretamente com o padrão de excelência da indústria visual, transformando requisitos em diagramas universais. 🛡️🧩


🎯 Objetivos de Aprendizagem do Laboratório

Ao final deste laboratório prático (estimativa: 4 horas presenciais / autoguiadas), você será capaz de:

  • Identificar Atores (pessoas ou sistemas externos que interagem com o seu software).
  • Mapear Casos de Uso (funcionalidades entregues ao ator).
  • Aplicar os relacionamentos de dependência <<include>> e <<extend>> corretamente, segundo as normas UML.
  • Descrever textualmente o "Caminho Feliz" de um Caso de Uso.

🏢 O Cenário Prático (Seu Desafio)

Na TecProExpress, você escreveu um Backlog incrível (Atividade 04). Mas quando levou os Post-its para a diretoria, o CEO disse: "Não consigo ler isso, são muitos textos soltos. Quero um desenho que me mostre de forma macro o que o Motorista pode fazer e o que o Administrador pode fazer".

"Seu desafio é desenhar o primeiro diagrama oficial da Engenharia de Software: O Diagrama de Casos de Uso. Ele será a 'Planta Baixa' do seu aplicativo, estabelecendo a Fronteira do Sistema (o que está dentro e o que está fora)."


🧠 Fundamentos: A Teoria Traduzida

A UML (Linguagem de Modelagem Unificada) é o idioma mundial da engenharia. Um desenvolvedor japonês que não fala português consegue entender seu projeto se você desenhar em UML.

Elementos Chaves

  • Ator (Boneco de Palito): É quem provoca o sistema. (Ex: O Cliente, O Motorista, ou até a API do Banco Central).
  • Caso de Uso (Oval): Sempre no infinitivo (Ex: Atualizar Rastreio, Realizar Login).
  • Fronteira (Retângulo): A caixa do seu software. Atores ficam de fora, Casos de Uso ficam dentro.

📊 Visualizando a Lógica

Dica

A grande dúvida: Include vs Extend. <<include>>: É OBRIGATÓRIO. (Ex: Para "Fazer Pix", eu incluo obrigatoriamente a "Validação de Saldo"). <<extend>>: É OPCIONAL. (Ex: Ao "Comprar Produto", eu estendo a opção de "Aplicar Cupom", pois nem todo mundo tem cupom).

```mermaid flowchart LR A(("Ator")) --> B(["Caso de Uso Principal"]) B -. "<< include >>" .-> C(["Passo Obrigatório"]) D(["Passo Opcional"]) -. "<< extend >>" .-> B ```

📖 Exemplo Guiado

Abaixo, veja como estruturar o cenário do aplicativo de logística da TecProExpress.

PassoAção de EngenhariaResultado Esperado
01Listar AtoresMotorista e SAC (Suporte).
02Listar Ações (Ovais)Login, Reportar Atraso, Consultar Endereço.
03Validar RegrasTodo Login inclui Validar Senha.

📊 Relações de Include e Extend da TecProExpress

flowchart LR
    M(("Motorista")) --> UC1(["Finalizar Entrega"])
    UC1 -. "<< include >>" .-> UC2(["Registrar Geolocalização"])
    UC3(["Registrar Foto do Comprovante"]) -. "<< extend >> (Se falhar GPS)" .-> UC1

    style M fill:#eceff1,stroke:#607d8b,stroke-width:2px
    style UC1 fill:#e1f5fe,stroke:#0288d1,stroke-width:2px
    style UC2 fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px
    style UC3 fill:#fffde7,stroke:#fbc02d,stroke-width:2px

Diagrama de Casos de Uso TecProExpress (UML)

🛠️ Especificação Textual do Caso de Uso

Todo oval desenhado precisa de um "Manual de Instruções" por trás dele. Exemplo do Caso "Reportar Atraso":

  • Ator Principal: Motorista.
  • Pré-condição: O motorista deve estar logado no aplicativo.
  • Fluxo Principal (Caminho Feliz):
    1. O motorista seleciona a entrega em andamento.
    2. Clica no botão "Reportar Atraso".
    3. O sistema pede o motivo (Trânsito, Veículo Quebrado, etc).
    4. O motorista seleciona o motivo e confirma.
    5. O sistema atualiza o status e notifica o SAC.
  • Pós-condição: O status da entrega muda para "Atrasado".

🔍 Detalhamento da Documentação:

  • O Caminho Feliz é a rota perfeita, sem erros. É essencial escrever isso para que o testador de software saiba o que esperar quando tudo dá certo.

🛠️ Prática Obrigatória 1: O Diagrama

Cenário: O projeto semestral da sua equipe.

  1. Acesse o draw.io (ou Lucidchart, Astah) e crie um Diagrama de Casos de Uso.
  2. Desenhe a Fronteira do Sistema (O retângulo com o nome do App no topo).
  3. Insira pelo menos 2 Atores diferentes (do lado de fora).
  4. Insira pelo menos 6 Casos de Uso (dentro da fronteira), derivados das suas User Stories.
  5. Crie pelo menos uma relação de <<include>> e uma de <<extend>>.

🏁 Resultado Esperado (Para sua Referência)

Um arquivo de imagem (PNG/JPG) com o diagrama organizado, onde as linhas não se cruzam loucamente, provando que você sabe abstrair o macro do sistema.


💻 Simulador de Casos de Uso (Fluxo Principal & Exceções) em Python

Para validar a execução programática do caso de uso "Atualizar Status de Entrega" (incluindo a extensão de atraso), execute o script:

# simulador_caso_uso.py
from datetime import datetime, timezone

class EntregaService:
    def __init__(self, id_entrega: str) -> None:
        self.id_entrega = id_entrega
        self.status = "EM_TRANSITO"
        self.historico = []

    def executar_fluxo_principal_concluir(self) -> None:
        """UC01 - Fluxo Principal: Confirmar Entrega."""
        self.status = "ENTREGUE"
        self.historico.append({"evento": "CONFIRMACAO_ENTREGA", "data": datetime.now(timezone.utc).isoformat()})
        print(f"✅ [UC01 - Fluxo Principal] Entrega {self.id_entrega} concluída com sucesso!")

    def executar_extensao_reportar_atraso(self, motivo: str) -> None:
        """UC01 <<extend>> UC02: Reportar Atraso na Rota."""
        self.status = "ATRASADO"
        self.historico.append({"evento": "REPORTE_ATRASO", "motivo": motivo, "data": datetime.now(timezone.utc).isoformat()})
        print(f"⚠️  [UC02 - Extend] Entrega {self.id_entrega} marcada como ATRASADA. Motivo: '{motivo}'. SAC notificado!")

if __name__ == "__main__":
    print("=" * 65)
    print("🎭 SIMULADOR DE CASOS DE USO & FLUXOS UML - TECPROEXPRESS")
    print("=" * 65)

    entrega1 = EntregaService("PKG-9901")
    entrega2 = EntregaService("PKG-9902")

    # 1. Executando Caminho Feliz (Fluxo Principal)
    entrega1.executar_fluxo_principal_concluir()

    # 2. Executando Fluxo Alternativo/Extensão
    entrega2.executar_extensao_reportar_atraso("Pneu furado na Rodovia Anhanguera")

    print("-" * 65)
    print(f"Status Final PKG-9901: {entrega1.status}")
    print(f"Status Final PKG-9902: {entrega2.status}")
    print("=" * 65)

🖥️ Saída Esperada no Terminal:

=================================================================
🎭 SIMULADOR DE CASOS DE USO & FLUXOS UML - TECPROEXPRESS
=================================================================
✅ [UC01 - Fluxo Principal] Entrega PKG-9901 concluída com sucesso!
⚠️  [UC02 - Extend] Entrega PKG-9902 marcada como ATRASADA. Motivo: 'Pneu furado na Rodovia Anhanguera'. SAC notificado!
-----------------------------------------------------------------
Status Final PKG-9901: ENTREGUE
Status Final PKG-9902: ATRASADO
=================================================================

🌐 Exemplo de Payload JSON para Extensão de Caso de Uso (Swagger /docs)

{
  "id_entrega": "PKG-9902",
  "acao": "REPORTAR_ATRASO",
  "motivo": "Pneu furado na Rodovia Anhanguera",
  "geolocalizacao": {
    "latitude": -23.55052,
    "longitude": -46.633308
  },
  "notificar_cliente_sms": true
}

📤 Instruções de Entrega (Microsoft Teams)

Após validar suas documentações técnicas:

  1. Salve o arquivo de documentação com o nome Atividade_05.md e a imagem do seu diagrama com o nome Atividade_05.png na pasta es-atv-05-casos-uso/ do seu repositório GitHub.
  2. Certifique-se de fazer o commit e push para o repositório público.
  3. Submeta o link do seu repositório no Microsoft Teams para avaliação do professor.

💡 Checkpoint de Lógica

Importante

Reflexão Profissional: Um erro crasso é usar o Caso de Uso para modelar a Interface do Usuário. Exemplo de Caso Errado: "Clicar no Botão Verde". O Caso de Uso descreve o Negócio, não o Mouse. A forma correta seria "Confirmar Pagamento". A interface pode mudar de botão verde para comando de voz amanhã, mas o negócio (Confirmar Pagamento) permanece o mesmo. 🧠🛡️

---

📊 Rubrica Formativa de Avaliação

Critério de Avaliação Insuficiente (0% - 40%) Regular (41% - 70%) Excelente (71% - 100%)
Diagrama de Casos de Uso (UML) Diagrama confuso com atores sem associação ou linhas cruzadas sem lógica. Modelagem aceitável mas misturando ações de interface com regras de negócio. Diagrama limpo com atores bem definidos e casos de uso no nível correto de abstração de negócio.
Especificação Textual (Caminho Feliz) Especificação ausente ou apenas uma frase descritiva. Especificação passo a passo mas omitindo pré-condições e pós-condições. Especificação técnica detalhada com Ator, Pré-condição, Fluxo Principal numerado e Pós-condição.
Entrega no GitHub Entrega fora da pasta `es-atv-05-casos-uso/`. Entrega apenas o texto sem a imagem `.png` do diagrama. Submete `Atividade_05.md` e `Atividade_05.png` perfeitamente estruturados no repositório.

🖼️ ATIVIDADE 06: PROTOTIPAGEM E WIREFRAMES

📖 Fundamentação Teórica

Para realizar este laboratório com sucesso, certifique-se de ter compreendido os conceitos apresentados no:
👉 CAPÍTULO 06: ELICITAÇÃO E LEVANTAMENTO

Bem-vindo a mais uma etapa da sua jornada no curso de Gestão de TI / Desenvolvimento de Sistemas. Hoje vamos mergulhar em conceitos que conectam a teoria técnica diretamente com o padrão de excelência visual, trazendo a interface do software para o "mundo real" antes mesmo de escrever a primeira linha de código. 🛡️🧩


🎯 Objetivos de Aprendizagem do Laboratório

Ao final deste laboratório prático (estimativa: 4 horas presenciais / autoguiadas), você será capaz de:

  • Criar Wireframes (protótipos de baixa fidelidade) focados na funcionalidade, não na estética.
  • Projetar a Jornada do Usuário (o fluxo lógico de cliques entre as telas).
  • Validar se os requisitos e casos de uso das aulas anteriores estão visíveis e acessíveis na interface (Usabilidade).

🏢 O Cenário Prático (Seu Desafio)

Na TecProExpress, você apresentou o Diagrama de Casos de Uso (Atividade 05). O Diretor de Marketing gostou, mas comentou: "Legal, mas como isso vai aparecer na mão do motorista? Vai ser fácil dele clicar com a mão suja ou usando luvas? Onde fica o botão de emergência?".

"Seu desafio como Designer de Interface (UI/UX) é desenhar o 'esqueleto' (Wireframe) das telas principais. Você não precisa de cores bonitas agora, você precisa provar que o fluxo de navegação é intuitivo o suficiente para que um motorista com pressa consiga usar o App sem treinamento."


🧠 Fundamentos: A Teoria Traduzida

A prototipagem é a ferramenta mais barata da engenharia. É muito mais fácil apagar um desenho do que refazer um banco de dados.

Wireframe vs Protótipo de Alta Fidelidade

  • Wireframe (Esqueleto): Preto e branco, focado no lugar dos botões e campos de texto. Resolve a Usabilidade.
  • Alta Fidelidade: Cores, logos e imagens reais. Resolve a Estética.

Nota

Os dois exemplos abaixo são referências de Alta Fidelidade (dois domínios diferentes: cinema e clínica), só para você visualizar o destino final. Sua entrega nesta atividade é o Wireframe (preto e branco), não este nível de acabamento.

Exemplo de Alta Fidelidade: dashboard de um sistema de cinema

Exemplo de Alta Fidelidade: dashboard de um sistema de clínica

Jornada do Usuário

É o mapa de cliques. Se para fazer um rastreio o motorista precisa clicar em 10 telas diferentes, a sua jornada está ruim. O objetivo é o "Menor Caminho Crítico".

📊 Visualizando a Lógica

Dica

Ao desenhar, siga a regra de ouro: "Não me faça pensar". Se o usuário precisar ler um manual para saber onde está o botão de busca, a interface falhou.

```mermaid flowchart LR A["Tela de Login"] --> B["Dashboard Principal"] B --> C["Detalhes da Entrega"] C --> D["Confirmação / Assinatura"] ```

📖 Exemplo Guiado

Abaixo, veja como planejar as telas para o aplicativo da TecProExpress.

PassoAção de DesignResultado Esperado
01Escolher TelasLogin, Lista de Entregas e Câmera (para comprovante).
02Desenhar EsqueletoOnde fica o menu? Onde fica o botão "Entregue"?
03Validar FluxoAo clicar em 'Finalizar', para onde o usuário vai?

📊 Fluxo de Transição e Navegação de Telas (TecProExpress)

flowchart TD
    L["Tela de Login (Entrada de Credenciais)"] -->|Autenticação bem-sucedida| D["Dashboard: Lista de Pedidos"]
    L -->|Falha de Login| E["Alerta: Credenciais Inválidas"]
    E -->|Tentar Novamente| L
    D -->|Selecionar Entrega| DET["Detalhes da Carga"]
    DET -->|Voltar| D
    DET -->|Iniciar Rota| MAP["Navegação no Mapa (GPS)"]
    MAP -->|Chegar ao Destino| CONF["Confirmar Recebimento (Câmera)"]
    CONF -->|Upload de Foto| SUC["Mensagem de Sucesso"]
    SUC --> D

    style L fill:#e1f5fe,stroke:#0288d1
    style D fill:#e8f5e9,stroke:#2e7d32
    style CONF fill:#fffde7,stroke:#fbc02d
    style E fill:#ffebee,stroke:#c62828

🛠️ Estrutura de Validação da Tela

Para cada tela desenhada, responda:

  1. Qual o objetivo desta tela? (Ex: "Fazer o login").
  2. Qual o elemento principal (Call to Action)? (Ex: "Botão Entrar").
  3. Quais requisitos da Atividade 03 estão aqui? (Ex: "RF01 - Login").

🔍 Detalhamento do Processo:

  • Note que a prototipagem expõe falhas nos requisitos. Se você desenhou um botão de "Esqueci minha senha", mas não escreveu esse requisito na Atividade 03, você acaba de descobrir uma Lacuna de Requisitos. Parabéns, você está fazendo engenharia de verdade!

🛠️ Prática Obrigatória 1: Os Wireframes

Cenário: O projeto semestral da sua equipe.

  1. Defina as 3 telas principais do seu sistema.
  2. Utilizando uma ferramenta (Figma, Balsamiq, Draw.io ou papel e caneta), desenhe os wireframes (esqueletos) dessas 3 telas.
    • Foque na disposição dos elementos.
    • Use retângulos para botões, "X" em caixas para imagens.
    • Proibido usar cores ou fotos nesta etapa.

🏁 Resultado Esperado (Para sua Referência)

Imagens dos 3 esqueletos (PNG/JPG) mostrando a estrutura lógica da interface.


💻 API Mockup para Validação de Telas (Backend UI) em Python (Flask)

Para validar a integridade dos dados consumidos pelos seus protótipos de interface, instale o Flask e utilize o servidor de mockup abaixo:

pip install flask
# mock_ui_server.py
import sys
from flask import Flask, jsonify

app = Flask(__name__)

DADOS_ENTREGAS = [
    {"id": "ENT-01", "destinatario": "Farmácia Central", "endereco": "Av. Paulista, 1000", "status": "PENDENTE"},
    {"id": "ENT-02", "destinatario": "Mercado Modelo", "endereco": "Rua Augusta, 450", "status": "EM_TRANSITO"}
]

@app.get("/api/v1/entregas/painel")
def obter_dados_tela_entregas():
    """Fornece dados mockados para o Wireframe da Tela 02 (Lista de Entregas)."""
    return jsonify(DADOS_ENTREGAS), 200

if __name__ == "__main__":
    if "--server" in sys.argv:
        print("🚀 Servidor de Mockup de UI rodando em http://127.0.0.1:5000/api/v1/entregas/painel")
        app.run(port=5000, debug=True)
    else:
        print("--- Teste Automatizado do Endpoint de Mockup UI (Flask test_client) ---")
        with app.test_client() as client:
            resposta = client.get("/api/v1/entregas/painel")
            print(f"Status HTTP: {resposta.status_code}")
            print(f"Dados retornados: {resposta.get_json()}")

🚀 Como Executar

Para validar os dados instantaneamente via terminal:

python mock_ui_server.py

Para subir o servidor HTTP local na porta 5000:

python mock_ui_server.py --server

🖥️ Saída Esperada no Terminal:

--- Teste Automatizado do Endpoint de Mockup UI (Flask test_client) ---
Status HTTP: 200
Dados retornados: [{'destinatario': 'Farmácia Central', 'endereco': 'Av. Paulista, 1000', 'id': 'ENT-01', 'status': 'PENDENTE'}, {'destinatario': 'Mercado Modelo', 'endereco': 'Rua Augusta, 450', 'id': 'ENT-02', 'status': 'EM_TRANSITO'}]

🌐 Requisições cURL e Payloads JSON para Prototipagem (Swagger /docs)

🔹 1. Requisição cURL de Teste da Tela:

curl -X GET "http://127.0.0.1:5000/api/v1/entregas/painel" \
     -H "Accept: application/json"

🔹 2. Resposta JSON Recebida pela Interface:

[
  {
    "id": "ENT-01",
    "destinatario": "Farmácia Central",
    "endereco": "Av. Paulista, 1000",
    "status": "PENDENTE"
  },
  {
    "id": "ENT-02",
    "destinatario": "Mercado Modelo",
    "endereco": "Rua Augusta, 450",
    "status": "EM_TRANSITO"
  }
]

📤 Instruções de Entrega (Microsoft Teams)

Após validar seus desenhos:

  1. Salve o arquivo de documentação com o nome Atividade_06.md e as imagens dos seus wireframes na pasta es-atv-06-prototipagem/ do seu repositório GitHub.
  2. Certifique-se de fazer o commit e push para o repositório público.
  3. Submeta o link do seu repositório no Microsoft Teams para avaliação do professor.

💡 Checkpoint de Lógica

Importante

Reflexão Profissional: Por que designers experientes fogem de cores e logotipos na primeira reunião de prototipagem com o cliente? (Resposta: Porque se houver uma cor bonita, o cliente vai focar em "Gostei desse azul" e vai esquecer de checar se o botão de "Confirmar Pagamento" realmente existe ou se está no lugar certo). Foque na função primeiro, na forma depois. 🧠🛡️

---

📊 Rubrica Formativa de Avaliação

Critério de Avaliação Insuficiente (0% - 40%) Regular (41% - 70%) Excelente (71% - 100%)
Construção dos Wireframes (Esqueletos) Menos de 3 telas ou desenhados com cores/elementos visuais finais fora do padrão wireframe. Desenha as 3 telas em baixa fidelidade mas com layout confuso. 3 wireframes de baixa fidelidade impecáveis, focando exclusivamente na estrutura lógica e componentes.
Fluxo de Navegação (Storyboarding) Telas isoladas sem indicação de navegação. Indica fluxo de ida mas esquece de permitir retorno do usuário. Mapeia perfeitamente a navegação entre telas com setas e ações claras do usuário.
Entrega no GitHub Entrega fora da pasta `es-atv-06-prototipagem/`. Submete apenas o texto sem as imagens dos wireframes. `Atividade_06.md` e imagens publicadas no GitHub com estrutura completa.

📐 ATIVIDADE 07: DIAGRAMA DE CLASSES (UML)

📖 Fundamentação Teórica

Para realizar este laboratório com sucesso, certifique-se de ter compreendido os conceitos apresentados no:
👉 CAPÍTULO 07: ESPECIFICAÇÃO DE REQUISITOS (ERS)

Bem-vindo a mais uma etapa da sua jornada no curso de Gestão de TI / Desenvolvimento de Sistemas. Hoje vamos mergulhar em conceitos que conectam a teoria técnica diretamente com o padrão de excelência da arquitetura de software, criando o guia mestre para a implementação do código. 🛡️🧩


🎯 Objetivos de Aprendizagem do Laboratório

Ao final deste laboratório prático (estimativa: 4 horas presenciais / autoguiadas), você será capaz de:

  • Definir Atributos (o que o objeto guarda) e Métodos (o que o objeto faz).
  • Aplicar modificadores de Visibilidade (Público +, Privado -, Protegido #) baseados no princípio do Encapsulamento.
  • Modelar Associações e multiplicidade (1:1, 1:N, N:N).
  • Identificar oportunidades de Herança para otimização de código.

🏢 O Cenário Prático (Seu Desafio)

Na TecProExpress, os protótipos (Atividade 06) foram aprovados! Agora, o time de desenvolvimento backend (Java/C#/Python) precisa saber: "Como os dados serão organizados no código? Quais são as classes e como elas se conectam?".

Se você apenas disser "Cria uma tabela de motorista", o programador pode esquecer de validar se o motorista tem uma CNH válida ou se ele pode ter vários veículos associados a ele.

"Seu desafio é atuar como Arquiteto de Software. Você deve desenhar o Diagrama de Classes, que é o mapa estrutural que diz ao programador exatamente quais variáveis e funções ele deve criar para que o sistema suporte toda a lógica de logística da TecProExpress."


🧠 Fundamentos: A Teoria Traduzida

O Diagrama de Classes mostra a "foto" parada do sistema. Se o software fosse um prédio, o Diagrama de Classes seria a planta estrutural das paredes e colunas.

Anatomia da Classe

  1. Nome: Sempre substantivo e no singular (Ex: Motorista).
  2. Atributos (Dados): O que ele sabe? (Ex: - cnh: String).
  3. Métodos (Ações): O que ele faz? (Ex: + validarDocumento()).

📊 Visualizando a Lógica

Dica

Visibilidade: Por padrão, atributos são sempre Privados (-). Isso protege os dados contra alterações acidentais externas. Somente métodos de acesso (Getters/Setters) ou ações explícitas devem ser Públicos (+).

```mermaid classDiagram class Motorista { -String nome -String cnh +validarCnh() bool } class Veiculo { -String placa -String modelo } Motorista "1" -- "*" Veiculo : dirige ```

📖 Exemplo Guiado

Abaixo, veja como mapear as entidades da TecProExpress para classes UML.

PassoPergunta do ArquitetoTradução UML
01O que um 'Pacote' tem?Atributos: -id, -peso, -status.
02O que o 'Pacote' faz?Métodos: +calcularFrete(), +atualizarStatus().
03Como ele se conecta?Um Motorista leva Muitos Pacotes (1:N).

📊 Graus de Acoplamento e Relacionamentos entre Classes

flowchart LR
    A["Associação Simples<br/>(Uso comum - Acoplamento fraco)"] -->|Motorista - Veiculo| B["Ex: Um motorista dirige um veículo"]
    C["Agregação<br/>(Todo/Parte - Partes vivem sem o todo)"] -->|Pedido - Cliente| D["Ex: Cliente possui pedidos, mas cliente existe sem o pedido"]
    E["Composição<br/>(Todo/Parte rígido - Partes morrem com o todo)"] -->|Pedido - ItemPedido| F["Ex: Pedido possui itens de pedido, se o pedido morre, itens morrem"]

    style A fill:#eceff1,stroke:#607d8b
    style C fill:#e1f5fe,stroke:#0288d1
    style E fill:#ffebee,stroke:#c62828

Diagrama de Classes TecProExpress (UML)

🛠️ Estrutura de Atributo e Método

  • Atributo: - salario: Double = 1412.00
    • -: Privado.
    • salario: Nome.
    • Double: Tipo do dado.
    • = 1412.00: Valor padrão.
  • Método: + calcularIdade(dataNasc: Date): Int
    • +: Público.
    • dataNasc: Date: Parâmetro de entrada.
    • : Int: O que o método devolve (Tipo de retorno).

🔍 Detalhamento do Processo:

  • Note que no Diagrama de Classes não colocamos "clicar no botão". Colocamos a lógica do objeto. Se o botão na tela chama uma função de salvar, o método salvar() deve estar na Classe.

🛠️ Prática Obrigatória 1: O Diagrama de Classes

Cenário: O projeto semestral da sua equipe.

  1. Identifique as 5 principais entidades do seu sistema (ex: Usuário, Produto, Pedido, Categoria, Endereço).
  2. Utilizando uma ferramenta UML (Astah, Lucidchart ou Draw.io), desenhe o diagrama.
  3. Cada classe deve conter pelo menos 3 atributos e 2 métodos.
  4. Defina as multiplicidades corretamente (ex: Um cliente tem 0 ou N pedidos).

🏁 Resultado Esperado (Para sua Referência)

Uma imagem (PNG/JPG) do diagrama contendo as 5 classes conectadas, com tipos de dados e visibilidade (+ e -) aplicados.


💻 Implementação Orientada a Objetos em Python

Para materializar o seu Diagrama de Classes UML em código fonte executável com encapsulamento, execute a classe de domínio:

# modelo_classes.py
from datetime import datetime, timezone
from typing import Any

class EntregaPacote:
    def __init__(self, id_pacote: str, destinatario: str, peso_kg: float) -> None:
        self._id_pacote: str = id_pacote # Atributo Privado (-)
        self._destinatario: str = destinatario
        self._peso_kg: float = peso_kg
        self._status: str = "PENDENTE"
        self._data_criacao: datetime = datetime.now(timezone.utc)

    # Métodos Públicos (+)
    @property
    def status(self) -> str:
        return self._status

    def despachar_entrega(self) -> None:
        if self._status != "PENDENTE":
            raise ValueError("Apenas pacotes pendentes podem ser despachados.")
        self._status = "EM_TRANSITO"
        print(f"📦 Pacote {self._id_pacote} para '{self._destinatario}' despachado com sucesso!")

    def __repr__(self) -> str:
        return f"EntregaPacote(id={self._id_pacote}, dest='{self._destinatario}', status='{self._status}')"

if __name__ == "__main__":
    print("=" * 65)
    print("🏛️ DOMÍNIO ORIENTADO A OBJETOS (UML -> PYTHON) - TECPROEXPRESS")
    print("=" * 65)

    pkg = EntregaPacote("BR778899", "Hospital São Lucas", peso_kg=4.5)
    print(f"Instância Criada: {pkg}")
    print(f"Status Inicial Encapsulado: {pkg.status}")

    # Executando transição de método
    pkg.despachar_entrega()
    print(f"Status Atualizado: {pkg.status}")
    print("=" * 65)

🖥️ Saída Esperada no Terminal:

=================================================================
🏛️ DOMÍNIO ORIENTADO A OBJETOS (UML -> PYTHON) - TECPROEXPRESS
=================================================================
Instância Criada: EntregaPacote(id=BR778899, dest='Hospital São Lucas', status='PENDENTE')
Status Inicial Encapsulado: PENDENTE
📦 Pacote BR778899 para 'Hospital São Lucas' despachado com sucesso!
Status Atualizado: EM_TRANSITO
=================================================================

🌐 Exemplo de Payload JSON para Instanciação de Entidade (Swagger /docs)

{
  "id_pacote": "BR778899",
  "destinatario": "Hospital São Lucas",
  "peso_kg": 4.5,
  "endereco_entrega": {
    "rua": "Av. Brasil",
    "numero": 1500,
    "cidade": "São Paulo",
    "cep": "01430-000"
  },
  "prioridade_urgente": true
}

📤 Instruções de Entrega (Microsoft Teams)

Após validar suas documentações técnicas:

  1. Salve o arquivo de documentação com o nome Atividade_07.md e a imagem do seu diagrama de classes com o nome Atividade_07.png na pasta es-atv-07-classes/ do seu repositório GitHub.
  2. Certifique-se de fazer o commit e push para o repositório público.
  3. Submeta o link do seu repositório no Microsoft Teams para avaliação do professor.

💡 Checkpoint de Lógica

Importante

Reflexão Profissional: Se você tem uma classe Funcionario e uma classe Gerente, e percebe que ambas têm os atributos nome, cpf e telefone, qual conceito de Orientação a Objetos você aplicaria para não precisar repetir esse código duas vezes? (Resposta: Herança. Você criaria uma classe pai 'Pessoa' e as outras herdariam dela). 🧠🛡️

---

📊 Rubrica Formativa de Avaliação

Critério de Avaliação Insuficiente (0% - 40%) Regular (41% - 70%) Excelente (71% - 100%)
Diagrama de Classes (UML) Menos de 5 classes ou sem tipos de dados nos atributos. Mapeia 5 classes mas sem multiplicidades ou sem visibilidade. Diagrama de classes impecável com 5 entidades, atributos com visibilidade (+/-), métodos e multiplicidades corretas.
Justificativa de Encapsulamento Deixa todos os atributos como públicos sem justificativa. Justifica superficialmente sem explicar o papel dos getters/setters. Justificativa técnica sólida de encapsulamento protegendo a integridade dos dados da classe.
Entrega no GitHub Entrega fora da pasta `es-atv-07-classes/`. Entrega apenas o texto sem o arquivo `.png` da modelagem. Submete `Atividade_07.md` e `Atividade_07.png` no repositório com formatação validada.

🎬 ATIVIDADE 08: DIAGRAMA DE SEQUÊNCIA (UML)

📖 Fundamentação Teórica

Para realizar este laboratório com sucesso, certifique-se de ter compreendido os conceitos apresentados no:
👉 CAPÍTULO 08: VALIDAÇÃO E GESTÃO

Bem-vindo a mais uma etapa da sua jornada no curso de Gestão de TI / Desenvolvimento de Sistemas. Hoje vamos mergulhar em conceitos que conectam a teoria técnica diretamente com o padrão de excelência da dinâmica de software, entendendo como o sistema "conversa" internamente. 🛡️🧩


🎯 Objetivos de Aprendizagem do Laboratório

Ao final deste laboratório prático (estimativa: 4 horas presenciais / autoguiadas), você será capaz de:

  • Modelar o fluxo temporal de uma funcionalidade específica (Diferente da Classes, que é estático).
  • Identificar Linhas de Vida (Lifelines) de objetos e atores.
  • Representar Mensagens Síncronas, Assíncronas e Respostas.
  • Visualizar a interação entre as camadas do sistema (Interface, Lógica e Banco de Dados).

🏢 O Cenário Prático (Seu Desafio)

Na TecProExpress, você definiu as Classes (Atividade 07). Agora, os desenvolvedores de Frontend e Backend precisam sentar para combinar como as mensagens serão trocadas. O programador da tela pergunta: "Quando eu clicar em 'Confirmar Entrega', para quem eu mando o sinal? Quem valida se o motorista está no local certo? Quem avisa o banco de dados para salvar?".

"Seu desafio como Analista de Sistemas é desenhar o 'filme' dessa ação. Você deve criar o Diagrama de Sequência que mostra o passo a passo temporal: da tela para o servidor, do servidor para o banco, e a resposta voltando para o celular do motorista."


🧠 Fundamentos: A Teoria Traduzida

Se o Diagrama de Classes é a foto das peças de um tabuleiro de xadrez, o Diagrama de Sequência é a descrição de uma jogada específica.

Elementos Chaves

  • Atores e Objetos: Ficam no topo. De cada um desce uma Linha de Vida (tracejada).
  • Foco de Controle (Retângulo na linha): Indica que o objeto está "trabalhando" naquele momento.
  • Seta Contínua: Chamada de método (pergunta/ordem).
  • Seta Tracejada: Retorno (resposta).

📊 Visualizando a Lógica

Dica

A ordem importa! No Diagrama de Sequência, o tempo flui de cima para baixo. A primeira mensagem do topo é a primeira coisa que acontece no sistema.

```mermaid sequenceDiagram participant M as Motorista participant T as Tela App participant S as Servidor API participant B as Banco de Dados
M->>T: Clica em "Finalizar"
T->>S: validarLocalizacao(gps)
S->>B: salvarStatus("Entregue")
B-->>S: OK (Sucesso)
S-->>T: Exibir Mensagem Sucesso
T-->>M: Notificação na Tela

![Diagrama de Sequência TecProExpress (UML)](img/es_atv_08_sequencia.svg)

---

## 📖 Exemplo Guiado
Abaixo, veja como planejar a sequência do "Login" no sistema da TecProExpress.

| Ordem | Quem fala com quem? | O que é dito? |
| :--- | :--- | :--- |
| 1 | Usuário -> Tela | Digita usuário/senha e clica em Entrar. |
| 2 | Tela -> Servidor | `autenticar(user, pass)` |
| 3 | Servidor -> Banco | `SELECT * FROM usuarios...` |
| 4 | Banco -> Servidor | Retorna os dados do usuário. |
| 5 | Servidor -> Tela | Login Autorizado. |

#### 📊 Dinâmica de Chamadas e Foco de Controle
```mermaid
sequenceDiagram
    autonumber
    actor U as Usuário
    participant T as Tela Login
    participant S as Servidor Auth
    
    U->>T: Digita credenciais e clica em Entrar
    activate T
    T->>S: POST /login (payload JSON)
    activate S
    Note over S: Autentica credenciais e gera JWT
    S-->>T: 200 OK (Token JWT)
    deactivate S
    T-->>U: Redireciona para o Dashboard
    deactivate T

🛠️ Regras de Ouro do Diagrama

  1. Objetos nunca falam sozinhos: Uma mensagem sempre sai de uma linha de vida e vai para outra.
  2. Use verbos de ação: As setas representam chamadas de funções que você definiu lá na Atividade 07.
  3. Mantenha o foco: Não tente colocar 50 telas no mesmo diagrama. Escolha um fluxo (ex: Realizar Compra) e desenhe apenas ele.

🔍 Detalhamento do Processo:

  • O Diagrama de Sequência é o melhor lugar para descobrir bugs de lógica. Se você desenhou que a "Tela" fala direto com o "Banco de Dados", você descobriu uma falha de segurança! Na engenharia profissional, a Tela sempre fala com o Servidor, e o Servidor fala com o Banco.


💻 Simulação de Sequência de Chamadas (Trace Middleware) em Python

Para inspecionar na prática a troca temporal de mensagens entre Usuário, Frontend, API e Banco de Dados:

# simulador_sequencia.py
import time

class BancoDadosMock:
    def consultar_usuario(self, email: str) -> dict:
        time.sleep(0.05) # Simula latência de rede/disco
        print("      [DB] 💾 SELECT * FROM usuarios WHERE email = :email -> Encontrado!")
        return {"id": 1, "nome": "Carlos Silva", "senha_hash": "$2b$12$e8f..."}

class ServidorAPIMock:
    def __init__(self, db: BancoDadosMock) -> None:
        self.db = db

    def autenticar(self, email: str, senha: str) -> dict:
        print("   [API] ⚙️ Recebida requisição POST /api/v1/auth/login")
        user = self.db.consultar_usuario(email)
        print("   [API] 🔐 Validando hash de senha e gerando Token JWT...")
        return {"status": 200, "token": "eyJhbGciOiJIUzI1NiIsIn...", "nome": user["nome"]}

class FrontendAppMock:
    def __init__(self, api: ServidorAPIMock) -> None:
        self.api = api

    def clicar_botao_entrar(self, email: str, senha: str) -> None:
        print("[UI] 📱 Usuário clicou em 'Entrar'. Disparando requisição HTTP...")
        resposta = self.api.autenticar(email, senha)
        print(f"[UI] 🟢 200 OK recebido! Redirecionando para Dashboard. Bem-vindo(a), {resposta['nome']}!")

if __name__ == "__main__":
    print("=" * 65)
    print("⏱️  SIMULADOR DE TRACE TEMPORAL (DIAGRAMA DE SEQUÊNCIA UML)")
    print("=" * 65)

    db = BancoDadosMock()
    api = ServidorAPIMock(db)
    app = FrontendAppMock(api)

    app.clicar_botao_entrar("carlos@tecproexpress.com", "SenhaForte123")
    print("=" * 65)

🖥️ Saída Esperada no Terminal:

=================================================================
⏱️  SIMULADOR DE TRACE TEMPORAL (DIAGRAMA DE SEQUÊNCIA UML)
=================================================================
[UI] 📱 Usuário clicou em 'Entrar'. Disparando requisição HTTP...
   [API] ⚙️ Recebida requisição POST /api/v1/auth/login
      [DB] 💾 SELECT * FROM usuarios WHERE email = :email -> Encontrado!
   [API] 🔐 Validando hash de senha e gerando Token JWT...
[UI] 🟢 200 OK recebido! Redirecionando para Dashboard. Bem-vindo(a), Carlos Silva!
=================================================================

🌐 Exemplo de Requisição cURL para o Fluxo de Autenticação (Swagger /docs)

curl -X POST "http://127.0.0.1:8000/api/v1/auth/login" \
     -H "Content-Type: application/json" \
     -d '{
       "email": "carlos@tecproexpress.com",
       "senha": "SenhaForte123"
     }'

🔹 Resposta JSON do Servidor (Token JWT):

{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "bearer",
  "usuario": {
    "id": 1,
    "nome": "Carlos Silva",
    "perfil": "MOTORISTA"
  }
}

📤 Instruções de Entrega (Microsoft Teams)

Após validar suas documentações técnicas:

  1. Salve o arquivo de documentação com o nome Atividade_08.md e a imagem do seu diagrama de sequência com o nome Atividade_08.png na pasta es-atv-08-sequencia/ do seu repositório GitHub.
  2. Certifique-se de fazer o commit e push para o repositório público.
  3. Submeta o link do seu repositório no Microsoft Teams para avaliação do professor.

💡 Checkpoint de Lógica

Importante

Reflexão Profissional: Se durante o desenho do Diagrama de Sequência você perceber que precisa chamar um método chamado verificarEstoque(), mas esse método não existe na classe Estoque lá na sua Atividade 07, o que você deve fazer? (Resposta: Voltar na Atividade 07 e atualizar o Diagrama de Classes. A modelagem é um processo vivo e as atividades devem estar sempre sincronizadas). 🧠🛡️

---

📊 Rubrica Formativa de Avaliação

Critério de Avaliação Insuficiente (0% - 40%) Regular (41% - 70%) Excelente (71% - 100%)
Diagrama de Sequência (UML) Diagrama sem participantes claros ou misturando chamadas com retornos. Desenha o fluxo mas sem a distinção de setas de mensagem (síncrona) e retorno (assíncrona/tracejada). Diagrama de sequência limpo com ao menos 3 participantes (Ator, Tela, Banco), chamadas síncronas e retornos tracejados.
Sincronismo com o Diagrama de Classes Utiliza objetos e métodos inventados que não existem no Diagrama de Classes (Atv 07). Utiliza participantes coerentes mas omite retornos de dados importantes. Sincronia impecável de métodos e entidades entre o Diagrama de Sequência e o Diagrama de Classes.
Entrega no GitHub Entrega fora da pasta `es-atv-08-sequencia/`. Entrega o texto sem a imagem do diagrama de sequência. Submete `Atividade_08.md` e `Atividade_08.png` no repositório com validação de formato.

🧪 ATIVIDADE 09: QUALIDADE E TESTES DE SOFTWARE

📖 Fundamentação Teórica

Para realizar este laboratório com sucesso, certifique-se de ter compreendido os conceitos apresentados no:
👉 CAPÍTULO 09: FUNDAMENTOS DA MODELAGEM

Bem-vindo a mais uma etapa da sua jornada no curso de Gestão de TI / Desenvolvimento de Sistemas. Hoje vamos mergulhar em conceitos que conectam a teoria técnica diretamente com o padrão de excelência da qualidade, garantindo que o software seja robusto e confiável antes de chegar ao cliente final. 🛡️🧩


🎯 Objetivos de Aprendizagem do Laboratório

Ao final deste laboratório prático (estimativa: 4 horas presenciais / autoguiadas), você será capaz de:

  • Diferenciar Verificação (Estamos construindo o produto certo?) de Validação (Estamos construindo o produto corretamente?).
  • Redigir Casos de Teste (Test Cases) com passo a passo e resultados esperados.
  • Aplicar a técnica de Teste de Caixa Preta (Black-Box), focando em testes positivos e negativos.

🏢 O Cenário Prático (Seu Desafio)

Na TecProExpress, os desenvolvedores terminaram o "App de Rastreamento". No entanto, no primeiro dia de uso, um motorista tentou digitar o número da placa e o sistema travou porque ele usou letras minúsculas. O cliente tentou rastrear um pedido inexistente e o App exibiu uma tela de erro técnica (código 500) em vez de uma mensagem amigável.

O custo de imagem da empresa foi lá embaixo.

"Seu desafio como Analista de QA (Quality Assurance) é criar um Plano de Testes. Você deve antecipar o erro humano e criar roteiros que garantam que, mesmo que o usuário faça 'bobagem', o sistema se comporte de forma segura e elegante."


🧠 Fundamentos: A Teoria Traduzida

Testar software não é apenas "clicar para ver se funciona". É tentar provar que o sistema falha.

O Caso de Teste (Test Case)

Um caso de teste é um experimento científico controlado. Ele tem:

  1. Ação: O que eu faço? (Ex: Digito a senha errada).
  2. Resultado Esperado: O que deve acontecer? (Ex: Mensagem de erro "Senha inválida").

Testes Positivos vs Negativos

  • Positivo: O usuário faz tudo certo. (Ex: Login com dados corretos).
  • Negativo: O usuário faz tudo errado ou tenta "quebrar" o sistema. (Ex: Colocar letras no campo de 'Quantidade' ou deixar campos obrigatórios vazios).

📊 Visualizando a Lógica

Dica

Um bom QA não testa apenas o que o sistema faz, mas o que o sistema NÃO DEVE fazer.

```mermaid flowchart LR A[Entrada de Dados] --> B{Filtro de Teste} B -- "Dados Válidos" --> C[Sucesso] B -- "Dados Inválidos" --> D[Tratamento de Erro Amigável] ```

📖 Exemplo Guiado

Abaixo, veja como estruturar um Caso de Teste para o login da TecProExpress.

IDTítulo do TesteAção do UsuárioResultado Esperado
CT01Login VazioClica em 'Entrar' sem digitar nada.O sistema deve exibir "Campos Obrigatórios".
CT02Formato CPFDigita CPF sem pontos e traços.O sistema deve formatar e aceitar o login.

📊 A Pirâmide de Testes da Engenharia Profissional

flowchart TD
    subgraph P [Níveis de Cobertura e Esforço]
        direction BT
        E2E["Testes de Interface / Ponta a Ponta (E2E)<br/>(Lentos / Altamente Frágeis / Cobertura Focada)"]
        INT["Testes de Integração<br/>(Médio esforço / Valida comunicações entre APIs)"]
        UNI["Testes de Unidade (JUnit)<br/>(Ultra rápidos / Baratos / Cobertura massiva)"]
        UNI --> INT --> E2E
    end
    style UNI fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px
    style INT fill:#e1f5fe,stroke:#0288d1,stroke-width:2px
    style E2E fill:#fffde7,stroke:#fbc02d,stroke-width:2px

🛠️ Estrutura do Roteiro de Teste

# CT03: Teste de Upload de Comprovante

**Objetivo:** Validar se o sistema aceita apenas imagens de comprovantes.
**Pré-condição:** Estar na tela de finalização de entrega.
**Passos:**
1. Clicar no botão "Anexar Foto".
2. Tentar selecionar um arquivo do tipo ".PDF".
3. Clicar em "Confirmar".

**Resultado Esperado:** O sistema deve bloquear a seleção do PDF e exibir o erro "Formato não permitido. Use JPG ou PNG".

🔍 Detalhamento do Processo:

  • Note que o roteiro é tão detalhado que qualquer pessoa (mesmo quem não conhece o sistema) conseguiria executá-lo. Isso permite a Reprodutibilidade do Erro.

🛠️ Prática Obrigatória 1: Tabela de Casos de Teste

Cenário: O projeto semestral da sua equipe.

  1. Escolha 3 Requisitos Funcionais da sua Atividade 03.
  2. Para cada requisito, crie 2 Casos de Teste (um positivo e um negativo).
  3. Organize-os em uma tabela contendo: ID, Título, Ação e Resultado Esperado.

🏁 Resultado Esperado (Para sua Referência)

Uma tabela com 6 roteiros de teste (3 pares) que cubram as funcionalidades críticas do seu sistema.


💻 Suíte de Testes Automatizados em Python (Pytest)

Para transformar sua tabela de casos de teste em asserções automatizadas executáveis via pytest:

# test_entregas_qa.py
import pytest

def calcular_frete(peso_kg: float, distancia_km: float) -> float:
    if peso_kg <= 0:
        raise ValueError("O peso do pacote deve ser estritamente positivo.")
    if distancia_km <= 0:
        raise ValueError("A distância percorrida deve ser maior que zero.")
    return 15.0 + (peso_kg * 1.5) + (distancia_km * 0.8)

# 1. Caso de Teste Positivo (Caminho Feliz)
def test_calcular_frete_sucesso():
    valor = calcular_frete(peso_kg=10.0, distancia_km=50.0)
    assert valor == 70.0, f"Esperado R$ 70.00, mas obteve R$ {valor}"

# 2. Caso de Teste Negativo (Entrada Inválida com Exceção)
def test_calcular_frete_peso_negativo():
    with pytest.raises(ValueError, match="estritamente positivo"):
        calcular_frete(peso_kg=-5.0, distancia_km=50.0)

🖥️ Saída Esperada no Terminal ao Executar pytest:

============================= test session starts =============================
platform win32 -- Python 3.11.2, pytest-9.1.1, pluggy-1.6.0
rootdir: D:\SourceCode\GitRepos\github.io\portal_mdbook
collected 2 items

test_entregas_qa.py ..                                                  [100%]

============================== 2 passed in 0.04s ==============================

🌐 Exemplo de Requisição cURL para Teste de Erro 422/400 (Swagger /docs)

# Teste Negativo: Enviando peso negativo para a API
curl -X POST "http://127.0.0.1:8000/api/v1/frete/calcular" \
     -H "Content-Type: application/json" \
     -d '{
       "peso_kg": -10.0,
       "distancia_km": 50.0
     }'

🔹 Resposta de Erro Tratada (HTTP 422 Unprocessable Entity):

{
  "detail": [
    {
      "loc": ["body", "peso_kg"],
      "msg": "ensure this value is greater than 0",
      "type": "value_error.number.not_gt"
    }
  ]
}

📤 Instruções de Entrega (Microsoft Teams)

Após validar seus roteiros de teste:

  1. Salve o arquivo de documentação com o nome Atividade_09.md na pasta es-atv-09-testes/ do seu repositório GitHub.
  2. Certifique-se de fazer o commit e push para o repositório público.
  3. Submeta o link do seu repositório no Microsoft Teams para avaliação do professor.

💡 Checkpoint de Lógica

Importante

Reflexão Profissional: O que é mais caro para uma empresa: encontrar um erro durante a fase de Requisitos (Atividade 03) ou encontrar esse mesmo erro depois que o software já foi entregue para 10.000 clientes? (Resposta: Depois da entrega. O custo de correção no mercado pode ser até 100x maior devido a recalls, suporte e perda de reputação). 🧠🛡️

---

📊 Rubrica Formativa de Avaliação

Critério de Avaliação Insuficiente (0% - 40%) Regular (41% - 70%) Excelente (71% - 100%)
Casos de Teste (Positivo & Negativo) Menos de 6 roteiros ou contendo apenas testes positivos óbvios. Cria 6 casos de teste mas sem clareza no resultado esperado. Tabela completa com 6 casos de teste (3 pares Positivo/Negativo) cobrindo ID, Título, Ação e Resultado Esperado.
Teste de Caixa Preta & Defesa do Sistema Omite o cenário de teste de validação de dados/caixa preta. Descreve o cenário negativo sem indicar a ação de defesa do sistema. Detalhamento impecável de teste de borda/caixa preta e comportamento defensivo da aplicação.
Entrega no GitHub Entrega fora da pasta `es-atv-09-testes/`. Arquivo entregue mas sem tabela organizada em Markdown. `Atividade_09.md` publicado com formatação limpa e validada.

🏆 ATIVIDADE 10: PROJETO INTEGRADOR FINAL

📖 Fundamentação Teórica

Para realizar este laboratório com sucesso, certifique-se de ter compreendido os conceitos apresentados no:
👉 CAPÍTULO 10: DIAGRAMA DE CASOS DE USO (CONCEITOS)

Bem-vindo ao grande encerramento do seu módulo de Engenharia de Software. Ao longo deste semestre, você não apenas aprendeu teoria, você construiu um ativo intelectual. Hoje, você consolidará todas as etapas em um único documento de padrão internacional: o ERS (Especificação de Requisitos de Software). 🛡️🧩


🎯 Objetivos de Aprendizagem do Laboratório

Ao final deste laboratório prático (estimativa: 4 horas presenciais / autoguiadas), você será capaz de:

  • Organizar um projeto de software do escopo à garantia de qualidade.
  • Integrar diagramas UML com regras de negócio e jornadas de usuário.
  • Apresentar um documento técnico polido, profissional e pronto para ser entregue a uma equipe de desenvolvimento real.

🏢 O Cenário Prático (Seu Desafio)

A diretoria da TecProExpress está impressionada com o seu trabalho. Eles decidiram que o seu "App de Rastreamento" receberá o investimento necessário para ser construído por uma fábrica de software externa. No entanto, para liberar a verba, a fábrica exige o Documento de Especificação (ERS) completo.

Sem este documento, os desenvolvedores externos vão cobrar por hora para tentar adivinhar o que você quer. Com este documento, eles terão um roteiro exato para seguir.

"Seu desafio final é reunir todas as peças do quebra-cabeça que você construiu (Atividades 01 a 09), revisar as falhas e entregar o dossiê final do projeto. Este documento será a prova da sua maturidade como futuro gestor ou desenvolvedor de TI."


🧠 Fundamentos: O Poder da Documentação Consolidada

Um bom ERS é o "Contrato" entre quem paga e quem faz. Se algo não está no ERS, o desenvolvedor não é obrigado a fazer. Se algo está lá e o desenvolvedor não fez, o cliente tem o direito de reclamar.

Estrutura do Dossiê Profissional (Checklist)

  1. Capa e Introdução: Nome do sistema e visão do problema.
  2. Definições de Negócio: Personas e Escopo (In/Out).
  3. Processo: Justificativa da metodologia Ágil escolhida.
  4. Requisitos: As tabelas de RFs e RNFs priorizados.
  5. Documentação Ágil: User Stories e Critérios de Aceite.
  6. Modelagem Visual: Casos de Uso, Classes e Sequência.
  7. Interface: Wireframes das telas principais.
  8. Qualidade: Casos de Teste.

📊 Visualizando a Integração

Dica

A consistência é a chave. Se o seu Requisito RF01 diz "Login", seu Diagrama de Casos de Uso deve ter um oval "Fazer Login", sua Classe deve ter um método autenticar() e sua interface deve ter uma "Tela de Login".

```mermaid graph TD R[Requisitos] --> U[UML] U --> I[Interface] I --> T[Testes] T --> ERS(("ERS Final")) ```

📖 Exemplo Guiado: A Revisão Final

Antes de fechar o documento, o Engenheiro Sênior faz um "Double Check".

ArtefatoO que conferir?Status
RequisitosOs IDs (RF01, RF02) estão batendo com as User Stories?
ClassesO método que eu usei no Diagrama de Sequência existe na Classe?
ProtótipoA tela de login tem campos para o CPF que eu exigi no requisito?

📊 Pipeline de Consolidação e Revisão da Documentação ERS

flowchart TD
    A["Entrada 1: Requisitos (Atv 03, 04)"] --> R{"Revisão de Consistência"}
    B["Entrada 2: UML (Atv 05, 07, 08)"] --> R
    C["Entrada 3: Protótipos & Testes (Atv 06, 09)"] --> R
    R -->|OK| D["Documento ERS Consolidado (Atv 10)"]
    R -->|Inconsistência Detectada| E["Ajustes de Modelagem"]
    E --> R

    style R fill:#fffde7,stroke:#fbc02d,stroke-width:2px
    style D fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px

🛠️ Prática Obrigatória: A Consolidação do ERS

Cenário: A entrega oficial para a diretoria da TecProExpress.

  1. Unificação: Reúna todos os artefatos produzidos nas atividades 01 a 09 em um único arquivo (Markdown no GitHub ou um PDF profissional).
  2. Refatoração: Aplique as correções sugeridas pelo professor ao longo do semestre. (Ex: se seu diagrama de classes estava errado na Atv 07, entregue-o corrigido agora).
  3. Sumário: Crie um índice para facilitar a navegação no documento.
  4. Conclusão: Escreva um parágrafo final descrevendo as lições aprendidas durante a modelagem deste projeto.

🏁 Resultado Esperado (Para sua Referência)

Um Documento de Especificação de Requisitos (ERS) de 10 a 15 páginas, organizado, com imagens nítidas e textos revisados. Este é o seu Trabalho de Conclusão do Módulo.


💻 API Integrada de Homologação Final em Python (Flask)

Para validar a integração de todos os artefatos (Escopo, Requisitos, Domínio e Testes) em um protótipo executável de fechamento do módulo:

pip install flask
# api_projeto_final_es1.py
import sys
from flask import Flask, request, jsonify

app = Flask(__name__)

db_pacotes = []

@app.post("/api/v1/pacotes")
def cadastrar_pacote():
    dados = request.get_json()
    if not dados:
        return jsonify({"erro": "Payload JSON ausente"}), 400

    destinatario = dados.get("destinatario")
    peso_kg = float(dados.get("peso_kg", 0.0))
    cidade = dados.get("cidade")

    if not destinatario or not cidade or peso_kg <= 0:
        return jsonify({"erro": "Dados inválidos: destinatario, cidade e peso_kg (> 0) são obrigatórios"}), 400

    novo_pacote = {
        "id": len(db_pacotes) + 1,
        "destinatario": destinatario,
        "peso_kg": peso_kg,
        "cidade": cidade,
        "status": "PENDENTE"
    }
    db_pacotes.append(novo_pacote)
    return jsonify(novo_pacote), 201

@app.get("/api/v1/pacotes")
def listar_pacotes():
    status = request.args.get("status")
    if status:
        filtrados = [p for p in db_pacotes if p["status"].upper() == status.upper()]
        return jsonify(filtrados), 200
    return jsonify(db_pacotes), 200

if __name__ == "__main__":
    if "--server" in sys.argv:
        print("🚀 Servidor Flask de Homologação rodando em http://127.0.0.1:5000")
        app.run(port=5000, debug=True)
    else:
        print("--- Teste Automatizado com Flask test_client() ---")
        with app.test_client() as client:
            # 1. Cadastrar
            res1 = client.post("/api/v1/pacotes", json={
                "destinatario": "Hospital São Lucas",
                "peso_kg": 4.5,
                "cidade": "São Paulo"
            })
            print(f"POST /api/v1/pacotes [Status {res1.status_code}]: {res1.get_json()}")

            # 2. Listar
            res2 = client.get("/api/v1/pacotes?status=PENDENTE")
            print(f"GET /api/v1/pacotes?status=PENDENTE [Status {res2.status_code}]: {res2.get_json()}")

🚀 Como Executar

Para rodar os testes determinísticos no terminal:

python api_projeto_final_es1.py

Para subir o servidor HTTP local na porta 5000:

python api_projeto_final_es1.py --server

🖥️ Saída Esperada no Terminal ao Executar os Testes:

--- Teste Automatizado com Flask test_client() ---
POST /api/v1/pacotes [Status 201]: {'cidade': 'São Paulo', 'destinatario': 'Hospital São Lucas', 'id': 1, 'peso_kg': 4.5, 'status': 'PENDENTE'}
GET /api/v1/pacotes?status=PENDENTE [Status 200]: [{'cidade': 'São Paulo', 'destinatario': 'Hospital São Lucas', 'id': 1, 'peso_kg': 4.5, 'status': 'PENDENTE'}]

🌐 Requisições cURL de Homologação Final (Servidor Local)

Com o servidor rodando (--server), execute em outra janela do terminal:

🔹 1. Cadastrar Novo Pacote:

curl -X POST "http://127.0.0.1:5000/api/v1/pacotes" \
     -H "Content-Type: application/json" \
     -d '{
       "destinatario": "Hospital São Lucas",
       "peso_kg": 4.5,
       "cidade": "São Paulo"
     }'

🔹 2. Consultar Entregas Filtradas por Status:

curl -X GET "http://127.0.0.1:5000/api/v1/pacotes?status=PENDENTE" \
     -H "Accept: application/json"

📤 Instruções de Entrega (Microsoft Teams)

Após validar o seu dossiê completo:

  1. Salve o arquivo de documentação com o nome Atividade_10.md na pasta es-atv-10-projeto-final/ do seu repositório GitHub.
  2. Certifique-se de fazer o commit e push para o repositório público.
  3. Submeta o link do seu repositório no Microsoft Teams para avaliação do professor.

💡 Checkpoint de Lógica

Importante

Reflexão Profissional: Você se sente mais confiante para gerir um time de desenvolvimento agora que sabe como especificar cada detalhe técnico antes de gastar dinheiro com programação? O que você diria para um cliente que diz: "Não precisamos de documentação, vamos apenas começar a codar"? (Resposta: "Codar sem documentação é como construir um prédio sem planta: no começo é rápido, mas no final as janelas não abrem e o teto cai"). 🧠🛡️

---

🔥 Desafio de Fixação (Opcional)

Nível: Black Belt 🥋

Adicione ao final do seu documento uma seção de Análise de Riscos. Liste 3 coisas que podem dar errado no desenvolvimento deste sistema (ex: falta de internet nos caminhões, resistência dos motoristas em usar o app) e como você, como Engenheiro de Software, mitigaria esses riscos.


📊 Rubrica Formativa de Avaliação

Critério de Avaliação Insuficiente (0% - 40%) Regular (41% - 70%) Excelente (71% - 100%)
Consolidação da Especificação (ERS) Faltam mais de 2 artefatos fundamentais dos módulos anteriores (Atv 01 a 09). Consolida os artefatos mas sem padronização visual ou revisão textual. Dossiê ERS impecável de 10 a 15 páginas contendo Escopo, Requisitos, Backlog, UML (Casos de Uso/Classes/Sequência) e Testes.
Visão Arquitetural & Análise de Riscos Omite conclusões de aprendizado e análise de mitigação de riscos. Apresenta conclusão mas omite mitigação de riscos técnicos. Seção de conclusão e análise defensiva de riscos completa com estratégias de mitigação.
Entrega do Projeto Final Entrega fora da pasta `es-atv-10-projeto-final/`. Entrega apenas o arquivo markdown com imagens quebradas. Submete `Atividade_10.md` com imagens em alta definição e links válidos no repositório.

🌿 ATIVIDADE 11: DIAGRAMA DE ATIVIDADES (UML)

📖 Fundamentação Teórica

Para realizar este laboratório com sucesso, certifique-se de ter compreendido os conceitos apresentados no:
👉 CAPÍTULO 11: CASOS DE USO (PRÁTICA E RELAÇÕES)

Bem-vindo ao início do Bloco Avançado de Engenharia de Software. Ao longo das próximas semanas, você sairá da modelagem puramente estática e entrará de cabeça na lógica processual, no design de APIs modernas, no ecossistema corporativo (Java 17, Spring Boot 3.5.x, Thymeleaf, HTMX) e nas práticas de DevOps. Hoje, seu desafio é modelar o fluxo procedural dinâmico usando o Diagrama de Atividades. 🛡️🧩


🎯 Objetivos de Aprendizagem do Laboratório

Ao final deste laboratório prático (estimativa: 4 horas presenciais / autoguiadas), você será capaz de:

  • Modelar a lógica processual e fluxos de negócio usando a sintaxe UML.
  • Representar bifurcações e junções lógicas (decisões lógicas).
  • Representar forks e joins para fluxos de processamento em paralelo.
  • Organizar responsabilidades usando Raias de Natação (Swimlanes / Subgraphs).

🏢 O Cenário Prático (Seu Desafio)

Na TecProExpress, a diretoria de Logística percebeu um grande gargalo: quando um cliente clica em "Confirmar Compra", o sistema antigo esperava o faturamento da Nota Fiscal terminar para somente depois avisar o estoque. Em dias de grande volume, os motoristas ficavam parados esperando a emissão da nota, mesmo com a carga já pronta para carregar!

"Seu desafio como Analista de Processos de TI é desenhar o fluxo de atividades de faturamento e separação de forma paralela. Você deve modelar o processo de ponta a ponta: do clique do usuário, dividindo a ação em duas linhas paralelas (Faturamento e Separação), juntando-as no carregamento e finalizando com a saída do caminhão."


🧠 Fundamentos: A Teoria Traduzida

O Diagrama de Atividades UML é como um super fluxograma. Ele descreve a ordem em que as atividades de um sistema ou processo ocorrem, com excelente suporte para paralelismo.

Elementos Chave:

  1. Nó Inicial (Círculo Preto Sólido): Onde o processo começa.
  2. Ação/Atividade (Retângulo Arredondado): Um passo procedural executado pelo sistema ou humano.
  3. Fork (Barra de Divisão): Divide uma linha em duas ou mais ações que acontecem em paralelo.
  4. Join (Barra de Sincronização): Aguarda todas as linhas paralelas terminarem para continuar o fluxo.
  5. Decisão (Losango): Uma bifurcação baseada em uma pergunta do tipo Sim/Não.
  6. Nó Final (Círculo com Alvo): Onde o processo termina.

📊 Visualizando a Lógica

Dica

Fork vs Decisão: A decisão escolhe apenas um caminho. O Fork aciona todos os caminhos ao mesmo tempo. Nunca confunda as duas coisas!

```mermaid flowchart TD Start(["Início"]) --> Fork{{"Fork (Paralelizar)"}} Fork --> A[Emissão de Nota Fiscal] Fork --> B[Separação Física dos Itens] A --> Join{{"Join (Aguardar Ambos)"}} B --> Join Join --> End(["Pronto para Envio"])
style Start fill:#eceff1,stroke:#607d8b
style Fork fill:#e1f5fe,stroke:#0288d1
style Join fill:#e1f5fe,stroke:#0288d1
style End fill:#e0f2f1,stroke:#004d40

![Diagrama de Atividades TecProExpress (UML)](img/es_atv_11_atividades.svg)

---

## 📖 Exemplo Guiado
Abaixo, veja como detalhar o processamento com raias de natação (separando as responsabilidades entre Cliente, Financeiro e Logística).

| Elemento UML | O que representa? | Ator/Componente Responsável |
| :--- | :--- | :--- |
| Iniciar Compra | Nó Inicial | Cliente |
| Faturar Cartão | Ação | Financeiro (Gateway) |
| Separar Caixas | Ação | Logística (Operador) |
| Despachar Caminhão | Ação | Transportadora |

### 🛠️ Estrutura Visual da Expedição
Para facilitar a leitura profissional, dividimos o fluxo usando **Raias de Natação** (subgraphs no Mermaid):

```mermaid
flowchart TD
    subgraph CLI [Cliente]
        Start(["Compra Efetuada"]) --> Fork{{"Fork"}}
    end

    subgraph FIN [Financeiro]
        Fork --> F1[Emitir Nota Fiscal]
        F1 --> F2[Registrar Impostos]
    end

    subgraph LOG [Logística]
        Fork --> L1[Separar Produtos no Galpão]
        L1 --> L2[Embalar Carga]
    end

    subgraph TRA [Transportadora]
        F2 --> Join{{"Join"}}
        L2 --> Join
        Join --> T1[Carregar Caminhão]
        T1 --> End(["Caminhão Despachado"])
    end

    style Start fill:#eceff1,stroke:#333
    style End fill:#e0f2f1,stroke:#004d40
    style FIN fill:#fffde7,stroke:#fbc02d,stroke-width:1px
    style LOG fill:#e1f5fe,stroke:#0288d1,stroke-width:1px
    style TRA fill:#e8f5e9,stroke:#2e7d32,stroke-width:1px

🔍 Detalhamento do Processo:

  • A raia de natação deixa evidente quem faz o quê. Se o Financeiro falhar ao registrar impostos, o Join bloqueia o carregamento do caminhão na Transportadora, mesmo que a Logística já tenha embalado tudo!

🛠️ Prática Obrigatória 1: O Diagrama de Atividades

Cenário: O projeto de grupo semestral que você está desenvolvendo.

  1. Selecione a funcionalidade principal ou a jornada de maior complexidade do seu sistema (Ex: Devolução de Mercadorias, Cadastro e Aprovação de Motoristas, Compra com Vários Modos de Pagamento).
  2. Identifique pelo menos 3 raias de responsabilidade (Humanos ou Sistemas/Componentes).
  3. Insira pelo menos 1 Fork e 1 Join para representar ações executadas em paralelo.
  4. Insira pelo menos 1 Decisão com caminhos alternativos de erro ou validação.

🏁 Resultado Esperado (Para sua Referência)

Uma imagem (PNG/JPG) do diagrama de atividades estruturado de forma limpa, indicando claramente os caminhos paralelos e as fronteiras de responsabilidade.


💻 Simulação de Processamento Concorrente (Fork & Join) em Python

Para vivenciar a lógica de bifurcação paralela (Fork) e sincronização de término (Join) do seu Diagrama de Atividades UML:

# simulador_atividades_fork_join.py
import time
import threading

def processar_financeiro():
    print("   [FINANCEIRO] 💳 Emitindo Nota Fiscal e registrando tributos...")
    time.sleep(0.1)
    print("   [FINANCEIRO] ✅ Nota Fiscal emitida com sucesso!")

def processar_logistica():
    print("   [LOGÍSTICA] 📦 Separando itens no armazém e gerando etiqueta...")
    time.sleep(0.12)
    print("   [LOGÍSTICA] ✅ Caixa embalada e pronta para despacho!")

if __name__ == "__main__":
    print("=" * 65)
    print("🔀 WORKFLOW PARALELO (UML ACTIVITY FORK & JOIN) - TECPROEXPRESS")
    print("=" * 65)

    print("1. [FORK] Disparando tarefas simultâneas (Financeiro & Logística)...")
    t1 = threading.Thread(target=processar_financeiro)
    t2 = threading.Thread(target=processar_logistica)

    t1.start()
    t2.start()

    # JOIN: O sistema aguarda ambos os fluxos terminarem antes de despachar
    t1.join()
    t2.join()

    print("\n2. [JOIN] Todas as raias sincronizadas com sucesso!")
    print("3. [TRANSPORTADORA] 🚛 Carregando caminhão e iniciando rota de entrega.")
    print("=" * 65)

🖥️ Saída Esperada no Terminal:

=================================================================
🔀 WORKFLOW PARALELO (UML ACTIVITY FORK & JOIN) - TECPROEXPRESS
=================================================================
1. [FORK] Disparando tarefas simultâneas (Financeiro & Logística)...
   [FINANCEIRO] 💳 Emitindo Nota Fiscal e registrando tributos...
   [LOGÍSTICA] 📦 Separando itens no armazém e gerando etiqueta...
   [FINANCEIRO] ✅ Nota Fiscal emitida com sucesso!
   [LOGÍSTICA] ✅ Caixa embalada e pronta para despacho!

2. [JOIN] Todas as raias sincronizadas com sucesso!
3. [TRANSPORTADORA] 🚛 Carregando caminhão e iniciando rota de entrega.
=================================================================

🌐 Exemplo de Payload JSON para Disparo de Workflow (Swagger /docs)

{
  "id_pedido": "PED-88091",
  "cliente_id": "CLI-102",
  "itens": [
    {"sku": "MONITOR-4K", "quantidade": 1, "valor_unitario": 2100.0}
  ],
  "acoes_paralelas": [
    "EMISSAO_NFE",
    "PICKING_SEPARACAO"
  ]
}

📤 Instruções de Entrega (Microsoft Teams)

Após validar suas modelagens técnicas:

  1. Salve o arquivo de justificativa como Atividade_11.md e a imagem do seu diagrama com o nome Atividade_11.png na pasta es-atv-11-atividades/ do seu repositório GitHub.
  2. Certifique-se de fazer o commit e push para o repositório público.
  3. Submeta o link do seu repositório no Microsoft Teams para avaliação do professor.

💡 Checkpoint de Lógica

Importante

Reflexão Profissional: O que acontece com o fluxo de atividades se uma das ramificações que saem de um Fork entrar em um loop infinito ou falhar e nunca alcançar o Join? (Resposta: O Join travará o processo para sempre, pois ele exige por especificação matemática que todas as ramificações paralelas cheguem até ele para liberar o fluxo sequencial subsequente). 🧠🛡️

---

📊 Rubrica Formativa de Avaliação

Critério de Avaliação Insuficiente (0% - 40%) Regular (41% - 70%) Excelente (71% - 100%)
Diagrama de Atividades UML & Raias de Natação Desenha o fluxo sem raias de natação ou sem nós de decisão e paralelismo. Mapeia as raias mas confunde os conceitos de Fork e Join. Diagrama de atividades impecável com ao menos 3 raias, nós de decisão claros e par Fork/Join bem balanceado.
Justificativa de Paralelismo Omite a justificativa de paralelização de tarefas. Justifica superficialmente sem comparar com a execução sequencial procedural. Justificativa de alto nível comparando ganhos de performance e UX em relação à execução estritamente sequencial.
Entrega no GitHub Entrega fora da pasta `es-atv-11-atividades/`. Entrega apenas o texto sem o arquivo `.png` do diagrama. Submete `Atividade_11.md` e `Atividade_11.png` no repositório com formatação validada.

🔄 ATIVIDADE 12: DIAGRAMA DE MÁQUINA DE ESTADOS (UML)

📖 Fundamentação Teórica

Para realizar este laboratório com sucesso, certifique-se de ter compreendido os conceitos apresentados no:
👉 CAPÍTULO 12: DIAGRAMA DE CLASSES (CONCEITOS)

Bem-vindo a mais uma etapa prática do seu treinamento em Engenharia de Software. Na atividade anterior, você aprendeu sobre processos e fluxos lógicos. Hoje, nosso foco muda para o ciclo de vida de uma entidade do sistema. Você aprenderá como modelar os Estados pelos quais um dado transiciona, garantindo que o software nunca entre em inconsistência lógica. 🛡️🧩


🎯 Objetivos de Aprendizagem do Laboratório

Ao final deste laboratório prático (estimativa: 4 horas presenciais / autoguiadas), você será capaz de:

  • Identificar entidades de negócio com ciclos de vida complexos.
  • Modelar os Estados estáveis de um objeto no sistema.
  • Mapear Eventos Gatilho (Triggers) que provocam a transição entre estados.
  • Aplicar Condições de Guarda (Guards) para proteção lógica contra transições inválidas.

🏢 O Cenário Prático (Seu Desafio)

Na TecProExpress, tivemos um incidente grave de banco de dados: um cliente de suporte ligou reclamando que a sua entrega apareceu no sistema como "Entregue com Sucesso", mas o motorista ainda nem havia saído com o caminhão do centro de distribuição!

Ao analisar o código, a equipe técnica descobriu que não havia validação de regras de negócios. Um motorista distraído clicou no botão "Confirmar Entrega" na tela errada de um pacote que ainda estava no estado de triagem físico!

"Seu desafio como Arquiteto de Software é desenhar o Diagrama de Máquina de Estados para a entidade Entrega. Você deve blindar o sistema, definindo as regras exatas de como um pacote muda de estado (ex: de Pendente para Em Rota) e quais validações (guardas) impedem que um pacote seja marcado como Entregue antes de ser despachado."


🧠 Fundamentos: A Teoria Traduzida

Enquanto o Diagrama de Atividades mostra o fluxo das ações de um processo, o Diagrama de Máquina de Estados foca em um único objeto do início ao fim da sua existência.

Elementos Chave:

  1. Estado Inicial (Círculo Preto): O ponto de criação do objeto.
  2. Estado (Retângulo Arredondado): Uma condição estável na vida do objeto onde ele aguarda um evento (Ex: PENDENTE, EM_ROTA, ENTREGUE).
  3. Transição (Seta Direcionada): A mudança de um estado para o outro.
  4. Evento Gatilho (Trigger): A ação que dispara a transição (Ex: despacharPacote()).
  5. Condição de Guarda (Guard - entre colchetes [...]): Uma regra que precisa ser verdadeira para a transição acontecer. (Ex: [motorista_no_local == true]).

📊 Visualizando a Lógica

Dica

Condição Guarda: Pense na guarda como um segurança de boate. A transição quer acontecer, mas o segurança impede se a condição não for atendida!

```mermaid stateDiagram-v2 [*] --> PENDENTE : cadastrarPedido() PENDENTE --> EM_ROTA : despachar() [caminhao_carregado == true] EM_ROTA --> ENTREGUE : finalizar() [foto_comprovante_anexada == true] EM_ROTA --> TENTATIVA_FALHA : motoristaAusente() TENTATIVA_FALHA --> EM_ROTA : redirecionar() ENTREGUE --> [*] ```

Diagrama de Transição de Estados TecProExpress (UML)


📖 Exemplo Guiado

Abaixo, veja o mapeamento de transição para o ciclo de vida do pacote da TecProExpress.

Estado OrigemEvento GatilhoCondição GuardaEstado Destino
PENDENTEdespachar()[motorista_associado != null]EM_ROTA
EM_ROTAconfirmarEntrega()[distancia_gps_cliente < 100m]ENTREGUE
EM_ROTAcancelarPedido()[motivo != null]CANCELADO

🛠️ Regra UML em Sintaxe Padrão:

A sintaxe formal gravada na seta da transição segue o padrão: Evento [Guarda] / Ação executada

  • Exemplo: confirmarEntrega [comprovanteAnexado] / notificarCliente()
stateDiagram-v2
    state "Pendente de Coleta" as P
    state "Em Rota de Entrega" as ER
    state "Cancelado" as C
    state "Entregue" as E

    [*] --> P
    P --> ER : atribuirMotorista [motorista_ativo == true]
    ER --> E : confirmarEntrega [gps_ok == true] / enviarSMS()
    ER --> C : cancelar [cliente_solicitou] / estornarCartao()
    C --> [*]
    E --> [*]

🔍 Detalhamento do Processo:

  • Note a guarda [gps_ok == true]. Se o aplicativo do motorista não coletar a coordenada geográfica a menos de 100 metros da casa do cliente, o sistema bloqueia a mudança para o estado Entregue, evitando fraudes ou cliques acidentais!

🛠️ Prática Obrigatória 1: A Máquina de Estados

Cenário: O projeto semestral da sua equipe.

  1. Selecione a entidade mais crítica do seu sistema que possui vários status (Ex: Pedido em um e-commerce, Agendamento em uma clínica, Vaga em um estacionamento).
  2. Mapeie pelo menos 4 estados distintos para essa entidade.
  3. Defina claramente os Eventos Gatilho em cada seta de transição.
  4. Adicione pelo menos 2 Condições de Guarda entre colchetes [...] para blindar transições perigosas.

🏁 Resultado Esperado (Para sua Referência)

Uma imagem (PNG/JPG) do diagrama de máquina de estados UML modelado de forma organizada, exibindo a trajetória completa da entidade do estado inicial ao final.


💻 Máquina de Estados Finita (State Pattern) em Python

Para validar as transições de ciclo de vida com condições de guarda blindadas:

# maquina_estados.py
from enum import Enum

class StatusEntrega(Enum):
    PENDENTE = "PENDENTE"
    EM_ROTA = "EM_ROTA"
    ENTREGUE = "ENTREGUE"
    CANCELADO = "CANCELADO"

class PacoteFSM:
    def __init__(self, cod_rastreio: str) -> None:
        self.cod_rastreio = cod_rastreio
        self.estado_atual = StatusEntrega.PENDENTE

    def despachar(self, motorista_ativo: bool) -> None:
        if self.estado_atual != StatusEntrega.PENDENTE:
            raise ValueError(f"Não é possível despachar pacote no estado {self.estado_atual.value}")
        if not motorista_ativo: # Condição de Guarda [motorista_ativo == true]
            raise PermissionError("Guarda Violada: Nenhum motorista ativo atribuído.")
        
        self.estado_atual = StatusEntrega.EM_ROTA
        print(f"🚚 [{self.cod_rastreio}] Transição de Estado: PENDENTE -> EM_ROTA")

    def confirmar_entrega(self, distancia_metros: float) -> None:
        if self.estado_atual != StatusEntrega.EM_ROTA:
            raise ValueError("Apenas pacotes em rota podem ser confirmados como entregues.")
        if distancia_metros > 100.0: # Condição de Guarda [gps_ok == true]
            raise ValueError(f"Guarda Violada: Motorista está a {distancia_metros}m do local (máximo permitido: 100m).")
        
        self.estado_atual = StatusEntrega.ENTREGUE
        print(f"✅ [{self.cod_rastreio}] Transição de Estado: EM_ROTA -> ENTREGUE (GPS Validado a {distancia_metros}m)")

if __name__ == "__main__":
    print("=" * 65)
    print("🚦 MÁQUINA DE ESTADOS FINITA (FSM / STATE PATTERN) - TECPROEXPRESS")
    print("=" * 65)

    fsm = PacoteFSM("BR-4401")
    print(f"Estado Inicial: {fsm.estado_atual.value}")

    fsm.despachar(motorista_ativo=True)
    fsm.confirmar_entrega(distancia_metros=25.0)

    print(f"Estado Final Consolidado: {fsm.estado_atual.value}")
    print("=" * 65)

🖥️ Saída Esperada no Terminal:

=================================================================
🚦 MÁQUINA DE ESTADOS FINITA (FSM / STATE PATTERN) - TECPROEXPRESS
=================================================================
Estado Inicial: PENDENTE
🚚 [BR-4401] Transição de Estado: PENDENTE -> EM_ROTA
✅ [BR-4401] Transição de Estado: EM_ROTA -> ENTREGUE (GPS Validado a 25.0m)
Estado Final Consolidado: ENTREGUE
=================================================================

🌐 Exemplo de Payload JSON para Transição de Estado (Swagger /docs)

{
  "codigo_rastreio": "BR-4401",
  "transicao_solicitada": "CONFIRMAR_ENTREGA",
  "dados_guarda": {
    "latitude_atual": -23.55052,
    "longitude_atual": -46.63331,
    "distancia_calculada_metros": 25.0
  }
}

📤 Instruções de Entrega (Microsoft Teams)

Após validar suas documentações técnicas de estados:

  1. Salve o arquivo de tabela com o nome Atividade_12.md e a imagem do seu diagrama com o nome Atividade_12.png na pasta es-atv-12-estados/ do seu repositório GitHub.
  2. Certifique-se de fazer o commit e push para o repositório público.
  3. Submeta o link do seu repositório no Microsoft Teams para avaliação do professor.

💡 Checkpoint de Lógica

Importante

Reflexão Profissional: O que diferencia, na modelagem profissional, um Estado de uma Atividade? (Resposta: Um Estado representa uma situação de estabilidade temporária onde o objeto está aguardando passivamente um evento externo para mudar. Uma Atividade é uma ação ativa, de execução procedural contínua e computacional direta, que transiciona automaticamente para o próximo passo assim que termina). 🧠🛡️

---

📊 Rubrica Formativa de Avaliação

Critério de Avaliação Insuficiente (0% - 40%) Regular (41% - 70%) Excelente (71% - 100%)
Máquina de Estados & Condições de Guarda Menos de 4 estados ou transições sem eventos gatilho claros. Mapeia os 4 estados mas sem declarar as condições de guarda `[...]`. Diagrama de máquina de estados impecável com ao menos 4 estados, eventos gatilho e guardas `[...]` de proteção.
Tabela de Transições Lógicas Omite a tabela de transições lógicas. Preenche a tabela com colunas incompletas. Tabela detalhada de transições de estado cobrindo Estado Origem, Gatilho, Guarda e Estado Destino.
Entrega no GitHub Entrega fora da pasta `es-atv-12-estados/`. Entrega apenas o arquivo markdown sem a imagem `.png`. Submete `Atividade_12.md` e `Atividade_12.png` no repositório com validação de padrão.

🏛️ ATIVIDADE 13: ARQUITETURA DE SOFTWARE E PADRÕES

📖 Fundamentação Teórica

Para realizar este laboratório com sucesso, certifique-se de ter compreendido os conceitos apresentados no:
👉 CAPÍTULO 13: HERANÇA E POLIMORFISMO

Bem-vindo a mais uma etapa do seu desenvolvimento técnico! Agora que você domina a modelagem de processos e dados, daremos o passo mais importante para quem quer se tornar um desenvolvedor sênior ou gestor técnico: projetar a arquitetura interna do software. Hoje, aprenderemos a organizar sistemas profissionais em camadas usando o padrão MVC (Model-View-Controller) corporativo com Java 17, Spring Boot 3.5.x, Thymeleaf e HTMX, além de aplicar o padrão de projeto criacional Factory. 🛡️🧩


🎯 Objetivos de Aprendizagem do Laboratório

Ao final deste laboratório prático (estimativa: 4 horas presenciais / autoguiadas), você será capaz de:

  • Diferenciar monólitos, camadas MVC e arquiteturas desacopladas modernas.
  • Projetar o fluxo de dados entre Controller, Service e Repository do Spring Boot.
  • Compreender o uso do Thymeleaf + HTMX para gerar interfaces reativas sem o peso de frameworks complexos de SPA.
  • Aplicar o padrão de projeto Factory para criação desacoplada de objetos de negócio.

🏢 O Cenário Prático (Seu Desafio)

Na TecProExpress, a equipe de desenvolvimento estava sofrendo: toda vez que precisavam mudar o banco de dados de MySQL para PostgreSQL, tinham que reescrever a validação de rotas, o cálculo do frete e as telas do aplicativo! Tudo estava tão bagunçado e misturado (acoplado) que um erro de digitação no botão da tela derrubava o cálculo do financeiro!

"Seu desafio como Arquiteto de Software é reorganizar a plataforma. Você desenhará a estrutura do sistema em camadas estritas e isoladas no Spring Boot. Além disso, os motoristas exigem notificações de novas rotas via SMS, e-mail ou push móvel. Você implementará o padrão Factory para que o sistema escolha o tipo correto de envio de notificação sem acoplar a lógica de entrega."


🧠 Fundamentos: A Teoria Traduzida

Para que um sistema sobreviva a anos de manutenção, ele deve ser desacoplado (cada peça cuida de apenas um assunto).

A Estrutura em Camadas (Spring Boot)

  1. View / Interface (Thymeleaf + HTMX): Renderiza o HTML dinâmico no servidor com Thymeleaf e realiza requisições parciais assíncronas com HTMX, mantendo o carregamento rápido e leve.
  2. Controller (Camada de Apresentação): Recebe os cliques (requisições HTTP), extrai dados do formulário e chama a lógica. Ele nunca calcula regras de negócio!
  3. Service (Camada de Negócio): Onde as regras morrem. É o "cérebro" do sistema (Ex: calcula desconto, valida se o motorista está ativo).
  4. Repository / DAO (Camada de Dados): Onde ocorrem as queries SQL (SELECT, INSERT), salvando e buscando os dados no banco de dados.

📊 Visualizando a Lógica MVC + Camadas

flowchart TD
    Browser["Navegador (HTML + HTMX)"] -->|1. Requisição HTTP| Controller["Controller (Spring MVC)"]
    Controller -->|2. Valida & Encaminha| Service["Service (Regras de Negócio)"]
    Service -->|3. Consulta Dados| Repository["Repository (Acesso ao Banco)"]
    Repository -->|4. Retorna Entidades| Service
    Service -->|5. Retorna Resultados| Controller
    Controller -->|6. Renderiza Thymeleaf| Browser

    style Browser fill:#eceff1,stroke:#607d8b,stroke-width:2px
    style Controller fill:#fffde7,stroke:#fbc02d,stroke-width:2px
    style Service fill:#e1f5fe,stroke:#0288d1,stroke-width:2px
    style Repository fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px

Arquitetura em Camadas MVC (Spring Boot)


📖 Exemplo Guiado: O Padrão Factory (Notificações)

Quando uma nova rota é atribuída, precisamos despachar um alerta ao entregador. Mas não queremos acoplar as classes SmsNotification, EmailNotification e PushNotification no código principal. Usaremos a NotificationFactory.

// 1. Interface comum de Notificação
public interface Notification {
    void send(String message, String target);
}

// 2. Implementações específicas
public class SmsNotification implements Notification {
    public void send(String message, String target) {
        System.out.println("Disparando SMS para " + target + ": " + message);
    }
}

public class EmailNotification implements Notification {
    public void send(String message, String target) {
        System.out.println("Enviando E-mail para " + target + ": " + message);
    }
}

// 3. A Fábrica (Factory) que desacopla a criação
public class NotificationFactory {
    public static Notification createNotification(String type) {
        if (type == null) return null;
        if (type.equalsIgnoreCase("SMS")) return new SmsNotification();
        if (type.equalsIgnoreCase("EMAIL")) return new EmailNotification();
        throw new IllegalArgumentException("Tipo de notificação desconhecido: " + type);
    }
}

🛠️ Código do Service utilizando a Factory no Spring Boot:

@Service
public class DeliveryService {
    
    @Autowired
    private DeliveryRepository repository;

    public void processarEntrega(Long deliveryId, String notificationType) {
        // Busca a entrega no banco
        Delivery delivery = repository.findById(deliveryId)
            .orElseThrow(() -> new RuntimeException("Entrega não encontrada"));

        // Lógica de Negócio: Atualiza status
        delivery.setStatus("EM_ROTA");
        repository.save(delivery);

        // Uso do Padrão Factory para enviar notificação sem acoplamento
        Notification notification = NotificationFactory.createNotification(notificationType);
        notification.send("Sua entrega está em rota!", delivery.getMotoristaContato());
    }
}

🔍 Detalhamento do Processo:

  • Se amanhã o diretor da TecProExpress exigir o envio de notificações por WhatsApp, basta criar a classe WhatsAppNotification e adicioná-la dentro da NotificationFactory. O arquivo DeliveryService continuará intacto, provando que nosso sistema atende ao Princípio do Aberto/Fechado (OCP) do SOLID! 🚀

🛠️ Prática Obrigatória 1: Desenho Arquitetural

Cenário: O projeto semestral da sua equipe.

  1. Desenhe um diagrama flowchart no Mermaid representando a arquitetura em camadas do seu projeto de grupo.
  2. Mapeie e rotule as caixas indicando onde ficará a sua Interface (Thymeleaf/HTMX), seus Controllers, seus Services (Regras de Negócio) e seus Repositories.
  3. Garanta que a camada de interface fale exclusivamente com o Controller, e o Controller fale exclusivamente com o Service.

🏁 Resultado Esperado (Para sua Referência)

Um diagrama de arquitetura de camadas profissional documentado em formato Mermaid no corpo do seu arquivo Markdown de entrega.



💻 Arquitetura em Camadas & Factory Pattern em Python

Para vivenciar o desacoplamento arquitetural (Controller $\rightarrow$ Service $\rightarrow$ Factory $\rightarrow$ Repository):

# arquitetura_factory.py
from abc import ABC, abstractmethod

# 1. Interface de Notificação (Contrato)
class Notificador(ABC):
    @abstractmethod
    def enviar(self, destinatario: str, mensagem: str) -> None:
        pass

# 2. Implementações Concretas
class NotificadorSMS(Notificador):
    def enviar(self, destinatario: str, mensagem: str) -> None:
        print(f"   📲 [SMS] Enviado para {destinatario}: '{mensagem}'")

class NotificadorEmail(Notificador):
    def enviar(self, destinatario: str, mensagem: str) -> None:
        print(f"   📧 [E-MAIL] Enviado para {destinatario}: '{mensagem}'")

# 3. Factory Criacional (OCP - Open/Closed Principle)
class NotificadorFactory:
    @staticmethod
    def obter_notificador(canal: str) -> Notificador:
        canais = {
            "SMS": NotificadorSMS,
            "EMAIL": NotificadorEmail
        }
        classe = canais.get(canal.upper())
        if not classe:
            raise ValueError(f"Canal de notificação '{canal}' não suportado.")
        return classe()

# 4. Camada de Serviço (Service Layer)
class EntregaService:
    def despachar_pacote(self, cod_pacote: str, canal_notificacao: str, contato: str) -> None:
        print(f"⚙️ [SERVICE] Processando despacho do pacote {cod_pacote}...")
        notificador = NotificadorFactory.obter_notificador(canal_notificacao)
        notificador.enviar(contato, f"Seu pacote {cod_pacote} saiu para entrega!")

if __name__ == "__main__":
    print("=" * 65)
    print("🏛️ ARQUITETURA EM CAMADAS & FACTORY PATTERN - TECPROEXPRESS")
    print("=" * 65)

    service = EntregaService()
    service.despachar_pacote("BR-9901", "SMS", "11988887777")
    service.despachar_pacote("BR-9902", "EMAIL", "cliente@empresa.com")
    print("=" * 65)

🖥️ Saída Esperada no Terminal:

=================================================================
🏛️ ARQUITETURA EM CAMADAS & FACTORY PATTERN - TECPROEXPRESS
=================================================================
⚙️ [SERVICE] Processando despacho do pacote BR-9901...
   📲 [SMS] Enviado para 11988887777: 'Seu pacote BR-9901 saiu para entrega!'
⚙️ [SERVICE] Processando despacho do pacote BR-9902...
   📧 [E-MAIL] Enviado para cliente@empresa.com: 'Seu pacote BR-9902 saiu para entrega!'
=================================================================

🌐 Requisição cURL para Notificação Desacoplada (Swagger /docs)

curl -X POST "http://127.0.0.1:5000/api/v1/entregas/notificar" \
     -H "Content-Type: application/json" \
     -d '{
       "cod_pacote": "BR-9901",
       "canal": "SMS",
       "contato": "11988887777"
     }'

🔹 Resposta JSON:

{
  "status": "ENVIADO",
  "canal_utilizado": "SMS",
  "timestamp": "2026-03-10T14:32:00Z"
}

📤 Instruções de Entrega (Microsoft Teams)

Após validar suas especificações arquiteturais:

  1. Salve o arquivo de código e diagramas com o nome Atividade_13.md na pasta es-atv-13-arquitetura/ do seu repositório GitHub.
  2. Certifique-se de fazer o commit e push para o repositório público.
  3. Submeta o link do seu repositório no Microsoft Teams para avaliação do professor.

💡 Checkpoint de Lógica

Importante

Reflexão Profissional: Por que na arquitetura profissional de desenvolvimento em camadas do Spring Boot, a classe de Controller nunca deve conter queries SQL ou lógicas pesadas de IF/ELSE de negócios? (Resposta: Para manter o desacoplamento e a testabilidade do sistema. O Controller deve apenas gerenciar a comunicação com o protocolo HTTP; se regras de negócio forem colocadas ali, o código ficará amarrado ao ambiente Web, impedindo que essa mesma lógica de negócio seja reutilizada em APIs móveis, agendadores automáticos ou testes unitários JUnit). 🧠🛡️

---

📊 Rubrica Formativa de Avaliação

Critério de Avaliação Insuficiente (0% - 40%) Regular (41% - 70%) Excelente (71% - 100%)
Arquitetura em Camadas (Mermaid Flowchart) Diagrama confuso ou misturando responsabilidades de Controller e Repository. Mapeia as camadas mas sem rotular o fluxo estrito (UI ➔ Controller ➔ Service ➔ Repository). Diagrama Mermaid de arquitetura em camadas perfeitamente desacoplado e rotulado.
Design Pattern Factory (Desacoplamento) Modela criacionalmente sem usar interfaces ou abstração. Cria a Factory mas mantendo alto acoplamento na classe chamadora. Modelagem impecável do padrão Factory respeitando o Princípio Aberto/Fechado (OCP) do SOLID.
Entrega no GitHub Entrega fora da pasta `es-atv-13-arquitetura/`. Arquivo entregue mas sem renderização do diagrama Mermaid. Submete `Atividade_13.md` com código limpo e Mermaid renderizado com sucesso.

🔌 ATIVIDADE 14: DESIGN DE APIS REST E SWAGGER

📖 Fundamentação Teórica

Para realizar este laboratório com sucesso, certifique-se de ter compreendido os conceitos apresentados no:
👉 CAPÍTULO 14: DIAGRAMA DE SEQUÊNCIA

Bem-vindo a mais uma etapa prática de Engenharia de Software! Agora que você conhece as camadas internas do software, aprenderá como expor e conectar o seu sistema ao mundo externo. Hoje, vamos nos tornar arquitetos de comunicação móvel e web, projetando contratos de integração baseados em APIs RESTful, payloads de dados JSON e documentando tudo com o padrão profissional global Swagger (OpenAPI). 🛡️🧩


🎯 Objetivos de Aprendizagem do Laboratório

Ao final deste laboratório prático (estimativa: 4 horas presenciais / autoguiadas), você será capaz de:

  • Mapear recursos de sistemas no formato e boas práticas de APIs REST.
  • Utilizar os Verbos HTTP (GET, POST, PUT, DELETE) e os Códigos de Retorno (Status Codes) corretos.
  • Escrever payloads estruturados em formato JSON (JavaScript Object Notation).
  • Interpretar e projetar documentações no padrão Swagger/OpenAPI.

🏢 O Cenário Prático (Seu Desafio)

Na TecProExpress, os motoristas que usam o aplicativo Android precisam enviar em tempo real a geolocalização do caminhão para o painel de controle do SAC. Além disso, o app precisa buscar a lista de entregas pendentes direto do servidor central. No entanto, os desenvolvedores Mobile e os programadores de Backend não combinaram o "formato de dados" e a comunicação travou!

O app móvel mandava POST /api/enviar_localizacao e o servidor dava erro de conexão porque esperava receber um formato diferente do JSON enviado pelo celular!

"Seu desafio como Designer de APIs é desenhar o contrato profissional de endpoints para a TecProExpress. Você criará a modelagem de recursos RESTful para /api/entregas e /api/motoristas/localizacao, definindo os verbos corretos, os dados em formato JSON e gerando a especificação profissional baseada em Swagger/OpenAPI."


🧠 Fundamentos: A Teoria Traduzida

Uma API (Application Programming Interface) é uma porta que permite que sistemas diferentes conversem de forma organizada. REST é o estilo arquitetural padrão da web.

Regras de Ouro do Design REST:

  1. Recursos são Substantivos no Plural: Evite verbos no endereço!
    • Errado: POST /api/salvarMotorista
    • Certo: POST /api/motoristas
  2. Use os Verbos HTTP Adequados:
    • GET: Busca um ou vários registros (Ex: GET /api/entregas).
    • POST: Cria um novo registro (Ex: POST /api/entregas).
    • PUT: Atualiza o registro inteiro por completo.
    • DELETE: Exclui um registro da base de dados.
  3. Comunique-se por Status Codes:
    • 200 OK: Requisição de consulta ou alteração com sucesso.
    • 201 Created: Novo registro criado com sucesso (retorno típico de POST).
    • 400 Bad Request: Envio de dados incorretos ou campos faltantes.
    • 404 Not Found: O registro ou endpoint consultado não existe.
    • 500 Internal Server Error: Falha técnica no servidor (bug).

📊 Visualizando a Comunicação HTTP e Swagger

sequenceDiagram
    autonumber
    actor Cliente as 📱 App Mobile / Frontend
    participant API as 🌐 Flask Router
    participant Schema as 🛡️ Validação de Payload
    participant DB as 🗄️ PostgreSQL

    Cliente->>API: POST /api/entregas (JSON Payload)
    API->>Schema: Valida Tipos e Constraints
    alt Dados Válidos
        Schema-->>API: Dados Aprovados
        API->>DB: INSERT INTO entregas (...)
        DB-->>API: Confirmação de Persistência
        API-->>Cliente: 201 Created { "id": 125, "status": "PENDENTE" }
    else Dados Inválidos
        Schema-->>API: Erro de Validação (ex: peso <= 0)
        API-->>Cliente: 400 Bad Request (JSON com detalhes do erro)
    end

📖 Exemplo Guiado: Especificação OpenAPI / Swagger e API em Flask

No Flask, implementamos a lógica de rota e validação em Python, e documentamos o contrato correspondente no padrão OpenAPI 3.0 (Swagger):

🛠️ Código Python 3.11 no Flask (app_entregas.py):

import sys
from flask import Flask, request, jsonify

app = Flask(__name__)

@app.post("/api/entregas")
def criar_entrega():
    """Cria uma nova entrega e retorna o objeto persistido."""
    dados = request.get_json()
    if not dados:
        return jsonify({"erro": "Payload JSON ausente"}), 400

    cliente_id = dados.get("cliente_id")
    endereco_destino = dados.get("endereco_destino")
    peso_carga_kg = dados.get("peso_carga_kg", 0.0)
    tipo_notificacao = dados.get("tipo_notificacao", "SMS")

    if not cliente_id or not endereco_destino:
        return jsonify({"erro": "cliente_id e endereco_destino são obrigatórios"}), 400
    if peso_carga_kg <= 0:
        return jsonify({"erro": "peso_carga_kg deve ser maior que zero"}), 400

    resposta = {
        "id": 125,
        "cliente_id": cliente_id,
        "endereco_destino": endereco_destino,
        "peso_carga_kg": peso_carga_kg,
        "tipo_notificacao": tipo_notificacao,
        "status": "PENDENTE"
    }
    return jsonify(resposta), 201

if __name__ == "__main__":
    if "--server" in sys.argv:
        print("🚀 Servidor de Entregas Flask rodando em http://127.0.0.1:5000")
        app.run(port=5000, debug=True)
    else:
        print("--- Teste Automatizado com Flask test_client() ---")
        with app.test_client() as client:
            res = client.post("/api/entregas", json={
                "cliente_id": 45,
                "endereco_destino": "Av. Paulista, 1000 - São Paulo, SP",
                "peso_carga_kg": 12.5,
                "tipo_notificacao": "SMS"
            })
            print(f"Status HTTP: {res.status_code}")
            print(f"Resposta JSON: {res.get_json()}")

📄 O Contrato JSON enviado pelo App Mobile (Payload):

{
  "cliente_id": 45,
  "endereco_destino": "Av. Paulista, 1000 - São Paulo, SP",
  "peso_carga_kg": 12.5,
  "tipo_notificacao": "SMS"
}

📄 Documentação Swagger (OpenAPI 3.0 YAML) correspondente:

openapi: 3.0.3
info:
  title: API TecProExpress
  version: 1.0.0
paths:
  /api/entregas:
    post:
      summary: Criar nova entrega
      tags:
        - Entregas
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - cliente_id
                - endereco_destino
              properties:
                cliente_id:
                  type: integer
                endereco_destino:
                  type: string
                peso_carga_kg:
                  type: number
                tipo_notificacao:
                  type: string
      responses:
        '201':
          description: Criado com sucesso
        '400':
          description: Dados inválidos

💻 Execução do Servidor Flask & Teste cURL no Terminal

Para iniciar o servidor e executar requisições reais contra a API:

🔹 1. Iniciar Servidor:

python app_entregas.py --server

🔹 2. Requisição cURL enviando o Payload JSON:

curl -X POST "http://127.0.0.1:5000/api/entregas" \
     -H "Content-Type: application/json" \
     -d '{
       "cliente_id": 45,
       "endereco_destino": "Av. Paulista, 1000 - São Paulo, SP",
       "peso_carga_kg": 12.5,
       "tipo_notificacao": "SMS"
     }'

🖥️ Saída Esperada no Terminal:

HTTP/1.1 201 Created
content-type: application/json

{
  "cliente_id": 45,
  "endereco_destino": "Av. Paulista, 1000 - São Paulo, SP",
  "peso_carga_kg": 12.5,
  "tipo_notificacao": "SMS",
  "id": 125,
  "status": "PENDENTE"
}

🛠️ Prática Obrigatória 1: Tabela de Endpoints da API

Cenário: O projeto semestral da sua equipe.

  1. Mapeie 3 endpoints críticos que o seu sistema precisará expor para um aplicativo celular ou sistema parceiro.
  2. Organize-os em uma tabela contendo: Verbo HTTP, Caminho (URL), O que faz (Descrição) e Status Code de Sucesso (ex: 200, 201).

🏁 Resultado Esperado (Para sua Referência)

Uma tabela com o roteiro de comunicação REST detalhando as requisições principais de forma limpa e seguindo as boas práticas.


🛠️ Prática Obrigatória 2: O Payload JSON e Contrato Swagger

Cenário: Detalhando os dados de tráfego.

  1. Para o endpoint principal de inserção de dados da Prática 1 (Ex: criar agendamento, realizar venda), escreva o payload em formato JSON real que o cliente enviará.
  2. Escreva a especificação profissional simplificada no padrão Swagger/OpenAPI (formato YAML) que descreve esse endpoint e o corpo da requisição de forma estruturada.

📤 Instruções de Entrega (Microsoft Teams)

Após validar o seu design de API:

  1. Salve o arquivo contendo a tabela de endpoints, o JSON e a especificação Swagger YAML com o nome Atividade_14.md na pasta es-atv-14-apis-swagger/ do seu repositório GitHub.
  2. Certifique-se de fazer o commit e push para o repositório público.
  3. Submeta o link do seu repositório no Microsoft Teams para avaliação do professor.

💡 Checkpoint de Lógica

Importante

Reflexão Profissional: Por que no design REST profissional, nunca usamos o verbo GET para realizar a deleção de um dado (ex: GET /api/entregas/excluir?id=5) em vez do correto DELETE (ex: DELETE /api/entregas/5)? (Resposta: Pela natureza idempotente e segura dos verbos HTTP. Navegadores e proxies de cache assumem que requisições GET são seguras e não causam efeitos colaterais. Se você usar GET para excluir, um robô de busca de indexação automática da internet ao varrer os links do seu sistema poderia, acidentalmente, apagar o banco de dados inteiro da empresa!). 🧠🛡️

---

📊 Rubrica Formativa de Avaliação

Critério de Avaliação Insuficiente (0% - 40%) Regular (41% - 70%) Excelente (71% - 100%)
Design de Endpoints REST & Verbos HTTP Usa verbos incorretos (ex: GET para exclusão) ou URLs fora do padrão RESTful. Mapeia os 3 endpoints mas omite códigos de status de resposta (Status Codes). Tabela de endpoints impecável com verbos HTTP semânticos (GET, POST, PUT, DELETE) e Status Codes corretos (200, 201, 204).
Payload JSON & Especificação Swagger OpenAPI YAML Erros na sintaxe JSON ou formato YAML desalinhado. Escreve o JSON mas omite a especificação Swagger YAML. Payload JSON de requisição perfeito e contrato OpenAPI 3.0 YAML completamente válido.
Entrega no GitHub Entrega fora da pasta `es-atv-14-apis-swagger/`. Arquivo entregue mas com formatação YAML quebrada. Submete `Atividade_14.md` com formatação limpa e blocos de código formatados no repositório.

🐙 ATIVIDADE 15: GITFLOW E TRABALHO COLABORATIVO

📖 Fundamentação Teórica

Para realizar este laboratório com sucesso, certifique-se de ter compreendido os conceitos apresentados no:
👉 CAPÍTULO 15: DIAGRAMAS DINÂMICOS (ESTADOS E ATIVIDADES)

Bem-vindo a mais uma etapa prática de Engenharia de Software! Até agora, você criou especificações e diagramas. Mas na vida real de desenvolvimento corporativo, as equipes trabalham juntas em uma base de código única. Como fazer para que 10 programadores editem o mesmo arquivo do Spring Boot ao mesmo tempo sem que um apague a alteração do outro? Hoje, aprenderemos a dominar o controle de versão profissional usando o Git e a metodologia estratégica GitFlow. 🛡️🧩


🎯 Objetivos de Aprendizagem do Laboratório

Ao final deste laboratório prático (estimativa: 4 horas presenciais / autoguiadas), você será capaz de:

  • Trabalhar de forma colaborativa usando repositórios Git/GitHub.
  • Aplicar o padrão de ramificação GitFlow (main, develop, feature/, hotfix/).
  • Criar Pull Requests (PR) e conduzir processos de Code Review.
  • Identificar e resolver de forma segura os clássicos Conflitos de Merge (Merge Conflicts).

🏢 O Cenário Prático (Seu Desafio)

Na TecProExpress, os desenvolvedores Beto e Lucas estavam trabalhando na mesma tela de faturamento. Beto alterou o cálculo do frete adicionando o imposto do ICMS. Lucas, trabalhando no mesmo arquivo na mesma linha de código, mudou a lógica para aceitar Pix. Ao subir o código no final do dia, as alterações se atropelaram, o servidor do Git travou e metade da lógica do Beto sumiu da empresa!

O gerente técnico ficou furioso: "Como vocês não usam branches dedicadas e GitFlow?!".

"Seu desafio como Engenheiro de Configuração (DevOps) é implantar a metodologia GitFlow na TecProExpress. Você desenhará a topologia de branches mostrando a trilha do desenvolvimento de uma nova funcionalidade, como ela passa por revisão técnica e como resolver os conflitos de mesclagem lógicos se dois desenvolvedores tocarem no mesmo arquivo."


🧠 Fundamentos: A Teoria Traduzida

O Git é a ferramenta de controle de versão (como a máquina do tempo do seu código). O GitFlow é o "manual de trânsito" que diz quais faixas lógicas os desenvolvedores devem trafegar.

As Faixas Lógicas (Branches) do GitFlow:

  1. main (antiga master): A faixa rápida de alta segurança. Contém apenas o código que está rodando em produção de forma 100% testada e estável.
  2. develop: A faixa central de integração. Onde o time junta todas as novas funcionalidades que estão sendo desenvolvidas para a próxima versão.
  3. feature/nome-da-tarefa: Faixas locais temporárias. Cada programador cria a sua para trabalhar isoladamente em uma tarefa específica (Ex: feature/login-htmx).
  4. hotfix/correcao-critica: Faixa de emergência direta. Criada a partir da main para corrigir um bug grave que está derrubando a produção de forma imediata.

📊 Visualizando a Topologia GitFlow

Dica

Code Review: Nunca mescle seu código na branch de integração sem que pelo menos um outro colega de equipe faça o "Double Check" no seu Pull Request. Quatro olhos veem muito melhor do que dois!

```mermaid gitGraph commit id: "v1.0.0 (Setup)" branch develop checkout develop commit id: "Inicializar Spring Boot" branch "feature/login" checkout "feature/login" commit id: "Criar Controller de Login" commit id: "Integrar Thymeleaf e HTMX" checkout develop merge "feature/login" id: "Merge: Sprint 1" checkout main merge develop id: "Release v1.1.0" ```

Fluxo Colaborativo GitFlow


📖 Exemplo Guiado: Anatomia de um Conflito de Merge

Se dois desenvolvedores editarem a linha 42 do arquivo DeliveryService.java de forma diferente, o Git não saberá qual escolher. Ele gerará um Merge Conflict e marcará o arquivo assim:

<<<<<<< HEAD
    // Lógica do Desenvolvedor Lucas (Branch Local)
    double freteFinal = valorBase * 1.10;
=======
    // Lógica do Desenvolvedor Beto (Branch Remota)
    double freteFinal = (valorBase + impostoIcms) * 1.05;
>>>>>>> feature/calculo-beto

🛠️ Como resolver o conflito passo a passo:

  1. Analise com calma: O conflito exibe a seção superior (<<<<<<< HEAD até =======) contendo o seu código, e a seção inferior (======= até >>>>>>>) contendo o código de quem já enviou.
  2. Consenso técnico: Converse com seu colega de equipe para entender as necessidades de ambos os códigos.
  3. Edição Limpa: Apague as marcações vermelhas do Git (<<<<<<<, =======, >>>>>>>) e mescle as duas lógicas na melhor solução possível:
    // Lógica Consolidada em Equipe
    double freteFinal = (valorBase + impostoIcms) * 1.10;
  1. Commit de Resolução: Salve o arquivo editado e faça o commit de sucesso (git commit -am "Resolvendo conflito de cálculo de frete").

🛠️ Prática Obrigatória 1: A Linha do Tempo GitFlow

Cenário: O fluxo de versionamento do seu projeto semestral.

  1. Escreva a sintaxe do Mermaid gitGraph (baseado no exemplo guiado) representando a evolução do seu projeto.
  2. O seu gráfico deve apresentar:
    • O commit inicial na branch main.
    • A criação e checkout da branch develop.
    • A criação de uma branch de feature/sua-funcionalidade-principal com pelo menos 2 commits individuais.
    • O merge da feature de volta para a develop.
    • O merge final da develop para a main selando a versão estável v1.0.0.

🏁 Resultado Esperado (Para sua Referência)

O diagrama interativo gitGraph renderizado no corpo do seu arquivo de entrega na documentação do mdBook.



💻 Simulação de Comandos GitFlow & Saída no Terminal

Para vivenciar a sequência de comandos de terminal para criação de branches e merge de features:

# 1. Criar e entrar na branch de feature a partir de develop
git checkout develop
git checkout -b feature/calculo-frete

# 2. Realizar alterações e commitar
git add src/main/java/com/tecproexpress/service/DeliveryService.java
git commit -m "feat: implementar cálculo dinâmico de frete com ICMS"

# 3. Retornar para develop e mesclar feature
git checkout develop
git merge --no-ff feature/calculo-frete -m "merge: integrar feature de cálculo de frete"

🖥️ Saída Esperada no Terminal:

Switched to branch 'develop'
Switched to a new branch 'feature/calculo-frete'
[feature/calculo-frete 8a3f910] feat: implementar cálculo dinâmico de frete com ICMS
 1 file changed, 18 insertions(+)
Switched to branch 'develop'
Merge made by the 'ort' strategy.
 src/main/java/com/tecproexpress/service/DeliveryService.java | 18 ++++++++++++++++++
 1 file changed, 18 insertions(+)

🛠️ Prática Obrigatória 2: Manual de Resolução de Conflitos

Cenário: Proteção contra perda de arquivos.

  1. Simule (em formato texto dentro do seu Markdown) um arquivo do seu projeto que sofreu conflito em equipe.
  2. Escreva o bloco mostrando as tags de demarcação (<<<<<<<, =======, >>>>>>>) com os códigos conflitantes.
  3. Escreva logo abaixo o arquivo final resolvido e justifique a escolha técnica da mesclagem.

📤 Instruções de Entrega (Microsoft Teams)

Após validar suas especificações de versionamento técnico:

  1. Salve o arquivo contendo os diagramas GitGraph e a simulação de conflito com o nome Atividade_15.md na pasta es-atv-15-gitflow-colaboracao/ do seu repositório GitHub.
  2. Certifique-se de fazer o commit e push para o repositório público.
  3. Submeta o link do seu repositório no Microsoft Teams para avaliação do professor.

💡 Checkpoint de Lógica

Importante

Reflexão Profissional: Por que a metodologia GitFlow proíbe terminantemente que os desenvolvedores façam commits diretos e pushes na branch main de produção? (Resposta: Para garantir estabilidade e governança do produto. A branch main representa o software rodando nos clientes; se pushes diretos fossem permitidos, qualquer erro de digitação local que quebrasse a compilação seria publicado imediatamente, parando as operações de faturamento ou de logística da empresa). 🧠🛡️

---

📊 Rubrica Formativa de Avaliação

Critério de Avaliação Insuficiente (0% - 40%) Regular (41% - 70%) Excelente (71% - 100%)
Fluxo GitFlow & Diagrama Mermaid gitGraph Erros na sintaxe do Mermaid ou ramificação direta sem branch `develop`. Desenha o GitGraph mas sem mostrar o ciclo de vida completo de uma branch `feature`. Diagrama Mermaid `gitGraph` impecável mapeando commits na `main`, criação da `develop`, ramificação de `feature` e tag de release.
Resolução de Conflitos (Merge Conflict) Não entende as marcações do Git (<<<<<<<, =======, >>>>>>>). Simula o conflito mas resolve de forma destrutiva apagando lógicas válidas. Simulação técnica perfeita de conflito de código com resolução consensual limpa e justificativa.
Entrega no GitHub Entrega fora da pasta `es-atv-15-gitflow-colaboracao/`. Arquivo entregue sem os blocos de código formatados. Submete `Atividade_15.md` com GitGraph funcional e documentação limpa no repositório.

⚙️ ATIVIDADE 16: PIPELINES DE CI/CD COM GITHUB ACTIONS

📖 Fundamentação Teórica

Para realizar este laboratório com sucesso, certifique-se de ter compreendido os conceitos apresentados no:
👉 CAPÍTULO 16: QUALIDADE DE SOFTWARE (SQA)

Bem-vindo a mais uma etapa da sua jornada na Engenharia de Software! Nas atividades anteriores, aprendemos sobre GitFlow e arquitetura. Hoje, daremos um passo fundamental na automação de processos industriais de software: a Integração Contínua (CI). Aprenderemos como automatizar a compilação e execução de testes JUnit a cada Push que seu time faz para o repositório, garantindo blindagem contra códigos bugados! 🛡️🧩


🎯 Objetivos de Aprendizagem do Laboratório

Ao final deste laboratório prático (estimativa: 4 horas presenciais / autoguiadas), você será capaz de:

  • Compreender os conceitos fundamentais de CI/CD (Integração Contínua e Entrega Contínua).
  • Projetar e mapear pipelines lógicos e suas sequências de execução.
  • Configurar um arquivo YAML de automação corporativa real com GitHub Actions.
  • Integrar testes unitários JUnit em aplicações Spring Boot 3.5.x e Java 17 no fluxo de CI.

🏢 O Cenário Prático (Seu Desafio)

Na TecProExpress, a gerência estava cansada: toda vez que um desenvolvedor subia uma nova alteração de código, ele jurava de pés juntos que o sistema estava "funcionando perfeitamente". Porém, ao publicar na nuvem, o aplicativo do motorista quebrava porque alguém esqueceu de testar a alteração localmente!

Os testes manuais demoravam horas de digitação improdutiva e, frequentemente, bugs passavam para produção.

"Seu desafio como Analista de DevOps é criar o primeiro Pipeline de Integração Contínua (CI) para a TecProExpress. Você configurará um arquivo do GitHub Actions que, de forma 100% automática, baixa o código do repositório, instala o Java 17, compila a aplicação Spring Boot e executa todos os testes unitários do JUnit toda vez que um programador enviar um Push ou Pull Request."


🧠 Fundamentos: A Teoria Traduzida

CI (Continuous Integration): É a prática de integrar alterações de código com frequência e validá-las de forma automática. CD (Continuous Delivery): É a publicação automatizada do código validado no servidor web (produção).

Anatomia de um Workflow do GitHub Actions:

  • Workflow: O arquivo YAML que define a automação (salvo na pasta .github/workflows/).
  • Trigger (on): O evento que aciona o pipeline (Ex: push ou pull_request na branch develop/main).
  • Jobs (Trabalhos): As seções do pipeline (Ex: build-and-test).
  • Steps (Passos): A sequência de comandos que roda dentro de uma máquina virtual dedicada na nuvem (Runner).

📊 Visualizando a Sequência de Automação

flowchart LR
    Push["1. Git Push / PR"] --> Trigger{"2. Trigger do GitHub"}
    Trigger -->|Dispara Runner| VM["Máquina Virtual Ubuntu"]
    subgraph VM [VM na Nuvem do GitHub]
        direction TB
        S1["Step 1: Baixar Código (Checkout)"] --> S2["Step 2: Instalar Java 17 (JDK)"]
        S2 --> S3["Step 3: Compilar & Testar (Maven + JUnit)"]
    end
    VM -->|Falha no Teste| Block["Pipeline Vermelho: Bloquear Deploy!"]
    VM -->|Sucesso total| Pass["Pipeline Verde: Liberar Mesclagem!"]

    style VM fill:#f9f9f9,stroke:#333
    style Block fill:#ffebee,stroke:#c62828,stroke-width:2px
    style Pass fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px

📖 Exemplo Guiado: Roteiro e Configuração do Pipeline

Para configurar a automação, precisaremos de duas coisas no nosso repositório Java Spring Boot: o teste unitário de negócio (JUnit) e o arquivo do pipeline (Actions).

1. 🛠️ O Teste Unitário JUnit em Java 17 (Cálculo de Frete):

Dentro da pasta src/test/java/com/tecproexpress/ do seu projeto Spring Boot, nós escrevemos o teste de validade da regra de negócio:

package com.tecproexpress.service;

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

public class DeliveryServiceTest {

    @Test
    public void deveCalcularFreteComSucesso() {
        double pesoCarga = 10.0; // 10kg
        double valorBase = 50.0;
        
        // Regra da TecProExpress: R$ 2.00 por Kg excedente
        double freteFinal = valorBase + (pesoCarga * 2.0);

        // Asserção JUnit de Sucesso
        assertEquals(70.0, freteFinal, "O cálculo de frete da TecProExpress falhou!");
    }
}

2. 📄 O Arquivo do Pipeline .github/workflows/ci.yml:

Este é o arquivo YAML que o GitHub interpreta para automatizar a verificação:

name: Java CI com Maven e Spring Boot

on:
  push:
    branches: [ "main", "develop" ]
  pull_request:
    branches: [ "main", "develop" ]

jobs:
  build-and-test:
    runs-on: ubuntu-latest

    steps:
    # 1. Faz o download do código do repositório
    - name: Checkout do Código
      uses: actions/checkout@v4

    # 2. Configura a versão do Java no ambiente de execução
    - name: Instalar JDK 17
      uses: actions/setup-java@v4
      with:
        java-version: '17'
        distribution: 'temurin'
        cache: maven

    # 3. Compila a aplicação Spring Boot e roda os testes do JUnit
    - name: Executar Build e Testes JUnit com Maven
      run: mvn -B clean test

💻 Execução Local do Pipeline CI & Logs no Terminal

Para validar localmente a mesma esteira que o GitHub Actions executará na nuvem:

# Executando validação completa de build e testes (mesmo comando do workflow YAML)
mvn -B clean test

🖥️ Saída Esperada no Terminal de CI/CD (GitHub Actions / Local):

[INFO] Scanning for projects...
[INFO] Building tecproexpress-delivery 1.0.0
[INFO] -------------------------------------------------------
[INFO]  T E S T S
[INFO] -------------------------------------------------------
[INFO] Running com.tecproexpress.service.DeliveryServiceTest
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.041 s -- in com.tecproexpress.service.DeliveryServiceTest
[INFO]   deveCalcularFreteComSucesso PASSED

[INFO] Results:
[INFO]
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO] BUILD SUCCESS
[INFO] -------------------------------------------------------
🚀 [CI STATUS] Build & Testes concluidos com 100% de sucesso!

🛠️ Prática Obrigatória 1: Desenho do Pipeline

Cenário: Mapeamento visual das etapas de automação do seu projeto.

  1. Desenhe um diagrama flowchart no Mermaid representando a sequência de passos lógicos que o seu pipeline do GitHub Actions deve executar.
  2. Adicione cores ou estilos para indicar a diferença visual entre o sucesso e a falha de um passo de teste.

🏁 Resultado Esperado (Para sua Referência)

Um diagrama Mermaid detalhado demonstrando o ciclo de CI acionado por um Push de branch e o desfecho do build.


🛠️ Prática Obrigatória 2: O Roteiro de CI e Teste de Unidade

Cenário: Escrevendo sua primeira verificação automatizada.

  1. Crie a especificação em formato Markdown do seu arquivo de configuração .github/workflows/ci.yml customizado para o seu repositório do projeto.
  2. Escreva a classe de teste JUnit (Java) correspondente a uma validação de dados lógica do seu sistema (Ex: Validar se uma senha tem menos de 8 caracteres e falha, Validar se a idade do usuário é maior que 18 para aprovação).

📤 Instruções de Entrega (Microsoft Teams)

Após validar a sua arquitetura de automação de testes:

  1. Salve o arquivo de especificações e códigos com o nome Atividade_16.md na pasta es-atv-16-pipelines-cicd/ do seu repositório GitHub.
  2. Certifique-se de fazer o commit e push para o repositório público.
  3. Submeta o link do seu repositório no Microsoft Teams para avaliação do professor.

💡 Checkpoint de Lógica

Importante

Reflexão Profissional: Se durante a execução do pipeline de CI no GitHub Actions um único teste unitário do JUnit falhar (ex: acusando AssertionError), o que o GitHub fará com o Pull Request de mesclagem de código daquele desenvolvedor? (Resposta: O GitHub marcará o build como "Falho" (Status Vermelho) e bloqueará a mesclagem automática do Pull Request, impedindo que o código defeituoso contamine a branch estável de desenvolvimento develop ou main). 🧠🛡️

---

📊 Rubrica Formativa de Avaliação

Critério de Avaliação Insuficiente (0% - 40%) Regular (41% - 70%) Excelente (71% - 100%)
Pipeline CI/CD (GitHub Actions YAML & Mermaid) Erros na sintaxe YAML do GitHub Actions ou fluxo do pipeline sem passos de build/test. Escreve o workflow `.yml` mas sem personalizar para a linguagem do projeto. Pipeline CI em YAML perfeitamente funcional com gatilhos (`push`/`pull_request`), setup de JDK/ambiente e execução automatizada de testes.
Automação de Testes de Unidade (JUnit) Sem asserções de teste automatizadas ou sintaxe inválida. Escreve a classe JUnit mas com asserções genéricas. Classe de teste JUnit impecável com asserções de sucesso e tratamento de falhas lógicas do sistema.
Entrega no GitHub Entrega fora da pasta `es-atv-16-pipelines-cicd/`. Arquivo entregue mas sem validação visual da execução do pipeline. Submete `Atividade_16.md` com YAML e JUnit formatados e validados no repositório.

🐳 ATIVIDADE 17: CONTAINERIZAÇÃO DE AMBIENTES COM DOCKER

📖 Fundamentação Teórica

Para realizar este laboratório com sucesso, certifique-se de ter compreendido os conceitos apresentados no:
👉 CAPÍTULO 17: ESTRATÉGIAS DE TESTE e CAPÍTULO 04 DE BANCO DE DADOS: SETUP COMPLETO

Bem-vindo a mais uma etapa prática da sua jornada na Engenharia de Software! Após automatizarmos nossos testes no pipeline de CI/CD, agora resolveremos um dos problemas mais clássicos e irritantes no desenvolvimento de software: "Na minha máquina funciona, por que não funciona no servidor de produção?" 🤦‍♂️💻

Hoje, aprenderemos sobre Docker e Docker Compose, as tecnologias líderes de mercado que revolucionaram a forma de empacotar, distribuir e rodar aplicações em ambientes isolados e idênticos, garantindo previsibilidade absoluta! 🚀


🎯 Objetivos de Aprendizagem do Laboratório

Ao final deste laboratório prático (estimativa: 4 horas presenciais / autoguiadas), você será capaz de:

  • Compreender os conceitos fundamentais de Contêineres vs Máquinas Virtuais.
  • Escrever um Dockerfile otimizado para empacotar uma aplicação web Python 3.11+ com Flask 3.x.
  • Criar um arquivo docker-compose.yml para orquestrar múltiplos contêineres (App Flask + PostgreSQL 17) em rede local isolada.
  • Gerenciar redes virtuais privadas e volumes persistentes no ambiente Docker.

🏢 O Cenário Prático (Seu Desafio)

Na TecProExpress, a equipe de desenvolvimento estava enfrentando um caos diário de configuração. Cada desenvolvedor usava uma versão diferente do Python, um usava SQLite local, outro usava PostgreSQL nativo do Windows na porta 5432, e um terceiro usava uma versão desatualizada de dependências que quebrava as migrações. A integração de novos programadores demorava até 3 dias apenas para configurar o ambiente local!

"Seu desafio como Arquiteto de Soluções é padronizar e automatizar 100% do ambiente da TecProExpress. Você criará o arquivo Dockerfile da aplicação web (Python 3.11, Flask 3.x e SQLAlchemy 2.0) e um arquivo docker-compose.yml que sobe, com um único comando, o servidor da aplicação e o banco de dados PostgreSQL 17 configurados para conversar entre si em uma rede virtual isolada."


🧠 Fundamentos: A Teoria Traduzida

  • Imagem Docker: É um pacote imutável e somente-leitura que contém tudo o que é necessário para rodar a aplicação (código, dependências do requirements.txt, runtime do Python, variáveis de ambiente e arquivos).
  • Contêiner Docker: É a instância em execução de uma imagem. Ele compartilha o kernel do sistema operacional do host, tornando-o extremamente leve, rápido e com consumo mínimo de memória.
  • Dockerfile: O script com comandos sequenciais para construir a Imagem do contêiner.
  • Docker Compose: A ferramenta que permite definir e rodar aplicações multi-contêiner. Com um único comando (docker compose up), todas as dependências (banco relacional, cache, app web) sobem prontas.

📊 Arquitetura do Ambiente Orquestrado com Docker

flowchart TD
    subgraph Host ["Máquina do Desenvolvedor (Host OS)"]
        direction TB
        subgraph DockerNet ["Rede Virtual Isolada (tecpro-network)"]
            direction LR
            App["Contêiner Web: Flask 3.x + SQLAlchemy 2.0"] <-->|Porta Interna 5432| DB[("Contêiner Database: PostgreSQL 17")]
        end
        PortApp["Porta Física Exposta: 5000"] <-->|Direciona Requisições HTTP| App
    end
    
    subgraph Persistencia ["Armazenamento Externo"]
        Volume[("Volume Docker: pgdata")] <---> DB
    end

    style Host fill:#f5f7fa,stroke:#333,stroke-width:2px
    style DockerNet fill:#e1f5fe,stroke:#0288d1,stroke-width:2px
    style Persistencia fill:#fff3e0,stroke:#ef6c00,stroke-width:2px
    style App fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px
    style DB fill:#ffe0b2,stroke:#f57c00,stroke-width:2px

📖 Exemplo Guiado: Dockerização de Ponta a Ponta

Para empacotarmos a aplicação Flask 3.x da TecProExpress integrada ao PostgreSQL, utilizaremos a estratégia recomendada de infraestrutura como código.

1. 🛠️ O Arquivo Dockerfile da Aplicação

Criamos um arquivo com o nome exato Dockerfile (sem extensão) na raiz do projeto para definir a construção da imagem leve:

# Imagem base oficial leve do Python 3.11
FROM python:3.11-slim

# Evita a geração de arquivos .pyc e força flush imediato de logs
ENV PYTHONDONTWRITEBYTECODE=1 \
    PYTHONUNBUFFERED=1

WORKDIR /app

# Copia dependências e instala sem cache para manter a imagem enxuta
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Copia código-fonte da aplicação
COPY . .

# Expõe a porta padrão do Flask
EXPOSE 5000

# Comando para iniciar o servidor web
CMD ["python", "main.py"]

2. 📄 O Arquivo docker-compose.yml de Orquestração

Na mesma raiz do projeto, criamos o arquivo docker-compose.yml para conectar a nossa aplicação ao banco de dados PostgreSQL de forma indestrutível:

services:
  # Serviço 1: Banco de Dados PostgreSQL 17
  database:
    image: postgres:17-alpine
    container_name: tecpro-db
    restart: always
    environment:
      POSTGRES_DB: tecproexpress
      POSTGRES_USER: tecpro_admin
      POSTGRES_PASSWORD: SenhaSuperSegura123
    ports:
      - "5432:5432"
    volumes:
      - pgdata:/var/lib/postgresql/data
    networks:
      - tecpro-network
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U tecpro_admin -d tecproexpress"]
      interval: 5s
      timeout: 5s
      retries: 5

  # Serviço 2: Aplicação Web Flask 3.x
  web-app:
    build: .
    container_name: tecpro-web-app
    restart: always
    ports:
      - "5000:5000"
    environment:
      - DATABASE_URL=postgresql+psycopg2://tecpro_admin:SenhaSuperSegura123@database:5432/tecproexpress
    depends_on:
      database:
        condition: service_healthy
    networks:
      - tecpro-network

# Definição dos Recursos Compartilhados
volumes:
  pgdata:
    driver: local

networks:
  tecpro-network:
    driver: bridge

💻 Execução do Docker Compose & Logs no Terminal

Para subir todo o ecossistema (Aplicação Web + Banco de Dados PostgreSQL) com um único comando:

# Subir os contêineres e compilar a imagem em segundo plano
docker compose up -d --build

# Inspecionar o status dos serviços em execução
docker compose ps

🖥️ Saída Esperada no Terminal:

[+] Building 3.2s (8/8) FINISHED
[+] Running 3/3
 ✔ Network tecpro-network     Created                                      0.1s
 ✔ Container tecpro-db        Started                                      0.4s
 ✔ Container tecpro-web-app   Started                                      0.8s

NAME                IMAGE                 COMMAND                  SERVICE    STATUS     PORTS
tecpro-db           postgres:17-alpine    "docker-entrypoint.s…"   database   running    0.0.0.0:5432->5432/tcp
tecpro-web-app      tecproexpress-web-app "python main.py"         web-app    running    0.0.0.0:5000->5000/tcp

🛠️ Prática Obrigatória 1: Desenho da Rede de Contêineres

Cenário: Mapeamento conceitual do ecossistema de contêineres e fluxo de comunicação.

  1. Desenhe um diagrama flowchart no Mermaid representando a arquitetura dos contêineres da sua aplicação.
  2. Destaque as portas físicas do Host (computador físico) expostas e como elas se mapeiam para as portas internas dos contêineres do Docker.
  3. Evidencie a comunicação interna entre a aplicação e o banco PostgreSQL utilizando a rede virtualizada privada.

🏁 Resultado Esperado (Para sua Referência)

Um diagrama visual nítido exibindo o redirecionamento de portas (port-forwarding), volumes de persistência e a barreira da rede virtual.


🛠️ Prática Obrigatória 2: Criando os Arquivos de Configuração

Cenário: Escrevendo os scripts de containerização para o seu projeto semestral.

  1. Escreva o script Dockerfile personalizado para o seu sistema de grupo, adaptando caso seu ecossistema necessite de variáveis ou passos específicos.
  2. Escreva o arquivo docker-compose.yml que orquestra a aplicação de vocês integrada com um banco de dados persistido e mapeamento de portas do host para o contêiner.

📤 Instruções de Entrega (Microsoft Teams)

Após projetar a sua infraestrutura containerizada:

  1. Salve o arquivo de especificações e códigos com o nome Atividade_17.md na pasta es-atv-17-conteineres-docker/ do seu repositório GitHub.
  2. Certifique-se de fazer o commit e push para o repositório público.
  3. Submeta o link do seu repositório no Microsoft Teams para avaliação do professor.

💡 Checkpoint de Lógica

Importante

Reflexão Profissional: No arquivo docker-compose.yml, a URL de conexão do banco de dados na aplicação web foi definida como postgresql+psycopg2://tecpro_admin:SenhaSuperSegura123@database:5432/tecproexpress em vez de localhost:5432. Por que a palavra database funciona ali dentro do Docker? E o que aconteceria com os dados do banco de dados do PostgreSQL quando o contêiner fosse deletado e recriado, considerando que usamos a seção volumes?

Respostas Técnicas:

  1. O Docker possui um servidor DNS interno embutido em suas redes do tipo bridge que resolve automaticamente o nome do serviço do Compose — no caso, database — diretamente para o endereço IP virtual interno do contêiner do Postgres.
  2. Graças à declaração do volume pgdata mapeado para o diretório /var/lib/postgresql/data, os dados bancários permanecem armazenados de forma persistente no sistema hospedeiro, não se perdendo mesmo quando o contêiner é totalmente destruído e recriado com docker compose down. 🐳💾🧠
---

📊 Rubrica Formativa de Avaliação

Critério de Avaliação Insuficiente (0% - 40%) Regular (41% - 70%) Excelente (71% - 100%)
Containerização com Dockerfile Dockerfile com sintaxe incorreta ou gerando imagens pesadas sem isolamento. Escreve o Dockerfile mas esquece flags de otimização (`--no-cache-dir`, variáveis de ambiente). Dockerfile impecável utilizando `python:3.11-slim`, variáveis `PYTHONDONTWRITEBYTECODE=1` e sem acúmulo de cache no pip.
Orquestração Multi-serviço (Docker Compose) Erros na sintaxe YAML do `docker-compose.yml` ou ausência de mapeamento de portas e volumes. Orquestra a aplicação e o banco mas esquece da persistência em `volumes`. Compose perfeito com serviços orquestrados (`depends_on` com `healthcheck`), DNS do Docker e volume persistente `pgdata`.
Entrega no GitHub Entrega fora da pasta `es-atv-17-conteineres-docker/`. Arquivo entregue sem a explicação da rede virtual do Compose. Submete `Atividade_17.md` contendo Dockerfile, Compose YAML e diagrama Mermaid de portas/redes formatados.

🛡️ ATIVIDADE 18: SEGURANÇA NO DESENVOLVIMENTO E OWASP

📖 Fundamentação Teórica

Para realizar este laboratório com sucesso, certifique-se de ter compreendido os conceitos apresentados no:
👉 CAPÍTULO 18: MANUTENÇÃO E EVOLUÇÃO

Bem-vindo a mais uma etapa crítica da sua jornada na Engenharia de Software! Até aqui, aprendemos a modelar, testar, automatizar e containerizar sistemas. Mas de que adianta um sistema incrivelmente rápido, com CI/CD verde e rodando em Docker, se ele puder ser facilmente invadido por um hacker, vazando dados confidenciais dos usuários? 😱🔓

Hoje, mergulharemos no universo do DevSecOps e do Desenvolvimento Seguro (Secure by Design). Aprenderemos a analisar vulnerabilidades antes mesmo de escrevermos o código, utilizando a metodologia de modelagem de ameaças STRIDE e as diretrizes internacionais da OWASP.


🎯 Objetivos de Aprendizagem do Laboratório

Ao final deste laboratório prático (estimativa: 4 horas presenciais / autoguiadas), você será capaz de:

  • Compreender a importância da segurança proativa no ciclo de desenvolvimento de software.
  • Aplicar a metodologia STRIDE para identificar ameaças na arquitetura do sistema.
  • Identificar as principais falhas do OWASP Top 10 (como Injection e Broken Authentication).
  • Implementar defesas de código e configurações seguras em Java 17 e Spring Boot.

🏢 O Cenário Prático (Seu Desafio)

Na TecProExpress, a segurança costumava ser vista como "problema do pessoal de redes" ou "algo para olhar depois que o software estiver pronto". Essa mentalidade cobrou seu preço: na semana passada, um motorista mal-intencionado conseguiu manipular o parâmetro id na URL da API e visualizou os dados pessoais de entrega e faturamento de outros clientes. Além disso, a tela de busca de fretes sofreu um ataque de injeção de SQL que quase apagou a tabela de produtos!

"Seu desafio como Engenheiro de Segurança de Aplicações (AppSec) é blindar a arquitetura da TecProExpress. Você conduzirá uma modelagem de ameaças STRIDE e programará mecanismos de proteção no Spring Boot para barrar ataques de injeção de código e vazamento de dados de autorização de forma definitiva."


🧠 Fundamentos: A Teoria Traduzida

1. A Metodologia STRIDE (Modelagem de Ameaças)

Criada pela Microsoft, serve para mapear o que pode dar errado em um sistema com base em seis pilares fundamentais:

  • Spoofing (Falsificação de identidade): Alguém fingindo ser outro usuário.
  • Tampering (Adulteração de dados): Modificar dados em trânsito ou no banco.
  • Repudiation (Repúdio): Um usuário alegar que não realizou uma ação por falta de logs auditáveis.
  • Information Disclosure (Vazamento de informações): Expor dados confidenciais a pessoas não autorizadas.
  • Denial of Service (Negação de Serviço): Derrubar o sistema por sobrecarga de requisições.
  • Elevation of Privilege (Elevação de privilégio): Um usuário comum executando funções de administrador.

2. OWASP Top 10

A Open Worldwide Application Security Project (OWASP) é uma fundação global sem fins lucrativos que mapeia e publica regularmente as 10 vulnerabilidades de segurança mais críticas e recorrentes em aplicações web no mundo todo.

📊 Vetores de Ataque Comuns vs Defesa Ativa

flowchart TD
    subgraph Vetores ["Vetores de Ataque (Ameaças)"]
        A1["Injeção de SQL (SQLi)"]
        A2["Manipulação de URL (BOLA/IDOR)"]
        A3["Brute Force (Senha Fraca)"]
    end

    subgraph Defesas ["Mecanismos de Defesa (Spring Boot)"]
        D1["Uso de Spring Data JPA / Prepared Statements"]
        D2["Controle de Acesso baseado em Roles (Spring Security)"]
        D3["Criptografia BCrypt + Rate Limiting"]
    end

    A1 -->|Mitigado por| D1
    A2 -->|Mitigado por| D2
    A3 -->|Mitigado por| D3

    style Vetores fill:#ffebee,stroke:#c62828,stroke-width:2px
    style Defesas fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px

📖 Exemplo Guiado: Codificação Segura e Defesa Ativa

Vamos analisar um exemplo de código vulnerável em Spring Boot e como corrigi-lo imediatamente usando boas práticas de programação.

1. Evitando a Injeção de SQL (SQL Injection)

Cenário vulnerável (O que NÃO fazer): Concatenar strings diretamente na consulta SQL permite que o atacante envie comandos maliciosos na variável input.

// VULNERÁVEL! Permite SQL Injection
public List<Pedido> buscarPedidosPorCidade(String input) {
    String query = "SELECT * FROM pedidos WHERE cidade = '" + input + "'";
    return entityManager.createNativeQuery(query, Pedido.class).getResultList();
}

Se o atacante passar o valor Joinville' OR '1'='1, a query executará retornando todos os pedidos do banco de dados!

Cenário Seguro (Como implementar): Usar consultas parametrizadas (Prepared Statements) ou Spring Data JPA, que higienizam as entradas automaticamente.

// SEGURO! Parâmetros higienizados pelo JPA/Hibernate
public List<Pedido> buscarPedidosPorCidadeSeguro(String input) {
    String query = "SELECT p FROM Pedido p WHERE p.cidade = :cidade";
    return entityManager.createQuery(query, Pedido.class)
                        .setParameter("cidade", input)
                        .getResultList();
}

2. Proteção de Acesso com Spring Security

Configuração para restringir privilégios em endpoints sensíveis da API (Evitando elevação de privilégios e Broken Access Control):

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .csrf(csrf -> csrf.disable()) // CSRF desabilitado apenas para fins didáticos de API
            .authorizeHttpRequests(auth -> auth
                // Endpoints públicos
                .requestMatchers("/api/public/**").permitAll()
                // Apenas motoristas autenticados podem ver entregas
                .requestMatchers("/api/entregas/**").hasRole("MOTORISTA")
                // Apenas gerentes podem deletar registros ou ver relatórios
                .requestMatchers("/api/admin/**").hasRole("GERENTE")
                .anyRequest().authenticated()
            )
            .httpBasic(Customizer.withDefaults()); // Autenticação básica simples
        
        return http.build();
    }
}

💻 Scanner de Segurança SAST (DevSecOps) & Saída no Terminal

Para auditar vulnerabilidades no código antes de cada commit, integramos o scanner estático de segurança via plugin Maven (SpotBugs + FindSecBugs):

# Executando scanner SAST via plugin Maven no código-fonte Java
mvn com.github.spotbugs:spotbugs-maven-plugin:check

🖥️ Saída Esperada no Terminal:

[INFO] --- spotbugs-maven-plugin:4.8.6:check (default-cli) @ tecproexpress-delivery ---
[INFO] BugInstance size is 1
[INFO] Error size is 0
--------------------------------------------------
>> Issue: [SQL_INJECTION_JPA] Possible SQL/JPQL Injection via native query concatenation
   Severity: High   Confidence: High   Rule: FindSecBugs SQL_INJECTION_JPA
   Location: com.tecproexpress.repository.PedidoRepository.buscarPedidosPorCidade(String)
   Linha: src/main/java/com/tecproexpress/repository/PedidoRepository.java:87
   86     public List<Pedido> buscarPedidosPorCidade(String input) {
   87         String query = "SELECT * FROM pedidos WHERE cidade = '" + input + "'";
--------------------------------------------------
>> Code scanned:
        Total classes analyzed: 24
        Total issues (by severity):
                Low: 0
                Medium: 0
                High: 1
[INFO] BUILD FAILURE
🛡️ [DEVSECOPS] Vulnerabilidade identificada! Bloqueando pipeline de deploy até correção com Prepared Statements/JPQL parametrizado.

🌐 Teste de Autorização com cURL (Acesso Permitido vs HTTP 403 Forbidden)

🔹 1. Tentativa sem Permissão de Administrador:

curl -X DELETE "http://127.0.0.1:8000/api/admin/pedidos/99" \
     -H "Authorization: Bearer token_usuario_comum"

🖥️ Resposta de Bloqueio (HTTP 403 Forbidden):

{
  "detail": "Acesso negado: Perfil requer privilégios de [GERENTE/ADMIN]"
}

🛠️ Prática Obrigatória 1: Matriz de Ameaças STRIDE

Cenário: Analisando a segurança do seu projeto de grupo.

  1. Crie uma tabela Markdown contendo uma análise de ameaças do seu sistema utilizando a metodologia STRIDE.
  2. Mapeie pelo menos 3 das 6 letras do STRIDE aplicadas ao seu projeto real.
  3. Preencha as colunas: Elemento do Sistema (ex: Tela de Login), Ameaça Identificada (ex: Usuário interceptar tráfego) e Mitigação Proposta (ex: Uso obrigatório de HTTPS e senhas salvas com Hash BCrypt).

🛠️ Prática Obrigatória 2: Higienização de Código

Cenário: Identificação e correção de falhas de segurança no código.

  1. Crie um arquivo Markdown demonstrando um trecho de código hipotético ou real do seu sistema que seja vulnerável a Injeção de SQL ou Falha de Autorização.
  2. Apresente, logo abaixo, a versão corrigida desse código (Refatoração de Segurança), explicando qual técnica foi aplicada para eliminar a vulnerabilidade.

📤 Instruções de Entrega (Microsoft Teams)

Após auditar e implementar as melhorias de segurança:

  1. Salve a sua matriz de ameaças e os trechos de código com o nome Atividade_18.md na pasta es-atv-18-seguranca-devsecops/ do seu repositório GitHub.
  2. Certifique-se de fazer o commit e push para o repositório público.
  3. Submeta o link do seu repositório no Microsoft Teams para avaliação do professor.

💡 Checkpoint de Lógica

Importante

Reflexão Profissional: Suponha que seu sistema utilize criptografia reversível (como AES) para salvar as senhas dos usuários no banco de dados, em vez de um algoritmo de hash criptográfico não-reversível (como BCrypt ou PBKDF2). Se um invasor conseguir acesso de leitura à tabela de usuários, por que salvar senhas com criptografia AES é perigoso? O que o invasor precisaria obter para ler todas as senhas em texto puro? (Resposta: A criptografia AES é bidirecional (simétrica), o que significa que se houver a chave de criptografia, o dado pode ser totalmente descriptografado para texto original. Se o invasor acessar o banco de dados e obter a chave de descriptografia que geralmente fica salva no código ou nas variáveis de ambiente do servidor, ele conseguirá reverter todas as senhas. Algoritmos de Hash como o BCrypt são unidirecionais e usam a técnica de Salting, tornando matematicamente impossível reverter o hash de volta à senha original, garantindo proteção mesmo que o banco de dados seja roubado). 🛡️🔑🧠

---

📊 Rubrica Formativa de Avaliação

Critério de Avaliação Insuficiente (0% - 40%) Regular (41% - 70%) Excelente (71% - 100%)
Modelagem de Ameaças (STRIDE) Tabela STRIDE ausente ou sem relação com os módulos do sistema. Preenche 3 letras da matriz STRIDE mas com mitigações genéricas. Matriz de Ameaças STRIDE completa identificando vulnerabilidades reais de arquitetura e mitigação técnica sólida (ex: HTTPS, BCrypt, RBAC).
Refatoração de Segurança & Prepared Statements Não consegue identificar trechos vulneráveis a SQL Injection ou Broken Access Control. Identifica a vulnerabilidade mas refatora sem usar consultas parametrizadas. Demonstração impecável de código vulnerável vs. código refatorado seguro utilizando Prepared Statements/JPA e Spring Security RBAC.
Entrega no GitHub Entrega fora da pasta `es-atv-18-seguranca-devsecops/`. Arquivo entregue sem os blocos de código formatados. Submete `Atividade_18.md` com tabelas STRIDE e trechos de código limpos e documentados no repositório.

📊 ATIVIDADE 19: MÉTRICAS DE SOFTWARE, ESTIMATIVAS E DORA

📖 Fundamentação Teórica

Para realizar este laboratório com sucesso, certifique-se de ter compreendido os conceitos apresentados no:
👉 CAPÍTULO 19: GERÊNCIA DE CONFIGURAÇÃO (SCM)

Bem-vindo a mais uma etapa indispensável da sua formação em Engenharia de Software! Nas últimas semanas, focamos intensamente em aspectos técnicos: codificação, arquitetura, testes, infraestrutura e segurança. Hoje, assumiremos o papel de Gestores de TI e Líderes Técnicos (Tech Leads) para responder a uma das perguntas mais desafiadoras feitas pelos diretores de qualquer empresa: "Quando o sistema estará pronto e quão eficiente é a nossa equipe de engenharia?" ⏰💼

Aprenderemos sobre técnicas de estimativas ágeis e conheceremos as famosas Métricas DORA, a referência global e científica de mercado usada para medir a velocidade de entrega e a estabilidade de equipes de alto desempenho em DevOps.


🎯 Objetivos de Aprendizagem do Laboratório

Ao final deste laboratório prático (estimativa: 4 horas presenciais / autoguiadas), você será capaz de:

  • Diferenciar estimativas tradicionais de estimativas ágeis (Story Points).
  • Calcular a velocidade de desenvolvimento (Velocity) de um time de desenvolvimento.
  • Compreender e calcular as 4 Métricas Chaves da DORA (Deployment Frequency, Lead Time, MTTR, Change Failure Rate).
  • Mapear e classificar o nível de maturidade operacional de uma equipe de software.

🏢 O Cenário Prático (Seu Desafio)

Na TecProExpress, a diretoria estava insatisfeita. Os projetos sempre atrasavam, as estimativas em "horas" eram imprecisas e, pior, ninguém sabia medir se as recentes automações (CI/CD, Docker e testes JUnit) realmente trouxeram melhorias reais para a velocidade ou se apenas aumentaram a complexidade técnica.

Os gerentes cobravam relatórios baseados em "linhas de código escritas por dia", o que incentivava os programadores a escreverem códigos inchados e ruins apenas para bater a meta.

"Seu desafio como Tech Lead / Agile Coach é reformular a forma de planejar e medir a eficácia do time da TecProExpress. Você calculará a capacidade da equipe em Story Points, estimará a entrega de um backlog crítico de rotas e implantará os indicadores DORA para provar cientificamente os benefícios das práticas DevOps no negócio."


🧠 Fundamentos: A Teoria Traduzida

1. Estimativas Ágeis vs Tradicionais

  • Estimativa Tradicional (Horas/Dias): Tenta adivinhar o tempo exato de uma tarefa. Costuma falhar porque ignora a complexidade subjetiva, interrupções e riscos técnicos.
  • Story Points (Estimativa de Esforço): Medida relativa baseada na Sequência de Fibonacci (1, 2, 3, 5, 8, 13...) que avalia o esforço, complexidade e incerteza de um item do backlog, comparando-o com tarefas já conhecidas.
  • Velocidade (Velocity): A soma de Story Points de tarefas que o time consegue entregar (mudar para o status 'Done') no período de uma Sprint (normalmente 2 semanas).

2. As 4 Métricas DORA (DevOps Research and Assessment)

O grupo de pesquisa do Google (DORA) comprovou estatisticamente que a performance de engenharia de software é classificada por 4 métricas equilibradas entre Velocidade de Entrega e Estabilidade do Sistema:

📊 O Quadrante de Indicadores DORA

flowchart TD
    subgraph DORA ["As 4 Métricas Fundamentais DORA"]
        direction TB
        subgraph Velocidade ["Eficácia & Velocidade (Entrega)"]
            direction LR
            DF["Deployment Frequency<br/>(Frequência de Deploy)"]
            LT["Lead Time for Changes<br/>(Tempo de Espera por Mudança)"]
        end
        subgraph Estabilidade ["Qualidade & Estabilidade (Segurança)"]
            direction LR
            CFR["Change Failure Rate<br/>(Taxa de Falha de Mudanças)"]
            MTTR["Mean Time to Restore<br/>(Tempo Médio de Recuperação)"]
        end
    end

    style DORA fill:#f9f9f9,stroke:#333,stroke-width:2px
    style Velocidade fill:#e1f5fe,stroke:#0288d1,stroke-width:2px
    style Estabilidade fill:#ffebee,stroke:#c62828,stroke-width:2px
  • Deployment Frequency (DF): Com que frequência sua equipe publica código em produção. (Meta Elite: Múltiplos deploys por dia).
  • Lead Time for Changes (LT): Quanto tempo leva para um commit de código sair da máquina do desenvolvedor e rodar com sucesso em produção. (Meta Elite: Menos de 1 hora).
  • Change Failure Rate (CFR): Qual a porcentagem de deploys em produção que causam falhas no sistema e exigem correção imediata (hotfix/rollback). (Meta Elite: 0% a 15%).
  • Mean Time to Restore (MTTR): Quanto tempo a equipe leva, em média, para recuperar o sistema de uma pane em produção. (Meta Elite: Menos de 1 hora).

📖 Exemplo Guiado: Calculando Capacidade e Métricas DevOps

A equipe de desenvolvimento da TecProExpress possui um histórico de desempenho nas últimas 3 Sprints de 15 dias:

  • Sprint 1: Entregou 24 Story Points.
  • Sprint 2: Entregou 30 Story Points (equipe mais focada).
  • Sprint 3: Entregou 21 Story Points (feriado e impedimento técnico).

1. Calculando a Velocidade Média do Time

$$\text{Velocidade Média} = \frac{24 + 30 + 21}{3} = 25 \text{ Story Points por Sprint}$$

Se o backlog total para o lançamento do novo módulo de motoristas tem um esforço estimado de 100 Story Points, o número de Sprints necessárias para entregar o projeto será: $$\text{Previsão de Sprints} = \frac{100 \text{ SP (Escopo)}}{25 \text{ SP (Velocidade)}} = 4 \text{ Sprints (aproximadamente 2 meses de trabalho)}$$

2. Painel de Indicadores DORA do Projeto (Simulado)

Com a adoção do pipeline CI/CD e Docker, o time registrou a seguinte performance no último mês:

  • Deploys em produção: 20 deploys efetuados no mês.
    • Deployment Frequency: Aprox. 1 deploy por dia útil (Nível: Alto).
  • Tempo de Commit a Produção: Em média 4 horas (desde o merge na main até rodar no servidor).
    • Lead Time for Changes: 4 horas (Nível: Alto).
  • Falhas pós-deploy: De 20 deploys, apenas 1 causou queda na API e exigiu rollback.
    • Change Failure Rate: $1 / 20 = \mathbf{5%}$ (Nível: Elite).
  • Tempo de recuperação da falha: O rollback demorou exatamente 20 minutos para ser efetuado.
    • Mean Time to Restore (MTTR): 20 minutos (Nível: Elite).

💻 Calculador de Velocidade Ágil & Métricas DORA em Python

Para auditar métricas de desempenho e projetar a capacidade de entrega do time:

# metricas_dora.py
sprints_passadas = [24, 30, 21]
velocidade_media = sum(sprints_passadas) / len(sprints_passadas)
backlog_restante_sp = 100
sprints_necessarias = round(backlog_restante_sp / velocidade_media, 1)

deploys_mes = 20
falhas_deploy = 1
change_failure_rate = (falhas_deploy / deploys_mes) * 100
mttr_minutos = 20
lead_time_horas = 4.0

print("=" * 65)
print("📊 PAINEL DE MÉTRICAS DORA & CAPACIDADE ÁGIL - TECPROEXPRESS")
print("=" * 65)
print(f"🏃 Velocidade Média: {velocidade_media:.1f} Story Points / Sprint")
print(f"📅 Backlog Restante: {backlog_restante_sp} SP -> Previsão de Entrega: {sprints_necessarias} Sprints")
print("-" * 65)
print("🎯 INDICADORES DORA (DevOps Research & Assessment):")
print(f"   • Deployment Frequency:   {deploys_mes} deploys/mês (Classificação: ALTO)")
print(f"   • Lead Time for Changes:  {lead_time_horas:.1f} horas      (Classificação: ALTO)")
print(f"   • Change Failure Rate:    {change_failure_rate:.1f}%         (Classificação: ELITE)")
print(f"   • Time to Restore (MTTR): {mttr_minutos} minutos      (Classificação: ELITE)")
print("=" * 65)

🖥️ Saída Esperada no Terminal:

=================================================================
📊 PAINEL DE MÉTRICAS DORA & CAPACIDADE ÁGIL - TECPROEXPRESS
=================================================================
🏃 Velocidade Média: 25.0 Story Points / Sprint
📅 Backlog Restante: 100 SP -> Previsão de Entrega: 4.0 Sprints
-----------------------------------------------------------------
🎯 INDICADORES DORA (DevOps Research & Assessment):
   • Deployment Frequency:   20 deploys/mês (Classificação: ALTO)
   • Lead Time for Changes:  4.0 horas      (Classificação: ALTO)
   • Change Failure Rate:    5.0%         (Classificação: ELITE)
   • Time to Restore (MTTR): 20 minutos      (Classificação: ELITE)
=================================================================

🌐 Exemplo de Payload JSON para Telemetria de Métricas DORA (Swagger /docs)

{
  "time_id": "TEAM-LOGISTICA-01",
  "periodo_coleta": "2026-03",
  "metricas_dora": {
    "deployment_frequency_mes": 20,
    "lead_time_hours": 4.0,
    "change_failure_rate_percent": 5.0,
    "mean_time_to_restore_minutes": 20
  },
  "classificacao_geral": "ELITE"
}

🛠️ Prática Obrigatória 1: Estimando o Backlog Ágil

Cenário: Planejando as entregas do projeto final de Engenharia de Software.

  1. Crie uma tabela Markdown listando 5 requisitos/funcionalidades chave restantes para a consolidação final do seu projeto de grupo.
  2. Atribua a cada funcionalidade uma estimativa em Story Points usando a escala de Fibonacci (1, 2, 3, 5, 8, 13). Justifique por que uma funcionalidade recebeu pontuação maior (ex: complexidade de banco de dados, API de terceiros) em comparação a uma simples (ex: CRUD básico).
  3. Defina uma velocidade média hipotética para o seu grupo (ex: 8 SP por Sprint de 1 semana) e calcule em quantas Sprints o grupo entregará o backlog total.

🛠️ Prática Obrigatória 2: Diagnóstico DORA do Time

Cenário: Avaliando a maturidade operacional e qualidade do processo de desenvolvimento.

  1. Crie uma seção no seu documento Markdown chamada "Painel DORA do Projeto".
  2. Defina e explique como seu grupo coletará ou simulará cada uma das 4 métricas DORA no fluxo de trabalho de desenvolvimento de vocês.
  3. Classifique o nível de maturidade simulada da sua equipe (Baixo, Médio, Alto ou Elite) com base em cada indicador de velocidade e estabilidade.

📤 Instruções de Entrega (Microsoft Teams)

Após estruturar os seus planos de estimativas e o painel DORA:

  1. Salve o arquivo de especificações com o nome Atividade_19.md na pasta es-atv-19-metricas-estimativas/ do seu repositório GitHub.
  2. Certifique-se de fazer o commit e push para o repositório público.
  3. Submeta o link do seu repositório no Microsoft Teams para avaliação do professor.

💡 Checkpoint de Lógica

Importante

Reflexão Profissional: Imagine que um gerente de TI tradicional decida cobrar do time uma meta agressiva de aumentar a Deployment Frequency (Frequência de Deploy) para 5 vezes ao dia, porém sem investir em automação de testes unitários (JUnit) ou pipeline de CI. O que acontecerá inevitavelmente com a métrica Change Failure Rate (Taxa de Falha de Mudanças) e com o MTTR (Tempo de Recuperação) da equipe? Por que as métricas DORA devem ser analisadas de forma equilibrada? (Resposta: Aumentar a velocidade de deploy sem automação de testes fará com que códigos não-validados sejam publicados muito mais rápido, disparando a taxa de falhas pós-deploy (CFR) para níveis alarmantes. Além disso, sem testes ou infraestrutura resiliente, o time gastará muito mais tempo localizando a origem de bugs em produção, aumentando drasticamente o MTTR. As métricas DORA são propositalmente balanceadas: a velocidade de entrega (DF e LT) deve sempre caminhando de mãos dadas com a estabilidade e qualidade (CFR e MTTR), garantindo rapidez sem sacrificar a segurança). 📊⚖️🧠

---

📊 Rubrica Formativa de Avaliação

Critério de Avaliação Insuficiente (0% - 40%) Regular (41% - 70%) Excelente (71% - 100%)
Estimativas Ágeis (Story Points & Fibonacci) Atribui pontos arbitrários sem usar a sequência de Fibonacci ou sem calcular a velocidade. Pontua 5 tarefas mas sem justificar as diferenças de complexidade técnica. Estimativa em Story Points impecável justificando complexidades técnicas e calculando a previsão realista de Sprints.
Diagnóstico de Métricas DevOps (DORA) Omite os 4 indicadores DORA (DF, LT, CFR, MTTR). Mapeia os indicadores mas sem classificar os níveis de maturidade da equipe. Painel DORA completo diagnosticando a velocidade e a estabilidade com reflexão sobre a métrica equilibrada.
Entrega no GitHub Entrega fora da pasta `es-atv-19-metricas-estimativas/`. Arquivo entregue mas sem tabelas organizadas em Markdown. Submete `Atividade_19.md` com tabelas de estimativas e painel DORA perfeitamente formatados.

🏆 ATIVIDADE 20: PROJETO FINAL AVANÇADO (FASE 2)

📖 Fundamentação Teórica

Para realizar este laboratório com sucesso, certifique-se de ter compreendido os conceitos apresentados no:
👉 CAPÍTULO 20: CONCLUSÃO E PRÓXIMOS PASSOS

Parabéns! Você alcançou a reta final da disciplina de Engenharia de Software (Projetos II)! 🎓🚀

Durante este semestre desafiador, você não apenas aprendeu os conceitos teóricos clássicos, mas os aplicou de forma prática e corporativa: desde o levantamento inicial de escopo e diagramação de casos de uso até a estruturação de códigos robustos em Java 17 / Spring Boot, criação de testes unitários com JUnit, design de APIs documentadas com Swagger, automação via GitHub Actions, conteinerização com Docker e gestão operacional orientada a métricas DORA.

Hoje, é o dia de consolidar essa vasta jornada prática em um único documento de nível executivo industrial: o Dossiê de Engenharia de Software Avançado - Fase 2.


🎯 Objetivos de Aprendizagem do Laboratório

Ao final deste laboratório prático (estimativa: 4 horas presenciais / autoguiadas), você será capaz de:

  • Consolidar artefatos de engenharia diversos em uma documentação técnica unificada e coerente de mercado.
  • Modelar a visão integradora de sistemas modernos cobrindo Processos, Arquitetura e DevOps.
  • Escrever resumos executivos profissionais focados em tomadas de decisões de negócios e viabilidade técnica.
  • Apresentar e defender o projeto técnico simulando o papel de um Arquiteto de Software ou CTO.

🏢 O Cenário Prático (Seu Desafio)

Na TecProExpress, a transformação digital foi um sucesso estrondoso. Graças ao trabalho árduo da equipe de engenharia, a empresa saiu do caos operacional e agora opera com pipelines automáticos, deploys seguros e isolados, e métricas claras de desempenho.

Os investidores e diretores da TecProExpress convocaram uma reunião geral do comitê executivo. Eles querem ver o resultado consolidado e a maturidade técnica da nova arquitetura de rotas de ponta a ponta antes de liberarem a verba milionária de expansão internacional do produto!

"Seu desafio como Chief Technology Officer (CTO) ou Arquiteto Líder da TecProExpress é estruturar e apresentar o Dossiê de Engenharia de Software Avançado. Você reunirá todos os artefatos desenvolvidos individualmente ou em grupo ao longo das últimas 10 semanas em uma documentação indestrutível e demonstrará a robustez operacional do seu projeto."


🧠 Fundamentos: A Visão Integradora Moderna

A engenharia moderna de software não enxerga a infraestrutura ou o processo de negócios como silos separados. O sucesso industrial depende da integração simétrica de três pilares centrais:

📊 O Mapa de Engenharia de Software Integrada

flowchart TD
    subgraph Pilar1 ["1. Engenharia de Processos (UML)"]
        direction TB
        P1["Diagrama de Atividades"] --> P2["Diagrama de Estados"]
    end

    subgraph Pilar2 ["2. Engenharia de Software (Arquitetura)"]
        direction TB
        A1["Arquitetura MVC & Patterns"] --> A2["Especificação Swagger/JSON"]
    end

    subgraph Pilar3 ["3. Engenharia de Operações (DevOps)"]
        direction TB
        D1["Workflow GitHub Actions (CI)"] --> D2["Docker Compose (Ambiente)"]
    end

    Pilar1 <-->|Alimenta| Pilar2
    Pilar2 <-->|Automatizado por| Pilar3

    style Pilar1 fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px
    style Pilar2 fill:#e1f5fe,stroke:#0288d1,stroke-width:2px
    style Pilar3 fill:#fff3e0,stroke:#ef6c00,stroke-width:2px
  1. Engenharia de Processos: Modela o comportamento e as regras de negócio sob a notação UML padrão de mercado (Diagramas de Atividades e Transições de Estado).
  2. Engenharia de Software: Define a separação estrutural de responsabilidades lógicas no código (Spring Boot, Thymeleaf e HTMX) e expõe as interfaces de comunicação claras e documentadas (Endpoints HTTP REST e Swagger).
  3. Engenharia de Operações (DevOps): Automatiza a esteira de validações de qualidade e isola as dependências de infraestrutura, garantindo portabilidade absoluta (GitHub Actions CI/CD e contêineres Docker).

📖 Roteiro de Consolidação: Estrutura do Dossiê Final

A entrega final do seu projeto de Engenharia de Software deve seguir rigidamente a estrutura formal de um Dossiê Técnico de mercado. O seu documento final deve conter as seguintes seções estruturadas:

📑 Estrutura Sugerida do Dossiê Técnico

# DOSSIÊ DE ENGENHARIA DE SOFTWARE AVANÇADO: [NOME DO SEU PROJETO]

## 1. Introdução e Resumo Executivo
* Apresentação curta do problema de negócio e os objetivos principais do sistema.

## 2. Engenharia de Processos UML (Atividades 11 e 12)
* O Diagrama de Atividades atualizado com as raias de responsabilidades paralelas.
* O Diagrama de Transição de Estados da principal entidade de negócio (ex: Entrega, Pedido).

## 3. Arquitetura de Software e APIs REST (Atividades 13 e 14)
* Visão geral da arquitetura de classes e pastas (Padrões e MVC Spring Boot).
* O contrato OpenAPI/Swagger detalhado em formato YAML/JSON dos endpoints do sistema.

## 4. Gerência de Configuração e DevOps (Atividades 15 e 16)
* O gráfico GitFlow (`gitGraph`) simulando o fluxo de trabalho colaborativo seguro.
* O arquivo de configuração do pipeline de CI (`ci.yml`) do GitHub Actions.

## 5. Containerização e Segurança (Atividades 17 e 18)
* Os arquivos `Dockerfile` e `docker-compose.yml` criados para subir a infraestrutura.
* A Matriz de Ameaças de Segurança (STRIDE) e um exemplo prático de correção de vulnerabilidade.

## 6. Métricas Operacionais e de Desempenho (Atividade 19)
* O cálculo de velocidade estimada da equipe (Story Points e Fibonacci).
* O diagnóstico simulado dos 4 indicadores DORA das práticas DevOps do grupo.

## 7. Conclusões e Aprendizados
* Breve parágrafo auto-avaliativo sobre os aprendizados práticos do grupo durante a disciplina.

💻 Pipeline de Homologação Final Automatizada & Logs no Terminal

Para executar o checklist completo de homologação do projeto final (Lint, Testes, Build de Imagem e Healthcheck):

# Executando checklist de homologação (build, testes e verificações estáticas)
mvn -B verify

🖥️ Saída Esperada no Terminal de Homologação:

=================================================================
🏆 AUDITORIA DE HOMOLOGAÇÃO DO PROJETO FINAL (FASE II)
=================================================================
[1/5] 🔍 Validando Sintaxe dos Diagramas Mermaid ................. [ OK ]
[2/5] 🧪 Executando Testes de Unidade e Integração (JUnit/Surefire) [ OK ] (12/12)
[3/5] 🛡️  Auditando Vulnerabilidades de Segurança (SAST SpotBugs/FindSecBugs) [ OK ] (0 issues)
[4/5] 🐳 Validando Configuração do Docker Compose ............... [ OK ]
[5/5] 📊 Calculando Indicadores DORA e Velocidade Ágil .......... [ OK ] (Elite)
-----------------------------------------------------------------
✨ RESULTADO FINAL: PROJETO APROVADO PARA PRODUÇÃO COM GRAU A!
=================================================================

🌐 Exemplo de Requisição cURL para Healthcheck da Aplicação em Produção

curl -X GET "http://127.0.0.1:8080/actuator/health" \
     -H "Accept: application/json"

🔹 Resposta JSON de Healthcheck do Sistema:

{
  "status": "UP",
  "components": {
    "db": {
      "status": "UP",
      "details": {
        "database": "PostgreSQL",
        "validationQuery": "isValid()"
      }
    },
    "diskSpace": {
      "status": "UP",
      "details": {
        "total": 499963174912,
        "free": 312548901888,
        "threshold": 10485760
      }
    }
  }
}

🛠️ Prática Obrigatória: A Consolidação do Dossiê de Engenharia

Cenário: Organizando o repositório e a apresentação final do produto de software.

Como equipe de engenharia do projeto semestral de vocês:

  1. Reúnam-se para revisar todos os artefatos práticos produzidos individualmente ou coletivamente nas Atividades 11 a 19.
  2. Corrijam qualquer imperfeição ou feedback técnico anterior (ex: ajustar a sintaxe de um diagrama Mermaid, corrigir um endpoint Swagger com verbo HTTP incorreto ou consertar uma credencial vulnerável no docker-compose).
  3. Criem o arquivo unificado Dossie_Engenharia_Fase_2.md na pasta final de entregas.
  4. Garantam que todos os diagramas Mermaid do documento final renderizem perfeitamente, usando aspas duplas nos nós contendo caracteres especiais ou parênteses, evitando bugs visuais.

📤 Instruções de Entrega (Microsoft Teams e GitHub)

Após estruturar o seu Dossiê Técnico Final Avançado:

  1. Salve o documento completo com o nome Dossie_Engenharia_Fase_2.md na pasta es-atv-20-projeto-final/ na raiz do seu repositório Git público do grupo.
  2. Certifique-se de realizar o commit final com uma mensagem profissional (Ex: docs: consolidação do dossiê de engenharia fase 2).
  3. Submeta o link do repositório final e a cópia em PDF do dossiê no Microsoft Teams para avaliação e agendamento da apresentação oral presencial para o professor e convidados.

💡 Checkpoint de Lógica

Importante

Reflexão Profissional e de Carreira: Ao final deste ciclo, qual o real valor de um Engenheiro de Software moderno que sabe modelar requisitos e processos de negócio (UML), arquitetar código limpo e desacoplado (MVC/Spring Boot), testar de forma automatizada (CI/CD) e orquestrar infraestrutura em nuvem de forma resiliente (Docker)? Como esse conjunto integrado de competências se traduz em vantagens competitivas em relação a um programador tradicional que apenas digita código bruto sem compreender arquitetura ou DevOps? (Resposta: O engenheiro completo ("T-Shaped") consegue enxergar a visão holística do produto. Ele não se limita a escrever linhas de código vulneráveis e isoladas; ele projeta sistemas seguros, escaláveis e altamente portáveis. Isso reduz custos operacionais drásticos para a empresa, elimina falhas críticas pós-deploys em produção e acelera a inovação operacional medida cientificamente pela DORA. Profissionais com essa visão de ponta a ponta são extremamente disputados por grandes corporações globais, assumindo papéis de liderança técnica como Tech Leads, Arquitetos de Software e CTOs). 🏆📈🧠

---

📊 Rubrica Formativa de Avaliação

Critério de Avaliação Insuficiente (0% - 40%) Regular (41% - 70%) Excelente (71% - 100%)
Consolidação do Dossiê de Engenharia Fase II Faltam mais de 3 seções do dossiê ou com artefatos quebrados/desalinhados. Consolida o documento de 7 seções mas sem padronização nos diagramas Mermaid. Dossiê técnico profissional completo (`Dossie_Engenharia_Fase_2.md`) integrando UML, Arquitetura, Swagger, GitFlow, CI/CD, Docker, STRIDE e DORA.
Qualidade da Apresentação Oral & Portfólio Apresentação informal sem demonstração técnica da arquitetura. Apresenta o sistema mas sem demonstrar o funcionamento do pipeline ou Docker. Apresentação executiva impecável demonstrando visão holística de produto, arquitetura de software e DevOps de ponta a ponta.
Entrega do Repositório Final Entrega com arquivos avulsos fora do repositório público. Entrega no GitHub mas sem a versão PDF anexada no Teams. Submete `Dossie_Engenharia_Fase_2.md` na pasta `es-atv-20-projeto-final/` com links válidos e PDF submetido no Teams.

Banco de Dados e Aplicações

📋 Plano de Ensino

Disciplina: Banco de Dados e Aplicações
Carga Horária: 80 horas (Aulas) + 40 horas (Atividades Autônomas)
Curso: Tecnólogo

🎯 Objetivo

Entender fundamentos e arquitetura de sistemas de bancos de dados bem como técnicas de projeto e implementação de banco de dados com o uso de ferramentas.

📚 Ementa

  • Sistemas de Arquivos vs. SGBD.
  • Sistemas de gerenciamento de banco de dados (SGBD): arquitetura e aspectos operacionais.
  • Aplicações e tecnologias emergentes em Banco de Dados.
  • Técnicas e ferramentas de gerenciamento de Banco de dados.
  • Storage.
  • Controle de concorrência.
  • Segurança e integridade.
  • Modelagem de dados a partir do modelo de negócios.
  • Modelo entidade-relacionamento e suas extensões.
  • Mapeamento de modelo Entidade-Relacionamento para modelo relacional.
  • Formas Normais (Normalização).
  • Linguagem de Manipulação (DML) e de Descrição (DDL) de dados.
  • Projeto e Implementação de Banco de Dados com ferramentas de produtividade.

📖 Bibliografia Básica

  1. BEIGHLEY, Lynn. Use a Cabeça SQL. Alta Books, 2008.
  2. HEUSER, C.A. Projeto de Banco de Dados. Serie Livros Didáticos, V.4. Bookman, 2009.
  3. SILBERSCHATZ, A.; KORTH, H. F.; SUDARSHAN, S. Sistema de Banco de Dados. Campus, 2006.

📖 Bibliografia Complementar

  1. MACHADO, Felipe Nery R. Banco de Dados – Projeto e implementação. São Paulo: Érica, 2004.
  2. ELMASRI, R.; NAVATHE, S. B. Sistemas de Banco de Dados: Fundamentos e Aplicações. SP: Pearson, 2005.

🧭 Navegação por Módulos

  1. Introdução e Arquitetura de SGBD
  2. Modelagem Conceitual (Entidade-Relacionamento)
  3. Modelagem Relacional e Normalização
  4. Linguagem SQL (DDL e DML)
  5. Administração, Concorrência e Segurança
  6. Projeto Prático e Tendências

🧰 CAPÍTULO 00: KIT DE SOBREVIVÊNCIA — BANCO DE DADOS


📖 Pré-requisito

Este capítulo assume que você já passou pelo Capítulo 00 — Fundamentos Comuns (terminal, Git, POO em Python, JSON/HTTP). Se ainda não viu, comece por lá.

🎯 Objetivos de Aprendizagem

Estimativa de dedicação: 2 horas de estudo autoguiado. Ao final deste capítulo, você será capaz de:

  • 🔹 Ler e escrever SELECT/INSERT básicos — o suficiente para acompanhar as Atividades 02 a 04, antes de o DDL/DML formal chegar na Atividade 05.
  • 🔹 Ler a notação Crow's Foot (pé de galinha) usada em todos os diagramas ER do curso.
  • 🔹 Subir e derrubar seu primeiro container Docker com um banco de dados.

🏢 Por que isso importa

A Atividade 01 já pede para você subir um banco via Docker, e as Atividades 02 a 04 já usam CREATE TABLE/INSERT/SELECT em exemplos e scripts de seed — três semanas antes de a Atividade 05 ensinar DDL formalmente. Este capítulo adianta o básico para você acompanhar sem travar.


🧠 1. SQL de Leitura em 15 Minutos

SQL é a linguagem para conversar com um banco relacional. Três comandos resolvem 80% do que você vai precisar nas primeiras semanas:

💻 Exemplo Completo

-- 1. Criar a estrutura (tabela) que vai guardar os dados
CREATE TABLE produto (
    id INT PRIMARY KEY AUTO_INCREMENT,
    nome VARCHAR(100) NOT NULL,
    preco DECIMAL(10,2) NOT NULL
);

-- 2. Inserir dados
INSERT INTO produto (nome, preco) VALUES ('Notebook TecPro X1', 3500.00);
INSERT INTO produto (nome, preco) VALUES ('Mouse Sem Fio', 89.90);

-- 3. Consultar dados
SELECT nome, preco FROM produto WHERE preco > 100;

🖥️ Resultado da Consulta

+----------------------+---------+
| nome                 | preco   |
+----------------------+---------+
| Notebook TecPro X1   | 3500.00 |
+----------------------+---------+

🔍 Detalhamento do Código:

  • CREATE TABLE: define o "molde" — nome da tabela e, entre parênteses, cada coluna com seu tipo (INT, VARCHAR(100), DECIMAL(10,2)).
  • PRIMARY KEY AUTO_INCREMENT: identificador único da linha, gerado automaticamente pelo banco.
  • INSERT INTO tabela (colunas) VALUES (...): adiciona uma linha nova.
  • SELECT colunas FROM tabela WHERE condição: recupera linhas que satisfazem a condição — aqui, só produtos com preço acima de 100 (por isso o "Mouse Sem Fio", 89.90, não aparece no resultado).

💡 Onde isso aparece de novo

Esse mesmo trio (CREATE TABLE / INSERT / SELECT) é reaproveitado em praticamente todo mini-projeto SQLAlchemy dos 20 capítulos teóricos — a diferença é que lá o SQL fica "escondido" atrás de classes Python.


🧠 2. Notação ER / Crow's Foot Ilustrada

Todos os diagramas de entidade-relacionamento do curso (a partir da Atividade 02) usam a notação Crow's Foot ("pé de galinha") para representar cardinalidade — quantas linhas de uma tabela se relacionam com quantas linhas de outra.

SímboloNomeSignificado
||Um, exatamenteExatamente 1
o|Zero ou um0 ou 1
o{Zero ou muitos0, 1 ou mais
|{Um ou muitos1 ou mais

💻 Exemplo em Mermaid erDiagram

erDiagram
    CLIENTE ||--o{ PEDIDO : realiza
    PEDIDO ||--|{ ITEM_PEDIDO : contém
    PRODUTO ||--o{ ITEM_PEDIDO : "está em"

🔍 Detalhamento do Diagrama:

  • CLIENTE ||--o{ PEDIDO: 1 Cliente (||) pode realizar 0 ou muitos Pedidos (o{) — mas todo Pedido pertence a exatamente 1 Cliente.
  • PEDIDO ||--|{ ITEM_PEDIDO: 1 Pedido tem 1 ou muitos Itens (|{) — um pedido sem nenhum item não faz sentido no negócio.
  • O lado com || ou o| (uma "barra") lê-se "um"; o lado com { (um "pé de galinha" aberto) lê-se "muitos".

🎯 Ferramenta oficial de modelagem

Para desenhar seus próprios diagramas ER, o curso usa o draw.io. O erDiagram do Mermaid acima é só para você aprender a ler rápido, direto no navegador — sem precisar abrir outra ferramenta.


🧠 3. Docker: Seu Primeiro Container

Um container é um "computador isolado dentro do seu computador", já com o banco de dados instalado e configurado — evita instalar MySQL/PostgreSQL direto no seu sistema operacional.

flowchart LR
    A["💻 Seu computador"] -->|"docker run"| B["📦 Container PostgreSQL"]
    B -->|"porta 5432"| C["🔌 Cliente SQL (pgAdmin / DBeaver)"]

    style B fill:#e3f2fd,stroke:#1e88e5

💻 Comandos Essenciais

# 1. Baixa a imagem e sobe um container PostgreSQL 17
docker run --name tecpro-postgres -e POSTGRES_PASSWORD=tecpro123 -p 5432:5432 -d postgres:17

# 2. Confere se o container está rodando
docker ps

# 3. Para o container quando terminar de usar
docker stop tecpro-postgres

🔍 Detalhamento do Comando:

  • --name tecpro-postgres: dá um nome amigável ao container (em vez de um ID aleatório).
  • -e POSTGRES_PASSWORD=tecpro123: variável de ambiente — a senha do usuário postgres dentro do container.
  • -p 5432:5432: mapeia a porta 5432 do container para a porta 5432 da sua máquina (host:container) — é assim que o pgAdmin/DBeaver consegue se conectar.
  • -d: roda o container em segundo plano (detached), sem travar o terminal.

⚠️ Erro clássico

Se docker run falhar com "port is already allocated", outro processo (talvez um PostgreSQL instalado direto no Windows) já está usando a porta 5432. Mude o mapeamento para -p 5433:5432 e conecte seu cliente SQL na porta 5433.


💡 Checkpoint de Lógica

Importante

Reflexão Profissional: Por que a notação Crow's Foot marca dois números em cada lado do relacionamento (ex: || de um lado e o{ do outro) em vez de só um? (Resposta: porque a cardinalidade real de um relacionamento sempre tem um mínimo e um máximo de cada lado — "um Pedido pertence a exatamente 1 Cliente" (mínimo=máximo=1) é uma regra de negócio diferente de "um Cliente tem 0 ou muitos Pedidos" (mínimo=0), e o diagrama precisa capturar as duas coisas para o mapeamento para tabelas na Atividade 03 funcionar sem ambiguidade.) 🧠🛡️


🧪 Quiz de Fixação e Autoavaliação

🧪 Quiz de Autoavaliação — Capítulo 00 (Banco de Dados)

1. No comando SELECT nome, preco FROM produto WHERE preco > 100;, o que a cláusula WHERE faz?

  • A) Cria a tabela produto.
  • B) Ordena os resultados por preço.
  • C) Filtra apenas as linhas que satisfazem a condição informada.
  • D) Insere uma nova linha na tabela.
💡 Ver Resposta e Justificativa

Resposta Correta: C
Justificativa: WHERE filtra linhas antes de retorná-las — só as que atendem à condição (preco > 100) aparecem no resultado.


2. Em CLIENTE ||--o{ PEDIDO, o que o símbolo o{ do lado de PEDIDO significa?

  • A) Exatamente 1 Pedido.
  • B) Zero ou muitos Pedidos.
  • C) Um ou muitos Pedidos, nunca zero.
  • D) O Pedido é opcional para o sistema existir.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: O "pé de galinha" aberto ({) combinado com o círculo (o) significa "zero ou muitos" — um Cliente pode existir sem nenhum Pedido ainda.


3. No comando docker run ... -p 5432:5432 -d postgres:17, para que serve o -p 5432:5432?

  • A) Define a senha do banco.
  • B) Mapeia a porta do container para uma porta acessível no seu computador.
  • C) Define o nome do container.
  • D) Roda o container em segundo plano.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: -p host:container conecta a porta interna do container a uma porta da sua máquina — sem isso, ferramentas como pgAdmin não conseguiriam alcançar o banco.


🛠️ Ponte para a Ação

🎯 Próximo Passo


📌 Resumo Executivo & Key Takeaways

  • SQL: CREATE TABLE define a estrutura, INSERT adiciona dados, SELECT ... WHERE recupera e filtra.
  • Crow's Foot: cada lado de um relacionamento tem um mínimo e um máximo — || é "exatamente 1", o{ é "zero ou muitos".
  • Docker: docker run -p host:container -d imagem sobe um banco isolado em segundo plano; docker ps confere, docker stop derruba.

🛢️ CAPÍTULO 01: INTRODUÇÃO E VISÃO GERAL DE BANCO DE DADOS


🎯 Objetivos de Aprendizagem

Ao final deste capítulo (estimativa: 2 horas de estudo autoguiado), você será capaz de:

  • 🔹 Diferenciar Dado bruto, Informação contextualizada e Conhecimento estratégico.
  • 🔹 Compreender o papel de um Sistema Gerenciador de Banco de Dados (SGBD) e sua evolução histórica (Hierárquico, Rede, Relacional e NoSQL).
  • 🔹 Dominar a Arquitetura ANSI/SPARC de 3 Níveis (Nível Externo/Visões, Nível Conceitual e Nível Interno/Físico).
  • 🔹 Implementar scripts de conexão e inspeção de catálogo em Python utilizando SQLAlchemy 2.0.

Seja bem-vindo ao módulo de Banco de Dados. Aqui aprenderemos os fundamentos conceituais e a implementação prática de sistemas relacionais modernos. 🛡️🧩

🏢 O Cenário Prático (Seu Desafio)

Imagine que você é o Arquiteto de Dados Júnior da TecProExpress. A empresa estava controlando entregas e clientes usando planilhas soltas do Excel, o que estava gerando duplicações, perda de dados e lentidão.

"O seu desafio é entender os pilares dos bancos de dados profissionais para migrar toda a operação da TecProExpress das planilhas para um Sistema Gerenciador de Banco de Dados (SGBD) robusto e seguro."


🧠 Fundamentos: A Teoria Traduzida

Explore os pilares que sustentam o universo dos dados:

📊 Visão Geral do Banco de Dados

flowchart TD
    root["🛢️ Banco de Dados Corporativo"]
    
    root --> C1["🧠 Conceitos Fundamentais"]
    C1 --> C1A["SGBD e Transações ACID"]
    C1 --> C1B["Big Data & Cloud Database"]
    C1 --> C1C["Ecossistema NoSQL"]
    
    root --> C2["📐 Modelagem de Dados"]
    C2 --> C2A["MER: Modelo Entidade-Relacionamento"]
    C2 --> C2B["Modelo Relacional & Mapeamento"]
    C2 --> C2C["Normalização (1FN, 2FN, 3FN)"]
    
    root --> C3["💻 Linguagem SQL"]
    C3 --> C3A["DDL: Estrutura e Schemas"]
    C3 --> C3B["DML: Manipulação e Transações"]
    C3 --> C3C["DQL: Consultas e Agregações"]
    
    root --> C4["🏢 Estudo de Caso TecProExpress"]
    C4 --> C4A["Frota e Rastreamento"]
    C4 --> C4B["Persistência Poliglota"]
    
    style root fill:#eff6ff,stroke:#2563eb,stroke-width:2px
    style C1 fill:#f0fdf4,stroke:#16a34a
    style C2 fill:#fffbeb,stroke:#d97706
    style C3 fill:#ede7f6,stroke:#7c3aed
    style C4 fill:#fce7f3,stroke:#db2777

🔍 Detalhamento dos Conceitos:

  • SGBD: "Sistema Gerenciador de Banco de Dados". É o programa que controla os dados (como o MySQL ou PostgreSQL).
  • ACID: Sigla para Atomicidade, Consistência, Isolamento e Durabilidade. São as regras que impedem que uma transferência bancária seja feita pela metade se acabar a energia.
  • DDL (Data Definition Language): Comandos que criam o "esqueleto" das tabelas.
  • DML (Data Manipulation Language): Comandos que inserem ou alteram os dados dentro do esqueleto.

🏛️ Arquitetura de 3 Níveis (ANSI/SPARC)

Para garantir a independência de dados, os SGBDs modernos organizam as informações em três camadas fundamentais:

flowchart TD
    subgraph NivelExterno["1. Nível Externo (Visões de Usuário)"]
        V1["📱 App Mobile / Painel Vendedor<br>(View: meus_pedidos)"]
        V2["📊 BI / Diretoria Financeira<br>(View: faturamento_mensal)"]
    end

    subgraph NivelConceitual["2. Nível Conceitual (Lógica Global / Schema)"]
        NC["🗄️ Tabelas & Relacionamentos Lógicos<br>(CLIENTE, PEDIDO, PRODUTO, ITEM_PEDIDO)"]
    end

    subgraph NivelInterno["3. Nível Interno / Físico (Armazenamento)"]
        NI["💾 Índices B-Tree, Páginas de 16KB no Disco, Tablespaces e Buffers"]
    end

    V1 -->|Mapeamento Externo/Conceitual| NC
    V2 -->|Mapeamento Externo/Conceitual| NC
    NC -->|Mapeamento Conceitual/Físico| NI

Arquitetura ANSI/SPARC de 3 Níveis

💡 O Manto dos Dados: O banco de dados não é apenas um "depósito", mas a base sólida sobre a qual toda a lógica de negócio de uma empresa é construída. 🛡️


📗 Stack Tecnológica Abordada

Adotamos uma abordagem comparativa (Poliglota) para maximizar sua empregabilidade:

TecnologiaFerramentaFoco Profissional
🐬 MySQL 8.4 LTSWorkbench 8.0Web (Node.js, PHP, Python).
🐘 PostgreSQL 17pgAdmin 4 v14Corporativo e Análise de Dados.
🍃 MongoDB 7.0Compass v1.4xFlexibilidade e Dados NoSQL.
⚙️ Apache Cassandra 4.xDocker / cqlshBig Data e Alta Disponibilidade.

Arquitetura de Persistência Poliglota


📖 Exemplo Guiado: A Diferença Prática (DDL -> DML)

Muitos iniciantes tentam inserir dados diretamente. Em um SGBD, precisamos seguir uma sequência lógica rígida: Primeiro a estrutura (DDL), depois os dados (DML).

🛠️ Código do Exemplo (Criar antes de Inserir)

-- PASSO 1: DDL (Criação da Tabela)
-- O 'CREATE TABLE' levanta as paredes antes de colocar os móveis.
CREATE TABLE cliente (
    id INT PRIMARY KEY,
    nome VARCHAR(100)
);

-- PASSO 2: DML (Inserção dos Dados)
-- O 'INSERT INTO' coloca os dados dentro da estrutura criada.
INSERT INTO cliente (id, nome) VALUES (1, 'TecProExpress');

🔍 Detalhamento do Código:

  • CREATE TABLE cliente: Instrução DDL que avisa ao banco para reservar um espaço chamado "cliente".
  • INT PRIMARY KEY: Define que a coluna "id" aceitará apenas números Inteiros e será a identificação única e obrigatória do registro.
  • VARCHAR(100): Define que a coluna "nome" aceita textos de até 100 caracteres.
  • INSERT INTO: Instrução DML que empurra os valores "1" e "TecProExpress" para dentro das colunas preparadas.

🛠️ Prática Obrigatória 1: Seu Primeiro Script

Cenário: A TecProExpress precisa de uma tabela para guardar o registro de seus veículos de frota.

  1. Crie a tabela veiculo com as colunas placa (Texto de 7 caracteres) e modelo (Texto de 50 caracteres).
  2. Insira 2 veículos de exemplo.

🚀 Script de Seed (Gabarito)

-- DDL
CREATE TABLE veiculo (
    placa VARCHAR(7) PRIMARY KEY,
    modelo VARCHAR(50)
);

-- DML
INSERT INTO veiculo (placa, modelo) VALUES ('ABC1234', 'Caminhão Mercedes');
INSERT INTO veiculo (placa, modelo) VALUES ('XYZ9876', 'Van Renault');

💻 Ponte Prática: Do SQL Manual ao SQLAlchemy 2.0 ORM

Como o desenvolvedor moderno conecta a teoria de DDL e DML à programação em Python 3.11?

🔴 1. A Abordagem Manual (Cursor e Strings SQL Soltas)

No modelo manual tradicional, enviamos comandos SQL como strings puras através de um cursor. O código fica sujeito a erros de digitação e retorna tuplas sem nomes:

# ❌ ABORDAGEM COM SQL MANUAL: Strings soltas e tuplas não tipadas
import sqlite3

conn = sqlite3.connect("frota_legada.db")
cursor = conn.cursor()

# DDL manual
cursor.execute("CREATE TABLE IF NOT EXISTS veiculo (placa TEXT PRIMARY KEY, modelo TEXT);")

# DML manual
cursor.execute("INSERT INTO veiculo VALUES ('ABC1234', 'Caminhão Volvo');")
conn.commit()

# Leitura via tupla posicional:
cursor.execute("SELECT * FROM veiculo;")
for linha in cursor.fetchall():
    print(f"Placa: {linha[0]}, Modelo: {linha[1]}") # ⚠️ Índices numéricos frágeis!

conn.close()

🟢 2. A Abordagem com SQLAlchemy 2.0 Declarative (Mapeamento ORM)

Com o SQLAlchemy 2.0, a tabela é representada como uma classe Python tipada com Mapped[...]. O SGBD é gerenciado com segurança e o objeto instanciado possui atributos nomeados:

# ✅ ABORDAGEM MODERNA COM SQLALCHEMY 2.0: Classes tipadas e sessões gerenciadas
from sqlalchemy import create_engine, String
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, Session

class Base(DeclarativeBase):
    pass

class VeiculoModel(Base):
    __tablename__ = "veiculos_frota"

    placa: Mapped[str] = mapped_column(String(7), primary_key=True)
    modelo: Mapped[str] = mapped_column(String(50), nullable=False)

    def __repr__(self) -> str:
        return f"VeiculoModel(placa='{self.placa}', modelo='{self.modelo}')"

🛠️ Mini-Projeto 01 (BD): Primeiro Schema Relacional e Persistência

Objetivo: Criar um banco de dados SQLite local, gerar as tabelas via DDL automático e persistir 3 veículos usando SQLAlchemy 2.0.

📋 Pré-requisitos e Instalação

No terminal do seu ambiente virtual (PowerShell ou Bash), instale a biblioteca necessária:

pip install sqlalchemy

💻 Código Completo e Autocontido (miniprojeto_01_bd_setup.py)

Crie o arquivo miniprojeto_01_bd_setup.py e insira o código abaixo integralmente:

"""
Mini-Projeto 01: Primeiro Schema Relacional e Persistência
Curso: GTI - Banco de Dados Relacionais e Engenharia de Software
Stack: Python 3.11+ | SQLAlchemy 2.0 | SQLite
"""
import os
from sqlalchemy import create_engine, select, String
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, Session

# 1. Definição Declarativa do Schema (DDL)
class Base(DeclarativeBase):
    pass

class VeiculoModel(Base):
    __tablename__ = "veiculos_frota"

    placa: Mapped[str] = mapped_column(String(7), primary_key=True)
    modelo: Mapped[str] = mapped_column(String(50), nullable=False)

    def __repr__(self) -> str:
        return f"VeiculoModel(placa='{self.placa}', modelo='{self.modelo}')"

# 2. Ponto de Entrada Executável
if __name__ == "__main__":
    DB_FILE = "tecpro_logistica.db"

    # Reset preventivo para garantir idempotência em testes repetidos
    if os.path.exists(DB_FILE):
        os.remove(DB_FILE)

    # Conexão com o motor do banco de dados (SQLite local)
    engine = create_engine(f"sqlite:///{DB_FILE}", echo=False)

    # DDL Automático: Cria todas as tabelas mapeadas no modelo
    print("🏗️ [DDL] Criando schema no banco de dados...")
    Base.metadata.create_all(bind=engine)

    # DML Seguro: Inserindo dados usando Sessão do SQLAlchemy
    print("📝 [DML] Persistindo veículos na base de dados...")
    with Session(engine) as session:
        v1 = VeiculoModel(placa="BRA2E19", modelo="Scania R450 6x2")
        v2 = VeiculoModel(placa="RIO9A88", modelo="Mercedes-Benz Actros")
        v3 = VeiculoModel(placa="SPO3F44", modelo="Volkswagen Delivery 11.180")

        # Evita duplicidades na execução repetida:
        session.merge(v1)
        session.merge(v2)
        session.merge(v3)
        session.commit()

    # DQL: Consultando os dados persistidos usando sintaxe SQLAlchemy 2.0
    print("\n🔍 [DQL] Consultando frota cadastrada (SQLAlchemy 2.0):")
    with Session(engine) as session:
        stmt = select(VeiculoModel)
        veiculos = session.scalars(stmt).all()
        for v in veiculos:
            print(f"  🚛 {v}")

🚀 Como Executar

Execute o script no terminal:

python miniprojeto_01_bd_setup.py

🖥️ Saída Esperada no Console

🏗️ [DDL] Criando schema no banco de dados...
📝 [DML] Persistindo veículos na base de dados...

🔍 [DQL] Consultando frota cadastrada (SQLAlchemy 2.0):
  🚛 VeiculoModel(placa='BRA2E19', modelo='Scania R450 6x2')
  🚛 VeiculoModel(placa='RIO9A88', modelo='Mercedes-Benz Actros')
  🚛 VeiculoModel(placa='SPO3F44', modelo='Volkswagen Delivery 11.180')

💡 Checkpoint de Lógica

Importante

Dica do Especialista: O mercado valoriza o profissional que entende a lógica por trás dos dados, não apenas quem decora comandos. Nunca inverta a ordem: sem DDL (estrutura), seu DML (dados) vai gerar um erro de "Tabela Inexistente". 🚀🛡️



🧪 Quiz de Fixação e Autoavaliação — Capítulo 01

1. Qual a diferença fundamental entre 'Dado' e 'Informação' na teoria de banco de dados?

  • A) Dado é o relatório final; Informação é o número bruto sem sentido.
  • B) Dado é um fato bruto isolado sem contexto (ex: '150'); Informação é o dado processado e contextualizado que gera significado (ex: 'Estoque de 150 unidades de parafusos').
  • C) Dado e Informação são exatamente a mesma coisa em computação.
  • D) Dado só existe em computadores de 64 bits.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: Dados são fatos brutos não processados. A informação surge quando o dado é contextualizado e interpretado pelo negócio para apoiar tomadas de decisão.


2. Na Arquitetura ANSI/SPARC de 3 Níveis, qual é o nível responsável por descrever a estrutura lógica de todo o banco de dados (tabelas, relacionamentos e restrições) de forma independente do armazenamento físico?

  • A) Nível Físico / Interno
  • B) Nível Conceitual / Lógico
  • C) Nível Externo (Visões de Usuário)
  • D) Nível de Rede
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: O Nível Conceitual define o esquema lógico global do banco. O Nível Externo define o que cada usuário vê (Views), e o Nível Interno cuida da gravação física em disco.


3. O que é a 'Independência Lógica de Dados' proporcionada por um SGBD moderno?

  • A) A capacidade de trocar o sistema operacional sem desligar o computador.
  • B) A capacidade de alterar o esquema conceitual (ex: adicionar uma nova coluna) sem precisar reescrever as consultas e aplicações que não utilizam aquela coluna.
  • C) A independência de não precisar pagar licença de software.
  • D) A ausência de regras de validação no banco.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: Independência lógica permite evoluir a estrutura do banco sem quebrar os programas e relatórios existentes que acessam as outras tabelas.


🎯 Laboratório Prático

Coloque este conhecimento em prática agora mesmo executando o roteiro autoguiado:
👉 ATIVIDADE 01: SETUP DO AMBIENTE

🏗️ CAPÍTULO 02: FUNDAMENTOS DE SGBDS E SISTEMAS DE ARQUIVOS


🎯 Objetivos de Aprendizagem

Ao final deste capítulo (estimativa: 2 horas de estudo autoguiado), você será capaz de:

  • 🔹 Analisar os 6 problemas clássicos da Era Pré-SGBD (Redundância, Inconsistência, Isolamento, Integridade, Atomicidade e Concorrência).
  • 🔹 Avaliar por que planilhas eletrônicas (Excel) são inadequadas para armazenamento de dados transacionais corporativos.
  • 🔹 Compreender os 4 pilares do modelo relacional: Autodescrição, Abstração, Múltiplas Visões e Concorrência.
  • 🔹 Implementar tabelas protegidas por restrições de integridade no SQLite e PostgreSQL.

Seja bem-vindo(a) à base da pirâmide do conhecimento em tecnologia. Nesta unidade, desconstruiremos a forma como as aplicações modernas armazenam e processam seu maior ativo: o Dado. 🛡️🧩

🏢 O Cenário Prático (Seu Desafio)

Imagine que você assumiu como Arquiteto de Banco de Dados na TecProExpress. A empresa atualizou o sistema de rastreamento de entregas, mas o setor de atendimento ainda salva os contatos dos clientes no Excel.

"Seu desafio é demonstrar para a diretoria, de forma técnica e prática, por que manter dados de clientes em planilhas gera inconsistência e por que a TecProExpress deve centralizar tudo no novo SGBD."


🧠 Fundamentos: A Teoria Traduzida

0. Sistemas de Arquivos: A Era Pré-SGBD

Antes de existir um SGBD, aplicações corporativas guardavam dados diretamente em arquivos no disco (um .dat ou .txt por programa, lido e escrito linha a linha pelo próprio código da aplicação). Esse modelo é chamado de Sistema de Processamento de Arquivos (File Processing System), e é o motivo histórico pelo qual o SGBD foi inventado.

flowchart LR
    subgraph "Era Pré-SGBD (Arquivos)"
        AppA["App de Vendas"] --> ArqA[("clientes_vendas.dat")]
        AppB["App de Cobrança"] --> ArqB[("clientes_cobranca.dat")]
    end

Cada aplicação mantinha sua própria cópia dos dados, sem nenhuma camada central de controle. Isso gerava seis problemas clássicos, catalogados na literatura de Banco de Dados:

ProblemaO que acontecia na prática
Redundância e InconsistênciaO mesmo cliente cadastrado em clientes_vendas.dat e em clientes_cobranca.dat; se o telefone mudasse, alguém tinha que lembrar de atualizar os dois arquivos — e quase sempre esquecia um.
Dificuldade de AcessoPara responder "quais clientes de SP compraram no último mês?", um programador precisava escrever um programa novo, específico, lendo o arquivo inteiro linha a linha.
Isolamento de DadosOs dados ficavam espalhados em formatos e arquivos diferentes, dificultando escrever um programa que precisasse combinar informações de mais de um arquivo.
Problemas de IntegridadeRegras de negócio (ex.: "saldo não pode ser negativo") ficavam soterradas dentro do código de cada aplicação, em vez de centralizadas — e cada app podia implementá-las (ou esquecê-las) de um jeito.
Problemas de AtomicidadeUma queda de energia no meio de uma transferência bancária podia debitar de uma conta sem creditar na outra, sem nenhum mecanismo de recuperação.
Anomalias de Acesso ConcorrenteDois funcionários editando o mesmo arquivo ao mesmo tempo podiam sobrescrever a alteração um do outro, sem aviso.
Problemas de SegurançaControlar quem podia ler ou alterar cada arquivo exigia lógica de permissão no sistema operacional, arquivo por arquivo — não havia um controle de acesso granular por dado.

💡 A Virada de Chave: O SGBD nasceu exatamente para resolver esses seis problemas de uma vez só, através de uma camada única de software entre a aplicação e o disco — é o que vamos explorar no restante deste capítulo.


1. O Ciclo de Vida da Informação

Na engenharia de software de alta performance, trabalhamos com ativos que seguem um fluxo de valor:

flowchart TD
    D["📄 DADO<br/>Fato Bruto"] --> P{"⚙️ SGBD<br/>Processamento"}
    P --> I["📊 INFORMAÇÃO<br/>Conhecimento"]
    I --> V["💰 VALOR<br/>Decisão Estratégica"]

🔍 Detalhamento do Fluxo:

  • DADO: Um fato isolado, por exemplo, o texto "TecProExpress". Sozinho, ele é mudo.
  • INFORMAÇÃO: O dado contextualizado. "TecProExpress é o nosso cliente VIP de transporte".
  • BANCO DE DADOS (DB): Uma coleção de dados logicamente relacionados.
  • SGBD (Sistema Gerenciador): O motor de execução (ex: MySQL 8.4) que processa esses dados em alta velocidade.

2. Por que não usar planilhas?

CaracterísticaPlanilhas (Excel) 📁SGBD Relacional (MySQL/Postgres) 🛡️
RedundânciaAlta (nomes repetidos)Minimizada e controlada
IntegridadeDepende de quem digitaAutomática com Constraints (CHECK, PK)
Acesso SimultâneoBloqueia o arquivo para outrosPermite milhares de transações ao mesmo tempo

📖 Exemplo Guiado: Protegendo os Dados (DDL -> DML)

A principal vantagem do SGBD é a Integridade. Veja como o SGBD impede erros que uma planilha permitiria. Lembre-se do fluxo obrigatório: primeiro desenhamos a tabela (DDL), depois inserimos o dado (DML).

🛠️ Código do Exemplo

-- PASSO 1: DDL (Criação da Estrutura com Regras)
CREATE TABLE cliente_vip (
    id INT PRIMARY KEY,
    nome VARCHAR(100) NOT NULL,
    limite_credito DECIMAL(10,2) CHECK (limite_credito >= 0)
);

-- PASSO 2: DML (Inserção Válida)
INSERT INTO cliente_vip (id, nome, limite_credito) VALUES (1, 'TecProExpress', 5000.00);

-- PASSO 3: Tentativa de Erro (O SGBD bloqueia!)
-- INSERT INTO cliente_vip (id, nome, limite_credito) VALUES (2, 'Transportadora B', -100.00);

🔍 Detalhamento do Código:

  • NOT NULL: Regra que impede que um cliente seja salvo sem nome. (No Excel, isso passaria).
  • CHECK (limite_credito >= 0): Regra matemática. O SGBD recusa qualquer limite de crédito negativo. O Passo 3 falharia de propósito para proteger os dados.

🛠️ Prática Obrigatória: Abstração de Dados

Cenário: O catálogo interno da TecProExpress. Um SGBD guarda a "receita" do banco (Metadados) em um Catálogo interno. Isso permite que diferentes perfis vejam o mesmo dado de formas diferentes.

  1. Crie uma visão (View) simulando o painel de um Gerente, que quer ver os dados de faturamento.
  2. Não se preocupe se errar a sintaxe avançada, foque na estrutura.

🚀 Script de Seed (Gabarito de Visão)

-- PASSO 1: DDL (Criar Tabela Mestre)
CREATE TABLE faturamento (
    id INT PRIMARY KEY,
    filial VARCHAR(50),
    lucro_total DECIMAL(15,2)
);

-- PASSO 2: DML (Popular)
INSERT INTO faturamento VALUES (101, 'São Paulo', 250000.00);
INSERT INTO faturamento VALUES (102, 'Rio de Janeiro', 180000.00);

-- PASSO 3: DDL (Criar a VIEW - Visão do Usuário)
CREATE VIEW relatorio_gerente AS
SELECT filial, lucro_total FROM faturamento WHERE lucro_total > 200000;

🔍 Detalhamento do Seed:

  • CREATE VIEW: Cria um "atalho" ou "lente virtual" que filtra os dados originais. O Gerente só verá a filial de São Paulo.


💻 Ponte Prática: Do SQL Manual ao SQLAlchemy 2.0 ORM

Como o SQLAlchemy 2.0 traduz os conceitos de Integridade, Constraints e Catálogo de Metadados para o código Python?

🔴 1. A Abordagem Manual (Gravação em Arquivos / CSV sem Validação)

Sem as travas do SGBD, qualquer dado inválido é gravado no disco sem aviso:

# ❌ ABORDAGEM SEM SGBD: Dicionários ou CSV aceitam dados absurdos
def salvar_cliente_csv(nome, limite):
    with open("clientes.csv", "a") as f:
        f.write(f"{nome},{limite}\n")

salvar_cliente_csv(nome="", limite=-5000.0) # ⚠️ Aceito sem nenhum erro!

🟢 2. A Abordagem com SQLAlchemy 2.0 (CheckConstraint e Integridade de Domínio)

Com o SQLAlchemy 2.0, definimos CheckConstraint e nullable=False diretamente na declaração da classe. Se alguém tentar gravar um limite negativo, o próprio motor do banco rejeita:

# ✅ ABORDAGEM COM SQLALCHEMY 2.0: Restrições de Integridade Relacional
from sqlalchemy import create_engine, String, Float, CheckConstraint, select
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, Session
from sqlalchemy.exc import IntegrityError

class Base(DeclarativeBase):
    pass

class ClienteVipModel(Base):
    __tablename__ = "clientes_vip"
    __table_args__ = (
        CheckConstraint("limite_credito >= 0.0", name="chk_limite_positivo"),
    )

    id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
    nome: Mapped[str] = mapped_column(String(100), nullable=False)
    limite_credito: Mapped[float] = mapped_column(Float, nullable=False, default=0.0)

    def __repr__(self) -> str:
        return f"ClienteVip(id={self.id}, nome='{self.nome}', limite=R$ {self.limite_credito:.2f})"

🛠️ Mini-Projeto 02 (BD): Validador de Integridade e Constraints

Objetivo: Criar uma tabela protegida com CheckConstraint e testar como o banco bloqueia transações que violam regras de integridade de domínio.

📋 Pré-requisitos e Instalação

No terminal do seu ambiente virtual (PowerShell ou Bash), instale a biblioteca necessária:

pip install sqlalchemy

💻 Código Completo e Autocontido (miniprojeto_02_integridade.py)

Crie o arquivo miniprojeto_02_integridade.py e insira o código abaixo integralmente:

"""
Mini-Projeto 02: Validador de Integridade e Constraints
Curso: GTI - Banco de Dados Relacionais e Engenharia de Software
Stack: Python 3.11+ | SQLAlchemy 2.0 | SQLite
"""
import os
from sqlalchemy import create_engine, String, Float, CheckConstraint, select
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, Session
from sqlalchemy.exc import IntegrityError

# 1. Definição Declarativa com Restrições de Integridade (Constraints)
class Base(DeclarativeBase):
    pass

class ClienteVipModel(Base):
    __tablename__ = "clientes_vip"
    __table_args__ = (
        CheckConstraint("limite_credito >= 0.0", name="chk_limite_positivo"),
    )

    id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
    nome: Mapped[str] = mapped_column(String(100), nullable=False)
    limite_credito: Mapped[float] = mapped_column(Float, nullable=False, default=0.0)

    def __repr__(self) -> str:
        return f"ClienteVip(id={self.id}, nome='{self.nome}', limite=R$ {self.limite_credito:.2f})"

# 2. Ponto de Entrada Executável
if __name__ == "__main__":
    DB_FILE = "tecpro_integridade.db"

    # Reset preventivo para garantir reprodutibilidade em execuções sucessivas
    if os.path.exists(DB_FILE):
        os.remove(DB_FILE)

    engine = create_engine(f"sqlite:///{DB_FILE}", echo=False)
    Base.metadata.create_all(bind=engine)

    print("🛡️ TESTE DE INTEGRIDADE RELACIONAL COM SQLALCHEMY 2.0")
    print("=" * 60)

    # 1. Inserção Válida:
    with Session(engine) as session:
        cliente_valido = ClienteVipModel(nome="TecProExpress Matriz", limite_credito=15000.0)
        session.add(cliente_valido)
        session.commit()
        print(f"✅ Inserção Válida Concluída: {cliente_valido}")

    # 2. Tentativa de Inserção Inválida (Limite Negativo):
    with Session(engine) as session:
        try:
            print("\n⚠️ Tentando inserir cliente com limite negativo (-500.00)...")
            cliente_invalido = ClienteVipModel(nome="Cliente Inadimplente", limite_credito=-500.0)
            session.add(cliente_invalido)
            session.commit()
        except IntegrityError:
            session.rollback()
            print("🛡️ BLOQUEIO DO SGBD: Transação revertida com sucesso (IntegrityError)!")
            print("   Regra 'chk_limite_positivo' protegeu a consistência dos dados.")

    # 3. Consulta final confirmando que apenas o registro válido existe:
    with Session(engine) as session:
        stmt = select(ClienteVipModel)
        registros = session.scalars(stmt).all()
        print(f"\n📊 Total de registros íntegros no banco: {len(registros)}")

🚀 Como Executar

Execute o script no terminal:

python miniprojeto_02_integridade.py

🖥️ Saída Esperada no Console

🛡️ TESTE DE INTEGRIDADE RELACIONAL COM SQLALCHEMY 2.0
============================================================
✅ Inserção Válida Concluída: ClienteVip(id=1, nome='TecProExpress Matriz', limite=R$ 15000.00)

⚠️ Tentando inserir cliente com limite negativo (-500.00)...
🛡️ BLOQUEIO DO SGBD: Transação revertida com sucesso (IntegrityError)!
   Regra 'chk_limite_positivo' protegeu a consistência dos dados.

📊 Total de registros íntegros no banco: 1

💡 Checkpoint de Lógica

Importante

Reflexão Profissional: Qual a diferença entre um DB (Database) e um DBMS (Sistema Gerenciador de Banco de Dados)? O DB é o arquivo salvo no disco rígido. O DBMS é o software (MySQL, Postgres, SQLite) que você usa para conversar com esse arquivo e garantir a segurança dele de forma simultânea. 🧠🛡️



🧪 Quiz de Fixação e Autoavaliação — Capítulo 02

1. Qual problema clássico dos sistemas antigos baseados em arquivos ocorre quando o mesmo telefone de cliente é atualizado em um arquivo, mas esquecido em outro?

  • A) Problema de Conectividade de Rede
  • B) Redundância e Inconsistência de Dados
  • C) Falha de Hardware
  • D) Overflow de Memória
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: Sem um banco centralizado, dados duplicados em múltiplos arquivos ficam desalinhados quando ocorrem atualizações parciais, gerando inconsistência grave.


2. Por que uma planilha Excel não deve ser usada como banco de dados transacional de um sistema de vendas com 50 atendentes simultâneos?

  • A) Porque o Excel não aceita números decimais.
  • B) Porque planilhas bloqueiam o arquivo inteiro para escrita concorrente, não oferecem garantias transacionais ACID reais e não garantem integridade referencial automática.
  • C) Porque planilhas só funcionam no Windows.
  • D) Porque o Excel não permite criar colunas de texto.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: Planilhas não possuem controle transacional multi-usuário (MVCC), bloqueando o arquivo ou sobrescrevendo alterações de outros usuários sem aviso.


3. O que significa a característica de 'Autodescrição' (Auto-cataloga) de um banco de dados relacional?

  • A) O banco escreve o código Python sozinho.
  • B) O banco de dados armazena não apenas os dados do usuário, mas também os seus próprios metadados (definição de tabelas, tipos, constraints e índices) em um catálogo interno do sistema (ex: information_schema).
  • C) O banco apaga registros antigos automaticamente.
  • D) O banco avisa quando a memória RAM está cheia.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: SGBDs são autodescritivos: a estrutura do schema é mantida em tabelas de catálogo do próprio SGBD, permitindo que ORMs e ferramentas inspecionem os dados.


🎯 Laboratório Prático

Coloque este conhecimento em prática agora mesmo executando o roteiro autoguiado:
👉 ATIVIDADE 01: SETUP DO AMBIENTE

🔄 CAPÍTULO 03: TRANSAÇÕES ACID, NUVEM E CONCORRÊNCIA


🎯 Objetivos de Aprendizagem

Ao final deste capítulo (estimativa: 2 horas de estudo autoguiado), você será capaz de:

  • 🔹 Dominar os 4 pilares transacionais ACID: Atomicidade (Tudo ou Nada), Consistência (Regras Válidas), Isolamento (Sem Interferência) e Durabilidade (Gravado em Disco).
  • 🔹 Analisar os 4 Níveis de Isolamento ANSI SQL: Read Uncommitted, Read Committed, Repeatable Read e Serializable.
  • 🔹 Compreender o mecanismo de Write-Ahead Logging (WAL) e recuperação de falhas em SGBDs.
  • 🔹 Implementar transações seguras em Python utilizando session.begin(), session.commit() e session.rollback().

No coração de sistemas críticos (Bancos, Hospitais, Logística), existe o conceito de Transação. Uma transação não é apenas um comando SQL solto, mas uma Unidade Lógica de Trabalho indestrutível. 🛡️🧩

🏢 O Cenário Prático (Seu Desafio)

Você atua na TecProExpress. Imagine que o sistema debita R$ 1.500 do saldo de um cliente para pagar um frete internacional, mas antes do sistema creditar esse valor na conta da TecProExpress, o servidor de banco de dados sofre uma queda de energia.

"Seu desafio é arquitetar o banco de dados para garantir que, caso o fluxo inteiro não se complete, o dinheiro volte imediatamente para a conta do cliente (Rollback). Nenhum centavo pode desaparecer do sistema."


🧠 Fundamentos: O Escudo de Confiança (ACID)

Para garantir que o seu banco nunca entre em um estado corrupto, o SGBD aplica as propriedades ACID:

  1. ⚛️ Atomicidade (Tudo ou Nada): Se uma transação tem 10 passos e o passo 9 falha, os 8 anteriores são desfeitos automaticamente.
  2. ⚖️ Consistência (Regras): A transação leva o banco de um estado válido para outro estado válido (ex: o saldo nunca ficará negativo se houver um CHECK >= 0).
  3. 🔒 Isolamento (Invisibilidade): Se o operador A e o operador B estiverem alterando o estoque ao mesmo tempo, um não enxerga os dados parciais do outro.
  4. 💾 Durabilidade (Permanência): Após o comando COMMIT, o dado está salvo no disco permanentemente. Nem mesmo um apagão remove o dado.

📊 Fluxo da Transação (TCL)

flowchart TD
    START["▶️ BEGIN TRANSACTION"] --> OP1["📝 UPDATE: Debitar Saldo Cliente"]
    OP1 --> OP2["📝 UPDATE: Creditar Conta TecProExpress"]
    OP2 --> DEC{"❓ Sucesso Total?"}
    DEC -- SIM --> COM["✅ COMMIT: Salva no Disco"]
    DEC -- NÃO --> ROL["❌ ROLLBACK: Desfaz Tudo"]
    COM --> END["🏁 Fim da Transação"]
    ROL --> END

Pilares ACID e Controle Transacional TCL


🔍 Detalhamento do Fluxo:

  • BEGIN TRANSACTION: Avisa ao banco que uma sequência de operações críticas começou.
  • COMMIT: A confirmação de que tudo deu certo.
  • ROLLBACK: O botão de "Pânico" ou "Ctrl+Z" que cancela todas as ações pendentes.

📖 Exemplo Guiado: Transações Seguras

Vamos simular o cenário da TecProExpress no banco de dados. Seguindo nossa regra de excelência: primeiro DDL, depois DML.

🛠️ Código do Exemplo

-- PASSO 1: DDL (Criar a conta corrente)
CREATE TABLE conta_corrente (
    id INT PRIMARY KEY,
    titular VARCHAR(100),
    saldo DECIMAL(10,2) CHECK (saldo >= 0)
);

-- PASSO 2: DML (Carga Inicial)
INSERT INTO conta_corrente VALUES (1, 'Cliente João', 2000.00);
INSERT INTO conta_corrente VALUES (2, 'TecProExpress', 0.00);

-- PASSO 3: O Fluxo Transacional (TCL + DML)
START TRANSACTION;
  -- Retira do Cliente
  UPDATE conta_corrente SET saldo = saldo - 1500.00 WHERE id = 1;
  -- Credita na Empresa
  UPDATE conta_corrente SET saldo = saldo + 1500.00 WHERE id = 2;
COMMIT;

🔍 Detalhamento do Código:

  • START TRANSACTION: Inicia a unidade lógica.
  • UPDATE: Altera os dados. O valor só será efetivado no banco de dados quando o COMMIT for lido e executado pelo sistema.

☁️ A Era da Nuvem (Cloud Databases)

Atualmente, não instalamos mais servidores físicos em salas geladas. Utilizamos a Cloud Computing para hospedar nossos SGBDs.

📊 Modelos de Nuvem

flowchart LR
    U1["👤 Usuário Final"]
    U2["🛠️ Dev / DBA"]
    U3["⚙️ SysAdmin / Infra"]
    
    subgraph Cloud ["Modelos de Serviços Cloud"]
    UC1(("SaaS: App Pronto"))
    UC2(("PaaS: DBaaS"))
    UC3(("IaaS: Infra Bruta"))
    end
    
    U1 --> UC1
    U2 --> UC2
    U3 --> UC3

🔍 Detalhamento dos Modelos:

  • IaaS (Infraestrutura como Serviço): Você aluga a máquina e tem que instalar o banco. Controle total, mas requer gestão manual.
  • PaaS (Plataforma como Serviço - Foco do DBA): O provedor (AWS, Azure) te entrega o banco pronto para conectar (DBaaS). Eles cuidam da atualização e do hardware.
  • SaaS (Software como Serviço): O usuário apenas usa o sistema (Ex: Netflix, Gmail).

🛠️ Prática Obrigatória: O Botão de Pânico

Cenário: Simular um erro transacional para a TecProExpress.

  1. Crie a tabela e os inserts da Prática Guiada acima.
  2. Inicie uma nova transação (START TRANSACTION).
  3. Tente transferir 3000.00 do Cliente João (que só tem 500 restantes). O banco dará um erro devido ao CHECK.
  4. Execute ROLLBACK;.

🏁 Resultado Esperado

Ao executar um SELECT * FROM conta_corrente, o saldo do Cliente João não deve ter sido negativado, comprovando que a transação bloqueou o estado inconsistente.



💻 Ponte Prática: Do SQL Manual ao SQLAlchemy 2.0 ORM

Como o Python e o SQLAlchemy 2.0 implementam o Controle Transacional ACID de forma blindada contra falhas?

🔴 1. A Abordagem com SQL Manual (Controle Transacional Manual)

No modelo manual, o desenvolvedor precisa lembrar de disparar rollback() no bloco except. Se esquecer, a conexão fica em estado inconsistente ou bloqueia o banco:

# ❌ ABORDAGEM COM SQL MANUAL: Risco de esquecer o rollback ou prender locks
import sqlite3

conn = sqlite3.connect("banco_legado.db")
cursor = conn.cursor()

try:
    cursor.execute("UPDATE contas SET saldo = saldo - 1500 WHERE id = 1;")
    cursor.execute("UPDATE contas SET saldo = saldo + 1500 WHERE id = 2;")
    conn.commit() # Se falhar antes daqui, o que acontece?
except Exception as err:
    conn.rollback() # Fácil de esquecer!
finally:
    conn.close()

🟢 2. A Abordagem com SQLAlchemy 2.0 (Gerenciador de Contexto session.begin())

Com o SQLAlchemy 2.0, usamos o gerenciador de contexto with session.begin():. A Atomicidade (A do ACID) é nativa: se qualquer linha do bloco levantar uma exceção, o rollback é executado instantaneamente:

# ✅ ABORDAGEM MODERNA COM SQLALCHEMY 2.0: Atomicidade e Rollback Automático
from sqlalchemy import create_engine, String, Float, CheckConstraint, select
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, Session

class Base(DeclarativeBase):
    pass

class ContaCorrenteModel(Base):
    __tablename__ = "contas_correntes"
    __table_args__ = (
        CheckConstraint("saldo >= 0.0", name="chk_saldo_positivo"),
    )

    id: Mapped[int] = mapped_column(primary_key=True)
    titular: Mapped[str] = mapped_column(String(100), nullable=False)
    saldo: Mapped[float] = mapped_column(Float, nullable=False)

    def __repr__(self) -> str:
        return f"Conta({self.titular}) -> Saldo: R$ {self.saldo:.2f}"

🛠️ Mini-Projeto 03 (BD): Transferência Transacional Segura (ACID)

Objetivo: Simular um serviço de transferência financeira PIX entre contas e comprovar o rollback automático quando uma regra de negócio for violada.

📋 Pré-requisitos e Instalação

No terminal do seu ambiente virtual (PowerShell ou Bash), instale a biblioteca necessária:

pip install sqlalchemy

💻 Código Completo e Autocontido (miniprojeto_03_transacoes.py)

Crie o arquivo miniprojeto_03_transacoes.py e insira o código abaixo integralmente:

"""
Mini-Projeto 03: Transferência Transacional Segura (ACID)
Curso: GTI - Banco de Dados Relacionais e Engenharia de Software
Stack: Python 3.11+ | SQLAlchemy 2.0 | SQLite
"""
import os
from sqlalchemy import create_engine, String, Float, CheckConstraint, select
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, Session

# 1. Definição Declarativa do Schema
class Base(DeclarativeBase):
    pass

class ContaCorrenteModel(Base):
    __tablename__ = "contas_correntes"
    __table_args__ = (
        CheckConstraint("saldo >= 0.0", name="chk_saldo_positivo"),
    )

    id: Mapped[int] = mapped_column(primary_key=True)
    titular: Mapped[str] = mapped_column(String(100), nullable=False)
    saldo: Mapped[float] = mapped_column(Float, nullable=False)

    def __repr__(self) -> str:
        return f"Conta({self.titular}) -> Saldo: R$ {self.saldo:.2f}"

# 2. Serviço com Controle Transacional ACID
class ServicoTransferencia:
    @staticmethod
    def transferir(engine, id_origem: int, id_destino: int, valor: float) -> bool:
        with Session(engine) as session:
            try:
                # with session.begin() abre a transação e faz COMMIT automático se tudo der certo
                with session.begin():
                    conta_origem = session.get(ContaCorrenteModel, id_origem)
                    conta_destino = session.get(ContaCorrenteModel, id_destino)

                    if not conta_origem or not conta_destino:
                        raise ValueError("Uma das contas informadas não existe!")

                    if conta_origem.saldo < valor:
                        raise ValueError(f"Saldo insuficiente na conta de {conta_origem.titular}!")

                    print(f"💸 Debitando R$ {valor:.2f} de {conta_origem.titular}...")
                    conta_origem.saldo -= valor

                    print(f"💰 Creditando R$ {valor:.2f} em {conta_destino.titular}...")
                    conta_destino.saldo += valor

                print("✅ [TCL] Transação confirmada com sucesso (COMMIT)!")
                return True
            except Exception as err:
                print(f"❌ [TCL] FALHA NA TRANSAÇÃO: {err}")
                print("🛡️ [ROLLBACK] Todas as operações foram revertidas pelo SGBD!")
                return False

# 3. Ponto de Entrada Executável
if __name__ == "__main__":
    DB_FILE = "tecpro_banco.db"

    # Reset preventivo para garantir idempotência em testes repetidos
    if os.path.exists(DB_FILE):
        os.remove(DB_FILE)

    engine = create_engine(f"sqlite:///{DB_FILE}", echo=False)
    Base.metadata.create_all(bind=engine)

    # Carga inicial de teste
    with Session(engine) as session:
        c1 = ContaCorrenteModel(id=1, titular="Cliente João", saldo=2000.0)
        c2 = ContaCorrenteModel(id=2, titular="TecProExpress Cargas", saldo=0.0)
        session.merge(c1)
        session.merge(c2)
        session.commit()

    print("🔐 SIMULAÇÃO DE TRANSAÇÕES ACID (TECPROEXPRESS):")
    print("=" * 60)

    # 1. Transferência Válida (R$ 1.500,00)
    ServicoTransferencia.transferir(engine, id_origem=1, id_destino=2, valor=1500.0)

    # 2. Transferência Inválida (Tentativa de transferir R$ 3.000,00 com saldo de apenas R$ 500,00)
    print("-" * 60)
    ServicoTransferencia.transferir(engine, id_origem=1, id_destino=2, valor=3000.0)

    # Consulta dos saldos finais:
    print("-" * 60)
    with Session(engine) as session:
        for conta in session.scalars(select(ContaCorrenteModel)).all():
            print(f"  📊 {conta}")
    print("=" * 60)

🚀 Como Executar

Execute o script no terminal:

python miniprojeto_03_transacoes.py

🖥️ Saída Esperada no Console

🔐 SIMULAÇÃO DE TRANSAÇÕES ACID (TECPROEXPRESS):
============================================================
💸 Debitando R$ 1500.00 de Cliente João...
💰 Creditando R$ 1500.00 em TecProExpress Cargas...
✅ [TCL] Transação confirmada com sucesso (COMMIT)!
------------------------------------------------------------
💸 Debitando R$ 3000.00 de Cliente João...
❌ [TCL] FALHA NA TRANSAÇÃO: Saldo insuficiente na conta de Cliente João!
🛡️ [ROLLBACK] Todas as operações foram revertidas pelo SGBD!
------------------------------------------------------------
  📊 Conta(Cliente João) -> Saldo: R$ 500.00
  📊 Conta(TecProExpress Cargas) -> Saldo: R$ 1500.00
============================================================

💡 Checkpoint de Lógica

Importante

Reflexão Profissional: Qual é a grande vantagem da nuvem em termos de transações financeiras? O DBaaS (Database as a Service) moderno oferece backups automáticos diários. Se um Rollback falhar por erro catastrófico, o provedor da nuvem pode restaurar o banco exatamente para 1 minuto antes do acidente. 🧠🛡️



🧪 Quiz de Fixação e Autoavaliação — Capítulo 03

1. Em uma transferência bancária de R$ 500, o sistema debita da Conta A, mas o servidor desliga antes de creditar na Conta B. Qual princípio ACID garante que o débito seja cancelado (desfeito) ao reiniciar?

  • A) Durabilidade
  • B) Atomicidade (Atomicity)
  • C) Isolamento
  • D) Autodescrição
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: Atomicidade garante que a transação é indivisível: ou todas as operações têm sucesso (COMMIT) ou nenhuma é aplicada (ROLLBACK total).


2. O que é o fenômeno da 'Leitura Suja' (Dirty Read) em bancos de dados relacionais?

  • A) Quando o monitor do computador está empoeirado.
  • B) Quando uma transação lê dados que foram alterados por outra transação que AINDA NÃO fez commit (e que pode sofrer rollback em seguida).
  • C) Quando uma consulta SELECT demora mais de 10 segundos.
  • D) Quando a tabela não possui chave primária.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: Leituras sujas acontecem no nível de isolamento 'Read Uncommitted', lendo dados 'fantasmas' que podem nunca se concretizar no banco.


3. Qual é o papel do log de escrita prévia (Write-Ahead Logging - WAL) no PostgreSQL e SQLite?

  • A) Registrar o histórico de acessos dos usuários para fins de RH.
  • B) Garantir a Durabilidade: gravar as mudanças no arquivo de log sequencial em disco ANTES de alterar as páginas de dados na memória RAM, permitindo recuperação instantânea após quedas de energia.
  • C) Calcular a fatura mensal do serviço em nuvem.
  • D) Apagar tabelas antigas.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: O WAL garante que, mesmo se o servidor for desligado da tomada, o SGBD reconstrói todas as transações comitadas ao reiniciar lendo o log sequencial.


🎯 Laboratório Prático

Coloque este conhecimento em prática agora mesmo executando o roteiro autoguiado:
👉 ATIVIDADE 12: TRANSAÇÕES E ACID

🐳 CAPÍTULO 04: SETUP COMPLETO E ARQUITETURA DUAL-DATABASE


🎯 Objetivos de Aprendizagem

Ao final deste capítulo (estimativa: 2 horas de estudo autoguiado), você será capaz de:

  • 🔹 Configurar o ambiente de desenvolvimento e produção com PostgreSQL 17, SQLite e MongoDB utilizando Docker Compose.
  • 🔹 Aplicar o padrão de Arquitetura Dual-Database: SQLite no desenvolvimento local e PostgreSQL em produção.
  • 🔹 Centralizar strings de conexão e credenciais utilizando variáveis de ambiente com arquivo .env.
  • 🔹 Validar o status de conectividade (Health Check) com SQLAlchemy 2.0 e psycopg2.

Bem-vindo à preparação do seu ambiente. A engenharia de plataformas modernas exige o domínio de diferentes ecossistemas. Você aprenderá a configurar um ambiente Poliglota, suportando tabelas e documentos. 🛡️🧩

🏢 O Cenário Prático (Seu Desafio)

Você lidera a nova célula de Engenharia de Dados da TecProExpress. A equipe de backend desenvolveu um novo painel administrativo, mas eles não conseguem se conectar ao banco de dados porque as portas estão bloqueadas ou os serviços não foram iniciados.

"Seu desafio é criar um laboratório local na sua máquina, garantindo que o PostgreSQL (Relacional) e o MongoDB (NoSQL) rodem simultaneamente em portas diferentes, sem conflitos, provando a saúde do ambiente com testes DDL/DML."


🧠 Fundamentos: Anatomia de um Serviço

Quando instalamos um banco de dados, não instalamos apenas uma "pasta de arquivos". Instalamos um Serviço (Daemon/Background Worker) que fica ouvindo por chamadas.

📊 Comunicação Cliente-Servidor

flowchart LR
    C1["👤 Desenvolvedor<br/>(DBeaver / Compass)"] --> P{"Porta Lógica"}
    P -- 5432 --> S1[("🐘 PostgreSQL Server")]
    P -- 27017 --> S2[("🍃 MongoDB Server")]
    
    style P fill:#ffcc80,stroke:#e65100

🔍 Detalhamento das Conexões:

  • Localhost (127.0.0.1): Endereço que aponta para a sua própria máquina.
  • Porta (Port): O "guichê" de atendimento. Cada banco tem uma porta padrão. Se dois bancos tentarem usar a mesma porta, o serviço cai.

🐘 SETUP RELACIONAL: PostgreSQL e MySQL

1. Download e Instalação

Para SGBDs relacionais pesados, usamos versões nativas LTS (Long Term Support).

  • PostgreSQL 17: Acesse o portal da EnterpriseDB. A porta padrão é a 5432.
  • MySQL 8.4: Acesse o MySQL Installer. A porta padrão é a 3306.

Importante

A Senha Mestra: Durante a instalação, você criará a senha para os superusuários (postgres ou root). O banco de dados é implacável: se você perder essa senha, terá que reinstalar todo o sistema e perderá os dados. Anote em local seguro!

---

🍃 SETUP NOSQL: MongoDB e Cassandra

1. MongoDB (Documentos Flexíveis)

Para dados não estruturados, como os logs das entregas da TecProExpress.

  • Servidor: Baixe o MongoDB Community Server 7.0+. Porta padrão: 27017.
  • Cliente: A instalação já traz o MongoDB Compass, que é a interface gráfica (IDE).

2. Apache Cassandra (Big Data via Docker)

Para lidar com bilhões de registros (ex: dados de GPS dos caminhões), usamos o Cassandra. Para evitar configurar a máquina virtual Java (JVM) na sua máquina, usaremos Docker.

# Passo 1: Baixar e rodar a imagem do Cassandra pelo terminal
docker run --name cassandra-tecpro -p 9042:9042 -d cassandra

📖 Exemplo Guiado: Validando o Setup (DDL -> DML)

A melhor forma de testar se a instalação do PostgreSQL ou MySQL funcionou é criar e povoar uma tabela de testes.

🛠️ Código de Validação

Abra o pgAdmin 4 (ou DBeaver), crie um banco de dados chamado teste_db e execute o script abaixo:

-- PASSO 1: DDL (Criar a estrutura de testes)
CREATE TABLE validacao_ambiente (
    id INT PRIMARY KEY,
    status_servidor VARCHAR(50)
);

-- PASSO 2: DML (Inserir os dados de teste)
INSERT INTO validacao_ambiente (id, status_servidor) VALUES (1, 'Setup Relacional Ativo na TecProExpress!');

-- PASSO 3: Query (Verificar a inserção)
SELECT * FROM validacao_ambiente;

🔍 Detalhamento do Teste:

  • O sucesso na execução deste bloco inteiro garante que a instalação do serviço e a permissão do usuário postgres estão perfeitamente saudáveis.

🛠️ Prática Obrigatória: Conectando a Nave-Mãe

Cenário: O ambiente de testes locais.

  1. Garanta que o Postgres (5432) e o Mongo (27017) estão ativos nos serviços do Windows/Mac.
  2. Abra a interface (pgAdmin/Compass).
  3. Crie as conexões.
  4. No MongoDB Compass, crie uma base de dados (Database) chamada tecpro_nosql e uma coleção chamada teste_conexao. Insira um documento qualquer em JSON para validar.

🏁 Resultado Esperado

Duas janelas verdes na sua máquina atestando que os motores relacional e documental operam simultaneamente sem choque de recursos.


🛢️ Arquitetura Dual-Database: SQLite (Desenvolvimento) ➔ PostgreSQL (Produção)

No desenvolvimento backend corporativo moderno, a aplicação nunca deve depender de um banco de dados específico.

Utilizamos o padrão Dual-Database, onde o desenvolvedor trabalha localmente com SQLite (sem atrito de instalação, em um arquivo .db) e o ambiente de produção roda PostgreSQL 17 (em contêineres Docker de alta performance), conectados pela mesma camada do SQLAlchemy 2.0:

flowchart TD
    APP["🌐 Aplicação Web (Flask / Python)"] ==> ORM["🧱 SQLAlchemy 2.0"]
    
    ORM -->|DATABASE_URL=sqlite:///dev.db| SQLITE["📁 SQLite Local (.db)<br/>• Arquivo único embutido<br/>• Zero atrito no Dia 1 de aula<br/>• Rápido para testes automatizados"]
    ORM -->|DATABASE_URL=postgresql://...| POSTGRES["🐘 PostgreSQL 17 (Docker)<br/>• Servidor de Banco Dedicado<br/>• Alta concorrência e transações ACID<br/>• Produção corporativa escalável"]

    style APP fill:#e3f2fd,stroke:#1565c0
    style ORM fill:#fff8e1,stroke:#f57f17
    style SQLITE fill:#f1f8e9,stroke:#558b2f
    style POSTGRES fill:#e0f2f1,stroke:#00695c

Arquitetura Dual-Database

📊 Tabela Comparativa: SQLite vs PostgreSQL

Característica📁 SQLite (Desenvolvimento)🐘 PostgreSQL (Produção)
ArquiteturaEmbutido (In-Process) dentro da aplicação.Servidor Dedicado (Client-Server) via TCP/IP (Porta 5432).
ArmazenamentoArquivo único .db no diretório do projeto.Tabelas segmentadas em Data Pages (8 KB) gerenciadas pelo motor.
Instalação / SetupZero. Nativo em todas as instalações do Python.Requer instalação de serviço ou contêiner via docker-compose.yml.
ConcorrênciaTravamento (lock) de arquivo em operações de escrita intensivas.Controle de Concorrência Multiversão (MVCC) de altíssima escala.
Connection PoolingDesnecessário (NullPool ou StaticPool).Obrigatório (QueuePool) para reciclar conexões ativas.
Migrações de SchemaExecutadas automaticamente via Alembic.Executadas automaticamente via Alembic.

💻 Ponte Prática: Do SQL Manual ao SQLAlchemy 2.0 ORM

Como o desenvolvedor corporativo gerencia strings de conexão, pools e valida o status dos SGBDs via Python?

🔴 1. A Abordagem Manual (Strings Conectivas Hardcoded e Inseguras)

No modelo amador, a senha do banco fica gravada diretamente no código fonte, sem pooling de conexões e sem tratamento de queda de rede:

# ❌ ABORDAGEM AMADORA: Senhas expostas e sem pool de conexões
import psycopg2

def conectar_manual():
    # Senhas e hosts fixos no código (Falha grave de segurança!)
    conn = psycopg2.connect("dbname=tecpro user=postgres password=root host=localhost port=5432")
    return conn

🟢 2. A Abordagem Profissional (Connection Pool e Health Check com SQLAlchemy)

Com o SQLAlchemy 2.0, usamos create_engine() configurando pool de conexões e testando o Health Check com a consulta padrão text("SELECT 1"):

# ✅ ABORDAGEM PROFISSIONAL: Healthcheck de Conexão e Gestão de Engine
import os
from sqlalchemy import create_engine, text
from sqlalchemy.orm import Session

class DatabaseHealthChecker:
    @staticmethod
    def testar_conectividade(database_url: str) -> bool:
        try:
            # pool_pre_ping=True testa se a conexão está viva antes de usá-la
            engine = create_engine(database_url, pool_pre_ping=True, echo=False)
            with engine.connect() as connection:
                resultado = connection.execute(text("SELECT 1")).scalar()
                return resultado == 1
        except Exception as err:
            print(f"  ❌ Falha de conexão ({database_url}): {err}")
            return False

🛠️ Mini-Projeto 04 (BD): Healthchecker Multi-Banco em Python

Objetivo: Criar um script de diagnóstico que valida a conectividade de diferentes engines de dados (SQLite local, SQLite em memória e PostgreSQL simulado).

📋 Pré-requisitos e Instalação

No terminal do seu ambiente virtual (PowerShell ou Bash), instale a biblioteca necessária:

pip install sqlalchemy

💻 Código Completo e Autocontido (miniprojeto_04_healthcheck.py)

Crie o arquivo miniprojeto_04_healthcheck.py e insira o código abaixo integralmente:

"""
Mini-Projeto 04: Healthchecker Multi-Banco em Python
Curso: GTI - Banco de Dados Relacionais e Engenharia de Software
Stack: Python 3.11+ | SQLAlchemy 2.0 | SQLite
"""
import os
from sqlalchemy import create_engine, text

# 1. Componente Diagnóstico de Conectividade
class DatabaseHealthChecker:
    @staticmethod
    def testar_conectividade(database_url: str) -> bool:
        try:
            # pool_pre_ping=True testa a saúde física da conexão antes de liberá-la
            engine = create_engine(database_url, pool_pre_ping=True, echo=False)
            with engine.connect() as connection:
                resultado = connection.execute(text("SELECT 1")).scalar()
                return resultado == 1
        except Exception as err:
            print(f"  ❌ Falha de conexão ({database_url}): {err}")
            return False

# 2. Ponto de Entrada Executável
if __name__ == "__main__":
    print("🔍 AUDITORIA DE SAÚDE DOS SGBDS (HEALTHCHECK) - TECPROEXPRESS")
    print("=" * 65)

    # Dicionário de URLs de conexão dos ambientes da empresa
    ambientes = {
        "1. SQLite Local (Desenvolvimento)": "sqlite:///tecpro_dev.db",
        "2. SQLite Memória (Testes Unitários CI/CD)": "sqlite:///:memory:",
        "3. PostgreSQL Simulado (Produção)": "sqlite:///tecpro_prod_simulado.db"
    }

    for nome_ambiente, url in ambientes.items():
        print(f"\n📡 Testando: {nome_ambiente}...")
        status = DatabaseHealthChecker.testar_conectividade(url)
        if status:
            print("  🟢 STATUS: Conexão Saudável (SELECT 1 OK)")
        else:
            print("  🔴 STATUS: Inacessível (Verificar portas e firewall)")

    print("\n" + "=" * 65)
    print("✅ Diagnóstico de conectividade concluído.")

🚀 Como Executar

Execute o script no terminal:

python miniprojeto_04_healthcheck.py

🖥️ Saída Esperada no Console

🔍 AUDITORIA DE SAÚDE DOS SGBDS (HEALTHCHECK) - TECPROEXPRESS
=================================================================

📡 Testando: 1. SQLite Local (Desenvolvimento)...
  🟢 STATUS: Conexão Saudável (SELECT 1 OK)

📡 Testando: 2. SQLite Memória (Testes Unitários CI/CD)...
  🟢 STATUS: Conexão Saudável (SELECT 1 OK)

📡 Testando: 3. PostgreSQL Simulado (Produção)...
  🟢 STATUS: Conexão Saudável (SELECT 1 OK)

=================================================================
✅ Diagnóstico de conectividade concluído.

💡 Checkpoint de Lógica

Dica

Dica do Especialista: Quando você rodar o Cassandra via Docker, a bandeira -d (detach) no comando permite que o terminal fique livre enquanto o banco roda silenciosamente nos bastidores. A conteinerização é o futuro da arquitetura de dados! 🚀🛡️



🧪 Quiz de Fixação e Autoavaliação — Capítulo 04

1. Por que a Arquitetura Dual-Database adota o SQLite no desenvolvimento local e o PostgreSQL em produção?

  • A) Porque o PostgreSQL não funciona em computadores de desenvolvimento.
  • B) Porque o SQLite permite que os alunos comecem a desenvolver no Dia 1 sem atrito de instalação de servidores (arquivo .db local), e o PostgreSQL no Docker assume em produção para suportar alta concorrência e transações pesadas sem alterar o código da aplicação.
  • C) Porque o SQLite é mais caro que o PostgreSQL.
  • D) Porque o SQLAlchemy só aceita um banco por vez.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: O SQLite elimina problemas de setup no Dia 1 de aula; em produção, a mesma aplicação conecta no PostgreSQL apenas trocando a variável DATABASE_URL.


2. Como uma aplicação backend bem projetada (Flask / SQLAlchemy) deve alternar entre o SQLite e o PostgreSQL?

  • A) Colocando comandos if ambiente == 'prod': espalhados em todos os arquivos de rota.
  • B) Lendo a variável de ambiente DATABASE_URL centralizada no arquivo .env (ex: sqlite:///dev.db vs postgresql://user:pass@host:5432/db).
  • C) Reescrevendo todos os modelos e queries do zero.
  • D) Desinstalando o Python e reinstalando o Node.js.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: O padrão 12-Factor App determina que a configuração deve ser injetada via variáveis de ambiente, mantendo o código da aplicação 100% agnóstico ao banco.


3. Em um arquivo docker-compose.yml, qual comando mapeia a porta interna 5432 do contêiner PostgreSQL para a porta 5432 do seu computador local?

  • A) volumes: - ./data:/var/lib/postgresql/data
  • B) ports: - '5432:5432'
  • C) environment: - POSTGRES_PASSWORD=root
  • D) restart: always
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: A diretiva ports: - 'host:container' (ex: '5432:5432') expõe o serviço do contêiner para ferramentas locais como DBeaver e pgAdmin.


🎯 Laboratório Prático

Coloque este conhecimento em prática agora mesmo executando o roteiro autoguiado:
👉 ATIVIDADE 01: SETUP DO AMBIENTE

📐 CAPÍTULO 05: MODELO RELACIONAL E MODELAGEM CONCEITUAL


🎯 Objetivos de Aprendizagem

Ao final deste capítulo (estimativa: 2 horas de estudo autoguiado), você será capaz de:

  • 🔹 Compreender os fundamentos do Modelo Entidade-Relacionamento (MER) de Peter Chen e do Modelo Relacional de Edgar Codd.
  • 🔹 Identificar Entidades Fortes, Entidades Fracas, Atributos Simples, Compostos e Multivalorados.
  • 🔹 Modelar cardinalidades fundamentais: Um para Um (1:1), Um para Muitos (1:N) e Muitos para Muitos (N:N).
  • 🔹 Construir diagramas relacionais e conceituais utilizando Mermaid e Draw.io.

O "coração" da engenharia de dados moderna é baseado na matemática. Criado na década de 70 por Edgar F. Codd (cientista da IBM), o Modelo Relacional revolucionou o mundo ao organizar informações de forma previsível e segura. 🛡️🧩

🏢 O Cenário Prático (Seu Desafio)

Você assumiu a área de Arquitetura de Dados da TecProExpress. A equipe de negócios enviou um documento textual gigante descrevendo como eles querem que o sistema funcione. Os programadores não sabem por onde começar a codificar as tabelas.

"Seu desafio é ser a ponte de comunicação. Você precisa traduzir as necessidades do negócio (Mundo Real) em um diagrama visual (Mini-mundo) e depois em código SQL estruturado."


🧠 Fundamentos: O Dicionário Relacional

No mercado profissional, evitamos termos amadores. Um Arquiteto de Elite domina a nomenclatura técnica.

Nome ComercialNomenclatura CientíficaO que representa na prática?
TabelaRelaçãoEstrutura que guarda entidades (ex: cliente).
Linha / RegistroTuplaUma ocorrência específica (ex: O cliente João).
Coluna / CampoAtributoPropriedade (ex: nome, cpf).
Tipo de DadoDomínioRegras de formato permitidas (ex: INT, VARCHAR).

📊 Anatomia de uma Tabela (Relação)

erDiagram
    PRODUTO {
        int id_produto PK
        string nome
        decimal preco
    }

🔍 Detalhamento das Regras de Ouro:

  1. Atomicidade: Cada célula deve conter apenas um único valor indivisível. (Nada de guardar dois telefones na mesma coluna).
  2. Unicidade: Não existem linhas 100% iguais (garantido pela PK - Primary Key).
  3. O Valor NULL: Representa ausência de informação. Atenção: NULL não é zero e nem um texto vazio ("").

📖 Exemplo Guiado: Criando o Dicionário (DDL -> DML)

A melhor forma de entender os domínios é aplicando restrições no código.

🛠️ Código do Exemplo

-- PASSO 1: DDL (Definindo Relação, Atributos e Domínios)
CREATE TABLE fornecedor (
    id INT PRIMARY KEY,
    nome VARCHAR(100) NOT NULL,
    status_ativo BOOLEAN
);

-- PASSO 2: DML (Inserindo Tuplas)
INSERT INTO fornecedor (id, nome, status_ativo) VALUES (1, 'TecProExpress', TRUE);
INSERT INTO fornecedor (id, nome, status_ativo) VALUES (2, 'Fornecedor Beta', NULL);

🔍 Detalhamento do Código:

  • VARCHAR(100): O domínio restringe o tamanho do atributo "nome" a 100 letras.
  • NULL: O segundo INSERT usa NULL porque ainda não sabemos o status do Fornecedor Beta.

📐 O Ciclo de Vida da Modelagem

O processo de traduzir o mundo real para tabelas ocorre em 3 fases:

  1. 🧠 Modelo Conceitual: Foca na regra de negócio. Desenho em alto nível (Diagrama Entidade-Relacionamento - DER) que o cliente consegue entender.
  2. ⚙️ Modelo Lógico: Traduz o diagrama para tabelas (com PKs e FKs), mas ainda sem código específico.
  3. 💻 Modelo Físico: O script de criação (SQL DDL) que roda dentro do SGBD (MySQL/Postgres).

📊 O Fluxo de Abstração

flowchart TD
    REAL["🌍 Mundo Real"] --> ABS{"🔍 Abstração"}
    ABS --> MINI["🗺️ Modelo Conceitual"]
    MINI --> LOG["📐 Modelo Lógico"]
    LOG --> FIS["💻 Modelo Físico (SQL)"]

🛠️ Prática Obrigatória: Abstração Inicial

Cenário: A TecProExpress quer modelar seus veículos de frota.

  1. Identifique as Entidades e Atributos para um veículo.
  2. Crie a tabela veiculo_frota usando DDL e insira uma tupla usando DML.

🚀 Script de Seed (Gabarito Físico)

-- DDL
CREATE TABLE veiculo_frota (
    id_veiculo INT PRIMARY KEY,
    placa VARCHAR(7) NOT NULL,
    capacidade_carga_kg DECIMAL(10,2)
);

-- DML
INSERT INTO veiculo_frota (id_veiculo, placa, capacidade_carga_kg) VALUES (101, 'ABC1234', 5000.00);


💻 Ponte Prática: Do SQL Manual ao SQLAlchemy 2.0 ORM

Como traduzir os conceitos matemáticos de Relação (Tabela), Tupla (Linha) e Domínio (Tipo de Dado) para o código Python moderno?

🔴 1. A Abordagem Manual (Dicionários em Memória sem Atomicidade)

No modelo procedural sem ORM, os dados de entidades vivem em dicionários desprotegidos. O desenvolvedor corre o risco de violar a atomicidade ou aceitar tipos inconsistentes:

# ❌ ABORDAGEM PROCEDURAL: Dicionários com tipos inconsistentes e campos soltos
fornecedores_memoria = [
    {"id": 1, "nome": "TecProExpress", "status": True},
    {"id": 2, "nome": 12345, "status": "ativo"} # ⚠️ Nome como número e status como string!
]

🟢 2. A Abordagem com SQLAlchemy 2.0 (Mapeamento Declarativo Tipado)

Com o SQLAlchemy 2.0, cada Relação é uma classe e cada Atributo possui seu Domínio explicitado com Type Hints:

# ✅ ABORDAGEM MODERNA: Mapeamento Conceitual -> Lógico -> Físico via SQLAlchemy
from sqlalchemy import create_engine, String, Boolean, Integer, select
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, Session

class Base(DeclarativeBase):
    pass

class FornecedorModel(Base):
    """Representa a Relação FORNECEDOR no Modelo Relacional."""
    __tablename__ = "fornecedores"

    id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    nome: Mapped[str] = mapped_column(String(100), nullable=False)
    status_ativo: Mapped[bool | None] = mapped_column(Boolean, nullable=True) # Aceita NULL

    def __repr__(self) -> str:
        status_str = "Ativo" if self.status_ativo is True else ("Inativo" if self.status_ativo is False else "Pendente (NULL)")
        return f"Fornecedor(id={self.id}, nome='{self.nome}', status={status_str})"

🛠️ Mini-Projeto 05 (BD): Catálogo de Fornecedores e Mapeamento Relacional

Objetivo: Implementar o ciclo completo da modelagem conceitual à persistência física de fornecedores com manipulação de valores NULL.

📋 Pré-requisitos e Instalação

No terminal do seu ambiente virtual (PowerShell ou Bash), instale a biblioteca necessária:

pip install sqlalchemy

💻 Código Completo e Autocontido (miniprojeto_05_dominios.py)

Crie o arquivo miniprojeto_05_dominios.py e insira o código abaixo integralmente:

"""
Mini-Projeto 05: Catálogo de Fornecedores e Mapeamento Relacional
Curso: GTI - Banco de Dados Relacionais e Engenharia de Software
Stack: Python 3.11+ | SQLAlchemy 2.0 | SQLite
"""
import os
from sqlalchemy import create_engine, String, Integer, Boolean, select
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, Session

# 1. Definição Declarativa do Schema
class Base(DeclarativeBase):
    pass

class FornecedorModel(Base):
    __tablename__ = "fornecedores"

    id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    nome: Mapped[str] = mapped_column(String(100), nullable=False)
    status_ativo: Mapped[bool | None] = mapped_column(Boolean, nullable=True)  # Aceita NULL

    def __repr__(self) -> str:
        status_str = "Ativo" if self.status_ativo is True else ("Inativo" if self.status_ativo is False else "Pendente (NULL)")
        return f"Fornecedor(id={self.id}, nome='{self.nome}', status={status_str})"

# 2. Ponto de Entrada Executável
if __name__ == "__main__":
    DB_FILE = "tecpro_fornecedores.db"

    # Reset preventivo para garantir idempotência em testes repetidos
    if os.path.exists(DB_FILE):
        os.remove(DB_FILE)

    engine = create_engine(f"sqlite:///{DB_FILE}", echo=False)
    Base.metadata.create_all(bind=engine)

    print("📐 MAPEAMENTO RELACIONAL: RELAÇÃO, TUPLAS E ATRIBUTOS")
    print("=" * 65)

    # 1. Inserindo tuplas relacionais
    with Session(engine) as session:
        f1 = FornecedorModel(nome="TecProExpress Matriz Logística", status_ativo=True)
        f2 = FornecedorModel(nome="Pneus & Cargas Brasil", status_ativo=False)
        f3 = FornecedorModel(nome="Novo Fornecedor em Homologação", status_ativo=None)  # NULL no banco

        session.add_all([f1, f2, f3])
        session.commit()
        print("✅ 3 Tuplas persistidas com sucesso na relação 'fornecedores'!")

    # 2. Consultando e exibindo as tuplas relacionais
    print("\n🔍 Consultando catálogo de fornecedores:")
    with Session(engine) as session:
        stmt = select(FornecedorModel)
        for fornecedor in session.scalars(stmt).all():
            print(f"  🏢 {fornecedor}")
    print("=" * 65)

🚀 Como Executar

Execute o script no terminal:

python miniprojeto_05_dominios.py

🖥️ Saída Esperada no Console

📐 MAPEAMENTO RELACIONAL: RELAÇÃO, TUPLAS E ATRIBUTOS
=================================================================
✅ 3 Tuplas persistidas com sucesso na relação 'fornecedores'!

🔍 Consultando catálogo de fornecedores:
  🏢 Fornecedor(id=1, nome='TecProExpress Matriz Logística', status=Ativo)
  🏢 Fornecedor(id=2, nome='Pneus & Cargas Brasil', status=Inativo)
  🏢 Fornecedor(id=3, nome='Novo Fornecedor em Homologação', status=Pendente (NULL))
=================================================================

💡 Checkpoint de Lógica

Importante

Reflexão Profissional: Por que a Modelagem Conceitual é considerada a fase mais crítica de um projeto de software? (Resposta: Porque código SQL mal feito pode ser reescrito rapidamente, mas se a equipe entender a regra de negócio errado no Modelo Conceitual, todo o sistema será construído para resolver o problema errado). 🧠🛡️



🧪 Quiz de Fixação e Autoavaliação — Capítulo 05

1. Quem foi o cientista da computação britânico que formulou o Modelo Relacional de Dados em 1970 nos laboratórios da IBM?

  • A) Alan Turing
  • B) Edgar Frank Codd (E. F. Codd)
  • C) Peter Chen
  • D) Linus Torvalds
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: E. F. Codd revolucionou a computação em 1970 com o artigo histórico 'A Relational Model of Data for Large Shared Data Banks', introduzindo tabelas, tuplas e álgebra relacional.


2. Qual cardinalidade existe entre 'Cliente' e 'Pedido' em um sistema de e-commerce tradicional?

  • A) 1 : 1 (Um cliente só pode fazer um único pedido na vida).
  • B) 1 : N (Um cliente pode fazer muitos pedidos ao longo do tempo, mas cada pedido pertence a exatamente um cliente).
  • C) N : N (Um pedido pode pertencer a 50 clientes diferentes ao mesmo tempo).
  • D) Nenhuma relação.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: Relacionamento 1:N clássico: a Chave Primária do Cliente (id_cliente) viaja para a tabela de Pedidos como Chave Estrangeira (id_cliente_fk).


3. No Modelo Entidade-Relacionamento (MER), o que é um 'Atributo Multivalorado' (ex: Telefones de um Fornecedor)?

  • A) Um atributo que só aceita números negativos.
  • B) Um atributo que pode conter mais de um valor simultâneo para a mesma entidade (ex: um fornecedor com 3 números de telefone diferentes).
  • C) Um atributo que guarda o preço em dólares.
  • D) A chave primária da tabela.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: Atributos multivalorados violam a 1ª Forma Normal no modelo relacional e devem ser transformados em uma tabela filha separada no mapeamento lógico.


🎯 Laboratório Prático

Coloque este conhecimento em prática agora mesmo executando o roteiro autoguiado:
👉 ATIVIDADE 02: MODELAGEM CONCEITUAL

🧬 CAPÍTULO 06: ANATOMIA DE ATRIBUTOS, DOMÍNIOS E TIPOS


🎯 Objetivos de Aprendizagem

Ao final deste capítulo (estimativa: 2 horas de estudo autoguiado), você será capaz de:

  • 🔹 Analisar a alocação física de dados em disco e memória RAM (Data Pages de 8KB/16KB, Row Headers, Null Bitmaps e Data Alignment).
  • 🔹 Dominar os tamanhos exatos em bits e bytes dos tipos SQL (TINYINT 8b, SMALLINT 16b, INT 32b, BIGINT 64b, NUMERIC exato e VARCHAR).
  • 🔹 Evitar as 4 armadilhas clássicas de conversão entre Python e SQL (Overflow numérico, Imprecisão do Float, Truncamento de String e Timestamps ingênuos).
  • 🔹 Implementar tabelas híbridas com colunas JSONB nativas no SQLAlchemy 2.0.

No Modelo Relacional, a organização da informação é cirúrgica. Para que um banco de dados seja escalável e performático (como no MySQL 8.4 ou PostgreSQL 17), precisamos entender como cada "pedaço" de dado se encaixa na estrutura. 🛡️🧩

🏢 O Cenário Prático (Seu Desafio)

Você atua na TecProExpress. O setor de desenvolvimento reclamou que os IDs dos novos pacotes estão colidindo e gerando erros nas aplicações. Além disso, eles precisam de uma forma de salvar "detalhes técnicos variáveis" de eletrônicos (voltagem, cor, garantia) que mudam para cada produto, tudo sem criar mil colunas vazias.

"Seu desafio é dominar a engenharia de chaves primárias (Autoincrement/Identity) e explorar como armazenar flexibilidade no modelo relacional usando campos JSON nativos."


🧠 Fundamentos: Anatomia de um Registro

1. O Atributo (Campo)

Um Atributo é a menor unidade de informação. Ele precisa seguir as regras do seu Domínio:

  • Atomicidade: O valor deve ser indivisível (ex: separar a Rua do Bairro em vez de criar um "Endereço Completo").
  • Tipo de Dado: INT, VARCHAR, DATE (Limita o que o usuário pode preencher).

2. A Chave Primária (PK)

Nenhuma tabela profissional existe sem uma Chave Primária. Ela é o DNA da tupla, garantindo a integridade dos registros.

🔍 As Duas Leis da PK:

  1. Unicidade: O valor nunca se repete.
  2. Obrigatoriedade: O campo da PK é sempre NOT NULL (Não pode ser vazio).

🛢️ Anatomia dos Tipos de Dados SQL: Bits, Bytes e Armazenamento no SGBD

Diferente do Python (onde a memória é gerenciada em objetos dinâmicos no Heap), os Sistemas Gerenciadores de Bancos de Dados (PostgreSQL, MySQL, SQLite) precisam de definições rígidas e previsíveis de tipos para gravar dados de forma contígua em Páginas de Disco e Buffer Pool (geralmente de 8 KB no PostgreSQL ou 16 KB no MySQL InnoDB).

flowchart LR
    subgraph DISK_PAGE ["🛢️ Armazenamento Físico no SGBD (Páginas de 8KB / 16KB)"]
        direction TB
        HDR["Row Header + Null Bitmap (1 bit por coluna)"]
        T1["TINYINT: 8 bits (1 byte) ➔ -128 a 127"]
        T2["SMALLINT: 16 bits (2 bytes) ➔ -32.768 a 32.767"]
        T3["INTEGER: 32 bits (4 bytes) ➔ -2,14 bilhões a +2,14 bilhões"]
        T4["BIGINT: 64 bits (8 bytes) ➔ -9 quintilhões a +9 quintilhões"]
        T5["NUMERIC(10,2): Decimal exato BCD (sem dízima binária)"]
        T6["VARCHAR(n): 1-2 bytes de tamanho + bytes do texto"]
        
        HDR --> T1 --> T2 --> T3 --> T4 --> T5 --> T6
    end

Alocação Física no SGBD

📊 Tabela Mestra de Tipos SQL: Bits, Bytes e Limites Físicos

Tipo SQLBitsBytes em DiscoFaixa de Valores / LimiteComo o SGBD Armazena
BOOLEAN / BOOL8 bits (ou 1 bit)1 byte (ou bitfield)TRUE, FALSE, NULLArmazenado como byte simples ou em mapa de bits interno.
TINYINT8 bits1 byte-128 a 127 (ou 0 a 255 com UNSIGNED)1 byte puro em complemento de dois.
SMALLINT16 bits2 bytes-32.768 a 32.767Inteiro de 16 bits com alinhamento de 2 bytes.
INT / INTEGER32 bits4 bytes-2.147.483.648 a 2.147.483.647Inteiro padrão de 32 bits com alinhamento de 4 bytes.
BIGINT64 bits8 bytes-9.223.372.036.854.775.808 a +9.223.372.036.854.775.807Inteiro longo de 64 bits com alinhamento de 8 bytes.
REAL / FLOAT(24)32 bits4 bytes~6 a 7 dígitos de precisão (IEEE 754)Ponto flutuante binário simples de 32 bits.
DOUBLE PRECISION / FLOAT(53)64 bits8 bytes~15 a 17 dígitos de precisão (IEEE 754)Ponto flutuante binário duplo de 64 bits.
NUMERIC(p, s) / DECIMAL(p, s)Variável~4 a 16 bytes (conforme $p$)Exato, sem dízima binária (ex: 10, 2 = 8 dígitos inteiros + 2 decimais)Formato BCD (Binary-Coded Decimal) em blocos de 4 dígitos por 2 bytes.
CHAR(n)$n \times 8$ bits$n$ bytes fixosExatamente $n$ caracteresFixo em disco: preenche com espaços em branco à direita se o texto for menor.
VARCHAR(n)Variável1 a 2 bytes + tamanho realAté $n$ caracteresPrefixo de tamanho (1 byte se $n \le 255$, 2 bytes se $n > 255$) + caracteres reais.
TEXT / BLOBVariávelInline ou TOASTAté 1 GB (PostgreSQL) / 4 GB (MySQL)Se $> 2\text{ KB}$, comprimido e movido para fora da página principal (TOAST).
DATE24 a 32 bits3 a 4 bytesAno 0001 a 9999Número inteiro representando o deslocamento de dias em relação a uma época base.
TIMESTAMP64 bits8 bytesData + Hora com precisão de microsegundosInteiro de 64 bits em microssegundos desde 2000-01-01 (PostgreSQL).
JSONBVariávelBinário EstruturadoIlimitadoÁrvore binária indexada com busca direta de chaves sem reparsing.

🧠 Como o SGBD Gerencia a Memória RAM e Disco:

  1. Páginas de Dados (Data Pages): O SGBD lê e grava blocos inteiros de 8 KB (PostgreSQL) ou 16 KB (MySQL). Quanto menores forem seus tipos de dados (SMALLINT em vez de BIGINT), mais registros cabem por página, reduzindo drasticamente o I/O de disco.
  2. Null Bitmap (Mapa de Nulos): Se uma coluna aceita NULL e estiver vazia, o banco não gasta espaço na coluna; ele apenas ativa 1 bit no cabeçalho da linha (Row Header).
  3. Data Alignment & Padding (Alinhamento de Memória): Processadores de 64 bits leem memória em blocos múltiplos de 8 bytes. Colocar colunas na ordem (BIGINT, INT, SMALLINT) é mais eficiente em disco do que alternar (SMALLINT, BIGINT, SMALLINT), pois evita bytes de enchimento (padding).

🌉 A Ponte Objeto-Relacional: Tabela de Equivalências e Mapeamento

Abaixo, veja como os tipos viajam da modelagem conceitual até a memória do Python e a persistência no SGBD via SQLAlchemy 2.0:

Conceito / Domínio SQL (PostgreSQL / SQLite) Python POO (Memória RAM) ORM (SQLAlchemy 2.0) Tamanho no SQL
Identificador Curto SMALLINT int Mapped[int] = mapped_column(SmallInteger) 16 bits (2 bytes)
Chave Primária / ID Padrão INTEGER / INT / SERIAL int Mapped[int] = mapped_column(Integer, primary_key=True) 32 bits (4 bytes)
Contador de Alta Escala BIGINT / BIGSERIAL int Mapped[int] = mapped_column(BigInteger) 64 bits (8 bytes)
Texto Limitado VARCHAR(100) str Mapped[str] = mapped_column(String(100)) Variável (1B + texto)
Valor Monetário / Contábil NUMERIC(10,2) / DECIMAL Decimal (da lib decimal) Mapped[Decimal] = mapped_column(Numeric(10,2)) Exato (~5 a 8 bytes)
Data e Hora de Auditoria TIMESTAMP / DATETIME datetime Mapped[datetime] = mapped_column(DateTime) 64 bits (8 bytes)
Booleano / Indicador BOOLEAN bool Mapped[bool] = mapped_column(Boolean) 1 byte
Documento / Metadados JSON / JSONB dict / list Mapped[dict[str, Any]] = mapped_column(JSON) Variável
flowchart LR
    subgraph Dominio ["1. Domínio Real"]
        D["Entidade: Carro<br/>Atributo: Placa, Preço"]
    end
    subgraph Python ["2. Memória (Python POO)"]
        P["class Carro:<br/>placa: str (Heap)<br/>preco: Decimal"]
    end
    subgraph SQL ["3. Persistência (SQL DDL)"]
        S["CREATE TABLE carro (<br/>placa VARCHAR(10) PK,<br/>preco NUMERIC(10,2))"]
    end
    Dominio --> Python
    Python <== "ORM SQLAlchemy (Conversão Binária)" ==> SQL
    style Dominio fill:#e3f2fd,stroke:#1e88e5
    style Python fill:#fff8e1,stroke:#fbc02d
    style SQL fill:#e8f5e9,stroke:#43a047

⚠️ As 4 Grandes Armadilhas de Conversão Python ↔ SQL:

  1. Armadilha do Overflow Numérico: Em Python, x = 10**20 funciona perfeitamente (precisão arbitrária). Porém, se você tentar salvar esse valor em uma coluna INT (32 bits) ou BIGINT (64 bits), o SGBD abortará a transação com NumericValueOutOfRange.
  2. Armadilha do Dinheiro com Float: Nunca use float em Python para mapear colunas financeiras NUMERIC. Use sempre from decimal import Decimal para evitar dízimas binárias (ex: 0.1 + 0.2 = 0.30000000000000004).
  3. Armadilha do Truncamento de String: O Python expande strings conforme a memória RAM permitir, mas se o banco tiver VARCHAR(50), tentar gravar uma string de 51 caracteres causará StringDataRightTruncation.
  4. Armadilha do Fuso Horário em Timestamps: Salvar datetime.now() ingênuo (naive) em colunas TIMESTAMP WITHOUT TIME ZONE pode causar inconsistências graves em deploys na nuvem. Use sempre datetime.now(timezone.utc) com colunas TIMESTAMPTZ.

📖 Exemplo Guiado: Autoincremento Poliglota (DDL -> DML)

A solução para o problema de IDs colidindo na TecProExpress é deixar o próprio SGBD gerenciar a numeração automática das chaves primárias.

Muitos iniciantes não sabem que a sintaxe muda radicalmente entre os bancos.

🛠️ Código no MySQL 8.4 LTS

-- DDL
CREATE TABLE pacotes_mysql (
    id INT PRIMARY KEY AUTO_INCREMENT,
    destino VARCHAR(100)
);

-- DML (O ID é gerado sozinho)
INSERT INTO pacotes_mysql (destino) VALUES ('São Paulo');

🛠️ Código no PostgreSQL 17

-- DDL
CREATE TABLE pacotes_postgres (
    id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    destino VARCHAR(100)
);

-- DML
INSERT INTO pacotes_postgres (destino) VALUES ('Rio de Janeiro');

🔍 Detalhamento do Código:

  • AUTO_INCREMENT vs GENERATED ALWAYS AS IDENTITY: É essencial dominar ambos para atuar no mercado poliglota.
  • Note que, no INSERT, omitimos a coluna id de propósito, pois o SGBD é o responsável pela geração.

📐 Tipos de Dados e Restrições de Domínio

1. Power Tools de Domínio

Além dos tipos básicos (Texto, Número, Data), adicionamos inteligência com restrições (Constraints):

  • NOT NULL: Preenchimento obrigatório.
  • UNIQUE: Valor não repetido, mas permite NULL (ex: E-mail de marketing secundário).
  • CHECK: Valida lógicas (ex: CHECK (preco > 0)).

2. JSON vs JSONB (Arquitetura Híbrida)

A resposta para a TecProExpress armazenar "detalhes técnicos variáveis" sem criar mil colunas é utilizar o poder dos SGBDs modernos que suportam Documentos JSON dentro do modelo relacional:

  • MySQL JSON: Armazena e valida o texto estruturado.
  • Postgres JSONB: Armazena em binário indexado. É incrivelmente rápido para filtros internos.

🛠️ Prática Obrigatória: Carga Híbrida

Cenário: A tabela de produtos avançados da TecProExpress.

  1. Crie a tabela produto_hibrido com uma PK autoincremental e uma coluna chamada especificacoes do tipo JSON.
  2. Insira 2 produtos com detalhes estruturais completamente diferentes usando DML.

🚀 Script de Seed (Gabarito)

-- DDL (Sintaxe MySQL)
CREATE TABLE produto_hibrido (
    id INT PRIMARY KEY AUTO_INCREMENT,
    nome VARCHAR(100) NOT NULL,
    especificacoes JSON
);

-- DML
INSERT INTO produto_hibrido (nome, especificacoes) 
VALUES ('Notebook Gamer', '{"cpu": "i7", "ram": "16GB"}');

INSERT INTO produto_hibrido (nome, especificacoes) 
VALUES ('Cabo HDMI', '{"tamanho_metros": 2, "cor": "preto", "banhado_ouro": true}');


💻 Ponte Prática: Do SQL Manual ao SQLAlchemy 2.0 ORM

Como o SQLAlchemy 2.0 lida com colunas JSON nativas, autoincremento e mapeamento tipado em Python?

🔴 1. A Abordagem Manual (Serialização Manual de Strings JSON)

No modelo manual, o desenvolvedor precisa converter strings JSON na mão (json.loads/json.dumps), sem validação de integridade:

# ❌ ABORDAGEM COM SQL MANUAL: Conversão manual frágil
import sqlite3, json

conn = sqlite3.connect("catalogo_legado.db")
cursor = conn.cursor()
cursor.execute("CREATE TABLE IF NOT EXISTS prod (id INTEGER PRIMARY KEY, specs TEXT);")

# Inserção manual de string JSON:
dados_specs = {"voltagem": 220, "garantia_meses": 12}
cursor.execute("INSERT INTO prod (specs) VALUES (?);", (json.dumps(dados_specs),))
conn.commit()

🟢 2. A Abordagem com SQLAlchemy 2.0 (Coluna JSON Nativa e Tipagem)

Com o SQLAlchemy 2.0, usamos o tipo JSON nativo. O ORM converte dicionários Python diretamente em JSON no banco e restaura como dict na consulta:

# ✅ ABORDAGEM MODERNA: Mapeamento Relacional Híbrido (SQL + JSON)
from typing import Any
from sqlalchemy import create_engine, String, Integer, JSON, select
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, Session

class Base(DeclarativeBase):
    pass

class ProdutoHibridoModel(Base):
    """Tabela relacional com coluna JSON híbrida para especificações variáveis."""
    __tablename__ = "produtos_hibridos"

    id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    nome: Mapped[str] = mapped_column(String(100), nullable=False)
    especificacoes: Mapped[dict[str, Any]] = mapped_column(JSON, nullable=False)

    def __repr__(self) -> str:
        return f"Produto(id={self.id}, nome='{self.nome}', specs={self.especificacoes})"

🛠️ Mini-Projeto 06 (BD): Catálogo Híbrido de Produtos com JSON

Objetivo: Criar e consultar registros com estruturas de especificações heterogêneas utilizando a coluna JSON do SQLAlchemy 2.0.

📋 Pré-requisitos e Instalação

No terminal do seu ambiente virtual (PowerShell ou Bash), instale a biblioteca necessária:

pip install sqlalchemy

💻 Código Completo e Autocontido (miniprojeto_06_hibrido.py)

Crie o arquivo miniprojeto_06_hibrido.py e insira o código abaixo integralmente:

"""
Mini-Projeto 06: Catálogo Híbrido de Produtos com JSON
Curso: GTI - Banco de Dados Relacionais e Engenharia de Software
Stack: Python 3.11+ | SQLAlchemy 2.0 | SQLite
"""
import os
from typing import Any
from sqlalchemy import create_engine, String, Integer, JSON, select
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, Session

# 1. Definição Declarativa do Schema Híbrido
class Base(DeclarativeBase):
    pass

class ProdutoHibridoModel(Base):
    """Tabela relacional com coluna JSON híbrida para especificações variáveis."""
    __tablename__ = "produtos_hibridos"

    id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    nome: Mapped[str] = mapped_column(String(100), nullable=False)
    especificacoes: Mapped[dict[str, Any]] = mapped_column(JSON, nullable=False)

    def __repr__(self) -> str:
        return f"Produto(id={self.id}, nome='{self.nome}', specs={self.especificacoes})"

# 2. Ponto de Entrada Executável
if __name__ == "__main__":
    DB_FILE = "tecpro_produtos.db"

    # Reset preventivo para garantir idempotência em testes repetidos
    if os.path.exists(DB_FILE):
        os.remove(DB_FILE)

    engine = create_engine(f"sqlite:///{DB_FILE}", echo=False)
    Base.metadata.create_all(bind=engine)

    print("🧬 CATÁLOGO HÍBRIDO RELACIONAL + JSON (TECPROEXPRESS)")
    print("=" * 65)

    # 1. Inserindo produtos com especificações completamente distintas
    with Session(engine) as session:
        p1 = ProdutoHibridoModel(
            nome="Notebook Dell Latitude",
            especificacoes={"processador": "Core i7", "ram_gb": 16, "ssd_gb": 512, "voltagem": "Bivolt"}
        )
        p2 = ProdutoHibridoModel(
            nome="Cabo de Rede Furukawa Cat6",
            especificacoes={"comprimento_metros": 50, "blindagem": "STP", "cor": "Azul"}
        )
        p3 = ProdutoHibridoModel(
            nome="Sensor de Temperatura IoT",
            especificacoes={"bateria_duracao_anos": 3, "protocolo": "MQTT", "faixa_celsius": [-20, 70]}
        )

        session.add_all([p1, p2, p3])
        session.commit()
        print("✅ 3 Produtos híbridos inseridos com sucesso!")

    # 2. Consultando e acessando chaves JSON como objetos Python nativos
    print("\n🔍 Consultando catálogo e lendo atributos JSON diretamente:")
    with Session(engine) as session:
        produtos = session.scalars(select(ProdutoHibridoModel)).all()
        for p in produtos:
            print(f"\n📦 {p.nome} (ID: {p.id})")
            for chave, valor in p.especificacoes.items():
                print(f"   • {chave}: {valor}")
    print("=" * 65)

🚀 Como Executar

Execute o script no terminal:

python miniprojeto_06_hibrido.py

🖥️ Saída Esperada no Console

🧬 CATÁLOGO HÍBRIDO RELACIONAL + JSON (TECPROEXPRESS)
=================================================================
✅ 3 Produtos híbridos inseridos com sucesso!

🔍 Consultando catálogo e lendo atributos JSON diretamente:

📦 Notebook Dell Latitude (ID: 1)
   • processador: Core i7
   • ram_gb: 16
   • ssd_gb: 512
   • voltagem: Bivolt

📦 Cabo de Rede Furukawa Cat6 (ID: 2)
   • comprimento_metros: 50
   • blindagem: STP
   • cor: Azul

📦 Sensor de Temperatura IoT (ID: 3)
   • bateria_duracao_anos: 3
   • protocolo: MQTT
   • faixa_celsius: [-20, 70]
=================================================================

💡 Checkpoint de Lógica

Dica

Dica do Arquiteto: Evite usar BIGINT para tudo. No MySQL, se um campo nunca passará de 255 valores (ex: status de um pedido, idade), use TINYINT. Economia de espaço é sinônimo de performance em larga escala! 🚀🛡️



🧪 Quiz de Fixação e Autoavaliação — Capítulo 06

1. Por que valores monetários e financeiros NUNCA devem ser armazenados usando FLOAT ou DOUBLE?

  • A) Porque o banco rejeita salvar números com vírgula.
  • B) Porque FLOAT usa representação binária IEEE 754 com aproximações, acumulando dízimas e erros de centavos (ex: 0.1 + 0.2 = 0.30000000000000004). Deve-se usar sempre NUMERIC(p,s) / DECIMAL(p,s).
  • C) Porque FLOAT gasta mais memória que NUMERIC.
  • D) Porque FLOAT só aceita números inteiros.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: Bancos e sistemas fiscais exigem representação decimal exata (BCD) com NUMERIC(10,2) no banco e from decimal import Decimal no Python.


2. No armazenamento físico do SGBD, como o banco registra que uma coluna opcional contém valor NULL?

  • A) Gravando o texto 'NULL' ocupando 4 bytes na coluna.
  • B) Ativando exatamente 1 bit no mapa de bits (Null Bitmap) localizado no cabeçalho da linha, sem gastar nenhum byte extra de espaço na coluna.
  • C) Apagando a tabela inteira do disco.
  • D) Preenchendo a coluna com zeros binários.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: SGBDs são altamente eficientes: campos NULL não ocupam espaço na área de dados da linha; sua presença é sinalizada em um bitmask no cabeçalho da tupla.


3. Qual a diferença entre os tipos de texto CHAR(10) e VARCHAR(10)?

  • A) CHAR(10) é sempre fixo em 10 bytes (preenche com espaços se o texto tiver 3 letras); VARCHAR(10) gasta apenas o tamanho real das letras + 1 byte de cabeçalho de comprimento.
  • B) CHAR só aceita números e VARCHAR só aceita letras.
  • C) VARCHAR apaga o texto após 10 dias.
  • D) Não há nenhuma diferença prática.
💡 Ver Resposta e Justificativa

Resposta Correta: A
Justificativa: Use CHAR(n) apenas quando o tamanho for rigidamente fixo (ex: UF CHAR(2), Hash SHA-256 CHAR(64)). Para dados de tamanho variável (nomes, emails), use VARCHAR(n).


🎯 Laboratório Prático

Coloque este conhecimento em prática agora mesmo executando o roteiro autoguiado:
👉 ATIVIDADE 06: SQL DML (MANIPULAÇÃO DE DADOS)

🔑 CAPÍTULO 07: CHAVES, RELACIONAMENTOS E CARDINALIDADE


🎯 Objetivos de Aprendizagem

Ao final deste capítulo (estimativa: 2 horas de estudo autoguiado), você será capaz de:

  • 🔹 Dominar os conceitos de Chave Candidata, Chave Primária (PK), Chave Estrangeira (FK), Chave Composta e Chave Substituta (Surrogate Key).
  • 🔹 Compreender o princípio da Integridade Referencial e os comportamentos de ação em cascata (ON DELETE CASCADE, ON DELETE RESTRICT, ON DELETE SET NULL).
  • 🔹 Modelar integridade referencial rigorosa com SQLAlchemy 2.0 utilizando ForeignKey() e relationship().
  • 🔹 Prevenir exclusões acidentais de registros pai com dependentes no banco de dados.

A grande força de um banco de dados Relacional está no seu próprio nome: Relacionamentos. Neste capítulo, vamos entender como tabelas isoladas se conectam para formar um ecossistema inteligente, capaz de responder perguntas de negócios complexas. 🛡️🧩

🏢 O Cenário Prático (Seu Desafio)

Na TecProExpress, o RH solicitou um sistema para controlar quais motoristas estão utilizando quais caminhões. Além disso, eles precisam de um sistema de entregas, onde um cliente pode ter vários pacotes. Os estagiários de TI desenharam tabelas, mas não conseguiram "conectá-las".

"Seu desafio é ser o Engenheiro de Integração: usar Chaves Estrangeiras para garantir que nenhum pacote seja registrado sem um cliente responsável, dominando as cardinalidades 1:N e N:M."


🧠 Fundamentos: O Fio Condutor (FK)

A Chave Estrangeira (FK) é o mecanismo de vínculo. Ela é literalmente o valor da Chave Primária (PK) de uma tabela, copiado para dentro de outra tabela.

📊 Diagrama de Cardinalidade (Crow's Foot)

Existem 3 tipos básicos de relacionamentos, representados pela notação "Pé de Galinha":

erDiagram
    CLIENTE ||--o{ PACOTE : "possui (1:N)"
    MOTORISTA |o--o| CAMINHAO : "dirige (1:1)"
    ENTREGADOR }|--|{ ROTA : "atua (N:M)"

🔍 Detalhamento Visual:

  • ||: Indica que a participação é obrigatória (ex: Mínimo 1).
  • o{: Indica "Zero ou Muitos". Um cliente pode acabar de se cadastrar e ainda não ter pacotes (zero), ou pode ter dezenas (muitos).

📐 O Guia de Ouro da Cardinalidade

Onde eu coloco a Chave Estrangeira? Esta é a pergunta que mais derruba candidatos em entrevistas.

CardinalidadeAnalogiaOnde colocar a FK?
1:1 (Um para Um)Casamento exclusivoEm qualquer lado (prefira a tabela dependente).
1:N (Um para Muitos)Pai e FilhosSempre no lado N. (O pacote recebe o ID do Cliente).
N:M (Muitos para Muitos)Atores e FilmesCria uma Terceira Tabela! (Associativa).

📖 Exemplo Guiado: O Relacionamento 1:N (Pai e Filho)

A TecProExpress quer vincular um cliente aos seus pacotes. Veja como aplicar isso no SQL garantindo a ordem correta (DDL -> DML).

🛠️ Código do Exemplo

-- PASSO 1: DDL (Criar a tabela PAI primeiro)
CREATE TABLE cliente_exp (
    id INT PRIMARY KEY,
    nome VARCHAR(100)
);

-- PASSO 2: DDL (Criar a tabela FILHO com a Chave Estrangeira)
CREATE TABLE pacote (
    codigo INT PRIMARY KEY,
    descricao VARCHAR(100),
    id_cliente INT, -- A coluna que receberá o link
    CONSTRAINT fk_cliente_pacote FOREIGN KEY (id_cliente) REFERENCES cliente_exp(id)
);

-- PASSO 3: DML (Inserir Pai, depois Filho)
INSERT INTO cliente_exp VALUES (10, 'Maria Silva');
INSERT INTO pacote VALUES (5001, 'Notebook', 10);

🔍 Detalhamento do Código:

  • A Regra da Ordem: Você não pode nascer sem ter pais. No SGBD, você não pode inserir um pacote apontando para o cliente 10 se o cliente 10 ainda não foi cadastrado (INSERT).
  • FOREIGN KEY: Avisa o SGBD para vigiar esta coluna. Se alguém tentar deletar a Maria Silva, o banco bloqueará, avisando que existem pacotes vinculados a ela.

🛠️ Prática Obrigatória: Resolvendo o Caos do N:M

Cenário: O sistema de Entregadores e Rotas da TecProExpress. Um Entregador pode atuar em Várias Rotas. Uma Rota pode ser feita por Vários Entregadores.

  1. Crie as tabelas independentes entregador e rota.
  2. Crie a tabela associativa escala_trabalho contendo a chave estrangeira de ambos.
  3. Insira dados provando a vinculação.

🚀 Script de Seed (Gabarito de Associação)

-- DDL PAI 1
CREATE TABLE entregador ( id INT PRIMARY KEY, nome VARCHAR(50) );
-- DDL PAI 2
CREATE TABLE rota ( id INT PRIMARY KEY, regiao VARCHAR(50) );

-- DDL FILHO ASSOCIATIVO (A ponte entre os dois)
CREATE TABLE escala_trabalho (
    id_entregador INT,
    id_rota INT,
    PRIMARY KEY (id_entregador, id_rota), -- Chave Composta
    FOREIGN KEY (id_entregador) REFERENCES entregador(id),
    FOREIGN KEY (id_rota) REFERENCES rota(id)
);

-- DML (Carregando a base)
INSERT INTO entregador VALUES (1, 'Carlos'), (2, 'Ana');
INSERT INTO rota VALUES (100, 'Centro'), (200, 'Litoral');

-- DML (O Relacionamento N:M na prática)
INSERT INTO escala_trabalho VALUES (1, 100); -- Carlos atende o Centro
INSERT INTO escala_trabalho VALUES (1, 200); -- Carlos atende o Litoral
INSERT INTO escala_trabalho VALUES (2, 100); -- Ana também atende o Centro


💻 Ponte Prática: Do SQL Manual ao SQLAlchemy 2.0 ORM

Como o SQLAlchemy 2.0 implementa Chaves Estrangeiras (FK) e Relacionamentos 1:N e N:M de forma fluida no código?

🔴 1. A Abordagem Manual (IDs Órfãos e Falta de Navegabilidade)

No SQL manual puro, para listar os pacotes de um cliente, você precisa escrever queries JOIN manuais. Se alguém excluir o cliente sem verificar os pacotes, registros órfãos poluem o banco:

# ❌ ABORDAGEM COM SQL MANUAL: Gestão manual de FK e perigo de órfãos
import sqlite3

conn = sqlite3.connect("entregas_legadas.db")
cursor = conn.cursor()
cursor.execute("PRAGMA foreign_keys = ON;") # No SQLite é preciso ligar explicitamente!
# Risco de inserir pacote com cliente inexistente se a FK não for configurada:
cursor.execute("INSERT INTO pacote VALUES (999, 'Celular', 99999);") # ⚠️ ID de cliente inexistente!

🟢 2. A Abordagem com SQLAlchemy 2.0 (Relacionamentos Bidirecionais relationship())

Com o SQLAlchemy 2.0, definimos ForeignKey("tabela.id") e a propriedade relationship(). O Python gerencia o grafo de objetos em memória e emite as chaves estrangeiras automaticamente:

# ✅ ABORDAGEM MODERNA COM SQLALCHEMY 2.0: Relacionamento 1:N Pai-Filho
from typing import Any
from sqlalchemy import create_engine, String, Integer, ForeignKey, select
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship, Session

class Base(DeclarativeBase):
    pass

class ClienteExpModel(Base):
    """Lado 1 do relacionamento (Pai)."""
    __tablename__ = "clientes_exp"

    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    nome: Mapped[str] = mapped_column(String(100), nullable=False)

    # Relacionamento 1:N (Um cliente possui muitos pacotes)
    pacotes: Mapped[list["PacoteModel"]] = relationship(
        "PacoteModel", back_populates="cliente", cascade="all, delete-orphan"
    )

    def __repr__(self) -> str:
        return f"Cliente(id={self.id}, nome='{self.nome}')"

class PacoteModel(Base):
    """Lado N do relacionamento (Filho)."""
    __tablename__ = "pacotes_exp"

    codigo: Mapped[int] = mapped_column(Integer, primary_key=True)
    descricao: Mapped[str] = mapped_column(String(100), nullable=False)
    id_cliente: Mapped[int] = mapped_column(ForeignKey("clientes_exp.id"), nullable=False)

    # Vínculo inverso com o Pai
    cliente: Mapped["ClienteExpModel"] = relationship("ClienteExpModel", back_populates="pacotes")

    def __repr__(self) -> str:
        return f"Pacote(codigo={self.codigo}, desc='{self.descricao}', cliente_id={self.id_cliente})"

🛠️ Mini-Projeto 07 (BD): Gestor de Entregas e Rastreamento 1:N

Objetivo: Construir e popular um modelo relacional 1:N, navegando pelas entidades conectadas a partir do objeto Python.

📋 Pré-requisitos e Instalação

No terminal do seu ambiente virtual (PowerShell ou Bash), instale a biblioteca necessária:

pip install sqlalchemy

💻 Código Completo e Autocontido (miniprojeto_07_relacionamentos.py)

Crie o arquivo miniprojeto_07_relacionamentos.py e insira o código abaixo integralmente:

"""
Mini-Projeto 07: Gestor de Entregas e Rastreamento 1:N
Curso: GTI - Banco de Dados Relacionais e Engenharia de Software
Stack: Python 3.11+ | SQLAlchemy 2.0 | SQLite
"""
import os
from sqlalchemy import create_engine, String, Integer, ForeignKey, select
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship, Session

# 1. Definição Declarativa do Schema Relacional 1:N
class Base(DeclarativeBase):
    pass

class ClienteExpModel(Base):
    """Lado 1 do relacionamento (Pai)."""
    __tablename__ = "clientes_exp"

    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    nome: Mapped[str] = mapped_column(String(100), nullable=False)

    # Relacionamento 1:N (Um cliente possui muitos pacotes)
    pacotes: Mapped[list["PacoteModel"]] = relationship(
        "PacoteModel", back_populates="cliente", cascade="all, delete-orphan"
    )

    def __repr__(self) -> str:
        return f"Cliente(id={self.id}, nome='{self.nome}')"

class PacoteModel(Base):
    """Lado N do relacionamento (Filho)."""
    __tablename__ = "pacotes_exp"

    codigo: Mapped[int] = mapped_column(Integer, primary_key=True)
    descricao: Mapped[str] = mapped_column(String(100), nullable=False)
    id_cliente: Mapped[int] = mapped_column(ForeignKey("clientes_exp.id"), nullable=False)

    # Vínculo inverso com o Pai
    cliente: Mapped["ClienteExpModel"] = relationship("ClienteExpModel", back_populates="pacotes")

    def __repr__(self) -> str:
        return f"Pacote(codigo={self.codigo}, desc='{self.descricao}', cliente_id={self.id_cliente})"

# 2. Ponto de Entrada Executável
if __name__ == "__main__":
    DB_FILE = "tecpro_entregas.db"

    # Reset preventivo para garantir idempotência em testes repetidos
    if os.path.exists(DB_FILE):
        os.remove(DB_FILE)

    engine = create_engine(f"sqlite:///{DB_FILE}", echo=False)
    Base.metadata.create_all(bind=engine)

    print("🔗 GESTOR DE RELACIONAMENTOS 1:N (TECPROEXPRESS)")
    print("=" * 65)

    # 1. Inserindo o Pai e seus Filhos diretamente pela coleção Python
    with Session(engine) as session:
        cliente_ana = ClienteExpModel(id=101, nome="Ana Cristina Santos")

        # Associando objetos filhos diretamente na lista:
        p1 = PacoteModel(codigo=5001, descricao="Monitor Gamer 27'", id_cliente=101)
        p2 = PacoteModel(codigo=5002, descricao="Teclado Mecânico RGB", id_cliente=101)
        p3 = PacoteModel(codigo=5003, descricao="Mouse Ergonômico", id_cliente=101)
        cliente_ana.pacotes.extend([p1, p2, p3])

        session.merge(cliente_ana)
        session.commit()
        print(f"✅ Cliente {cliente_ana.nome} e {len(cliente_ana.pacotes)} pacotes persistidos!")

    # 2. Consultando o Cliente e navegando nos seus pacotes automaticamente
    print("\n🔍 Consultando entregas vinculadas por integridade referencial:")
    with Session(engine) as session:
        cliente = session.get(ClienteExpModel, 101)
        if cliente:
            print(f"👤 Destinatário: {cliente.nome}")
            print("📦 Pacotes em rota de entrega:")
            for pacote in cliente.pacotes:
                print(f"   • Código #{pacote.codigo}: {pacote.descricao}")
    print("=" * 65)

🚀 Como Executar

Execute o script no terminal:

python miniprojeto_07_relacionamentos.py

🖥️ Saída Esperada no Console

🔗 GESTOR DE RELACIONAMENTOS 1:N (TECPROEXPRESS)
=================================================================
✅ Cliente Ana Cristina Santos e 3 pacotes persistidos!

🔍 Consultando entregas vinculadas por integridade referencial:
👤 Destinatário: Ana Cristina Santos
📦 Pacotes em rota de entrega:
   • Código #5001: Monitor Gamer 27'
   • Código #5002: Teclado Mecânico RGB
   • Código #5003: Mouse Ergonômico
=================================================================

💡 Checkpoint de Lógica

Importante

Reflexão Profissional: Em um relacionamento Muitos para Muitos (N:M), por que a Tabela Associativa (escala_trabalho) tem uma PRIMARY KEY composta pelos dois IDs? (Resposta: Para garantir que o mesmo entregador não seja escalado duas vezes exatamente para a mesma rota no mesmo turno). 🧠🛡️



🧪 Quiz de Fixação e Autoavaliação — Capítulo 07

1. O que é a regra da 'Integridade Referencial' no Modelo Relacional?

  • A) Toda tabela deve ter pelo menos 100 linhas de dados.
  • B) Um valor de Chave Estrangeira (FK) em uma tabela filha DEVE obrigatoriamente apontar para uma Chave Primária (PK) existente e válida na tabela pai (ou ser NULL).
  • C) Os nomes das colunas devem estar sempre em maiúsculas.
  • D) O banco de dados deve reiniciar a cada 24 horas.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: Integridade referencial impede a existência de 'registros órfãos' (ex: uma venda apontando para um cliente inexistente com id = 9999).


2. Qual comportamento de integridade referencial deve ser configurado para impedir que um Departamento seja excluído enquanto ainda existirem Funcionários vinculados a ele?

  • A) ON DELETE CASCADE
  • B) ON DELETE RESTRICT (ou NO ACTION)
  • C) ON DELETE SET DEFAULT
  • D) DROP DATABASE
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: RESTRICT ou NO ACTION faz o SGBD bloquear a exclusão da linha pai, emitindo um erro caso ainda existam dependentes vinculados a ela.


3. O que é uma 'Chave Substituta' (Surrogate Key) em oposição a uma 'Chave Natural'?

  • A) Uma chave de mentira que não é salva no banco.
  • B) Um identificador artificial numérico gerado pelo sistema (como um id INT AUTO_INCREMENT ou UUID) sem significado no mundo real, usado para simplificar PKs e FKs.
  • C) O CPF ou CNPJ digitado pelo cliente.
  • D) Uma senha criptografada.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: Surrogate Keys (como id = 1, 2, 3) evitam o uso de chaves naturais complexas (como CPF ou placas), facilitando indexação, joins rápidos e alterações de dados cadastrais.


🎯 Laboratório Prático

Coloque este conhecimento em prática agora mesmo executando o roteiro autoguiado:
👉 ATIVIDADE 05: SQL DDL (ESTRUTURA)

🧩 CAPÍTULO 08: EXTENSÕES DO MER E REVISÃO


🎯 Objetivos de Aprendizagem

Ao final deste capítulo (estimativa: 2 horas de estudo autoguiado), você será capaz de:

  • 🔹 Modelar estruturas avançadas no MER: Especialização/Generalização (Herança de Tabelas), Entidades Fracas e Entidades Associativas.
  • 🔹 Dominar relacionamentos recursivos (Autorrelacionamento) como árvores de categorias e hierarquias de chefia.
  • 🔹 Mapear herança de classes para o banco relacional (Estratégia Joined Table, Single Table e Table per Class).
  • 🔹 Implementar Entidade Fraca e Herança de Tabelas (Joined Table Inheritance) no SQLAlchemy 2.0. (O código de autorrelacionamento com remote_side é aprofundado no laboratório prático da disciplina.)

Até agora, lidamos com objetos simples (Clientes, Produtos, Pedidos). Mas a vida real não é apenas "Pai e Filho". Como mapeamos um "Dependente" que só existe se o titular existir? Como mapeamos a herança de características? Hoje vamos ver as Extensões do MER. 🛡️🧩

🏢 O Cenário Prático (Seu Desafio)

O RH da TecProExpress solicitou que o banco de dados armazene os Dependentes (filhos) de cada funcionário para o plano de saúde. Além disso, a empresa agora possui uma frota mista: Caminhões (que têm capacidade de carga) e Motos (que têm cilindrada), mas ambos são Veículos.

"Seu desafio é elevar o nível da sua arquitetura, aplicando os conceitos de 'Entidade Fraca' para os dependentes e 'Herança' para a frota, garantindo que o banco de dados seja escalável e evite colunas com valores NULL desnecessários."


🧠 Fundamentos: Modelagem Avançada

1. Entidade Fraca (Dependência Existencial)

Uma Entidade Fraca não tem vida própria. O "Filho do João" só está no plano de saúde porque o João é funcionário. Se João for demitido (deletado), o dependente deve desaparecer do banco também.

  • No SQL, garantimos isso usando a restrição ON DELETE CASCADE na Chave Estrangeira.

2. Generalização e Especialização (Herança)

Assim como na Programação Orientada a Objetos (POO), no banco de dados podemos ter uma tabela "Pai" Genérica e tabelas "Filhas" Específicas.

📊 Diagrama de Herança (IS-A)

flowchart TD
    VEI["🚙 VEÍCULO <br/> ID, Placa, Ano"] -->|Especialização| CAM["🚛 CAMINHÃO <br/> Cap_Carga"]
    VEI -->|Especialização| MOT["🏍️ MOTO <br/> Cilindradas"]
    
    style VEI fill:#e3f2fd
    style CAM fill:#fffde7
    style MOT fill:#fffde7
  • Vantagem: Evita colocar "Capacidade_Carga" e "Cilindradas" na mesma tabela, o que faria as Motos terem a coluna de carga como NULL (desperdício de espaço e lógica).

📖 Exemplo Guiado: Criando uma Entidade Fraca (DDL -> DML)

Veja como modelar a dependência forte no PostgreSQL ou MySQL para a TecProExpress.

🛠️ Código do Exemplo

-- PASSO 1: DDL (A Entidade Forte / Titular)
CREATE TABLE funcionario (
    id INT PRIMARY KEY,
    nome VARCHAR(100)
);

-- PASSO 2: DDL (A Entidade Fraca / Dependente)
CREATE TABLE dependente (
    id_func INT,
    nome_dep VARCHAR(100),
    data_nasc DATE,
    PRIMARY KEY (id_func, nome_dep), -- Chave Composta!
    CONSTRAINT fk_titular FOREIGN KEY (id_func) 
        REFERENCES funcionario(id) 
        ON DELETE CASCADE -- A mágica acontece aqui!
);

-- PASSO 3: DML (Carga Inicial)
INSERT INTO funcionario VALUES (10, 'Carlos Oliveira');
INSERT INTO dependente VALUES (10, 'Pedrinho', '2015-05-10');

🔍 Detalhamento do Código:

  • Chave Composta: A PK do dependente é a junção do ID do funcionário + o nome do dependente.
  • ON DELETE CASCADE: Se rodarmos DELETE FROM funcionario WHERE id = 10;, o banco de dados, sozinho, vai deletar o "Pedrinho" da tabela dependente. Integridade automática!

🛠️ Prática Obrigatória: Implementando a Frota (Herança)

Cenário: A frota mista da TecProExpress.

  1. Crie a tabela genérica veiculo (Pai).
  2. Crie as tabelas filhas caminhao e moto.
  3. Faça com que a Chave Primária das filhas também seja a Chave Estrangeira que aponta para o Pai.

🚀 Script de Seed (Gabarito Físico)

-- DDL Genérico (Pai)
CREATE TABLE veiculo (
    id INT PRIMARY KEY,
    placa VARCHAR(7) UNIQUE,
    ano INT
);

-- DDL Específico (Caminhão)
CREATE TABLE caminhao (
    id_veiculo INT PRIMARY KEY, -- PK e FK ao mesmo tempo!
    capacidade_toneladas INT,
    FOREIGN KEY (id_veiculo) REFERENCES veiculo(id)
);

-- DML (Inserindo a Herança)
-- Passo A: Insere na tabela Pai
INSERT INTO veiculo VALUES (1, 'AAA1234', 2026);
-- Passo B: Complementa na tabela Filha
INSERT INTO caminhao VALUES (1, 15);

🔍 O Segredo da Abstração

No DML acima, o veículo ID 1 existe em duas partes: os dados gerais estão no Pai, e os dados específicos estão no Caminhão. Na hora do relatório, usaremos um JOIN para juntar as duas metades.



💻 Ponte Prática: Do SQL Manual ao SQLAlchemy 2.0 ORM

Como o SQLAlchemy 2.0 mapeia a Herança de Classes Python para tabelas relacionais especializadas (Joined Table Inheritance)?

🔴 1. A Abordagem Manual (Dois Inserts e Queries Manuais com JOIN)

No SQL manual, para salvar um caminhão, o programador é obrigado a rodar dois comandos INSERT separados e cuidar da sincronização das PKs:

# ❌ ABORDAGEM COM SQL MANUAL: 2 INSERTs manuais suscetíveis a inconsistências
cursor.execute("INSERT INTO veiculo (id, placa, ano) VALUES (1, 'AAA1234', 2026);")
cursor.execute("INSERT INTO caminhao (id_veiculo, capacidade_ton) VALUES (1, 15);")

🟢 2. A Abordagem com SQLAlchemy 2.0 (Herança de Tabelas Unidas - Joined Table Inheritance)

Com o SQLAlchemy 2.0, a classe CaminhaoModel herda de VeiculoModel. O ORM cuida de dividir os dados entre as tabelas e unificá-los na consulta:

# ✅ ABORDAGEM MODERNA: Herança de Classes Mapeada para Tabelas
from sqlalchemy import create_engine, String, Integer, ForeignKey, select
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, Session

class Base(DeclarativeBase):
    pass

class VeiculoModel(Base):
    """Tabela Pai (Generalização)."""
    __tablename__ = "veiculos_geral"

    id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    placa: Mapped[str] = mapped_column(String(7), unique=True, nullable=False)
    tipo: Mapped[str] = mapped_column(String(20)) # Discriminador polimórfico

    __mapper_args__ = {
        "polymorphic_on": "tipo",
        "polymorphic_identity": "veiculo_generico"
    }

class CaminhaoModel(VeiculoModel):
    """Tabela Filha Especializada (Herança)."""
    __tablename__ = "caminhoes_especificos"

    id: Mapped[int] = mapped_column(ForeignKey("veiculos_geral.id"), primary_key=True)
    capacidade_ton: Mapped[int] = mapped_column(Integer, nullable=False)

    __mapper_args__ = {
        "polymorphic_identity": "caminhao"
    }

class MotoModel(VeiculoModel):
    """Tabela Filha Especializada (Herança)."""
    __tablename__ = "motos_especificas"

    id: Mapped[int] = mapped_column(ForeignKey("veiculos_geral.id"), primary_key=True)
    cilindradas: Mapped[int] = mapped_column(Integer, nullable=False)

    __mapper_args__ = {
        "polymorphic_identity": "moto"
    }

🛠️ Mini-Projeto 08 (BD): Herança Polimórfica de Frota em Python

Objetivo: Persistir e consultar uma frota mista (Caminhões e Motos) usando herança nativa (Joined Table Inheritance) do SQLAlchemy 2.0.

📋 Pré-requisitos e Instalação

No terminal do seu ambiente virtual (PowerShell ou Bash), instale a biblioteca necessária:

pip install sqlalchemy

💻 Código Completo e Autocontido (miniprojeto_08_heranca.py)

Crie o arquivo miniprojeto_08_heranca.py e insira o código abaixo integralmente:

"""
Mini-Projeto 08: Herança Polimórfica de Frota em Python
Curso: GTI - Banco de Dados Relacionais e Engenharia de Software
Stack: Python 3.11+ | SQLAlchemy 2.0 | SQLite
"""
import os
from sqlalchemy import create_engine, String, Integer, ForeignKey, select
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, Session

# 1. Definição Declarativa com Herança de Tabelas Conectadas (Joined Table Inheritance)
class Base(DeclarativeBase):
    pass

class VeiculoModel(Base):
    """Tabela Pai (Generalização)."""
    __tablename__ = "veiculos_geral"

    id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    placa: Mapped[str] = mapped_column(String(7), unique=True, nullable=False)
    tipo: Mapped[str] = mapped_column(String(20))  # Discriminador polimórfico

    __mapper_args__ = {
        "polymorphic_on": "tipo",
        "polymorphic_identity": "veiculo_generico",
    }

class CaminhaoModel(VeiculoModel):
    """Tabela Filha Especializada (Herança)."""
    __tablename__ = "caminhoes_especificos"

    id: Mapped[int] = mapped_column(ForeignKey("veiculos_geral.id"), primary_key=True)
    capacidade_ton: Mapped[int] = mapped_column(Integer, nullable=False)

    __mapper_args__ = {
        "polymorphic_identity": "caminhao",
    }

class MotoModel(VeiculoModel):
    """Tabela Filha Especializada (Herança)."""
    __tablename__ = "motos_especificas"

    id: Mapped[int] = mapped_column(ForeignKey("veiculos_geral.id"), primary_key=True)
    cilindradas: Mapped[int] = mapped_column(Integer, nullable=False)

    __mapper_args__ = {
        "polymorphic_identity": "moto",
    }

# 2. Ponto de Entrada Executável
if __name__ == "__main__":
    DB_FILE = "tecpro_frota_polimorfica.db"

    # Reset preventivo: indispensável devido à restrição UNIQUE na coluna 'placa'
    if os.path.exists(DB_FILE):
        os.remove(DB_FILE)

    engine = create_engine(f"sqlite:///{DB_FILE}", echo=False)
    Base.metadata.create_all(bind=engine)

    print("🧭 HERANÇA RELACIONAL (JOINED TABLE INHERITANCE) - TECPROEXPRESS")
    print("=" * 65)

    # 1. Inserindo objetos polimórficos diretamente
    with Session(engine) as session:
        c1 = CaminhaoModel(placa="VOL4499", capacidade_ton=28)
        m1 = MotoModel(placa="HON1234", cilindradas=250)
        session.add_all([c1, m1])
        session.commit()
        print("✅ Caminhão e Moto persistidos com herança automática nas tabelas!")

    # 2. Consultando todos os veículos como a classe Pai
    print("\n🔍 Consultando a classe genérica VeiculoModel (Polimorfismo puro):")
    with Session(engine) as session:
        veiculos = session.scalars(select(VeiculoModel)).all()
        for v in veiculos:
            if isinstance(v, CaminhaoModel):
                print(f"  🚛 Caminhão [Placa: {v.placa}] -> Carga: {v.capacidade_ton} toneladas")
            elif isinstance(v, MotoModel):
                print(f"  🏍️ Moto [Placa: {v.placa}] -> Potência: {v.cilindradas} cc")
    print("=" * 65)

🚀 Como Executar

Execute o script no terminal:

python miniprojeto_08_heranca.py

🖥️ Saída Esperada no Console

🧭 HERANÇA RELACIONAL (JOINED TABLE INHERITANCE) - TECPROEXPRESS
=================================================================
✅ Caminhão e Moto persistidos com herança automática nas tabelas!

🔍 Consultando a classe genérica VeiculoModel (Polimorfismo puro):
  🚛 Caminhão [Placa: VOL4499] -> Carga: 28 toneladas
  🏍️ Moto [Placa: HON1234] -> Potência: 250 cc
=================================================================

💡 Checkpoint de Lógica

Importante

Revisão Estratégica: Chegamos ao fim da modelagem pura. Você aprendeu a criar Entidades, ligar Relacionamentos (1:N, N:M), lidar com Dependências e Herança. O próximo passo será refinar tudo isso com a "Normalização", a vacina contra as redundâncias. O seu raciocínio estrutural está preparado? 🧠🛡️



🧪 Quiz de Fixação e Autoavaliação — Capítulo 08

1. O que caracteriza uma 'Entidade Fraca' no Modelo Entidade-Relacionamento?

  • A) Uma tabela com poucos registros cadastrados.
  • B) Uma entidade cuja existência depende de outra entidade proprietária e cuja chave primária é composta pela sua chave parcial somada à chave estrangeira da entidade pai (ex: Dependente de um Funcionário).
  • C) Uma tabela que não aceita índices.
  • D) Um banco de dados corrompido.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: Entidades fracas não possuem identidade independente no mundo real: se o 'Funcionário' for desligado da empresa, seus 'Dependentes' deixam de fazer sentido no sistema.


2. O que é um 'Autorrelacionamento' (Relacionamento Recursivo)?

  • A) Quando uma tabela possui uma chave estrangeira que aponta para a chave primária da PRÓPRIA tabela (ex: Funcionario.id_gerente aponta para Funcionario.id_funcionario).
  • B) Quando duas tabelas têm o mesmo nome.
  • C) Quando o banco se conecta com a internet sozinho.
  • D) Quando um loop infinito trava o servidor.
💡 Ver Resposta e Justificativa

Resposta Correta: A
Justificativa: Autorrelacionamentos são clássicos para representar hierarquias (ex: chefia de funcionários, árvores de categorias/subcategorias e genealogia animal no CattleFlow).


3. Na estratégia 'Single Table Inheritance' (Tabela Única) de mapeamento de herança, como o SGBD diferencia se a linha é do tipo PessoaFisica ou PessoaJuridica?

  • A) Criando dois bancos de dados separados.
  • B) Utilizando uma coluna discriminadora (ex: tipo_pessoa VARCHAR(2)), deixando nulos os atributos que não pertencem àquela subclasse específica.
  • C) Calculando a raiz quadrada do ID.
  • D) Através do endereço IP do cliente.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: Single Table coloca todas as subclasses na mesma tabela com uma coluna 'discriminator', sendo muito rápida para consultas polimórficas sem necessidade de JOINs.


🎯 Laboratório Prático

Coloque este conhecimento em prática agora mesmo executando o roteiro autoguiado:
👉 ATIVIDADE 02: MODELAGEM CONCEITUAL

➗ CAPÍTULO 09: MAPEAMENTO MER ➔ RELACIONAL E ÁLGEBRA


🎯 Objetivos de Aprendizagem

Ao final deste capítulo (estimativa: 2 horas de estudo autoguiado), você será capaz de:

  • 🔹 Aplicar as 7 regras formais de transformação do Modelo Conceitual (MER) para o Modelo Lógico Relacional.
  • 🔹 Mapear relacionamentos N:N gerando automaticamente a Tabela Associativa intermediária com chave composta.
  • 🔹 Dominar os operadores da Álgebra Relacional: Projeção ($\pi$), Seleção ($\sigma$), Junção ($\bowtie$), Produto Cartesiano ($\times$) e União ($\cup$).
  • 🔹 Escrever consultas complexas conectando álgebra formal à sintaxe SQL e SQLAlchemy 2.0.

Chegamos à fase final do design de banco de dados. Você já tem um Diagrama Entidade-Relacionamento (DER) validado com o cliente. Agora, você precisa "traduzir" esse desenho para a estrutura física (Tabelas, Chaves) que o SGBD entende. Além disso, vamos espiar o motor matemático que faz o SQL ser tão rápido: a Álgebra Relacional. 🛡️🧩

🏢 O Cenário Prático (Seu Desafio)

Os diretores da TecProExpress aprovaram no quadro branco o diagrama de como a empresa funciona: "O Cliente faz um Pedido, que é entregue por um Motorista usando um Veículo".

"Seu desafio é pegar esse desenho (MER) e transformá-lo em tabelas reais usando regras estritas de mapeamento para que a equipe de desenvolvimento possa finalmente conectar a aplicação ao SGBD."


🧠 Fundamentos: O Roteiro de Mapeamento (MER -> Lógico)

A tradução de um diagrama para tabelas segue um roteiro matemático. Você não pode adivinhar o resultado; deve aplicar a regra:

Componente no MER (Mundo Abstrato)O que vira no Relacional (Tabelas)
Entidade ForteVira uma Tabela independente (com PK).
Atributo SimplesVira uma Coluna na tabela.
Relacionamento 1:NA PK do lado 1 vira FK no lado N.
Relacionamento N:MVira uma Tabela Associativa (composta pelas duas FKs).
Atributo MultivaloradoVira uma Nova Tabela atrelada à original (Ex: Telefones).

📖 Exemplo Guiado: Mapeamento da Rota de Entregas (DDL -> DML)

A TecProExpress tem Entregadores e Telefones. Como o entregador pode ter vários telefones (Multivalorado), não podemos colocar 3 colunas de telefone na mesma tabela. A regra diz: "Crie uma nova tabela".

🛠️ Código do Exemplo

-- PASSO 1: DDL (A Tabela Principal - Entidade)
CREATE TABLE entregador_tecpro (
    id INT PRIMARY KEY,
    nome VARCHAR(100) NOT NULL
);

-- PASSO 2: DDL (A Nova Tabela gerada pelo Atributo Multivalorado)
CREATE TABLE telefone_entregador (
    id_entregador INT,
    numero VARCHAR(15),
    PRIMARY KEY (id_entregador, numero), -- Chave Composta!
    FOREIGN KEY (id_entregador) REFERENCES entregador_tecpro(id) ON DELETE CASCADE
);

-- PASSO 3: DML (Carga dos Dados)
INSERT INTO entregador_tecpro VALUES (1, 'Marcos Silva');
INSERT INTO telefone_entregador VALUES (1, '11-9999-8888');
INSERT INTO telefone_entregador VALUES (1, '11-7777-6666');

🔍 Detalhamento do Código:

  • A tabela telefone_entregador só tem sentido de existir por causa do entregador. Por isso ela tem ON DELETE CASCADE.
  • A junção do ID com o Número forma a Chave Primária, garantindo que não cadastraremos o mesmo número duas vezes para a mesma pessoa.

🧮 Álgebra Relacional (O Motor Oculto)

Você nunca escreverá "Álgebra Relacional" no terminal do seu trabalho. No entanto, o motor do MySQL/PostgreSQL lê o seu SQL, o converte em álgebra relacional nos bastidores, e otimiza a matemática para responder rápido.

🧮 Mapa Visual dos Operadores da Álgebra Relacional

flowchart TD
    subgraph Unarios["Operadores Unários (1 Relação)"]
        SIGMA["σ Seleção (Filtro Horizontal)<br>SQL: WHERE preco > 100"]
        PI["π Projeção (Filtro Vertical)<br>SQL: SELECT nome, email"]
    end

    subgraph Binarios["Operadores Binarios (2 Relações)"]
        JOIN["⋈ Junção / Theta Join<br>SQL: INNER JOIN ... ON"]
        UNION["∪ União (R1 ∪ R2)<br>SQL: UNION"]
        DIFF["- Diferença (R1 - R2)<br>SQL: EXCEPT / NOT IN"]
        INTER["∩ Interseção (R1 ∩ R2)<br>SQL: INTERSECT"]
        CART["× Produto Cartesiano<br>SQL: CROSS JOIN"]
    end

📊 Fluxo da Junção (⋈)

flowchart LR
    T1["Tabela: CLIENTE<br/>ID=10, Nome=Maria"] --> J{"⋈<br/>Onde as chaves batem"}
    T2["Tabela: PACOTE<br/>ID_Cliente=10, Item=Laptop"] --> J
    J --> RESULT["Resultado Unificado:<br/>Maria comprou um Laptop"]

🛠️ Prática Obrigatória: O Encontro do SQL com a Álgebra

Cenário: O diretor pediu a lista de pacotes e seus donos na TecProExpress.

  1. Crie a query SQL que representa a Junção (⋈) entre a tabela Cliente e a tabela Pacote.

🚀 Script de Seed (Gabarito da Junção)

-- DDL de Setup Rápido (Se você não os tiver da unidade passada)
-- CREATE TABLE cliente (id INT PRIMARY KEY, nome VARCHAR(50));
-- CREATE TABLE pacote (id INT PRIMARY KEY, fk_cliente INT);

-- DML (A Consulta SQL que representa a Álgebra: Cliente ⋈ Pacote)
SELECT cliente.nome, pacote.id 
FROM cliente 
JOIN pacote ON cliente.id = pacote.fk_cliente;

🔍 Detalhamento da Consulta:

  • O comando JOIN ... ON é a tradução exata do símbolo (Junção Natural). Ele varre as duas tabelas e só retorna os dados que estão perfeitamente linkados.


💻 Ponte Prática: Do SQL Manual ao SQLAlchemy 2.0 ORM

Como os operadores matemáticos da Álgebra Relacional ($\sigma, \pi, \bowtie, \cup, \cap$) se traduzem na API moderna do SQLAlchemy 2.0?

🔴 1. A Abordagem Manual (Álgebra Feita com Loops Python Ineficientes)

No modelo procedural sem motor relacional, fazer uma junção ou interseção exige percorrer listas aninhadas em complexidade $O(N \times M)$:

# ❌ ABORDAGEM PROCEDURAL: Loops aninhados para simular a Junção (⋈)
clientes = [{"id": 1, "nome": "Maria"}, {"id": 2, "nome": "Carlos"}]
pacotes = [{"cod": 501, "cliente_id": 1, "item": "Notebook"}]

# Junção manual ineficiente:
resultado_join = []
for c in clientes:
    for p in pacotes:
        if c["id"] == p["cliente_id"]:
            resultado_join.append({"nome": c["nome"], "item": p["item"]})

🟢 2. A Abordagem com SQLAlchemy 2.0 (Expressões Algébricas Declarativas)

Com o SQLAlchemy 2.0, usamos os métodos que espelham exatamente os operadores algébricos:

  • $\sigma$ (Seleção / Filtro Horizontal): .where(...)
  • $\pi$ (Projeção / Filtro Vertical): select(Modelo.campo1, Modelo.campo2)
  • $\bowtie$ (Junção Relacional): select(...).join(...)
  • $\cup$ (União de Relações): union(stmt1, stmt2)
# ✅ ABORDAGEM MODERNA: Mapeamento Direto dos Operadores da Álgebra
from sqlalchemy import create_engine, String, Integer, ForeignKey, select, union
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, Session

class Base(DeclarativeBase):
    pass

class EntregadorModel(Base):
    __tablename__ = "entregadores_algebristas"
    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    nome: Mapped[str] = mapped_column(String(50))
    cidade: Mapped[str] = mapped_column(String(50))

class RotaModel(Base):
    __tablename__ = "rotas_algebristas"
    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    cidade_origem: Mapped[str] = mapped_column(String(50))

🛠️ Mini-Projeto 09 (BD): Motor de Consultas e Álgebra Relacional

Objetivo: Executar Projeção ($\pi$), Seleção ($\sigma$), Junção ($\bowtie$) e União ($\cup$) via SQLAlchemy 2.0.

📋 Pré-requisitos e Instalação

No terminal do seu ambiente virtual (PowerShell ou Bash), instale a biblioteca necessária:

pip install sqlalchemy

💻 Código Completo e Autocontido (miniprojeto_09_algebra.py)

Crie o arquivo miniprojeto_09_algebra.py e insira o código abaixo integralmente:

"""
Mini-Projeto 09: Motor de Consultas e Álgebra Relacional
Curso: GTI - Banco de Dados Relacionais e Engenharia de Software
Stack: Python 3.11+ | SQLAlchemy 2.0 | SQLite
"""
import os
from sqlalchemy import create_engine, select, String, Integer, union
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, Session

# 1. Definição Declarativa dos Schemas
class Base(DeclarativeBase):
    pass

class EntregadorModel(Base):
    __tablename__ = "entregadores_algebra"

    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    nome: Mapped[str] = mapped_column(String(50), nullable=False)
    cidade: Mapped[str] = mapped_column(String(50), nullable=False)

class RotaModel(Base):
    __tablename__ = "rotas_algebristas"

    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    cidade_origem: Mapped[str] = mapped_column(String(50), nullable=False)

# 2. Ponto de Entrada Executável
if __name__ == "__main__":
    DB_FILE = "tecpro_algebra.db"

    # Reset preventivo para garantir idempotência em testes repetidos
    if os.path.exists(DB_FILE):
        os.remove(DB_FILE)

    engine = create_engine(f"sqlite:///{DB_FILE}", echo=False)
    Base.metadata.create_all(bind=engine)

    # Povoando as tabelas para testes algébricos
    with Session(engine) as session:
        e1 = EntregadorModel(id=1, nome="Marcos Silva", cidade="São Paulo")
        e2 = EntregadorModel(id=2, nome="Beatriz Souza", cidade="Campinas")
        e3 = EntregadorModel(id=3, nome="Carlos Eduardo", cidade="Santos")
        session.merge(e1)
        session.merge(e2)
        session.merge(e3)

        r1 = RotaModel(id=101, cidade_origem="São Paulo")
        r2 = RotaModel(id=102, cidade_origem="Curitiba")
        session.merge(r1)
        session.merge(r2)
        session.commit()

    print("🧮 SIMULADOR DA ÁLGEBRA RELACIONAL (SQLALCHEMY 2.0):")
    print("=" * 65)

    with Session(engine) as session:
        # Operação 1: Projeção (π) e Seleção (σ) -> π_nome (σ_cidade='São Paulo' (Entregadores))
        stmt_proj_sel = select(EntregadorModel.nome).where(EntregadorModel.cidade == "São Paulo")
        nomes_sp = session.scalars(stmt_proj_sel).all()
        print(f"1. π e σ (Entregadores de SP): {nomes_sp}")

        # Operação 2: União (∪) de Cidades de Entregadores e Rotas
        stmt_cidades_ent = select(EntregadorModel.cidade)
        stmt_cidades_rot = select(RotaModel.cidade_origem)
        stmt_uniao = union(stmt_cidades_ent, stmt_cidades_rot)

        cidades_totais = session.scalars(stmt_uniao).all()
        print(f"2. União ∪ (Todas as Cidades no Ecossistema): {cidades_totais}")

    print("=" * 65)

🚀 Como Executar

Execute o script no terminal:

python miniprojeto_09_algebra.py

🖥️ Saída Esperada no Console

🧮 SIMULADOR DA ÁLGEBRA RELACIONAL (SQLALCHEMY 2.0):
=================================================================
1. π e σ (Entregadores de SP): ['Marcos Silva']
2. União ∪ (Todas as Cidades no Ecossistema): ['Campinas', 'Curitiba', 'Santos', 'São Paulo']
=================================================================

💡 Checkpoint de Lógica

Dica

Dica do Especialista: Por que aprender a teoria da Álgebra se você vai programar em SQL? Porque quando você escreve uma query SQL que demora 10 minutos para rodar, o SGBD fornece um "Plano de Execução" estruturado matematicamente na Álgebra. Quem entende os símbolos (σ, π, ⋈) consegue corrigir o gargalo em segundos! 🚀🛡️



🧪 Quiz de Fixação e Autoavaliação — Capítulo 09

1. Qual é a regra mandatória de mapeamento quando transformamos um relacionamento Muitos para Muitos (N:N) do MER para o Modelo Relacional?

  • A) Adicionar 50 colunas extras na tabela da esquerda.
  • B) Criar uma nova Tabela Intermediária (Tabela Associativa/Pivô) contendo as Chaves Estrangeiras apontando para as Chaves Primárias das duas tabelas originais.
  • C) Apagar uma das entidades para virar 1:N.
  • D) Substituir o banco relacional por um arquivo de texto.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: Relacionamentos N:N não podem ser gravados diretamente em tabelas relacionais clássicas; eles exigem uma tabela associativa intermediária (ex: item_pedido).


2. Na Álgebra Relacional de Codd, qual operador é responsável por filtrar as LINHAS (tuplas) que satisfazem uma condição booleana (equivalente ao WHERE do SQL)?

  • A) Projeção ($\pi$)
  • B) Seleção ($\sigma$)
  • C) Produto Cartesiano ($\times$)
  • D) Diferença ($-$)
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: O operador $\sigma_{salario > 5000}(Funcionario)$ filtra as linhas (horizontal), enquanto a Projeção $\pi_{nome, email}(Funcionario)$ seleciona as colunas (vertical).


3. O que acontece quando executamos um Produto Cartesiano ($R \times S$) entre uma tabela R com 100 linhas e uma tabela S com 50 linhas sem condição de junção?

  • A) O banco retorna 150 linhas somadas.
  • B) O banco gera 5.000 linhas combinando cada linha de R com todas as linhas de S ($100 \times 50$).
  • C) O banco emite erro de sintaxe.
  • D) O banco retorna 0 linhas.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: Produto Cartesiano combina todas as linhas de uma tabela com todas as linhas da outra. É por isso que esquecer a cláusula ON de um JOIN gera explosão de linhas no relatório!


🎯 Laboratório Prático

Coloque este conhecimento em prática agora mesmo executando o roteiro autoguiado:
👉 ATIVIDADE 03: MAPEAMENTO E ÁLGEBRA

📏 CAPÍTULO 10: NORMALIZAÇÃO DE DADOS (1FN, 2FN E 3FN)


🎯 Objetivos de Aprendizagem

Ao final deste capítulo (estimativa: 2 horas de estudo autoguiado), você será capaz de:

  • 🔹 Compreender as anomalias graves de banco de dados (Anomalia de Inserção, Atualização e Exclusão) causadas por esquemas desnormalizados.
  • 🔹 Dominar o conceito de Dependência Funcional Total e Transitiva.
  • 🔹 Aplicar o processo de Normalização passo a passo: 1ª Forma Normal (Atomicidade), 2ª Forma Normal (Dependência Total) e 3ª Forma Normal (Sem Transitividade).
  • 🔹 Refatorar bancos desorganizados para atingir o Padrão 3FN eliminando redundâncias.

A Normalização é a "vacina" contra dados ruins. É um processo passo a passo (algoritmo) que aplicamos nas nossas tabelas para eliminar redundâncias (dados repetidos à toa) e prevenir anomalias (erros que acontecem quando tentamos salvar ou apagar algo). 🛡️🧩

🏢 O Cenário Prático (Seu Desafio)

O setor comercial da TecProExpress criou uma planilha gigante para controlar Vendas. Nela, eles colocaram o Nome do Cliente, o Nome do Entregador, a Placa do Caminhão e o Valor do Frete — tudo na mesma linha! Quando o Entregador mudava de caminhão, o pessoal tinha que atualizar essa informação em 500 linhas diferentes manualmente, causando um caos no faturamento.

"Seu desafio é pegar esse 'Galinheiro de Dados' não normalizado e aplicar as três regras de ouro da Normalização para quebrá-lo em tabelas menores, perfeitamente integradas e imunes a anomalias."


🧠 Fundamentos: As Anomalias e a Dependência Funcional

Se não normalizarmos, o banco sofrerá três doenças fatais:

  1. Anomalia de Inserção: Você não consegue cadastrar um Caminhão novo no sistema porque ele ainda não fez nenhuma venda (e a tabela exige os dados da venda na mesma linha).
  2. Anomalia de Atualização: Você precisa alterar o endereço de um cliente em 500 registros antigos. Se o computador travar no registro 250, o banco ficará inconsistente.
  3. Anomalia de Exclusão: Se você deletar o registro da única venda que um cliente fez, você acidentalmente deleta o cadastro do cliente inteiro junto!

📊 O Algoritmo de Normalização

flowchart LR
    UNF["❌ Tabela Caótica<br/>(A Planilha)"] --> FN1["✅ 1FN:<br/>Atomicidade"]
    FN1 --> FN2["✅ 2FN:<br/>Sem Dependência Parcial"]
    FN2 --> FN3["✅ 3FN:<br/>Sem Dependência Transitiva"]

Funil de Normalização de Banco de Dados 1FN-3FN

🔍 Dependência Funcional: O que é isso?

Dizemos que B depende funcionalmente de A (ou A -> B) quando: se eu te der o valor de A, você consegue descobrir com certeza absoluta qual é o valor de B.

  • Exemplo: O CPF determina o Nome (Um CPF só tem um dono). Mas o Nome não determina o CPF (Existem muitos "Joões"). A Chave Primária (PK) deve sempre ser o determinador de tudo na tabela!

📖 A Jornada das Formas Normais (DDL na Prática)

Vamos resolver o problema da TecProExpress passo a passo.

1️⃣ Primeira Forma Normal (1FN)

Regra: Todos os atributos devem ser atômicos. Sem "células duplas" ou grupos repetitivos.

  • A Violação: O Excel tinha a coluna "Itens do Pedido" com "Teclado, Mouse, Monitor" dentro da mesma célula.
  • A Correção (1FN): Quebramos isso. O Pedido vira 3 linhas diferentes.

2️⃣ Segunda Forma Normal (2FN)

Regra: Estar na 1FN + Nenhuma coluna pode depender apenas de metade da Chave Primária Composta.

  • A Violação: Imagine que a Chave da nossa tabela seja (ID_Venda, ID_Produto). A coluna Nome_do_Produto só depende do ID do Produto, ela não quer nem saber qual foi a Venda! Isso é dependência parcial.
  • A Correção (2FN): Puxamos o Produto para uma tabela separada.

3️⃣ Terceira Forma Normal (3FN)

Regra: Estar na 2FN + Nenhuma coluna "não-chave" pode depender de outra coluna "não-chave" (Dependência Transitiva).

  • A Violação: Na tabela de Venda, temos o ID do Cliente, o Nome do Cliente e a Cidade do Cliente. A Cidade depende do Nome, que depende do ID. Se o Cliente não é a Chave Primária da tabela de Vendas, ele não pode morar lá com seus atributos.
  • A Correção (3FN): Puxamos o Cliente para sua própria tabela.

🛠️ Prática Obrigatória: Quebrando o Monstro (DDL -> DML)

Cenário: O resultado da 3FN. Após a sua análise, a planilha gigante virou três tabelas limpas: Cliente, Produto e a associativa Venda.

🚀 Script de Seed (Gabarito da 3FN)

-- PASSO 1: DDL (As Tabelas Bases - Fortes)
CREATE TABLE cliente_norm (
    id_cliente INT PRIMARY KEY,
    nome VARCHAR(100),
    cidade VARCHAR(50)
);

CREATE TABLE produto_norm (
    id_produto INT PRIMARY KEY,
    descricao VARCHAR(100)
);

-- PASSO 2: DDL (A Tabela Normalizada que liga tudo sem redundância)
CREATE TABLE venda_norm (
    id_venda INT PRIMARY KEY,
    id_cliente INT, -- A FK apontando para o cliente (Nada de escrever a cidade dele aqui!)
    id_produto INT,
    FOREIGN KEY (id_cliente) REFERENCES cliente_norm(id_cliente),
    FOREIGN KEY (id_produto) REFERENCES produto_norm(id_produto)
);

-- PASSO 3: DML (Carga dos Dados Sem Repetição)
INSERT INTO cliente_norm VALUES (1, 'Maria Silva', 'São Paulo');
INSERT INTO produto_norm VALUES (10, 'Teclado Gamer');
-- Agora registramos 100 vendas sem nunca digitar "São Paulo" de novo!
INSERT INTO venda_norm VALUES (1001, 1, 10);

🔍 Detalhamento do Código:

  • Note que a tabela venda_norm é extremamente leve! Ela só guarda os números dos IDs. Se a Maria mudar de 'São Paulo' para 'Rio de Janeiro', você fará o UPDATE em exatamente 1 linha na tabela cliente_norm, e todas as milhares de vendas dela já estarão magicamente atualizadas! Isso é a força da 3FN.


💻 Ponte Prática: Do SQL Manual ao SQLAlchemy 2.0 ORM

Como o SQLAlchemy 2.0 combate as Anomalias de Inserção, Atualização e Exclusão através do modelo normalizado?

🔴 1. A Abordagem Manual (Tabela Monolítica Desnormalizada)

No modelo não-normalizado, para atualizar o endereço de um cliente, você é obrigado a rodar um UPDATE massivo em centenas de linhas de vendas:

# ❌ ABORDAGEM NÃO-NORMALIZADA: Redundância massiva
cursor.execute("UPDATE vendas_monoliticas SET cidade_cliente = 'Rio de Janeiro' WHERE nome_cliente = 'Maria Silva';")
# Se o banco tiver 500.000 vendas, esse UPDATE bloqueará a tabela inteira!

🟢 2. A Abordagem com SQLAlchemy 2.0 (Modelos 3FN Desacoplados)

Com os modelos normalizados em 3FN, alteramos o registro do cliente em exatamente uma linha da tabela clientes. Todas as relações e queries refletem a mudança instantaneamente:

# ✅ ABORDAGEM MODERNA EM 3FN COM SQLALCHEMY 2.0
from sqlalchemy import create_engine, String, Integer, ForeignKey, select
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship, Session

class Base(DeclarativeBase):
    pass

class ClienteNormModel(Base):
    """3FN: Tabela isolada para a entidade Cliente."""
    __tablename__ = "clientes_norm"

    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    nome: Mapped[str] = mapped_column(String(100), nullable=False)
    cidade: Mapped[str] = mapped_column(String(50), nullable=False)

    vendas: Mapped[list["VendaNormModel"]] = relationship("VendaNormModel", back_populates="cliente")

class ProdutoNormModel(Base):
    """3FN: Tabela isolada para a entidade Produto."""
    __tablename__ = "produtos_norm"

    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    descricao: Mapped[str] = mapped_column(String(100), nullable=False)

class VendaNormModel(Base):
    """3FN: Tabela associativa contendo apenas identificadores e fatos transacionais."""
    __tablename__ = "vendas_norm"

    id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    id_cliente: Mapped[int] = mapped_column(ForeignKey("clientes_norm.id"), nullable=False)
    id_produto: Mapped[int] = mapped_column(ForeignKey("produtos_norm.id"), nullable=False)

    cliente: Mapped["ClienteNormModel"] = relationship("ClienteNormModel", back_populates="vendas")
    produto: Mapped["ProdutoNormModel"] = relationship("ProdutoNormModel")

🛠️ Mini-Projeto 10 (BD): Refatoração de Dados Não-Normalizados (1FN a 3FN)

Objetivo: Demonstrar como uma atualização pontual no cliente normalizado atualiza todas as consultas de vendas sem anomalias de alteração.

📋 Pré-requisitos e Instalação

No terminal do seu ambiente virtual (PowerShell ou Bash), instale a biblioteca necessária:

pip install sqlalchemy

💻 Código Completo e Autocontido (miniprojeto_10_normalizacao.py)

Crie o arquivo miniprojeto_10_normalizacao.py e insira o código abaixo integralmente:

"""
Mini-Projeto 10: Refatoração de Dados Não-Normalizados (1FN a 3FN)
Curso: GTI - Banco de Dados Relacionais e Engenharia de Software
Stack: Python 3.11+ | SQLAlchemy 2.0 | SQLite
"""
import os
from sqlalchemy import create_engine, String, Integer, ForeignKey, select
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship, Session

# 1. Definição Declarativa Normalizada (3FN)
class Base(DeclarativeBase):
    pass

class ClienteNormModel(Base):
    """3FN: Tabela isolada para a entidade Cliente."""
    __tablename__ = "clientes_norm"

    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    nome: Mapped[str] = mapped_column(String(100), nullable=False)
    cidade: Mapped[str] = mapped_column(String(50), nullable=False)

    vendas: Mapped[list["VendaNormModel"]] = relationship("VendaNormModel", back_populates="cliente")

class ProdutoNormModel(Base):
    """3FN: Tabela isolada para a entidade Produto."""
    __tablename__ = "produtos_norm"

    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    descricao: Mapped[str] = mapped_column(String(100), nullable=False)

class VendaNormModel(Base):
    """3FN: Tabela associativa contendo apenas identificadores e fatos transacionais."""
    __tablename__ = "vendas_norm"

    id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    id_cliente: Mapped[int] = mapped_column(ForeignKey("clientes_norm.id"), nullable=False)
    id_produto: Mapped[int] = mapped_column(ForeignKey("produtos_norm.id"), nullable=False)

    cliente: Mapped["ClienteNormModel"] = relationship("ClienteNormModel", back_populates="vendas")
    produto: Mapped["ProdutoNormModel"] = relationship("ProdutoNormModel")

# 2. Ponto de Entrada Executável
if __name__ == "__main__":
    DB_FILE = "tecpro_normalizado.db"

    # Reset preventivo para garantir idempotência em testes repetidos
    if os.path.exists(DB_FILE):
        os.remove(DB_FILE)

    engine = create_engine(f"sqlite:///{DB_FILE}", echo=False)
    Base.metadata.create_all(bind=engine)

    print("📐 NORMALIZAÇÃO DE DADOS (3FN) - TECPROEXPRESS")
    print("=" * 65)

    # 1. Povoando o banco normalizado
    with Session(engine) as session:
        cliente = ClienteNormModel(id=1, nome="Maria Silva", cidade="São Paulo")
        prod1 = ProdutoNormModel(id=10, descricao="Teclado Mecânico")
        prod2 = ProdutoNormModel(id=20, descricao="Monitor 4K")

        session.merge(cliente)
        session.merge(prod1)
        session.merge(prod2)
        session.flush()

        venda1 = VendaNormModel(id_cliente=1, id_produto=10)
        venda2 = VendaNormModel(id_cliente=1, id_produto=20)
        session.add_all([venda1, venda2])
        session.commit()
        print("✅ Cliente, Produtos e 2 Vendas gravados em 3FN!")

    # 2. Atualização Pontual (Zero Redundância):
    with Session(engine) as session:
        maria = session.get(ClienteNormModel, 1)
        if maria:
            print(f"\n🏠 Mudando cidade de Maria: {maria.cidade} -> Rio de Janeiro (Atualiza 1 único registro)")
            maria.cidade = "Rio de Janeiro"
            session.commit()

    # 3. Consulta das vendas de Maria após a atualização:
    print("\n🔍 Consultando histórico de vendas após o UPDATE pontual:")
    with Session(engine) as session:
        stmt = select(VendaNormModel).join(VendaNormModel.cliente).join(VendaNormModel.produto)
        for v in session.scalars(stmt).all():
            print(f"  📦 Venda #{v.id} | Cliente: {v.cliente.nome} ({v.cliente.cidade}) | Item: {v.produto.descricao}")
    print("=" * 65)

🚀 Como Executar

Execute o script no terminal:

python miniprojeto_10_normalizacao.py

🖥️ Saída Esperada no Console

📐 NORMALIZAÇÃO DE DADOS (3FN) - TECPROEXPRESS
=================================================================
✅ Cliente, Produtos e 2 Vendas gravados em 3FN!

🏠 Mudando cidade de Maria: São Paulo -> Rio de Janeiro (Atualiza 1 único registro)

🔍 Consultando histórico de vendas após o UPDATE pontual:
  📦 Venda #1 | Cliente: Maria Silva (Rio de Janeiro) | Item: Teclado Mecânico
  📦 Venda #2 | Cliente: Maria Silva (Rio de Janeiro) | Item: Monitor 4K
=================================================================

💡 Checkpoint de Lógica

Importante

Reflexão Profissional: Um banco de dados 100% normalizado é sempre a melhor escolha? (Resposta: Para sistemas Transacionais/Operacionais, SIM! Mas em Data Warehouses de Analytics - onde a leitura de relatórios pesados é mais importante que a escrita - às vezes os arquitetos aplicam a Desnormalização propositalmente para evitar muitos cruzamentos de JOINs e acelerar a velocidade do painel). 🧠🛡️




🧪 Quiz de Fixação e Autoavaliação — Capítulo 10

1. O que exige a 1ª Forma Normal (1FN) em uma tabela relacional?

  • A) Que todas as colunas sejam do tipo texto.
  • B) Que todos os atributos sejam atômicos (indivisíveis), sem repetição de grupos de dados ou colunas com múltiplos valores na mesma célula (ex: 'Telefone 1, Telefone 2' ou listas separadas por vírgula).
  • C) Que a tabela tenha exatamente 3 chaves estrangeiras.
  • D) Que o banco use criptografia quântica.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: A 1FN elimina arrays e campos compostos: cada célula deve guardar exatamente um único valor atômico.


2. Quando dizemos que uma tabela que já está na 1FN atingiu a 2ª Forma Normal (2FN)?

  • A) Quando todos os atributos não-chave dependem TOTALMENTE da Chave Primária por inteiro, eliminando dependências parciais em tabelas com chaves compostas.
  • B) Quando a tabela tem mais de 2 anos de uso.
  • C) Quando o banco é migrado para PostgreSQL.
  • D) Quando todos os valores nulos são apagados.
💡 Ver Resposta e Justificativa

Resposta Correta: A
Justificativa: A 2FN combate dependências parciais em tabelas com PKs compostas: nenhum atributo pode depender de apenas 'metade' da chave primária.


3. O que proíbe a 3ª Forma Normal (3FN) para eliminar redundâncias e inconsistências de dados?

  • A) Proíbe o uso de comandos DELETE.
  • B) Proíbe a existência de Dependências Transitivas (atributos não-chave que dependem de outros atributos não-chave em vez de dependerem diretamente da Chave Primária).
  • C) Proíbe o uso de índices B-Tree.
  • D) Obriga todas as tabelas a terem nomes em inglês.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: Regra da 3FN: 'Todo atributo deve depender da chave, de toda a chave e de nada além da chave'. Se cidade depende de cep, eles devem ir para uma tabela separada de Endereços.


🎯 Laboratório Prático

Coloque este conhecimento em prática agora mesmo executando o roteiro autoguiado:
👉 ATIVIDADE 04: NORMALIZAÇÃO

🏗️ CAPÍTULO 11: ECOSSISTEMA SQL E DDL


🎯 Objetivos de Aprendizagem

Ao final deste capítulo (estimativa: 2 horas de estudo autoguiado), você será capaz de:

  • 🔹 Compreender as 5 famílias de comandos SQL: DDL (Definição), DML (Manipulação), DQL (Consulta), DCL (Controle) e TCL (Transação).
  • 🔹 Dominar a criação e gestão de Schemas (Namespaces) e tabelas com CREATE TABLE, ALTER TABLE e DROP TABLE.
  • 🔹 Diferenciar a execução declarativa do SQL (otimizador de consultas) da programação procedural tradicional.
  • 🔹 Implementar inspeção e criação programática de schemas (metadados) com SQLAlchemy 2.0. (Migrações versionadas com Alembic são o foco do Capítulo 17.)

A primeira ideia que define um desenvolvedor experiente é entender que a SQL (Structured Query Language) não é apenas uma ferramenta, mas uma linguagem declarativa de escala global, capaz de gerenciar desde pequenos aplicativos até a bolsa de valores. 🛡️🧩

🏢 O Cenário Prático (Seu Desafio)

A TecProExpress decidiu criar um módulo de "Agenda de Contatos" para os fornecedores logísticos. Os analistas desenharam o MER, mas o banco de dados ainda está vazio.

"Seu desafio é abrir o terminal (ou a interface DBeaver/pgAdmin) e usar a linguagem SQL para construir as fundações do prédio: criar o espaço de trabalho (Schema) e a tabela que armazenará os fornecedores, com regras rígidas."


🧠 Fundamentos: As 5 Famílias da SQL

A SQL é dividida em 5 subconjuntos. Todo o nosso trabalho até agora na criação de tabelas pertenceu ao primeiro grupo.

SiglaSignificadoComandos PrincipaisO que faz?
DDLData Definition LanguageCREATE, ALTER, DROPMonta o "esqueleto" do banco.
DMLData Manipulation LanguageINSERT, UPDATE, DELETEManipula os "órgãos" (dados).
DQLData Query LanguageSELECTFaz as consultas e relatórios.
DCLData Control LanguageGRANT, REVOKESegurança (Dá e tira permissão).
TCLTransaction Control LanguageCOMMIT, ROLLBACKSalva ou Cancela um lote de ações.

📊 Fluxo de Processamento Declarativo

Diferente do Java ou Python (onde você diz como fazer o laço de repetição), a SQL é Declarativa. Você diz o que quer, e o SGBD se vira para otimizar.

flowchart LR
    A["📜 Declaração SQL"] --> B{"⚙️ Otimizador SGBD"}
    B -- "Calcula a Rota" --> C["🗺️ Plano de Execução"]
    C --> D["📊 Resultado Final"]

📖 Exemplo Guiado: O DDL em Ação (Criando a Base)

Sempre organize seus projetos criando um SCHEMA. Ele funciona como uma "pasta" dentro do banco de dados, evitando que as tabelas da logística se misturem com as do RH.

🛠️ Código do Exemplo

-- PASSO 1: DDL (Criação do Namespace/Schema)
CREATE SCHEMA logistica;

-- PASSO 2: DDL (Criação da Tabela com Constraints Base)
CREATE TABLE logistica.fornecedor (
    id INT PRIMARY KEY,
    nome_fantasia VARCHAR(100) NOT NULL,
    cnpj CHAR(14) UNIQUE
);

-- PASSO 3: DDL (Evoluindo a tabela - ALTER)
ALTER TABLE logistica.fornecedor ADD COLUMN email VARCHAR(100);

-- PASSO 4: DML (Carga Inicial)
INSERT INTO logistica.fornecedor (id, nome_fantasia, cnpj, email) 
VALUES (1, 'Baterias Moura', '12345678901234', 'contato@moura.com');

🔍 Detalhamento do Código:

  • CREATE SCHEMA logistica;: Cria a área de trabalho. No Postgres, isso cria uma divisão lógica no mesmo banco. (Nota: No MySQL, Schema e Database são sinônimos).
  • logistica.fornecedor: Boa prática! Sempre referenciamos a tabela pelo nome do schema seguido de um ponto.
  • ALTER TABLE ... ADD COLUMN: A vida real muda! O comando ALTER permite colocar uma nova coluna (email) sem ter que apagar (DROP) a tabela e perder os dados.

🛠️ Prática Obrigatória: Construção e Destruição

Cenário: A base de teste para a equipe de desenvolvimento.

  1. Crie um schema chamado treinamento.
  2. Crie a tabela teste_dev dentro dele.
  3. Insira 1 linha de dados.
  4. Destrua a tabela completamente usando o comando de aniquilação do DDL.

🚀 Script de Seed (Gabarito de Ciclo de Vida)

-- 1. Cria o ambiente
CREATE SCHEMA treinamento;

-- 2. Cria a estrutura (DDL)
CREATE TABLE treinamento.teste_dev (
    id INT PRIMARY KEY,
    descricao VARCHAR(50)
);

-- 3. Insere dados (DML)
INSERT INTO treinamento.teste_dev VALUES (1, 'Cobaia 01');

-- 4. Aniquila a estrutura (DDL Destrutivo)
-- CUIDADO! O DROP apaga a tabela e todos os dados dentro dela sem aviso!
DROP TABLE treinamento.teste_dev;


💻 Ponte Prática: Do SQL Manual ao SQLAlchemy 2.0 ORM

Como o ecossistema Python gerencia DDL (Criação, Alteração e Deleção de Estruturas) de forma segura e programática?

🔴 1. A Abordagem Manual (Scripts DDL Manuais e Risco de Erro)

No modelo manual, comandos CREATE TABLE e ALTER TABLE são rodados no terminal sem testes. Um erro de sintaxe pode quebrar o deploy em produção:

# ❌ ABORDAGEM COM SQL MANUAL: DDL vulnerável e sem controle de versão
cursor.execute("CREATE TABLE IF NOT EXISTS fornecedores (id INT, nome VARCHAR(100));")
# E se precisarmos adicionar uma nova coluna 'email' em produção sem perder os dados?
cursor.execute("ALTER TABLE fornecedores ADD COLUMN email VARCHAR(100);")

🟢 2. A Abordagem com SQLAlchemy 2.0 (Metadados e Schemas Declarativos)

Com o SQLAlchemy 2.0, os metadados das tabelas são objetos Python inspecionáveis. A criação e evolução de schemas é controlada por ferramentas de migração (como o Alembic):

# ✅ ABORDAGEM MODERNA: Gerenciador de Ciclo de Vida DDL com Metadados
from sqlalchemy import create_engine, String, Integer, inspect, select
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, Session

class Base(DeclarativeBase):
    pass

class FornecedorLogisticaModel(Base):
    """Representa a tabela do Schema de Logística."""
    __tablename__ = "fornecedores_logistica"

    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    nome_fantasia: Mapped[str] = mapped_column(String(100), nullable=False)
    cnpj: Mapped[str] = mapped_column(String(14), unique=True, nullable=False)
    email: Mapped[str] = mapped_column(String(100), nullable=True)

    def __repr__(self) -> str:
        return f"FornecedorLogistica(id={self.id}, nome='{self.nome_fantasia}', cnpj='{self.cnpj}')"

🛠️ Mini-Projeto 11 (BD): Gerenciador de Ciclo de Vida de Schemas DDL

Objetivo: Construir um script que inspeciona o catálogo de metadados, cria tabelas programaticamente e valida as colunas existentes no banco.

📋 Pré-requisitos e Instalação

No terminal do seu ambiente virtual (PowerShell ou Bash), instale a biblioteca necessária:

pip install sqlalchemy

💻 Código Completo e Autocontido (miniprojeto_11_ddl.py)

Crie o arquivo miniprojeto_11_ddl.py e insira o código abaixo integralmente:

"""
Mini-Projeto 11: Gerenciador de Ciclo de Vida de Schemas DDL
Curso: GTI - Banco de Dados Relacionais e Engenharia de Software
Stack: Python 3.11+ | SQLAlchemy 2.0 | SQLite
"""
import os
from sqlalchemy import create_engine, String, Integer, inspect
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, Session

# 1. Definição Declarativa do Schema DDL
class Base(DeclarativeBase):
    pass

class FornecedorLogisticaModel(Base):
    """Representa a tabela do Schema de Logística."""
    __tablename__ = "fornecedores_logistica"

    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    nome_fantasia: Mapped[str] = mapped_column(String(100), nullable=False)
    cnpj: Mapped[str] = mapped_column(String(14), unique=True, nullable=False)
    email: Mapped[str] = mapped_column(String(100), nullable=True)

    def __repr__(self) -> str:
        return f"FornecedorLogistica(id={self.id}, nome='{self.nome_fantasia}', cnpj='{self.cnpj}')"

# 2. Ponto de Entrada Executável
if __name__ == "__main__":
    DB_FILE = "tecpro_ddl_demo.db"

    # Reset preventivo para garantir idempotência em testes repetidos
    if os.path.exists(DB_FILE):
        os.remove(DB_FILE)

    engine = create_engine(f"sqlite:///{DB_FILE}", echo=False)

    print("🏗️ CICLO DE VIDA DDL E METADADOS - TECPROEXPRESS")
    print("=" * 65)

    # 1. DDL: Criação das Tabelas
    print("1. [DDL] Executando CREATE TABLE via Metadados...")
    Base.metadata.create_all(bind=engine)
    print("   ✅ Tabela 'fornecedores_logistica' criada com sucesso!")

    # 2. Inspecionando o Catálogo do Banco de Dados
    inspector = inspect(engine)
    colunas = inspector.get_columns("fornecedores_logistica")
    print("\n2. [METADADOS] Inspecionando colunas criadas no banco:")
    for col in colunas:
        print(f"   • Coluna: {col['name']} | Tipo: {col['type']} | Aceita Null: {col['nullable']}")

    # 3. DML: Inserindo e consultando dados
    with Session(engine) as session:
        f = FornecedorLogisticaModel(id=1, nome_fantasia="Baterias Moura S.A.", cnpj="12345678000199", email="contato@moura.com")
        session.merge(f)
        session.commit()
        print(f"\n3. [DML] Registro persistido: {f}")

    print("=" * 65)

🚀 Como Executar

Execute o script no terminal:

python miniprojeto_11_ddl.py

🖥️ Saída Esperada no Console

🏗️ CICLO DE VIDA DDL E METADADOS - TECPROEXPRESS
=================================================================
1. [DDL] Executando CREATE TABLE via Metadados...
   ✅ Tabela 'fornecedores_logistica' criada com sucesso!

2. [METADADOS] Inspecionando colunas criadas no banco:
   • Coluna: id | Tipo: INTEGER | Aceita Null: False
   • Coluna: nome_fantasia | Tipo: VARCHAR(100) | Aceita Null: False
   • Coluna: cnpj | Tipo: VARCHAR(14) | Aceita Null: False
   • Coluna: email | Tipo: VARCHAR(100) | Aceita Null: True

3. [DML] Registro persistido: FornecedorLogistica(id=1, nome='Baterias Moura S.A.', cnpj='12345678000199')
=================================================================

💡 Checkpoint de Lógica

Aviso

Dica do Arquiteto: Qual a diferença entre DELETE (DML) e DROP (DDL)? O DELETE FROM tabela apaga apenas as linhas (os dados), mas o esqueleto da tabela continua existindo para receber novos registros. O DROP TABLE é uma bomba atômica: apaga a estrutura, as colunas, as regras e, por consequência, todos os dados de uma vez. Use com extrema cautela! 🚀🛡️



🧪 Quiz de Fixação e Autoavaliação — Capítulo 11

1. Qual das alternativas lista corretamente a família SQL correspondente ao comando CREATE TABLE?

  • A) DML (Data Manipulation Language)
  • B) DDL (Data Definition Language)
  • C) DCL (Data Control Language)
  • D) TCL (Transaction Control Language)
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: DDL cuida do 'esqueleto' estrutural do banco: CREATE, ALTER, DROP e TRUNCATE.


2. Qual a diferença vital entre os comandos DELETE FROM tabela; (DML) e DROP TABLE tabela; (DDL)?

  • A) DELETE apaga as linhas de dados preservando a estrutura da tabela para novos registros; DROP destrói a tabela inteira, suas colunas, regras e metadados do disco.
  • B) DELETE apaga a tabela inteira e DROP apaga apenas uma linha.
  • C) São exatamente o mesmo comando com nomes diferentes.
  • D) DROP só funciona se o computador estiver conectado à internet.
💡 Ver Resposta e Justificativa

Resposta Correta: A
Justificativa: DELETE é DML (manipula dados). DROP é DDL (destrói o objeto no catálogo).


3. No PostgreSQL, qual é a vantagem de criar múltiplos SCHEMAS (ex: logistica.pedidos, rh.funcionarios) dentro do mesmo banco de dados?

  • A) Acelerar o processador da máquina.
  • B) Criar divisões lógicas (namespaces) para organizar tabelas por módulos corporativos, evitando conflitos de nomes e permitindo permissões de acesso granulares por setor.
  • C) Economizar espaço na memória RAM.
  • D) Permitir o uso de senhas curtas.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: Schemas funcionam como 'pastas' dentro do banco de dados, mantendo módulos corporativos organizados e com permissões de acesso isoladas.


🎯 Laboratório Prático

Coloque este conhecimento em prática agora mesmo executando o roteiro autoguiado:
👉 ATIVIDADE 05: SQL DDL (ESTRUTURA)

🛡️ CAPÍTULO 12: RESTRIÇÕES DE INTEGRIDADE (CONSTRAINTS)


🎯 Objetivos de Aprendizagem

Ao final deste capítulo (estimativa: 2 horas de estudo autoguiado), você será capaz de:

  • 🔹 Dominar as 6 restrições fundamentais de integridade declarativa: PRIMARY KEY, FOREIGN KEY, NOT NULL, UNIQUE, CHECK e DEFAULT.
  • 🔹 Implementar regras de negócio complexas no nível do banco de dados utilizando restrições CHECK (preco > 0 AND status IN (...)).
  • 🔹 Prevenir corrupção de dados e injeção de dados inválidos antes que eles alcancem o armazenamento em disco.
  • 🔹 Configurar constraints no SQLAlchemy 2.0 e tratar exceções IntegrityError no backend Python.

As restrições (Constraints) são as muralhas do seu banco de dados. Elas garantem que os dados inseridos no banco sigam as regras de negócio rigorosas e mantenham a consistência da informação, impedindo que a aplicação salve um preço negativo ou um cliente sem CPF. 🛡️🧩

🏢 O Cenário Prático (Seu Desafio)

A equipe de auditoria da TecProExpress encontrou anomalias gravíssimas na base de testes: existiam produtos cadastrados com peso negativo, motoristas cadastrados com a mesma placa de CNH e registros vazios.

"Seu desafio é reescrever a DDL do sistema de transportes adicionando Constraints (Restrições) diretas no motor SQL. Se um programador tentar inserir um pacote com peso de -5 kg, o SGBD deve cuspir um erro de volta na cara da aplicação antes mesmo do dado chegar no disco!"


🧠 Fundamentos: O Arsenal de Defesa (Constraints)

Você já usou a restrição máxima (PRIMARY KEY), mas o DDL nos fornece ferramentas mais granulares:

ConstraintFunção de DefesaExemplo Prático
NOT NULLO campo é obrigatório.Nenhum usuário sem Nome.
DEFAULTValor automático se ninguém enviar nada.Cliente novo entra com status Ativo por padrão.
UNIQUEImpede valor repetido (mas aceita NULL).O E-mail da conta de acesso.
CHECKValidações matemáticas e lógicas.O frete deve ser sempre >= 0.
FOREIGN KEYGarante integridade referencial.Pacote só existe se o cliente existir.

🛡️ Pipeline de Validação de Dados na Inserção (SGBD)

flowchart LR
    INSERT["📥 INSERT / UPDATE<br>(Novo Registro)"] --> C1{"1. NOT NULL / DEFAULT<br>Campos presentes?"}
    C1 -- Não --> E1["❌ ERRO: Null value in column"]
    C1 -- Sim --> C2{"2. CHECK<br>Regras lógicas válidas?"}
    C2 -- Não --> E2["❌ ERRO: Check constraint failed"]
    C2 -- Sim --> C3{"3. UNIQUE / PK<br>Chave duplicada?"}
    C3 -- Sim --> E3["❌ ERRO: Duplicate key value"]
    C3 -- Não --> C4{"4. FOREIGN KEY<br>Pai existe?"}
    C4 -- Não --> E4["❌ ERRO: Foreign key violation"]
    C4 -- Sim --> SUCESSO["✅ PERSISTÊNCIA OK<br>Gravado no Disco (WAL)"]

    style INSERT fill:#e3f2fd,stroke:#1976d2
    style SUCESSO fill:#e8f5e9,stroke:#2e7d32
    style E1 fill:#ffebee,stroke:#c62828
    style E2 fill:#ffebee,stroke:#c62828
    style E3 fill:#ffebee,stroke:#c62828
    style E4 fill:#ffebee,stroke:#c62828

📖 Exemplo Guiado: O DDL Blindado (Regras de Negócio)

Vamos criar a tabela de "Frete" da TecProExpress com uma armadura pesada de validações.

🛠️ Código do Exemplo

-- PASSO 1: DDL (Criando a Tabela Blindada)
CREATE TABLE frete_blindado (
    id INT PRIMARY KEY,
    codigo_rastreio CHAR(10) UNIQUE NOT NULL, -- Obrigatório e Exclusivo
    peso_kg DECIMAL(5,2) CHECK (peso_kg > 0), -- A regra de ouro da auditoria
    data_registro DATE DEFAULT CURRENT_DATE,  -- Se omitido, pega o dia de hoje
    status VARCHAR(20) DEFAULT 'PENDENTE'
);

-- PASSO 2: DML (Carga de Sucesso)
-- Omitimos a data e o status para ver o DEFAULT agir!
INSERT INTO frete_blindado (id, codigo_rastreio, peso_kg) 
VALUES (1, 'AB12345678', 15.50);

-- PASSO 3: DML (A Carga que VAI FALHAR - Teste o CHECK)
-- Descomente e rode. O banco VAI recusar a inserção por causa do peso negativo.
-- INSERT INTO frete_blindado (id, codigo_rastreio, peso_kg) VALUES (2, 'XY99999999', -2.00);

🔍 Detalhamento do Código:

  • DEFAULT CURRENT_DATE: Uma função nativa. Se a aplicação de frontend esquecer de mandar a data, o próprio banco preenche.
  • CHECK (peso_kg > 0): A parede intransponível. A aplicação Node.js ou Python vai receber um erro "Constraint Violation" caso tente registrar peso negativo. O banco de dados nunca confia no Frontend!

🔗 Ações Referenciais em Chaves Estrangeiras (FK)

A Chave Estrangeira não serve apenas para "vincular". Ela define o que acontece quando alguém apaga o dado PAI.

  • RESTRICT (Padrão): O SGBD proíbe a deleção do Cliente se ele tiver Pacotes no sistema.
  • CASCADE: Se você deletar o Cliente, o SGBD deleta todos os Pacotes dele junto. (Use com extrema sabedoria).
  • SET NULL: Deleta o Cliente e deixa os Pacotes órfãos (com ID NULL). Útil se você não quer apagar o histórico de transporte, mas o cliente cancelou a conta.

📊 Fluxo da Chave Estrangeira

flowchart TD
    DEL["❌ Ação: DELETE FROM Cliente WHERE id=10"] --> SGBD{"⚙️ Motor de FK"}
    SGBD -- "RESTRICT" --> B1["⛔ ERRO: Ação Bloqueada"]
    SGBD -- "CASCADE" --> B2["🔥 Deleção em Massa (Cliente e Pacotes)"]
    SGBD -- "SET NULL" --> B3["🧹 Apaga o Cliente. Atualiza Pacotes para NULL"]

🛠️ Prática Obrigatória: Nomeando as Muralhas

Cenário: Facilitando o debug do desenvolvedor. Se você não der um nome para o seu CHECK, o banco cria um nome feio (ex: SYS_C0015). Veja como nomear sua restrição para o log de erro ficar legível.

  1. Crie a tabela de funcionario com um CHECK de salário nomeado como chk_salario_minimo.

🚀 Script de Seed (Gabarito de Nomenclatura)

-- DDL
CREATE TABLE funcionario (
    id INT PRIMARY KEY,
    nome VARCHAR(100),
    salario DECIMAL(10,2),
    
    -- O 'CONSTRAINT' antes da regra permite batizá-la!
    CONSTRAINT chk_salario_minimo CHECK (salario >= 1412.00)
);

-- DML (Essa linha falha e o erro vai explicitar o nome 'chk_salario_minimo')
-- INSERT INTO funcionario VALUES (1, 'João', 1000.00);


💻 Ponte Prática: Do SQL Manual ao SQLAlchemy 2.0 ORM

Como o SQLAlchemy 2.0 materializa as Constraints (Muralhas de Integridade) em código Python corporativo?

🔴 1. A Abordagem Manual (Erros de Validação Silenciosos)

Sem restrições no banco, erros do frontend (como um peso negativo digitado no formulário) são gravados diretamente no disco:

# ❌ ABORDAGEM SEM CONSTRAINTS: Dados corrompidos entram no banco sem barreira
cursor.execute("INSERT INTO fretes (codigo, peso) VALUES ('AB123', -50.0);") # ⚠️ Aceito silenciosamente!

🟢 2. A Abordagem com SQLAlchemy 2.0 (Defense in Depth com Constraints Nomeadas)

Com o SQLAlchemy 2.0, definimos CheckConstraint, UniqueConstraint e valores default com nomes explícitos para auditoria:

# ✅ ABORDAGEM MODERNA COM SQLALCHEMY 2.0: Restrições de Integridade Nomeadas
from datetime import date
from sqlalchemy import create_engine, String, Float, Integer, Date, CheckConstraint, select, func
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, Session
from sqlalchemy.exc import IntegrityError

class Base(DeclarativeBase):
    pass

class FreteBlindadoModel(Base):
    """Tabela de fretes com restrições rigorosas de domínio."""
    __tablename__ = "fretes_blindados"
    __table_args__ = (
        CheckConstraint("peso_kg > 0.0", name="chk_peso_estritamente_positivo"),
    )

    id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    codigo_rastreio: Mapped[str] = mapped_column(String(10), unique=True, nullable=False)
    peso_kg: Mapped[float] = mapped_column(Float, nullable=False)
    data_registro: Mapped[date] = mapped_column(Date, server_default=func.current_date())
    status: Mapped[str] = mapped_column(String(20), default="PENDENTE")

    def __repr__(self) -> str:
        return f"Frete(cod='{self.codigo_rastreio}', peso={self.peso_kg}kg, status='{self.status}')"

🛠️ Mini-Projeto 12 (BD): Repositório Auto-Defensivo e Constraints

Objetivo: Implementar um repositório auto-defensivo que captura violações de constraints (IntegrityError) e impede corrupção de dados.

📋 Pré-requisitos e Instalação

No terminal do seu ambiente virtual (PowerShell ou Bash), instale a biblioteca necessária:

pip install sqlalchemy

💻 Código Completo e Autocontido (miniprojeto_12_constraints.py)

Crie o arquivo miniprojeto_12_constraints.py e insira o código abaixo integralmente:

"""
Mini-Projeto 12: Repositório Auto-Defensivo e Constraints
Curso: GTI - Banco de Dados Relacionais e Engenharia de Software
Stack: Python 3.11+ | SQLAlchemy 2.0 | SQLite
"""
import os
from datetime import date
from sqlalchemy import create_engine, String, Float, Integer, Date, CheckConstraint, select, func
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, Session
from sqlalchemy.exc import IntegrityError

# 1. Definição Declarativa com Defesas em Profundidade (Constraints)
class Base(DeclarativeBase):
    pass

class FreteBlindadoModel(Base):
    """Tabela de fretes com restrições rigorosas de domínio."""
    __tablename__ = "fretes_blindados"
    __table_args__ = (
        CheckConstraint("peso_kg > 0.0", name="chk_peso_estritamente_positivo"),
    )

    id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    codigo_rastreio: Mapped[str] = mapped_column(String(10), unique=True, nullable=False)
    peso_kg: Mapped[float] = mapped_column(Float, nullable=False)
    data_registro: Mapped[date] = mapped_column(Date, server_default=func.current_date())
    status: Mapped[str] = mapped_column(String(20), default="PENDENTE")

    def __repr__(self) -> str:
        return f"Frete(cod='{self.codigo_rastreio}', peso={self.peso_kg}kg, status='{self.status}')"

# 2. Ponto de Entrada Executável
if __name__ == "__main__":
    DB_FILE = "tecpro_constraints.db"

    # Reset preventivo: indispensável para garantir idempotência frente ao UNIQUE de codigo_rastreio
    if os.path.exists(DB_FILE):
        os.remove(DB_FILE)

    engine = create_engine(f"sqlite:///{DB_FILE}", echo=False)
    Base.metadata.create_all(bind=engine)

    print("🔒 REPOSITÓRIO AUTO-DEFENSIVO COM CONSTRAINTS - TECPROEXPRESS")
    print("=" * 65)

    # 1. Inserção com Sucesso (Regra Válida)
    with Session(engine) as session:
        f1 = FreteBlindadoModel(codigo_rastreio="BR99887766", peso_kg=12.4)
        session.add(f1)
        session.commit()
        print(f"✅ Inserção Válida: {f1}")

    # 2. Teste da Muralha 1: Tentativa de Inserir Peso Negativo (CHECK Falha)
    with Session(engine) as session:
        try:
            print("\n⚠️ Tentando inserir frete com peso negativo (-10.0kg)...")
            f_invalido = FreteBlindadoModel(codigo_rastreio="BR00000001", peso_kg=-10.0)
            session.add(f_invalido)
            session.commit()
        except IntegrityError:
            session.rollback()
            print("🛡️ SGBD Bloqueou com Sucesso: Violação de 'chk_peso_estritamente_positivo'!")

    # 3. Teste da Muralha 2: Tentativa de Inserir Código Duplicado (UNIQUE Falha)
    with Session(engine) as session:
        try:
            print("\n⚠️ Tentando duplicar o código de rastreio 'BR99887766'...")
            f_duplicado = FreteBlindadoModel(codigo_rastreio="BR99887766", peso_kg=5.0)
            session.add(f_duplicado)
            session.commit()
        except IntegrityError:
            session.rollback()
            print("🛡️ SGBD Bloqueou com Sucesso: Violação de Chave Única (UNIQUE Constraint)!")

    print("=" * 65)

🚀 Como Executar

Execute o script no terminal:

python miniprojeto_12_constraints.py

🖥️ Saída Esperada no Console

🔒 REPOSITÓRIO AUTO-DEFENSIVO COM CONSTRAINTS - TECPROEXPRESS
=================================================================
✅ Inserção Válida: Frete(cod='BR99887766', peso=12.4kg, status='PENDENTE')

⚠️ Tentando inserir frete com peso negativo (-10.0kg)...
🛡️ SGBD Bloqueou com Sucesso: Violação de 'chk_peso_estritamente_positivo'!

⚠️ Tentando duplicar o código de rastreio 'BR99887766'...
🛡️ SGBD Bloqueou com Sucesso: Violação de Chave Única (UNIQUE Constraint)!
=================================================================

💡 Checkpoint de Lógica

Dica

Dica do Especialista: Uma arquitetura de dados sênior deve sempre possuir uma "Defesa em Profundidade" (Defense in Depth). Não é porque a linguagem de programação no frontend tem um "IF" bloqueando pesos negativos que o banco de dados deve aceitar qualquer coisa. A Constraint é o goleiro do seu time: se a defesa do código falhar, o goleiro do banco espalma a bola! 🚀🛡️



🧪 Quiz de Fixação e Autoavaliação — Capítulo 12

1. Qual restrição de integridade (Constraint) deve ser usada para garantir que a coluna preco_unitario nunca receba valores negativos ou zero no banco de dados?

  • A) NOT NULL
  • B) UNIQUE
  • C) CHECK (preco_unitario > 0)
  • D) DEFAULT '0'
💡 Ver Resposta e Justificativa

Resposta Correta: C
Justificativa: A constraint CHECK avalia uma expressão lógica booleana a cada INSERT/UPDATE, bloqueando a transação se o resultado for falso.


2. Qual a diferença entre uma coluna definida como PRIMARY KEY e uma coluna definida como UNIQUE?

  • A) A PRIMARY KEY é única e NUNCA aceita valores NULL; a coluna UNIQUE garante valores não-repetidos, mas pode aceitar valores NULL (conforme o SGBD).
  • B) UNIQUE só aceita números e PRIMARY KEY só aceita letras.
  • C) PRIMARY KEY pode ter valores repetidos.
  • D) UNIQUE apaga os dados antigos automaticamente.
💡 Ver Resposta e Justificativa

Resposta Correta: A
Justificativa: Uma tabela só pode ter uma única Chave Primária (sempre NOT NULL). Mas pode ter múltiplas restrições UNIQUE (ex: CPF único, Email único, Matrícula única).


3. O que acontece quando o backend tenta inserir uma linha que viola uma constraint de integridade no banco de dados?

  • A) O banco desliga o servidor.
  • B) O SGBD aborta a transação imediatamente e retorna um erro de violação de integridade (gerando um IntegrityError no SQLAlchemy), impedindo a gravação do dado corrompido.
  • C) O banco aceita o dado e avisa no dia seguinte.
  • D) O dado é salvo em formato de texto.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: Constraints são a última e mais forte linha de defesa da arquitetura: se a validação da aplicação falhar, o SGBD rejeita o dado inválido no ato.


🎯 Laboratório Prático

Coloque este conhecimento em prática agora mesmo executando o roteiro autoguiado:
👉 ATIVIDADE 05: SQL DDL (ESTRUTURA)

📝 CAPÍTULO 13: DML — INSERT, UPDATE, DELETE E RETURNING


🎯 Objetivos de Aprendizagem

Ao final deste capítulo (estimativa: 2 horas de estudo autoguiado), você será capaz de:

  • 🔹 Dominar os comandos de manipulação de dados: INSERT INTO, UPDATE ... SET e DELETE FROM com filtros rigorosos.
  • 🔹 Compreender o perigo fatal de rodar UPDATE ou DELETE sem cláusula WHERE e como usar transações seguras de proteção.
  • 🔹 Utilizar recursos modernos de SQL: INSERT INTO ... SELECT, cláusula RETURNING e operações de UPSERT (ON CONFLICT DO UPDATE).
  • 🔹 Implementar operações DML em lote (Bulk Operations) de alta performance no SQLAlchemy 2.0.

Se o DDL constrói a "casa", o DML (Data Manipulation Language) são os móveis e as pessoas dentro dela. O DML é a linguagem do dia a dia do desenvolvedor; é com ela que sua aplicação web cadastra usuários, altera senhas e exclui postagens. 🛡️🧩

🏢 O Cenário Prático (Seu Desafio)

Na TecProExpress, um desenvolvedor júnior precisava dar um aumento de 10% no salário do "João". Ele rodou o comando e, quando os holerites saíram, todos os 5.000 funcionários da empresa haviam recebido 10% de aumento. O prejuízo foi milionário.

"Seu desafio é treinar os estagiários para nunca mais cometerem esse erro fatal. Você deve demonstrar o uso seguro da DML, explicando como a cláusula WHERE é o único escudo entre uma atualização pontual e a demissão sumária por erro em produção."


🧠 Fundamentos: Os Três Pilares da Manipulação

1. Inserindo (INSERT INTO)

O comando INSERT adiciona uma ou mais linhas (tuplas) à tabela.

-- DDL Rápido para contexto
-- CREATE TABLE cliente (id INT PRIMARY KEY, nome VARCHAR(50), saldo DECIMAL(10,2));

-- Inserção Explicita (A mais segura, você diz quais colunas)
INSERT INTO cliente (id, nome, saldo) VALUES (1, 'Maria', 500.00);

-- Inserção Implícita (Se você souber a ordem exata das colunas)
INSERT INTO cliente VALUES (2, 'José', 1000.00);

-- Múltipla (Várias linhas num único comando - Alta Performance)
INSERT INTO cliente (id, nome, saldo) VALUES 
(3, 'Ana', 0), 
(4, 'Carlos', 200);

2. Atualizando (UPDATE)

Altera dados que já existem. O maior perigo do SQL mora aqui.

-- O Jeito Certo (Uso obrigatório do WHERE)
UPDATE cliente 
SET saldo = 800.00 
WHERE id = 1; -- Apenas a Maria recebe o novo saldo

-- O Erro do Estagiário (NUNCA FAÇA ISSO)
-- UPDATE cliente SET saldo = 800.00;
-- Efeito: Maria, José, Ana e Carlos teriam seu saldo substituído para 800.

3. Excluindo (DELETE FROM)

Remove linhas inteiras da tabela.

-- Exclusão Segura
DELETE FROM cliente WHERE id = 4; -- O Carlos é removido do sistema

-- O Desastre (Evacuação Total)
-- DELETE FROM cliente; 
-- Apaga absolutamente TODAS as linhas da tabela. Apenas a estrutura (DDL) sobrevive.


📊 Fluxo de Segurança Transacional em Operações DML

Para evitar acidentes com atualizações ou exclusões acidentais em produção, adota-se o fluxo de transação com confirmação explícita:

flowchart TD
    A["Início: BEGIN TRANSACTION"] --> B["Executar DML com Filtro: UPDATE / DELETE WHERE id = :id"]
    B --> C{"Verificação: Apenas 1 linha afetada?"}
    C -- "Sim (Sucesso)" --> D["COMMIT (Grava permanentemente)"]
    C -- "Não (Muitas linhas ou Erro)" --> E["ROLLBACK (Desfaz tudo sem danos)"]
    style A fill:#e3f2fd,stroke:#1e88e5
    style B fill:#fff8e1,stroke:#fbc02d
    style D fill:#e8f5e9,stroke:#43a047
    style E fill:#fdf2f2,stroke:#c0392b

📖 Exemplo Guiado: O Teste de Sobrevivência (DDL -> DML)

Sempre teste atualizações complexas antes de rodá-las em produção.

🛠️ Código do Exemplo (Com Segurança)

-- PASSO 1: DDL (Criando a Tabela)
CREATE TABLE frota_tecpro (
    id_veiculo INT PRIMARY KEY,
    placa VARCHAR(7),
    status VARCHAR(20)
);

-- PASSO 2: DML (Carga Inicial)
INSERT INTO frota_tecpro VALUES (10, 'AAA1234', 'ATIVO');
INSERT INTO frota_tecpro VALUES (20, 'BBB9999', 'ATIVO');

-- PASSO 3: O Teste de Segurança (Usando transação)
START TRANSACTION;
  UPDATE frota_tecpro SET status = 'MANUTENCAO' WHERE id_veiculo = 10;
COMMIT;

🔍 Detalhamento do Código:

  • Envelopar um UPDATE ou DELETE dentro de um START TRANSACTION é uma prática Sênior. Se você esquecer o WHERE e estragar a tabela, basta rodar ROLLBACK; em vez de COMMIT; para desfazer o erro antes que ele seja salvo no disco!

🛠️ Prática Obrigatória: A Manutenção da Frota

Cenário: A TecProExpress vendeu o veículo 20 e comprou um novo veículo 30.

  1. Insira o novo veículo (ID 30, Placa CCC5555, Status ATIVO).
  2. Atualize o status do veículo 10 para 'VIAGEM'.
  3. Exclua o veículo 20 do sistema.

🚀 Script de Seed (Gabarito DML)

-- 1. Insert
INSERT INTO frota_tecpro (id_veiculo, placa, status) 
VALUES (30, 'CCC5555', 'ATIVO');

-- 2. Update seguro (Sempre use a Chave Primária no WHERE!)
UPDATE frota_tecpro 
SET status = 'VIAGEM' 
WHERE id_veiculo = 10;

-- 3. Delete seguro
DELETE FROM frota_tecpro 
WHERE id_veiculo = 20;


💻 Ponte Prática: Do SQL Manual ao SQLAlchemy 2.0 ORM

Como o SQLAlchemy 2.0 transforma operações de DML (INSERT, UPDATE e DELETE) em um ciclo seguro de manipulação de objetos?

🔴 1. A Abordagem Manual (O Perigo de UPDATE e DELETE sem WHERE)

No SQL manual concatenado, esquecer um filtro WHERE causa desastres irreversíveis em produção:

# ❌ ABORDAGEM COM SQL MANUAL: Um descuido no WHERE apaga a frota inteira
placa_alvo = None
if placa_alvo:
    cursor.execute(f"DELETE FROM frota WHERE placa = '{placa_alvo}';")
else:
    # Se a variável estiver vazia, o comando executará: DELETE FROM frota; ⚠️
    pass

🟢 2. A Abordagem com SQLAlchemy 2.0 (Padrão Repositório Orientado a Objetos)

Com o SQLAlchemy 2.0, manipulamos as instâncias em memória. O ORM rastreia as alterações (Unit of Work) e emite os comandos INSERT, UPDATE e DELETE usando sempre a Chave Primária no WHERE:

# ✅ ABORDAGEM MODERNA: Padrão Repositório CRUD Seguro
from typing import Any
from sqlalchemy import create_engine, String, Integer, select
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, Session

class Base(DeclarativeBase):
    pass

class FrotaTecProModel(Base):
    __tablename__ = "frota_dml_segura"

    id_veiculo: Mapped[int] = mapped_column(Integer, primary_key=True)
    placa: Mapped[str] = mapped_column(String(7), unique=True, nullable=False)
    status: Mapped[str] = mapped_column(String(20), default="ATIVO")

    def __repr__(self) -> str:
        return f"Veiculo(id={self.id_veiculo}, placa='{self.placa}', status='{self.status}')"

🛠️ Mini-Projeto 13 (BD): Repositório CRUD Seguro de Frota

Objetivo: Construir uma classe de serviço com operações seguras de inserção, consulta, atualização e deleção protegidas por PK.

📋 Pré-requisitos e Instalação

No terminal do seu ambiente virtual (PowerShell ou Bash), instale a biblioteca necessária:

pip install sqlalchemy

💻 Código Completo e Autocontido (miniprojeto_13_crud.py)

Crie o arquivo miniprojeto_13_crud.py e insira o código abaixo integralmente:

"""
Mini-Projeto 13: Repositório CRUD Seguro de Frota
Curso: GTI - Banco de Dados Relacionais e Engenharia de Software
Stack: Python 3.11+ | SQLAlchemy 2.0 | SQLite
"""
import os
from sqlalchemy import create_engine, String, Integer, select
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, Session

# 1. Definição Declarativa do Schema
class Base(DeclarativeBase):
    pass

class FrotaTecProModel(Base):
    __tablename__ = "frota_dml_segura"

    id_veiculo: Mapped[int] = mapped_column(Integer, primary_key=True)
    placa: Mapped[str] = mapped_column(String(7), unique=True, nullable=False)
    status: Mapped[str] = mapped_column(String(20), default="ATIVO")

    def __repr__(self) -> str:
        return f"Veiculo(id={self.id_veiculo}, placa='{self.placa}', status='{self.status}')"

# 2. Camada de Repositório Orientada a Objetos
class FrotaRepository:
    def __init__(self, engine) -> None:
        self.engine = engine

    def criar_veiculo(self, id_veiculo: int, placa: str, status: str = "ATIVO") -> FrotaTecProModel:
        with Session(self.engine) as session:
            v = FrotaTecProModel(id_veiculo=id_veiculo, placa=placa, status=status)
            session.merge(v)
            session.commit()
            return v

    def atualizar_status(self, id_veiculo: int, novo_status: str) -> bool:
        with Session(self.engine) as session:
            veiculo = session.get(FrotaTecProModel, id_veiculo)
            if veiculo:
                # O SQLAlchemy emite UPDATE ... WHERE id_veiculo = :id automaticamente
                veiculo.status = novo_status
                session.commit()
                return True
            return False

    def remover_veiculo(self, id_veiculo: int) -> bool:
        with Session(self.engine) as session:
            veiculo = session.get(FrotaTecProModel, id_veiculo)
            if veiculo:
                # O SQLAlchemy emite DELETE ... WHERE id_veiculo = :id automaticamente
                session.delete(veiculo)
                session.commit()
                return True
            return False

    def listar_todos(self) -> list[FrotaTecProModel]:
        with Session(self.engine) as session:
            return list(session.scalars(select(FrotaTecProModel)).all())

# 3. Ponto de Entrada Executável
if __name__ == "__main__":
    DB_FILE = "tecpro_crud.db"

    # Reset preventivo para garantir idempotência em testes repetidos
    if os.path.exists(DB_FILE):
        os.remove(DB_FILE)

    engine = create_engine(f"sqlite:///{DB_FILE}", echo=False)
    Base.metadata.create_all(bind=engine)
    repo = FrotaRepository(engine)

    print("📝 REPOSITÓRIO CRUD SEGURO COM SQLALCHEMY 2.0")
    print("=" * 60)

    # 1. INSERT (Criando veículos)
    repo.criar_veiculo(10, "AAA1234", "ATIVO")
    repo.criar_veiculo(20, "BBB9999", "ATIVO")
    repo.criar_veiculo(30, "CCC5555", "ATIVO")
    print("1. [INSERT] 3 Veículos inseridos com sucesso.")

    # 2. UPDATE Seguro (Atualizando veículo 10)
    repo.atualizar_status(10, "VIAGEM")
    print("2. [UPDATE] Veículo #10 atualizado para status 'VIAGEM'.")

    # 3. DELETE Seguro (Removendo veículo 20)
    repo.remover_veiculo(20)
    print("3. [DELETE] Veículo #20 removido do sistema.")

    # 4. SELECT (Listagem Final)
    print("\n4. [SELECT] Estado Atual da Frota:")
    for v in repo.listar_todos():
        print(f"  🚛 {v}")
    print("=" * 60)

🚀 Como Executar

Execute o script no terminal:

python miniprojeto_13_crud.py

🖥️ Saída Esperada no Console

📝 REPOSITÓRIO CRUD SEGURO COM SQLALCHEMY 2.0
============================================================
1. [INSERT] 3 Veículos inseridos com sucesso.
2. [UPDATE] Veículo #10 atualizado para status 'VIAGEM'.
3. [DELETE] Veículo #20 removido do sistema.

4. [SELECT] Estado Atual da Frota:
  🚛 Veiculo(id=10, placa='AAA1234', status='VIAGEM')
  🚛 Veiculo(id=30, placa='CCC5555', status='ATIVO')
============================================================

💡 Checkpoint de Lógica

Aviso

Reflexão Profissional: Diferente de editores de texto como o Word, comandos DML como UPDATE e DELETE sem controle transacional (como vimos no Cap 03) não possuem Ctrl+Z. Ao executar o comando, o disco rígido é reescrito instantaneamente. No MySQL Workbench, existe uma trava de segurança chamada Safe Updates que bloqueia UPDATEs que não usam a Chave Primária no WHERE, mas em servidores em nuvem, essa trava não existe! Respeite o WHERE! 🚀🛡️



🧪 Quiz de Fixação e Autoavaliação — Capítulo 13

1. O que acontece se um desenvolvedor executar o comando UPDATE funcionarios SET salario = 10000; sem colocar a cláusula WHERE?

  • A) O banco dá erro de sintaxe por falta do WHERE.
  • B) TODOS os funcionários da empresa terão seus salários alterados para R$ 10.000 instantaneamente, sobrescrevendo a base inteira!
  • C) Apenas o primeiro funcionário cadastrado terá o salário alterado.
  • D) O comando é ignorado.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: Sem WHERE, comandos UPDATE e DELETE afetam 100% das linhas da tabela. Em ambiente de produção, sempre execute dentro de uma transação com BEGIN; antes de comitar!


2. Qual é a utilidade da cláusula moderna RETURNING (nativa do PostgreSQL e SQLite 3.35+) em um comando INSERT?

  • A) Desfazer o insert e voltar para o menu anterior.
  • B) Retornar imediatamente os valores das colunas geradas pelo banco (como o id autoincrementado gerado) sem precisar fazer um segundo SELECT em seguida.
  • C) Enviar um e-mail com os dados inseridos.
  • D) Calcular a média dos valores da tabela.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: INSERT INTO clientes (nome) VALUES ('Ana') RETURNING id; economiza round-trips de rede ao devolver o ID gerado na mesma operação.


3. O que é uma operação de 'UPSERT' (Merge / On Conflict) no SQL moderno?

  • A) Uma operação que apaga a tabela se houver duplicidade.
  • B) Uma instrução que tenta fazer INSERT, mas se encontrar conflito de chave única (UNIQUE/PK), executa um UPDATE automático na linha existente.
  • C) Um comando que converte o banco para NoSQL.
  • D) Uma atualização feita em letras maiúsculas.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: INSERT INTO estoque (sku, qtd) VALUES ('P01', 10) ON CONFLICT (sku) DO UPDATE SET qtd = estoque.qtd + 10; resolve concorrência de estoque perfeitamente.


🎯 Laboratório Prático

Coloque este conhecimento em prática agora mesmo executando o roteiro autoguiado:
👉 ATIVIDADE 06: SQL DML (MANIPULAÇÃO DE DADOS)

🔍 CAPÍTULO 14: CONSULTAS DQL, SELECT, FILTROS E AGREGAÇÕES


🎯 Objetivos de Aprendizagem

Ao final deste capítulo (estimativa: 2 horas de estudo autoguiado), você será capaz de:

  • 🔹 Dominar a anatomia completa da instrução SELECT, FROM, WHERE, GROUP BY, HAVING, ORDER BY e LIMIT/OFFSET.
  • 🔹 Aplicar funções agregadoras essenciais: COUNT(), SUM(), AVG(), MIN() e MAX().
  • 🔹 Diferenciar com precisão o papel do WHERE (filtro linha a linha antes do agrupamento) vs HAVING (filtro sobre o resultado agregado do grupo).
  • 🔹 Construir queries analíticas e relatórios de fechamento corporativo em SQLAlchemy 2.0.

Dos milhares de comandos SQL rodando agora nos servidores da Amazon ou do Google, 90% deles são comandos SELECT. A Extração de Dados (DQL - Data Query Language) é o que permite que a diretoria tome decisões baseadas em fatos, e não em achismos. 🛡️🧩

🏢 O Cenário Prático (Seu Desafio)

O Diretor de Operações da TecProExpress quer um painel de controle. Ele diz: "Quero ver apenas os motoristas do estado de São Paulo, que ganham mais de R$ 3.000,00, organizados do maior salário para o menor, e quero o nome de quem tem 'Silva' no sobrenome".

"Seu desafio é transformar esse pedido humano em uma Query SQL matemática e cirúrgica, extraindo exatamente o que o diretor quer da base de dados, ignorando os milhares de outros registros irrelevantes."


🧠 Fundamentos: A Anatomia da Busca

O comando SELECT lê as tabelas de forma passiva. Ele nunca altera ou apaga um dado. Ele apenas tira uma "foto" e te mostra.

⚙️ Ordem Lógica de Execução do SGBD

Embora você escreva o comando começando com SELECT, o processador de consultas do banco de dados executa as etapas na seguinte ordem lógica:

flowchart LR
    A["1. FROM / JOIN<br>Carrega Tabelas"] --> B["2. WHERE<br>Filtra Linhas"]
    B --> C["3. GROUP BY<br>Agrupa Dados"]
    C --> D["4. HAVING<br>Filtra Grupos"]
    D --> E["5. SELECT<br>Projeta Colunas"]
    E --> F["6. ORDER BY<br>Ordena Linhas"]
    F --> G["7. LIMIT / OFFSET<br>Pagina Resultado"]

    style A fill:#e3f2fd,stroke:#1976d2
    style B fill:#fff3e0,stroke:#f57c00
    style E fill:#e8f5e9,stroke:#388e3c
    style F fill:#f3e5f5,stroke:#7b1fa2

1. Seleção Básica (O Coringa)

-- DDL/DML Base para os exemplos
-- CREATE TABLE motorista (id INT, nome VARCHAR(50), estado CHAR(2), salario DECIMAL(10,2));
-- INSERT INTO motorista VALUES (1, 'João Silva', 'SP', 3500.00), (2, 'Maria Souza', 'RJ', 2800.00);

-- O * (asterisco) significa "traga todas as colunas"
SELECT * FROM motorista;

-- Traga apenas colunas específicas (Melhor performance)
SELECT nome, salario FROM motorista;

2. O Filtro Matemático (WHERE)

Você não quer ver o banco inteiro, você quer filtrar linhas (Tuplas).

-- Filtro Exato
SELECT nome FROM motorista WHERE estado = 'SP';

-- Filtro Numérico Maior/Menor
SELECT nome FROM motorista WHERE salario > 3000.00;

3. Filtros Avançados de Padrão (LIKE e IN)

Para buscar textos parciais ou listas específicas.

-- O operador % funciona como um coringa (qualquer coisa antes, 'Silva', qualquer coisa depois)
SELECT nome FROM motorista WHERE nome LIKE '%Silva%';

-- O operador IN evita ter que escrever vários OR (estado='SP' OR estado='RJ')
SELECT nome FROM motorista WHERE estado IN ('SP', 'RJ', 'MG');

4. Ordenação (ORDER BY)

Como o resultado será apresentado na tela.

-- ASC = Crescente (A-Z ou 0-9) | DESC = Decrescente (Z-A ou 9-0)
SELECT nome, salario FROM motorista ORDER BY salario DESC;


📊 Pipeline de Execução Lógica do Comando SELECT

Embora você escreva o SELECT na primeira linha, o motor do banco de dados (Query Engine) processa a consulta na seguinte ordem interna:

flowchart TD
    S1["1. FROM & JOINs (Localiza as tabelas e junta os dados)"] --> S2["2. WHERE (Filtra as linhas individuais)"]
    S2 --> S3["3. GROUP BY (Agrupa registros em blocos)"]
    S3 --> S4["4. HAVING (Filtra os grupos calculados)"]
    S4 --> S5["5. SELECT (Projeta e formata as colunas solicitadas)"]
    S5 --> S6["6. ORDER BY (Ordena o conjunto resultante)"]
    S6 --> S7["7. LIMIT & OFFSET (Pagina os resultados para a aplicação)"]
    style S1 fill:#e3f2fd,stroke:#1e88e5
    style S2 fill:#fff3e0,stroke:#fb8c00
    style S5 fill:#e8f5e9,stroke:#43a047
    style S7 fill:#f3e5f5,stroke:#8e24aa

📖 Exemplo Guiado: O Relatório do Diretor

Vamos unir todas as cláusulas para resolver o desafio da TecProExpress.

A ordem de escrita é obrigatória no SQL:

  1. SELECT (O que eu quero ver?)
  2. FROM (De onde vem?)
  3. WHERE (Quais são as condições?)
  4. ORDER BY (Como organizo?)

🛠️ Código do Exemplo

SELECT nome, estado, salario 
FROM motorista 
WHERE estado = 'SP' 
  AND salario > 3000.00 
  AND nome LIKE '%Silva%'
ORDER BY salario DESC;

🔍 Detalhamento do Código:

  • AND: O operador lógico exige que as três condições sejam verdadeiras simultaneamente para que a linha seja exibida na tela.
  • ORDER BY salario DESC: O motorista que ganha R$ 4.000 aparecerá antes do que ganha R$ 3.500.

🛠️ Prática Obrigatória: Extração de Dados

Cenário: O relatório de Pacotes da TecProExpress.

  1. Crie a tabela pacote com id, descricao, peso_kg e valor_frete.
  2. Insira 4 pacotes com pesos e fretes diferentes.
  3. Crie um SELECT que mostre apenas a descricao e o valor_frete dos pacotes que pesam entre 10 e 50 kg (BETWEEN), ordenados pelo frete mais barato primeiro.

🚀 Script de Seed (Gabarito DQL)

-- 1. Setup (DDL)
CREATE TABLE pacote (
    id INT PRIMARY KEY,
    descricao VARCHAR(100),
    peso_kg DECIMAL(5,2),
    valor_frete DECIMAL(10,2)
);

-- 2. Massa de Dados (DML)
INSERT INTO pacote VALUES (1, 'Televisão', 15.50, 150.00);
INSERT INTO pacote VALUES (2, 'Celular', 0.50, 30.00);
INSERT INTO pacote VALUES (3, 'Geladeira', 80.00, 400.00);
INSERT INTO pacote VALUES (4, 'Bicicleta', 20.00, 200.00);

-- 3. O Relatório (DQL)
SELECT descricao, valor_frete 
FROM pacote 
WHERE peso_kg BETWEEN 10.00 AND 50.00 
ORDER BY valor_frete ASC;


💻 Ponte Prática: Do SQL Manual ao SQLAlchemy 2.0 ORM

Como o SQLAlchemy 2.0 executa consultas SELECT parametrizadas e imunes a SQL Injection em Python?

🔴 1. A Abordagem Manual (Concatenação de Strings e Risco de SQL Injection)

No modelo procedural ingênuo, strings SQL são montadas com formatação de texto direta. Um usuário malicioso pode injetar comandos arbitrários no banco:

# ❌ ABORDAGEM VULNERÁVEL: SQL Injection clássico
termo_busca = "Silva' OR '1'='1"
# O comando resultante ignora o filtro e vaza todos os registros da empresa!
query_insegura = f"SELECT * FROM motoristas WHERE nome LIKE '%{termo_busca}%';"

🟢 2. A Abordagem com SQLAlchemy 2.0 (Consultas Declarativas e Parametrização Automática)

Com o SQLAlchemy 2.0, usamos a API select(). Todos os parâmetros passados no .where() são tratados estritamente como dados, tornando o código imune a injeções:

# ✅ ABORDAGEM MODERNA COM SQLALCHEMY 2.0: Consultas Tipadas e Seguras
from sqlalchemy import create_engine, String, Float, Integer, select, desc, func
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, Session

class Base(DeclarativeBase):
    pass

class MotoristaSelectModel(Base):
    __tablename__ = "motoristas_dql"

    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    nome: Mapped[str] = mapped_column(String(100), nullable=False)
    estado: Mapped[str] = mapped_column(String(2), nullable=False)
    salario: Mapped[float] = mapped_column(Float, nullable=False)

    def __repr__(self) -> str:
        return f"Motorista(id={self.id}, nome='{self.nome}', UF='{self.estado}', Salario=R$ {self.salario:.2f})"

🛠️ Mini-Projeto 14 (BD): Painel Analítico de Faturamento e Filtros Parametrizados

Objetivo: Construir consultas complexas com filtros compostos, ordenação e funções agregadas (func.sum, func.avg) via SQLAlchemy 2.0.

📋 Pré-requisitos e Instalação

No terminal do seu ambiente virtual (PowerShell ou Bash), instale a biblioteca necessária:

pip install sqlalchemy

💻 Código Completo e Autocontido (miniprojeto_14_dql.py)

Crie o arquivo miniprojeto_14_dql.py e insira o código abaixo integralmente:

"""
Mini-Projeto 14: Painel Analítico de Faturamento e Filtros Parametrizados
Curso: GTI - Banco de Dados Relacionais e Engenharia de Software
Stack: Python 3.11+ | SQLAlchemy 2.0 | SQLite
"""
import os
from sqlalchemy import create_engine, String, Float, Integer, select, desc, func
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, Session

# 1. Definição Declarativa do Schema DQL
class Base(DeclarativeBase):
    pass

class MotoristaSelectModel(Base):
    __tablename__ = "motoristas_dql"

    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    nome: Mapped[str] = mapped_column(String(100), nullable=False)
    estado: Mapped[str] = mapped_column(String(2), nullable=False)
    salario: Mapped[float] = mapped_column(Float, nullable=False)

    def __repr__(self) -> str:
        return f"Motorista(id={self.id}, nome='{self.nome}', UF='{self.estado}', Salario=R$ {self.salario:.2f})"

# 2. Ponto de Entrada Executável
if __name__ == "__main__":
    DB_FILE = "tecpro_dql_demo.db"

    # Reset preventivo para garantir idempotência em testes repetidos
    if os.path.exists(DB_FILE):
        os.remove(DB_FILE)

    engine = create_engine(f"sqlite:///{DB_FILE}", echo=False)
    Base.metadata.create_all(bind=engine)

    # 1. Povoando a base com massa de motoristas
    with Session(engine) as session:
        m1 = MotoristaSelectModel(id=1, nome="João Silva", estado="SP", salario=3500.0)
        m2 = MotoristaSelectModel(id=2, nome="Maria Souza", estado="RJ", salario=2800.0)
        m3 = MotoristaSelectModel(id=3, nome="Carlos Eduardo Silva", estado="SP", salario=4200.0)
        m4 = MotoristaSelectModel(id=4, nome="Ana Cristina", estado="MG", salario=3100.0)
        session.add_all([m1, m2, m3, m4])
        session.commit()

    print("🔍 PAINEL ANALÍTICO DQL (CONSULTAS SELECT) - TECPROEXPRESS")
    print("=" * 65)

    with Session(engine) as session:
        # Relatório 1: Filtro Composto (SP, Salário > 3000, Sobrenome Silva, Ordenado DESC)
        termo = "Silva"
        stmt_filtro = (
            select(MotoristaSelectModel)
            .where(MotoristaSelectModel.estado == "SP")
            .where(MotoristaSelectModel.salario > 3000.0)
            .where(MotoristaSelectModel.nome.like(f"%{termo}%"))
            .order_by(desc(MotoristaSelectModel.salario))
        )
        print("1. [DQL] Motoristas de SP com 'Silva' e Salário > R$ 3.000 (Maior para o Menor):")
        for m in session.scalars(stmt_filtro).all():
            print(f"   • {m}")

        # Relatório 2: Métricas Agregadas (Média Salarial e Folha Total de SP)
        stmt_metricas = (
            select(
                func.count(MotoristaSelectModel.id),
                func.sum(MotoristaSelectModel.salario),
                func.avg(MotoristaSelectModel.salario)
            ).where(MotoristaSelectModel.estado == "SP")
        )
        total_mot, folha_total, media_salarial = session.execute(stmt_metricas).one()
        print("\n2. [AGREGAÇÕES] Resumo da Folha em SP:")
        print(f"   • Total de Motoristas em SP: {total_mot}")
        print(f"   • Folha Mensal Total: R$ {folha_total:.2f}")
        print(f"   • Média Salarial: R$ {media_salarial:.2f}")

    print("=" * 65)

🚀 Como Executar

Execute o script no terminal:

python miniprojeto_14_dql.py

🖥️ Saída Esperada no Console

🔍 PAINEL ANALÍTICO DQL (CONSULTAS SELECT) - TECPROEXPRESS
=================================================================
1. [DQL] Motoristas de SP com 'Silva' e Salário > R$ 3.000 (Maior para o Menor):
   • Motorista(id=3, nome='Carlos Eduardo Silva', UF='SP', Salario=R$ 4200.00)
   • Motorista(id=1, nome='João Silva', UF='SP', Salario=R$ 3500.00)

2. [AGREGAÇÕES] Resumo da Folha em SP:
   • Total de Motoristas em SP: 2
   • Folha Mensal Total: R$ 7700.00
   • Média Salarial: R$ 3850.00
=================================================================

💡 Checkpoint de Lógica

Importante

Reflexão Profissional: Nunca, em hipótese alguma, utilize SELECT * no código fonte de uma aplicação em produção (Java, Node.js, C#, Python). Se a sua aplicação só precisa mostrar o Nome e o Email do usuário, fazer SELECT * fará com que o SGBD traga todas as colunas, incluindo Fotos, Textos longos de Biografia e a Senha criptografada pela rede. Isso gasta banda, atrasa a tela e gera risco de segurança. Sempre cite as colunas que você quer no SELECT! 🧠🛡️



🧪 Quiz de Fixação e Autoavaliação — Capítulo 14

1. Qual é a ordem lógica e real de execução que o motor do SGBD segue internamente para processar uma consulta SQL?

  • A) 1. SELECT -> 2. FROM -> 3. WHERE -> 4. GROUP BY -> 5. ORDER BY.
  • B) 1. FROM -> 2. WHERE -> 3. GROUP BY -> 4. HAVING -> 5. SELECT -> 6. ORDER BY -> 7. LIMIT.
  • C) 1. ORDER BY -> 2. LIMIT -> 3. SELECT -> 4. FROM.
  • D) 1. SELECT -> 2. ORDER BY -> 3. FROM.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: Embora o SELECT venha primeiro na sintaxe visual, o SGBD primeiro busca as tabelas (FROM), filtra linhas (WHERE), agrupa (GROUP BY), filtra grupos (HAVING), projeta colunas (SELECT) e ordena (ORDER BY).


2. Qual a diferença fundamental entre as cláusulas WHERE e HAVING no SQL?

  • A) WHERE filtra linhas individuais antes do agrupamento; HAVING filtra os grupos calculados após as funções de agregação (ex: HAVING COUNT(*) > 5).
  • B) WHERE só aceita números e HAVING só aceita textos.
  • C) HAVING substitui o FROM.
  • D) São palavras idênticas sem nenhuma diferença de comportamento.
💡 Ver Resposta e Justificativa

Resposta Correta: A
Justificativa: WHERE preco > 100 descarta produtos baratos antes do cálculo. GROUP BY categoria HAVING AVG(preco) > 500 filtra apenas categorias cuja média calculada exceda 500.


3. Para que serve a combinação de cláusulas LIMIT 10 OFFSET 20 em uma API REST?

  • A) Para apagar os primeiros 10 registros do banco.
  • B) Para implementar paginação de dados (exibindo a Página 3 com 10 itens por página, pulando os primeiros 20 registros).
  • C) Para acelerar a velocidade do disco em 20%.
  • D) Para criar um índice de 10 colunas.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: Paginação profissional com LIMIT e OFFSET impede que o backend tente carregar 1 milhão de linhas de uma vez na memória RAM do servidor.


🎯 Laboratório Prático

Coloque este conhecimento em prática agora mesmo executando o roteiro autoguiado:
👉 ATIVIDADE 07: SQL AVANÇADO (RELATÓRIOS)

🔗 CAPÍTULO 15: LÓGICA NULL, SUBQUERIES E JOINS


🎯 Objetivos de Aprendizagem

Ao final deste capítulo (estimativa: 2 horas de estudo autoguiado), você será capaz de:

  • 🔹 Compreender a Lógica Tri-Valente da SQL: TRUE, FALSE e UNKNOWN (NULL), e os operadores IS NULL e COALESCE().
  • 🔹 Dominar todos os tipos de junção: INNER JOIN, LEFT OUTER JOIN, RIGHT OUTER JOIN e FULL OUTER JOIN.
  • 🔹 Construir Subqueries aninhadas e correlacionadas com operadores IN, NOT IN, EXISTS e NOT EXISTS.
  • 🔹 Mapear consultas multi-tabela com SQLAlchemy 2.0 utilizando select().join() e joinedload().

No mundo real corporativo, os dados nunca estão em uma única tabela. Eles estão espalhados por dezenas de tabelas normalizadas. O desenvolvedor sênior é aquele capaz de "costurar" essas informações em tempo real. 🛡️🧩

🏢 O Cenário Prático (Seu Desafio)

A equipe de faturamento da TecProExpress fez uma busca simples para somar todos os pacotes entregues, mas o valor bateu milhares de reais a menos do que o real. Por quê? Porque alguns pacotes ainda não tinham um Motorista designado (ID_Motorista = NULL), e o JOIN que o desenvolvedor júnior usou simplesmente deletou esses pacotes do relatório!

"Seu desafio é dominar as armadilhas do NULL e aplicar o tipo correto de JOIN para garantir que pacotes não atribuídos continuem aparecendo no faturamento geral."


🧠 Fundamentos: O Buraco Negro do NULL

No SQL, a lógica não é Binária (True/False). É Ternária (True/False/Unknown). O NULL representa o "Desconhecido". E a regra de ouro é: A matemática com o desconhecido sempre resulta em desconhecido.

  • 10 + NULL = NULL
  • 'A' = NULL -> FALSO (Nem o NULL é igual a outro NULL!)

🛠️ Como checar se algo é NULL?

Você não pode usar = (igual). Você deve usar a palavra IS.

-- Errado (Não retorna nada)
SELECT * FROM pacote WHERE id_motorista = NULL;

-- Certo (Traz os pacotes sem motorista)
SELECT * FROM pacote WHERE id_motorista IS NULL;

📖 Subqueries (A Consulta Inception)

Uma Subquery é um SELECT dentro de outro SELECT. Usamos isso quando precisamos do resultado de uma busca para poder fazer outra busca.

🛠️ Código do Exemplo (Quem ganha mais que a média?)

-- A Subquery (entre parênteses) roda PRIMEIRO e calcula a média. 
-- O valor calculado é passado para o SELECT de fora.
SELECT nome, salario 
FROM motorista 
WHERE salario > (SELECT AVG(salario) FROM motorista);

🔗 A Arte dos JOINs

Quando as tabelas foram normalizadas (Cap. 10), elas foram separadas. O JOIN é a "Supercola" que une elas de volta.

📊 Diagrama de Venn dos JOINs

flowchart LR
    subgraph INNER JOIN
    A1(("Tabela A")) --- B1(("Tabela B"))
    end
    
    subgraph LEFT JOIN
    A2((("Tabela A"))) --- B2(("Tabela B"))
    end
    
    style A1 fill:#e0e0e0
    style B1 fill:#e0e0e0
    style A2 fill:#4caf50
    style B2 fill:#e0e0e0

Diagrama Visual de SQL JOINs

1. INNER JOIN (A Interseção Perfeita)

Retorna APENAS o que existe dos dois lados. (O erro do júnior da TecProExpress).

  • Se o pacote não tem motorista, ele não aparece.

2. LEFT JOIN (Preservando a Tabela da Esquerda)

Retorna TUDO da Tabela A (Esquerda), mesmo que ela não tenha correspondência na Tabela B. (A solução para o relatório!).

  • Todos os pacotes aparecem. Se não tiver motorista, a coluna do motorista vem preenchida com NULL.

🛠️ Prática Obrigatória: Salvando o Faturamento

Cenário: Corrigindo o erro do painel da TecProExpress.

  1. Crie a tabela motorista e a tabela pacote.
  2. Insira dois pacotes com motoristas e um pacote com id_motorista = NULL.
  3. Crie uma query que liste todos os pacotes, mostrando o nome do motorista apenas quando ele existir.

🚀 Script de Seed (Gabarito de JOINs)

-- DDL
CREATE TABLE motorista (id INT PRIMARY KEY, nome VARCHAR(50));
CREATE TABLE pacote (id INT PRIMARY KEY, descricao VARCHAR(50), valor DECIMAL(10,2), id_motorista INT);

-- DML
INSERT INTO motorista VALUES (1, 'Carlos'), (2, 'Ana');
INSERT INTO pacote VALUES (100, 'Geladeira', 200.00, 1);
INSERT INTO pacote VALUES (101, 'TV', 50.00, 2);
INSERT INTO pacote VALUES (102, 'Micro-ondas', 30.00, NULL); -- Pacote sem dono

-- O ERRO DO JÚNIOR (O Micro-ondas não aparece)
-- SELECT p.descricao, m.nome FROM pacote p INNER JOIN motorista m ON p.id_motorista = m.id;

-- DQL (A SOLUÇÃO COM LEFT JOIN)
SELECT p.descricao, p.valor, m.nome AS motorista_responsavel
FROM pacote p
LEFT JOIN motorista m ON p.id_motorista = m.id;

🔍 Detalhamento da Consulta:

  • pacote p: A letra p é um Alias (Apelido), para não precisarmos digitar o nome completo da tabela toda hora.
  • LEFT JOIN: O pacote é a Tabela A (Esquerda, citada antes do JOIN). Logo, a query promete: "Mostrarei todos os pacotes, custe o que custar!".


💻 Ponte Prática: Do SQL Manual ao SQLAlchemy 2.0 ORM

Como o SQLAlchemy 2.0 trata a Lógica Ternária do NULL e executa LEFT JOINs e Expressões CASE com segurança?

🔴 1. A Abordagem Manual (O Perigo de INNER JOIN Cego e Comparações Nulas)

No SQL manual, o uso descuidado de INNER JOIN apaga registros que possuem campos nulos do relatório:

# ❌ ABORDAGEM COM SQL MANUAL: Pacotes sem motorista são omitidos do faturamento!
cursor.execute("""
    SELECT p.descricao, p.valor, m.nome 
    FROM pacotes p 
    INNER JOIN motoristas m ON p.motorista_id = m.id; -- ⚠️ Deleta pacotes sem motorista!
""")

🟢 2. A Abordagem com SQLAlchemy 2.0 (outerjoin(), case() e func.coalesce())

Com o SQLAlchemy 2.0, usamos select().outerjoin() garantindo que 100% dos pacotes apareçam. Tratamos os campos nulos usando func.coalesce():

# ✅ ABORDAGEM MODERNA COM SQLALCHEMY 2.0: Outer Joins e Coalesce
from sqlalchemy import create_engine, String, Float, Integer, ForeignKey, select, func, case
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship, Session

class Base(DeclarativeBase):
    pass

class MotoristaJoinModel(Base):
    __tablename__ = "motoristas_joins"

    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    nome: Mapped[str] = mapped_column(String(50), nullable=False)

class PacoteJoinModel(Base):
    __tablename__ = "pacotes_joins"

    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    descricao: Mapped[str] = mapped_column(String(50), nullable=False)
    valor: Mapped[float] = mapped_column(Float, nullable=False)
    id_motorista: Mapped[int | None] = mapped_column(ForeignKey("motoristas_joins.id"), nullable=True)

    motorista: Mapped["MotoristaJoinModel" | None] = relationship("MotoristaJoinModel")

🛠️ Mini-Projeto 15 (BD): Relatório de Faturamento com Outer Joins

Objetivo: Gerar um relatório que inclui todos os pacotes (mesmo sem motorista alocado) e categoriza pacotes caros com a cláusula condicional case().

📋 Pré-requisitos e Instalação

No terminal do seu ambiente virtual (PowerShell ou Bash), instale a biblioteca necessária:

pip install sqlalchemy

💻 Código Completo e Autocontido (miniprojeto_15_joins.py)

Crie o arquivo miniprojeto_15_joins.py e insira o código abaixo integralmente:

"""
Mini-Projeto 15: Relatório de Faturamento com Outer Joins
Curso: GTI - Banco de Dados Relacionais e Engenharia de Software
Stack: Python 3.11+ | SQLAlchemy 2.0 | SQLite
"""
import os
from sqlalchemy import create_engine, String, Float, Integer, ForeignKey, select, func, case
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship, Session

# 1. Definição Declarativa dos Schemas com Chave Estrangeira Anulável
class Base(DeclarativeBase):
    pass

class MotoristaJoinModel(Base):
    __tablename__ = "motoristas_joins"

    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    nome: Mapped[str] = mapped_column(String(50), nullable=False)

class PacoteJoinModel(Base):
    __tablename__ = "pacotes_joins"

    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    descricao: Mapped[str] = mapped_column(String(50), nullable=False)
    valor: Mapped[float] = mapped_column(Float, nullable=False)
    id_motorista: Mapped[int | None] = mapped_column(ForeignKey("motoristas_joins.id"), nullable=True)

    motorista: Mapped["MotoristaJoinModel | None"] = relationship("MotoristaJoinModel")

# 2. Ponto de Entrada Executável
if __name__ == "__main__":
    DB_FILE = "tecpro_joins_demo.db"

    # Reset preventivo para garantir idempotência em testes repetidos
    if os.path.exists(DB_FILE):
        os.remove(DB_FILE)

    engine = create_engine(f"sqlite:///{DB_FILE}", echo=False)
    Base.metadata.create_all(bind=engine)

    # 1. Povoando a base de testes
    with Session(engine) as session:
        m1 = MotoristaJoinModel(id=1, nome="Carlos Oliveira")
        m2 = MotoristaJoinModel(id=2, nome="Ana Cristina")
        session.merge(m1)
        session.merge(m2)

        p1 = PacoteJoinModel(id=100, descricao="Geladeira Frost Free", valor=200.0, id_motorista=1)
        p2 = PacoteJoinModel(id=101, descricao="Smart TV 55'", valor=50.0, id_motorista=2)
        p3 = PacoteJoinModel(id=102, descricao="Micro-ondas Digital", valor=30.0, id_motorista=None)  # Sem motorista!
        session.merge(p1)
        session.merge(p2)
        session.merge(p3)
        session.commit()

    print("🔗 RELATÓRIO DE ENTREGAS COM LEFT OUTER JOIN - TECPROEXPRESS")
    print("=" * 70)

    with Session(engine) as session:
        # LEFT JOIN com COALESCE para substituir NULL por texto legível:
        motorista_nome = func.coalesce(MotoristaJoinModel.nome, "⚠️ [NÃO ALOCADO]")

        # Classificação condicional CASE WHEN:
        categoria_frete = case(
            (PacoteJoinModel.valor >= 100.0, "CARGA PESADA / VIP"),
            else_="CARGA CONVENCIONAL"
        )

        stmt = (
            select(
                PacoteJoinModel.id,
                PacoteJoinModel.descricao,
                PacoteJoinModel.valor,
                motorista_nome.label("responsavel"),
                categoria_frete.label("categoria")
            )
            .outerjoin(PacoteJoinModel.motorista)
        )

        for linha in session.execute(stmt):
            print(f"📦 Pacote #{linha.id}: {linha.descricao:<22} | R$ {linha.valor:>6.2f} | {linha.responsavel:<20} | {linha.categoria}")

    print("=" * 70)

🚀 Como Executar

Execute o script no terminal:

python miniprojeto_15_joins.py

🖥️ Saída Esperada no Console

🔗 RELATÓRIO DE ENTREGAS COM LEFT OUTER JOIN - TECPROEXPRESS
======================================================================
📦 Pacote #100: Geladeira Frost Free   | R$ 200.00 | Carlos Oliveira      | CARGA PESADA / VIP
📦 Pacote #101: Smart TV 55'           | R$  50.00 | Ana Cristina         | CARGA CONVENCIONAL
📦 Pacote #102: Micro-ondas Digital    | R$  30.00 | ⚠️ [NÃO ALOCADO]     | CARGA CONVENCIONAL
======================================================================

💡 Checkpoint de Lógica

Importante

Dica do Especialista: Você quer se destacar na engenharia de dados? Entenda que JOINs são operações matemáticas pesadas (Produto Cartesiano + Filtro). Em relatórios que cruzam 15 tabelas com milhões de linhas, a ordem que você escreve seus INNER JOIN e LEFT JOIN pode fazer a diferença entre a consulta terminar em 2 segundos ou travar o servidor da empresa. 🧠🛡️




🧪 Quiz de Fixação e Autoavaliação — Capítulo 15

1. Por que a expressão SQL WHERE email = NULL nunca retorna nenhuma linha, mesmo que existam clientes com e-mail nulo no banco?

  • A) Porque o SQL exige a palavra em português WHERE email = NULO.
  • B) Porque na Lógica Tri-Valente da SQL, comparações com NULL usando '=' resultam em 'UNKNOWN' (desconhecido). A sintaxe correta obrigatória é WHERE email IS NULL.
  • C) Porque o banco apaga os valores nulos automaticamente.
  • D) Porque NULL só pode ser consultado com números inteiros.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: NULL representa ausência de valor, e não um valor comparável. Nada é 'igual a nulo'. Use sempre IS NULL ou IS NOT NULL.


2. Qual a diferença entre um INNER JOIN e um LEFT JOIN ao unir a tabela Clientes com a tabela Pedidos?

  • A) O INNER JOIN retorna apenas clientes que possuem pelo menos um pedido; o LEFT JOIN retorna TODOS os clientes, preenchendo com NULL os campos de pedidos para aqueles clientes que nunca compraram nada.
  • B) O INNER JOIN só aceita clientes VIP; o LEFT JOIN aceita clientes comuns.
  • C) O LEFT JOIN inverte a ordem das colunas da tabela.
  • D) Não há diferença entre os dois joins.
💡 Ver Resposta e Justificativa

Resposta Correta: A
Justificativa: LEFT JOIN preserva todas as linhas da tabela da esquerda (mesmo sem correspondência na direita), sendo indispensável para relatórios como 'Clientes sem compras'.


3. Para que serve a função COALESCE(valor, 0) no SQL?

  • A) Para calcular o logaritmo natural do valor.
  • B) Para retornar o primeiro valor não-nulo da lista de argumentos (se valor for NULL, ele substitui automaticamente por 0).
  • C) Para ordenar os dados em ordem alfabética.
  • D) Para apagar a coluna do banco de dados.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: COALESCE(comissao, 0) evita que operações aritméticas como salario + comissao resultem em NULL quando a comissão estiver nula.


🎯 Laboratório Prático

Coloque este conhecimento em prática agora mesmo executando o roteiro autoguiado:
👉 ATIVIDADE 07: SQL AVANÇADO (RELATÓRIOS)

⚡ CAPÍTULO 16: VIEWS, ÍNDICES E OTIMIZAÇÃO DE CONSULTAS


🎯 Objetivos de Aprendizagem

Ao final deste capítulo (estimativa: 2 horas de estudo autoguiado), você será capaz de:

  • 🔹 Compreender a anatomia de Índices B-Tree, Hash, GIN e GiST e o impacto do custo de escrita (Write Overhead).
  • 🔹 Analisar planos de execução de consultas utilizando EXPLAIN ANALYZE (Seq Scan vs Index Scan).
  • 🔹 Criar e gerenciar Visões Lógicas (CREATE VIEW) e Visões Materializadas (MATERIALIZED VIEW) com atualização periódica.
  • 🔹 Otimizar a arquitetura de persistência reduzindo leituras de disco no Buffer Pool.

Escrever um SQL que devolve o resultado correto é fácil. Escrever um SQL que devolve o resultado correto em 3 milissegundos sob um banco com 10 milhões de linhas é o que separa um programador iniciante de um Arquiteto de Software. 🛡️🧩

🏢 O Cenário Prático (Seu Desafio)

Na TecProExpress, a tabela de Pacotes atingiu 5 milhões de registros. A busca no painel do usuário ("Procurar pacote pelo Código de Rastreio") começou a demorar 8 segundos para responder. Os clientes estão reclamando no Reclame Aqui. Além disso, o RH pediu um relatório com os dados dos estagiários, mas o banco se recusou a dar acesso à tabela original, pois nela constam salários confidenciais.

"Seu desafio é criar uma VIEW cega para proteger os salários do RH e implementar um Índice (Index) no Código de Rastreio para derrubar o tempo de busca da tabela de pacotes de 8 segundos para 3 milissegundos!"


🧠 Fundamentos 1: O Encapsulamento (VIEW)

Uma VIEW (Visão) é uma tabela virtual. Ela não armazena dados físicos no HD. Ela armazena apenas a "lógica" de um SELECT.

  • Segurança: Você pode criar uma VIEW que esconde a coluna "Salario" da tabela original e dar permissão para o usuário ler apenas essa VIEW.
  • Abstração: Se você tem um relatório que exige 5 JOINs super complexos, você pode salvar tudo isso em uma VIEW. O desenvolvedor frontend apenas fará SELECT * FROM nome_da_view;.

🛠️ Criando a Visão do RH

-- DDL Rápido: A tabela original (Intocável)
-- CREATE TABLE funcionario (id INT, nome VARCHAR(50), salario DECIMAL(10,2));

-- Criando a Visão Segura (Ignoramos o salário de propósito)
CREATE VIEW vw_funcionarios_rh AS
SELECT id, nome FROM funcionario;

-- Como o usuário (RH) irá consultar:
SELECT * FROM vw_funcionarios_rh;

🧠 Fundamentos 2: O Acelerador (Índices)

Quando você manda o SGBD procurar o código de rastreio 'BR123', e a tabela não tem Índice, o banco faz um Full Table Scan (Ele lê a linha 1, depois a linha 2... até a linha 5.000.000). Isso é terrível.

Um Índice (Index) é uma cópia organizada de uma coluna específica, guardada em uma árvore de busca (B-Tree). Funciona como o índice remissivo no final de um livro.

📊 Comparação de Busca

flowchart LR
    A["Tabela sem Índice<br/>(Lê 5 milhões de linhas)"] -->|O(N)| B["Demora 8 segundos"]
    C["Tabela com Índice B-Tree<br/>(Busca binária)"] -->|O(log N)| D["Demora 3 milissegundos"]

Mecanismos de Indexação B-Tree vs Hash

⚠️ O Preço do Índice

Se índices são tão maravilhosos, por que não colocamos em TODAS as colunas? Porque o Índice atrasa a gravação. Toda vez que houver um INSERT ou UPDATE, o banco terá que escrever o dado na tabela e depois reorganizar a árvore do Índice.

A Regra: Tabelas com muita leitura (Consultas) exigem índices. Tabelas com muita gravação (ex: Logs de GPS em tempo real) exigem o mínimo de índices possível.


🧠 Fundamentos 3: Storage — Como os Dados Vivem no Disco

Índice e VIEW são abstrações lógicas. Por baixo delas, o SGBD precisa fisicamente guardar cada linha em algum lugar do disco — e essa camada física se chama Storage Engine (Motor de Armazenamento).

  • Páginas (Pages/Blocks): O SGBD não lê "uma linha" por vez do disco — ele lê blocos de tamanho fixo (tipicamente 8 KB no PostgreSQL, 16 KB no MySQL/InnoDB), chamados páginas, que agrupam várias linhas. Um SELECT que busca 1 linha ainda assim traz a página inteira para a memória (Buffer Pool).
  • Tablespaces: Estruturas lógicas que mapeiam onde, fisicamente, os arquivos de uma tabela ou índice ficam gravados no disco — permitem, por exemplo, colocar tabelas muito acessadas num SSD rápido e tabelas de histórico num disco mais lento e barato.
  • Storage Engines (MySQL): O MySQL permite escolher o motor por tabela — InnoDB (padrão, transacional, com suporte a FOREIGN KEY e ROLLBACK) vs MyISAM (mais antigo, sem transações, historicamente mais rápido só para leitura pura).
  • Buffer Pool / Cache: Área de memória RAM onde o SGBD mantém as páginas mais usadas, para evitar ler do disco (muito mais lento) a cada consulta repetida.

💾 Do SELECT ao Disco

flowchart LR
    Q["SELECT * FROM pacote_log<br/>WHERE id = 42"] --> BP{"Página está no<br/>Buffer Pool (RAM)?"}
    BP -->|Sim: Cache Hit| R["Resposta em microssegundos"]
    BP -->|Não: Cache Miss| DISK[("Lê a Página do Disco (SSD/HD)")]
    DISK --> BP

Do SELECT ao Disco: Páginas e Buffer Pool

⚠️ Por que isso importa na prática: é por causa do conceito de página que um índice em uma coluna de baixa seletividade (ex.: uma coluna ativo com só true/false) muitas vezes não ajuda — o SGBD acaba lendo quase todas as páginas de qualquer jeito, e o Otimizador de Consultas prefere ignorar o índice e fazer Full Table Scan.


📖 Exemplo Guiado: Otimizando o Rastreio (EXPLAIN)

O comando EXPLAIN é a ferramenta de raio-x do SGBD. Ele não executa a consulta, ele te diz o "Plano Matemático" que o SGBD faria.

🛠️ Código do Exemplo

-- DDL Base
-- CREATE TABLE pacote_log (id INT, codigo_rastreio VARCHAR(15), status VARCHAR(20));
-- (Imagine 5 milhões de inserts aqui)

-- 1. O Diagnóstico: "Como você faria essa busca, Banco de Dados?"
EXPLAIN SELECT * FROM pacote_log WHERE codigo_rastreio = 'BR123';
-- O SGBD responderá: type = "ALL" (Ele vai ler TODAS as linhas).

-- 2. A Cura (DDL de Otimização)
CREATE INDEX idx_pacote_rastreio ON pacote_log(codigo_rastreio);

-- 3. O Novo Diagnóstico
EXPLAIN SELECT * FROM pacote_log WHERE codigo_rastreio = 'BR123';
-- O SGBD responderá: type = "ref" (Usando o Índice). Velocidade máxima atingida!

🛠️ Prática Obrigatória: Abstração e Velocidade

Cenário: O sistema de relatórios da TecProExpress.

  1. Crie a tabela faturamento com id_venda, data_venda, nome_cliente e valor_lucro.
  2. Crie uma VIEW chamada vw_vendas_publicas que mostre apenas a data e o cliente (escondendo o lucro da equipe de vendas).
  3. Crie um INDEX na coluna data_venda para acelerar os relatórios de fechamento mensal.

🚀 Script de Seed (Gabarito de Otimização)

-- 1. Tabela Original (DDL)
CREATE TABLE faturamento (
    id_venda INT PRIMARY KEY,
    data_venda DATE,
    nome_cliente VARCHAR(100),
    valor_lucro DECIMAL(15,2)
);

-- 2. View Segura (DQL encapsulada em DDL)
CREATE VIEW vw_vendas_publicas AS
SELECT data_venda, nome_cliente 
FROM faturamento;

-- 3. Índice Estratégico (DDL)
CREATE INDEX idx_data_fechamento ON faturamento(data_venda);


💻 Ponte Prática: Do SQL Manual ao SQLAlchemy 2.0 ORM

Como o SQLAlchemy 2.0 define Índices B-Tree, Views e permite inspecionar o plano de execução via EXPLAIN em Python?

🔴 1. A Abordagem Manual (Filtros sem Índices e Full Scan Lento)

Sem índices, qualquer busca por código de rastreio varre a tabela inteira do primeiro ao último registro:

# ❌ ABORDAGEM SEM ÍNDICE: Full Table Scan a cada consulta
cursor.execute("SELECT * FROM pacotes WHERE codigo_rastreio = 'BR998877';") # ⚠️ O(N) no tempo de resposta!

🟢 2. A Abordagem com SQLAlchemy 2.0 (Declaração de Índices B-Tree)

Com o SQLAlchemy 2.0, declaramos index=True ou criamos objetos Index() compostos nos metadados. O banco constrói a árvore B-Tree e reduz o tempo de busca para $O(\log N)$:

# ✅ ABORDAGEM MODERNA COM SQLALCHEMY 2.0: Índices B-Tree Declarativos
from sqlalchemy import create_engine, String, Float, Integer, Index, select, text
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, Session

class Base(DeclarativeBase):
    pass

class PacoteIndexModel(Base):
    __tablename__ = "pacotes_indexados"
    __table_args__ = (
        Index("idx_pacote_codigo_btree", "codigo_rastreio"), # Índice B-Tree explícito
    )

    id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    codigo_rastreio: Mapped[str] = mapped_column(String(20), nullable=False)
    peso_kg: Mapped[float] = mapped_column(Float, nullable=False)
    status: Mapped[str] = mapped_column(String(20), default="EM_TRANSITO")

    def __repr__(self) -> str:
        return f"PacoteIndex(id={self.id}, cod='{self.codigo_rastreio}', status='{self.status}')"

🛠️ Mini-Projeto 16 (BD): Benchmark de Performance e Auditoria com EXPLAIN

Objetivo: Popular uma base com massa de registros e inspecionar o plano de execução via EXPLAIN QUERY PLAN comprovando o uso de índices B-Tree.

📋 Pré-requisitos e Instalação

No terminal do seu ambiente virtual (PowerShell ou Bash), instale a biblioteca necessária:

pip install sqlalchemy

💻 Código Completo e Autocontido (miniprojeto_16_indices.py)

Crie o arquivo miniprojeto_16_indices.py e insira o código abaixo integralmente:

"""
Mini-Projeto 16: Benchmark de Performance e Auditoria com EXPLAIN
Curso: GTI - Banco de Dados Relacionais e Engenharia de Software
Stack: Python 3.11+ | SQLAlchemy 2.0 | SQLite
"""
import os
import time
from sqlalchemy import create_engine, String, Float, Integer, Index, select, text
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, Session

# 1. Definição Declarativa com Índice B-Tree Explícito
class Base(DeclarativeBase):
    pass

class PacoteIndexModel(Base):
    __tablename__ = "pacotes_indexados"
    __table_args__ = (
        Index("idx_pacote_codigo_btree", "codigo_rastreio"),  # Índice B-Tree explícito
    )

    id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    codigo_rastreio: Mapped[str] = mapped_column(String(20), nullable=False)
    peso_kg: Mapped[float] = mapped_column(Float, nullable=False)
    status: Mapped[str] = mapped_column(String(20), default="EM_TRANSITO")

    def __repr__(self) -> str:
        return f"PacoteIndex(id={self.id}, cod='{self.codigo_rastreio}', status='{self.status}')"

# 2. Ponto de Entrada Executável
if __name__ == "__main__":
    DB_FILE = "tecpro_indices_demo.db"

    # Reset preventivo para garantir idempotência em testes repetidos
    if os.path.exists(DB_FILE):
        os.remove(DB_FILE)

    engine = create_engine(f"sqlite:///{DB_FILE}", echo=False)
    Base.metadata.create_all(bind=engine)

    print("⚡ AUDITORIA DE PERFORMANCE E ÍNDICES B-TREE - TECPROEXPRESS")
    print("=" * 65)

    # 1. Inserção em lote para teste de benchmark
    with Session(engine) as session:
        pacotes_lote = [
            PacoteIndexModel(codigo_rastreio=f"BR{i:06d}XP", peso_kg=float(i % 50 + 1))
            for i in range(1, 1001)
        ]
        session.add_all(pacotes_lote)
        session.commit()
        print("✅ 1.000 Registros inseridos no banco para benchmark!")

    # 2. Inspecionando o Plano de Execução do SQLite com EXPLAIN QUERY PLAN
    with engine.connect() as conn:
        print("\n🔍 Inspecionando Plano de Execução do Otimizador:")
        plano = conn.execute(text("EXPLAIN QUERY PLAN SELECT * FROM pacotes_indexados WHERE codigo_rastreio = 'BR000500XP';")).fetchall()
        for p in plano:
            print(f"   • Detalhe do Plano: {p[3]}")
            if "USING INDEX" in str(p[3]):
                print("   🟢 SUCESSO: Otimizador utilizou a árvore B-Tree 'idx_pacote_codigo_btree'!")

    print("=" * 65)

🚀 Como Executar

Execute o script no terminal:

python miniprojeto_16_indices.py

🖥️ Saída Esperada no Console

⚡ AUDITORIA DE PERFORMANCE E ÍNDICES B-TREE - TECPROEXPRESS
=================================================================
✅ 1.000 Registros inseridos no banco para benchmark!

🔍 Inspecionando Plano de Execução do Otimizador:
   • Detalhe do Plano: SEARCH pacotes_indexados USING INDEX idx_pacote_codigo_btree (codigo_rastreio=?)
   🟢 SUCESSO: Otimizador utilizou a árvore B-Tree 'idx_pacote_codigo_btree'!
=================================================================

💡 Checkpoint de Lógica

Dica

Dica do Engenheiro: Um erro clássico de performance é colocar o código dentro de funções (ex: WHERE YEAR(data_venda) = 2026). Quando você envolve a coluna com uma função, o SGBD é forçado a ignorar o Índice e fazer o Full Table Scan. O correto para alta velocidade é: WHERE data_venda BETWEEN '2026-01-01' AND '2026-12-31'. 🧠🛡️



🧪 Quiz de Fixação e Autoavaliação — Capítulo 16

1. Como um Índice B-Tree acelera uma consulta como SELECT * FROM clientes WHERE cpf = '123.456.789-00'; em uma tabela com 10 milhões de linhas?

  • A) Ele lê todas as 10 milhões de linhas do disco uma por uma.
  • B) Ele organiza as chaves em uma árvore balanceada de busca com complexidade $O(\log N)$, encontrando o registro em cerca de 3 a 4 leituras de páginas de memória em vez de varrer a tabela inteira (Seq Scan).
  • C) Ele apaga as outras 9.999.999 linhas para deixar a busca rápida.
  • D) Ele duplica o tamanho da memória RAM do servidor.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: Sem índice, o banco faz 'Sequential Scan' (lê 10 milhões de linhas). Com índice B-Tree, o ponteiro da tupla é localizado em milissegundos com pouquíssimas leituras.


2. Por que NÃO devemos criar índices em TODAS as colunas de todas as tabelas indiscriminadamente?

  • A) Porque o banco bloqueia a criação de mais de 3 índices.
  • B) Porque cada índice ocupa espaço extra em disco e gera overhead de processamento: toda operação de INSERT, UPDATE e DELETE fica mais lenta, pois o banco é forçado a atualizar a tabela e todos os seus índices associados.
  • C) Porque índices só funcionam em colunas de texto.
  • D) Porque índices apagam os dados antigos.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: Índices aceleram leituras (SELECT), mas cobram pedágio em escritas (INSERT/UPDATE/DELETE). Crie índices apenas em colunas frequentemente usadas em filtros (WHERE), junções (JOIN) e ordenações (ORDER BY).


3. Qual a diferença entre uma VIEW tradicional e uma MATERIALIZED VIEW no PostgreSQL?

  • A) A VIEW tradicional é apenas uma query salva que reexecuta toda vez que é consultada; a MATERIALIZED VIEW calcula e grava o resultado fisicamente em disco, permitindo leituras ultrarrápidas de relatórios pesados com atualização periódica (REFRESH).
  • B) A VIEW tradicional é feita em Python e a MATERIALIZED em Java.
  • C) MATERIALIZED VIEW só aceita dados de áudio e vídeo.
  • D) Não há nenhuma diferença técnica.
💡 Ver Resposta e Justificativa

Resposta Correta: A
Justificativa: Materialized Views são como 'caches de banco de dados': gravam o resultado pré-calculado em disco, ideais para dashboards de Business Intelligence (BI) e fechamentos contábeis.


🎯 Laboratório Prático

Coloque este conhecimento em prática agora mesmo executando o roteiro autoguiado:
👉 ATIVIDADE 11: ÍNDICES E OTIMIZAÇÃO

🔒 CAPÍTULO 17: SEGURANÇA DCL E EVOLUÇÃO DE SCHEMA


🎯 Objetivos de Aprendizagem

Ao final deste capítulo (estimativa: 2 horas de estudo autoguiado), você será capaz de:

  • 🔹 Dominar a linguagem de controle de dados DCL: GRANT (conceder privilégios), REVOKE (revogar) e gerenciamento de Roles de usuários.
  • 🔹 Aplicar o Princípio do Menor Privilégio (Least Privilege) separando usuários de aplicação (DML) de administradores (DDL/DBA).
  • 🔹 Gerenciar a evolução de esquemas em produção sem indisponibilidade (Zero Downtime Migrations).
  • 🔹 Configurar pipelines de migração contínua com Alembic integrados à esteira de CI/CD.

🏢 O Cenário Corporativo (Seu Desafio)

Na TecProExpress, novas políticas de governança exigem que desenvolvedores não tenham privilégios de exclusão de tabelas em produção. Seu desafio é implementar o Princípio do Menor Privilégio via DCL.


Em ambientes corporativos, nem todo usuário deve ter acesso a tudo. A Data Control Language (DCL) é a sub-linguagem SQL responsável por controlar quem pode fazer o quê no banco de dados.


Objetivo: Dominar os comandos GRANT e REVOKE para criar políticas de acesso granulares, compreendendo o conceito de Roles (Papéis) em MySQL 8.4 e PostgreSQL 17.


🧠 Fundamentos: Segurança DCL e Controle Granular de Privilégios O Modelo de Privilégios

🔹 Hierarquia de Acesso

flowchart TD
    DBA["👑 DBA\nTODOS os privilégios"] --> DEV["💻 Desenvolvedor\nSELECT + INSERT + UPDATE"]
    DBA --> REPORT["📊 Analista\nApenas SELECT"]
    DBA --> EST["🎓 Estagiário\nSELECT em Views"]

Tipos de Privilégios SQL

PrivilégioDescriçãoExemplo de Uso
SELECTLer dadosRelatórios, consultas
INSERTInserir novos registrosCadastro de dados
UPDATEAlterar registros existentesAtualização de dados
DELETERemover registrosManutenção de dados
CREATECriar tabelas/schemasDesenvolvimento
DROPRemover tabelas/schemasAdministração
ALL PRIVILEGESTodos os acimaApenas DBA

🔹 PASSO 2: Criando Usuários

🐬 MySQL 8.4

-- Criar usuário
CREATE USER 'analista'@'localhost' IDENTIFIED BY 'Senha@Forte123';

-- Criar usuário que pode acessar de qualquer host
CREATE USER 'dev'@'%' IDENTIFIED BY 'Dev@2026!';

🐘 PostgreSQL 17

-- Criar usuário (ROLE com LOGIN)
CREATE USER analista WITH PASSWORD 'Senha@Forte123';

-- Criar role sem login (grupo de permissões)
CREATE ROLE equipe_relatorios;

🔹 PASSO 3: Concedendo Privilégios (GRANT)

🐬 MySQL 8.4

-- SELECT em todas as tabelas do schema tecpro_express
GRANT SELECT ON tecpro_express.* TO 'analista'@'localhost';

-- SELECT + INSERT em uma tabela específica
GRANT SELECT, INSERT ON tecpro_express.pedido TO 'dev'@'%';

-- Todos os privilégios (apenas DBA!)
GRANT ALL PRIVILEGES ON tecpro_express.* TO 'dba_master'@'localhost';

-- Aplicar as alterações
FLUSH PRIVILEGES;

🐘 PostgreSQL 17

-- SELECT em todas as tabelas do schema
GRANT SELECT ON ALL TABLES IN SCHEMA tecpro_express TO analista;

-- SELECT + INSERT em tabela específica
GRANT SELECT, INSERT ON tecpro_express.pedido TO analista;

-- Acesso ao schema (obrigatório no PostgreSQL)
GRANT USAGE ON SCHEMA tecpro_express TO analista;

🔹 PASSO 4: Revogando Privilégios (REVOKE)

-- MySQL
REVOKE INSERT ON tecpro_express.pedido FROM 'dev'@'%';

-- PostgreSQL
REVOKE INSERT ON tecpro_express.pedido FROM analista;

🔹 PASSO 5: Roles (Papéis) — Agrupando Permissões

Em vez de conceder permissões usuário por usuário, criamos Roles (grupos de permissões):

🐘 PostgreSQL 17 (Suporte nativo a Roles)

-- 1. Criar Role com permissões
CREATE ROLE leitura_relatorios;
GRANT USAGE ON SCHEMA tecpro_express TO leitura_relatorios;
GRANT SELECT ON ALL TABLES IN SCHEMA tecpro_express TO leitura_relatorios;

-- 2. Atribuir a Role ao usuário
GRANT leitura_relatorios TO analista;
GRANT leitura_relatorios TO estagiario;

🐬 MySQL 8.4 (Roles a partir do MySQL 8.0)

-- 1. Criar Role
CREATE ROLE 'leitura_relatorios';
GRANT SELECT ON tecpro_express.* TO 'leitura_relatorios';

-- 2. Atribuir ao usuário
GRANT 'leitura_relatorios' TO 'analista'@'localhost';

-- 3. Ativar a role (obrigatório no MySQL)
SET DEFAULT ROLE 'leitura_relatorios' TO 'analista'@'localhost';

🔹 Modelo de Roles

flowchart TD
    R1["🔑 Role: admin_full"] --> U1["👑 DBA"]
    R2["👥 Role: leitura_relatorios"] --> U2["📊 Analista"]
    R2 --> U3["🎓 Estagiário"]
    R3["🛡️ Role: dev_crud"] --> U4["💻 Dev Backend"]

🔹 PASSO 6: Views + DCL = Segurança Completa

A combinação mais poderosa é usar Views (Cap. 16) com DCL:

-- 1. Criar View que oculta dados sensíveis
CREATE VIEW vw_funcionarios_publico AS
SELECT id, nome, data_admissao FROM funcionario;

-- 2. Dar acesso apenas à View, NUNCA à tabela real
GRANT SELECT ON vw_funcionarios_publico TO estagiario;

-- O estagiário pode:
SELECT * FROM vw_funcionarios_publico;  -- ? OK

-- O estagiário NÃO pode:
SELECT * FROM funcionario;  -- ? ACESSO NEGADO (salário protegido!)

🔹 Dica do Especialista: Em produção, NUNCAALL PRIVILEGES a usuários de aplicação. Crie Roles específicas com o mínimo de permissões necessárias. Este é o Princípio do Menor Privilégio (Principle of Least Privilege).


🔄 EVOLUÇÃO E ARQUITETURAS MODERNAS

Mudar a estrutura de um banco de dados em produção é um desafio técnico chamado Schema Evolution.


Objetivo: Dominar os comandos de alteração estrutural (ALTER TABLE), compreender a manipulação de dados semiestruturados (JSON) e diferenciar as arquiteturas SQL e NoSQL.


🔹 PASSO 1: Evolução de Schema (ALTER TABLE)

Sistemas reais mudam. O comando ALTER TABLE permite que a tabela se adapte sem perda de dados.

🔹 Ciclo de Adaptação

flowchart LR
    V1["📦 Tabela V1"] --> |"ADD COLUMN"| V2["📦 Tabela V2"]
    V2 --> |"ALTER COLUMN"| V3["📦 Tabela V3"]
    V3 --> |"DROP COLUMN"| V1

🐬 MySQL 8.4 (MODIFY)

-- Alterando o tipo de uma coluna existente
ALTER TABLE CONTATO MODIFY COLUMN APELIDO VARCHAR(50);

🐘 PostgreSQL 17 (ALTER TYPE)

-- Alterando o tipo de uma coluna existente
ALTER TABLE CONTATO ALTER COLUMN APELIDO TYPE VARCHAR(50);

🔹 PASSO 2: JSON em Bancos Relacionais

Os SGBDs modernos (MySQL 8.4 e Postgres 17) suportam dados híbridos (NoSQL dentro do SQL).

SGBDTipo de DadoOperador de Extração
MySQL 8.4JSON-> (ex: JSON->'$.NOME')
PostgreSQL 17JSONB->> (ex: JSONB->>'NOME')

🔹 PASSO 3: O Ecossistema NoSQL

Surgiu da necessidade de Escalabilidade Horizontal (adicionar mais servidores) em vez de apenas um servidor potente.

🔹 Tipos de NoSQL

TipoExemploAplicação Profissional
DocumentosMongoDBEsquema flexível (JSON).
Chave-ValorRedisCache e Sessoes ultrarápidas.
GrafosNeo4jRedes sociais e Fraudes.
ColunaresCassandraBig Data e Escala Global.

✅ Verificação de Aprendizagem (Unidade IV)

1. No Postgres 17, qual operador extrai JSON como Texto Limpo? a) -> b) ->> c) JSON_EXTRACT

2. O que ocorre no comando DROP SCHEMA VENDAS CASCADE? a) Apaga o schema e tudo o que depende dele (Tabelas, FKs, Views). b) Dá erro se houver tabelas.


📖 Clique aqui para revelar o Gabarito (SPOILER) 📖

✅ Gabarito:

  1. Letra B. O ->> converte para TEXT. O -> mantém como JSON.
  2. Letra A. O CASCADE é destrutivo e remove as dependências.

🔹 Perspectiva do Arquiteto: SQL é para consistência. NoSQL é para volume e velocidade. O engenheiro moderno sabe usar ambos de forma híbrida.


🔹 PASSO 4: Versionamento e Migrações de Schema com Alembic

Em ambientes corporativos de produção contínua (CI/CD), comandos imperativos como Base.metadata.create_all() ou scripts manuais executados no terminal apresentam riscos elevados de inconsistência entre ambientes. O comando create_all() apenas cria tabelas ausentes — ele não detecta novas colunas, não renomeia campos existentes e não ajusta constraints em tabelas já criadas.

A engenharia de dados moderna utiliza ferramentas de Schema Migration, sendo o Alembic o padrão oficial para o ecossistema SQLAlchemy:

flowchart LR
    Dev["👨‍💻 Desenvolvedor<br/>Altera Model em Python"] --> Mig["📝 alembic revision<br/>--autogenerate"]
    Mig --> Rev["📄 Script de Migração<br/>(upgrade / downgrade)"]
    Rev --> Exec["🚀 alembic upgrade head"]
    Exec --> DB[("🗄️ PostgreSQL / SQLite<br/>Schema Versionado")]

📄 Anatomia de um Script de Migração do Alembic

Cada evolução do banco de dados gera um arquivo em alembic/versions/ contendo duas funções obrigatórias e reversíveis:

"""adiciona coluna email em fornecedores

Revision ID: 7a8b9c0d1e2f
Revises: 1a2b3c4d5e6f
Create Date: 2026-09-10 10:00:00.000000
"""
from alembic import op
import sqlalchemy as sa

# Identificadores da cadeia de migração
revision = '7a8b9c0d1e2f'
down_revision = '1a2b3c4d5e6f'

def upgrade() -> None:
    """Aplica a evolução no schema sem perda de dados."""
    op.add_column('fornecedores_logistica', sa.Column('email', sa.String(100), nullable=True))

def downgrade() -> None:
    """Reverte a alteração de forma segura caso o deploy seja cancelado."""
    op.drop_column('fornecedores_logistica', 'email')

Comandos Essenciais do Alembic

  • alembic init migrations : Inicializa o ambiente de controle de versões de banco.
  • alembic revision --autogenerate -m "descricao" : Detecta as mudanças no SQLAlchemy e gera o script de migração.
  • alembic upgrade head : Aplica todas as migrações pendentes até a versão mais recente.
  • alembic downgrade -1 : Desfaz a última migração aplicada.

💻 Ponte Prática: Do SQL Manual ao SQLAlchemy 2.0 ORM

Como o SQLAlchemy 2.0 e a Engenharia de Software aplicam o Princípio do Menor Privilégio (DCL) através de conexões segregadas?

🔴 1. A Abordagem Manual (Superusuário Único na Aplicação)

No modelo amador, a aplicação inteira (inclusive rotas públicas de consulta) conecta como root ou postgres. Se houver uma falha de SQL Injection, o invasor tem poder de DROP DATABASE:

# ❌ FALHA GRAVE DE SEGURANÇA: Aplicação web usando superusuário para tudo
DATABASE_URL = "postgresql://postgres:root_password@localhost:5432/tecpro"

🟢 2. A Abordagem com SQLAlchemy 2.0 (Segregação de Privilégios Read-Only e Read-Write)

Com o SQLAlchemy 2.0, segregamos as fontes de conexão: relatórios e painéis de BI conectam através de um usuário restrito (analista_leitura com apenas GRANT SELECT), enquanto rotas transacionais usam credenciais DML:

# ✅ ABORDAGEM MODERNA COM SQLALCHEMY 2.0: Segregação de Motores (Read Replicas)
from sqlalchemy import create_engine, String, Integer, select, text
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, Session

class Base(DeclarativeBase):
    pass

class LogAuditoriaDclModel(Base):
    __tablename__ = "logs_auditoria_dcl"

    id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    evento: Mapped[str] = mapped_column(String(100), nullable=False)
    usuario_origem: Mapped[str] = mapped_column(String(50), nullable=False)

    def __repr__(self) -> str:
        return f"AuditLog(id={self.id}, evento='{self.evento}', user='{self.usuario_origem}')"

🛠️ Mini-Projeto 17 (BD): Gestor de Conexões com Segregação DCL

Objetivo: Implementar um roteador de sessões que impede operações de escrita em sessões configuradas para leitura analítica.

📋 Pré-requisitos e Instalação

No terminal do seu ambiente virtual (PowerShell ou Bash), instale a biblioteca necessária:

pip install sqlalchemy

💻 Código Completo e Autocontido (miniprojeto_17_dcl.py)

Crie o arquivo miniprojeto_17_dcl.py e insira o código abaixo integralmente:

"""
Mini-Projeto 17: Gestor de Conexões com Segregação DCL
Curso: GTI - Banco de Dados Relacionais e Engenharia de Software
Stack: Python 3.11+ | SQLAlchemy 2.0 | SQLite
"""
import os
from sqlalchemy import create_engine, String, Integer, select
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, Session

# 1. Definição Declarativa do Schema de Auditoria
class Base(DeclarativeBase):
    pass

class LogAuditoriaDclModel(Base):
    __tablename__ = "logs_auditoria_dcl"

    id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    evento: Mapped[str] = mapped_column(String(100), nullable=False)
    usuario_origem: Mapped[str] = mapped_column(String(50), nullable=False)

    def __repr__(self) -> str:
        return f"AuditLog(id={self.id}, evento='{self.evento}', user='{self.usuario_origem}')"

# 2. Roteador de Permissões DCL (Read / Write Segregation)
class SecurityRoutingManager:
    def __init__(self, db_url: str) -> None:
        self.engine_write = create_engine(db_url, echo=False)
        self.engine_read = create_engine(db_url, echo=False)  # Em produção: aponta para Read Replica

    def gravar_log_transacional(self, evento: str, usuario: str) -> None:
        with Session(self.engine_write) as session:
            novo_log = LogAuditoriaDclModel(evento=evento, usuario_origem=usuario)
            session.add(novo_log)
            session.commit()
            print(f"✍️ [DCL-WRITE] Log registrado com sucesso pelo usuário '{usuario}'.")

    def consultar_logs_auditoria(self) -> list[LogAuditoriaDclModel]:
        with Session(self.engine_read) as session:
            print("👁️ [DCL-READ] Executando consulta somente-leitura (GRANT SELECT):")
            return list(session.scalars(select(LogAuditoriaDclModel)).all())

# 3. Ponto de Entrada Executável
if __name__ == "__main__":
    DB_FILE = "tecpro_dcl_demo.db"

    # Reset preventivo para garantir idempotência em testes repetidos
    if os.path.exists(DB_FILE):
        os.remove(DB_FILE)

    db_path = f"sqlite:///{DB_FILE}"
    manager = SecurityRoutingManager(db_path)
    Base.metadata.create_all(bind=manager.engine_write)

    print("🔐 GESTOR DE SEGURANÇA E PRIVILÉGIOS DCL - TECPROEXPRESS")
    print("=" * 65)

    # 1. Escrita Transacional (Permissão DML)
    manager.gravar_log_transacional("LOGIN_EFETUADO", "carlos.silva")
    manager.gravar_log_transacional("ALTERACAO_SENHA", "maria.souza")

    # 2. Leitura Analítica (Permissão DQL / Read-Only)
    logs = manager.consultar_logs_auditoria()
    for log in logs:
        print(f"   • {log}")

    print("=" * 65)

🚀 Como Executar

Execute o script no terminal:

python miniprojeto_17_dcl.py

🖥️ Saída Esperada no Console

🔐 GESTOR DE SEGURANÇA E PRIVILÉGIOS DCL - TECPROEXPRESS
=================================================================
✍️ [DCL-WRITE] Log registrado com sucesso pelo usuário 'carlos.silva'.
✍️ [DCL-WRITE] Log registrado com sucesso pelo usuário 'maria.souza'.
👁️ [DCL-READ] Executando consulta somente-leitura (GRANT SELECT):
   • AuditLog(id=1, evento='LOGIN_EFETUADO', user='carlos.silva')
   • AuditLog(id=2, evento='ALTERACAO_SENHA', user='maria.souza')
=================================================================

💡 Checkpoint de Lógica

Dica

Dica Final: Todo problema complexo pode ser decomposto em partes menores. Use essa técnica e aplique as ferramentas assimiladas. 🧠🛡️



🧪 Quiz de Fixação e Autoavaliação — Capítulo 17

1. O que determina o 'Princípio do Menor Privilégio' na segurança de bancos de dados corporativos?

  • A) O usuário da aplicação backend (ex: app_backend_user) deve receber apenas as permissões estritamente necessárias para sua operação (como SELECT, INSERT, UPDATE, DELETE), NUNCA recebendo permissões de superusuário ou privilégios destrutivos de DDL como DROP DATABASE.
  • B) Todos os desenvolvedores devem usar a mesma senha 'root'.
  • C) Os clientes devem ter acesso direto ao terminal do banco de dados.
  • D) O banco de dados deve ficar sem senha para facilitar os testes.
💡 Ver Resposta e Justificativa

Resposta Correta: A
Justificativa: Se uma aplicação web sofrer invasão por injeção SQL, um usuário com menor privilégio impede que o invasor apague tabelas (DROP TABLE) ou roube dados de outros schemas.


2. Qual comando SQL é utilizado para conceder permissão de leitura sobre a tabela de clientes para o grupo de analistas?

  • A) PERMIT SELECT ON clientes TO analistas;
  • B) GRANT SELECT ON TABLE clientes TO role_analistas;
  • C) ALLOW READ clientes GROUP analistas;
  • D) SET PERMISSION clientes = TRUE;
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: Comandos DCL padrão: GRANT [privilégios] ON [objeto] TO [usuário/role]; e REVOKE [privilégios] ON [objeto] FROM [usuário/role];.


3. Por que em produção corporativa NÃO se deve usar Base.metadata.create_all(bind=engine) para atualizar tabelas existentes?

  • A) Porque o comando apaga todos os dados do banco.
  • B) Porque o create_all() apenas cria tabelas novas que não existem; ele NÃO altera colunas, não renomeia campos e não cria novas constraints em tabelas já criadas. Para evolução contínua, deve-se usar ferramentas de Migration como o Alembic.
  • C) Porque o comando só funciona em computadores Linux.
  • D) Porque o comando consome toda a memória RAM.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: Ferramentas de migração (como Alembic ou Flyway) versionam cada alteração de schema em scripts (upgrade() e downgrade()), permitindo evolução segura e rastreável.


🎯 Laboratório Prático

Coloque este conhecimento em prática agora mesmo executando o roteiro autoguiado:
👉 ATIVIDADE 19: SCHEMA MIGRATIONS

🍃 CAPÍTULO 18: NOSQL E BANCOS ORIENTADOS A DOCUMENTOS (MONGODB)


🎯 Objetivos de Aprendizagem

Ao final deste capítulo (estimativa: 2 horas de estudo autoguiado), você será capaz de:

  • 🔹 Compreender a taxonomia dos bancos NoSQL (Documentos, Chave-Valor, Colunares e Grafos) e o Teorema CAP (Consistência, Disponibilidade e Tolerância a Partições).
  • 🔹 Modelar coleções no MongoDB utilizando BSON (Binary JSON), identificando quando Embutir (Embed) vs Referenciar (Reference).
  • 🔹 Construir pipelines de agregação avançados com $match, $group, $project, $sort e $unwind.
  • 🔹 Integrar persistência poliglota no Python utilizando pymongo e Flask.

🏢 O Cenário Corporativo (Seu Desafio)

A TecProExpress iniciou a coleta massiva de telemetria e catálogos semiestruturados. Seu papel é modelar coleções flexíveis em MongoDB com alta escalabilidade horizontal.


Bem-vindo(a) à fronteira atual da Engenharia de Dados corporativa. A partir de agora, expandiremos a sua mente arquitetural para além do clássico paradigma relacional.


Objetivo: Compreender as motivações essenciais que deram origem ao movimento NoSQL, suas diferenças arquiteturais em relação aos sistemas RDBMS, e o impacto estratégico do Teorema CAP na concepção de sistemas distribuídos de escala global.


🧠 Fundamentos: NoSQL e Bancos Orientados a Documentos (MongoDB) A Origem do Paradigma "Not Only SQL"

Por mais de três décadas, bancos como PostgreSQL e SQL Server dominaram absolutos. Contudo, com o advento das Redes Sociais, da Internet das Coisas (IoT) e de volumes na casa dos Petabytes, o modelo relacional rígido sofreu impactos em escalabilidade.

Surgiram então os bancos de dados não-relacionais (NoSQL), priorizando velocidade massiva de leitura/escrita e esquemas flexíveis. Atualmente, o termo é amplamente aceito como Not Only SQL (Não Apenas SQL), indicando que ele complementa o relacional, e não tenta substitui-lo de forma arrogante.


🔹 PASSO 2: O Desafio Estratégico do Teorema CAP

Ao abandonarmos um único servidor potente e passarmos a rodar bancos de dados cruzando oceanos em centenas de máquinas (clusters), esbarramos numa lei física da engenharia de computação postulada por Eric Brewer: O Teorema CAP.

Para qualquer banco de dados distribuído de grande escala, é matematicamente impossível garantir simultaneamente as 3 propriedades abaixo:

  1. C (Consistência): Todos os clientes enxergam a mesma informação simultaneamente.
  2. A (Disponibilidade): O sistema responde sempre, mesmo que dados estejam desatualizados.
  3. P (Tolerância à Partição): O sistema sobrevive se um cabo de rede for cortado entre o Brasil e o Japão.

🔹 Balanço Arquitetural (Teorema CAP)

flowchart TD
    CAP["⚖️ Teorema CAP"]
    CAP -->|CP| MGB["MongoDB / HBase"]
    CAP -->|AP| CAS["Cassandra / DynamoDB"]
    CAP -->|CA| RDB["PostgreSQL / MySQL"]

    subgraph CP_GRP ["Prioridade CP"]
        direction TB
        MGB_info["Garante Consistência<br/>Resiste a quedas de rede<br/>Mas pode falhar na Disponibilidade"]
    end
    
    subgraph AP_GRP ["Prioridade AP"]
        direction TB
        CAS_info["Sempre Disponível<br/>Resiste a quedas de rede<br/>Consistência eventual"]
    end

🔹 Atenção: Em sistemas complexos nativos da nuvem (Cloud Native), as "Partições" (quedas de comunicação entre os hubs nos EUA e Europa, por exemplo) não são probabilidades, são certezas. Portanto, você deve escolher na realidade entre CP ou AP.


🔹 PASSO 3: Por que o MongoDB 7.0 LTS?

Dentre de dezenas de modalidades de sistemas "NoSQL", o MongoDB domina a vasta maioria dos cenários contemporâneos.

  1. Arquitetura baseada em Documentos: Os dados não são forçados em matrizes bi-dimensionais (tabelas e colunas). Eles fluem de forma hierárquica usando BSON (Uma versão binária de hiper-performance do JSON).
  2. Schema Dinâmico (Flexible Schema): Um usuário pode ter um endereço com número de casa e outro usuário com lote, andar de apartamento, tudo sob a mesma entidade sem precisar gerar "colunas nulas".
  3. Filosofia CP: Ele prioriza a Consistência dos dados acima de tudo através do sistema elegante do seu "Replica Set".

🔹 Nota do Arquiteto: Você não precisa escolher entre relacional e NoSQL. A arquitetura corporativa moderna utiliza a Persistência Poliglota: Usar PostgreSQL 🔹 para auditoria financeira e MongoDB 🔹 para a navegação acelerada de um feed infinito.


🗺️ MODELAGEM ORIENTADA A DOCUMENTOS

Se na engenharia SQL nós dividimos e isolamos os dados em dezenas de tabelas (Normalização), na engenharia Documental aplicamos frequentemente a fusão das informações vitais num pilar estratégico único.


Objetivo: Diferenciar as abordagens de Modelagem Relacional da Abordagem Documental do MongoDB 7.0, compreendendo os conceitos de Embedding, Referencing e Documentos Aninhados.


🔹 PASSO 1: O Paradigma do Preço Computacional

Em sistemas RDBMS massivos, o JOIN computacional pode ser dispendioso. No MongoDB 7.0 (arquitetura BSON), se um Cliente possui "Endereços" e "Telefones", qual a forma mais rápida de exibi-los? Guardando tudo junto do Cliente!

O ato de acoplar sub-detalhes dentro do seu registro primário recebe o nome acadêmico de Embedding (Documento Incorporado).




🔹 PASSO 2: Embedding vs DBRefs (Referências Virtuais)

Devemos analisar a necessidade de arquitetura com precisão lógica:

  1. 📖 Embedding (Guardar Junto): Acessa tudo numa pancada de I/O em tela. Excelente para relacionamentos 1:1 e relacionamentos 1:N onde "N" é um número controlado.
  2. 📖 References (Chavear/Apontar): Guarda apenas o _id do documento equivalente (Similar ao modelo Foreign Key - FK do relacional). Obrigatório se envolver um crescimento contínuo e infinito de registros M:N.

🔹 PASSO 3: Diagrama de Entidade-Documento (Mermaid Híbrido)

A notação de Chen (losangos e elipses) perde eficiência diante de estruturas fortemente encadeadas (Arrays/Objetos profundos).

A notação didática da escola de inteligência de dados sugere fundir o MER (Entidade-Relacionamento) utilizando Tipos JSON explícitos (Array/Object) e detalhando no modelo do conector Mermaid a natureza da associação (EMBEDDED vs REFERENCE):

🔹 Modelo Híbrido Didático (Composição MongoDB)

erDiagram    
    CLIENTE ||--o{ EMBEDDED_ENDERECO : "EMBEDDED (JSON)"
    CLIENTE ||--o{ COMPRA : "REFERENCE (DBRef)"
    
    CLIENTE {
        ObjectId _id PK
        String nome
        String email
        Array~String~ telefones
        Array~Object~ enderecos
        Document metadados
    }
    
    EMBEDDED_ENDERECO {
        String logradouro
        String cidade
        String uf
    }
    
    COMPRA {
        ObjectId _id PK
        ObjectId cliente_id FK
        Decimal128 valor_total
        Date criado_em
    }

🔹 Atenção (Rigidez Tecnológica): Na Engenharia MongoDB v7.0, todos os documentos ganham compulsoriamente um identificador único indexado de alta performance chamado _id (do tipo ObjectId, equivalente conceitual ao UUID relacional).


🔹 PASSO 4: Regra Prática de Engenharia de Sistemas (Desempenho)

A resposta da pergunta clássica moderna — "Qual abordagem usar no Mongo?"

  • Você tem um PostBlog que sempre precisa exibir uma miniatura de perfil do Autor (nome e foto_url)? Faça Embedding dos metadados essenciais.
  • Você tem um PostBlog que precisa registrar visualizações de milhares de máquinas (Analytics)? O array explodirá muito rápido! Use obrigatoriamente Referencing (DBRefs) via IDs para uma collection de logs secundária separada.

🔹 Dica de Engenheiro de Dados: A normalização (1NF, 2NF, 3NF) foi projetada historicamente para salvar espaço de disco (que era caro em 1980 na matriz do sistema bancário). Hoje o disco é infinitamente barato comparado ao custo de se demorar 2 minutos aguardando relatórios em RAM para a máquina analítica de negócio (Analytics). Priorize sempre a velocidade das "QueryReads"!


⚙️ OPERAÇÕES CRUD MODERNAS E SINTAXE MQL

Tendo compreendido que o NoSQL é focado em matrizes declarativas flexíveis (Documentos BSON), você deve abandonar a mentalidade rígida do SQL em prol da versatilidade ágil do MQL (MongoDB Query Language).


Objetivo: Diferenciar o comportamento transacional do SQL para a manipulação rápida de documentos usando funções Javascript assíncronas no motor V8 do MongoDB 7.0 (via mongosh e Compass).


🔹 PASSO 1: Estrutura Base (Collection MQL)

Em ambientes RDBMS (🐘 PostgreSQL), as faturas dependem dos produtos, os telefones da tabela dependente e todos são fortemente tipados.

No MongoDB 7.0, os dados residem numa Collection. Ao invés de Insert, Select, Update e Delete monolíticos, o MongoDB padronizou seus vetores via métodos tipificados JavaScript.


🔹 PASSO 2: INSERT (Create)

A função insertOne() e insertMany() recebem objetos literais estruturados e aceitam metadados não tipados (JSON arrays nativos):

/* INSERINDO O CLIENTE COM ARRAY (TELEFONES) NA COLLECTION DE "USUARIOS" */
db.usuarios.insertOne({
    nome: "Linus Torvalds",
    email: "linus@kernel.org",
    idade: 54,
    telefones: ["55-8888-1234", "55-9999-4321"],
    endereco: {
        logradouro: "Rua Open Source, 101",
        cep: "1001-500"
    },
    status_assinatura: "Ativa"
});

🔹 PASSO 3: FIND (Read)

No MQL, o SELECT * FROM TABELA cede espaço à abstração mais veloz e paramétrica da busca por expressões regulares e encadeamentos ($gt, $in e propriedades dot.notation):

/* BUSCANDO CLI COM MAIS DE 40 ANOS (Gt = Greater Than) */
db.usuarios.find({ idade: { $gt: 40 } });

/* BUSCAR CLIENTE PELO LUGAR NIDIFICADO (DOT NOTATION) */
db.usuarios.find({ "endereco.cep": "1001-500" });

🔹 PASSO 4: UPDATE (Update Parcial BSON)

Se errar a lógica do comando e submeter o nó inteiro em vez de apenas o parâmetro usando a instrução chave de atualização (O operador set), o arquivo JSON é sobrescrito:

/* O OPERADOR $SET SÓ ALTERA A CHAVE SELECIONADA. SE NÃO LHE INSTRUIR, ELE APAGA TODO RESTO! */
db.usuarios.updateOne(
    { email: "linus@kernel.org" }, 
    { $set: { status_assinatura: "Suspensa" } }
);

/* O OPERADOR $PUSH INSERE UM NOVO ITEM NO FINAL DE UM ARRAY */
db.usuarios.updateOne(
    { nome: "Linus Torvalds" }, 
    { $push: { telefones: "00-0000-0000" } }
);

🔹 PASSO 5: DELETE (Removendo O Documento Inteiro)

/* Exclui o primeiro documento encontrado que tem menos de 18 anos */
db.usuarios.deleteOne({ idade: { $lt: 18 } });

🔹 Diferença de Dialeto SQL vs MQL: No PostgreSQL 17, UPDATE table SET campo = valor atinge a tabela inteira se esquecer o WHERE. No MongoDB, o updateOne é cirúrgico e trava na primeira correspondência, evitando acidentes nucleares de DBA.


⚙️ AGGREGATION FRAMEWORK E PIPELINE

Se o find() te leva de 0 a 100 na abstração rápida, o Aggregation Framework te eleva de 100 a 1000, dominando qualquer Business Intelligence complexo.


Objetivo: Extrair poder máximo de relatórios cruzados (Análises, Filtros de Etapas, Projetos de Sub-campos Arrays, Group By nativo MQL).


🔹 PASSO 1: A Arquitetura do Operador de Tubulação (Pipeline)

No relacional (SQL), a leitura da CPU e Ram do servidor obedece à linearidade estrita SELECT, FROM, WHERE, GROUP BY, HAVING. Na matemática NoSQL distribuída, os dados massivos desistem das tabelas únicas, processando blocos e empurrando o resultado via "Pipeline" (Em lotes de estágios) para que uma máquina minúscula aguente trabalhar grandes Gigabytes.

🔹 Ciclo de Estágios do Pipeline do BSON

flowchart LR
    M1["🔍 1. $match"] --> G1["👥 2. $group"]
    G1 --> P1["📊 3. $project"]
    P1 --> S1["🔀 4. $sort"]
  • $match: Filtra como o clássico WHERE e joga metade dos documentos no Lixo cedo (economiza Ram).
  • $group: O clássico GROUP BY e agregadores de soma (SUM, AVG).
  • $project: Modela quais atributos a aplicação cliente receberá (Economiza Banda/Tráfego de Internet).
  • $sort: Põe os dados em Ordem (ORDER BY).

🔹 PASSO 2: Anatomia de Extração no MongoDB 7.0

Como o engenheiro descobre "Quantidade de Dinheiro da Filial SP nos últimos 7 dias?".

db.faturas.aggregate([
    
    // 1º ETAPA DO CANO: CORTA O QUE NÃO FOR DE SÃO PAULO
    { 
       $match: { filial: "SP", ano: 2026 } 
    },
    
    // 2º ETAPA: JUNTA TODOS QUE SOBRARAM SOMANDO A FATURA E AGRUPANDO PELO VENDEDOR
    { 
       $group: { 
           _id: "$vendedor", 
           faturamento_total: { $sum: "$valor_liquido" },
           total_vendas: { $sum: 1 } 
       } 
    },
    
    // 3º ETAPA: ORDENANDO O MELHOR FATURAMENTO
    {
       $sort: { faturamento_total: -1 } // -1 = DESCENDING
    }

]);

🔹 PASSO 3: Cross-Joins $lookup do MQL (DBRefs Profundos)

A ideia de que "MongoDB e NoSQL não faz Join" é Fake News Arquitetônica. Através do estágio $lookup da Pipeline moderna (> MongoDB 6.0+), unimos Arrays nativamente:

/* BUSCANDO PEDIDO E SUAS REFERÊNCIAS DE FATURA EXCLUSIVA */
db.pedidos.aggregate([
   {
     $lookup: {
       from: "detalhesFatura",   // Qual a "tabela" secundária 
       localField: "faturaId",   // Onde está o ID nesse Documento A
       foreignField: "_id",      // Onde está o ID no Documento B (Fatura)
       as: "faturaCorrigida"     // Qual será o nome do Objeto Inserido Array
     }
  }
]);

🔹 Nota do DBA Master: Assim que uma Collection cresce com velocidade, usar $lookup esgota a CPU (ele compara 1 doc contra milhares toda hora). Se precisar buscar juntas sempre, mude a estrutura do projeto da Empresa e faça EMBEDDING JSON direto!


⚡ PERFORMANCE: ÍNDICES E SCHEMA ANALYSIS

Como engenheiro estrito do NoSQL, não há compilação linear como no RDBMS, há verificação de cache e leituras contínuas (I/O). Se a coleção da empresa tem milhões de usuários, um db.usuarios.find({ status: "Ativo" }) fará uma verificação um por um no HD (Collection Scan).


Objetivo: Explorar os mecanismos nativos de performance visual do Compass v1.4x, criando Índices B-Tree eficientes para evitar Gargalos de I/O em produções NoSQL de larga escala.


🔹 PASSO 1: Índices Básicos via Shell

Um índice empacota metadados de acesso (Ponteiros Lógicos) na RAM e os ordena (ASC/DESC). No MQL, adicionamos performance com precisão em Campos Filhos de forma fluída.

/* CRIANDO O MAPEAMENTO RÁPIDO PARA ORDENAR IDADES DOS CLIENTES (-1 para Z-A Descendente) */
db.usuarios.createIndex({ idade: -1 });

/* ÍNDICES DE ARRAYS INTEIROS (Multikey Index) */
db.loja.createIndex({ "categorias_produto": 1 });

🔹 PASSO 2: O Poder do Compass (Visual Schema Analysis)

Diferente do SQL (onde você sabe na vírgula quais são os campos tipados de uma Tabela Fixa), no MongoDB 7.0 um documento pode ser livre de Schema ("Schemaless"). Então, como descobrir as chaves de 1 milhão de Jsons diferentes salvos lá dentro em um servidor AWS? Através do Compass!

  1. Aba Schema: No MongoDB Compass, abra a Collection e vá na aba Schema.
  2. Analyze (Amostragem Rápida): Clique em Analyze Schema. O Compass irá varrer alguns milhares de BSONs originais e exibir um Gráfico de Barras indicando que:
    • 80% dos documentos possuem a chave data_criacao.
    • Os 20% restantes possuem um createdAt (Sujeira do legado NoSQL).
  3. Tome a Decisão: Faça correções com $set ou indexe ambos se for um Legacy Bank.

🔹 PASSO 3: Identificando Lerdeza (.explain())

Mesmo com Índices, como provar se uma Query está utilizando ele de verdade (Sendo performática) ou lendo TUDO na marra (COLLSCAN)?

/* O EXPLAIN() MOSTRARÁ OS BASTIDORES DO MOTOR (PLANO DE EXECUÇÃO), TEMPO DE MILISEGUNDOS, ETC */
db.faturas.find({ filial: "SP" }).explain("executionStats");

O que Focar na Resposta MQL?

  • executionTimeMillis: Tempo (Menos é Mais).
  • totalDocsExamined: Quantos Documentos ele puxou para analisar um a um? Se deu 10.000 e ele retornou apenas 50 resultados reais, seu servidor está processando e apagando o excedente do Lixo na RAM por falta flagrante de Índice exato!

🔹 Dica de Infra: Se um índice consome 2 Gigabytes da Ram, o motor do MongoDB se sente à vontade para expulsar a sua base diária principal pro SwapHD limitando a velocidade. Analise e Crie índices apenas para os campos "Mais Buscados do WHERE e ORDER BY". Não construa Índices baseados na esperança do Cliente!


🏁 CONSIDERAÇÕES FINAIS E DESAFIOS: UNIDADE VI (NOSQL)

O Engenheiro Poliglota não teme o paradigma Não-Relacional. Ele sabe que a escolha da tecnologia deve pender para o melhor benefício da Aplicação (App) da Empresa, e não para preferências literais de uma década.


Objetivo: Formatar o domínio lógico recém-adquirido entre os paradigmas Relacionais (SQL) e Orientados a Documento (MQL), mesclando uma lista robusta de avaliações Múltipla Escolha e Desafios Práticos com resoluções explicativas.


📋 Lista de Exercícios: Questões Objetivas

1. Sob a visão estratégica do Teorema CAP, a Arquitetura do MongoDB prioriza qual das combinações em sua entrega de Distribuição Padrão? a) AP (Disponibilidade contínua acima de Consistência). b) AC (Consistência Forte em um Único Servidor). c) CP (Consistência Constante e Tolerância a Partição de Redes). d) CA (Alta Disponibilidade com Particionamento Relacional).

2. Na Arquitetura MongoDB (NoSQL de Documentos), qual o maior motivador para a Abordagem Didática de Embedding (Fusão de Documentos)? a) Fazer com que o campo ocupe o mínimo tamanho no disco rígido do servidor. b) Simular tabelas fixas Relacionais. c) Facilitar os $lookups no motor V8 de MapReduce. d) Reduzir a latência do aplicativo, centralizando a massa de metadados em uma única operação de Leitura (I/O).

3. Por que o Operador de Pipeline da Collection MongoDB inicia, em 99% das vezes Profissionais (Escala Analítica), com a Etapa $match na Função de Aggregations? a) Porque sem Ordenação Linear não existe Agrupamento. b) Para diminuir o peso final do tráfego JSON pela internet. c) Para cortar imediatamente da Memória RAM os documentos inúteis à pergunta, agilizando os cruzamentos das fases subsequentes. d) O $match apenas renomeia os campos internos cruzando referências.

4. Em Operações CRUD MQL, qual o comportamento imediato de proteção ao utilizar a função updateOne() (com os parâmetros corretos de filtro e $set), em comparação direta a um UPDATE equivalente no SQL Padrão? a) O MQL atualizará todos os documentos por padrão, igual ao SQL sem WHERE. b) O MQL travará na primeira correspondência encontrada, protegendo a base de adulterações acidentais em massa. c) O MQL criará uma nova coleção de backup temporária. d) O MQL deletará o documento caso o $set inclua campos nulos.


📋 Lista de Desafios Práticos MongoDB 7.0

Desafio 1: Inserção Multi-nível (O CRUD Moderno)

Você foi encarregado de modelar a inserção de um Aluno na nova plataforma NoSQL do MEC. O Aluno possui RG, Nome e concluiu dois cursos de tecnologia em anos distintos.

  • Tarefa: Crie o comando em MQL que insira este único documento na collection estudantes, garantindo que o histórico de cursos seja Embutido (Array de Objetos).

Desafio 2: Análise de Performance Visual

Após subir uma collection de Pedidos_Ecommerce com 1 milhão de vendas, o seu aplicativo web começa a congelar (Timeout) na tela de "Pedidos do Vendedor Z".

  • Tarefa: Explique como utilizar as ferramentas embutidas (db.collection.explain() ou Compass) e quais métricas chaves ler para comprovar a falta de índices e resolver o estrangulamento.

Desafio 3: O Paradigma MQL Translator (Refatoração Analítica)

O sistema antigo em PostgreSQL possuía um relatório gerencial que avaliava o total arrecadado das vendas feitas fisicamente na loja ('POS'):

SELECT VENDEDOR_NOME, 
       SUM(VALOR_TOTAL) AS ARRECADACAO 
FROM VENDAS 
WHERE TIPO_VENDA = 'POS' 
GROUP BY VENDEDOR_NOME 
ORDER BY ARRECADACAO DESC;
  • Tarefa: Reescreva esse comportamento mental do administrador para a abordagem de Aggregation Pipeline do MongoDB. Traduza linha a linha o sentido do motor de dados.

📖 Clique aqui para revelar os Gabaritos e Soluções Detalhadas 🔹

✅ Gabarito e Justificativas das Questões Objetivas:

  1. Letra C. Justificativa: Em clusters corporativos, partições de rede são inevitáveis. O MongoDB escolhe "Ocultar o Errado Mapeado" protegendo a Consistência (CP), em vez da disponibilidade irrestrita de dados corrompidos.
  2. Letra D. Justificativa: Evitar pulular entre Registros (JOINs de disco) é fundamental. Quando a UI precisa de "Perfil Completo + Endereço", buscar do mesmo cilindro (Embedding) destrói as latências clássicas de IOPS.
  3. Letra C. Justificativa: A memória RAM do banco é preciosa. Se o Motor não filtra e joga o "Lixo" fora logo na primeira linha de execução ($match), a RAM esgota rapidamente ao entrar na etapa de Agrupamento ($group) com dados irrelevantes.
  4. Letra B. Justificativa: Sistemas Node.JS / Python muitas vezes executam operações genéricas. Ter métodos estanques como o updateOne é a evolução máxima do Safe Design comparado à perigosa instrução genérica UPDATE do SQL ANSI.

🔹 Solução Detalhada dos Desafios:

Solução Desafio 1 (Inserção de Embedding): Uma das vantagens cruciais do MongoDB é não criar "Tabela de Histórico de Cursos" atrelada ao Cliente por FK. Tudo flui de forma atômica no JSON.

db.estudantes.insertOne({
    rg: "123.456.789",
    nome: "Ricardo Machado",
    historico_cursos: [
        { nome: "Arquitetura Python", ano_conclusao: 2024 },
        { nome: "SQL Transacional", ano_conclusao: 2025 }
    ]
});

Solução Desafio 2 (Análise de Índice - Profiling): O gargalo é o clássico COLLSCAN (Varredura de Coleção Inteira). A justificativa prática do analista seria:

  1. No Shell da AWS / Servidor, eu faria: db.Pedidos_Ecommerce.find({ vendedor: "Vendedor Z" }).explain("executionStats").
  2. A métrica totalDocsExamined provavelmente mostraria 1 Milhão.
  3. A métrica nReturned mostraria a realidade (ex: 50 pedidos).
  4. Analisando a discrepância (Ler 1M e Devolver 50), o banco teve alto estrangulamento de CPU. Solução: Engatar imediatamente um db.Pedidos_Ecommerce.createIndex({ vendedor: 1 }).

Solução Desafio 3 (Tradução para MQL Pipeline): No MongoDB, o Pipeline "quebra" as instruções relativas (SQL Linear) empurrando a massa residual do Topo para a Base.

db.vendas.aggregate([
    // 1ª Etapa (WHERE SQL): Corta tudo da RAM que NÃO vier do canal físico (POS).
    { $match: { tipo_venda: "POS" } },

    // 2ª Etapa (GROUP/SUM): Agrupa a massa sobrevivente criando o novo eixo virtual.
    { $group: {
        _id: "$vendedor_nome",                   // O EIXO Agrupador
        arrecadacao: { $sum: "$valor_total" }    // O Acumulador
    }},

    // 3ª Etapa (ORDER BY DESC): Ordena o novo objeto finalizado
    { $sort: { arrecadacao: -1 } }
]);

💻 Ponte Prática: Do SQL Relacional ao NoSQL com PyMongo

Como a Engenharia de Dados consome o MongoDB e documentos BSON através do Python?

🔴 1. A Abordagem Relacional (JOINs Excessivos para Dados Heterogêneos)

No modelo relacional tradicional, armazenar telemetrias de sensores IoT exige criar dezenas de colunas com valor NULL ou fragmentar em 4 tabelas conectadas por JOIN:

-- ❌ MODELO RELACIONAL RÍGIDO: JOINs pesados para montar o objeto de telemetria
SELECT s.id, s.modelo, l.temperatura, l.vibracao, l.timestamp
FROM sensores s
JOIN logs_telemetria l ON s.id = l.sensor_id;

🟢 2. A Abordagem Documental com PyMongo (Embedding e Aggregation Pipeline)

No MongoDB com PyMongo, o documento BSON é manipulado diretamente como um dict do Python, com listas e objetos aninhados (Embedding):

# ✅ ABORDAGEM MODERNA: Inserção de Documentos Hierárquicos e Agregação
from datetime import datetime, timezone
from typing import Any

class SensorLogDocument:
    @staticmethod
    def criar_payload_telemetria(caminhao_id: str, gps_lat: float, gps_long: float, temp_carga: float) -> dict[str, Any]:
        return {
            "caminhao_id": caminhao_id,
            "timestamp": datetime.now(timezone.utc).isoformat(),
            "localizacao": {
                "tipo": "Point",
                "coordenadas": [gps_long, gps_lat]
            },
            "sensores": {
                "temperatura_celsius": temp_carga,
                "pressao_pneus_psi": [32.1, 32.0, 31.9, 32.2]
            }
        }

🛠️ Mini-Projeto 18 (BD): Armazenador de Telemetria e Pipeline de Agregação

Objetivo: Construir um coletor de telemetria documental em Python e processar médias agregadas via Pipeline NoSQL.

📋 Pré-requisitos e Instalação

O código abaixo utiliza apenas bibliotecas nativas do Python 3.11+ (com simulador em memória de pipeline BSON compatível com PyMongo). Para conectar a uma instância real do MongoDB, bastaria instalar:

pip install pymongo

💻 Código Completo e Autocontido (miniprojeto_18_mongodb.py)

Crie o arquivo miniprojeto_18_mongodb.py e insira o código abaixo integralmente:

"""
Mini-Projeto 18: Armazenador de Telemetria e Pipeline de Agregação
Curso: GTI - Banco de Dados Relacionais e Engenharia de Software
Stack: Python 3.11+ | NoSQL Documental / BSON Simulado
"""
from datetime import datetime, timezone
from typing import Any

# 1. Gerador de Documentos BSON com Subdocumentos e Arrays Embutidos
class SensorLogDocument:
    @staticmethod
    def criar_payload_telemetria(caminhao_id: str, gps_lat: float, gps_long: float, temp_carga: float) -> dict[str, Any]:
        return {
            "caminhao_id": caminhao_id,
            "timestamp": datetime.now(timezone.utc).isoformat(),
            "localizacao": {
                "tipo": "Point",
                "coordenadas": [gps_long, gps_lat]
            },
            "sensores": {
                "temperatura_celsius": temp_carga,
                "pressao_pneus_psi": [32.1, 32.0, 31.9, 32.2]
            }
        }

# 2. Simulador de Collection NoSQL e Aggregation Pipeline
class MockMongoCollection:
    """Simulador em memória da API nativa do PyMongo (MongoClient)."""
    def __init__(self) -> None:
        self.documentos: list[dict[str, Any]] = []

    def insert_many(self, docs: list[dict[str, Any]]) -> None:
        self.documentos.extend(docs)

    def aggregate(self, pipeline: list[dict[str, Any]]) -> list[dict[str, Any]]:
        """Interpreta o estágio $group do pipeline recebido (didático: cobre $group + $avg, não o MQL completo)."""
        documentos = self.documentos
        for estagio in pipeline:
            if "$group" in estagio:
                documentos = self._aplicar_group(documentos, estagio["$group"])
        return documentos

    def _aplicar_group(self, documentos: list[dict[str, Any]], especificacao: dict[str, Any]) -> list[dict[str, Any]]:
        campo_id = especificacao["_id"].lstrip("$")
        acumuladores = {chave: valor for chave, valor in especificacao.items() if chave != "_id"}

        grupos: dict[Any, list[dict[str, Any]]] = {}
        for doc in documentos:
            grupos.setdefault(doc[campo_id], []).append(doc)

        resultado = []
        for chave, docs_grupo in grupos.items():
            linha: dict[str, Any] = {"_id": chave, "total_leituras": len(docs_grupo)}
            for nome_campo, expressao in acumuladores.items():
                if "$avg" in expressao:
                    caminho = expressao["$avg"].lstrip("$").split(".")
                    valores = [self._ler_caminho(d, caminho) for d in docs_grupo]
                    linha[nome_campo] = sum(valores) / len(valores)
            resultado.append(linha)
        return resultado

    @staticmethod
    def _ler_caminho(doc: dict[str, Any], caminho: list[str]) -> Any:
        valor: Any = doc
        for chave in caminho:
            valor = valor[chave]
        return valor

# 3. Ponto de Entrada Executável
if __name__ == "__main__":
    print("🍃 COLETOR DE TELEMETRIA NOSQL MONGODB (BSON) - TECPROEXPRESS")
    print("=" * 65)

    db_telemetria = MockMongoCollection()

    # 1. Inserção de documentos com subdocumentos e arrays aninhados (Embedding)
    docs = [
        SensorLogDocument.criar_payload_telemetria("CAM-01", -23.5505, -46.6333, temp_carga=-18.5),
        SensorLogDocument.criar_payload_telemetria("CAM-01", -23.5510, -46.6340, temp_carga=-17.8),
        SensorLogDocument.criar_payload_telemetria("CAM-02", -22.9068, -43.1729, temp_carga=-15.0)
    ]
    db_telemetria.insert_many(docs)
    print("✅ 3 Documentos de Telemetria com GeoJSON e Arrays persistidos!")

    # 2. Executando Pipeline de Agregação ($group / $avg)
    pipeline = [
        {"$group": {"_id": "$caminhao_id", "temperatura_media": {"$avg": "$sensores.temperatura_celsius"}}}
    ]
    print("\n🔍 Executando Aggregation Pipeline (Média de Temperatura por Caminhão):")
    for res in db_telemetria.aggregate(pipeline):
        print(f"   🚛 Caminhão: {res['_id']} | Média Térmica: {res['temperatura_media']:.2f}°C | Leituras: {res['total_leituras']}")

    print("=" * 65)

🚀 Como Executar

Execute o script no terminal:

python miniprojeto_18_mongodb.py

🖥️ Saída Esperada no Console

🍃 COLETOR DE TELEMETRIA NOSQL MONGODB (BSON) - TECPROEXPRESS
=================================================================
✅ 3 Documentos de Telemetria com GeoJSON e Arrays persistidos!

🔍 Executando Aggregation Pipeline (Média de Temperatura por Caminhão):
   🚛 Caminhão: CAM-01 | Média Térmica: -18.15°C | Leituras: 2
   🚛 Caminhão: CAM-02 | Média Térmica: -15.00°C | Leituras: 1
=================================================================

Dica do Especialista

A Próxima Fronteira: O NoSQL não apaga o modelo tabular RDBMS, as duas engrenagens dominam o mercado global sob a ótica da Arquitetura Distribuída. Domine Ambas, o Emprego Perfeito te espera logo em seguida!


🧪 Quiz de Fixação e Autoavaliação — Capítulo 18

1. O que afirma o Teorema CAP de Eric Brewer para bancos de dados distribuídos?

  • A) Todo banco de dados deve ter pelo menos 3 cópias na nuvem.
  • B) Em um sistema distribuído sujeito a partições de rede (P), é impossível garantir simultaneamente Consistência estrita (C) e Alta Disponibilidade total (A); o arquiteto deve escolher priorizar CP ou AP.
  • C) O banco de dados relacional é sempre superior ao NoSQL.
  • D) A velocidade da internet é limitada pelo tamanho do cabo.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: Teorema CAP: diante de falhas de rede entre nós (P), bancos CP (como MongoDB) priorizam dados consistentes rejeitando escritas incertas; bancos AP (como Cassandra) priorizam responder sempre, com consistência eventual.


2. No MongoDB, qual é a principal vantagem de 'Embutir' (Embedding / Subdocumento) os itens dentro do próprio documento do Pedido em vez de normalizar em outra coleção?

  • A) Economizar memória RAM do computador.
  • B) Alta performance de leitura: o pedido completo e todos os seus itens são recuperados em exatamente UMA ÚNICA operação de I/O em disco, eliminando a necessidade de JOINs caros.
  • C) Permitir que o documento tenha tamanho infinito sem limites.
  • D) Impedir que o cliente altere o pedido.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: NoSQL orientado a documentos favorece 'dados que são lidos juntos devem ser guardados juntos', reduzindo latência e I/O de rede.


3. Qual estágio do Aggregation Pipeline do MongoDB é equivalente à cláusula GROUP BY e funções de agregação do SQL?

  • A) $match
  • B) $group
  • C) $project
  • D) $sort
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: Mapeamento do Aggregation Pipeline: $match = WHERE; $group = GROUP BY + Funções agregadoras ($sum, $avg); $project = SELECT; $sort = ORDER BY; $limit = LIMIT.


🎯 Laboratório Prático

Coloque este conhecimento em prática agora mesmo executando o roteiro autoguiado:
👉 ATIVIDADE 08: NOSQL MONGODB

🏛️ CAPÍTULO 19: APACHE CASSANDRA E BIG DATA (WIDE-COLUMN)


🎯 Objetivos de Aprendizagem

Ao final deste capítulo (estimativa: 2 horas de estudo autoguiado), você será capaz de:

  • 🔹 Compreender a arquitetura descentralizada Masterless (Peer-to-Peer) e o anel de nós (Ring Topology) do Apache Cassandra.
  • 🔹 Dominar a modelagem orientada a consultas no CQL (Cassandra Query Language), identificando Partition Keys e Clustering Columns.
  • 🔹 Analisar o conceito de Consistência Eventual e Quórum de Leitura/Escrita ($R + W > N$).
  • 🔹 Implementar tabelas colunares para ingestão de telemetria massiva e séries temporais de IoT.

🏢 O Cenário Corporativo (Seu Desafio)

A frota de rastreadores da TecProExpress gera 100.000 eventos de GPS por segundo. Seu desafio é modelar tabelas colunares no Apache Cassandra com topologia Masterless.


Bem-vindo ao universo das tabelas imensas, de leituras e gravações em nível global. O Apache Cassandra 4.x (e sua vertente em C++, o ScyllaDB) revolucionou a forma como gigantes da tecnologia absorvem dados.


Objetivo: Compreender a ruptura da arquitetura clássica Client-Server, abraçando a distribuição Descentralizada (Peer-to-Peer), o protocolo de rede Gossip e o formato estrutural do Anel Bi-Direcional (Ring).


🧠 Fundamentos: Apache Cassandra, Wide-Column e Teorema CAP O Fim do Servidor Principal (Masterless)

Na engenharia clássica do SQL (mesmo particionada), é comum existir um Servidor "Mestre" (que domina a escrita) e Servidores "Escravos" (que fazem cópia para leitura). Se o mestre cai, o banco pára de gravar até que a equipe assuma ou um escravo se promova.

O Cassandra é Masterless (Sem Mestre). Ele adota a arquitetura Peer-to-Peer originada no manifesto do Amazon Dynamo. Todas as máquinas (Nós) ligadas na mesma rede são iguais. Todas podem escrever, todas podem ler. Se um nó queima, os usuários nem percebem.


🔹 PASSO 2: A Matemática do Anel (Ring)

Os dados não ficam num nó aleatório. Eles são distribuídos através de um cálculo criptográfico (Hashes) distribuindo as fatias da pizza igualmente em um Anel Virtuall (Ring).

🔹 Análise da Arquitetura Distribuída (Cassandra Ring)

flowchart TD
    APP["📱 Sua Aplicação Node.js / Python"]

    subgraph Cluster_Cassandra ["Anel de Dados / Ring"]
        direction LR
        NO_A(("Nó Europa"))
        NO_B(("Nó Brasil"))
        NO_C(("Nó USA"))
        NO_D(("Nó Ásia"))

        NO_A <-->|Gossip| NO_B
        NO_B <-->|Gossip| NO_C
        NO_C <-->|Gossip| NO_D
        NO_D <-->|Gossip| NO_A
    end

    APP -.->|"Conecta em Qualquer Nó"| NO_B
    APP -.->|"O Nó vira Coordenador"| NO_C

📡 O Protocolo Gossip (A Fofoca)

Como o Nó do Brasil sabe que o Nó da Ásia caiu? Através do protocolo Gossip (Fofoca). A cada segundo, os nós se comunicam trocando metadados uns dos outros. "Ei, eu estou vivo, e falei com o Nó da Ásia e ele está morto há 30 segundos". Todo o anel toma consciência instintiva do estado da infraestrutura.


🔹 PASSO 3: Soluções On-Premise vs Cloud Providers

Montar servidores bare-metal pelo mundo exige engenheiros Sêniores de Redes. Portanto, a indústria foca nos Cloud Providers (DBaaS). O Cassandra brilha sob as opções Serverless:

  • DataStax Astra DB: O ambiente oficial criado pelos mantenedores do Cassandra. Gerenciamento zero para o programador, foco 100% no uso da linguagem (CQL).
  • Amazon Keyspaces (for Apache Cassandra): A réplica compatível da AWS.
  • ScyllaDB: Construído em C++, é compatível nativamente com os drivers CQL do Cassandra, oferecendo performance bruta de latência em milissegundos para casos radicais e sem o "peso" na memória exigido pelo Java.

🔹 Dica de Infra: Mesmo utilizando a Nuvem sob demanda (Astra, AWS Keyspaces), estudar o funcionamento local (Docker) ou a lógica do Ring é mandatório, pois o mau uso das chaves derruba sua aplicação inteira gerando custos imprevistos na AWS.


🗺️ MODELAGEM DE DADOS: O FIM DO JOIN

No RDBMS (Relacional), você modela as tabelas baseando-se no que será Guardado (Data-Driven). O aluno faz um MER focado primariamente na clareza (Normalização) (User, Pedido, Item) para evitar dados duplicados. Isso é lindo no Papel, mas mata o HDD.


Objetivo: Adotar a arquitetura Query-Driven Modeling (Modelagem Focada na Pergunta), compreendendo o motivo vital pelo qual o ecossistema Distribuído aboliu as amarras do RDBMS.


🔹 PASSO 1: A Morte da Teoria dos Conjuntos Analíticos

🔹 REGRA DE OURO E DE SANGUE: NÃO EXISTE JOIN NO CASSANDRA!

Se você tiver uma tabela de USUARIOS em um Nó no Japão, e a tabela de COMPRAS no Nó do Brasil, tentar realizar um SELECT ... INNER JOIN exigiria que milhões de Gigabytes subissem no cabo de fibra ótica cruzando o Oceano toda vez que alguém clicasse em "Ver meu Histórico". O Timeout quebraria a internet.

Portanto, o Cassandra abandonou a junção de servidor (JOIN). Para que as respostas sejam servidas em menos de 10 milissegundos, você precisou abolir a normalização e forçar a "Leitura Seqüencial".


🔹 PASSO 2: Mudança de Paradigma (Query-Driven)

Para desenhar o seu banco a partir de agora, você precisa seguir este exato ritual:

  1. Comece pelas Queries (Quais Perguntas a UI do seu App fará ao usuário?).
  2. Desenhe uma Tabela Específica para Cada Pergunta.

🔹 Dica de Performance: No Cassandra, duplicar dados não é pecado, é estratégia primária. Se seu App tem duas abas na interface ("Buscar por Usuário" e "Buscar por E-mail"), você não vai usar subconsultas secundárias ou Joins reversos. Você terá obrigatoriamente duas tabelas gigantes: users_by_id e users_by_email.


🔹 PASSO 3: O Custo de Escrita vs. Custo de Leitura

No mundo anterior (SQL Clássico) aprendemos: "Evite gravar o mesmo dado duas vezes senão o HD acaba e a anomalia acontece."

No ecossistema de dados distribuído de altíssima escala (Cassandra/ScyllaDB), a verdade é matemática: Escrita é Barata, Leitura Cruzada é Extremamente Cara. O Cassandra foi projetado com otimizações nativas poderosas para escrever simultaneamente em 3, 5 ou 10 tabelas em microssegundos (utilizando o CommitLog em memória nativa), mas ele detesta ter que procurar peças que faltam usando a CPU dele (o Read dele é pior sem os metadados ideais).

Então, sempre crie e use comandos síncronos da sua Linguagem (Seu backend Node, Spring Boot, etc.) e mande ele inserir o mesmo evento de Venda nas 3 tabelas específicas ao vivo na hora da compra (Desnormalização Forte).


🔑 CHAVES: PARTITION KEY VS CLUSTERING KEY

A essência de toda a magia distribuída, de por que o Cassandra alcança performance na casa dos milissegundos operando em Petabytes, jaz inteiramente no Design Matemático da sua Primary Key Composta (Chave Primária).


Objetivo: Diferenciar o comportamento físico dos discos quando criamos Partition Keys (Localizador Global de Nó) e Clustering Keys (Localizador Magnético Interno / Ordem).


🔹 PASSO 1: A Dissecção da Chave Primária CQL

Na linguagem CQL, a Primary Key base não significa "Valor Único Aleatório Auto-Increment" como estamos moldados a pensar no MySQL Clássico. Aqui, uma "Chave Primária" é o controle total sobre o Motor Físico do cluster.

Se declararmos a tabela pedidos_by_cliente:

CREATE TABLE pedidos_by_cliente (
    cliente_id UUID,
    data_compra TIMESTAMP,
    produto TEXT,
    valor DECIMAL,
    PRIMARY KEY ((cliente_id), data_compra)
);
  1. A primeira parte isolada em parênteses (cliente_id) é a Partition Key (K-Hash).
  2. A segunda parte data_compra é a Clustering Key (K-Ordem).

🔹 PASSO 2: O Agrupamento de Discos Virtuais

O papel vital dessas chaves para o desenvolvedor Cloud:

  • Partition Key (A Viagem Geográfica): Determina em GIGABYTES ONDE (em qual computador/Nó físico de Tóquio, NY ou SP) aquele registro viverá. Registros com o mesmo cliente_id cairão infalivelmente no mesmo servidor da rede.
  • Clustering Key (A Viagem Magnética): Uma vez que fomos enviados ao Nó correto de Tóquio, ela dita a Ordem exata em que as linhas serão gravadas fisicamente juntas lado a lado. Por padrão (ASC).

🔹 NÃO EXISTE JOIN NO CASSANDRA! Portanto, agrupar fisicamente (Via Partition e Clustering) os dados que você deseja resgatar na sua tela inicial se torna vitalícia a regra do engenheiro da Alta Disponibilidade.


🔹 PASSO 3: Lógica Visual do Motor (Diagrama de Chato)

Abaixo vemos as dependências matemáticas geradas pela criação exata de chaves. O Nó que reter a Partição "X" reterá todas as ordenações magnéticas vinculadas a essa "Partição K".

🔹 Modelagem Híbrida de Escala

erDiagram    
    TABELA_PEDIDOS ||--o{ REGISTRO_FISICO : "Contém (Via Partition/Clustering)"
    
    TABELA_PEDIDOS {
        UUID cliente_id "PK (Partition_Key) - Hashing Token"
        TIMESTAMP data_compra "CK (Clustering_Key) - Order ASC"
        TEXT produto "Payload Data"
        DECIMAL valor "Payload Data"
    }

    REGISTRO_FISICO {
        Disco log_sequencial_node
        Ordenamento data_crescente
    }

🔹 Dica de Engenheiro: Um "Ponto de Atenção" fatal na carreira de um Analista de Dados Distribuído! Ao criar a Query, o WHERE no Cassandra é Obrigatório e Engessado. Você não pode buscar com um WHERE data_compra = X sem ANTES dizer WHERE cliente_id = Y. Você precisa informar primeiro ao Motor Distribuído qual país ele deve ir voar (Partition), para então ele ordenar e varrer a porta.


📘 CQL BÁSICO: COLLECTIONS E TIME TO LIVE (TTL)

Ao aceitar a natureza imutável do Apache Cassandra e o "Fim do JOIN", o Cassandra Query Language (CQL) compensou essa rigidez arquitetural entregando mecanismos elegantes poderosos de inserção temporária e arrays nativos via Colecionadores.


Objetivo: Operacionalizar consultas e comandos de gravação distribuídos, explorando Tipos Complexos (List, Set, Map), O Fetiche mortal do Allow Filtering e as Exclusões Orgânicas (Tombstones e TTL).


🔹 PASSO 1: A Gravação Distribuída (Actor Model)

Sistemas Cloud escrevem metralhando milhares de Nós da forma mais brutalmente assíncrona.

🔹 Workflow das Operações Multi-Escrita

flowchart LR
    APP("📱 Aplicação Back-end") -- "INSERT INTO (Level: QUORUM)" --> COORD("🖥️ Coordinator Node")
    
    subgraph Gravacao_Ponto_Comum ["Gravação Ponto-Comum (Replica = 3)"]
        direction TB
        COORD --> N1[("💾 Escrita Nó Principal")]
        COORD --> N2[("💾 Escrita Réplica 1")]
        COORD --> N3[("💾 Escrita Réplica 2")]
    end

(O Node de contato, chamado Coordinator, orquestra magicamente o disparo da replicação global em milissegundos)


🔹 PASSO 2: Trabalhando Collections (Sem JOINs de Fato)

Ao modelar entidades Query-Driven, às vezes queremos incluir anexos menores sem precisar gerar Duplicações Extras gigantes de tabelas inteiras.

O CQL disponibiliza SET, LIST e MAP.

/* ADICIONAR CONTEÚDO EXTRA EMBUTIDO */
CREATE TABLE user_perfis (
    email text PRIMARY KEY,
    nome text,
    telefones SET<text>,          -- Lista de valores únicos
    historico LIST<timestamp>,    -- Lista ordenada aceita duplicados
    preferencias MAP<text, text>  -- Chave : Valor (Dicionário)
);

/* INSERINDO METADADOS NOSQL (Sets utilizam chaves e Maps também) */
INSERT INTO user_perfis (email, nome, telefones, preferencias)
VALUES ('ceo@empresa.com', 'Alex', {'55999999', '55888888'}, {'tema':'dark', 'alertas':'on'});

🔹 PASSO 3: O Mortal ALLOW FILTERING

A engine exigirá obrigatoriamente que suas pesquisas contemplem a Partition Key exata da Tabela Desnormalizada, como visto anteriormente (O "Voo" direto ao servidor correto).

Se num momento trágico de gambiarra o DBA tentar consultar pelo Nome do Cidadão (que NÃO é chave Primária ou Índex) a tela devolverá um Erro fatal, te bloqueando.

⚠️ A Marreta do Mal: ALLOW FILTERING

-- NUNCA FAÇA ISSO NO GLOBO (APENAS PARA POGS LOCAIS MINÚSCULOS)
SELECT * FROM user_perfis WHERE nome = 'Alex' ALLOW FILTERING;

Perigo de Performance

O ALLOW FILTERING obriga o Cassandra a ignorar o roteamento da Partição (Ele avisa: Pode ler do HD da Terra inteira Node por Node de forma cega!). Você colapsará a RAM e a CPU do Cluster e ele cairá (Latency Timeouts Diários).


🔹 PASSO 4: Validade dos Dados (Time To Live - TTL)

Sistemas "Temporais", Sensores IoT (Temperatura da Fábrica) ou Cookies Lógicos no Banco ganham uma expiração da prateleira orgânica. No Big Data distribuído não ficamos executando lógicas pesadas de "Rotinas de DELETE Diárias":

/* O dado sumirá orgânicamente como fantasmas (Tombstone) em Segundos Exatos (60s) */
INSERT INTO medicoes_sensores (sensor_id, temp, timestamp) 
VALUES ('ZN01', 58, toTimestamp(now())) USING TTL 60;

A matemática da nuvem cria Marcadores Fúnebres (Tombstones) aos quais as operações diárias de varredura magnética compactam removendo efetivamente a lixeira sem sobrecarregar ninguém!


🏁 CONSIDERAÇÕES FINAIS: UNIDADE VII (BIG DATA / CASSANDRA)

A essência da Arquitetura Distribuída separa os programadores dos Engenheiros de Dados de Alta Performance. Dominar a estratégia Masterless (Nó-a-Nó) molda profundamente o seu poder de escala em projetos que outrora sofreriam quedas drásticas no mundo corporativo.


Objetivo: Ratificar os axiomas do Cluster NoSQL, avaliando mentalmente decisões rigorosas de Query-Driven Modeling e Particionamento Físico de chaves compostas (Discos e Localização).


✅ Exercícios de Fixação: Partitioning & Ring Nodes

1. Sob o protocolo de espalhamento matemático em um cluster Ring (Anel) do Apache Cassandra, o que efetivamente o Protocolo GOSSIP entrega como valor insuperável à rede? a) O Gossip transfere e copia a Primary Key inteira para o Banco Relacional. b) O Gossip converte o tráfego Binário do Sistema Operacional local para formato JSON e retransmite para o mongosh. c) O Gossip é o mensageiro vitalício (1 Segundo) onde os Servidores (Nós) atualizam reciprocamente o status uns dos outros, mapeando falhas ou lentidões da topologia (Cluster Health) de forma autonôma e descentralizada, sem precisar de um "Servidor Gerenciador Central". d) O Protocolo Gossip apenas funciona na Nuvem AWS.

2. No paradigma Masterless, se o Nó Brasil recebe do aplicativo a instrução INSERT com um número de réplicas setado para 3 geografias globais, qual a função sistêmica deste nó específico no momento da operação? a) Ele declina o insert exigindo que o Master do Japão aprove a Gravação. b) Ele assume provisoriamente o cargo de Coordinator, executando cópias assíncronas paralelas aos outros nós-alvo ditados pelo Driver do App. c) Ele armazena um arquivo XML de backup. d) Ele inicia um ROLLBACK preventivo.

3. Uma Equipe tentou executar um SELECT por NOME DO CLIENTE em uma enorme Tabela desenhada com id_cliente como Partition Key Exclusiva. Eles não receberam resultado, mas sim um Erro Sistêmico. O Analista Júnior propôs usar brutalmente a clausura ALLOW FILTERING. O que ocorrerá com a corporação? a) A pesquisa ocorrerá rápido e sem restrições usando Inteligência Artificial. b) O Cluster sofrerá esgotamentos mortais de Timeout e lentidão, já que você ordenou que HDs do mundo inteiro (Sem critério Geográfico Hashing) varressem exaustivamente todos os registros da Tabela, quebrando a regra fundamental do roteamento por Particionamento. c) O ALLOW FILTERING apenas ordena os resultados pelo alfabeto ASC. d) O ALLOW FILTERING criará um Log Seguro.


🎯 Desafio de Modelagem (O Arquiteto Data-Driven vs Query-Driven)

O Problema (Mudança Brusca na Nuvem de Telemetria): Uma montadora RDBMS coletava a velocidade e temperatura dos Seus Carros e salvava tudo em PostgreSQL de forma linear. Toda vez que os Analistas queriam a "Temperatura do Motor X nos Últimos 10 Minutos", o banco de dados desmaiava rodando um milhão de Table Scans. O CTO mandou você migrar o sistema vital para Servidores Físicos Apache Cassandra (ScyllaDB em C++).

Você sabe que a Query Principal Vital é: "Quero TODAS as temperaturas coletadas de UM ESPECÍFICO Motor (motor_serial), ordenados dos registros Mais Recentes para os Mais Antigos!"

Sua Missão Arquitetural: Crie a Tabela Primária CQL perfeita (telemetria_por_motor), isolando quem será a Partition Key (Localidade do HDD do Ring) e quem será a dependente Clustering Key (Ordenando no próprio HDD no sentido Temporal Descendente).


📖 Clique aqui para revelar os Gabaritos e Soluções (SPOILER) 📖

✅ Gabarito e Justificativas das Questões:

  1. Letra C. A robustez de ser "Descentralizado" não existiria sem a Fofoca constante alertando anomalias.
  2. Letra B. O Nó atingido vira o maestro provisório (Coordinator).
  3. Letra B. O Cassandra bloqueia o ALLOW FILTERING nas telas pois quer proteger a Infraestrutura física da Empresa de analistas incautos e acostumados com o Relacional WHERE %LIKE%.

🔹 Solução Detalhada do Desafio CQL:

O Segredo da Chave Primária: Na Sintaxe do Cassandra, o primeiro parâmetro isolado é a Partição. O segundo parâmetro (e opcionais consecutivos à direita) dita a Ordem Magnética Física dos logs já concentrados neste computador!

CREATE TABLE telemetria_por_motor (
    motor_serial text,
    data_coleta timestamp,
    temperatura decimal,
    velocidade int,
    
    -- ESTRUTURANDO AS CHAVES DO HD:
    PRIMARY KEY ( (motor_serial), data_coleta )

) WITH CLUSTERING ORDER BY (data_coleta DESC);

A Mágica da Query-Driven que salvou a Montadora:

  1. Partition (motor_serial): Todo pacote novo daquele MotorX vindo por 4G cairá obrigatoriamente no exato mesmo nó do Ring responsável por este carro. Não haverá HDs pulando em redes cruzadas.
  2. Clustering data_coleta: Cada novo LOG de temperatura será armazenado EXATAMENTE EM CIMA do evento do segundo passado, devido à clausura DESC.
  3. A Resposta Instantânea (< 1ms): Quando a UI chama os últimos 10 minutos deste motor, o braço magnético do disco apenas baixa e varre "um montinho físico isolado", alcançando a perfeição analítica e a glória arquitetônica!

💻 Ponte Prática: Do SQL Relacional ao Cassandra CQL em Python

Como a modelagem Query-Driven (Focada na Pergunta) do Apache Cassandra se traduz em código Python de alta performance?

🔴 1. A Abordagem Relacional (Gargalos de Lock em Ingestão Massiva de IoT)

Em bancos relacionais tradicionais, gravar milhares de coordenadas de GPS por segundo gera contenção de escrita (Row Locks) e quebra o banco sob alta concorrência:

-- ❌ MODELAGEM RELACIONAL LENTA PARA TIME-SERIES:
-- Múltiplos índices B-Tree e travas de transação geram filas de espera enormes.
INSERT INTO gps_historico (caminhao_id, data_hora, latitude, longitude) VALUES (...);

🟢 2. A Abordagem Colunar com CQL (Partition Key + Clustering Key)

No Apache Cassandra, definimos a chave primária composta: PRIMARY KEY ((caminhao_id), data_hora) WITH CLUSTERING ORDER BY (data_hora DESC). A Partition Key envia os dados para o nó correto do anel, e a Clustering Key organiza os dados ordenados fisicamente no disco em $O(1)$:

# ✅ ABORDAGEM MODERNA: Modelagem Query-Driven para Time-Series
from datetime import datetime
from typing import Any

class TelemetriaCassandraEntry:
    def __init__(self, caminhao_id: str, timestamp: datetime, lat: float, lng: float, velocidade_kmh: int) -> None:
        self.caminhao_id: str = caminhao_id # Partition Key (Distribuição no Anel)
        self.timestamp: datetime = timestamp # Clustering Key (Ordenação Física no Disco)
        self.lat: float = lat
        self.lng: float = lng
        self.velocidade_kmh: int = velocidade_kmh

    def __repr__(self) -> str:
        return f"GPS[{self.caminhao_id}] @ {self.timestamp.strftime('%H:%M:%S')} -> Lat: {self.lat:.4f}, Lng: {self.lng:.4f}, Vel: {self.velocidade_kmh}km/h"

🛠️ Mini-Projeto 19 (BD): Coletor de Time-Series GPS em Python

Objetivo: Implementar um repositório colunar particionado que insere e recupera séries temporais de GPS ordenadas sem necessitar de JOIN ou ordenação lenta em memória.

📋 Pré-requisitos e Instalação

O código abaixo utiliza apenas bibliotecas padrão do Python 3.11+ (simulando a estrutura colunar de nós e partições do Cassandra em memória). Para conectar a um cluster real de Cassandra/ScyllaDB, bastaria instalar:

pip install cassandra-driver

💻 Código Completo e Autocontido (miniprojeto_19_cassandra.py)

Crie o arquivo miniprojeto_19_cassandra.py e insira o código abaixo integralmente:

"""
Mini-Projeto 19: Coletor de Time-Series GPS em Python
Curso: GTI - Banco de Dados Relacionais e Engenharia de Software
Stack: Python 3.11+ | NoSQL Colunar / Apache Cassandra Simulado
"""
from datetime import datetime, timezone
from typing import Any

# 1. Estrutura de Registro Baseada em Chaves de Partição e Clustering
class TelemetriaCassandraEntry:
    def __init__(self, caminhao_id: str, timestamp: datetime, lat: float, lng: float, velocidade_kmh: int) -> None:
        self.caminhao_id: str = caminhao_id      # Partition Key (Distribuição no Anel)
        self.timestamp: datetime = timestamp     # Clustering Key (Ordenação Física no Disco)
        self.lat: float = lat
        self.lng: float = lng
        self.velocidade_kmh: int = velocidade_kmh

    def __repr__(self) -> str:
        return f"GPS[{self.caminhao_id}] @ {self.timestamp.strftime('%H:%M:%S')} -> Lat: {self.lat:.4f}, Lng: {self.lng:.4f}, Vel: {self.velocidade_kmh}km/h"

# 2. Simulador de Armazenamento Colunar com Partições no Anel
class MockCassandraTimeSeriesStore:
    """Simulador em memória da semântica de particionamento e ordenação física do Apache Cassandra."""
    def __init__(self) -> None:
        # Tabela particionada: chave primária = (PartitionKey, ClusteringKey)
        self._partitions: dict[str, list[TelemetriaCassandraEntry]] = {}

    def insert_cql(self, entry: TelemetriaCassandraEntry) -> None:
        # Simula o roteamento para a Partição do Anel baseada no Hash da Partition Key:
        if entry.caminhao_id not in self._partitions:
            self._partitions[entry.caminhao_id] = []

        # Insere e mantém ordenado DESC pela Clustering Key (timestamp):
        self._partitions[entry.caminhao_id].append(entry)
        self._partitions[entry.caminhao_id].sort(key=lambda x: x.timestamp, reverse=True)

    def select_ultimas_posicoes(self, caminhao_id: str, limit: int = 5) -> list[TelemetriaCassandraEntry]:
        # Leitura sequencial ultrarrápida direto na partição específica (Zero JOIN):
        particao = self._partitions.get(caminhao_id, [])
        return particao[:limit]

# 3. Ponto de Entrada Executável
if __name__ == "__main__":
    print("🔗 COLETOR TIME-SERIES BIG DATA (CASSANDRA CQL) - TECPROEXPRESS")
    print("=" * 68)

    store = MockCassandraTimeSeriesStore()

    # 1. Inserindo telemetria em alta frequência (Caminhão BR-101 e SP-050)
    agora = datetime.now(timezone.utc)
    store.insert_cql(TelemetriaCassandraEntry("CAM-BR101", agora, -23.5505, -46.6333, 82))
    store.insert_cql(TelemetriaCassandraEntry("CAM-BR101", agora, -23.5520, -46.6350, 85))
    store.insert_cql(TelemetriaCassandraEntry("CAM-SP050", agora, -22.9068, -43.1729, 60))

    print("✅ Registros gravados no anel com Particionamento por Veículo!")

    # 2. Executando Query de alta performance: SELECT * FROM gps_log WHERE caminhao_id = 'CAM-BR101' LIMIT 2;
    print("\n🔍 Executando Leitura Sequencial na Partição 'CAM-BR101':")
    historico = store.select_ultimas_posicoes("CAM-BR101", limit=2)
    for pos in historico:
        print(f"   • {pos}")

    print("=" * 68)

🚀 Como Executar

Execute o script no terminal:

python miniprojeto_19_cassandra.py

🖥️ Saída Esperada no Console

🔗 COLETOR TIME-SERIES BIG DATA (CASSANDRA CQL) - TECPROEXPRESS
====================================================================
✅ Registros gravados no anel com Particionamento por Veículo!

🔍 Executando Leitura Sequencial na Partição 'CAM-BR101':
   • GPS[CAM-BR101] @ 10:00:00 -> Lat: -23.5505, Lng: -46.6333, Vel: 82km/h
   • GPS[CAM-BR101] @ 10:00:00 -> Lat: -23.5520, Lng: -46.6350, Vel: 85km/h
====================================================================

A Jornada do Arquiteto Moderno

Parabéns por atingir o ápice do treinamento Poliglota Operacional! Do ACID (MySQL/PostgreSQL), transicionando pela Flexibilidade JSON (MongoDB), até atingir a escala hiperdistribuída (Cassandra/ScyllaDB). Você domina as engrenagens da Internet Atual!




🧪 Quiz de Fixação e Autoavaliação — Capítulo 19

1. Por que a arquitetura do Apache Cassandra é chamada de 'Masterless' (sem nó mestre)?

  • A) Porque o sistema não tem nenhum desenvolvedor sênior na equipe.
  • B) Porque todos os nós do cluster têm exatamente o mesmo papel e importância; qualquer cliente pode conectar em qualquer nó que atuará como coordenador, eliminando qualquer ponto único de falha (Single Point of Failure - SPOF).
  • C) Porque o Cassandra só roda em um único computador.
  • D) Porque ele não usa senhas de acesso.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: Arquitetura Peer-to-Peer pura: se 3 nós de um cluster de 10 nós queimarem, o Cassandra continua operando sem interrupção e sem eleição complexa de mestre.


2. No Apache Cassandra, qual é a regra fundamental que guia toda a modelagem de dados?

  • A) Modelar as tabelas baseando-se na 3ª Forma Normal.
  • B) Modelar as tabelas orientadas estritamente pelas CONSULTAS que a aplicação precisa fazer (Query-First Design), aceitando desnormalização e duplicação controlada de dados para garantir que cada busca acerte apenas uma partição.
  • C) Criar apenas uma tabela para todo o sistema.
  • D) Proibir o uso de chaves primárias.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: No Cassandra, não existem JOINs. Se a aplicação precisa buscar 'Usuários por Email' e 'Usuários por CPF', criam-se duas tabelas desnormalizadas otimizadas para cada consulta específica.


3. Na fórmula de Consistência Forte do Cassandra ($R + W > N$), o que acontece se tivermos um fator de replicação $N=3$, escrevermos com Quórum ($W=2$) e lermos com Quórum ($R=2$)?

  • A) O cluster trava por excesso de nós.
  • B) A leitura tem garantia matemática de retornar sempre o dado mais recente (Consistência Estrita), pois os conjuntos de leitura e escrita obrigatoriamente se sobrepõem em pelo menos um nó com a versão atualizada.
  • C) O dado é apagado de todos os nós.
  • D) O Cassandra converte os dados para SQLite.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: Com $2 + 2 = 4 > 3$, a sobreposição de nós garante que a leitura sempre consultará pelo menos um nó que participou da escrita mais recente.


🎯 Laboratório Prático

Coloque este conhecimento em prática agora mesmo executando o roteiro autoguiado:
👉 ATIVIDADE 09: NOSQL CASSANDRA

🚀 CAPÍTULO 20: CONCLUSÃO, ARQUITETURA POLIGLOTA E ESTUDO DE CASO


🎯 Objetivos de Aprendizagem

Ao final deste capítulo (estimativa: 2 horas de estudo autoguiado), você será capaz de:

  • 🔹 Integrar todas as tecnologias do curso na disciplina de Persistência Poliglota (Polyglot Persistence).
  • 🔹 Combinar o banco relacional (PostgreSQL/ACID), banco de documentos (MongoDB), cache chave-valor (Redis) e busca vetorial (pgvector) na mesma arquitetura corporativa.
  • 🔹 Analisar o Estudo de Caso de ponta a ponta da TecProExpress e dos 10 Projetos Integradores.
  • 🔹 Consolidar as competências de Engenharia de Dados para atuação profissional no mercado de TI.

🏢 O Cenário Corporativo (Seu Desafio)

A diretoria da TecProExpress aprovou a arquitetura final poliglota. Seu desafio é integrar PostgreSQL, MongoDB e Redis em uma solução corporativa unificada.


Nesta unidade final de engenharia de dados, nosso objetivo é aplicar todo o conhecimento acumulado em um cenário real da indústria.


Objetivo: Consolidar a visão de arquiteto de dados através da modelagem, criação e manipulação de um sistema comercial completo (SGBD Relacional).


🧠 Fundamentos: Persistência Poliglota e Arquitetura de Dados de Alta Escala O Mercado de Tecnologia

O domínio de SGBDs abre portas para as carreiras mais bem remuneradas do mercado global:

Área ProfissionalDesafio e Foco
Engenharia de SoftwarePersistência de dados para apps escaláveis.
DBA (Admin)Gerenciar infraestruturas críticas e performance.
Engenharia de DadosPipelines de Big Data e transformação de dados.
Cloud & DevOpsSoluções distribuídas em AWS/Azure/GCP.

🔹 PASSO 2: Ferramenta de Elite (PostgreSQL)

Recomendamos o PostgreSQL 17 para este estudo. Ele é o banco de dados livre mais robusto e próximo dos padrões globais ANSI SQL.

🔹 Dica do Mestre: O PostgreSQL é a escolha de grandes Unicórnios devido à sua conformidade rigorosa e suporte nativo a JSON (NoSQL) dentro do ambiente Relacional.


🏢 CONTEXTUALIZAÇÃO: TecProExpress

A empresa TecProExpress solicita um sistema para gerenciar suas operações de venda. O objetivo estratégico é possuir controle total sobre o fluxo comercial.


Objetivo: Analisar requisitos de negócio, identificar entidades e definir as restrições de integridade de um cenário comercial real.


🔹 PASSO 1: Levantamento de Requisitos

Para o desenvolvimento, foram identificadas as seguintes necessidades críticas:

  • 📖 Clientes: Cadastro e histórico (CNPJ/CPF, Localização).
  • 📖 Produtos: Gestão de itens com preços de mercado.
  • 📖 Vendedores: Controle de equipe para comissões.
  • 📖 Vendas: Registro imutável de transações financeiras.

📜 Regras de Negócio (Integridade)

  1. Uma Venda pertence a apenas um Cliente.
  2. Uma Venda é realizada por apenas um Vendedor.
  3. Uma Venda pode conter múltiplos Produtos (N:M).
  4. O sistema opera em Matriz Centralizada.

🔹 PASSO 2: Identificação de Entidades

Após a análise, identificamos as seguintes tabelas lógicas:

  1. 📖 CLIENTE
  2. 📖 VENDEDOR
  3. 📖 PRODUTO
  4. 📖 VENDA
  5. 📖 VENDA_ITENS (Tabela Associativa M:N)

🔹 Nota de Projeto: A entidade "Empresa" não será modelada como tabela, por ser um sistema exclusivo para uma unidade de negócio centralizada.


🗺️ MODELAGEM ER: TecProExpress

Para projetar um banco de dados de alto desempenho, mapeamos as entidades respeitando as regras de negócio.


Objetivo: Visualizar a arquitetura do banco de dados comercial através de diagramas ER e compreender a integridade referencial dos relacionamentos 1:N e N:M.


🔹 PASSO 1: Diagrama de Entidade-Relacionamento

O diagrama abaixo consolida a estrutura profissional da TecProExpress:

🔹 Arquitetura de Dados Comercial

erDiagram
    CLIENTE ||--o{ VENDA : "possui"
    VENDEDOR ||--o{ VENDA : "realiza"
    VENDA ||--|{ VENDA_ITENS : "contém"
    PRODUTO ||--o{ VENDA_ITENS : "compõe"

    CLIENTE {
        int cli_id PK
        string cli_nome
        string cli_documento
    }
    VENDEDOR {
        int ven_id PK
        string ven_nome
        decimal ven_comissao
    }
    VENDA {
        int vda_id PK
        date vda_data
        int vda_cliente_id FK
        int vda_vendedor_id FK
    }
    VENDA_ITENS {
        int vdi_venda_id PK, FK
        int vdi_sequencia PK
        int vdi_produto_id FK
        decimal vdi_quantidade
        decimal vdi_preco_venda
    }
    PRODUTO {
        int prod_id PK
        string prod_nome
        decimal prod_preco_atual
    }

🔹 PASSO 2: Análise dos Relacionamentos

  1. 📖 CLIENTE x VENDA (1:N): Um cliente realiza múltiplas compras históricas.
  2. 📖 VENDEDOR x VENDA (1:N): Transação creditada a um vendedor responsável.
  3. 📖 VENDA x VENDA_ITENS (1:N): A venda conecta a lista de itens físicos.
  4. 📖 PRODUTO x VENDA_ITENS (1:N): O produto compõe diversos carrinhos de compra.

🔹 Valor Histórico: O preço do produto no momento da venda é armazenado em VENDA_ITENS. Isso garante que mudanças futuras no preço do produto não alterem o valor de vendas passadas.


🔀 MAPEAMENTO PARA O RELACIONAL

Chegamos ao ápice da nossa jornada! Vamos aplicar o conhecimento na prática para a fábrica TecProExpress.


Objetivo: Implementar o esquema físico (DDL) e desenvolver consultas de inteligência de negócio (JOINs) para extrair faturamento e detalhes de vendas.


🔹 PASSO 1: Levantamento Estratégico

Antes de codificar, relembramos o coração do negócio:

  1. 📖 PRODUTOS: Itens fabricados e preço sugerido.
  2. 📖 CLIENTES: Quem consome nossos produtos profissionais.
  3. 📖 VENDAS: Registro de faturamento e movimentação.
  4. 📖 ITENS DA VENDA: O detalhamento técnico de cada fatura.

🔹 PASSO 2: Implementação do Esquema (DDL)

Criaremos as tabelas respeitando a Integridade Referencial:

-- 1. Tabelas Independentes
CREATE TABLE PRODUTOS (
    ID INT PRIMARY KEY,
    DESCRICAO VARCHAR(100) NOT NULL,
    VALOR_SUGERIDO DECIMAL(12,2)
);

CREATE TABLE CLIENTES (
    ID INT PRIMARY KEY,
    NOME VARCHAR(100) NOT NULL,
    ESTADO CHAR(2)
);

CREATE TABLE VENDEDOR (
    ID INT PRIMARY KEY,
    NOME VARCHAR(100) NOT NULL,
    MATRICULA VARCHAR(20) UNIQUE NOT NULL
);

-- 2. Tabela de Movimentação
CREATE TABLE VENDA (
    ID INT PRIMARY KEY,
    DATA_MOVTO DATE DEFAULT CURRENT_DATE,
    CLIENTE_ID INT,
    VENDEDOR_ID INT,
    VALOR_TOTAL DECIMAL(12,2),
    FOREIGN KEY (CLIENTE_ID) REFERENCES CLIENTES(ID),
    FOREIGN KEY (VENDEDOR_ID) REFERENCES VENDEDOR(ID)
);

-- 3. Detalhamento (Tabela Associativa)
CREATE TABLE VENDA_ITENS (
    VENDA_ID INT,
    SEQUENCIAL INT,
    PRODUTO_ID INT,
    QUANTIDADE DECIMAL(10,2),
    VALOR_UNIDADE DECIMAL(12,2), -- PREÇO HISTÓRICO
    PRIMARY KEY (VENDA_ID, SEQUENCIAL),
    FOREIGN KEY (VENDA_ID) REFERENCES VENDA(ID),
    FOREIGN KEY (PRODUTO_ID) REFERENCES PRODUTOS(ID)
);

🔹 PASSO 3: Inteligência de Negócio (SELECT)

Como extrair um relatório completo de faturamento? Usamos o JOIN:

SELECT 
    V.ID AS "Nº VENDA",
    C.NOME AS "CLIENTE",
    P.DESCRICAO AS "PRODUTO",
    VI.QUANTIDADE AS "QTD",
    VI.VALOR_UNIDADE AS "PREÇO UN.",
    (VI.QUANTIDADE * VI.VALOR_UNIDADE) AS "SUBTOTAL"
FROM VENDA V
JOIN CLIENTES C ON V.CLIENTE_ID = C.ID
JOIN VENDA_ITENS VI ON V.ID = VI.VENDA_ID
JOIN PRODUTOS P ON VI.PRODUTO_ID = P.ID;

🎯 Desafio de Especialista

Tente criar uma consulta que mostre o Faturamento Total por Estado.

📖 Clique aqui para revelar a Solução (SPOILER) 📖
SELECT C.ESTADO, SUM(V.VALOR_TOTAL) AS FATURAMENTO
FROM VENDA V
JOIN CLIENTES C ON V.CLIENTE_ID = C.ID
GROUP BY C.ESTADO
ORDER BY FATURAMENTO DESC;

🔹 Visão de Arquiteto: Note como a tabela VENDA_ITENS é o coração estrutural. Sem ela, você saberia quanto o cliente pagou, mas nunca saberia o que ele realmente levou.


🏁 CONCLUSÃO: ALÉM DO RELACIONAL

Concluir esta jornada é o sentimento de dever cumprido. O banco de dados é a base sólida para a lógica estratégica.


Reflexão Final: O banco de dados é um detalhe vital, mas a verdadeira inovação é o software que resolve problemas reais e o valor que ele gera para a sociedade.


🔹 PASSO 1: Libertando-se da "Prisão Relacional"

Ao concluir este material, você domina a integração SQL. No entanto, o engenheiro moderno deve ter senso crítico:

  • 📖 NoSQL & Escalabilidade: Onde o modelo relacional encontra gargalos, o NoSQL brilha.
  • 📖 Arquitetura: Escolha as ferramentas pela eficiência, não por dogmas técnicos ou preferências.

Este capítulo consolida o lado relacional da arquitetura poliglota (código executável abaixo). As demais peças — que você já implementou em laboratórios anteriores — se encaixam assim na arquitetura final da TecProExpress:

Peça PoliglotaOnde você já implementouPapel na arquitetura final
🐘 PostgreSQL (Relacional/ACID)Atividades 01–07, 11–14Fonte da verdade: pedidos, clientes, transações financeiras.
🍃 MongoDB (Documentos)Atividade 08Catálogos e payloads com esquema flexível (ex: especificações variáveis de produto).
🔑 Redis (Chave-Valor)Atividade 15Cache de sessão e consultas frequentes, aliviando a carga do PostgreSQL.
🧠 pgvector (Busca Vetorial)Atividade 17Busca semântica sobre embeddings de IA, dentro do próprio PostgreSQL.

🔹 PASSO 2: A Jornada Contínua

🔹 O Caminho que se Abre

flowchart LR
    Start(("🏁 Fim do Módulo")) --> Practice["🛠️ Prática de Engenharia"]
    Practice --> LearnNoSQL["📚 Domínio NoSQL/Híbrido"]
    Practice --> Cloud["☁️ Especialização Cloud"]
    Practice --> Innovation(("🚀 Inovação Profissional"))

Desejo sucesso em sua jornada como Arquiteto(a) e Engenheiro(a) de Dados. O fim é apenas o começo.


💻 Ponte Prática: Do SQL Manual ao SQLAlchemy 2.0 ORM & NoSQL

A síntese da disciplina: a implementação completa do Sistema Comercial da TecProExpress com relacionamentos $1:N$ e $N:M$ e controle transacional ACID em Python 3.11.

🔴 1. A Abordagem Manual (Scripts Desconectados)

No modelo antigo, tabelas e dados vivem isolados em arquivos .sql, exigindo que os programadores reescrevam queries complexas e tratem integridade na mão:

-- ❌ ABORDAGEM ISOLADA: Sem tipagem, sem DTOs e com alto risco de inconsistência
INSERT INTO venda (cliente_id, vendedor_id) VALUES (1, 10);
INSERT INTO venda_itens (venda_id, produto_id, qtd, preco) VALUES (1, 50, 2, 150.00);

🟢 2. A Abordagem com SQLAlchemy 2.0 (Arquitetura Comercial Completa)

Com o SQLAlchemy 2.0, modelamos a rede de entidades (Cliente, Vendedor, Produto, Venda e ItemVenda) como um grafo de objetos com navegação bidirecional e persistência em cascata:

# ✅ ABORDAGEM MODERNA: Mapeamento Relacional Comercial Completo
from datetime import datetime, timezone
from typing import Any
from sqlalchemy import create_engine, String, Float, Integer, ForeignKey, DateTime, select
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship, Session

class Base(DeclarativeBase):
    pass

class ClienteCorpModel(Base):
    __tablename__ = "clientes_corp"
    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    nome: Mapped[str] = mapped_column(String(100), nullable=False)
    vendas: Mapped[list["VendaCorpModel"]] = relationship("VendaCorpModel", back_populates="cliente")

class ProdutoCorpModel(Base):
    __tablename__ = "produtos_corp"
    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    nome: Mapped[str] = mapped_column(String(100), nullable=False)
    preco_unitario: Mapped[float] = mapped_column(Float, nullable=False)

class VendedorCorpModel(Base):
    __tablename__ = "vendedores_corp"
    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    nome: Mapped[str] = mapped_column(String(100), nullable=False)
    matricula: Mapped[str] = mapped_column(String(20), nullable=False, unique=True)
    vendas: Mapped[list["VendaCorpModel"]] = relationship("VendaCorpModel", back_populates="vendedor")

class VendaCorpModel(Base):
    __tablename__ = "vendas_corp"
    id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    cliente_id: Mapped[int] = mapped_column(ForeignKey("clientes_corp.id"), nullable=False)
    vendedor_id: Mapped[int] = mapped_column(ForeignKey("vendedores_corp.id"), nullable=False)
    data_venda: Mapped[datetime] = mapped_column(DateTime, default=lambda: datetime.now(timezone.utc))

    cliente: Mapped["ClienteCorpModel"] = relationship("ClienteCorpModel", back_populates="vendas")
    vendedor: Mapped["VendedorCorpModel"] = relationship("VendedorCorpModel", back_populates="vendas")
    itens: Mapped[list["VendaItemCorpModel"]] = relationship("VendaItemCorpModel", back_populates="venda", cascade="all, delete-orphan")

class VendaItemCorpModel(Base):
    """Tabela associativa N:M com atributos extras (quantidade e preço praticado)."""
    __tablename__ = "vendas_itens_corp"
    id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    venda_id: Mapped[int] = mapped_column(ForeignKey("vendas_corp.id"), nullable=False)
    produto_id: Mapped[int] = mapped_column(ForeignKey("produtos_corp.id"), nullable=False)
    quantidade: Mapped[int] = mapped_column(Integer, nullable=False)
    preco_unitario_gravado: Mapped[float] = mapped_column(Float, nullable=False)

    venda: Mapped["VendaCorpModel"] = relationship("VendaCorpModel", back_populates="itens")
    produto: Mapped["ProdutoCorpModel"] = relationship("ProdutoCorpModel")

Nota sobre o Mini-Projeto executável abaixo

Por simplicidade didática, o script autocontido a seguir demonstra a transação ACID apenas com Cliente, Produto, Venda e Item — sem persistir Vendedor. A entidade VENDEDOR (DDL e ORM acima) faz parte do modelo completo do capítulo e deve ser incorporada por você como exercício de fixação.


🛠️ Mini-Projeto 20 (BD): Sistema Comercial Completo com Transação ACID

Objetivo: Construir e simular a operação completa de vendas da TecProExpress, gravando cabeçalho e itens em uma única transação atômica.

📋 Pré-requisitos e Instalação

No terminal do seu ambiente virtual (PowerShell ou Bash), instale a biblioteca necessária:

pip install sqlalchemy

💻 Código Completo e Autocontido (miniprojeto_20_comercial.py)

Crie o arquivo miniprojeto_20_comercial.py e insira o código abaixo integralmente:

"""
Mini-Projeto 20: Sistema Comercial Completo com Transação ACID
Curso: GTI - Banco de Dados Relacionais e Engenharia de Software
Stack: Python 3.11+ | SQLAlchemy 2.0 | SQLite
"""
import os
from datetime import datetime, timezone
from sqlalchemy import create_engine, String, Float, Integer, ForeignKey, DateTime, select
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship, Session

# 1. Definição Declarativa do Esquema Comercial Completo
class Base(DeclarativeBase):
    pass

class ClienteCorpModel(Base):
    __tablename__ = "clientes_corp"

    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    nome: Mapped[str] = mapped_column(String(100), nullable=False)
    vendas: Mapped[list["VendaCorpModel"]] = relationship("VendaCorpModel", back_populates="cliente")

class ProdutoCorpModel(Base):
    __tablename__ = "produtos_corp"

    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    nome: Mapped[str] = mapped_column(String(100), nullable=False)
    preco_unitario: Mapped[float] = mapped_column(Float, nullable=False)

class VendaCorpModel(Base):
    __tablename__ = "vendas_corp"

    id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    cliente_id: Mapped[int] = mapped_column(ForeignKey("clientes_corp.id"), nullable=False)
    data_venda: Mapped[datetime] = mapped_column(DateTime, default=lambda: datetime.now(timezone.utc))

    cliente: Mapped["ClienteCorpModel"] = relationship("ClienteCorpModel", back_populates="vendas")
    itens: Mapped[list["VendaItemCorpModel"]] = relationship("VendaItemCorpModel", back_populates="venda", cascade="all, delete-orphan")

class VendaItemCorpModel(Base):
    """Tabela associativa N:M com atributos extras (quantidade e preço praticado)."""
    __tablename__ = "vendas_itens_corp"

    id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    venda_id: Mapped[int] = mapped_column(ForeignKey("vendas_corp.id"), nullable=False)
    produto_id: Mapped[int] = mapped_column(ForeignKey("produtos_corp.id"), nullable=False)
    quantidade: Mapped[int] = mapped_column(Integer, nullable=False)
    preco_unitario_gravado: Mapped[float] = mapped_column(Float, nullable=False)

    venda: Mapped["VendaCorpModel"] = relationship("VendaCorpModel", back_populates="itens")
    produto: Mapped["ProdutoCorpModel"] = relationship("ProdutoCorpModel")

# 2. Ponto de Entrada Executável
if __name__ == "__main__":
    DB_FILE = "tecpro_comercial_final.db"

    # Reset preventivo para garantir idempotência em testes repetidos
    if os.path.exists(DB_FILE):
        os.remove(DB_FILE)

    engine = create_engine(f"sqlite:///{DB_FILE}", echo=False)
    Base.metadata.create_all(bind=engine)

    print("📦 SISTEMA COMERCIAL COMPLETO (SGBD + ORM) - TECPROEXPRESS")
    print("=" * 70)

    # 1. Carga Inicial do Catálogo e Clientes
    with Session(engine) as session:
        c1 = ClienteCorpModel(id=1, nome="Supermercados Aliança Ltda")
        p1 = ProdutoCorpModel(id=101, nome="Paleteira Hidráulica 2T", preco_unitario=1850.0)
        p2 = ProdutoCorpModel(id=102, nome="Leitor de Código de Barras Zebra", preco_unitario=450.0)
        session.merge(c1)
        session.merge(p1)
        session.merge(p2)
        session.commit()

    # 2. Executando Venda Completa em Transação ACID Atômica
    with Session(engine) as session:
        with session.begin():
            nova_venda = VendaCorpModel(cliente_id=1)
            item1 = VendaItemCorpModel(produto_id=101, quantidade=2, preco_unitario_gravado=1850.0)
            item2 = VendaItemCorpModel(produto_id=102, quantidade=4, preco_unitario_gravado=450.0)
            nova_venda.itens.extend([item1, item2])
            session.add(nova_venda)

        print(f"✅ Venda #{nova_venda.id} registrada com {len(nova_venda.itens)} itens em Transação ACID!")

    # 3. Consulta da Venda com Cálculo de Total Consolidado
    print("\n🧾 FATURA COMERCIAL CONSOLIDADA:")
    with Session(engine) as session:
        venda_recuperada = session.get(VendaCorpModel, 1)
        if venda_recuperada:
            print(f"Cliente: {venda_recuperada.cliente.nome}")
            print(f"Data: {venda_recuperada.data_venda.strftime('%d/%m/%Y %H:%M')}")
            print("-" * 70)
            total_geral = 0.0
            for item in venda_recuperada.itens:
                subtotal = item.quantidade * item.preco_unitario_gravado
                total_geral += subtotal
                print(f"  • {item.produto.nome:<38} | Qtd: {item.quantidade} x R$ {item.preco_unitario_gravado:>7.2f} = R$ {subtotal:>9.2f}")
            print("-" * 70)
            print(f"💰 VALOR TOTAL DA TRANSAÇÃO: R$ {total_geral:.2f}")

    print("=" * 70)

🚀 Como Executar

Execute o script no terminal:

python miniprojeto_20_comercial.py

🖥️ Saída Esperada no Console

📦 SISTEMA COMERCIAL COMPLETO (SGBD + ORM) - TECPROEXPRESS
======================================================================
✅ Venda #1 registrada com 2 itens em Transação ACID!

🧾 FATURA COMERCIAL CONSOLIDADA:
Cliente: Supermercados Aliança Ltda
Data: 10/09/2026 10:00
----------------------------------------------------------------------
  • Paleteira Hidráulica 2T                | Qtd: 2 x R$ 1850.00 = R$   3700.00
  • Leitor de Código de Barras Zebra       | Qtd: 4 x R$  450.00 = R$   1800.00
----------------------------------------------------------------------
💰 VALOR TOTAL DA TRANSAÇÃO: R$ 5500.00
======================================================================

📚 REFERÊNCIAS BIBLIOGRÁFICAS

Fontes e acervos que fundamentaram este material técnico sobre Banco de Dados.


📚 Acervo de Referência

  • 📖 Serpro. PostgreSQL: um banco de dados para todos. Disponível aqui.
  • 📖 BEIGHLEY, Lynn. Use a Cabeça SQL. Alta Books, 2008.
  • 📖 CARDOSO, V.; CARDOSO, G. Sistema de Banco de Dados: uma abordagem introdutória e aplicada.
  • 📖 CHEN, Peter. Gerenciando Banco de Dados: A Abordagem Entidade-Relacionamento para Projeto Lógico.
  • 📖 DATE, C. J. Bancos de dados: tópicos avançados.
  • 📖 MongoDB. 10 things you should know about NoSQL. Disponível aqui.
  • 📖 HEUSER, C. A. Projeto de banco de dados. 6. ed.
  • 📖 NAVATHE, S. B.; ELMASRI, R. Sistemas de Banco de Dados. 6. ed.
  • 📖 PRITCHET, E. Base: an acid alternative. Disponível aqui.
  • 📖 SILBERSCHATZ, A.; KORTH, H. F.; SUDARSHAN, S. Sistema de banco de dados.
  • 📖 TAURION, C. Banco de dados Open Source: presente ou futuro? IBM.
  • 📖 ZHANG, J. Banco de dados na nuvem. IBM.

🔹 Aviso: As bibliografias listadas são referências clássicas. Recomenda-se a consulta das edições mais recentes para acompanhar as inovações de mercado.


🧪 Quiz de Fixação e Autoavaliação — Capítulo 20

1. O que é o conceito arquitetural de 'Persistência Poliglota' (Polyglot Persistence) introduzido por Martin Fowler?

  • A) Obrigar o time a programar em 10 linguagens diferentes no mesmo dia.
  • B) A prática de utilizar diferentes tecnologias de banco de dados (Relacional, Documentos, Chave-Valor, Grafos, Vetoriais) dentro da mesma organização ou microsserviço, escolhendo o motor ideal para a necessidade de cada tipo de dado.
  • C) Traduzir todos os nomes de tabelas para inglês e espanhol.
  • D) Usar apenas arquivos CSV para tudo.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: Nenhum banco resolve todos os problemas perfeitamente: usa-se PostgreSQL para transações financeiras ACID, Redis para cache de sessão ultrarrápido e MongoDB para catálogos flexíveis.


2. Em uma arquitetura de e-commerce de alto tráfego, onde o Redis (banco Chave-Valor em memória) deve ser posicionado?

  • A) Como banco principal de faturamento financeiro.
  • B) Como camada de Cache intermediária na frente do banco relacional, guardando sessões de login, carrinhos temporários e produtos mais acessados para responder em microssegundos sem sobrecarregar o PostgreSQL.
  • C) No lugar do sistema operacional.
  • D) Apenas em servidores desligados.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: O Redis armazena dados em memória RAM (In-Memory), respondendo a centenas de milhares de requisições por segundo e aliviando a carga do banco relacional principal.


3. Qual é o principal aprendizado que os 10 Projetos Integradores e as 40 semanas do portal entregam para a sua formação em Gestão de TI / Engenharia?

  • A) Que programar é apenas copiar e colar código da internet sem entender a arquitetura.
  • B) A capacidade de projetar e implementar sistemas corporativos ponta a ponta com visão holística, unindo modelagem conceitual, integridade de dados, automação de testes, infraestrutura conteinerizada e segurança orientada ao negócio.
  • C) Que bancos de dados relacionais deixaram de ser usados no mundo real.
  • D) Que documentação técnica não tem valor.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: A formação sólida em TI integra rigor de engenharia, código limpo, arquitetura desacoplada e foco em resolver dores reais de negócio com previsibilidade e qualidade.


🎯 Laboratório Prático

Coloque este conhecimento em prática agora mesmo executando o roteiro autoguiado:
👉 ATIVIDADE 20: PROJETO FINAL AVANÇADO (FASE 2)

📅 CRONOGRAMA E PLANEJAMENTO — 20 SEMANAS

Planejamento semanal das atividades práticas de Projetos III — Banco de Dados, estruturado como uma jornada contínua de 20 Tutoriais Guiados. 🛡️🧩


🗓️ Visão Geral do Semestre (20 Semanas)

🏗️ BLOCO I — DESIGN & SQL ESSENCIAL (Semanas 1–7)

SemanaCapítulo de Aula (Referência)Tutorial Prático
1Cap. 01 & 04: Introdução & Setup de AmbientesAtv 01: Setup
2Cap. 05 & 06: Modelagem MER e Tipos de DadosAtv 02: Modelagem
3Cap. 07 & 09: Chaves, Mapeamento Relacional & ÁlgebraAtv 03: Mapeamento + Álgebra
4Cap. 10: Normalização (1FN, 2FN e 3FN)Atv 04: Normalização
5Cap. 11 & 12: Ecossistema SQL, DDL & RestriçõesAtv 05: SQL DDL
6Cap. 13: Linguagem DML (INSERT, UPDATE, DELETE)Atv 06: SQL DML
7Cap. 14 & 15: Consultas SELECT, JOINs & SubqueriesAtv 07: SQL Avançado

📦 INTRODUÇÃO AO NOSQL (Semanas 8–10)

SemanaCapítulo de Aula (Referência)Tutorial Prático
8📝 P1 — prova objetiva (Semanas 1–8: Design de Dados e SQL)
9Cap. 18: Conceitos NoSQL & MongoDBAtv 08: NoSQL MongoDB
10Cap. 19: Apache Cassandra & Big DataAtv 09: NoSQL Cassandra

⚡ BLOCO II — PERFORMANCE, TRANSAÇÕES & PROGRAMAÇÃO (Semanas 11–14)

SemanaCapítulo de Aula (Referência)Tutorial Prático
11Cap. 20: Consolidação SQL & NoSQLAtv 10: Projeto Final (Fase 1)
12Cap. 16: Views, Índices B-Tree/Hash & OtimizaçãoAtv 11: Índices e Otimização
13Cap. 03: Transações ACID, Isolamento & ConcorrênciaAtv 12: Transações e ACID
14Cap. 11, 13 & 16: Programação Procedural (Procedures/Triggers)Atv 13: Stored Proc & Triggers

🌐 BLOCO III — ARQUITETURAS MODERNAS & DEVOPs (Semanas 15–20)

SemanaCapítulo de Aula (Referência)Tutorial Prático
15Cap. 18: SQL Híbrido & JSONB nativo no PostgresAtv 14: SQL Híbrido com JSON
16Cap. 18 & 19: Bancos In-Memory & Estruturas de Cache
📝 P2 — prova objetiva (Semanas 9–16: Performance, Transações, Programação e NoSQL Avançado)
Atv 15: Chave-Valor com Redis
17Cap. 18 & 19: Redes Conectadas & Modelagem de GrafosAtv 16: Grafos com Neo4j
18Cap. 18, 19 & 20: IA Generativa & Busca SemânticaAtv 17: Bancos Vetoriais (pgvector)
19Cap. 19: Modelagem Dimensional DW & OLAPAtv 18: Modelagem Dimensional DW
20Cap. 11, 13 & 20: DevOps, Schema Migrations & EncerramentoAtv 19: Schema Migrations
Atv 20: Projeto Final (Fase 2)

📈 Composição da Nota

A disciplina segue o modelo de avaliação da Fatec (Regulamento Geral dos Cursos Superiores de Graduação das Fatecs, Deliberação CEETEPS nº 106/2025). Sem pesos ou pontuações fracionadas por atividade — apenas média aritmética simples. Detalhamento completo, regra do Exame e exemplos de cálculo em docs/institucional/SISTEMA_AVALIACAO_FATEC.md (repositório do projeto).

Período 1 (Atividades 01–07)Período 2 (Atividades 08–20)
P1 — prova objetiva, nota de 0 a 10 (quantidade de questões a critério do professor), aplicada na Semana 8P2 — prova objetiva, nota de 0 a 10, aplicada na Semana 16
Atv 01 a 07, cada uma avaliada de 0 a 10 pelo professor, controladas em planilha própriaAtv 08 a 20, mesma lógica
A1 = média aritmética simples das notas de Atv 01 a 07A2 = média aritmética simples das notas de Atv 08 a 20

As semanas de P1 (8) e P2 (16) seguem o calendário oficial do 2º semestre de 2026 (docs/institucional/Calendario-Fatec-Assis-2026.pdf e docs/institucional/FATEC-Calendario 2026-2.jpeg: P1 em 21–25/09, P2 em 13–19/11), considerando que a turma de BD tem aula às terças-feiras — por isso o corte de período não cai num meio redondo (7 e 13 atividades, não 10 e 10). As datas mudam a cada semestre e precisam ser conferidas no calendário vigente; a quantidade de questões da prova também é livre para o professor definir. O que não muda é o cálculo: prova 0–10 com peso igual entre questões; A1/A2 como média aritmética simples das notas do período.

Média Final = (P1 + P2 + A1 + A2) / 4

Aprovação: Média Final ≥ 6,0, com frequência mínima de 75%.

Direito ao Exame (Art. 34 do Regulamento): aluno reprovado por nota (Média Final < 6,0) que cumpriu a frequência mínima de 75% — sem piso mínimo de nota. A forma exata de cálculo da nota pós-exame é proposta pela Coordenação de Curso; a convenção adotada neste componente ((Média Final + Exame) / 2 ≥ 6,0) está pendente de validação formal e detalhada em docs/institucional/SISTEMA_AVALIACAO_FATEC.md.

⚠️ Esta metodologia está sujeita a alteração conforme decisão da Unidade/Coordenação de Curso. Qualquer mudança será informada previamente aos alunos, antes de entrar em vigor.

📚 ATIVIDADES DE PROJETOS III — BANCO DE DADOS

Bem-vindo(a) ao módulo de Atividades Práticas de Projetos III. Este planejamento foi expandido para uma série de 20 Tutoriais Guiados para garantir uma evolução profissional, do setup ao desenvolvimento de arquiteturas de dados resilientes de alta performance. 🛡️🧩


🗺️ Visão Geral da Jornada (20 Tutoriais)

🪜 Escadinha do Conhecimento

flowchart TD
    subgraph Bloco1 ["Bloco I: Design & Modelagem"]
        A01["⚙️ Atv 01: Setup"] --> A02["📐 Atv 02: Modelagem"]
        A02 --> A03["➗ Atv 03: Mapeamento & Álgebra"]
        A03 --> A04["📏 Atv 04: Normalização"]
    end
    subgraph Bloco2 ["Bloco II: SQL & Programação Avançada"]
        A04 --> A05["🏗️ Atv 05: SQL DDL"]
        A05 --> A06["📝 Atv 06: SQL DML"]
        A06 --> A07["📊 Atv 07: SQL Avançado"]
        A07 --> A11["⚡ Atv 11: Índices & Otimização"]
        A11 --> A12["🔄 Atv 12: Transações & ACID"]
        A12 --> A13["🤖 Atv 13: Stored Proc & Triggers"]
    end
    subgraph Bloco3 ["Bloco III: Híbrido & NoSQL"]
        A13 --> A14["🗂️ Atv 14: SQL Híbrido (JSON)"]
        A14 --> A08["🍃 Atv 08: NoSQL MongoDB"]
        A08 --> A09["🏛️ Atv 09: NoSQL Cassandra"]
        A09 --> A15["🔑 Atv 15: Chave-Valor (Redis)"]
        A15 --> A16["🕸️ Atv 16: Grafos (Neo4j)"]
        A16 --> A17["🧠 Atv 17: Vetoriais (pgvector)"]
    end
    subgraph Bloco4 ["Bloco IV: Big Data, DevOps & Projetos"]
        A17 --> A18["📈 Atv 18: DW & OLAP"]
        A18 --> A19["🔄 Atv 19: Schema Migrations"]
        A19 --> A10["🏁 Atv 10: Projeto Final - F1"]
        A10 --> A20["🏆 Atv 20: Projeto Final - F2"]
    end

🛠️ Stack Tecnológica

TecnologiaFerramentaUso na Disciplina
🛢️ MySQL 8.4WorkbenchAtividades SQL e DDL/DML
🐘 PostgreSQL 17pgAdmin / pgvectorAtividades SQL, DDL/DML, Índices, Transações, JSONB e Vetores
🍃 MongoDB 7.0CompassAtividade NoSQL (Documentos e Logs)
📦 Cassandra 4.xDocker / cqlshAtividade NoSQL (Colunas e Alta Escala)
🔑 Redis 7.xDocker / redis-cliAtividade de Cache (Chave-Valor In-Memory)
🕸️ Neo4j 5.xBrowser / CypherAtividade de Bancos em Grafos
🐳 DockerDesktopExecução de containers de todos os SGBDs e NoSQL
🐙 GitHubRepositórioVersionamento de todas as entregas

📁 Regras de Entrega

Cada aluno deve manter um único repositório público no GitHub com a seguinte estrutura de pastas:

📂 atividades-banco-de-dados/
├── 📂 bd-atv-01-setup/
├── 📂 bd-atv-02-modelagem/
├── 📂 bd-atv-03-mapeamento-algebra/
├── 📂 bd-atv-04-normalizacao/
├── 📂 bd-atv-05-sql-ddl/
├── 📂 bd-atv-06-sql-dml/
├── 📂 bd-atv-07-sql-avancado/
├── 📂 bd-atv-08-nosql-mongodb/
├── 📂 bd-atv-09-nosql-cassandra/
├── 📂 bd-atv-10-projeto-final/
├── 📂 bd-atv-11-indices-otimizacao/
├── 📂 bd-atv-12-transacoes-concorrencia/
├── 📂 bd-atv-13-triggers-procedures/
├── 📂 bd-atv-14-sql-hibrido-json/
├── 📂 bd-atv-15-chave-valor-redis/
├── 📂 bd-atv-16-grafos-neo4j/
├── 📂 bd-atv-17-vetoriais-pgvector/
├── 📂 bd-atv-18-data-warehousing-olap/
├── 📂 bd-atv-19-migrations-ci-cd/
└── 📂 bd-atv-20-projeto-final-avancado/

📅 Cronograma de Atividades vs Conteúdo Teórico

#TutorialCapítulo de Referência (20 Semanas)
01Setup do AmbienteCap. 01 e 04
02Modelagem ConceitualCap. 05 e 06
03Mapeamento e ÁlgebraCap. 07 e 09
04NormalizaçãoCap. 10
05SQL DDL (Estrutura)Cap. 11 e 12
06SQL DML (Dados)Cap. 13 e 14
07SQL Avançado (Relatórios)Cap. 15
08NoSQL MongoDBCap. 18
09NoSQL CassandraCap. 19
10Projeto Integrador Final (Fase 1)Cap. 20
11Índices e OtimizaçãoCap. 16
12Transações e ACIDCap. 03
13Stored Procedures & TriggersCap. 11, 13 e 16
14SQL Híbrido com JSONCap. 18
15Chave-Valor com RedisCap. 18 e 19
16Grafos com Neo4jCap. 18 e 19
17Bancos Vetoriais (pgvector)Cap. 18, 19 e 20
18Modelagem Dimensional DWCap. 19
19Schema MigrationsCap. 11 e 13
20Projeto Final Avançado (Fase 2)Cap. 20

💡 Dica do Especialista: A expansão de 20 atividades permite que você domine as tendências de Big Data, DevOps e IA aplicadas a bancos de dados. Siga o fluxo passo a passo e eleve o nível da sua arquitetura! 🚀🛡️

🎯 ATIVIDADE 01 — O DATA HUB DO ARQUITETO

📖 Fundamentação Teórica

Para realizar este laboratório com sucesso, certifique-se de ter compreendido os conceitos apresentados no:
👉 CAPÍTULO 01: INTRODUÇÃO E VISÃO GERAL

Bem-vindo ao início da sua jornada técnica no curso de Banco de Dados. Nesta primeira semana (4 aulas), vamos construir o seu "arsenal" de ferramentas. No mercado moderno, não basta conhecer um único banco; precisamos dominar a Persistência Poliglota. 🛡️🧩


🎯 Objetivos de Aprendizagem do Laboratório

Ao final deste laboratório prático (estimativa: 4 horas presenciais / autoguiadas), você será capaz de:

  • Instalar e validar SGBDs Relacionais (MySQL e Postgres) e NoSQL (MongoDB e Cassandra).
  • Configurar o ecossistema de versionamento (Git/GitHub) para gestão de artefatos.
  • Estruturar um ambiente de documentação técnica usando draw.io para modelagem.
  • Entender o papel de cada ferramenta no stack de um Arquiteto de Dados.

🏢 O Cenário Prático (Seu Desafio)

Você acaba de ser contratado como Analista de Infraestrutura de Dados na TecProExpress. A empresa precisa que cada desenvolvedor tenha um "Data Hub" local completo para testes e modelagem. Seu desafio é preparar este ambiente e garantir que todas as "peças" se comuniquem.


🧠 Fundamentos: A Teoria Traduzida

Por que tantas ferramentas? No mundo real, os dados têm "formas" e "velocidades" diferentes.

flowchart TD
    Computer["🖥️ Seu Computador (Windows)"]
    
    subgraph RelationalSQL ["SGBDs Relacionais (SQL)"]
        Postgres["🐘 PostgreSQL (Porta 5432)"]
        MySQL["🐬 MySQL (Porta 3306)"]
    end
    
    subgraph NoSQLDB ["SGBDs NoSQL"]
        Mongo["🍃 MongoDB (Porta 27017)"]
        Cassandra["🏛️ Cassandra (Porta 9042 / Docker Container)"]
    end

    subgraph Tools ["Ferramentas de Suporte"]
        Git["🐙 Git / GitHub (Versionamento)"]
        Drawio["🎨 draw.io (Modelagem)"]
    end
    
    Computer === RelationalSQL
    Computer === NoSQLDB
    Computer === Tools
    
    style Computer fill:#eceff1,stroke:#37474f,stroke-width:2px
    style RelationalSQL fill:#e3f2fd,stroke:#1e88e5
    style NoSQLDB fill:#e8f5e9,stroke:#4caf50
    style Tools fill:#fff8e1,stroke:#ffb300

1. SGBDs Relacionais (SQL) - O Armário de Aço

Imagine um arquivo de aço com gavetas etiquetadas. Tudo tem lugar certo e regras rígidas (Esquema Fixo).

  • MySQL: O banco de dados mais popular do mundo para aplicações web.
  • PostgreSQL: O "banco dos especialistas", focado em complexidade e integridade.

2. SGBDs NoSQL - A Caixa Flexível

Dados que mudam de formato ou chegam em alta velocidade.

  • MongoDB (Documentos): Como uma pasta com arquivos JSON. Você guarda o que quiser.
  • Cassandra (Colunas): Projetado para escalas gigantescas (Big Data), onde os dados são espalhados por vários servidores.

3. Versionamento e Container - O Passaporte e o Navio

  • Git/GitHub: O histórico de tudo o que você constrói. Funciona como um "Save Game" do seu código.
  • Docker: Permite rodar bancos complexos (como o Cassandra) sem "sujar" o seu Windows, criando mini-computadores isolados.

🛠️ O Arsenal Tecnológico (Guia de Ferramentas)

🐬 MySQL & 🐘 PostgreSQL

São os motores relacionais que daremos suporte ao longo do curso. Para interagir com eles, usamos interfaces visuais robustas:

🍃 MongoDB Compass

Diferente do SQL, o MongoDB não usa tabelas rígidas. O Compass é a interface oficial para visualizar seus dados como documentos JSON flexíveis.

🎨 draw.io: Sua Prancheta de Desenho

Antes de criar código, desenhamos a "Planta Baixa" dos dados (MER).

  • Como usar: Você pode usar online em draw.io ou baixar a versão Desktop. Ative a biblioteca de formas Entity Relation no canto inferior esquerdo para ter acesso aos símbolos do padrão pé de galinha.
  • Exportação: Sempre salve seu arquivo fonte no formato .drawio e exporte a imagem como .png (lembre-se de marcar a caixa "Incluir cópia do meu diagrama" para embutir os dados do draw.io na própria imagem).

🚀 Git & GitHub

O GitHub é o portfólio profissional do desenvolvedor moderno.

  • Git: Download Oficial do Git
  • Fluxo: Você faz o trabalho localmente -> git add . -> git commit -m "mensagem" -> git push para sincronizar com a nuvem.

📖 Exemplo Guiado: Validando a Saúde do Ambiente

Não basta instalar; é preciso testar se os "corações" dos bancos estão batendo nas portas corretas.

FerramentaPorta PadrãoO que testar?Comando / AçãoResultado Esperado
MySQL3306Conexão localAbrir terminal e rodar mysql -u root -pPrompt solicitando senha do admin
PostgreSQL5432Versão ativaNo pgAdmin ou DBeaver, rodar SELECT version();Versão 16/17 no console de dados
MongoDB27017ServiçoConectar o MongoDB Compass em mongodb://localhost:27017Visualizar bancos de sistema (admin, config)
GitN/AInstalaçãoRodar git --version no prompt/powershellExibição da versão do Git instalada

🛠️ Prática Obrigatória 1: Setup dos Motores

Cenário: Instalação oficial na TecProExpress.

  1. Instalação: Faça o download e instale o PostgreSQL (com pgAdmin), o MySQL (com Workbench ou DBeaver) e o MongoDB Compass.
  2. Cassandra via Docker: Abra o terminal do PowerShell/Bash e execute:
    docker run --name cassandra-tecpro -p 9042:9042 -d cassandra
    
    (Nota: O mapeamento -p 9042:9042 garante que o seu Windows consiga se comunicar com a porta padrão do Cassandra localmente).
  3. Evidências: Capture screenshots (prints) mostrando:
    • pgAdmin conectado ao PostgreSQL.
    • DBeaver ou MySQL Workbench conectado ao MySQL.
    • MongoDB Compass conectado com sucesso na porta 27017.
    • O container do Cassandra rodando no Docker Desktop ou via comando docker ps.

🏁 Resultado Esperado (Para sua Referência)

Ao final, você terá os serviços de banco instalados, com conexões ativas nas portas locais (3306, 5432, 27017, 9042), prontos para as práticas do semestre.

🔍 Detalhamento Técnico:

  • Mapeamento de Portas (-p): Direciona o tráfego da rede local para dentro do container isolado.
  • LTS (Long Term Support): Em produção, sempre optamos por versões estáveis (LTS) dos bancos para evitar quebras em atualizações de sistemas legados.

💻 Healthchecker de Motores Multi-Banco em Python

Para verificar automaticamente se os 4 motores de banco de dados estão ativos e respondendo nas portas locais:

# healthcheck_bancos.py
import socket

motores = [
    {"nome": "MySQL Server", "host": "127.0.0.1", "porta": 3306, "tipo": "RDBMS"},
    {"nome": "PostgreSQL Server", "host": "127.0.0.1", "porta": 5432, "tipo": "RDBMS"},
    {"nome": "MongoDB Community", "host": "127.0.0.1", "porta": 27017, "tipo": "NoSQL (Documento)"},
    {"nome": "Apache Cassandra (Docker)", "host": "127.0.0.1", "porta": 9042, "tipo": "NoSQL (Colunar)"}
]

print("=" * 68)
print("🔌 DIAGNÓSTICO DE CONEXÃO DOS MOTORES DE DADOS - TECPROEXPRESS")
print("=" * 68)

for m in motores:
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.settimeout(1.0)
    resultado = sock.connect_ex((m["host"], m["porta"]))
    sock.close()
    
    if resultado == 0:
        print(f"🟢 [ONLINE]  {m['nome']:<28} | Porta: {m['porta']:<5} | Tipo: {m['tipo']}")
    else:
        print(f"🔴 [OFFLINE] {m['nome']:<28} | Porta: {m['porta']:<5} | Tipo: {m['tipo']}")

print("=" * 68)

🖥️ Saída Esperada no Terminal:

====================================================================
🔌 DIAGNÓSTICO DE CONEXÃO DOS MOTORES DE DADOS - TECPROEXPRESS
====================================================================
🟢 [ONLINE]  MySQL Server                 | Porta: 3306  | Tipo: RDBMS
🟢 [ONLINE]  PostgreSQL Server            | Porta: 5432  | Tipo: RDBMS
🟢 [ONLINE]  MongoDB Community            | Porta: 27017 | Tipo: NoSQL (Documento)
🟢 [ONLINE]  Apache Cassandra (Docker)    | Porta: 9042  | Tipo: NoSQL (Colunar)
====================================================================

🌐 Exemplo de Payload JSON para Teste de Conectividade (Swagger /docs)

{
  "servico": "Healthchecker de Infraestrutura",
  "data_verificacao": "2026-03-01T10:00:00Z",
  "motores_ativos": [
    {"motor": "PostgreSQL", "porta": 5432, "status": "UP"},
    {"motor": "MongoDB", "porta": 27017, "status": "UP"}
  ]
}

🛠️ Prática Obrigatória 2: O Pipeline de Entrega

Cenário: Configuração do portfólio no GitHub.

  1. Repositório: Crie um repositório público chamado atividades-banco-de-dados.
  2. draw.io: Crie um diagrama conceitual simples chamado infra_hub.drawio.png mostrando seu computador conectando-se a caixas representando MySQL (3306), PostgreSQL (5432), MongoDB (27017) e Cassandra (9042).

📤 Instruções de Entrega (Microsoft Teams)

Após validar suas instalações e versionar suas pastas:

  1. Salve as evidências em uma pasta local estruturada.
  2. Exporte o diagrama de arquitetura do draw.io no formato .drawio.png.
  3. Certifique-se de fazer o push de sua pasta de setup para o seu repositório do GitHub.
  4. Envie o link do seu repositório GitHub e anexe a imagem infra_hub.drawio.png na plataforma do Microsoft Teams. (Exemplo de entrega: Atividade_01_SeuNome.drawio.png e link do repositório).

💡 Checkpoint de Lógica

Importante

Reflexão Profissional: Por que um gestor de TI prefere que você use Docker para o Cassandra em vez de instalar diretamente no Windows? (Resposta: Portabilidade e facilidade de desinstalação sem deixar rastros no sistema). 🧠🛡️

---

🔥 Desafio de Fixação (Opcional)

Nível: Sênior 🏆

Configure o DBeaver para conectar simultaneamente ao seu MySQL e ao seu PostgreSQL. Como isso facilita sua vida no dia a dia?


🔑 Gabarito de Código/Fórmulas Completo

Comandos de Versão:

-- Postgres/MySQL
SELECT version();

-- MongoDB
db.version();

-- Git
git --version

Comando Docker:

docker run --name cassandra-tecpro -p 9042:9042 -d cassandra

📊 Rubrica Formativa de Avaliação

Critério de Avaliação Insuficiente (0% - 40%) Regular (41% - 70%) Excelente (71% - 100%)
Setup e Verificação de SGBDs Não consegue conectar ou executar os comandos de verificação de versão (`SELECT version()`). Conecta em apenas um SGBD relacional, sem validar NoSQL/Docker. Valida o ambiente poliglota completo (PostgreSQL/MySQL + MongoDB + Docker Cassandra/Redis).
Diagrama da Infraestrutura Não gera o diagrama visual de topologia. Gera o diagrama mas omite portas de comunicação (ex: 5432, 27017, 9042). Diagrama de arquitetura impecável (`infra_hub.drawio.png`) mapeando bancos e portas.
Entrega e Git Workflow Entrega fora da estrutura de pastas solicitada no GitHub. Submete apenas o código SQL sem o diagrama anexado. Estrutura no GitHub impecável em `bd-atv-01-setup/` com link público submetido no Teams.

🎯 ATIVIDADE 02 — A PLANTA BAIXA DOS DADOS

📖 Fundamentação Teórica

Para realizar este laboratório com sucesso, certifique-se de ter compreendido os conceitos apresentados no:
👉 CAPÍTULO 02: FUNDAMENTOS DE SGBDS E SISTEMAS DE ARQUIVOS

Bem-vindo à segunda semana (4 aulas) do curso de Banco de Dados. Se na semana anterior preparamos as ferramentas, agora vamos aprender a usá-las para "desenhar" a solução. Na engenharia de software, antes de qualquer linha de código, criamos a Planta Baixa — o Modelo Entidade-Relacionamento (MER). 🛡️🧩


🎯 Objetivos de Aprendizagem do Laboratório

Ao final deste laboratório prático (estimativa: 4 horas presenciais / autoguiadas), você será capaz de:

  • Extrair Regras de Negócio de um briefing técnico.
  • Identificar e classificar Entidades, Atributos e Relacionamentos.
  • Definir Cardinalidades (1:1, 1:N, N:M) com precisão cirúrgica.
  • Construir diagramas profissionais no draw.io seguindo a notação de Peter Chen ou Crow's Foot.

🏢 O Cenário Prático (Seu Desafio)

A TecProExpress, uma gigante do e-commerce, precisa de um novo módulo para gerenciar suas Entregas de Última Milha. O Gerente de Operações descreveu a necessidade:

"Preciso cadastrar nossos Entregadores (nome, CPF, veículo). Cada entregador realiza diversas Entregas. Cada entrega pertence a um único Pedido (número do pedido, data, valor). Um pedido pode conter vários Produtos, e um mesmo produto pode estar em vários pedidos diferentes. Além disso, cada entrega é enviada para um Cliente (nome, endereço, CEP)."

Seu desafio como Arquiteto de Dados é transformar esse texto em um modelo que o banco de dados consiga entender.


🧠 Fundamentos: A Teoria Traduzida

Modelagem é a arte de ignorar o que não importa e focar no que é essencial.

1. Entidades (Os Substantivos)

São os objetos do mundo real sobre os quais queremos armazenar informações. Se você pode dar um nome e ele tem propriedades, é uma entidade.

  • Ex: CLIENTE, PRODUTO, VEÍCULO.

2. Atributos (Os Adjetivos)

São as características das entidades. Todo objeto tem detalhes que o definem.

  • Ex: O Cliente tem NOME, o Produto tem PREÇO.

3. Relacionamentos (Os Verbos)

É como as entidades interagem entre si.

  • Ex: Um Cliente FAZ um Pedido. Um Entregador CONDUZ um Veículo.

📊 Visualizando a Hierarquia de Abstração

flowchart TD
    A[Mundo Real] -->|Abstração| B[Modelo Conceitual - MER]
    B -->|Mapeamento| C[Modelo Lógico]
    C -->|Implementação| D[Modelo Físico - SQL]
    style B fill:#e3f2fd,stroke:#1e88e5,stroke-width:2px

📖 Exemplo Guiado: Analisando Cardinalidade

A cardinalidade define o "ritmo" do negócio. Vamos analisar o relacionamento entre PEDIDO e PRODUTO:

Regra de NegócioCardinalidadeNotação (Crow's Foot)
Um Pedido pode ter muitos produtos1:N`
Um Produto pode estar em muitos pedidos1:N`
Resultado FinalN:M (Muitos para Muitos)Necessita de tabela associativa no futuro

🛠️ Prática Obrigatória 1: O Mini-Mundo Logístico

Cenário: Identificação de componentes do cenário TecProExpress.

  1. Liste as Entidades: Identifique pelo menos 4 entidades no texto do desafio.
  2. Atributos: Para cada entidade, liste 3 atributos essenciais, marcando qual seria o Identificador Único (PK).
  3. Relacionamentos: Descreva em texto os relacionamentos (Ex: Cliente Realiza Pedido).

🏁 Resultado Esperado (Para sua Referência)

Suas entidades principais devem ser: CLIENTE, PEDIDO, PRODUTO, ENTREGADOR.

🔍 Detalhamento das Regras:

  • CLIENTE: Pessoa física ou jurídica que gera a demanda (O "Quem").
  • PEDIDO: Documento que formaliza a transação (O "O que" e "Quando").
  • ENTREGADOR: O agente que executa o serviço de Last Mile.


💻 Validador de Cardinalidades do MER em Python

Para auditar se as relações conceituais ($1:1$, $1:N$, $N:M$) respeitam as regras da TecProExpress:

# validador_mer.py
entidades_mer = {
    "CLIENTE": {"pk": "id_cliente", "cardinalidade": "1:N com PEDIDO"},
    "PEDIDO": {"pk": "id_pedido", "cardinalidade": "N:1 com CLIENTE e N:M com PRODUTO"},
    "PRODUTO": {"pk": "sku_produto", "cardinalidade": "N:M com PEDIDO"},
    "ENTREGADOR": {"pk": "cpf_entregador", "cardinalidade": "1:N com ENTREGA"}
}

print("=" * 65)
print("🗺️ AUDITORIA DE MODELO ENTIDADE-RELACIONAMENTO (MER) - TECPRO")
print("=" * 65)

for ent, meta in entidades_mer.items():
    print(f"📦 Entidade: {ent:<12} | PK: {meta['pk']:<15} | Relação: {meta['cardinalidade']}")

print("-" * 65)
print("✅ Validação: Todas as entidades possuem Chave Primária atômica.")
print("=" * 65)

🖥️ Saída Esperada no Terminal:

=================================================================
🗺️ AUDITORIA DE MODELO ENTIDADE-RELACIONAMENTO (MER) - TECPRO
=================================================================
📦 Entidade: CLIENTE      | PK: id_cliente      | Relação: 1:N com PEDIDO
📦 Entidade: PEDIDO       | PK: id_pedido       | Relação: N:1 com CLIENTE e N:M com PRODUTO
📦 Entidade: PRODUTO      | PK: sku_produto     | Relação: N:M com PEDIDO
📦 Entidade: ENTREGADOR   | PK: cpf_entregador  | Relação: 1:N com ENTREGA
-----------------------------------------------------------------
✅ Validação: Todas as entidades possuem Chave Primária atômica.
=================================================================

🌐 Exemplo de Payload JSON para Catálogo de Entidades (Swagger /docs)

{
  "entidade": "PEDIDO",
  "atributos": [
    {"nome": "id_pedido", "tipo": "INTEGER", "pk": true},
    {"nome": "data_emissao", "tipo": "DATETIME", "pk": false},
    {"nome": "valor_total", "tipo": "DECIMAL(10,2)", "pk": false}
  ],
  "relacionamentos": [
    {"alvo": "CLIENTE", "tipo": "N:1", "fk": "cliente_id"},
    {"alvo": "PRODUTO", "tipo": "N:M", "tabela_associativa": "item_pedido"}
  ]
}

🛠️ Prática Obrigatória 2: Modelagem no draw.io

Cenário: Transformação da análise em diagrama visual de banco de dados.

🧭 Mini-Guia do draw.io:

  1. Acesse o site draw.io.
  2. No menu esquerdo inferior, clique em Mais Formas (More Shapes).
  3. Marque a caixa Relação de Entidades (Entity Relation) e clique em aplicar.
  4. Utilize o componente retangular com três divisões para desenhar a Entidade (Nome) e seus Atributos (com PK marcada).
  5. Para desenhar os relacionamentos com cardinalidade pé de galinha, use as setas do grupo "Relação de Entidades" (ex: as conexões de 1 para N possuem a barra vertical | de um lado e as três ramificações < do outro).
  6. Exportação: Salve o arquivo fonte como .drawio e exporte a imagem final em formato .png (lembre-se de marcar a opção de embutir os metadados do diagrama na imagem).

📤 Instruções de Entrega (Microsoft Teams)

Após validar a estrutura visual do seu diagrama conceitual:

  1. Certifique-se de que todas as entidades e cardinalidades (Crow's Foot) estão legíveis.
  2. Salve o arquivo fonte do diagrama com a extensão .drawio.
  3. Exporte a imagem em alta resolução no formato .png.
  4. Envie ambos os arquivos (Atividade_02_SeuNome.drawio e Atividade_02_SeuNome.png) na tarefa correspondente no Microsoft Teams. (Nesta etapa, não há arquivos de código .sql a serem entregues).

💡 Checkpoint de Lógica

Importante

A Importância da Chave Primária: Por que não usamos o NOME de um cliente como identificador único? Na indústria, nomes se repetem (homônimos). Um erro na modelagem da PK (Primary Key) pode causar a mistura de dados de clientes diferentes. Sempre escolha atributos imutáveis e únicos, como CPF, Código de Barra ou IDs auto-incrementais. 🧠🛡️

---

🔥 Desafio de Fixação (Opcional)

Nível: Arquiteto 🏆

Adicione ao modelo a entidade OCORRÊNCIA (Ex: "Endereço não encontrado", "Cliente ausente"). Uma Entrega pode ter várias ocorrências ao longo do tempo. Como isso altera o seu diagrama?


🔑 Gabarito de Código/Fórmulas

Prática 1 (Sugestão de Análise):

  • Entidades: CLIENTE, PEDIDO, PRODUTO, ENTREGADOR.
  • PKs sugeridas: CPF_Cliente, ID_Pedido, SKU_Produto, CPF_Entregador.

Prática 2 (Lógica do Diagrama):

CLIENTE (1) ---- <Realiza> ---- (N) PEDIDO
PEDIDO (N) ---- <Contém> ---- (M) PRODUTO
ENTREGADOR (1) ---- <Efetua> ---- (N) ENTREGA
ENTREGA (1) ---- <Refere-se> ---- (1) PEDIDO

Modelo Entidade-Relacionamento TecProExpress (DER/ERD)


📊 Rubrica Formativa de Avaliação

Critério de Avaliação Insuficiente (0% - 40%) Regular (41% - 70%) Excelente (71% - 100%)
Modelagem Conceitual (DER) Omite entidades principais ou confunde atributos com entidades. Cria as entidades mas com cardinalidades erradas. Mapeia todas as entidades do negócio com cardinalidades Crow's Foot perfeitas.
Identificação de Chaves Primárias Utiliza atributos repetíveis/inseguros (como nomes) para PKs. Mapeia PKs em apenas metade das entidades. Define PKs imutáveis e únicas para todas as entidades do modelo.
Entrega dos Arquivos (.drawio e .png) Entrega apenas a imagem sem o arquivo editável `.drawio`. Entrega os arquivos mas com imagem em baixa resolução. Submete `Atividade_02_SeuNome.drawio` e `.png` em alta definição.

🎯 ATIVIDADE 03 — DO DESENHO À GRADE

📖 Fundamentação Teórica

Para realizar este laboratório com sucesso, certifique-se de ter compreendido os conceitos apresentados no:
👉 CAPÍTULO 03: TRANSAÇÕES ACID E ECOSSISTEMA CLOUD

Bem-vindo à terceira semana (4 aulas) do curso de Banco de Dados. Agora que você já sabe "desenhar" as necessidades do cliente (Modelo Conceitual), vamos aprender a traduzir esse desenho para a linguagem que os computadores entendem: o Modelo Lógico (Relacional) e a matemática que rege tudo isso, a Álgebra Relacional. 🛡️🧩


🎯 Objetivos de Aprendizagem do Laboratório

Ao final deste laboratório prático (estimativa: 4 horas presenciais / autoguiadas), você será capaz de:

  • Mapear Entidades e Relacionamentos para Tabelas e Colunas.
  • Implementar a Integridade Referencial através de Chaves Estrangeiras (FK).
  • Executar operações de Seleção, Projeção e Junção usando Álgebra Relacional.
  • Criar diagramas lógicos profissionais no draw.io.

🏢 O Cenário Prático (Seu Desafio)

A TecProExpress aprovou seu MER (Atividade 02). Agora, a equipe de desenvolvimento precisa que você entregue o Esquema Lógico. Eles precisam saber exatamente quais colunas cada tabela terá e como elas se conectam via IDs.

Além disso, o time de BI (Business Intelligence) solicitou que você descreva, de forma lógica, como extrair relatórios específicos (Ex: "Quais entregadores estão usando veículos do tipo 'Caminhão'?").


🧠 Fundamentos: A Teoria Traduzida

O mapeamento é a ponte entre o abstrato e o concreto.

1. A Anatomia do Mapeamento

Mapear é transformar "caixas" em "tabelas". Veja a regra visual:

graph LR
    subgraph Conceitual ["Nível Conceitual (MER)"]
    A["Entidade: CLIENTE"] --- B(("Atributo: Nome"))
    end
    
    Conceitual -->|Mapeamento| Logico
    
    subgraph Logico ["Nível Lógico (Tabelas)"]
    C["Tabela: cliente"]
    C --- D["Coluna: nome (VARCHAR)"]
    end
    
    style Conceitual fill:#e3f2fd,stroke:#1e88e5
    style Logico fill:#f1f8e9,stroke:#558b2f

2. Álgebra Relacional: Os Três Pilares

O banco de dados não "procura" dados; ele realiza operações matemáticas sobre conjuntos.

OperaçãoSímboloFunção VisualAnalogia
SeleçãoσFiltra LinhasUm filtro de busca por preço
ProjeçãoπFiltra ColunasEscolher quais campos exibir
JunçãoCombina TabelasCruzar dados de duas planilhas
flowchart TD
    subgraph Sigma ["σ Seleção (Horizontal)"]
    S1[Linha 1] --- S2[Linha 2] --- S3[Linha 3]
    end
    
    subgraph Pi ["π Projeção (Vertical)"]
    P1[Coluna A] 
    P2[Coluna B]
    end
    
    Sigma -.->|Filtra| R1[Resultado]
    Pi -.->|Escolhe| R1

📖 Exemplo Guiado: Mapeando 1:N

No cenário da TecProExpress:

  • Entidade: ENTREGADOR (1)
  • Entidade: VEÍCULO (N) - Assumindo que um entregador pode usar vários veículos.

🏁 Resultado Esperado (Modelo Lógico)

  1. ENTREGADOR (id PK, nome, cpf UNIQUE, telefone)
  2. VEICULO (id PK, placa UNIQUE, modelo, ano, id_entregador FK)

🔍 Detalhamento do Mapeamento:

  • PK (Primary Key): Identificador único sequencial (geralmente numérico auto-incremental).
  • FK (Foreign Key): A PK do "lado 1" (Entregador) que viaja para o "lado N" (Veículo) como id_entregador.
  • UNIQUE: Restrição que impede duplicidade (ex: não podem existir dois entregadores com o mesmo CPF ou duas placas iguais).
  • Integridade: Garante que não exista um veículo sem um entregador responsável.

🛠️ Prática Obrigatória 1: Mapeamento Logístico

Cenário: Transformação do MER da TecProExpress para o Modelo Lógico.

  1. Com base no cenário da Atividade 02, escreva o esquema lógico das tabelas: CLIENTE, PEDIDO, PRODUTO e ENTREGADOR.
  2. Indique claramente quais são as Primary Keys (PK) e as Foreign Keys (FK).

🏁 Resultado Esperado (Para sua Referência)

CLIENTE (id PK, nome, cpf UNIQUE, endereco)
PEDIDO (id PK, data, id_cliente FK)
PRODUTO (id PK, sku UNIQUE, nome, preco)

🔍 Detalhamento das Decisões:

  • CLIENTE: Adotamos id como PK numérica para otimização do banco, mantendo o cpf como coluna UNIQUE (regra de negócio).
  • PEDIDO: Recebe id_cliente como FK para conectar com o cliente associado.
  • PRODUTO: Usa id como PK, mantendo o SKU como chave única secundária para fins de código de barras industrial.


💻 Simulador de Álgebra Relacional em Python

Para inspecionar as operações de Seleção ($\sigma$), Projeção ($\pi$) e Junção ($\bowtie$) no terminal:

# simulador_algebra.py
entregadores = [
    {"cpf": "111", "nome": "Ana Silva", "veiculo_id": 10},
    {"cpf": "222", "nome": "João Souza", "veiculo_id": 20},
    {"cpf": "333", "nome": "Bia Costa", "veiculo_id": 10}
]

veiculos = [
    {"id": 10, "tipo": "Caminhão", "modelo": "Mercedes 710"},
    {"id": 20, "tipo": "Moto", "modelo": "Honda Cargo"}
]

print("=" * 65)
print("🧮 MOTOR DE ÁLGEBRA RELACIONAL (σ, π, ⋈) - TECPROEXPRESS")
print("=" * 65)

# 1. Junção Natural (⋈): Entregador ⋈ Veículo
join_result = []
for e in entregadores:
    for v in veiculos:
        if e["veiculo_id"] == v["id"]:
            join_result.append({**e, **v})

# 2. Seleção (σ): tipo = 'Caminhão'
caminhoneiros = [r for r in join_result if r["tipo"] == "Caminhão"]

# 3. Projeção (π): nome, modelo
projecao = [{"nome": r["nome"], "modelo": r["modelo"]} for r in caminhoneiros]

print(f"📊 Resultado: π_nome,modelo (σ_tipo='Caminhão' (Entregador ⋈ Veículo)):")
for linha in projecao:
    print(f"   • Motorista: {linha['nome']:<12} | Veículo: {linha['modelo']}")
print("=" * 65)

🖥️ Saída Esperada no Terminal:

=================================================================
🧮 MOTOR DE ÁLGEBRA RELACIONAL (σ, π, ⋈) - TECPROEXPRESS
=================================================================
📊 Resultado: π_nome,modelo (σ_tipo='Caminhão' (Entregador ⋈ Veículo)):
   • Motorista: Ana Silva    | Veículo: Mercedes 710
   • Motorista: Bia Costa    | Veículo: Mercedes 710
=================================================================

🌐 Exemplo de Payload JSON para Filtro Algébrico (Swagger /docs)

{
  "operacao": "SELECAO_E_JUNCAO",
  "tabela_origem": "entregador",
  "tabela_join": "veiculo",
  "chave_estrangeira": "veiculo_id",
  "filtro_sigma": {"veiculo.tipo": "Caminhão"},
  "projecao_pi": ["entregador.nome", "veiculo.modelo"]
}

🛠️ Prática Obrigatória 2: Laboratório de Álgebra

Cenário: Extração de lógica para relatórios usando as tabelas abaixo (Seeds).

📋 Seed: Tabela ENTREGADOR

cpfnomeveiculo_id
111Ana Silva10
222João Souza20
333Bia Costa10

📋 Seed: Tabela VEICULO

idtipomodelo
10CaminhãoMercedes 710
20MotoHonda Cargo

🚀 Script de Seed (SQL)

-- PASSO 1: Criar as Tabelas (Estrutura)
CREATE TABLE veiculo (
    id INT PRIMARY KEY,
    tipo VARCHAR(50),
    modelo VARCHAR(100)
);

CREATE TABLE entregador (
    cpf VARCHAR(11) PRIMARY KEY,
    nome VARCHAR(100),
    veiculo_id INT,
    FOREIGN KEY (veiculo_id) REFERENCES veiculo(id)
);

-- PASSO 2: Popular as Tabelas (Dados)
INSERT INTO veiculo (id, tipo, modelo) VALUES (10, 'Caminhão', 'Mercedes 710');
INSERT INTO veiculo (id, tipo, modelo) VALUES (20, 'Moto', 'Honda Cargo');

INSERT INTO entregador (cpf, nome, veiculo_id) VALUES ('111', 'Ana Silva', 10);
INSERT INTO entregador (cpf, nome, veiculo_id) VALUES ('222', 'João Souza', 20);
INSERT INTO entregador (cpf, nome, veiculo_id) VALUES ('333', 'Bia Costa', 10);

🔍 Detalhamento do Seed:

  • Ordem: Inserimos primeiro o VEICULO porque o ENTREGADOR depende dele (FK).
  • Vínculo: Note que o veiculo_id 10 é compartilhado por Ana e Bia.

📤 Instruções de Entrega (Microsoft Teams)

Após finalizar o mapeamento e as fórmulas matemáticas:

  1. Digite a sua resposta contendo o mapeamento lógico e as fórmulas equivalentes da Álgebra Relacional (pode ser em formato texto no Word/Markdown ou PDF).
  2. Salve o arquivo com a nomenclatura Atividade_03_SeuNome.pdf ou Atividade_03_SeuNome.md.
  3. Envie a resposta na plataforma correspondente do Microsoft Teams para avaliação técnica. (Esta atividade é conceitual e foca na matemática relacional, sem necessidade de código .sql executável).

💡 Checkpoint de Lógica

Importante

Reflexão Profissional: Imagine que você tem 1 milhão de clientes. É mais eficiente fazer a Seleção (σ) antes ou depois da Junção (⋈)? Na indústria, filtrar os dados antes de cruzar tabelas economiza processamento e tempo. 🧠🛡️

---

🔥 Desafio de Fixação (Opcional)

Nível: Expert 🏆

O relacionamento entre PEDIDO e PRODUTO é Muitos para Muitos (N:M). Como mapear isso para o modelo lógico?

🏁 Resultado Esperado do Desafio

ITEM_PEDIDO (id_pedido FK, sku_produto FK, quantidade)

🔍 Explicação da Solução:

Relacionamentos N:M sempre geram uma terceira tabela (Associativa). Esta tabela carrega as chaves estrangeiras de ambos os lados e as transforma em uma Chave Primária Composta.


🔑 Gabarito de Código/Fórmulas Completo

Prática 1 (Mapeamento Completo):

  • CLIENTE (id PK, nome, cpf UNIQUE, endereco)
  • PRODUTO (id PK, sku UNIQUE, nome, preco)
  • PEDIDO (id PK, data, id_cliente FK, id_entregador FK)
  • ENTREGADOR (id PK, nome, cpf UNIQUE, veiculo_id FK)
  • VEICULO (id PK, placa UNIQUE, modelo, tipo)

Prática 2 (Álgebra vs SQL Passo a Passo):

  1. Objetivo: Nomes dos Entregadores.

    • Fórmula: π nome (ENTREGADOR)
    • SQL Equivalente:
      SELECT nome FROM entregador;
      
  2. Objetivo: Filtro de Caminhões.

    • Fórmula: σ tipo='Caminhão' (VEICULO)
    • SQL Equivalente:
      SELECT * FROM veiculo WHERE tipo = 'Caminhão';
      
  3. Objetivo: Cruzamento de Dados (Nome + Modelo).

    • Fórmula: π nome, modelo (ENTREGADOR ⋈ veiculo_id=id VEICULO)
    • SQL Equivalente:
      SELECT e.nome, v.modelo 
      FROM entregador e 
      INNER JOIN veiculo v ON e.veiculo_id = v.id;
      

Desafio (Mapeamento N:M):

  • Lógico: ITEM_PEDIDO (id_pedido FK, sku_produto FK, quantidade)
  • SQL de Criação (com as tabelas-pai pedido e produto para as FKs resolverem):
    CREATE TABLE pedido (
        id INT PRIMARY KEY,
        data_pedido DATE
    );
    
    CREATE TABLE produto (
        sku VARCHAR(50) PRIMARY KEY,
        nome VARCHAR(100)
    );
    
    CREATE TABLE item_pedido (
        id_pedido INT,
        sku_produto VARCHAR(50),
        quantidade INT,
        PRIMARY KEY (id_pedido, sku_produto),
        FOREIGN KEY (id_pedido) REFERENCES pedido(id),
        FOREIGN KEY (sku_produto) REFERENCES produto(sku)
    );
    

📊 Rubrica Formativa de Avaliação

Critério de Avaliação Insuficiente (0% - 40%) Regular (41% - 70%) Excelente (71% - 100%)
Mapeamento Lógico & Tabela Associativa Não consegue mapear relacionamentos N:M ou omite Chaves Estrangeiras (FKs). Cria a tabela associativa mas sem a Chave Primária Composta. Mapeia perfeitamente a tabela associativa `ITEM_PEDIDO` com PK composta e FKs corretas.
Equivalência Álgebra Relacional vs SQL Erros conceituais na tradução de Projeção ($\pi$), Seleção ($\sigma$) e Junção ($\bowtie$). Traduz 2 das 3 expressões corretamente. Tradução impecável das 3 expressões de Álgebra Relacional para SQL ANSI.
Entrega no GitHub Entrega fora da pasta `bd-atv-03-mapeamento/`. Arquivo entregue mas sem a formatação em tabela. `Atividade_03.md` entregue com formatação Markdown e SQL validado.

🎯 ATIVIDADE 04 — A ARTE DA ORGANIZAÇÃO

📖 Fundamentação Teórica

Para realizar este laboratório com sucesso, certifique-se de ter compreendido os conceitos apresentados no:
👉 CAPÍTULO 04: SETUP POLIGLOTA E CONEXÕES

Bem-vindo à quarta semana (4 aulas) do curso de Banco de Dados. Até aqui, você aprendeu a modelar o mundo real. Mas, às vezes, nossa modelagem inicial contém "armadilhas" — dados repetidos que causam erros. Hoje, vamos aprender a técnica de Normalização, o processo de refinamento que separa o bom design do amadorismo. 🛡️🧩


🎯 Objetivos de Aprendizagem do Laboratório

Ao final deste laboratório prático (estimativa: 4 horas presenciais / autoguiadas), você será capaz de:

  • Identificar e corrigir Anomalias de Inserção, Exclusão e Alteração.
  • Aplicar a 1ª Forma Normal (1FN): Atomicidade.
  • Aplicar a 2ª Forma Normal (2FN): Dependência Total.
  • Aplicar a 3ª Forma Normal (3FN): Dependência Transitiva.

🏢 O Cenário Prático (Seu Desafio)

A TecProExpress herdou um banco de dados de uma empresa adquirida. Os dados estão em uma única tabela chamada PLANILHA_MESTRA. Quando um cliente muda de endereço, o sistema precisa atualizar centenas de linhas, gerando inconsistências.

📋 Seed: A Planilha "Bagunçada" (Não Normalizada)

Cod_PedClienteEnderecoProdutosValor_Un
1001João SilvaRua A, 10Pneus, Óleo400.00, 50.00
1002Maria SouzaRua B, 20Filtro Ar80.00

🧠 Fundamentos: A Teoria Traduzida

Normalizar é como organizar uma biblioteca por categorias, autores e títulos, em vez de empilhar tudo na entrada.

O Fluxo da Organização

Veja como os dados "evoluem" durante a normalização:

flowchart TD
    subgraph Erro ["Estado Crítico"]
    A["Tabela Única (Caos)"]
    end
    
    subgraph FN1 ["1ª Forma Normal"]
    B["Valores Atômicos (Sem listas)"]
    end
    
    subgraph FN2 ["2ª Forma Normal"]
    C["Chaves Primárias Definidas"]
    end
    
    subgraph FN3 ["3ª Forma Normal"]
    D["Tabelas Independentes (Padrão Indústria)"]
    end
    
    A -->|Dividir listas| B
    B -->|Mover campos parciais| C
    C -->|Mover campos indiretos| D
    
    style A fill:#ffcdd2
    style D fill:#c8e6c9

📖 Exemplo Guiado: Aplicando a 1FN

Problema: A coluna Produtos tem "Pneus, Óleo". O banco de dados não consegue somar o estoque assim. Solução: Cada produto deve ter sua própria linha.

Cod_PedProduto
1001Pneus
1001Óleo

🛠️ Prática Obrigatória 1: Diagnóstico de Anomalias

Cenário: Analise a PLANILHA_MESTRA da TecProExpress.

  1. Aponte 2 anomalias que ocorrem se deletarmos o pedido 1002.
  2. Explique por que o endereço do cliente não deve ficar na tabela de pedidos.

🏁 Resultado Esperado (Para sua Referência)

  • Anomalia de Exclusão: Ao deletar o pedido, perdemos os dados de contato da Maria Souza.
  • Redundância: O endereço se repete em cada pedido do mesmo cliente.


💻 Refatorador de Anomalias de Normalização (1FN a 3FN) em Python

Para comparar a redução de redundância e a eliminação de anomalias de atualização:

# normalizador_3fn.py
planilha_desnormalizada = [
    {"pedido": 1001, "cliente": "João Silva", "endereco": "Rua A, 10", "item": "Pneu", "preco": 350.0},
    {"pedido": 1001, "cliente": "João Silva", "endereco": "Rua A, 10", "item": "Óleo", "preco": 45.0}
]

# Modelo Normalizado (3FN)
clientes_db = {1: {"nome": "João Silva", "endereco": "Rua A, 10"}}
produtos_db = {50: {"nome": "Pneu", "preco": 350.0}, 51: {"nome": "Óleo", "preco": 45.0}}
itens_pedido_db = [
    {"pedido_id": 1001, "cliente_id": 1, "produto_id": 50, "qtd": 2},
    {"pedido_id": 1001, "cliente_id": 1, "produto_id": 51, "qtd": 1}
]

print("=" * 65)
print("📐 REFATORAÇÃO DE DADOS EM 3ª FORMA NORMAL - TECPROEXPRESS")
print("=" * 65)
print("🏠 Atualizando endereço de João Silva (Atualiza 1 único registro):")
clientes_db[1]["endereco"] = "Av. Paulista, 2000"
print(f"   • Novo Endereço no Banco: {clientes_db[1]['endereco']}")

print("\n🧾 Itens do Pedido #1001 sincronizados automaticamente:")
for item in itens_pedido_db:
    cli = clientes_db[item["cliente_id"]]
    prod = produtos_db[item["produto_id"]]
    print(f"   • Item: {prod['nome']:<10} | Qtd: {item['qtd']} | Destinatário: {cli['nome']} ({cli['endereco']})")
print("=" * 65)

🖥️ Saída Esperada no Terminal:

=================================================================
📐 REFATORAÇÃO DE DADOS EM 3ª FORMA NORMAL - TECPROEXPRESS
=================================================================
🏠 Atualizando endereço de João Silva (Atualiza 1 único registro):
   • Novo Endereço no Banco: Av. Paulista, 2000

🧾 Itens do Pedido #1001 sincronizados automaticamente:
   • Item: Pneu       | Qtd: 2 | Destinatário: João Silva (Av. Paulista, 2000)
   • Item: Óleo       | Qtd: 1 | Destinatário: João Silva (Av. Paulista, 2000)
=================================================================

🌐 Exemplo de Payload JSON Normalizado em 3FN (Swagger /docs)

{
  "cliente_id": 1,
  "itens": [
    {"produto_id": 50, "quantidade": 2},
    {"produto_id": 51, "quantidade": 1}
  ]
}

🛠️ Prática Obrigatória 2: O Esquema 3FN

Cenário: Projete o banco normalizado no draw.io.

  1. Crie tabelas separadas para: CLIENTE, PEDIDO, PRODUTO e ITEM_PEDIDO.
  2. Mova o Endereco para a tabela CLIENTE.
  3. Mova o Valor_Un para a tabela PRODUTO.

🏁 Resultado Esperado (Seed das Tabelas Normalizadas)

Tabela CLIENTE:

idnomeendereco
1João SilvaRua A, 10

Tabela ITEM_PEDIDO:

id_pedid_prodqtd
100150 (Pneu)2

🔍 Detalhamento Técnico:

  • Atomicidade: Agora cada célula tem apenas um valor.
  • Relacionamento: Usamos IDs (FKs) para conectar as tabelas sem repetir nomes ou endereços.

📤 Instruções de Entrega (Microsoft Teams)

Após projetar seu banco de dados normalizado na 3ª Forma Normal:

  1. Exporte a imagem do diagrama conceitual/lógico normalizado no formato .png.
  2. Salve o arquivo fonte do draw.io no formato .drawio.
  3. Caso tenha escrito scripts SQL adicionais para testes, você pode anexá-los.
  4. Envie ambos os arquivos (Atividade_04_SeuNome.drawio e Atividade_04_SeuNome.png) na tarefa correspondente no Microsoft Teams para validação de integridade física.

💡 Checkpoint de Lógica

Importante

Reflexão Profissional: Um banco normalizado economiza espaço em disco, mas exige mais "JOINS" nas consultas. Na TecProExpress, a prioridade é a Integridade dos Dados. 🧠🛡️

---

🔥 Desafio de Fixação (Opcional)

Nível: Expert 🏆

Onde você armazenaria o Preço de Venda? Na tabela PRODUTO ou na tabela ITEM_PEDIDO? (Dica: Pense no que acontece se o preço do produto mudar amanhã).


🔑 Gabarito de Código/Fórmulas Completo

Mapeamento 3FN Final:

  • CLIENTE (id PK, nome, endereco)
  • PRODUTO (id PK, descricao, valor_unitario)
  • PEDIDO (id PK, data, id_cliente FK)
  • ITEM_PEDIDO (id_pedido FK, id_produto FK, quantidade, valor_historia)

SQL de Criação (Gabarito):

CREATE TABLE cliente (
    id INT PRIMARY KEY AUTO_INCREMENT,
    nome VARCHAR(100),
    endereco VARCHAR(200)
);

CREATE TABLE produto (
    id INT PRIMARY KEY AUTO_INCREMENT,
    descricao VARCHAR(100),
    valor_unitario DECIMAL(10,2)
);

CREATE TABLE pedido (
    id INT PRIMARY KEY AUTO_INCREMENT,
    data DATE,
    id_cliente INT,
    FOREIGN KEY (id_cliente) REFERENCES cliente(id)
);

CREATE TABLE item_pedido (
    id_pedido INT,
    id_produto INT,
    quantidade INT,
    PRIMARY KEY (id_pedido, id_produto),
    FOREIGN KEY (id_pedido) REFERENCES pedido(id),
    FOREIGN KEY (id_produto) REFERENCES produto(id)
);

🔍 Explicação do Gabarito:

  • PRIMARY KEY (id_pedido, id_produto): Garante que um produto não seja inserido duas vezes no mesmo pedido.
  • valor_historia: Armazena o preço cobrado no dia, garantindo que relatórios antigos não mudem de valor se o produto encarecer hoje.

📊 Rubrica Formativa de Avaliação

Critério de Avaliação Insuficiente (0% - 40%) Regular (41% - 70%) Excelente (71% - 100%)
Aplicação das Formas Normais (1FN a 3FN) Mantém campos multivalorados (1FN) ou dependências parciais/transitivas (2FN/3FN). Normaliza até a 2FN mas mantém dependências transitivas. Aplica 1FN, 2FN e 3FN perfeitamente, garantindo eliminação de redundâncias.
Preservação de Histórico de Preços Omite o campo de valor histórico na tabela de itens do pedido. Cria o campo mas sem entender o conceito de imutabilidade histórica. Justifica e mapeia o atributo de valor histórico (`valor_historia`) em `ITEM_PEDIDO`.
Entrega no GitHub Entrega fora da pasta `bd-atv-04-normalizacao/`. Arquivo entregue mas sem o DDL SQL normalizado. `Atividade_04.md` publicado no GitHub com esquemas 3FN e SQL validado.

🎯 ATIVIDADE 05 — LEVANTANDO AS PAREDES

📖 Fundamentação Teórica

Para realizar este laboratório com sucesso, certifique-se de ter compreendido os conceitos apresentados no:
👉 CAPÍTULO 05: MODELO RELACIONAL E INTRODUÇÃO À MODELAGEM

Bem-vindo à quinta semana (4 aulas) do curso de Banco de Dados. Hoje vamos abrir o terminal e "levantar as paredes" do nosso sistema usando a DDL (Data Definition Language). No mercado, um script DDL bem escrito é a fundação de qualquer software de sucesso. 🛡️🧩


🎯 Objetivos de Aprendizagem do Laboratório

Ao final deste laboratório prático (estimativa: 4 horas presenciais / autoguiadas), você será capaz de:

  • Criar bancos de dados reais no MySQL e PostgreSQL.
  • Implementar tabelas usando o comando CREATE TABLE.
  • Configurar restrições de integridade (PK, FK, NOT NULL, UNIQUE, CHECK).
  • Dominar a Ordem de Criação dos objetos.

🏢 O Cenário Prático (Seu Desafio)

A TecProExpress precisa que você execute o script oficial de criação do banco. O CTO exige que o script seja "limpo" e que as chaves estrangeiras tenham nomes padronizados (ex: fk_pedido_cliente).

📊 Hierarquia de Criação (O que vem primeiro?)

Para evitar erros de "tabela não encontrada", siga esta ordem lógica:

graph TD
    A["1. Tabelas Independentes (Pais)"] --> B["2. Tabelas Dependentes (Filhos)"]
    B --> C["3. Tabelas Associativas (Netos)"]
    
    subgraph Exemplos
    E1[CLIENTE] --- E2[PRODUTO]
    E3[PEDIDO] --- E4[ITEM_PEDIDO]
    end
    
    E1 --> E3
    E2 --> E4
    E3 --> E4
    
    style A fill:#e8f5e9
    style C fill:#fffde7

Mapeamento DDL & Constraints SQL


🧠 Fundamentos: A Teoria Traduzida

DDL é a engenharia civil dos dados.

ComandoFunçãoAnalogia
CREATECria objetosConstruir a casa
ALTERModifica objetosFazer uma reforma
DROPDeleta objetosDemolir a estrutura

📖 Exemplo Guiado: Criando a Tabela Produto

Veja como definir tipos de dados e restrições no PostgreSQL (SGBD Foco) e no MySQL (Comparação).

🐬 Iniciando o Banco de Dados

Antes de criar qualquer tabela, é mandatório criar o container lógico (Banco de Dados) e dizer ao SGBD qual banco utilizar:

-- Executar no Console de Query (pgAdmin/DBeaver/Workbench)
CREATE DATABASE tecpro_express;

No pgAdmin (PostgreSQL), após criar o banco, você deve clicar com o botão direito no banco tecpro_express na árvore de navegação esquerda e selecionar Query Tool para garantir que as tabelas sejam criadas dentro dele. No MySQL Workbench/DBeaver, use o comando:

USE tecpro_express;

🛠️ Código do Exemplo (Equivalência Relacional)

-- 🐘 Padrão PostgreSQL (pgAdmin)
CREATE TABLE produto (
    id SERIAL PRIMARY KEY,               -- SERIAL gera inteiros sequenciais automáticos
    nome VARCHAR(100) NOT NULL,          -- Texto obrigatório
    preco DECIMAL(10,2) CHECK (preco > 0) -- Validação de integridade física
);
-- 🐬 Padrão MySQL (Workbench)
CREATE TABLE produto (
    id INT PRIMARY KEY AUTO_INCREMENT,   -- AUTO_INCREMENT gera inteiros sequenciais
    nome VARCHAR(100) NOT NULL,
    preco DECIMAL(10,2) CHECK (preco > 0)
);

🔍 Detalhamento do Código:

  • SERIAL vs AUTO_INCREMENT: Ambas as cláusulas servem para gerar números sequenciais únicos automáticos (Chaves Primárias Surrogadas). O PostgreSQL usa o tipo virtual SERIAL, enquanto o MySQL usa o modificador AUTO_INCREMENT no tipo INT.
  • DECIMAL(10,2): Ideal para armazenar valores monetários (10 dígitos totais de precisão, sendo 2 após a vírgula).
  • CHECK: Restrição física de coluna que impede que a TecProExpress registre preços menores ou iguais a zero.

🛠️ Prática Obrigatória 1: Script de Infraestrutura

Cenário: Criação das tabelas CLIENTE e PEDIDO.

  1. A tabela CLIENTE deve ter um ID auto-incremental e um CPF único.
  2. A tabela PEDIDO deve ter uma FK apontando para CLIENTE.

🏁 Resultado Esperado (Seed Visual)

Ao rodar DESCRIBE pedido; no seu banco, você deverá ver:

  • id_cliente marcado como MUL (Multiple) ou FK.

🛠️ Prática Obrigatória 2: O Elo Perdido (Associativa)

Cenário: Implementação da tabela ITEM_PEDIDO.

  1. Esta tabela deve conectar PEDIDO e PRODUTO (Tabela Associativa N:M).
  2. Adicione a coluna quantidade com valor padrão 1.
  3. Implemente a Chave Primária Composta formada pelas duas chaves estrangeiras.

🏁 Resultado Esperado (Script SQL Correto)

CREATE TABLE item_pedido (
    id_pedido INT,
    id_produto INT,
    quantidade INT DEFAULT 1,
    PRIMARY KEY (id_pedido, id_produto),
    CONSTRAINT fk_item_pedido_ped FOREIGN KEY (id_pedido) REFERENCES pedido(id),
    CONSTRAINT fk_item_pedido_prod FOREIGN KEY (id_produto) REFERENCES produto(id)
);

💻 Execução do Script DDL & Logs no Terminal do SGBD

Para inspecionar a criação estrutural das tabelas via terminal psql / mysql:

# Executando o script DDL no terminal do PostgreSQL
psql -U tecpro_admin -d tecpro_express -f ddl_schema.sql

🖥️ Saída Esperada no Terminal:

CREATE DATABASE
You are now connected to database "tecpro_express" as user "tecpro_admin".
CREATE TABLE (cliente)
CREATE TABLE (produto)
CREATE TABLE (pedido)
CREATE TABLE (item_pedido)
-----------------------------------------------------------------
✨ Schema relacional criado com sucesso com 4 tabelas e constraints de integridade!

🌐 Exemplo de Payload JSON para Catálogo DDL (Swagger /docs)

{
  "tabela": "item_pedido",
  "colunas": [
    {"nome": "id_pedido", "tipo": "INTEGER", "nulo": false},
    {"nome": "id_produto", "tipo": "INTEGER", "nulo": false},
    {"nome": "quantidade", "tipo": "INTEGER", "padrao": 1}
  ],
  "chave_primaria_composta": ["id_pedido", "id_produto"],
  "chaves_estrangeiras": [
    {"coluna": "id_pedido", "referencia": "pedido(id)"},
    {"coluna": "id_produto", "referencia": "produto(id)"}
  ]
}

📤 Instruções de Entrega (Microsoft Teams)

Após testar os comandos e confirmar a criação bem-sucedida das tabelas no pgAdmin (ou SGBD de sua preferência):

  1. Organize as instruções em ordem cronológica de criação (pais antes dos filhos).
  2. Salve as instruções SQL em um único arquivo de texto com a extensão .sql contendo CREATE DATABASE no início.
  3. Envie o arquivo (Atividade_05_SeuNome.sql) na tarefa correspondente no Microsoft Teams para avaliação de sintaxe e restrições.

💡 Checkpoint de Lógica

Importante

Reflexão Profissional: O que acontece se você tentar deletar um CLIENTE que já tem PEDIDOS cadastrados? O banco impedirá a exclusão para proteger a Integridade Referencial. Isso é segurança de dados na prática! 🧠🛡️

---

🔥 Desafio de Fixação (Opcional)

Nível: Arquiteto 🏆

Pesquise a diferença entre INT, BIGINT e SERIAL. Qual deles você usaria para um sistema que processa 1 bilhão de entregas por mês?


🔑 Gabarito de Código/Fórmulas Completo

🐘 Gabarito Oficial PostgreSQL (pgAdmin)

-- 1. Criar Banco de Dados
CREATE DATABASE tecpro_express;
-- (Nota: No pgAdmin, lembre-se de abrir uma Query Tool apontando para o banco tecpro_express após criá-lo)

-- 2. Criar Tabelas Pais
CREATE TABLE cliente (
    id SERIAL PRIMARY KEY,
    nome VARCHAR(100) NOT NULL,
    cpf CHAR(11) UNIQUE
);

CREATE TABLE produto (
    id SERIAL PRIMARY KEY,
    nome VARCHAR(100) NOT NULL,
    preco DECIMAL(10,2) CHECK (preco > 0)
);

-- 3. Criar Tabela Dependente (Filho)
CREATE TABLE pedido (
    id SERIAL PRIMARY KEY,
    data_ped DATE,
    id_cliente INT,
    CONSTRAINT fk_ped_cli FOREIGN KEY (id_cliente) REFERENCES cliente(id)
);

-- 4. Criar Tabela Associativa (Neto)
CREATE TABLE item_pedido (
    id_pedido INT,
    id_produto INT,
    quantidade INT DEFAULT 1,
    PRIMARY KEY (id_pedido, id_produto),
    CONSTRAINT fk_item_ped FOREIGN KEY (id_pedido) REFERENCES pedido(id),
    CONSTRAINT fk_item_prod FOREIGN KEY (id_produto) REFERENCES produto(id)
);

🐬 Gabarito Alternativo MySQL (DBeaver / Workbench)

-- 1. Criar e Usar Banco
CREATE DATABASE tecpro_express;
USE tecpro_express;

-- 2. Criar Tabelas Pais
CREATE TABLE cliente (
    id INT PRIMARY KEY AUTO_INCREMENT,
    nome VARCHAR(100) NOT NULL,
    cpf CHAR(11) UNIQUE
);

CREATE TABLE produto (
    id INT PRIMARY KEY AUTO_INCREMENT,
    nome VARCHAR(100) NOT NULL,
    preco DECIMAL(10,2) CHECK (preco > 0)
);

-- 3. Criar Tabela Dependente
CREATE TABLE pedido (
    id INT PRIMARY KEY AUTO_INCREMENT,
    data_ped DATE,
    id_cliente INT,
    CONSTRAINT fk_ped_cli FOREIGN KEY (id_cliente) REFERENCES cliente(id)
);

-- 4. Criar Tabela Associativa
CREATE TABLE item_pedido (
    id_pedido INT,
    id_produto INT,
    quantidade INT DEFAULT 1,
    PRIMARY KEY (id_pedido, id_produto),
    CONSTRAINT fk_item_ped FOREIGN KEY (id_pedido) REFERENCES pedido(id),
    CONSTRAINT fk_item_prod FOREIGN KEY (id_produto) REFERENCES produto(id)
);

🔍 Explicação do Gabarito:

  • Ordem de Criação: Os bancos de dados exigem que tabelas sem Foreign Keys (cliente, produto) sejam criadas primeiro, antes das tabelas que fazem referência a elas (pedido, item_pedido). Caso contrário, ocorrerá erro de referência.
  • UNIQUE: Garante a integridade lógica (ex: impossibilita que dois clientes compartilhem o mesmo CPF).
  • CONSTRAINT: Dar nomes explícitos às chaves estrangeiras (ex: fk_ped_cli) facilita diagnósticos de erros e remoção de restrições em updates de produção.

📊 Rubrica Formativa de Avaliação

Critério de Avaliação Insuficiente (0% - 40%) Regular (41% - 70%) Excelente (71% - 100%)
Sintaxe SQL DDL e Restrições (Constraints) Erros de sintaxe SQL ou ausência de Chaves Primárias (`PRIMARY KEY`) e Estrangeiras (`FOREIGN KEY`). Cria as tabelas mas sem declarar restrições de integridade `NOT NULL`, `UNIQUE` ou `CHECK`. Script DDL SQL impecável com ordem correta de criação (Mestres ➔ Detalhes) e `CONSTRAINT`s nomeadas.
Execução e Teste no SGBD Script causa erro de referência circular ao ser executado. Executa no banco mas sem validar integridade de chaves estrangeiras. Executa perfeitamente no PostgreSQL/MySQL sem nenhum warning ou erro de chave.
Entrega no GitHub Entrega fora da pasta `bd-atv-05-sql-ddl/`. Entrega script `.sql` sem o relatório de validação. Submete `Atividade_05.sql` e `Atividade_05.md` devidamente validados no repositório.

🎯 ATIVIDADE 06 — MOBILIANDO A CASA

📖 Fundamentação Teórica

Para realizar este laboratório com sucesso, certifique-se de ter compreendido os conceitos apresentados no:
👉 CAPÍTULO 06: ANATOMIA DE ATRIBUTOS, DOMÍNIOS E TIPOS

Bem-vindo à sexta semana (4 aulas) do curso de Banco de Dados. Sua estrutura teórica já está pronta. Agora, vamos "mobiliar" o sistema — inserir dados reais e aprender a fazer as perguntas certas ao banco de dados usando a DML (Data Manipulation Language). 🛡️🧩


🎯 Objetivos de Aprendizagem do Laboratório

Ao final deste laboratório prático (estimativa: 4 horas presenciais / autoguiadas), você será capaz de:

  • Inserir registros usando o comando INSERT INTO.
  • Consultar dados de forma seletiva com SELECT e WHERE.
  • Utilizar Operadores Lógicos (e / ou) para filtrar resultados com precisão.
  • Ordenar informações com ORDER BY.

🏢 O Cenário Prático (Seu Desafio)

A TecProExpress iniciou suas operações! Você recebeu uma lista de clientes iniciais e produtos. Seu desafio é popular o banco e gerar os primeiros relatórios para a gerência.

Importante

A Regra de Ouro: Você não pode colocar móveis em uma casa que ainda não tem paredes. No banco de dados, primeiro CRIAMOS a tabela (DDL) e depois INSERIMOS os dados (DML).

---

🧠 Fundamentos: A Teoria Traduzida

Consultar o banco é como usar o filtro de pesquisa do Instagram ou da Amazon.

O Fluxo da Pergunta (SELECT)

Veja como o banco processa sua solicitação:

flowchart LR
    A[1. Tabela Criada] --> B[2. Dados Inseridos]
    B --> C{"3. WHERE: Filtro"}
    C -- Atende --> D[Exibir Linha]
    C -- Não Atende --> E[Descartar]
    D --> F["4. ORDER BY: Ordenação"]
    
    style A fill:#e8f5e9
    style B fill:#fffde7

📖 Exemplo Guiado: O Passo a Passo Completo

Para quem está começando, este é o fluxo obrigatório para qualquer banco de dados.

1. Criando o "Espaço" (DDL)

-- Primeiro, criamos a tabela para receber os produtos
CREATE TABLE produto (
    id INT PRIMARY KEY AUTO_INCREMENT,
    nome VARCHAR(100),
    preco DECIMAL(10,2)
);

2. Inserindo a "Mobília" (DML)

-- Agora que a tabela existe, inserimos os dados
INSERT INTO produto (nome, preco) VALUES 
('Smartphone X', 2500.00),
('Cabo USB', 25.00),
('Monitor 4K', 1800.00);

3. Fazendo a Pergunta (Query)

-- Selecionamos apenas o que nos interessa
SELECT nome, preco FROM produto WHERE preco > 100 AND preco < 2000;

🔍 Detalhamento do Código:

  • CREATE TABLE: Prepara o terreno. Sem isso, o comando INSERT dá erro de "Table doesn't exist".
  • INSERT INTO: "Empurra" os dados para dentro das colunas nome e preco.
  • AND: O operador e exige que as duas condições sejam verdadeiras ao mesmo tempo.

🛠️ Prática Obrigatória 1: Populando a TecProExpress

Cenário: Carga inicial de teste.

  1. Crie a tabela cliente com as colunas id, nome e cidade.
  2. Insira 5 Clientes (use cidades como 'São Paulo', 'Curitiba' e 'Santos').

🚀 Script de Seed Completo (SQL)

Dica

Copie e cole este bloco no seu SGBD para garantir que o ambiente esteja pronto:

```sql -- PASSO 1: Criar a tabela CREATE TABLE cliente ( id INT PRIMARY KEY AUTO_INCREMENT, nome VARCHAR(100), cidade VARCHAR(50) );

-- PASSO 2: Inserir os dados INSERT INTO cliente (nome, cidade) VALUES ('Marcos Silva', 'São Paulo'), ('Julia Costa', 'Curitiba'), ('Roberto Dias', 'Santos'), ('Ana Paula', 'São Paulo'), ('Lucas Melo', 'Santos');


---

## 💻 Execução DML & Resultado Esperado no Terminal

Para executar o script de inserção e a consulta com filtros no terminal do SGBD:

```sql
-- Executando a consulta com operador OR e ORDER BY
SELECT nome, cidade FROM cliente WHERE cidade = 'São Paulo' OR cidade = 'Santos' ORDER BY nome ASC;

🖥️ Saída Esperada no Terminal:

     nome      |   cidade   
---------------+------------
 Ana Paula     | São Paulo
 Lucas Melo    | Santos
 Marcos Silva  | São Paulo
 Roberto Dias  | Santos
(4 rows)

🌐 Requisição cURL e Payload JSON para Inserção em Lote (Swagger /docs)

curl -X POST "http://127.0.0.1:8000/api/v1/clientes/bulk" \
     -H "Content-Type: application/json" \
     -d '[
       {"nome": "Marcos Silva", "cidade": "São Paulo"},
       {"nome": "Julia Costa", "cidade": "Curitiba"},
       {"nome": "Roberto Dias", "cidade": "Santos"}
     ]'

🔹 Resposta JSON:

{
  "mensagem": "3 clientes inseridos com sucesso!",
  "status_code": 201
}

🛠️ Prática Obrigatória 2: Relatórios de Operação

Cenário: O Gerente solicitou dados específicos.

  1. Filtro Flexível (ou): Liste os clientes que moram em 'São Paulo' ou 'Santos'.
  2. Ordenação: Liste todos os produtos do mais caro para o mais barato (DESC).

📤 Instruções de Entrega (Microsoft Teams)

Após validar seus códigos SQL locais:

  1. Certifique-se de que os seus comandos INSERT INTO e SELECT estão em conformidade com o padrão ANSI SQL.
  2. Salve os comandos em um único arquivo de script com a extensão .sql (Ex: Atividade_06_SeuNome.sql).
  3. Certifique-se de incluir comentários no código explicando o papel dos filtros lógicos AND (operador e para restringir linhas) e OR (operador ou para ampliar critérios) nas consultas.
  4. Envie o arquivo .sql correspondente no Microsoft Teams para avaliação técnica.

💡 Checkpoint de Lógica

Importante

Dica para Iniciantes: Sempre que você fechar seu banco de dados e abrir de novo, verifique se a tabela ainda existe. Se você estiver usando um banco em memória ou temporário, precisará rodar o CREATE TABLE novamente antes de inserir dados. 🧠🛡️

---

🔥 Desafio de Fixação (Opcional)

Nível: Analista Sênior 🏆

Use o operador LIKE para encontrar todos os clientes cujo nome comece com a letra "A". Como o WHERE se comporta nesse caso?


🔑 Gabarito de Código/Fórmulas Completo

Consultas da Prática 2:

-- 1. Filtro Flexível (OU)
SELECT * FROM cliente 
WHERE cidade = 'São Paulo' OR cidade = 'Santos';

-- 2. Ordenação Decrescente (Z-A / Maior-Menor)
SELECT * FROM produto 
ORDER BY preco DESC;

🔍 Explicação do Gabarito:

  • OR: Traz registros que atendam a QUALQUER uma das condições.
  • DESC: Inverte a ordem natural. No preço, coloca o mais caro primeiro.

📊 Rubrica Formativa de Avaliação

Critério de Avaliação Insuficiente (0% - 40%) Regular (41% - 70%) Excelente (71% - 100%)
Sintaxe SQL DML (INSERT, SELECT, ORDER BY) Erros de sintaxe em inserção ou consultas sem cláusulas de filtro (`WHERE`). Escreve as queries `INSERT` e `SELECT` mas sem demonstrar o uso de `ORDER BY DESC`. Queries SQL DML perfeitas com sementes de dados (`INSERT INTO`), filtros lógicos `AND`/`OR` e ordenação `ORDER BY`.
Comentários & Explicação Técnica Sem comentários no script SQL. Comentários superficiais nos comandos SQL. Comentários ricos explicando a diferença entre `AND` (restritivo) e `OR` (ampliativo) no resultado das linhas.
Entrega do Script SQL Entrega script com erros de execução. Script entregue mas fora do padrão de extensão `.sql`. Submete `Atividade_06_SeuNome.sql` limpo, formatado e testado no SGBD.

🎯 ATIVIDADE 07 — O PODER DA INFORMAÇÃO

📖 Fundamentação Teórica

Para realizar este laboratório com sucesso, certifique-se de ter compreendido os conceitos apresentados no:
👉 CAPÍTULO 07: CHAVES, RELACIONAMENTOS E CARDINALIDADE

Bem-vindo à sétima semana (4 aulas) do curso de Banco de Dados. Agora que você já sabe inserir dados e fazer filtros básicos, vamos aprender o que realmente torna um Analista de Dados valioso: a capacidade de cruzar informações de tabelas diferentes e realizar cálculos automáticos. 🛡️🧩


🎯 Objetivos de Aprendizagem do Laboratório

Ao final deste laboratório prático (estimativa: 4 horas presenciais / autoguiadas), você será capaz de:

  • Realizar junções entre tabelas usando INNER JOIN.
  • Utilizar funções de agregação (COUNT, SUM, AVG, MAX, MIN).
  • Agrupar resultados com GROUP BY.
  • Filtrar grupos com HAVING.

🏢 O Cenário Prático (Seu Desafio)

O Diretor Financeiro da TecProExpress precisa de um relatório consolidado. Ele não quer ver apenas "quem comprou", ele quer saber o Total Gasto por Cliente e quais são os Produtos mais vendidos.

Para isso, você precisará conectar as tabelas CLIENTE, PEDIDO e ITEM_PEDIDO, realizando somas e contagens automáticas.


🧠 Fundamentos: A Teoria Traduzida

Dados isolados são apenas registros. Dados cruzados são Inteligência de Negócio.

1. O Aperto de Mão (INNER JOIN)

Imagine que a tabela CLIENTE tem o nome e a tabela PEDIDO tem a data. O JOIN é o ponto de encontro onde o id_cliente de ambas as tabelas se reconhece.

graph LR
    A["CLIENTE (ID: 1, Nome: Ana)"] -- "id_cliente = id_cliente" --- B["PEDIDO (ID: 101, Data: 12/05)"]
    style A fill:#e3f2fd
    style B fill:#fffde7

2. A Calculadora do Banco (Agregações)

Em vez de somar na mão, o banco faz por você:

  • COUNT: Conta quantas linhas existem.
  • SUM: Soma valores numéricos.
  • AVG: Calcula a média.

📖 Exemplo Guiado: Relatório de Vendas

Veja como unir tabelas e somar valores em um único comando.

1. Preparando o Ambiente (DDL e Seed)

-- Estrutura Simplificada
CREATE TABLE categoria (id INT PRIMARY KEY, nome VARCHAR(50));
CREATE TABLE produto (id INT PRIMARY KEY, nome VARCHAR(50), preco DECIMAL(10,2), cat_id INT);

-- Seed
INSERT INTO categoria VALUES (1, 'Eletrônicos'), (2, 'Acessórios');
INSERT INTO produto VALUES (1, 'Mouse', 50.00, 2), (2, 'Teclado', 150.00, 2), (3, 'Monitor', 900.00, 1);

2. A Consulta com JOIN e SUM

-- Qual o valor total em estoque por categoria?
SELECT c.nome, SUM(p.preco) AS total_estoque
FROM categoria c
INNER JOIN produto p ON c.id = p.cat_id
GROUP BY c.nome;

🔍 Detalhamento do Código:

  • INNER JOIN ... ON: Diz ao banco: "Traga apenas os produtos que possuem uma categoria válida".
  • SUM(p.preco): Soma os preços de todos os produtos que caírem no mesmo grupo.
  • GROUP BY: Organiza os resultados em "gavetas" pelo nome da categoria.

🛠️ Prática Obrigatória 1: Agregando Valores

Cenário: Análise de inventário da TecProExpress.

  1. Escreva uma consulta que conte quantos clientes existem cadastrados.
  2. Escreva uma consulta que mostre o preço do produto mais caro e do mais barato.
  3. Calcule a média de preços de todos os produtos.

🏁 Resultado Esperado (Seed Visual)

O resultado deve ser um valor único (ex: "Média: 450.50").


🛠️ Prática Obrigatória 2: O Grande Relatório (Join)

Cenário: Cruzamento de Pedidos e Clientes.

  1. Liste o Nome do Cliente e a Data do Pedido de todos os pedidos realizados.
  2. Desafio de Agrupamento: Mostre o nome do cliente e quantos pedidos cada um realizou até agora.

🚀 Script de Seed para Teste (SQL)

-- Use as tabelas da Atividade 06 e adicione estes dados:
INSERT INTO pedido (data_ped, id_cliente) VALUES 
('2026-05-01', 1),
('2026-05-02', 1),
('2026-05-03', 2);

💻 Execução de Consulta Agrupada (JOIN + GROUP BY) no Terminal

Para testar o relatório de total de pedidos agrupados por cliente:

-- Query de agregação com JOIN e GROUP BY
SELECT c.nome, COUNT(p.id) AS total_pedidos
FROM cliente c
INNER JOIN pedido p ON c.id = p.id_cliente
GROUP BY c.nome
HAVING COUNT(p.id) >= 1;

🖥️ Saída Esperada no Terminal:

     nome     | total_pedidos 
--------------+---------------
 Marcos Silva |             2
 Julia Costa  |             1
(2 rows)

🌐 Requisição cURL para Relatório Agregado (Swagger /docs)

curl -X GET "http://127.0.0.1:8000/api/v1/relatorios/pedidos-por-cliente" \
     -H "Accept: application/json"

🔹 Resposta JSON:

[
  {
    "cliente": "Marcos Silva",
    "total_pedidos": 2,
    "valor_acumulado": 745.00
  },
  {
    "cliente": "Julia Costa",
    "total_pedidos": 1,
    "valor_acumulado": 150.00
  }
]

📤 Instruções de Entrega (Microsoft Teams)

Após testar suas consultas agrupadas e junções no pgAdmin (ou SGBD de sua escolha):

  1. Formate suas consultas mantendo palavras-chave em maiúsculas (ex: SELECT, INNER JOIN, GROUP BY, HAVING).
  2. Salve todas as queries da Prática 1 e 2 em um único script .sql.
  3. Adicione comentários detalhados explicando a diferença prática entre LEFT JOIN e INNER JOIN e quando usar HAVING em vez de WHERE.
  4. Envie o arquivo (Atividade_07_SeuNome.sql) no Microsoft Teams.

💡 Checkpoint de Lógica

Importante

Reflexão Profissional: Qual a diferença entre WHERE e HAVING? O WHERE filtra linhas antes do agrupamento. O HAVING filtra o resultado depois que o cálculo (como o SUM) já foi feito. 🧠🛡️

---

🔥 Desafio de Fixação (Opcional)

Nível: Arquiteto de BI 🏆

Crie um relatório que mostre apenas os clientes que gastaram mais de R$ 1.000,00 no total de suas compras. Você precisará usar JOIN, SUM, GROUP BY e HAVING.


🔑 Gabarito de Código/Fórmulas Completo

Prática 1 (Agregações):

SELECT COUNT(*) FROM cliente;
SELECT MAX(preco), MIN(preco) FROM produto;
SELECT AVG(preco) FROM produto;

Prática 2 (Joins):

-- Nome do Cliente e Data
SELECT c.nome, p.data_ped 
FROM cliente c 
INNER JOIN pedido p ON c.id = p.id_cliente;

-- Contagem por Cliente
SELECT c.nome, COUNT(p.id) AS total_pedidos
FROM cliente c
LEFT JOIN pedido p ON c.id = p.id_cliente
GROUP BY c.nome;

🔍 Explicação do Gabarito:

  • LEFT JOIN: Usei aqui para que mesmo clientes que ainda não compraram nada apareçam na lista com o valor 0. O INNER JOIN os excluiria.
  • AS total_pedidos: Renomeia a coluna no resultado para ficar mais legível para o usuário final.

📊 Rubrica Formativa de Avaliação

Critério de Avaliação Insuficiente (0% - 40%) Regular (41% - 70%) Excelente (71% - 100%)
Junções de Tabelas (INNER JOIN vs LEFT JOIN) Erros de sintaxe nas cláusulas `ON` de junção ou confunde os tipos de JOIN. Escreve `INNER JOIN` mas não consegue aplicar `LEFT JOIN` para registros nulos. Aplica `INNER JOIN` e `LEFT JOIN` com maestria, justificando a inclusão de registros nulos.
Funções de Agregação & Agrupamento Erro de execução por omitir colunas no `GROUP BY` ou usar `WHERE` em vez de `HAVING`. Utiliza `COUNT`/`SUM` com `GROUP BY` mas sem aplicar filtros `HAVING`. Queries complexas impecáveis utilizando `COUNT`, `SUM`, `AVG`, `GROUP BY` e `HAVING`.
Entrega do Script SQL Entrega script com palavras-chave em minúsculo ou sem formatação. Script entregue mas sem explicações nos comentários. Submete `Atividade_07_SeuNome.sql` perfeitamente formatado e com comentários explicativos.

🎯 ATIVIDADE 08 — ALÉM DAS TABELAS

📖 Fundamentação Teórica

Para realizar este laboratório com sucesso, certifique-se de ter compreendido os conceitos apresentados no:
👉 CAPÍTULO 08: EXTENSÕES DO MER E REVISÃO DA MODELAGEM

Bem-vindo à oitava semana (4 aulas) do curso de Banco de Dados. Até aqui, vivemos em um mundo de tabelas rígidas e linhas fixas (SQL). Mas e se precisarmos armazenar dados que mudam de formato o tempo todo? Hoje vamos entrar no universo NoSQL com o MongoDB, o banco de documentos mais popular do mundo. 🛡️🧩


🎯 Objetivos de Aprendizagem do Laboratório

Ao final deste laboratório prático (estimativa: 4 horas presenciais / autoguiadas), você será capaz de:

  • Compreender a diferença entre Relacional (SQL) e Não Relacional (NoSQL).
  • Manipular Coleções e Documentos (JSON/BSON).
  • Realizar inserções e consultas básicas usando o MongoDB Compass e o Shell.
  • Entender o conceito de Esquema Flexível.

🏢 O Cenário Prático (Seu Desafio)

A TecProExpress está expandindo seu catálogo para produtos internacionais. O problema é que cada país envia detalhes diferentes: uns mandam voltagem, outros mandam tamanho de tela, outros mandam material. No SQL, teríamos muitas colunas vazias.

Seu desafio é criar uma coleção de PRODUTOS_NOSQL no MongoDB para armazenar esses dados de forma flexível, garantindo que nenhum detalhe técnico seja perdido.


🧠 Fundamentos: A Teoria Traduzida

NoSQL não significa "Não SQL", mas sim "Not Only SQL" (Não Apenas SQL).

1. Tabela vs. Coleção

No MongoDB, não temos tabelas, temos Coleções. Dentro delas, não temos linhas, temos Documentos.

graph LR
    subgraph SQL ["Mundo Relacional"]
    A[Tabela] --> B[Linha]
    B --> C[Coluna Fixa]
    end
    
    subgraph NoSQL ["Mundo MongoDB"]
    D[Coleção] --> E[Documento JSON]
    E --> F[Campos Variáveis]
    end
    
    style SQL fill:#f5f5f5
    style NoSQL fill:#e8f5e9

2. O Formato JSON

Os dados são guardados como objetos. Exemplo:

{
  "nome": "Smartphone",
  "preco": 2000,
  "detalhes": { "cor": "Preto", "ram": "8GB" }
}

📖 Exemplo Guiado: Sua Primeira Coleção

Veja como o MongoDB cria tudo automaticamente no momento da inserção.

1. Criando e Populando (DML NoSQL)

Diferente do SQL, se a coleção não existir, o MongoDB a cria na hora.

// Usando o Banco 'tecpro_express'
use('tecpro_express');

// Inserindo um produto com campos únicos
db.produtos_nosql.insertOne({
  "item": "Notebook",
  "marca": "TecPro",
  "especificacoes": { "cpu": "i7", "ssd": "512GB" },
  "tags": ["oferta", "tecnologia"]
});

2. Consultando (Query NoSQL)

// Buscar todos os notebooks
db.produtos_nosql.find({ "item": "Notebook" });

🔍 Detalhamento do Código:

  • db.colecao: Comando para acessar uma coleção específica.
  • insertOne: Insere um único documento.
  • { "chave": "valor" }: Estrutura de filtro básica.

🛠️ Prática Obrigatória 1: Carga Flexível

Cenário: Cadastro de produtos variados na TecProExpress.

  1. Abra o MongoDB Compass ou o Shell.
  2. Crie uma coleção chamada catalogo.
  3. Insira 3 Documentos com estruturas diferentes:
    • Um produto com voltagem e cor.
    • Um produto com tamanho_tela e peso.
    • Um produto com uma lista de materiais (array).

🚀 Script de Seed (NoSQL)

db.catalogo.insertMany([
  { "nome": "Luminária", "voltagem": "Bivolt", "cor": "Branca" },
  { "nome": "Monitor", "tamanho_tela": "27 pol", "peso": "4kg" },
  { "nome": "Cadeira", "materiais": ["Couro", "Aço", "Plástico"] }
]);

🛠️ Prática Obrigatória 2: Buscas Inteligentes

Cenário: O time de marketing quer filtrar o catálogo.

  1. Escreva uma consulta que traga apenas produtos da cor 'Branca'.
  2. Escreva uma consulta que utilize o operador $gt (greater than) para listar produtos com preço acima de 100.

💻 Execução de Queries no Mongo Shell & Logs no Terminal

Para testar consultas documentais flexíveis via terminal mongosh:

// Consulta com filtro por array e projeção
db.catalogo.find(
  { "materiais": "Couro" },
  { "_id": 0, "nome": 1, "materiais": 1 }
);

🖥️ Saída Esperada no Terminal do Mongo Shell:

[
  {
    "nome": "Cadeira",
    "materiais": [
      "Couro",
      "Aço",
      "Plástico"
    ]
  }
]

🌐 Requisição cURL e Payload JSON Documental (Swagger /docs)

curl -X POST "http://127.0.0.1:8000/api/v1/catalogo/documentos" \
     -H "Content-Type: application/json" \
     -d '{
       "nome": "Monitor UltraWide",
       "atributos_dinamicos": {
         "tamanho_tela": "34 polegadas",
         "resolucao": "3440x1440",
         "taxa_atualizacao_hz": 144
       },
       "tags": ["gamer", "produtividade", "usb-c"]
     }'

🔹 Resposta JSON:

{
  "inserted_id": "65e8b4f1293a4b0012f9810a",
  "status": "CRIADO_COM_SUCESSO"
}

📤 Instruções de Entrega (Microsoft Teams)

Após testar suas operações JSON no MongoDB Compass ou no Mongo Shell:

  1. Salve as consultas de busca (db.catalogo.find(...)) e inserção criadas em um arquivo com a extensão .js (JavaScript/BSON script).
  2. Opcionalmente, envie capturas de tela mostrando os documentos inseridos visíveis na aba "Documents" do MongoDB Compass.
  3. Envie o arquivo (Atividade_08_SeuNome.js) na plataforma do Microsoft Teams para avaliação de modelagem orientada a documentos.

💡 Checkpoint de Lógica

Importante

Reflexão Profissional: Ter um esquema flexível significa que você pode salvar qualquer coisa, mas não significa que você deve. Na indústria, mesmo no NoSQL, mantemos um padrão mínimo para que o sistema não vire uma bagunça de dados sem sentido. 🧠🛡️

---

🔥 Desafio de Fixação (Opcional)

Nível: Desenvolvedor FullStack 🏆

Pesquise como realizar um UPDATE no MongoDB usando o operador $set. Como você alteraria apenas o preço de um documento sem apagar o resto dos dados?


🔑 Gabarito de Código/Fórmulas Completo

Prática 1 (Inserção):

db.catalogo.insertOne({ "nome": "Teclado", "idioma": "ABNT2" });

Prática 2 (Consultas):

// Busca por campo exato
db.catalogo.find({ "cor": "Branca" });

// Busca com Operador (Preço > 100)
db.catalogo.find({ "preco": { $gt: 100 } });

🔍 Explicação do Gabarito:

  • $gt: Significa "Greater Than" (Maior que). Operadores no MongoDB começam com $.
  • find(): Se deixado vazio find({}), ele retorna todos os documentos da coleção.

📊 Rubrica Formativa de Avaliação

Critério de Avaliação Insuficiente (0% - 40%) Regular (41% - 70%) Excelente (71% - 100%)
Modelagem de Documentos JSON/BSON Erros na sintaxe JSON ou modelagem rígida tentando imitar SQL no NoSQL. Cria os documentos JSON mas sem utilizar o poder de esquemas flexíveis (documentos aninhados ou arrays). Modelagem orientada a documentos BSON impecável, explorando esquemas flexíveis e tipos ricos.
Consultas com Operadores Mongo ($gt, $set) Erros nas buscas `db.find()` ou omissão de operadores do MongoDB. Executa buscas simples mas não consegue aplicar filtros numéricos com `$gt`. Queries avançadas utilizando `find()` com operadores de comparação (`$gt`) e atualização (`$set`).
Entrega de Scripts JS Entrega fora do padrão de extensão `.js`. Script entregue sem capturas de tela do MongoDB Compass. Submete `Atividade_08_SeuNome.js` testado e validado no MongoDB.

🎯 ATIVIDADE 09 — ESCALA PLANETÁRIA

📖 Fundamentação Teórica

Para realizar este laboratório com sucesso, certifique-se de ter compreendido os conceitos apresentados no:
👉 CAPÍTULO 09: MAPEAMENTO MER-RELACIONAL E ÁLGEBRA

Bem-vindo à nona semana (4 aulas) do curso de Banco de Dados. Após explorarmos o MongoDB, vamos subir o nível de escala. Hoje vamos conhecer o Apache Cassandra, o banco de dados utilizado por gigantes como Netflix e Uber para lidar com bilhões de transações por segundo. 🛡️🧩


🎯 Objetivos de Aprendizagem do Laboratório

Ao final deste laboratório prático (estimativa: 4 horas presenciais / autoguiadas), você será capaz de:

  • Entender a arquitetura de Colunas de Larga Escala (Wide Column Store).
  • Criar e gerenciar Keyspaces e Tabelas no Cassandra.
  • Utilizar a linguagem CQL (Cassandra Query Language).
  • Compreender a importância da Chave de Partição para a escalabilidade.

🏢 O Cenário Prático (Seu Desafio)

A TecProExpress instalou sensores IoT em toda a sua frota de caminhões. Cada caminhão envia sua posição GPS, velocidade e temperatura da carga a cada 5 segundos. Com milhares de veículos, o volume de dados é gigantesco.

Seu desafio é configurar um ambiente no Cassandra para receber esse fluxo de dados, garantindo que as informações sejam gravadas de forma ultra-rápida e nunca sejam perdidas.


🧠 Fundamentos: A Teoria Traduzida

O Cassandra não organiza dados em uma única máquina; ele os espalha por um Anel (Cluster) de servidores.

1. O Keyspace (O Condomínio)

No SQL temos "Databases". No Cassandra, temos Keyspaces. É aqui que definimos como os dados serão replicados entre as máquinas.

2. A Chave de Partição (O Endereço)

Imagine um grande armazém. Para achar uma caixa rápido, você precisa saber o número da prateleira. No Cassandra, a Partition Key decide em qual servidor do mundo o dado será guardado.

graph TD
    subgraph Cluster ["Anel Cassandra"]
    N1[Nó 1] --- N2[Nó 2]
    N2 --- N3[Nó 3]
    N3 --- N1
    end
    
    Data[Dado de GPS] -->|Hash da Chave| N2
    
    style Cluster fill:#f3e5f5,stroke:#7b1fa2

📖 Exemplo Guiado: Criando sua Infraestrutura

Veja como o CQL é amigável e parecido com o SQL tradicional.

💡 Os blocos abaixo usam CQL (Cassandra Query Language), sintaticamente parecido com SQL mas com diferenças importantes de modelagem — usamos o realce de sintaxe SQL por não haver um fence dedicado para CQL neste portal.

1. Preparando o Terreno (Keyspace)

-- Criando o Keyspace de Logística
CREATE KEYSPACE tecpro_logistica 
WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1};

USE tecpro_logistica;

2. Criando a Tabela de Sensores (DDL)

CREATE TABLE telemetria (
    veiculo_id INT,
    data_hora TIMESTAMP,
    latitude DECIMAL,
    longitude DECIMAL,
    velocidade FLOAT,
    PRIMARY KEY (veiculo_id, data_hora)
);

🔍 Detalhamento do Código:

  • SimpleStrategy: Estratégia de replicação para ambientes de teste (um único datacenter).
  • PRIMARY KEY (veiculo_id, data_hora):
    • veiculo_id: Chave de Partição (espalha os caminhões pelo cluster).
    • data_hora: Chave de Agrupamento (ordena os logs de cada caminhão por tempo).

🛠️ Prática Obrigatória 1: Setup e Carga

Cenário: Monitoramento de Frota na TecProExpress.

  1. Acesse seu ambiente Cassandra (via Docker ou local).
  2. Crie o Keyspace tecpro_iot.
  3. Crie a tabela status_caminhao para armazenar id, placa, temperatura e data_leitura.

🚀 Script de Seed (CQL)

INSERT INTO status_caminhao (id, placa, temperatura, data_leitura) 
VALUES (101, 'ABC-1234', 5.5, toTimestamp(now()));

INSERT INTO status_caminhao (id, placa, temperatura, data_leitura) 
VALUES (101, 'ABC-1234', 5.2, toTimestamp(now()));


💻 Execução de Queries no Terminal CQL (cqlsh) & Logs

Para testar consultas sequenciais particionadas via terminal cqlsh:

-- Consultando séries temporais ordenadas fisicamente no disco
SELECT id, placa, temperatura, data_leitura 
FROM tecpro_iot.status_caminhao 
WHERE id = 101 
LIMIT 2;

🖥️ Saída Esperada no Terminal do cqlsh:

 id  | data_leitura                    | placa    | temperatura
-----+---------------------------------+----------+-------------
 101 | 2026-03-01 14:22:05.120000+0000 | ABC-1234 |         5.2
 101 | 2026-03-01 14:22:00.080000+0000 | ABC-1234 |         5.5

(2 rows)

🌐 Requisição cURL para Ingestão de Telemetria Time-Series (Swagger /docs)

curl -X POST "http://127.0.0.1:8000/api/v1/telemetria/iot" \
     -H "Content-Type: application/json" \
     -d '{
       "veiculo_id": 101,
       "placa": "ABC-1234",
       "temperatura_celsius": 5.2,
       "velocidade_kmh": 78
     }'

🔹 Resposta JSON:

{
  "status": "REGISTRADO_NO_CLUSTER",
  "partition_key": "101",
  "replication_factor": 3
}


📤 Instruções de Entrega (Microsoft Teams)

Após testar suas tabelas e consultas no Cassandra (via cqlsh):

  1. Salve o script contendo a criação do Keyspace, da tabela com a Primary Key adequada (Chave de Partição + Chave de Agrupamento) e as consultas de teste em um arquivo com a extensão .cql ou .sql contendo comandos CQL.
  2. Adicione comentários no código explicando por que a escolha da Partition Key (veiculo_id/id) é vital para a distribuição horizontal de dados do cluster.
  3. Envie o arquivo (Atividade_09_SeuNome.cql) na tarefa correspondente no Microsoft Teams.

💡 Checkpoint de Lógica

Importante

Reflexão Profissional: No Cassandra, nós desenhamos as tabelas com base nas perguntas que vamos fazer (Query-Driven Design). Se você tentar filtrar por algo que não faz parte da chave primária, o Cassandra pode recusar a consulta para não perder performance. 🧠🛡️

---

🔥 Desafio de Fixação (Opcional)

Nível: Arquiteto de Sistemas 🏆

O que é o Replication Factor (RF)? Se você configurar RF: 3 em um cluster de 5 máquinas, o que acontece se duas máquinas pegarem fogo ao mesmo tempo?


🔑 Gabarito de Código/Fórmulas Completo

Prática 1 (Criação):

CREATE TABLE status_caminhao (
    id INT,
    placa TEXT,
    temperatura FLOAT,
    data_leitura TIMESTAMP,
    PRIMARY KEY (id, data_leitura)
) WITH CLUSTERING ORDER BY (data_leitura DESC);

Prática 2 (Consulta):

SELECT * FROM status_caminhao WHERE id = 101;

🔍 Explicação do Gabarito:

  • WITH CLUSTERING ORDER BY: Faz com que os logs mais recentes apareçam no topo da lista automaticamente, sem precisar de um ORDER BY lento na hora da consulta.
  • TEXT: No Cassandra, usamos TEXT em vez de VARCHAR para a maioria das strings.

📊 Rubrica Formativa de Avaliação

Critério de Avaliação Insuficiente (0% - 40%) Regular (41% - 70%) Excelente (71% - 100%)
Design Orientado a Consultas (Query-Driven) Modela tabelas sem considerar as chaves de partição do Cassandra. Define a Partition Key mas sem declarar a Clustering Key para ordenação física. Modelagem CQL impecável com Partition Key (distribuição) e Clustering Key (ordenação temporal) com `WITH CLUSTERING ORDER BY`.
Arquitetura de Cluster & Distribuição Omite justificativas de distribuição de dados e Replication Factor. Explica o funcionamento do cluster de forma superficial. Comentários ricos explicando a importância da Partition Key para a performance de gravação distribuída.
Entrega do Script CQL Entrega script com erros de sintaxe no `cqlsh`. Script entregue mas fora do padrão `.cql`. Submete `Atividade_09_SeuNome.cql` com Keyspace e tabelas devidamente testados.

🎯 ATIVIDADE 10 — ARQUITETO DE ELITE

📖 Fundamentação Teórica

Para realizar este laboratório com sucesso, certifique-se de ter compreendido os conceitos apresentados no:
👉 CAPÍTULO 10: NORMALIZAÇÃO (1FN A 3FN)

Parabéns! Você chegou à décima semana (4 aulas) e ao primeiro grande marco do curso de Banco de Dados. Hoje, você deixará de ser um estudante para se tornar um Arquiteto de Soluções. O desafio final da Fase 1 é consolidar tudo o que aprendemos sobre SQL e NoSQL em um único ecossistema corporativo. 🛡️🏆


🎯 Objetivos de Aprendizagem do Laboratório

Ao final deste laboratório prático (estimativa: 4 horas presenciais / autoguiadas), você será capaz de:

  • Um Modelo Relacional completo e normalizado (3FN).
  • Um script de Banco de Dados Real com carga de dados (DDL/DML).
  • Relatórios de Business Intelligence usando Joins e Agregações.
  • Uma camada de Persistência NoSQL para dados flexíveis.

🏢 O Cenário Prático (Seu Desafio Final)

A TecProExpress vai lançar o serviço "Ultra-Priority", focado em entregas de alto valor (como obras de arte e joias). Este serviço exige:

  1. Segurança Total (Relacional): Cadastro rigoroso de clientes, apólices de seguro e rotas.
  2. Rastreamento Detalhado (NoSQL): Logs de sensores térmicos, fotos da carga e assinaturas digitais que mudam de formato conforme o país.

Seu desafio é entregar a Arquitetura de Dados completa para este novo negócio.


🧠 O Roadmap do Arquiteto

Veja o caminho que seus dados vão percorrer:

flowchart TD
    A[1. Modelagem MER] --> B[2. Mapeamento 3FN]
    B --> C[3. Script SQL DDL]
    C --> D[4. Seed de Dados DML]
    D --> E[5. Relatórios BI]
    E --> F[6. Integração NoSQL]
    
    style A fill:#e3f2fd
    style F fill:#e8f5e9
    style C fill:#fffde7

🛠️ Passo 1: A Fundação Relacional (SQL)

Tarefa: Crie o esquema de banco de dados para o serviço Ultra-Priority.

  1. Modelagem: Desenhe no draw.io as tabelas CLIENTE, SEGURO e ENTREGA_ESPECIAL.
  2. Normalização: Garanta que os dados de seguro não estejam duplicados.
  3. Implementação: Escreva o script CREATE TABLE com todas as PKs e FKs.

🛠️ Passo 2: Inteligência e Auditoria

Tarefa: Gere os relatórios que o board da TecProExpress exigiu.

  1. Join: Liste o nome do cliente, o valor da apólice de seguro e o status da entrega.
  2. Agregação: Calcule o valor total segurado que está em trânsito no momento (SUM).

🛠️ Passo 3: Flexibilidade NoSQL (MongoDB)

Tarefa: Crie uma coleção no MongoDB chamada logs_rastreamento.

  1. Insira documentos JSON que contenham dados variados de sensores (ex: Um log com temperatura, outro com foto_url, outro com biometria_recebedor).
  2. Realize uma busca que traga apenas logs com alerta: true.

🚀 Script de Seed Consolidado (Gabarito de Teste)

-- DDL Rápido
CREATE TABLE cliente (id INT PRIMARY KEY, nome VARCHAR(100));
CREATE TABLE seguro (id INT PRIMARY KEY, valor DECIMAL(15,2), descricao TEXT);
CREATE TABLE entrega_especial (id INT PRIMARY KEY, id_cliente INT, id_seguro INT, FOREIGN KEY (id_cliente) REFERENCES cliente(id), FOREIGN KEY (id_seguro) REFERENCES seguro(id));

-- DML de Carga
INSERT INTO cliente VALUES (1, 'Farmácia Central');
INSERT INTO seguro VALUES (1, 500000.00, 'Cobertura Diamante');
INSERT INTO entrega_especial VALUES (1001, 1, 1);

💻 Execução do Pipeline Poliglota (SQL + NoSQL) no Terminal

Para verificar a persistência poliglota integrada (PostgreSQL + MongoDB):

# integrador_poliglota.py
print("=" * 65)
print("🌐 VALIDAÇÃO POLIGLOTA (RDBMS SEGUROS + NoSQL LOGS) - TECPRO")
print("=" * 65)
print("1. [PostgreSQL] Inserindo Apólice de Seguro R$ 500.000,00 ... [OK]")
print("2. [MongoDB]    Gravando Telemetria JSON na Coleção 'logs' ... [OK]")
print("3. [Relatório]  Cruzando dados em tempo real...")
print("   • Cliente: Farmácia Central | Seguro: R$ 500.000,00 | Temp: 22.5°C")
print("=" * 65)

🖥️ Saída Esperada no Terminal:

=================================================================
🌐 VALIDAÇÃO POLIGLOTA (RDBMS SEGUROS + NoSQL LOGS) - TECPRO
=================================================================
1. [PostgreSQL] Inserindo Apólice de Seguro R$ 500.000,00 ... [OK]
2. [MongoDB]    Gravando Telemetria JSON na Coleção 'logs' ... [OK]
3. [Relatório]  Cruzando dados em tempo real...
   • Cliente: Farmácia Central | Seguro: R$ 500.000,00 | Temp: 22.5°C
=================================================================

🌐 Requisições cURL Poliglotas (Swagger /docs)

🔹 1. Cadastro de Seguro Relacional (PostgreSQL):

curl -X POST "http://127.0.0.1:8000/api/v1/seguros" \
     -H "Content-Type: application/json" \
     -d '{"id": 1, "valor": 500000.0, "descricao": "Cobertura Diamante"}'

🔹 2. Ingestão de Log Dinâmico (MongoDB):

curl -X POST "http://127.0.0.1:8000/api/v1/logs-rastreamento" \
     -H "Content-Type: application/json" \
     -d '{
       "entrega_id": 1001,
       "evento": "Check-in Aeroporto",
       "detalhes": {"temp_externa": 22.5, "umidade": 40}
     }'

📤 Instruções de Entrega (Microsoft Teams)

Parabéns por concluir o Projeto Integrador (Fase 1)! Para realizar a sua entrega com sucesso:

  1. Certifique-se de organizar seu repositório Git pessoal seguindo rigorosamente a estrutura de pastas descrita na página inicial (index.md).
  2. Adicione ao repositório:
    • O diagrama lógico no draw.io (.drawio e .drawio.png).
    • O script SQL completo (.sql) com CREATE DATABASE, criação física (DDL), carga de testes (DML) e os relatórios analíticos solicitados.
    • Os scripts MongoDB de persistência flexível em formato .js ou .json.
  3. Submeta na plataforma do Microsoft Teams:
    • O link público do seu repositório GitHub.
    • Os arquivos consolidados para verificação direta.

💡 Checkpoint de Lógica

Importante

Conselho de Carreira: Um projeto integrador é a sua melhor peça de portfólio. No GitHub, não suba apenas o código; use o README.md para explicar por que você escolheu o SQL para o seguro e o NoSQL para os logs. Isso demonstra visão arquitetural. 🧠🛡️

---

🔥 Desafio de Fixação (Opcional)

Nível: CTO (Chief Technology Officer) 🏆

Tente integrar os dados: No seu relatório final, como você associaria o id_entrega do SQL com o documento_id do MongoDB? (Dica: Pesquise sobre referências cruzadas entre bancos poliglotas).


🔑 Gabarito de Código/Fórmulas Completo

Relatório de Valor Segurado (BI):

SELECT c.nome, SUM(s.valor) AS risco_total
FROM cliente c
JOIN entrega_especial e ON c.id = e.id_cliente
JOIN seguro s ON s.id = e.id_seguro
GROUP BY c.nome;

Inserção NoSQL (Logística):

db.logs_rastreamento.insertOne({
  "entrega_id": 1001,
  "evento": "Check-in Aeroporto",
  "detalhes": { "temp_externa": 22.5, "umidade": 40 },
  "alerta": false
});

🔍 Explicação do Gabarito:

  • JOIN Triplo: Conecta três tabelas para cruzar a pessoa, o serviço e o valor.
  • Objeto Detalhes: No MongoDB, usamos objetos aninhados para guardar dados técnicos que não precisam de colunas fixas no SQL.

📊 Rubrica Formativa de Avaliação

Critério de Avaliação Insuficiente (0% - 40%) Regular (41% - 70%) Excelente (71% - 100%)
Arquitetura de Dados Poliglota (SQL + NoSQL) Tenta resolver todo o problema com apenas um tipo de banco sem justificativa. Utiliza SQL e NoSQL mas sem justificativa arquitetural clara no `README.md`. Arquitetura Poliglota madura usando SQL para transações financeiras/seguro e NoSQL para rastreamento/logs técnicos.
Integração & Referências Cruzadas Sem integração ou identificadores correlacionados entre os bancos. Correlaciona os dados mas com inconsistência nos tipos de dados das chaves. Estratégia perfeita de amarração cruzada (`entrega_id` / `id_cliente`) integrando relatórios SQL com documentos MongoDB.
Portfólio & Entrega no GitHub Entrega com arquivos avulsos sem documentação. Entrega os scripts SQL e NoSQL mas com `README.md` incompleto. Repositório público no GitHub no formato de portfólio profissional com `README.md` explicativo e scripts validados.

🎯 ATIVIDADE 11 — O VELOCÍMETRO DOS DADOS

📖 Fundamentação Teórica

Para realizar este laboratório com sucesso, certifique-se de ter compreendido os conceitos apresentados no:
👉 CAPÍTULO 11: ECOSSISTEMA SQL E DDL

Bem-vindo à décima primeira semana (4 aulas) do curso de Banco de Dados. Até aqui, trabalhamos com tabelas pequenas, onde as consultas respondem instantaneamente. Mas o que acontece quando a tabela tem 10 milhões de linhas? Sem os índices corretos, seu banco travará e a aplicação ficará lenta. Hoje, você aprenderá a criar índices e a ler o plano de execução de uma consulta para atuar como um Especialista em Performance (Tuning). 🛡️⚡


🎯 Objetivos de Aprendizagem do Laboratório

Ao final deste laboratório prático (estimativa: 4 horas presenciais / autoguiadas), você será capaz de:

  • Compreender a diferença entre busca sequencial (Sequential Scan) e busca por índice (Index Scan).
  • Criar e gerenciar Índices B-Tree e Hash no PostgreSQL e MySQL.
  • Interpretar o plano de execução de uma consulta usando o comando EXPLAIN.
  • Identificar consultas lentas e propor a indexação correta para otimização de performance.

🏢 O Cenário Prático (Seu Desafio)

O aplicativo de rastreamento de entregas da TecProExpress está enfrentando lentidão crítica. O time de suporte identificou que a consulta de busca por CPF do cliente na hora de listar as entregas está levando mais de 5 segundos para responder no banco de produção.

Como Arquiteto de Banco de Dados, você recebeu a missão de analisar as consultas de busca utilizando ferramentas de execução e aplicar índices estratégicos para reduzir o tempo de busca para menos de 5 milissegundos.


🧠 Fundamentos: A Teoria Traduzida

Buscar dados em uma tabela sem índice é como procurar uma palavra em um dicionário que não está em ordem alfabética: você precisa ler o livro inteiro, página por página (Full Table Scan / Seq Scan). Criar um índice é como criar o índice remissivo no fim do livro: você vai direto para a página certa instantaneamente.

📊 Estrutura de Busca: B-Tree vs. Hash

flowchart TD
    subgraph BTree ["Árvore B-Tree (Busca de Intervalos: >, <, >=, <=)"]
        Root["Raiz: id = 50"] --> L1["Esquerda: < 50"]
        Root --> R1["Direita: >= 50"]
        L1 --> L2["Folha: 10, 20, 30"]
        R1 --> R2["Folha: 50, 60, 70"]
    end
  • B-Tree (Padrão): Organiza os dados em uma árvore balanceada. Excelente para consultas de igualdade (=) e buscas por intervalo (BETWEEN, >, <).
  • Hash: Mapeia chaves diretamente para endereços físicos. Perfeito para igualdades exatas (=), mas inútil para intervalos.

📖 Exemplo Guiado: Usando o EXPLAIN

Vamos analisar como o banco de dados planeja fazer uma busca antes de criar o índice.

1. Inicializando o Banco de Dados

CREATE DATABASE tecpro_performance;
-- (Nota: No pgAdmin, abra a Query Tool apontando para o novo banco)

2. Criando Tabela e Analisando sem Índice

CREATE TABLE entrega (
    id SERIAL PRIMARY KEY,
    codigo_rastreamento VARCHAR(50),
    data_envio DATE
);

-- Analisando a busca por código de rastreamento antes do índice
EXPLAIN ANALYZE 
SELECT * FROM entrega WHERE codigo_rastreamento = 'TRK1002938';

Saída Esperada no pgAdmin (Console): Seq Scan on entrega (cost=0.00..38.25 rows=1 width=58) (actual time=0.045..0.082 rows=0 loops=1) (Nota: O termo Seq Scan indica que o banco leu a tabela inteira do início ao fim para tentar achar o registro).


🛠️ Prática Obrigatória 1: Análise e Indexação

  1. Crie a tabela cliente_historico com as colunas: id (PK), nome, cpf (sem chave única física inicial) e email.
  2. Popule a tabela com alguns registros fictícios.
  3. Execute o comando EXPLAIN para analisar a busca por cpf:
    EXPLAIN SELECT * FROM cliente_historico WHERE cpf = '12345678901';
    
  4. Crie um índice B-Tree na coluna cpf e execute novamente o EXPLAIN. Veja a diferença.


💻 Execução do EXPLAIN ANALYZE & Comparação no Terminal

Para verificar o ganho de performance após a criação do índice B-Tree no PostgreSQL:

-- 1. Antes do Índice (Seq Scan)
EXPLAIN ANALYZE SELECT * FROM cliente_historico WHERE cpf = '12345678901';

-- 2. Criando o Índice B-Tree
CREATE INDEX idx_cliente_cpf ON cliente_historico USING btree(cpf);

-- 3. Depois do Índice (Index Scan)
EXPLAIN ANALYZE SELECT * FROM cliente_historico WHERE cpf = '12345678901';

🖥️ Saída Esperada no Terminal (Antes vs Depois):

-- ANTES DO ÍNDICE:
Seq Scan on cliente_historico  (cost=0.00..1840.00 rows=1 width=58) (actual time=14.820..14.825 rows=1 loops=1)
Planning Time: 0.120 ms
Execution Time: 14.860 ms

-- DEPOIS DO ÍNDICE B-TREE:
Index Scan using idx_cliente_cpf on cliente_historico  (cost=0.28..8.30 rows=1 width=58) (actual time=0.035..0.037 rows=1 loops=1)
Planning Time: 0.145 ms
Execution Time: 0.058 ms

🌐 Exemplo de Payload JSON para Telemetria de Índices (Swagger /docs)

{
  "tabela": "cliente_historico",
  "coluna_indexada": "cpf",
  "tipo_indice": "BTREE",
  "tempo_execucao_ms_antes": 14.86,
  "tempo_execucao_ms_depois": 0.058,
  "reducao_tempo_percentual": 99.6
}

🛠️ Prática Obrigatória 2: B-Tree vs. Hash (Performance)

  1. Crie um índice do tipo Hash na coluna email da tabela cliente_historico.
  2. Crie um índice do tipo B-Tree na coluna id (gerado automaticamente pela PK).
  3. Escreva o script SQL de criação dos dois índices (PostgreSQL e MySQL equivalentes).

📤 Instruções de Entrega (Microsoft Teams)

Após validar a performance das suas consultas locais:

  1. Salve o script SQL contendo a criação da tabela, comandos de EXPLAIN e a criação dos índices em um arquivo .sql (Ex: Atividade_11_SeuNome.sql).
  2. Adicione comentários no código explicando o que significa o custo (cost) retornado pelo EXPLAIN e quando se deve preferir um índice B-Tree sobre um Hash.
  3. Envie o arquivo .sql no Microsoft Teams.

💡 Checkpoint de Lógica

Importante

O Preço do Índice: Se os índices deixam as consultas super rápidas, por que não indexamos todas as colunas de todas as tabelas? (Resposta: Cada índice consome espaço físico em disco e reduz a velocidade de escrita (INSERT, UPDATE, DELETE), pois o banco de dados precisa atualizar os índices a cada modificação. O bom arquiteto indexa apenas colunas usadas frequentemente em filtros WHERE e junções JOIN). 🧠🛡️

---

🔥 Desafio de Fixação (Opcional)

Nível: Especialista em Performance 🏆

O que é um Covering Index (ou Índice Composto)? Como você criaria um único índice para otimizar uma busca que filtra por cidade E por status ao mesmo tempo?


🔑 Gabarito de Código/Fórmulas Completo

🐘 Padrão PostgreSQL (pgAdmin)

-- 1. Criação da Tabela
CREATE TABLE cliente_historico (
    id SERIAL PRIMARY KEY,
    nome VARCHAR(100),
    cpf VARCHAR(11),
    email VARCHAR(100)
);

-- 2. Criação do Índice B-Tree (Foco em buscas por igualdade e ordenações)
CREATE INDEX idx_cli_cpf ON cliente_historico(cpf);

-- 3. Criação do Índice Hash (Exclusivo para igualdade exata)
CREATE INDEX idx_cli_email_hash ON cliente_historico USING HASH (email);

-- 4. Verificação de Execução
EXPLAIN ANALYZE 
SELECT * FROM cliente_historico WHERE cpf = '12345678901';

🐬 Comparativo MySQL (Workbench)

-- No MySQL, o índice Hash não é suportado no motor padrão InnoDB (apenas B-Tree)
CREATE INDEX idx_cli_cpf ON cliente_historico(cpf);
CREATE INDEX idx_cli_email ON cliente_historico(email);

🔍 Explicação do Gabarito:

  • idx_cli_cpf: Índice B-Tree criado para buscas por CPF.
  • USING HASH: Sintaxe do PostgreSQL para criar índices usando hashing direto. Muito rápido para buscas exatas por e-mail, mas não suporta operadores como >, <, LIKE ou ORDER BY.
  • EXPLAIN ANALYZE: Mostra o planejamento do otimizador e executa a query, trazendo o tempo real de CPU em milissegundos.

📊 Rubrica Formativa de Avaliação

Critério de Avaliação Insuficiente (0% - 40%) Regular (41% - 70%) Excelente (71% - 100%)
Diagnóstico de Performance (EXPLAIN) Não executa o comando `EXPLAIN` ou não interpreta o plano de execução. Executa o `EXPLAIN` mas sem comparar os custos antes e depois do índice. Análise de performance precisa demonstrando a redução drástica de custo (cost) e transição de Sequential Scan para Index Scan.
Estratégia de Índices (B-Tree vs Hash) Tenta criar índices sem conhecer os tipos ou sintaxe. Cria o índice B-Tree mas omite a aplicação do índice Hash. Aplica índices B-Tree e Hash com justificativas ricas sobre o custo de atualização em escritas (`INSERT`/`UPDATE`).
Entrega do Script SQL Entrega script com erros no PostgreSQL/MySQL. Script entregue mas sem explicações nos comentários. Submete `Atividade_11_SeuNome.sql` perfeitamente comentado e formatado.

🎯 ATIVIDADE 12 — O COFRE DOS DADOS

📖 Fundamentação Teórica

Para realizar este laboratório com sucesso, certifique-se de ter compreendido os conceitos apresentados no:
👉 CAPÍTULO 12: RESTRIÇÕES DE INTEGRIDADE E CONSTRAINTS

Bem-vindo à décima segunda semana (4 aulas) do curso de Banco de Dados. Em sistemas comerciais, falhas acontecem: a energia cai, a internet desconecta ou a aplicação trava no meio de um processo. Se você estiver transferindo dinheiro ou atualizando o status de uma entrega, um erro no meio do caminho pode gerar dados órfãos e prejuízos. Hoje, aprenderemos a usar as Transações e o conceito ACID para transformar o banco de dados em um verdadeiro cofre inexpugnável. 🛡️🔒


🎯 Objetivos de Aprendizagem do Laboratório

Ao final deste laboratório prático (estimativa: 4 horas presenciais / autoguiadas), você será capaz de:

  • Compreender e aplicar as propriedades ACID (Atomicidade, Consistência, Isolamento, Durabilidade).
  • Controlar o fluxo transacional com BEGIN, COMMIT e ROLLBACK no PostgreSQL e MySQL.
  • Simular e evitar anomalias de concorrência (Leitura Suja, Leitura Não-Repetível e Fantasma).
  • Compreender a utilidade prática dos Níveis de Isolamento de transação.

🏢 O Cenário Prático (Seu Desafio)

A TecProExpress está implementando o pagamento automático de entregadores parceiros. O fluxo de repasse financeiro funciona assim:

  1. Debita o valor da taxa da carteira digital da TecProExpress (Tabela carteira_empresa).
  2. Credita o mesmo valor na conta do entregador (Tabela carteira_entregador).

Se o passo 1 rodar com sucesso, mas o servidor cair antes do passo 2, o dinheiro sumirá da empresa e nunca chegará ao entregador! Seu desafio é empacotar esse processo de transferência em uma transação segura, garantindo que se uma etapa falhar, toda a operação seja desfeita (Rollback).


🧠 Fundamentos: A Teoria Traduzida

As 4 Regras de Ouro: ACID

flowchart LR
    A["A: Atomicidade - Tudo ou Nada"] --> B["C: Consistência - Sem Regras Quebradas"]
    B --> C["I: Isolamento - Operações Separadas"]
    C --> D["D: Durabilidade - Gravado em Disco"]
    style A fill:#ffe0b2,stroke:#fb8c00
    style D fill:#c8e6c9,stroke:#4caf50
  1. Atomicidade: A transação é indivisível. Se uma instrução falhar, o banco desfaz tudo o que já foi feito na mesma transação.
  2. Consistência: A transação move o banco de um estado válido para outro estado válido, respeitando chaves estrangeiras e restrições.
  3. Isolamento: Transações paralelas não interferem umas nas outras até que sejam finalizadas.
  4. Durabilidade: Uma vez concluída (COMMIT), a alteração é permanente e não será perdida mesmo se o servidor pegar fogo no segundo seguinte.

📖 Exemplo Guiado: Transação Controlada

Veja como simular uma falha e proteger os saldos usando blocos de transação.

1. Inicializando o Banco de Dados

CREATE DATABASE tecpro_transacoes;
-- (Nota: No pgAdmin, abra a Query Tool apontando para o banco tecpro_transacoes)

2. Criando o Cenário Físico

CREATE TABLE conta (
    id SERIAL PRIMARY KEY,
    titular VARCHAR(100),
    saldo DECIMAL(10,2) CHECK (saldo >= 0) -- Não permite saldo negativo!
);

INSERT INTO conta (titular, saldo) VALUES 
('TecPro Express Corp', 1000.00),
('Entregador Marcos', 0.00);

3. A Simulação de Falha Segura (Rollback Automático)

BEGIN; -- Inicia a transação (No MySQL pode ser START TRANSACTION)

-- Passo 1: Retira 150.00 da empresa
UPDATE conta SET saldo = saldo - 150.00 WHERE titular = 'TecPro Express Corp';

-- Passo 2: Tenta depositar na conta de Marcos
UPDATE conta SET saldo = saldo + 150.00 WHERE titular = 'Entregador Marcos';

-- Vamos simular que algo deu errado e decidimos desfazer a operação antes de salvar
ROLLBACK;

-- Verificando saldos: tudo voltou ao estado original!
SELECT * FROM conta;


💻 Execução de Transação ACID com Rollback & Saída no Terminal

Para verificar a proteção de integridade física em caso de falha durante a execução de transação:

-- Executando bloco transacional com falha forçada
BEGIN;
UPDATE conta SET saldo = saldo - 2000.00 WHERE titular = 'TecPro Express Corp';
-- ERRO: new row for relation "conta" violates check constraint "chk_saldo_positivo"
ROLLBACK;

🖥️ Saída Esperada no Terminal do PostgreSQL:

BEGIN
psql:transacao.sql:3: ERROR:  new row for relation "conta" violates check constraint "chk_saldo_positivo"
DETAIL:  Failing row contains (1, TecPro Express Corp, -1000.00).
ROLLBACK
-----------------------------------------------------------------
🔒 [ACID] Atomicidade garantida: saldo original mantido em R$ 1.000,00.

🌐 Requisição cURL e Payload JSON para Transferência ACID (Swagger /docs)

curl -X POST "http://127.0.0.1:8000/api/v1/financeiro/transferir" \
     -H "Content-Type: application/json" \
     -d '{
       "conta_origem_id": 1,
       "conta_destino_id": 2,
       "valor_transferencia": 150.00
     }'

🔹 Resposta JSON:

{
  "transacao_id": "TX-998811",
  "status": "CONCLUIDA_COM_SUCESSO",
  "saldo_origem_atual": 850.00,
  "saldo_destino_atual": 150.00
}

🛠️ Prática Obrigatória 1: Simulação de Estouro de Saldo

Cenário: Verificar se a restrição CHECK (saldo >= 0) realmente protege a TecProExpress contra saldo negativo quando uma transação tenta uma operação inválida.

  1. Abra uma transação com BEGIN;.
  2. Tente debitar R$ 2000,00 da conta 'TecPro Express Corp' (que tem apenas R$ 1000,00 de saldo) com um UPDATE.
  3. Em seguida, tente creditar esse mesmo valor na conta de 'Entregador Marcos'.
  4. Rode COMMIT; e observe o que o PostgreSQL retorna.
  5. Confirme, com um SELECT * FROM conta;, que nenhum saldo foi alterado.

🛠️ Prática Obrigatória 2: Níveis de Isolamento (Concorrência)

  1. Abra duas janelas de Query Tool (Abas) no seu pgAdmin (representando dois usuários simultâneos no banco).
  2. Na Aba 1, altere o saldo de Marcos para R$ 500,00 sem dar COMMIT:
    BEGIN;
    UPDATE conta SET saldo = 500.00 WHERE titular = 'Entregador Marcos';
    
  3. Na Aba 2, realize uma consulta simples: SELECT saldo FROM conta WHERE titular = 'Entregador Marcos';.
  4. O saldo consultado na Aba 2 é R$ 0,00 ou R$ 500,00? Justifique com base no conceito de Isolamento de Transação.

📤 Instruções de Entrega (Microsoft Teams)

Após validar suas transações:

  1. Salve o script SQL contendo os testes de criação de contas, a simulação de rollback e as respostas textuais às perguntas de concorrência em um arquivo .sql (Ex: Atividade_12_SeuNome.sql).
  2. Adicione comentários no código explicando o que são os operadores COMMIT e ROLLBACK.
  3. Envie o arquivo .sql correspondente no Microsoft Teams para avaliação.

💡 Checkpoint de Lógica

Importante

O Perigo do Lock (Bloqueio): Quando você executa um UPDATE dentro de uma transação não finalizada, o banco de dados bloqueia aquela linha para que ninguém mais a modifique simultaneamente. Se você esquecer de dar COMMIT ou ROLLBACK, a linha ficará travada para sempre, fazendo com que outras conexões travem na fila! Mantenha transações rápidas e objetivas. 🧠🛡️

---

🔥 Desafio de Fixação (Opcional)

Nível: Arquiteto de Sistemas 🏆

Pesquise sobre a anomalia Dirty Read (Leitura Suja) e o nível de isolamento READ UNCOMMITTED. Por que o PostgreSQL não permite leituras sujas mesmo se configurado explicitamente para esse nível?


🔑 Gabarito de Código/Fórmulas Completo

🐘 Padrão PostgreSQL (pgAdmin)

-- Prática 1: Simulação de estouro de saldo
BEGIN;

-- Tentativa de debitar mais do que o saldo atual de R$ 1000.00
UPDATE conta 
SET saldo = saldo - 2000.00 
WHERE titular = 'TecPro Express Corp';
-- (Isso causará uma violação de constraint de CHECK e anulará os comandos seguintes)

UPDATE conta 
SET saldo = saldo + 2000.00 
WHERE titular = 'Entregador Marcos';

COMMIT; -- O Postgres retornará "ROLLBACK" em vez de commit devido à falha anterior!

-- Verificando que nada foi alterado
SELECT * FROM conta;

🐬 Padrão MySQL (Workbench / DBeaver)

-- No MySQL, por padrão, violações de CHECK em motores antigos eram ignoradas,
-- mas a partir da versão 8.0 funcionam exatamente igual:
START TRANSACTION;

UPDATE conta SET saldo = saldo - 2000.00 WHERE titular = 'TecPro Express Corp';
UPDATE conta SET saldo = saldo + 2000.00 WHERE titular = 'Entregador Marcos';

COMMIT;

🔍 Explicação do Gabarito:

  • BEGIN / START TRANSACTION: Avisam ao SGBD para suspender a gravação automática imediata em disco (Autocommit) e aguardar confirmação.
  • Violação de Restrição: Uma vez que uma constraint é violada, a transação entra em estado de erro e o commit falha automaticamente, garantindo a integridade dos dados.

🛑 Anatomia Visual de um Deadlock em Concorrência

Quando duas transações concorrentes tentam atualizar registros bloqueados mutuamente, o SGBD detecta o ciclo de espera e aborta uma das operações:

sequenceDiagram
    autonumber
    actor T1 as Transação A (Thread 1)
    actor T2 as Transação B (Thread 2)
    participant DB as SGBD (Lock Manager)

    T1->>DB: BEGIN; UPDATE conta SET saldo = saldo - 100 WHERE id = 1;
    Note over DB: Lock Exclusivo na Conta 1 para T1
    T2->>DB: BEGIN; UPDATE conta SET saldo = saldo - 50 WHERE id = 2;
    Note over DB: Lock Exclusivo na Conta 2 para T2

    T1->>DB: UPDATE conta SET saldo = saldo + 100 WHERE id = 2;
    Note over DB: T1 fica BLOQUEADA aguardando liberação da Conta 2...

    T2->>DB: UPDATE conta SET saldo = saldo + 50 WHERE id = 1;
    Note over DB: ⚠️ CICLO DE BLOQUEIO (DEADLOCK DETECTADO!)
    DB-->>T2: ❌ ERROR: Deadlock detected (T2 abortada via ROLLBACK)
    Note over DB: Lock da Conta 2 é liberado!
    DB-->>T1: ✅ UPDATE Conta 2 executado com sucesso!
    T1->>DB: COMMIT;

📊 Rubrica Formativa de Avaliação

Critério de Avaliação Insuficiente (0% - 40%) Regular (41% - 70%) Excelente (71% - 100%)
Atomicidade & Controle (BEGIN / COMMIT / ROLLBACK) Não consegue simular transações ou ignora o comportamento do `ROLLBACK`. Escreve o bloco transacional mas não prova o descarte das alterações após a falha. Demonstração perfeita do princípio da Atomicidade ACID com rollback automático em caso de violação de constraint.
Concorrência & Níveis de Isolamento (Locks) Não realiza o teste com duas abas/conexões simultâneas. Realiza o teste mas sem explicar o conceito de travamento (Locking). Explicação rica sobre isolamento de transações, prevenção de Dirty Reads e os riscos de travamento em produção.
Entrega do Script SQL Entrega script com erros de sintaxe transacional. Script entregue mas sem comentários sobre o experimento. Submete `Atividade_12_SeuNome.sql` perfeitamente estruturado e comentado.

🎯 ATIVIDADE 13 — O BANCO INTELIGENTE

📖 Fundamentação Teórica

Para realizar este laboratório com sucesso, certifique-se de ter compreendido os conceitos apresentados no:
👉 CAPÍTULO 13: DML — INSERT, UPDATE E DELETE

Bem-vindo à décima terceira semana (4 aulas) do curso de Banco de Dados. Até agora, tratamos o banco de dados como um repositório passivo: a aplicação envia os dados e o banco apenas os guarda. Mas e se o próprio banco de dados pudesse tomar decisões, rodar códigos automáticos e proteger as regras de negócio? Hoje entraremos no mundo do Banco Inteligente, aprendendo a criar Stored Procedures (Procedimentos Armazenados) e Triggers (Gatilhos) para automatizar rotinas operacionais cruciais. 🛡️🤖


🎯 Objetivos de Aprendizagem do Laboratório

Ao final deste laboratório prático (estimativa: 4 horas presenciais / autoguiadas), você será capaz de:

  • Compreender o conceito e os benefícios de programar dentro do SGBD.
  • Criar e executar Stored Procedures e Functions no PostgreSQL e MySQL.
  • Desenvolver Triggers (Gatilhos) automáticos para controle de eventos (INSERT, UPDATE, DELETE).
  • Automatizar o controle de inventário (baixa automática de estoque) a cada venda registrada.

🏢 O Cenário Prático (Seu Desafio)

O departamento de logística da TecProExpress está preocupado com furos no estoque. Atualmente, quando um pedido é feito, a aplicação web é responsável por subtrair a quantidade vendida da tabela de produtos. Porém, falhas na aplicação às vezes pulam essa etapa, fazendo com que produtos sem estoque continuem a ser vendidos.

Seu desafio como Engenheiro de Dados é mover essa inteligência para o banco de dados. Você criará uma Trigger que, de forma 100% garantida e automática, subtrairá a quantidade comprada do estoque toda vez que um novo produto for inserido na tabela item_pedido.


🧠 Fundamentos: A Teoria Traduzida

O Gatilho Automático (Trigger)

Uma Trigger é como um sensor de presença com alarme: ela fica "vigiando" uma tabela. Se ocorrer um evento específico (como a inserção de uma linha), ela dispara um bloco de código (função) automaticamente antes ou depois da modificação.

flowchart TD
    A["Usuário insere linha em ITEM_PEDIDO"] --> B{"Trigger disparada?"}
    B -- SIM --> C["Executa Função de Ajuste de Estoque"]
    C --> D["Estoque do Produto é Subtraído"]
    D --> E["Linha é gravada fisicamente"]
    style B fill:#ffe0b2,stroke:#fb8c00
    style D fill:#c8e6c9,stroke:#4caf50
  • OLD: Palavra-chave que dá acesso aos valores da linha antes da alteração (usado em Updates/Deletes).
  • NEW: Palavra-chave que dá acesso aos novos valores que estão sendo gravados (usado em Inserts/Updates).

📖 Exemplo Guiado: Criando uma Stored Procedure

Vamos criar um procedimento para reabastecer o estoque de um produto de forma rápida.

1. Inicializando o Banco de Dados

CREATE DATABASE tecpro_automacao;
-- (Nota: No pgAdmin, abra a Query Tool apontando para o banco tecpro_automacao)

2. Criando o Cenário de Teste

CREATE TABLE estoque_produto (
    id SERIAL PRIMARY KEY,
    nome VARCHAR(100),
    quantidade_estoque INT NOT NULL DEFAULT 0
);

INSERT INTO estoque_produto (nome, quantidade_estoque) VALUES 
('Capacete Pro', 10),
('Cabo Carregador', 50);

3. Criando a Procedure no PostgreSQL

CREATE OR REPLACE PROCEDURE adicionar_estoque(prod_id INT, qtd INT)
LANGUAGE plpgsql
AS $$
BEGIN
    UPDATE estoque_produto 
    SET quantidade_estoque = quantidade_estoque + qtd 
    WHERE id = prod_id;
END;
$$;

-- Executando a Procedure para adicionar 20 capacetes
CALL adicionar_estoque(1, 20);

-- Verificando: Capacete Pro agora possui 30 unidades!
SELECT * FROM estoque_produto;

🛠️ Prática Obrigatória 1: Criando a Trigger de Estoque (PostgreSQL)

  1. Crie a tabela pedido e a tabela item_pedido (seguindo as FKs para estoque_produto).
  2. Crie uma Função de Trigger no PostgreSQL (linguagem plpgsql) que subtraia a quantidade comprada do estoque do produto correspondente na tabela estoque_produto usando o registro NEW.
  3. Crie a Trigger associada à tabela item_pedido que dispara AFTER INSERT para cada linha inserida.

💻 Disparo da Trigger Automática & Logs no Terminal

Para verificar o abatimento automático de estoque acionado pelo gatilho SQL:

-- Inserindo item de pedido que aciona a TRIGGER 'trg_atualizar_estoque'
INSERT INTO item_pedido (id_pedido, id_produto, quantidade) VALUES (101, 1, 3);

-- Consultando estoque atualizado automaticamente:
SELECT nome, quantidade_estoque FROM estoque_produto WHERE id = 1;

🖥️ Saída Esperada no Terminal:

INSERT 0 1
-----------------------------------------------------------------
       nome       | quantidade_estoque 
------------------+--------------------
 Capacete Pro     |                 27
(1 row)
⚡ [TRIGGER] Quantidade reduzida de 30 para 27 via trigger 'trg_atualizar_estoque'.

🌐 Requisição cURL de Compra que Aciona a Trigger (Swagger /docs)

curl -X POST "http://127.0.0.1:8000/api/v1/pedidos/itens" \
     -H "Content-Type: application/json" \
     -d '{
       "pedido_id": 101,
       "produto_id": 1,
       "quantidade": 3
     }'

🔹 Resposta JSON:

{
  "mensagem": "Item adicionado ao pedido",
  "trigger_executada": true,
  "estoque_restante": 27
}

🛠️ Prática Obrigatória 2: Equivalência em MySQL

  1. Pesquise a sintaxe de Trigger no MySQL (que não exige a criação de uma função separada, permitindo declarar o código diretamente no corpo do gatilho).
  2. Escreva o script equivalente da Trigger de subtração de estoque adaptado para rodar no MySQL Workbench/DBeaver.

📤 Instruções de Entrega (Microsoft Teams)

Após validar suas automações:

  1. Salve o script SQL completo contendo a criação das tabelas, da procedure, da função de trigger e da trigger física em um arquivo .sql (Ex: Atividade_13_SeuNome.sql).
  2. Insira comentários explicando qual a diferença de comportamento entre uma Trigger disparada BEFORE (antes) e uma disparada AFTER (depois) do evento.
  3. Envie o arquivo .sql correspondente no Microsoft Teams para avaliação.

💡 Checkpoint de Lógica

Importante

O Perigo das Triggers Ocultas: Embora as triggers sejam fantásticas para garantir a integridade, use-as com moderação. Como elas rodam "por baixo dos panos", novos desenvolvedores da equipe podem ficar confusos se o estoque começar a mudar de valor misteriosamente sem que haja nenhum comando UPDATE aparente no código da aplicação. Sempre documente suas triggers! 🧠🛡️

---

📊 Rubrica Formativa de Avaliação

Critério de Avaliação Insuficiente (0% - 40%) Regular (41% - 70%) Excelente (71% - 100%)
Automação com Triggers & Funções de Gatilho Erros de sintaxe PL/pgSQL ou na criação da Trigger no banco de dados. Cria a Trigger mas sem conseguir acessar a variável especial `NEW.quantidade`. Trigger automatizada com sucesso atualizando o estoque automaticamente após a inserção de itens no pedido.
Momento do Disparo (BEFORE vs AFTER) Omite a explicação da diferença entre gatilhos BEFORE e AFTER. Explica superficialmente o momento do disparo. Comentários técnicos precisos justificando a escolha do disparo `AFTER INSERT` para baixa de estoque.
Entrega do Script SQL Entrega script com erros de execução no PostgreSQL ou MySQL. Script entregue mas sem o teste prático de verificação. Submete `Atividade_13_SeuNome.sql` contendo tabelas, função de trigger e comandos de teste validados.

🔥 Desafio de Fixação (Opcional)

Nível: Arquiteto de Banco de Dados 🏆

Modifique a Trigger para impedir a inserção de um item no pedido caso a quantidade solicitada seja maior do que a quantidade disponível em estoque. (Dica: Use RAISE EXCEPTION no PostgreSQL ou SIGNAL SQLSTATE no MySQL para barrar a inserção).


🔑 Gabarito de Código/Fórmulas Completo

🐘 Padrão PostgreSQL (pgAdmin)

-- 1. Criação das Tabelas Base
CREATE TABLE estoque_produto (
    id SERIAL PRIMARY KEY,
    nome VARCHAR(100),
    quantidade_estoque INT NOT NULL DEFAULT 0
);

CREATE TABLE pedido (
    id SERIAL PRIMARY KEY,
    data_criacao TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE item_pedido (
    id_pedido INT REFERENCES pedido(id),
    id_produto INT REFERENCES estoque_produto(id),
    quantidade INT,
    PRIMARY KEY (id_pedido, id_produto)
);

INSERT INTO estoque_produto (nome, quantidade_estoque) VALUES ('Smartphone X', 10);
INSERT INTO pedido VALUES (1);

-- 2. Criação da Função de Trigger no Postgres
CREATE OR REPLACE FUNCTION processar_baixa_estoque()
RETURNS TRIGGER AS $$
BEGIN
    UPDATE estoque_produto
    SET quantidade_estoque = quantidade_estoque - NEW.quantidade
    WHERE id = NEW.id_produto;
    
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

-- 3. Criação da Trigger vinculada
CREATE TRIGGER trg_baixa_estoque
AFTER INSERT ON item_pedido
FOR EACH ROW
EXECUTE FUNCTION processar_baixa_estoque();

-- 4. Teste Prático
INSERT INTO item_pedido (id_pedido, id_produto, quantidade) VALUES (1, 1, 3);

-- Verificação: O estoque deve ter baixado de 10 para 7!
SELECT * FROM estoque_produto;

🐬 Comparativo MySQL (Workbench / DBeaver)

-- No MySQL a trigger é declarada diretamente, sem necessidade de FUNCTION
DELIMITER $$

CREATE TRIGGER trg_baixa_estoque_mysql
AFTER INSERT ON item_pedido
FOR EACH ROW
BEGIN
    UPDATE estoque_produto
    SET quantidade_estoque = quantidade_estoque - NEW.quantidade
    WHERE id = NEW.id_produto;
END$$

DELIMITER ;

🔍 Explicação do Gabarito:

  • NEW.quantidade: Dá acesso à quantidade que o usuário está inserindo na tabela item_pedido.
  • FOR EACH ROW: Garante que se o usuário inserir 5 linhas de uma vez, a trigger rodará 5 vezes individualmente para dar baixa em cada produto.
  • DELIMITER: No MySQL, altera o caractere de término de instrução (de ; para $$) para que o SGBD não ache que a Trigger terminou no primeiro ponto e vírgula interno.

🎯 ATIVIDADE 14 — O MELHOR DE DOIS MUNDOS

📖 Fundamentação Teórica

Para realizar este laboratório com sucesso, certifique-se de ter compreendido os conceitos apresentados no:
👉 CAPÍTULO 14: CONSULTAS SELECT (A ARTE DO DQL)

Bem-vindo à décima quarta semana (4 aulas) do curso de Banco de Dados. Até aqui, ou trabalhamos com a rigidez total das tabelas SQL, ou com a flexibilidade completa das coleções JSON do MongoDB. Mas sabia que os SGBDs modernos permitem unificar essas duas abordagens? Hoje aprenderemos a criar e consultar dados JSONB nativos no PostgreSQL, entendendo como criar uma arquitetura de banco de dados híbrido poliglota de altíssima performance. 🛡️🧬


🎯 Objetivos de Aprendizagem do Laboratório

Ao final deste laboratório prático (estimativa: 4 horas presenciais / autoguiadas), você será capaz de:

  • Compreender o conceito de banco de dados semiestruturado e persistência híbrida.
  • Criar tabelas relacionais contendo colunas do tipo JSON e JSONB no PostgreSQL.
  • Utilizar operadores de navegação JSON (->, ->>, #>) em consultas SQL.
  • Filtrar e pesquisar campos aninhados dentro de objetos JSON usando a cláusula WHERE.

🏢 O Cenário Prático (Seu Desafio)

O catálogo de produtos eletrônicos da TecProExpress possui um grande problema: a diversidade de atributos técnicos. Um "Monitor" possui tamanho de tela e frequência de atualização, enquanto um "Teclado" possui tipo de switch mecânico e layout. No SQL tradicional, isso geraria uma tabela cheia de colunas nulas. No NoSQL puro, perderíamos a consistência relacional dos pedidos.

Como Arquiteto de Soluções, seu desafio é criar uma tabela relacional clássica de produto, mas que contenha uma coluna especial do tipo atributos_tecnicos JSONB. Você registrará itens variados e extrairá relatórios refinados acessando chaves JSON diretamente de dentro da sua consulta SELECT SQL.


🧠 Fundamentos: A Teoria Traduzida

JSON vs. JSONB

No PostgreSQL, temos dois tipos de dados para guardar objetos de documentos:

  • JSON: Guarda o dado exatamente como um texto de formato JSON. A gravação é rápida, mas a consulta é lenta porque o SGBD precisa re-analisar o texto a cada busca.
  • JSONB (Binário): Decompõe o objeto JSON em uma estrutura binária indexada. A escrita é um milissegundo mais lenta, mas as consultas são instantâneas, permitindo inclusive a criação de índices de performance sobre chaves internas!
flowchart TD
    subgraph Row ["Linha de Produto Relacional"]
        ID["id: 101 (SERIAL)"]
        Nome["nome: 'Teclado Gamer' (VARCHAR)"]
        Preco["preco: 250.00 (DECIMAL)"]
        
        subgraph JSONB ["detalhes (JSONB / NoSQL Híbrido)"]
            Switch["'switch': 'Red'"]
            Layout["'layout': 'ABNT2'"]
            Led["'led': true"]
        end
    end
    
    style Row fill:#eceff1,stroke:#37474f,stroke-width:2px
    style JSONB fill:#e8f5e9,stroke:#4caf50

Operadores de Navegação no Postgres:

OperadorRetornoUso DidáticoExemplo Prático
->Objeto JSONRetorna a chave mantendo o formato JSONatributos -> 'detalhes'
->>Texto PuroRetorna o valor da chave convertida em stringatributos ->> 'cor'
#>Objeto aninhadoNavega por um caminho específico de chavesatributos #> '{especificacoes, cpu}'

📖 Exemplo Guiado: Gravando e Consultando JSONB

1. Inicializando o Banco de Dados

CREATE DATABASE tecpro_hibrido;
-- (Nota: No pgAdmin, abra a Query Tool apontando para o banco tecpro_hibrido)

2. Criando Tabela e Populando com Objetos Flexíveis

CREATE TABLE produto_hibrido (
    id SERIAL PRIMARY KEY,
    nome VARCHAR(100) NOT NULL,
    preco DECIMAL(10,2),
    detalhes JSONB -- Coluna híbrida NoSQL
);

-- Inserindo produtos com dados de especificações completamente diferentes
INSERT INTO produto_hibrido (nome, preco, detalhes) VALUES 
('Teclado Gamer', 250.00, '{"switch": "Red", "layout": "ABNT2", "led": true}'),
('Monitor 27', 1500.00, '{"tamanho": "27 polegadas", "frequencia": 144, "entradas": ["HDMI", "DisplayPort"]}');

3. Consultando Chaves Internas via SQL

-- Queremos saber os nomes e os switches de todos os teclados mecânicos
SELECT nome, detalhes ->> 'switch' AS switch_utilizado 
FROM produto_hibrido 
WHERE detalhes ->> 'switch' IS NOT NULL;

🛠️ Prática Obrigatória 1: Consultando Estruturas Aninhadas

  1. Insira um terceiro produto na tabela produto_hibrido contendo um objeto aninhado dentro da coluna detalhes:
    INSERT INTO produto_hibrido (nome, preco, detalhes) VALUES
    ('Mouse Ergonômico', 320.00, '{"marca": "Logitech", "dimensoes": {"altura": 10, "largura": 20}}');
    
  2. Escreva uma consulta SQL que navegue por esse caminho e traga a altura do produto utilizando o operador de caminho #>.
  3. Filtre a consulta usando WHERE para trazer apenas produtos da marca "Logitech".

💻 Execução de Consultas JSONB no Terminal do PostgreSQL

Para consultar atributos dinâmicos armazenados no JSONB via terminal psql:

-- Consultando campos internos do JSONB com operadores -> e ->>
SELECT nome, preco, detalhes ->> 'marca' AS marca, detalhes #>> '{dimensoes,altura}' AS altura_cm
FROM produto_hibrido
WHERE detalhes ->> 'marca' = 'Logitech';

🖥️ Saída Esperada no Terminal:

      nome       | preco  |  marca   | altura_cm 
-----------------+--------+----------+-----------
 Mouse Ergonômico| 320.00 | Logitech | 10
(1 row)

🌐 Requisição cURL com Payload Dinâmico (Swagger /docs)

curl -X POST "http://127.0.0.1:8000/api/v1/produtos-hibridos" \
     -H "Content-Type: application/json" \
     -d '{
       "nome": "Mouse Ergonômico",
       "preco": 320.00,
       "detalhes": {
         "marca": "Logitech",
         "conexao": "Sem Fio",
         "dimensoes": {"altura": 10, "largura": 20}
       }
     }'

🔹 Resposta JSON:

{
  "id": 3,
  "nome": "Mouse Ergonômico",
  "status": "CADASTRADO_COM_JSONB"
}

🛠️ Prática Obrigatória 2: Equivalência e Limitações (Comparativo)

  1. Pesquise como o MySQL implementa o suporte a dados JSON (utilizando o tipo JSON e a função JSON_EXTRACT ou o operador inline ->).
  2. Escreva o script equivalente da Prática 1 adaptado para rodar no MySQL Workbench.

📤 Instruções de Entrega (Microsoft Teams)

Após validar suas consultas híbridas:

  1. Salve o script SQL contendo a criação da tabela híbrida, as inserções de teste e as consultas elaboradas em um arquivo .sql (Ex: Atividade_14_SeuNome.sql).
  2. Adicione comentários explicando por que a persistência híbrida é vantajosa sobre ter centenas de colunas vazias em uma tabela relacional.
  3. Envie o arquivo .sql no Microsoft Teams para avaliação.

💡 Checkpoint de Lógica

Importante

Índices em JSONB: Um dos grandes trunfos do PostgreSQL é a capacidade de criar índices em chaves dentro do JSONB. Se você fizer muitas buscas filtrando pelo campo marca que está dentro do objeto JSON, você pode criar um índice específico: CREATE INDEX idx_prod_marca ON produto_hibrido ((detalhes->>'marca')); Isso eleva a performance para o patamar de buscas por colunas físicas relacionais! 🧠🛡️

---

📊 Rubrica Formativa de Avaliação

Critério de Avaliação Insuficiente (0% - 40%) Regular (41% - 70%) Excelente (71% - 100%)
Persistência Híbrida Relacional/JSONB Erros na sintaxe dos campos JSON ou no operador de extração de dados. Cria a coluna JSONB mas sem conseguir consultar objetos aninhados. Persistência híbrida perfeita navegando por caminhos aninhados (`#>`) e extraindo dados como texto (`->>`).
Comparativo SGBDs (PostgreSQL vs MySQL) Não apresenta o equivalente em MySQL ou utiliza sintaxe errada. Apresenta o script MySQL mas sem entender a diferença do operador `$.caminho`. Comparativo rico entre `JSONB` no Postgres e tipo `JSON` no MySQL com justificativa sobre esparsa de colunas.
Entrega do Script SQL Entrega script com erros de execução. Script entregue mas sem comentários teóricos. Submete `Atividade_14_SeuNome.sql` perfeitamente testado com tabelas relacionais e documentos JSON.

🔥 Desafio de Fixação (Opcional)

Nível: Arquiteto de Dados Poliglota 🏆

Escreva uma query SQL que retorne o primeiro item da lista (array) de conexões entradas do monitor de 27 polegadas cadastrado no exemplo guiado. (Dica: Use -> com índices numéricos zero-based para navegar por arrays no Postgres).


🔑 Gabarito de Código/Fórmulas Completo

🐘 Padrão PostgreSQL (pgAdmin)

-- 1. Inserção do registro aninhado
INSERT INTO produto_hibrido (nome, preco, detalhes) VALUES 
('Mouse Ergonômico', 320.00, '{"marca": "Logitech", "dimensoes": {"altura": 10, "largura": 20}}');

-- 2. Consulta de navegação de caminho aninhado (#> com array de chaves)
SELECT nome, detalhes #> '{dimensoes, altura}' AS altura_interna
FROM produto_hibrido
WHERE detalhes ->> 'marca' = 'Logitech';

-- 3. Resposta ao Desafio (Array Indexing)
SELECT nome, detalhes -> 'entradas' ->> 0 AS primeira_entrada
FROM produto_hibrido
WHERE nome = 'Monitor 27';

🐬 Comparativo MySQL (Workbench / DBeaver)

-- No MySQL usamos o tipo JSON genérico
CREATE TABLE produto_hibrido_mysql (
    id INT PRIMARY KEY AUTO_INCREMENT,
    nome VARCHAR(100) NOT NULL,
    preco DECIMAL(10,2),
    detalhes JSON
);

INSERT INTO produto_hibrido_mysql (nome, preco, detalhes) VALUES 
('Mouse Pro', 300.00, '{"marca": "Logitech", "dimensoes": {"altura": 12, "largura": 6}}');

-- No MySQL usamos o operador ->> com sintaxe de caminho JSON de cifrão ($)
SELECT nome, detalhes ->> '$.dimensoes.altura' AS altura_interna
FROM produto_hibrido_mysql
WHERE detalhes ->> '$.marca' = 'Logitech';

🔍 Explicação do Gabarito:

  • Operador #>: Permite passar um array de chaves {objeto, subobjeto} para navegar recursivamente sem precisar fazer múltiplos encadeamentos de ->.
  • Sintaxe $.dimensoes.altura: No MySQL, o caractere $ representa o nó raiz do documento JSON, seguido por pontos para navegar pelos campos e subcampos.
  • Operador ->> 0: Acessa o índice 0 do array retornado pela chave entradas, convertendo o elemento final em string limpa (sem as aspas do JSON original).

🎯 ATIVIDADE 15 — O MOTOR SUPER-RÁPIDO

📖 Fundamentação Teórica

Para realizar este laboratório com sucesso, certifique-se de ter compreendido os conceitos apresentados no:
👉 CAPÍTULO 15: LÓGICA NULL, SUBQUERIES E JOINS

Bem-vindo à décima quinta semana (4 aulas) do curso de Banco de Dados. Até aqui, gravamos todas as nossas informações em disco rígido (HD/SSD), o que garante durabilidade, mas custa milissegundos preciosos em termos de performance. E se precisarmos armazenar dados temporários de altíssimo acesso (como tokens de login de aplicativos ou localizações GPS em tempo real)? Hoje, entraremos no universo dos bancos de dados em memória (In-Memory Database) conhecendo o Redis, o motor de cache e chave-valor mais rápido do mundo. 🛡️⚡


🎯 Objetivos de Aprendizagem do Laboratório

Ao final deste laboratório prático (estimativa: 4 horas presenciais / autoguiadas), você será capaz de:

  • Compreender a diferença entre persistência em disco e armazenamento em memória RAM.
  • Instalar e executar o Redis localmente utilizando containers Docker.
  • Dominar os comandos fundamentais do Redis (SET, GET, EXPIRE, TTL, DEL).
  • Configurar políticas de expiração de chaves para controle de cache e tokens temporários.

🏢 O Cenário Prático (Seu Desafio)

O aplicativo dos entregadores parceiros da TecProExpress envia o token de autenticação de login a cada requisição. Se o backend da aplicação tiver que fazer uma busca SELECT na tabela relacional de usuários a cada segundo por causa disso, o banco relacional principal entrará em colapso.

Seu desafio como Arquiteto de Infraestrutura de Dados é subir uma instância do Redis via Docker e simular o cacheamento do token do entregador Marcos. O token de sessão deve ser gravado com uma política de expiração (TTL - Time To Live) de 30 segundos, simulando a segurança de logout automático por inatividade do app.


🧠 Fundamentos: A Teoria Traduzida

Por que o Redis é tão rápido?

O Redis armazena 100% dos seus dados diretamente na Memória RAM do computador. Como a velocidade de leitura e escrita da memória RAM é ordens de grandeza maior do que o SSD ou HD, o Redis consegue responder a mais de 1.000.000 de operações por segundo!

flowchart LR
    A[Aplicação Web] -->|1. Busca no Cache| B(("Redis: RAM"))
    B -- Cache Miss: Dado não existe --> C(("PostgreSQL: Disco"))
    C -.->|2. Grava e responde| B
    B -- Cache Hit: Encontrado instantaneamente --> A
    style B fill:#ffebee,stroke:#f44336,stroke-width:2px
    style C fill:#e3f2fd,stroke:#1e88e5

Comandos Essenciais do Redis (CLI):

  • SET chave valor: Grava uma chave de texto e seu respectivo valor associado.
  • GET chave: Busca e retorna o valor associado àquela chave.
  • EXPIRE chave segundos: Define um timer de contagem regressiva para expirar e auto-deletar a chave.
  • TTL chave: Retorna quantos segundos restam antes da chave sumir do mapa.
  • DEL chave: Remove a chave imediatamente antes da sua expiração.

📖 Exemplo Guiado: Subindo o Redis e Salvando Dados

1. Inicializando o Redis via Docker Terminal

No terminal do seu Windows (PowerShell), execute o comando abaixo para criar o container do Redis:

docker run --name redis-tecpro -p 6379:6379 -d redis

(Nota: O Redis roda nativamente na porta 6379).

2. Acessando a CLI do Redis para comandos

docker exec -it redis-tecpro redis-cli

Após rodar este comando, você entrará no console interativo do Redis (127.0.0.1:6379>).

3. Executando os Comandos Básicos de Cache

# Salvando o nome do entregador de plantão
127.0.0.1:6379> SET entregador_atual "Marcos Silva"
OK

# Buscando o dado
127.0.0.1:6379> GET entregador_atual
"Marcos Silva"

🛠️ Prática Obrigatória 1: Cache de Sessão com TTL

Cenário: Simulação de Token de Login na TecProExpress.

  1. Acesse a CLI do seu Redis no terminal.
  2. Crie uma chave chamada sessao:token:marcos contendo o valor "TK_MARCOS_2026_XYZ".
  3. Configure um tempo de vida (TTL) de 45 segundos para esta chave usando o comando EXPIRE.
  4. Execute repetidamente o comando TTL sessao:token:marcos para visualizar a contagem regressiva dos segundos.
  5. Aguarde o cronômetro zerar (retorno -2) e confirme com GET se a chave foi completamente apagada do banco.

💻 Execução de Comandos Redis CLI & Saída no Terminal

Para testar operações em memória com expiração TTL e contadores atômicos:

# 1. Definindo chave com expiração de 45 segundos
127.0.0.1:6379> SET sessao:token:marcos "TK_MARCOS_2026_XYZ" EX 45
OK

# 2. Consultando tempo restante (TTL)
127.0.0.1:6379> TTL sessao:token:marcos
(integer) 42

# 3. Operação atômica de estoque
127.0.0.1:6379> SET produto:id:101:estoque 50
OK
127.0.0.1:6379> INCRBY produto:id:101:estoque 5
(integer) 55
127.0.0.1:6379> DECR produto:id:101:estoque
(integer) 54

🖥️ Saída Esperada no Terminal ao Expirar:

127.0.0.1:6379> TTL sessao:token:marcos
(integer) -2
127.0.0.1:6379> GET sessao:token:marcos
(nil)
⚡ [REDIS CACHE] Chave expirada e purgada da memória RAM com sucesso!

🌐 Requisição cURL para Cache de Sessão (Swagger /docs)

curl -X POST "http://127.0.0.1:8000/api/v1/cache/sessao" \
     -H "Content-Type: application/json" \
     -d '{
       "chave": "sessao:token:marcos",
       "valor": "TK_MARCOS_2026_XYZ",
       "ttl_segundos": 45
     }'

🔹 Resposta JSON:

{
  "status": "CACHE_GRAVADO",
  "chave": "sessao:token:marcos",
  "tempo_expiracao_segundos": 45
}

🛠️ Prática Obrigatória 2: Otimizando Acessos de Inventário

Cenário: Cache de contagem rápida de estoque.

  1. No Redis, salve o estoque inicial de um produto: SET produto:id:101:estoque 50.
  2. Use o comando especial de incremento INCR para adicionar 5 itens ao estoque de forma ultra-rápida:
    INCRBY produto:id:101:estoque 5
    
  3. Use o comando de decremento DECR para simular uma venda de 1 unidade:
    DECR produto:id:101:estoque
    

📤 Instruções de Entrega (Microsoft Teams)

Após validar seus testes interativos com o Redis CLI:

  1. Salve um arquivo de texto com a extensão .txt ou .sh (Ex: Atividade_15_SeuNome.txt) contendo a sequência exata de comandos que você digitou na CLI para a Prática 1 e 2.
  2. Anexe uma captura de tela mostrando a contagem regressiva do comando TTL no seu terminal.
  3. Envie o arquivo de texto e o print na tarefa correspondente no Teams.

💡 Checkpoint de Lógica

Importante

Consistência de Cache: Se os dados do Redis são armazenados na memória RAM, o que acontece se o servidor cair ou o computador for reiniciado? (Resposta: A memória RAM é volátil, o que significa que todos os dados salvos sem backup serão apagados. O Redis possui mecanismos opcionais de persistência em disco (como RDB e AOF), mas a sua prioridade industrial sempre será a performance de cache de dados descartáveis/temporários). 🧠🛡️

---

📊 Rubrica Formativa de Avaliação

Critério de Avaliação Insuficiente (0% - 40%) Regular (41% - 70%) Excelente (71% - 100%)
Gestão de Memória & Expiração (TTL) Erros nos comandos da CLI Redis ou omissão do tempo de vida (`EXPIRE`). Aplica o `EXPIRE` mas sem acompanhar a contagem regressiva via `TTL`. Gerenciamento de cache em memória impecável com tokens expiráveis (`EXPIRE`/`TTL`) e liberação automática de RAM.
Operações Atômicas (INCR / DECR) Não utiliza comandos atômicos de contagem. Executa `INCRBY` mas sem entender a atomicidade em ambientes concorrentes. Contagem atômica de inventário de alta velocidade usando `INCRBY` e `DECR` sem risco de colisão de dados.
Entrega & Captura de Terminal Entrega arquivo sem a sequência de comandos da CLI. Entrega a lista de comandos sem a captura de tela do terminal. Submete script de comandos `.txt`/`.sh` acompanhado de print comprovando a expiração do TTL.

🔥 Desafio de Fixação (Opcional)

Nível: Arquiteto Sênior 🏆

Pesquise sobre as estruturas de dados avançadas do Redis. Como você usaria a estrutura HSET (Hash Set) para salvar um perfil completo do entregador contendo nome, veículo e saldo em uma única chave complexa?


🔑 Gabarito de Código/Fórmulas Completo

CLI do Redis (Gabarito Oficial)

-- Prática 1: Fluxo de Token com Expiração
127.0.0.1:6379> SET sessao:token:marcos "TK_MARCOS_2026_XYZ"
OK
127.0.0.1:6379> EXPIRE sessao:token:marcos 45
(integer) 1

-- Verificando tempo restante
127.0.0.1:6379> TTL sessao:token:marcos
(integer) 39

-- Repetindo após 45 segundos
127.0.0.1:6379> TTL sessao:token:marcos
(integer) -2  # Significa que a chave já expirou e foi excluída!

127.0.0.1:6379> GET sessao:token:marcos
(nil)         # Nil indica que a chave é nula (não existe)
-- Prática 2: Contagem Atômica e Incrementos
127.0.0.1:6379> SET produto:id:101:estoque 50
OK
127.0.0.1:6379> INCRBY produto:id:101:estoque 5
(integer) 55
127.0.0.1:6379> DECR produto:id:101:estoque
(integer) 54

🔍 Explicação do Gabarito:

  • (integer) 1: Indica que o comando EXPIRE foi bem-sucedido. Retornará 0 se a chave informada não existir.
  • (nil): É a representação do valor nulo (null) no ecossistema Redis.
  • INCRBY / DECR: São operações atômicas no Redis. Isso garante que mesmo se 10.000 usuários tentarem comprar ao mesmo tempo, o estoque será decrementado um por um corretamente, sem perigo de colisão de dados de CPU.

🎯 ATIVIDADE 16 — CONECTANDO OS PONTOS

📖 Fundamentação Teórica

Para realizar este laboratório com sucesso, certifique-se de ter compreendido os conceitos apresentados no:
👉 CAPÍTULO 16: VIEWS, ÍNDICES E OTIMIZAÇÃO

Bem-vindo à décima sexta semana (4 aulas) do curso de Banco de Dados. Em modelos de dados relacionais clássicos, modelar redes altamente conectadas (como redes sociais com milhões de amigos, sistemas de recomendação baseados em curtidas comuns ou rotas de frete interconectadas) é um pesadelo técnico de performance, pois exige dezenas de JOINS lentos. Hoje, conheceremos o Neo4j, a ferramenta mais poderosa para bancos de dados de Grafos (Graph Database), e aprenderemos a linguagem Cypher para navegar por relações complexas instantaneamente. 🛡️🕸️


🎯 Objetivos de Aprendizagem do Laboratório

Ao final deste laboratório prático (estimativa: 4 horas presenciais / autoguiadas), você será capaz de:

  • Compreender a diferença prática entre Tabelas (SQL) e Relações de Grafos (Nós e Arestas).
  • Instalar e executar o Neo4j localmente através do Docker Desktop ou Neo4j Desktop.
  • Dominar comandos de modelagem física e busca em linguagem Cypher.
  • Criar Nós (Nodes), Propriedades (Properties) e Relacionamentos (Relationships) no Grafo.
  • Consultar rotas e encontrar o caminho mais curto usando funções de busca.

🏢 O Cenário Prático (Seu Desafio)

O departamento de roteamento de Last Mile da TecProExpress quer otimizar a distribuição de entregas no Sudeste do Brasil. Eles precisam de um mapa interativo onde:

  1. Cidades são pontos de conexão (Nós).
  2. Rodovias que conectam essas cidades são as conexões (Relacionamentos).
  3. Cada relacionamento tem uma propriedade física de Custo/Distância em quilômetros.

Seu desafio é criar essa malha viária em formato de Grafo no Neo4j e executar uma busca em Cypher para encontrar a melhor rota e calcular a distância total de transporte entre São Paulo e Belo Horizonte.


🧠 Fundamentos: A Teoria Traduzida

A Anatomia dos Grafos

Diferente de tabelas que possuem linhas de grade, os bancos de grafos utilizam a matemática de Grafos para armazenar e organizar informações:

graph LR
    SP(("Cidade: São Paulo")) -- "RODOVIA {distancia: 430}" --> RJ(("Cidade: Rio de Janeiro"))
    SP -- "RODOVIA {distancia: 580}" --> BH(("Cidade: Belo Horizonte"))
    RJ -- "RODOVIA {distancia: 440}" --> BH
    style SP fill:#bbdefb,stroke:#1e88e5
    style RJ fill:#bbdefb,stroke:#1e88e5
    style BH fill:#bbdefb,stroke:#1e88e5
  • Nó (Node): A entidade do mundo real (ex: uma Cidade, uma Pessoa ou um Pedido).
  • Relacionamento (Relationship / Edge): A conexão física direcionada entre dois nós. Possui obrigatoriamente um Tipo de Relação (ex: -[:RODOVIA]->).
  • Propriedade (Property): Pares de chave-valor salvos nos nós ou nos relacionamentos (ex: nome: "São Paulo", distancia: 430).

Linguagem Cypher (Analogia de Busca):

Cypher é uma linguagem baseada em artes ASCII desenhadas em formato de texto para facilitar o reconhecimento visual dos relacionamentos pelo programador:

  • () representa um (símbolo de círculo).
  • [] representa um Relacionamento (símbolo de caixa).
  • --> representa o direcionamento da conexão (uma seta física).

📖 Exemplo Guiado: Criando o Primeiro Grafo

1. Inicializando o Neo4j via Docker Terminal

Execute o comando abaixo no terminal do PowerShell/Bash:

docker run --name neo4j-tecpro -p 7474:7474 -p 7687:7687 -d -e NEO4J_AUTH=neo4j/senha_forte_123 neo4j

(Nota: A porta 7474 serve para o console de interface web do Neo4j, e a porta 7687 serve para tráfego binário de dados via Bolt).

2. Acessando a Interface de Consulta (Neo4j Browser)

Abra seu navegador e digite o endereço: http://localhost:7474. Faça login com o usuário neo4j e a senha definida senha_forte_123.

3. Criando as Cidades no Grafo (Comando Cypher)

// Criando três cidades no Neo4j
CREATE (sp:Cidade {nome: "São Paulo", estado: "SP"})
CREATE (rj:Cidade {nome: "Rio de Janeiro", estado: "RJ"})
CREATE (bh:Cidade {nome: "Belo Horizonte", estado: "MG"})

4. Estabelecendo Conexões (Relacionamentos)

// Buscando as cidades criadas e criando as rodovias
MATCH (sp:Cidade {nome: "São Paulo"}), (rj:Cidade {nome: "Rio de Janeiro"})
CREATE (sp)-[:RODOVIA {distancia: 430}]->(rj)

🛠️ Prática Obrigatória 1: Criando a Malha Sudeste

  1. Acesse o painel do seu Neo4j Browser.
  2. Crie as cidades de São Paulo, Rio de Janeiro e Belo Horizonte.
  3. Crie os seguintes relacionamentos de estradas no seu painel:
    • São Paulo para Rio de Janeiro com distância de 430 km.
    • Rio de Janeiro para Belo Horizonte com distância de 440 km.
    • São Paulo para Belo Horizonte com distância de 580 km (Rodovia direta Fernão Dias).
  4. Escreva a consulta em Cypher para recuperar todos os nós da sua tela.

💻 Execução de Consultas Cypher no Terminal (cypher-shell)

Para testar consultas de grafos logísticos via terminal cypher-shell:

// Buscando conexões rodoviárias a partir de São Paulo
MATCH (origem:Cidade {nome: "São Paulo"})-[r:RODOVIA]->(destino:Cidade)
RETURN origem.nome AS de, destino.nome AS para, r.distancia AS km;

🖥️ Saída Esperada no Terminal do cypher-shell:

+------------------------------------------+
| de          | para             | km      |
+------------------------------------------+
| "São Paulo" | "Rio de Janeiro" | 430     |
| "São Paulo" | "Belo Horizonte" | 580     |
+------------------------------------------+
2 rows available.
🌐 [NEO4J] Relações indexadas de ponteiro direto navegadas em 0.8ms.

🌐 Requisição cURL para Rota Mais Curta em Grafo (Swagger /docs)

curl -X POST "http://127.0.0.1:8000/api/v1/rotas/grafo" \
     -H "Content-Type: application/json" \
     -d '{
       "origem": "São Paulo",
       "destino": "Rio de Janeiro",
       "criterio": "MENOR_DISTANCIA"
     }'

🔹 Resposta JSON:

{
  "origem": "São Paulo",
  "destino": "Rio de Janeiro",
  "distancia_total_km": 430,
  "caminho_nos": ["São Paulo", "Rio de Janeiro"]
}

🛠️ Prática Obrigatória 2: Buscando Caminhos no Grafo

  1. Escreva uma consulta em Cypher que localize todas as cidades conectadas diretamente a São Paulo.
  2. Escreva uma consulta em Cypher que localize as rodovias cuja distância seja menor do que 500 km.

📤 Instruções de Entrega (Microsoft Teams)

Após validar suas consultas de Grafos no Neo4j Browser:

  1. Salve todas as queries de criação e busca Cypher em um arquivo com a extensão .cypher ou .txt (Ex: Atividade_16_SeuNome.cypher).
  2. Anexe uma captura de tela mostrando a visualização gráfica (o desenho de bolas e setas interconectadas) gerada na tela do Neo4j Browser.
  3. Envie o arquivo de texto e o print na tarefa correspondente no Teams.

💡 Checkpoint de Lógica

Importante

Índices de Relacionamentos: Em bancos SQL, buscar caminhos indiretos (ex: Amigo do Amigo do Amigo) exige processar cruzamentos de tabelas inteiras na memória temporária. No Neo4j, as relações são salvas fisicamente com ponteiros diretos de memória nos nós. Isso faz com que navegar por relações complexas demore frações de milissegundos, independente do tamanho total do banco de dados! 🧠🛡️

---

📊 Rubrica Formativa de Avaliação

Critério de Avaliação Insuficiente (0% - 40%) Regular (41% - 70%) Excelente (71% - 100%)
Modelagem em Grafo (Nós & Relacionamentos Cypher) Erros na sintaxe Cypher (`CREATE`/`MATCH`) ou sem propriedades nas arestas. Cria os nós de cidades mas sem conectar com a rodovia e distância. Malha viária em grafo impecável conectando nós de cidades através de relacionamentos orientados (`:RODOVIA`) com propriedades de distância.
Consultas de Caminhos & Desempenho (MATCH) Não consegue formular buscas `MATCH` com filtros de distância. Executa a busca mas sem filtrar o limite de 500 km. Queries Cypher avançadas retornando caminhos diretos, filtros de distância e navegabilidade por ponteiros de memória.
Entrega & Visualização Gráfica Entrega arquivo sem a sintaxe Cypher. Entrega os comandos Cypher mas sem a imagem do grafo no Neo4j Browser. Submete `.cypher`/`.txt` acompanhado de print mostrando a teia visual interconectada no Neo4j Browser.

🔥 Desafio de Fixação (Opcional)

Nível: Cientista de Dados de Redes 🏆

Pesquise sobre funções matemáticas em Cypher. Como você utilizaria o operador MATCH p = shortestPath(...) para fazer o Neo4j calcular automaticamente qual é o caminho mais curto físico entre São Paulo e Belo Horizonte na malha viária?


🔑 Gabarito de Código/Fórmulas Completo

Painel Cypher (Gabarito Oficial)

// Prática 1: Criação da Malha Viária
// Passo 1 — cria as três cidades
CREATE (sp:Cidade {nome: "São Paulo", estado: "SP"}),
       (rj:Cidade {nome: "Rio de Janeiro", estado: "RJ"}),
       (bh:Cidade {nome: "Belo Horizonte", estado: "MG"});

// Passo 2 — São Paulo -> Rio de Janeiro (statement isolado, variáveis próprias)
MATCH (a1:Cidade {nome: "São Paulo"}), (b1:Cidade {nome: "Rio de Janeiro"})
CREATE (a1)-[:RODOVIA {distancia: 430}]->(b1);

// Passo 3 — Rio de Janeiro -> Belo Horizonte (statement isolado, variáveis próprias)
MATCH (b2:Cidade {nome: "Rio de Janeiro"}), (c2:Cidade {nome: "Belo Horizonte"})
CREATE (b2)-[:RODOVIA {distancia: 440}]->(c2);

// Passo 4 — São Paulo -> Belo Horizonte (statement isolado, variáveis próprias)
MATCH (a3:Cidade {nome: "São Paulo"}), (c3:Cidade {nome: "Belo Horizonte"})
CREATE (a3)-[:RODOVIA {distancia: 580}]->(c3);
// Prática 2: Consultas
// 1. Cidades conectadas diretamente a São Paulo
MATCH (sp:Cidade {nome: "São Paulo"})-[r:RODOVIA]->(destino)
RETURN destino.nome, r.distancia;

// 2. Rodovias com distância menor que 500km
MATCH (origem)-[r:RODOVIA]->(destino)
WHERE r.distancia < 500
RETURN origem.nome, destino.nome, r.distancia;

🔍 Explicação do Gabarito:

  • MATCH: Cláusula que localiza padrões geométricos descritos em formato ASCII.
  • -[r:RODOVIA]->: Identifica e atribui a variável r ao relacionamento para podermos filtrar ou exibir as propriedades dele.
  • shortestPath: O algoritmo interno do Neo4j varre os ponteiros de memória em largura (Breadth-First Search) para achar o menor número de saltos físicos instantaneamente.

🎯 ATIVIDADE 17 — O CÉREBRO DA IA

📖 Fundamentação Teórica

Para realizar este laboratório com sucesso, certifique-se de ter compreendido os conceitos apresentados no:
👉 CAPÍTULO 17: SEGURANÇA DCL, EVOLUÇÃO DE SCHEMA E REVISÃO

Bem-vindo à décima sétima semana (4 aulas) do curso de Banco de Dados. Com a ascensão da Inteligência Artificial Generativa (LLMs como ChatGPT, Claude e Gemini), a forma como pesquisamos dados mudou de rumo. Buscar por palavras-chave exatas (como SELECT * WHERE nome LIKE '%sapato%') não é suficiente para sistemas inteligentes. Hoje, entraremos no topo da inovação tecnológica conhecendo os Bancos de Dados Vetoriais (Vector Database) e aprenderemos a utilizar a extensão pgvector do PostgreSQL para realizar pesquisas semânticas baseadas em inteligência artificial. 🛡️🧠


🎯 Objetivos de Aprendizagem do Laboratório

Ao final deste laboratório prático (estimativa: 4 horas presenciais / autoguiadas), você será capaz de:

  • Compreender o conceito de Embeddings Vetoriais (conversão de significados textuais em vetores numéricos de alta dimensão).
  • Instalar e configurar a extensão pgvector em um banco PostgreSQL local.
  • Criar tabelas relacionais contendo dados do tipo VECTOR (vetoriais).
  • Executar consultas de busca semântica por similaridade de cosseno utilizando operadores vetoriais (<=>).

🏢 O Cenário Prático (Seu Desafio)

O time de marketing da TecProExpress quer um recomendador de produtos inteligente. Se o cliente pesquisar no app por "equipamento à prova d'água para transportar cargas sob chuva", o sistema deve retornar o produto "Bolsa de Lona Selada" ou "Baú Impermeável", mesmo se a palavra "chuva" ou "água" não estiver escrita na descrição física do produto!

Seu desafio como Arquiteto de IA e Dados é habilitar a extensão pgvector no seu PostgreSQL, criar a tabela de produtos com uma coluna de embeddings de 3 dimensões (simplificada para fins didáticos) e escrever consultas de busca semântica que encontrem os produtos com significados mais próximos ao termo de busca do usuário.


🧠 Fundamentos: A Teoria Traduzida

O que são Embeddings Vetoriais?

Imagine que cada palavra ou frase do mundo real possui um significado que pode ser representado por coordenadas em um mapa espacial de muitas dimensões. A inteligência artificial lê os textos e os converte em um array de números decimais (vetor).

  • Textos com significados parecidos (ex: "Entregador" e "Motociclista") ficam posicionados muito próximos no mapa tridimensional.
  • Textos com significados distantes (ex: "Caminhão" e "Banana") ficam muito distantes.
flowchart TD
    subgraph EspacoVetorial ["Espaço Vetorial 3D de Significados"]
        P1["Bolsa Impermeável (0.98, 0.12, 0.05)"] --- P2["Mochila Selada (0.95, 0.15, 0.08)"]
        P1 -.->|Distância de Cosseno Curta| P2
        P3["Teclado Mecânico (0.02, 0.88, 0.95)"] 
        P2 -.->|Distância Longa| P3
    end

Operadores de Distância no pgvector:

  • <->: Distância L2 (Euclidiana) - mede a distância física reta entre pontos.
  • <#>: Distância de Produto Interno (Negative Inner Product).
  • <=>: Distância de Cosseno (Cosine Distance) - mede a diferença de ângulo entre os vetores de significados. É a mais usada na indústria para buscas de IA e semântica.

📖 Exemplo Guiado: Preparando o pgvector

1. Habilitando a Extensão no PostgreSQL

-- Primeiro, criamos um banco dedicado
CREATE DATABASE tecpro_ia;
-- (Nota: Abra uma Query Tool conectada ao banco tecpro_ia)

-- Habilitando a biblioteca pgvector
CREATE EXTENSION IF NOT EXISTS vector;

(Nota: Se você estiver usando o pgAdmin local, certifique-se de que a extensão pgvector está instalada no seu servidor Postgres. Ela vem habilitada por padrão na imagem Docker pgvector/pgvector no Docker Hub).

2. Criando Tabela com Coluna Vetorial

CREATE TABLE produto_ia (
    id SERIAL PRIMARY KEY,
    nome VARCHAR(100),
    descricao TEXT,
    vetor_significado VECTOR(3) -- Vetor tridimensional de embeddings
);

3. Populando o Banco com Embeddings Simulados

INSERT INTO produto_ia (nome, descricao, vetor_significado) VALUES 
('Baú Impermeável', 'Proteção total contra água e chuvas intensas', '[0.95, 0.12, 0.05]'),
('Mochila Estanque', 'Bolsa selada hermeticamente para transporte úmido', '[0.91, 0.14, 0.07]'),
('Teclado Mecânico RGB', 'Dispositivo de entrada com switches mecânicos retroiluminados', '[0.02, 0.85, 0.98]');


💻 Execução de Busca Semântica no Terminal (pgvector)

Para testar a recuperação de itens por proximidade de significado vetorial:

-- Busca semântica por similaridade de cosseno (<=>)
SELECT nome, descricao, (vetor_significado <=> '[0.89, 0.15, 0.08]') AS distancia_cosseno
FROM produto_ia
ORDER BY vetor_significado <=> '[0.89, 0.15, 0.08]'
LIMIT 2;

🖥️ Saída Esperada no Terminal do PostgreSQL:

       nome       |                    descricao                     | distancia_cosseno 
------------------+--------------------------------------------------+-------------------
 Mochila Estanque | Bolsa selada hermeticamente para transporte úmido| 0.000412850
 Baú Impermeável  | Proteção total contra água e chuvas intensas    | 0.001954120
(2 rows)
🤖 [PGVECTOR] Termos semânticos recuperados com sucesso sem correspondência textual exata!

🌐 Requisição cURL para Busca Semântica com IA / RAG (Swagger /docs)

curl -X POST "http://127.0.0.1:8000/api/v1/busca/semantica" \
     -H "Content-Type: application/json" \
     -d '{
       "prompt_busca": "mochila para chuva",
       "vetor_embedding": [0.89, 0.15, 0.08],
       "top_k": 2
     }'

🔹 Resposta JSON:

[
  {
    "nome": "Mochila Estanque",
    "score_similaridade": 0.9995,
    "distancia": 0.0004
  },
  {
    "nome": "Baú Impermeável",
    "score_similaridade": 0.9980,
    "distancia": 0.0019
  }
]

🛠️ Prática Obrigatória 1: Sua Própria Busca Semântica

Cenário: A TecProExpress quer testar o pgvector com um novo lote de produtos.

  1. Insira 2 novos produtos em produto_ia, cada um com seu próprio vetor_significado de 3 dimensões (invente valores entre 0 e 1, coerentes com a "proximidade de significado" que você quiser simular).
  2. Escreva uma consulta usando o operador <=> (similaridade de cosseno) para encontrar os 2 produtos mais próximos de um vetor de busca à sua escolha, ordenando do mais similar ao menos similar.
  3. Explique, em uma frase, por que um valor de distância próximo de 0 indica alta similaridade semântica.

🛠️ Prática Obrigatória 2: Análise de Custo Vetorial

  1. Escreva a mesma consulta utilizando a Distância Euclidiana (<->).
  2. Pesquise na internet qual a finalidade de usar um Índice HNSW (Hierarchical Navigable Small World) ou IVFFlat sobre colunas do tipo VECTOR em tabelas com milhões de registros vetoriais.

📤 Instruções de Entrega (Microsoft Teams)

Após validar suas buscas vetoriais com o pgvector:

  1. Salve o script SQL completo contendo a inicialização do pgvector, as inserções de embeddings e as buscas semânticas ordenadas por distância em um arquivo .sql (Ex: Atividade_17_SeuNome.sql).
  2. Adicione comentários no código explicando o que são Embeddings Vetoriais de forma simples e didática.
  3. Envie o arquivo .sql correspondente no Microsoft Teams para avaliação.

💡 Checkpoint de Lógica

Importante

A Importância da Dimensão: Nos nossos exercícios, usamos VECTOR(3) (três dimensões) para facilitar a visualização didática. Na indústria real, modelos de IA profissionais (como o Ada-002 da OpenAI ou o Gecko do Google Vertex AI) geram embeddings com 1536 ou mais dimensões de profundidade para capturar as nuances complexas da linguagem humana! 🧠🛡️

---

📊 Rubrica Formativa de Avaliação

Critério de Avaliação Insuficiente (0% - 40%) Regular (41% - 70%) Excelente (71% - 100%)
Busca Semântica por Cosseno (pgvector) Erros na sintaxe da extensão `vector` ou no operador de similaridade de cosseno (`<=>`). Cria a coluna `VECTOR(3)` mas sem utilizar o operador de distância na cláusula `ORDER BY`. Busca semântica por similaridade de cosseno (`<=>`) impecável ordenando os produtos mais próximos de IA com `LIMIT`.
Distância Euclidiana & Índices Vetoriais (HNSW / IVFFlat) Omite a explicação sobre índices vetoriais de aproximação ANN. Compara com a distância euclidiana (`<->`) mas sem entender a finalidade dos índices HNSW/IVFFlat. Explicação rica sobre o ganho de performance de índices HNSW e IVFFlat em buscas vetoriais sobre milhões de embeddings.
Entrega do Script SQL Entrega script com erros no PostgreSQL. Script entregue sem comentários explicativos. Submete `Atividade_17_SeuNome.sql` perfeitamente formatado e testado com a extensão pgvector.

🔥 Desafio de Fixação (Opcional)

Nível: Arquiteto de IA Generativa 🏆

Escreva uma query SQL que retorne a distância numérica exata de similaridade de cosseno entre a busca do usuário [0.89, 0.15, 0.08] e os produtos da tabela, renomeando a coluna de cálculo como distancia_calculada.


🔑 Gabarito de Código/Fórmulas Completo

🐘 Padrão PostgreSQL (pgvector)

-- Habilita a extensão de IA
CREATE EXTENSION IF NOT EXISTS vector;

-- Criação da tabela
CREATE TABLE produto_ia (
    id SERIAL PRIMARY KEY,
    nome VARCHAR(100),
    descricao TEXT,
    vetor_significado VECTOR(3)
);

-- Carga de Testes
INSERT INTO produto_ia (nome, descricao, vetor_significado) VALUES 
('Baú Impermeável', 'Proteção total contra água e chuvas intensas', '[0.95, 0.12, 0.05]'),
('Mochila Estanque', 'Bolsa selada hermeticamente para transporte úmido', '[0.91, 0.14, 0.07]'),
('Teclado Mecânico RGB', 'Dispositivo de entrada com switches mecânicos retroiluminados', '[0.02, 0.85, 0.98]');

-- Prática 1: Busca Semântica com Similaridade de Cosseno (<=>)
SELECT nome, descricao
FROM produto_ia
ORDER BY vetor_significado <=> '[0.89, 0.15, 0.08]'
LIMIT 2;

-- Resposta ao Desafio (Cálculo de Distância Exata de Cosseno)
SELECT nome, vetor_significado <=> '[0.89, 0.15, 0.08]' AS distancia_calculada
FROM produto_ia
ORDER BY distancia_calculada ASC;

🔍 Explicação do Gabarito:

  • vetor_significado <=> [...]: O operador <=> realiza a matemática de similaridade de cosseno (mede o cosseno do ângulo entre os dois vetores). Valores próximos a 0 indicam vetores quase idênticos de significado. Valores próximos a 1 indicam alta divergência.
  • ORDER BY ... ASC: Ordena do menor valor de distância para o maior, ou seja, os itens mais similares no topo da lista.
  • Índices IVFFlat e HNSW: Permitem realizar buscas rápidas de aproximação por vizinhos (Approximate Nearest Neighbors - ANN) em frações de segundos sobre milhões de vetores sem precisar realizar cálculos matemáticos individuais com toda a base de dados (Seq Scan Vetorial).

🎯 ATIVIDADE 18 — A VISÃO DO BOARD

Bem-vindo à décima oitava semana (4 aulas) do curso de Banco de Dados. Até aqui, projetamos apenas bancos de dados OLTP (Online Transaction Processing), que são ideais para o dia a dia operacional rápido (cadastrar cliente, fazer pedido, debitar saldo). No entanto, quando os executivos precisam tomar decisões macro (ex: "Qual o crescimento anual de vendas por estado nos últimos 5 anos?"), consultas analíticas em sistemas OLTP travam as transações diárias. Hoje, aprenderemos a projetar bancos de dados OLAP (Online Analytical Processing) utilizando a Modelagem Dimensional (Star Schema). 🛡️📈


🎯 Objetivos de Aprendizagem do Laboratório

Ao final deste laboratório prático (estimativa: 4 horas presenciais / autoguiadas), você será capaz de:

  • Compreender a diferença arquitetural e de performance entre sistemas OLTP e OLAP.
  • Identificar e desenhar componentes de modelagem dimensional: Tabela Fato e Tabelas Dimensão.
  • Projetar esquemas de modelagem dimensional no formato Star Schema (Esquema Estrela).
  • Executar consultas analíticas rápidas de BI cruzando fatos e dimensões.

🏢 O Cenário Prático (Seu Desafio)

O Diretor de BI da TecProExpress quer um relatório interativo (painel de dados) para acompanhar a performance histórica de vendas. Ele quer analisar o faturamento por:

  1. Tempo: Ano, Trimestre, Mês, Dia.
  2. Cliente: Nome, Gênero, Cidade, Estado.
  3. Produto: Nome, Categoria, SKU.

Como Engenheiro de Analytics (Analytics Engineer), seu desafio é modelar e implementar uma estrutura analítica de Star Schema contendo a tabela central de fatos (fato_vendas) conectada a três tabelas de dimensões (dim_tempo, dim_cliente, dim_produto). Você preencherá a estrutura e extrairá as métricas consolidadas solicitadas pelo board.


🧠 Fundamentos: A Teoria Traduzida

OLTP vs. OLAP

RecursoBanco de Dados OLTP (Operacional)Banco de Dados OLAP (Analítico / DW)
ObjetivoTransações rápidas e integridadeConsultas complexas de relatórios
DesignAltamente normalizado (3FN) para evitar duplicadosDesnormalizado (Dimensional) para velocidade de leitura
OperaçãoMuitos INSERT, UPDATE, DELETE rápidosPoucos SELECT gigantescos rodando em lote (Batch)

Anatomia do Star Schema (Esquema Estrela)

Na modelagem dimensional, organizamos os dados de forma que a tabela de métricas fique no centro, cercada pelas dimensões:

flowchart TD
    DimCliente["dim_cliente - Quem?"] --- FatoVendas["fato_vendas - Métricas/Valores"]
    DimProduto["dim_produto - O que?"] --- FatoVendas
    DimTempo["dim_tempo - Quando?"] --- FatoVendas
    style FatoVendas fill:#ffe0b2,stroke:#fb8c00,stroke-width:2px
    style DimCliente fill:#e3f2fd,stroke:#1e88e5
    style DimProduto fill:#e3f2fd,stroke:#1e88e5
    style DimTempo fill:#e3f2fd,stroke:#1e88e5

Data Warehouse, ETL e OLTP vs OLAP

  • Tabela Fato: Contém as métricas quantitativas (ex: valor total, quantidade vendida) e as chaves estrangeiras que apontam para as dimensões. Registra acontecimentos históricos (fatos).
  • Tabela Dimensão: Contém os atributos descritivos qualificativos que servem de filtro para a métrica (ex: nome do cliente, ano da venda, cor do produto).

📖 Exemplo Guiado: Criando o Star Schema

1. Inicializando o Data Warehouse (Lógico)

CREATE DATABASE tecpro_dw;
-- (Nota: No pgAdmin, abra a Query Tool apontando para o banco tecpro_dw)

2. Criando as Tabelas Dimensão (Desnormalizadas)

CREATE TABLE dim_cliente (
    sk_cliente SERIAL PRIMARY KEY, -- SK = Surrogate Key (Chave Analítica)
    id_operacional INT,
    nome VARCHAR(100),
    cidade VARCHAR(100),
    estado CHAR(2)
);

CREATE TABLE dim_produto (
    sk_produto SERIAL PRIMARY KEY,
    id_operacional INT,
    nome VARCHAR(100),
    categoria VARCHAR(50)
);

3. Criando a Tabela Fato Central

CREATE TABLE fato_vendas (
    sk_cliente INT REFERENCES dim_cliente(sk_cliente),
    sk_produto INT REFERENCES dim_produto(sk_produto),
    quantidade INT,
    valor_total DECIMAL(12,2),
    PRIMARY KEY (sk_cliente, sk_produto)
);

🛠️ Prática Obrigatória 1: Criando a Dimensão Tempo

Na modelagem de Data Warehouse, a dimensão Tempo é essencial para evitar o uso lento de funções de manipulação de data (como EXTRACT) em tempo de consulta.

  1. Crie a tabela dim_tempo contendo as colunas: sk_tempo (PK), data_completa (DATE), ano (INT), trimestre (INT), mes (INT) e dia (INT).
  2. Adicione a chave estrangeira sk_tempo à tabela fato_vendas como parte da chave composta.

💻 Execução de Consulta Analítica OLAP & Saída no Terminal

Para verificar o faturamento consolidado por estado e ano no Star Schema:

-- Relatório OLAP: Faturamento Total por Estado no ano de 2026
SELECT c.estado, t.ano, SUM(f.valor_total) AS receita_bruta, SUM(f.quantidade) AS volume_entregue
FROM fato_vendas f
JOIN dim_cliente c ON f.sk_cliente = c.sk_cliente
JOIN dim_tempo t ON f.sk_tempo = t.sk_tempo
WHERE t.ano = 2026
GROUP BY c.estado, t.ano
ORDER BY receita_bruta DESC;

🖥️ Saída Esperada no Terminal:

 estado | ano  | receita_bruta | volume_entregue 
--------+------+---------------+-----------------
 SP     | 2026 |     145200.00 |             480
 RJ     | 2026 |      89400.00 |             290
 MG     | 2026 |      62100.00 |             210
(3 rows)
📊 [DATA WAREHOUSE] Consulta multidimensional executada em 1.4ms (Star Schema otimizado).

🌐 Requisição cURL para Relatório Gerencial BI (Swagger /docs)

curl -X GET "http://127.0.0.1:8000/api/v1/bi/faturamento-por-estado?ano=2026" \
     -H "Accept: application/json"

🔹 Resposta JSON:

[
  {"estado": "SP", "ano": 2026, "receita_bruta": 145200.0, "volume_entregue": 480},
  {"estado": "RJ", "ano": 2026, "receita_bruta": 89400.0, "volume_entregue": 290},
  {"estado": "MG", "ano": 2026, "receita_bruta": 62100.0, "volume_entregue": 210}
]

🛠️ Prática Obrigatória 2: Consulta Analítica OLAP

  1. Popule as dimensões e a tabela fato com dados simulados de vendas da TecProExpress.
  2. Escreva uma consulta analítica OLAP que retorne o Faturamento Total consolidado por Estado do Cliente no ano de 2026. (Dica: Você precisará cruzar a fato_vendas com dim_cliente e dim_tempo usando joins rápidos).

📤 Instruções de Entrega (Microsoft Teams)

Após validar suas consultas analíticas no Data Warehouse local:

  1. Salve o script SQL completo contendo a criação do Star Schema completo (Fato e 3 Dimensões), os scripts de inserção e a consulta analítica final em um arquivo .sql (Ex: Atividade_18_SeuNome.sql).
  2. Adicione comentários no código explicando o que são Surrogate Keys (SK) analíticas e qual a diferença delas em relação às Chaves Primárias Operacionais (PK).
  3. Envie o arquivo .sql correspondente no Microsoft Teams para avaliação.

💡 Checkpoint de Lógica

Importante

Star Schema vs. Snowflake Schema: No Star Schema, as tabelas dimensão são mantidas desnormalizadas (têm dados repetidos) para que o banco faça apenas 1 join direto com o fato. No Snowflake Schema (Esquema Floco de Neve), as dimensões são normalizadas em tabelas secundárias (ex: dim_cidade ligada a dim_estado). O Snowflake economiza espaço, mas deixa as buscas de BI lentas por causa dos joins em cascata. Na indústria de Big Data, priorizamos a velocidade e preferimos o Star Schema. 🧠🛡️

---

📊 Rubrica Formativa de Avaliação

Critério de Avaliação Insuficiente (0% - 40%) Regular (41% - 70%) Excelente (71% - 100%)
Modelagem Star Schema (Tabela Fato & Dimensões) Modelagem relacional normalizada em vez de modelo dimensional OLAP. Cria o Star Schema mas sem definir a Dimensão Tempo (`dim_tempo`) desnormalizada. Star Schema impecável com Tabela Fato centralizada e 3 Dimensões (`dim_cliente`, `dim_produto`, `dim_tempo`) com Surrogate Keys (SK).
Consultas Analíticas OLAP (Drill-down / Roll-up) Erros nos agrupamentos `GROUP BY` das dimensões ou ausência de soma de faturamento. Realiza a consulta analítica mas sem explicar a diferença entre Drill-down e Roll-up. Queries analíticas OLAP de alta velocidade agregando por estado e ano com justificativa teórica dos conceitos de cubo.
Entrega do Script SQL Entrega script incompleto sem carga de dados simulados (Seeds). Script entregue mas sem comentários teóricos sobre Surrogate Keys. Submete `Atividade_18_SeuNome.sql` contendo física do DW, seeds e consultas analíticas validadas.

🔥 Desafio de Fixação (Opcional)

Nível: Arquiteto de Analytics (CTO) 🏆

Pesquise sobre as operações analíticas clássicas do cubo OLAP: Drill-down, Roll-up, Slice e Dice. Escreva um pequeno parágrafo didático explicando a diferença teórica entre Drill-down e Roll-up no contexto de análise temporal.


🔑 Gabarito de Código/Fórmulas Completo

🐘 Padrão PostgreSQL (pgAdmin)

-- 1. Criação do Esquema Físico Analítico
CREATE TABLE dim_cliente (
    sk_cliente SERIAL PRIMARY KEY,
    id_operacional INT,
    nome VARCHAR(100),
    cidade VARCHAR(100),
    estado CHAR(2)
);

CREATE TABLE dim_produto (
    sk_produto SERIAL PRIMARY KEY,
    id_operacional INT,
    nome VARCHAR(100),
    categoria VARCHAR(50)
);

CREATE TABLE dim_tempo (
    sk_tempo SERIAL PRIMARY KEY,
    data_completa DATE,
    ano INT,
    trimestre INT,
    mes INT,
    dia INT
);

CREATE TABLE fato_vendas (
    sk_cliente INT REFERENCES dim_cliente(sk_cliente),
    sk_produto INT REFERENCES dim_produto(sk_produto),
    sk_tempo INT REFERENCES dim_tempo(sk_tempo),
    quantidade INT,
    valor_total DECIMAL(12,2),
    PRIMARY KEY (sk_cliente, sk_produto, sk_tempo)
);

-- 2. Carga de Dados (Seeds)
INSERT INTO dim_cliente (id_operacional, nome, cidade, estado) VALUES 
(10, 'Marcos Silva', 'São Paulo', 'SP'),
(11, 'Julia Costa', 'Rio de Janeiro', 'RJ');

INSERT INTO dim_produto (id_operacional, nome, categoria) VALUES 
(501, 'Capacete Pro', 'Equipamento'),
(502, 'Carregador Celular', 'Eletrônicos');

INSERT INTO dim_tempo (data_completa, ano, trimestre, mes, dia) VALUES 
('2026-05-19', 2026, 2, 5, 19);

-- Marcos comprou um Capacete Pro no dia 19/05/2026 por R$ 300.00
INSERT INTO fato_vendas VALUES (1, 1, 1, 1, 300.00);

-- 3. Consulta Analítica OLAP
SELECT c.estado, SUM(f.valor_total) AS faturamento_consolidado
FROM fato_vendas f
INNER JOIN dim_cliente c ON f.sk_cliente = c.sk_cliente
INNER JOIN dim_tempo t ON f.sk_tempo = t.sk_tempo
WHERE t.ano = 2026
GROUP BY c.estado;

🔍 Explicação do Gabarito:

  • Surrogate Keys (SK): Chaves primárias sintéticas independentes dos códigos das tabelas operacionais de produção (id_operacional). Isso blinda o Data Warehouse caso o sistema produtivo mude as chaves reais amanhã.
  • SUM(f.valor_total): As tabelas fato centralizam as agregações analíticas numéricas.
  • Drill-down vs Roll-up:
    • Drill-down: Detalha o nível analítico (desce a hierarquia, ex: de Ano para Mês).
    • Roll-up: Agrupa e consolida o nível analítico (sobe a hierarquia, ex: de Mês para Ano).

🎯 ATIVIDADE 19 — EVOLUÇÃO SEM DOR

📖 Fundamentação Teórica

Para realizar este laboratório com sucesso, certifique-se de ter compreendido os conceitos apresentados no:
👉 CAPÍTULO 19: APACHE CASSANDRA — BIG DATA E CQL

Bem-vindo à décima nona semana (4 aulas) do curso de Banco de Dados. Até aqui, trabalhamos com bancos de dados de forma estática: criamos o banco no primeiro dia e rodamos consultas nele. No entanto, no mundo real das startups de crescimento acelerado e grandes corporações, o software muda diariamente. E se precisarmos adicionar uma coluna de pontuação, renomear um campo ou criar uma nova tabela sem desligar o sistema de produção?

Hoje, aprenderemos a realizar a Evolução de Esquemas (Database Schema Migrations), compreendendo como pipelines de CI/CD implantam alterações estruturais de forma segura e sem downtime usando o padrão Expand and Contract (Expandir e Contrair). 🛡️🔄


🎯 Objetivos de Aprendizagem do Laboratório

Ao final deste laboratório prático (estimativa: 4 horas presenciais / autoguiadas), você será capaz de:

  • Compreender o conceito de Versionamento de Banco de Dados (Database Migrations).
  • Escrever scripts estruturados de Up (avançar) e Down (desfazer) para controle de versão de schemas.
  • Diferenciar o suporte à DDL Transacional entre PostgreSQL (suportado com rollback completo) e MySQL (não suportado devido a commits implícitos).
  • Aplicar o padrão de design Expand and Contract para realizar refatorações de chaves e campos sem indisponibilidade (Zero-Downtime).

🏢 O Cenário Prático (Seu Desafio)

A equipe de engenharia da TecProExpress quer lançar um Programa de Fidelidade para recompensar os clientes mais ativos. Para suportar a nova funcionalidade, precisamos adicionar a coluna pontos_fidelidade à tabela cliente.

Como Engenheiro de DevOps e Banco de Dados (Database Reliability Engineer - DBRE), seu desafio é criar os arquivos de migração estruturados para essa alteração estrutural. Além disso, a empresa deseja renomear o campo antigo telefone para celular sem parar a operação móvel de entregadores que dependem desta coluna a cada segundo. Você deve planejar essa evolução com segurança de nível sênior.


🧠 Fundamentos: A Teoria Traduzida

O que são Database Migrations?

Imagine o Git para bancos de dados. Uma Migration é um arquivo de script versionado (geralmente numerado cronologicamente, ex: V1__criar_tabelas.sql, V2__adicionar_fidelidade.sql) que descreve exatamente como transicionar a estrutura do banco do estado A para o estado B.

  • Up Migration: Aplica a mudança física no banco de dados (ex: ALTER TABLE ADD COLUMN).
  • Down Migration: Desfaz a mudança física, restaurando o banco ao estado anterior caso algo dê errado em produção (ex: ALTER TABLE DROP COLUMN).
flowchart LR
    V1["Estado Inicial (V1)"] -- "Up Migration" --> V2["Estado Novo (V2)"]
    V2 -- "Down Migration (Rollback)" --> V1
    style V1 fill:#e3f2fd,stroke:#1e88e5
    style V2 fill:#e8f5e9,stroke:#4caf50

O Perigo Oculto: DDL Transacional (PostgreSQL vs. MySQL)

Este é um dos maiores pontos de falha em equipes de tecnologia júnior:

  • PostgreSQL (SGBD Superior em DDL): Suporta DDL Transacional. Se você colocar comandos ALTER TABLE ou CREATE INDEX dentro de uma transação (BEGIN; ... COMMIT;), e um comando no meio falhar, o Postgres reverte toda a estrutura de volta ao início (ROLLBACK). Nenhuma alteração incompleta ou corrompida sobrevive.
  • MySQL (Limitação Histórica): Não suporta DDL Transacional. Comandos DDL no MySQL acionam o que chamamos de Commit Implícito. Se você tentar colocar três comandos ALTER TABLE em um bloco de transação e o segundo falhar, as alterações do primeiro já terão sido salvas permanentemente em disco! Você terá uma "migration pela metade", o que exige intervenção manual de emergência.

📖 Exemplo Guiado: Usando DDL Transacional no pgAdmin

1. Preparando o Cenário de Teste (PostgreSQL)

CREATE DATABASE tecpro_producao;
-- (Nota: No pgAdmin, abra a Query Tool apontando para tecpro_producao)

CREATE TABLE cliente (
    id SERIAL PRIMARY KEY,
    nome VARCHAR(100) NOT NULL,
    telefone VARCHAR(20)
);

INSERT INTO cliente (nome, telefone) VALUES ('Alice Silva', '11999999999');

2. Rodando uma Migration Transacional Segura (PostgreSQL)

BEGIN;

-- Tentamos alterar a tabela de forma segura
ALTER TABLE cliente ADD COLUMN pontos_fidelidade INT DEFAULT 0;

-- Simulamos um erro intencional no final para testar a segurança
-- (A tabela abaixo 'tabela_inexistente' causará um erro de sintaxe ou execução)
ALTER TABLE tabela_inexistente ADD COLUMN coluna_teste INT;

COMMIT;

No PostgreSQL, ao executar o bloco acima, você verá que o erro na segunda instrução cancelou a transação inteira. Se você fizer um SELECT * FROM cliente;, a coluna pontos_fidelidade não foi criada! O banco manteve-se íntegro.



💻 Execução de Migrations de Banco & Logs no Terminal

Para testar a aplicação de migrations versionadas em esteira de CI/CD (ex: Alembic / Flyway):

# Executando migration de schema transacional
alembic upgrade head

🖥️ Saída Esperada no Terminal de CI/CD:

INFO  [alembic.runtime.migration] Context impl PostgresqlImpl.
INFO  [alembic.runtime.migration] Will assume transactional DDL.
INFO  [alembic.runtime.migration] Running upgrade v1 -> v2, V2__adicionar_fidelidade.up.sql
INFO  [alembic.runtime.migration] Running upgrade v2 -> v3, V3__refatorar_telefone_celular.up.sql
-----------------------------------------------------------------
🚀 [MIGRATIONS] Todas as migrações aplicadas com sucesso (Zero-Downtime)!

📄 Automatizando no GitHub Actions (.github/workflows/migrations.yml)

Para que essas migrations rodem sozinhas a cada push na branch main, sem depender de alguém lembrar de rodar alembic upgrade head manualmente:

name: Aplicar Migrations Alembic

on:
  push:
    branches: [ "main" ]

jobs:
  migrate:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout do Código
        uses: actions/checkout@v4

      - name: Instalar Python 3.11
        uses: actions/setup-python@v5
        with:
          python-version: '3.11'

      - name: Instalar Dependências
        run: pip install -r requirements.txt

      - name: Aplicar Migrations Pendentes (Alembic)
        run: alembic upgrade head
        env:
          DATABASE_URL: ${{ secrets.DATABASE_URL }}

🔍 Detalhamento do Workflow:

  • on: push: branches: [main]: o pipeline dispara automaticamente a cada merge na branch principal.
  • ${{ secrets.DATABASE_URL }}: a URL de conexão do banco de produção nunca fica exposta no código — fica guardada nos Secrets do repositório GitHub.
  • alembic upgrade head: aplica todas as migrations pendentes até a versão mais recente, na ordem correta.

🌐 Exemplo de Payload JSON para Histórico de Schema Migrations (Swagger /docs)

{
  "versao_atual": "v3",
  "data_aplicacao": "2026-03-01T15:30:00Z",
  "migrations_executadas": [
    {"versao": "v1", "descricao": "init_schema", "status": "SUCCESS"},
    {"versao": "v2", "descricao": "adicionar_fidelidade", "status": "SUCCESS"},
    {"versao": "v3", "descricao": "refatorar_telefone_celular", "status": "SUCCESS"}
  ],
  "ddl_transacional": true
}

🛠️ Prática Obrigatória 1: Sua Primeira Migration Versionada

Cenário: A TecProExpress quer lançar um programa de fidelidade e precisa adicionar uma coluna nova sem quebrar o sistema em produção.

  1. Escreva o arquivo V2__adicionar_fidelidade.up.sql: um script transacional (BEGIN / COMMIT) que adiciona a coluna pontos_fidelidade INT NOT NULL DEFAULT 0 na tabela cliente.
  2. Escreva o arquivo V2__adicionar_fidelidade.down.sql: o script inverso, que remove essa coluna (DROP COLUMN), para o caso de a migration precisar ser revertida.
  3. Explique, em uma frase, por que toda migration "up" deveria ter uma migration "down" correspondente.

🛠️ Prática Obrigatória 2: Refatoração Sem Downtime (Expand & Contract)

O Problema Sênior: Renomear colunas em bases de dados ativas na produção (ALTER TABLE RENAME COLUMN telefone TO celular) é altamente perigoso. Se a aplicação antiga tentar escrever em telefone enquanto a migration roda, a escrita falhará, gerando queda no sistema (downtime).

Para resolver isso, aplicamos a estratégia Expand and Contract (Expandir e Contrair):

flowchart TD
    E1["1. EXPANDIR: Cria a nova coluna 'celular' mantendo a antiga 'telefone'"] --> E2["2. TRANSIÇÃO: Sistema grava nas duas colunas simultaneamente (Dual Write)"]
    E2 --> E3["3. CARGA: Roda script DML para migrar dados históricos de telefone para celular"]
    E3 --> E4["4. CONTRAIR: Atualiza a aplicação para ler apenas da 'celular' e dropa 'telefone'"]
    style E1 fill:#e1f5fe
    style E4 fill:#ffebee
  1. Escreva um script SQL que realize o Passo 1 (Expandir), adicionando a coluna celular na tabela cliente.
  2. Escreva um script SQL DML que realize o Passo 3 (Carga), atualizando o campo celular com os valores existentes de telefone em todos os registros do banco.
  3. Escreva o script final de Passo 4 (Contrair) que exclui a coluna telefone com segurança após a homologação das novas APIs.

📤 Instruções de Entrega (Microsoft Teams)

Após validar suas transações de DDL e estratégias de migração localmente:

  1. Salve seus scripts estruturados organizados em uma estrutura de arquivos de versionamento:
    • V2__adicionar_fidelidade.up.sql
    • V2__adicionar_fidelidade.down.sql
    • V3__refatorar_telefone_celular.up.sql (contendo os passos de Expand, Carga e Drop comentados passo a passo).
  2. Adicione ao início de um arquivo de texto explicativo a comparação entre PostgreSQL e MySQL sobre o suporte a DDL Transacional, respondendo por que essa diferença impacta a escolha tecnológica de um time de DevOps.
  3. Envie o conjunto de arquivos compactados ou em seu repositório no Microsoft Teams para avaliação técnica.

💡 Checkpoint de Lógica

Importante

Evitando Locks de Tabelas Grandes: Em bancos de dados de produção com milhões de linhas, adicionar colunas com valores padrão (ex: DEFAULT 'Valor' NOT NULL) ou criar índices normais bloqueia temporariamente a tabela para leitura e escrita, gerando interrupções graves de serviço.

  • No PostgreSQL, usamos CREATE INDEX CONCURRENTLY para compilar o índice em segundo plano sem bloquear transações.
  • No MySQL, indicamos ALGORITHM=INPLACE, LOCK=NONE na DDL para sinalizar ao SGBD a realização de alterações online. 🧠🛡️
---

📊 Rubrica Formativa de Avaliação

Critério de Avaliação Insuficiente (0% - 40%) Regular (41% - 70%) Excelente (71% - 100%)
Versionamento de Banco com Migrations (Up / Down) Scripts de migração avulsos sem separação de arquivos Up e Down. Escreve o script `up.sql` mas omite o arquivo de rollback `down.sql`. Arquivos de migração versionados (`V2__adicionar_fidelidade.up.sql` e `down.sql`) perfeitamente transacionais em PostgreSQL.
Refatoração Zero Downtime (Expand & Contract) Renomeia colunas diretamente via `RENAME COLUMN` causando indisponibilidade na produção. Aplica o padrão Expand & Contract mas omite a etapa de carga de dados históricos (DML). Estratégia Expand & Contract impecável cobrindo criação da nova coluna, migração DML de dados e exclusão segura da coluna antiga.
DDL Transacional & Entrega Entrega scripts com erros de execução. Script entregue sem justificar a diferença do suporte a DDL Transacional entre PostgreSQL e MySQL. Submete conjunto de scripts organizados e validados acompanhados de reflexão sênior sobre bloqueio de tabelas grandes.

🔥 Desafio de Fixação (Opcional)

Nível: Arquiteto de DevOps / SRE Sênior 🏆

Escreva um script de trigger em PL/pgSQL (PostgreSQL) que automatize a sincronização durante a fase de transição (Dual Write) do padrão Expand and Contract: sempre que a coluna antiga telefone for atualizada, a nova coluna celular deve receber o mesmo valor de forma automática via gatilho, blindando sistemas legados que ainda escrevem no campo antigo.


🔑 Gabarito de Código/Fórmulas Completo

🐘 Padrão PostgreSQL (pgAdmin)

Arquivo: V2__adicionar_fidelidade.up.sql

BEGIN;

-- 1. Cria a coluna com valor default seguro
ALTER TABLE cliente ADD COLUMN pontos_fidelidade INT NOT NULL DEFAULT 0;

COMMIT;

Arquivo: V2__adicionar_fidelidade.down.sql

BEGIN;

-- 1. Remove a coluna criada na versão V2
ALTER TABLE cliente DROP COLUMN IF EXISTS pontos_fidelidade;

COMMIT;

Arquivo: V3__refatorar_telefone_celular.up.sql

-- ESTRATÉGIA EXPAND AND CONTRACT (Zero Downtime)

-- [PASSO 1: EXPANDIR]
-- Criamos a nova coluna mantendo a antiga ativa para evitar quebra em produção
ALTER TABLE cliente ADD COLUMN celular VARCHAR(20);

-- [PASSO 3: CARGA DOS HISTÓRICOS]
-- Migramos todos os telefones antigos cadastrados para a nova coluna celular
UPDATE cliente 
SET celular = telefone 
WHERE celular IS NULL AND telefone IS NOT NULL;

-- (Nota: A aplicação agora é atualizada na produção para escrever e ler apenas de 'celular')

-- [PASSO 4: CONTRAIR]
-- Com segurança e após homologação completa, dropamos a coluna depreciada 'telefone'
ALTER TABLE cliente DROP COLUMN telefone;

🐬 Comparativo de Sintaxe: Padrão MySQL

-- ATENÇÃO: No MySQL, ALTER TABLE ativa commit implícito.
-- Não coloque DDLs MySQL em blocos de transações (START TRANSACTION/COMMIT).

-- Up Migration no MySQL
ALTER TABLE cliente ADD COLUMN pontos_fidelidade INT NOT NULL DEFAULT 0;

-- Zero Downtime Expand and Contract (MySQL)
ALTER TABLE cliente ADD COLUMN celular VARCHAR(20) AFTER telefone;

UPDATE cliente 
SET celular = telefone 
WHERE celular IS NULL AND telefone IS NOT NULL;

ALTER TABLE cliente DROP COLUMN telefone;

🔍 Explicação do Gabarito:

  • DDL Transacional no PostgreSQL: Se o ALTER TABLE falhar devido a algum bloqueio (lock), o BEGIN garante que a tabela não será modificada pela metade, revertendo tudo para o estado original.
  • Dual Write / Migração de Dados: O comando UPDATE sincroniza o estado dos dados históricos. Em tabelas gigantescas, essa carga é executada em lotes pequenos (ex: 5000 linhas por vez) para evitar o travamento completo do banco por horas.
  • Implicit Commits: No MySQL, comandos como ALTER TABLE, DROP TABLE, CREATE TABLE salvam as mudanças imediatamente na estrutura do disco de dados e terminam a transação em execução silenciosamente, impedindo qualquer chance de ROLLBACK.

🎯 ATIVIDADE 20 — ARQUITETO SUPREMO

📖 Fundamentação Teórica

Para realizar este laboratório com sucesso, certifique-se de ter compreendido os conceitos apresentados no:
👉 CAPÍTULO 20: ESTUDO DE CASO (TecProExpress) E CONCLUSÃO

Parabéns! Você alcançou a vigésima semana do curso de Banco de Dados. Chegar até aqui é a prova definitiva do seu comprometimento e evolução técnica. De um estudante que estava instalando o SGBD local na primeira semana, você se transformou em um Engenheiro de Dados Sênior / Arquiteto de Soluções de Alta Performance.

O desafio final da Fase 2 é criar uma arquitetura de dados poliglota completa para a plataforma global TecProExpress Enterprise Engine, projetada para lidar com milhões de transações por minuto, auditorias em tempo real, cache veloz de motoristas e recomendações inteligentes baseadas em IA. 🛡️🏆


🎯 Objetivos de Aprendizagem do Laboratório

Ao final deste laboratório prático (estimativa: 4 horas presenciais / autoguiadas), você será capaz de:

  • A Persistência Poliglota (Polyglot Persistence) em ambiente real, usando cada banco de dados para seu propósito ideal.
  • Uma camada de transações relacionais segura (ACID) em PostgreSQL com triggers de integridade física de estoque.
  • Um sistema de cache e rastreamento em tempo real extremamente rápido com Redis.
  • Uma central de auditoria e logs telemétricos flexíveis de sensores em MongoDB.
  • Um mecanismo de recomendação semântica inteligente de rotas baseado em IA usando pgvector.

🏢 O Cenário Prático (Seu Desafio Final)

A TecProExpress fechou uma aliança internacional com grandes indústrias farmacêuticas e e-commerces globais. O novo sistema corporativo precisa garantir:

  1. Segurança e Consistência (SQL - PostgreSQL): Controle absoluto de saldo de estoque nos galpões e processamento de pagamentos sob severas garantias ACID.
  2. Rastreamento Instantâneo (Cache - Redis): Monitorar a posição geográfica exata em tempo real de milhares de motoristas, atualizada a cada 3 segundos.
  3. Auditoria Telemétrica (Documento - MongoDB): Registrar os logs de temperatura de cargas de vacinas e medicamentos especiais coletados por sensores IoT.
  4. Despacho Inteligente (IA - pgvector): Combinar semanticamente solicitações textuais complexas de clientes (ex: "preciso de transporte para carga refrigerada que não pode sofrer vibrações") com as características descritivas dos veículos disponíveis na frota.

Como Arquiteto Supremo, você irá consolidar esta solução integrada.


🧠 A Arquitetura Poliglota do Ecossistema

Abaixo está o mapeamento dos fluxos de dados que você irá implementar:

flowchart TD
    App["Aplicação Cliente / Gateway"] -->|Transações Operacionais & IA| Postgres["PostgreSQL - Core Relacional + pgvector"]
    App -->|Localização em Tempo Real / TTL| Redis["Redis - Caching e Status Rápido"]
    App -->|Logs de Telemetria de Sensores IoT| Mongo["MongoDB - Trilha de Auditoria e Documentos"]

    style Postgres fill:#e3f2fd,stroke:#1e88e5,stroke-width:2px
    style Redis fill:#ffebee,stroke:#e53935,stroke-width:2px
    style Mongo fill:#e8f5e9,stroke:#4caf50,stroke-width:2px

🛠️ Passo 1: O Núcleo Transacional Seguro (PostgreSQL / SQL)

O núcleo financeiro da TecProExpress exige consistência total de estoque. Você deve garantir que a liberação de um frete reduza automaticamente o inventário disponível e registre a movimentação financeira.

  1. Tabelas: Crie as tabelas produto, pedido e estoque (com chaves numéricas substitutas).
  2. Gatilho de Integridade (Trigger): Crie uma trigger em PL/pgSQL que impeça a inserção de um pedido se a quantidade solicitada for maior que o estoque atual (RAISE EXCEPTION), ou reduza a quantidade em estoque automaticamente após a aprovação do pedido.
  3. Transação Concorrente: Escreva um bloco de transação segura que processe a venda e atualize as tabelas de forma atômica.

🛠️ Passo 2: O Cache de Alta Velocidade (Redis)

Os motoristas da TecProExpress enviam coordenadas geográficas de segundo em segundo. Salvar isso no PostgreSQL travaria as tabelas operacionais. Usaremos o Redis para cache rápido com tempo de expiração automática (TTL).

  1. Rastreamento: Use o CLI do Redis para registrar a localização de um motorista (id: 502) no formato de Hash (latitude, longitude, status: em_transito).
  2. TTL Constraint: Defina que esses dados de localização expiram automaticamente em 30 segundos (EXPIRE) caso o sinal do motorista caia, garantindo que o painel de controle detecte a perda de sinal.

🛠️ Passo 3: Auditoria Telemétrica e Flexível (MongoDB)

Cargas frias coletam dados de sensores IoT de temperatura. Como os sensores mudam conforme a marca e o modelo (alguns gravam apenas temperatura, outros gravam pressão e vibração), o MongoDB é o repositório perfeito.

  1. Crie documentos contendo a telemetria estruturada das caixas de vacinas.
  2. Escreva uma query agregada que busque todas as telemetrias onde a temperatura ultrapassou o limite crítico de segurança (ex: superior a 8.0 graus Celsius) para enviar alertas à central.

🛠️ Passo 4: O Mecanismo de Inteligência Vetorial (pgvector)

O cliente solicita um serviço escrevendo um texto livre. Precisamos cruzar esse texto com a descrição dos veículos de carga usando busca semântica para sugerir a frota ideal.

  1. Habilite o pgvector na base PostgreSQL.
  2. Crie a tabela frota_ia contendo o modelo do caminhão, descrição dos acessórios e um vetor tridimensional de significados (VECTOR(3)).
  3. Realize a busca semântica por similaridade de cosseno com o embedding de busca do cliente.

💻 Execução do Pipeline Poliglota Avançado & Logs no Terminal

Para verificar a orquestração simultânea dos 4 motores de dados (PostgreSQL + Redis + MongoDB + pgvector):

# integrador_poliglota_avancado.py
print("=" * 68)
print("🚀 ARQUITETURA POLIGLOTA AVANÇADA (PROJETO FINAL BD II) - TECPRO")
print("=" * 68)
print("1. [PostgreSQL Core]  Transação ACID e Trigger de Estoque executadas ... [OK]")
print("2. [Redis Cache]      Token de sessão do motorista ativo (TTL 300s) ... [OK]")
print("3. [MongoDB IoT]      Telemetria de baú refrigerado a 4.2°C persistida . [OK]")
print("4. [pgvector IA]      Busca semântica de caminhão refrigerado (Dist: 0.002) [OK]")
print("-" * 68)
print("🏆 [HOMOLOGAÇÃO] Todos os 4 motores de persistência 100% integrados!")
print("=" * 68)

🖥️ Saída Esperada no Terminal de Homologação:

====================================================================
🚀 ARQUITETURA POLIGLOTA AVANÇADA (PROJETO FINAL BD II) - TECPRO
====================================================================
1. [PostgreSQL Core]  Transação ACID e Trigger de Estoque executadas ... [OK]
2. [Redis Cache]      Token de sessão do motorista ativo (TTL 300s) ... [OK]
3. [MongoDB IoT]      Telemetria de baú refrigerado a 4.2°C persistida . [OK]
4. [pgvector IA]      Busca semântica de caminhão refrigerado (Dist: 0.002) [OK]
--------------------------------------------------------------------
🏆 [HOMOLOGAÇÃO] Todos os 4 motores de persistência 100% integrados!
====================================================================

🌐 Requisições cURL Poliglotas Avançadas (Swagger /docs)

🔹 1. Busca Semântica de Veículo com pgvector:

curl -X POST "http://127.0.0.1:8000/api/v1/frota/recomendar-ia" \
     -H "Content-Type: application/json" \
     -d '{"descricao_demanda": "transporte urgente de vacinas com controle térmico"}'

🔹 2. Auditoria de Temperatura Crítica no MongoDB:

curl -X GET "http://127.0.0.1:8000/api/v1/telemetria/alertas?temp_maxima=8.0" \
     -H "Accept: application/json"

📤 Instruções de Entrega (Microsoft Teams)

Como entrega deste projeto integrador sênior final da Fase 2, organize seus arquivos em um repositório Git ou pacote compactado (.zip) com a seguinte estrutura profissional:

  1. /relacional: Arquivo relacional_core.sql contendo a DDL estruturada, sementes (DML), a trigger de estoque PL/pgSQL e os scripts de transações ACID.
  2. /caching: Arquivo driver_cache.redis listando todos os comandos executados no Redis CLI para controle de driver e monitoramento de TTL.
  3. /telemetria: Arquivo sensor_logs.js contendo os scripts de inserção NoSQL no MongoDB e a consulta analítica de auditoria IoT.
  4. /inteligencia: Arquivo despacho_ia.sql contendo a criação da frota vetorial no pgvector e a query de recomendação semântica de cosseno.
  5. README.md: Um documento sênior explicando a justificativa arquitetural do projeto, detalhando por que a arquitetura poliglota foi implementada e quais problemas de performance ela resolveu na TecProExpress.

💡 Checkpoint de Lógica

Importante

O Desafio dos Sistemas Distribuídos: Ao usar múltiplos bancos de dados (Persistência Poliglota), manter as informações sincronizadas é um grande desafio. Se o pagamento for cancelado no PostgreSQL, como avisamos o MongoDB e o Redis? Em sistemas avançados de software, usamos o Saga Pattern ou Event-Driven Architecture (Arquitetura Baseada em Eventos) com brokers de mensageria (como Apache Kafka ou RabbitMQ) para garantir que todas as pontas do sistema reajam de forma assíncrona e consistente às mudanças do ecossistema. 🧠🛡️

---

📊 Rubrica Formativa de Avaliação

Critério de Avaliação Insuficiente (0% - 40%) Regular (41% - 70%) Excelente (71% - 100%)
Arquitetura de Persistência Poliglota (SQL, NoSQL, Redis, pgvector) Utliza apenas um tipo de banco de dados ou com scripts quebrados de conexão. Integra 2 dos 4 motores de banco de dados do projeto final. Arquitetura Poliglota impecável integrando PostgreSQL Relacional (Triggers), Redis Cache (TTL), MongoDB Telemetria (Documentos) e pgvector (IA).
Consistência & Teorema CAP Não entende os desafios de sincronização em sistemas distribuídos. Explica o Teorema CAP mas sem relacionar com as garantias do Postgres e MongoDB. Análise sênior de consistência de dados em ambiente distribuído justificando o uso de Saga Pattern/Event-Driven Architecture e o Teorema CAP.
Estrutura de Entrega & README Técnico Entrega arquivos soltos sem organização por pastas. Organiza a estrutura de diretórios mas com `README.md` superficial. Entrega integradora sênior completa com pastas `/relacional`, `/caching`, `/telemetria`, `/inteligencia` e `README.md` arquitetural impecável.

🔥 Desafio de Fixação (Opcional)

Nível: Arquiteto de Soluções Principal (Staff Engineer) 🏆

Pesquise e desenhe em formato texto ou no seu README uma explicação sucinta sobre o Teorema CAP (Consistência, Disponibilidade e Tolerância a Partições). Explique para a equipe executiva qual das garantias o PostgreSQL (SQL) prioriza em comparação com o MongoDB (NoSQL) quando ocorre uma falha na infraestrutura de servidores de rede.


🔑 Gabarito de Código/Fórmulas Completo

🐘 1. PostgreSQL Relacional & Trigger (pgAdmin)

-- DDL Estruturada
CREATE TABLE produto (
    id SERIAL PRIMARY KEY,
    nome VARCHAR(100),
    preco DECIMAL(10,2)
);

CREATE TABLE estoque (
    id_produto INT PRIMARY KEY REFERENCES produto(id),
    quantidade_disponivel INT CHECK (quantidade_disponivel >= 0)
);

CREATE TABLE pedido (
    id SERIAL PRIMARY KEY,
    id_produto INT REFERENCES produto(id),
    quantidade INT
);

-- Trigger de Validação de Estoque em PL/pgSQL
CREATE OR REPLACE FUNCTION valida_e_atualiza_estoque()
RETURNS TRIGGER AS $$
DECLARE
    qtd_atual INT;
BEGIN
    SELECT quantidade_disponivel INTO qtd_atual 
    FROM estoque WHERE id_produto = NEW.id_produto;

    IF qtd_atual IS NULL OR qtd_atual < NEW.quantidade THEN
        RAISE EXCEPTION 'Erro: Estoque insuficiente para o produto %!', NEW.id_produto;
    END IF;

    UPDATE estoque 
    SET quantidade_disponivel = quantidade_disponivel - NEW.quantidade
    WHERE id_produto = NEW.id_produto;

    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trg_valida_pedido
BEFORE INSERT ON pedido
FOR EACH ROW
EXECUTE FUNCTION valida_e_atualiza_estoque();

🔴 2. Redis Caching CLI Commands

-- 1. Cria o cache de geolocalização do motorista
HSET motorista:502 latitude -23.550520 longitude -46.633308 status em_transito

-- 2. Define o tempo de vida (TTL) para 30 segundos
EXPIRE motorista:502 30

-- 3. Consulta rápida do status
HGET motorista:502 status

🍃 3. MongoDB Telemetria de Sensores IoT

// Inserção de Logs de Temperatura da Vacina
db.telemetria_vacina.insertMany([
  {
    "carga_id": 9091,
    "sensor": "SENS-TEMP-01",
    "leitura": { "temperatura_celsius": 4.2, "umidade": 35 },
    "data_hora": ISODate("2026-05-19T16:00:00Z"),
    "status": "normal"
  },
  {
    "carga_id": 9091,
    "sensor": "SENS-TEMP-01",
    "leitura": { "temperatura_celsius": 9.5, "umidade": 38 },
    "data_hora": ISODate("2026-05-19T16:05:00Z"),
    "status": "critico"
  }
]);

// Consulta de Auditoria para Alertas Críticos (Temperatura > 8 graus)
db.telemetria_vacina.find({
  "leitura.temperatura_celsius": { $gt: 8.0 }
});

🧠 4. Busca Semântica pgvector (IA)

CREATE EXTENSION IF NOT EXISTS vector;

CREATE TABLE frota_ia (
    id SERIAL PRIMARY KEY,
    modelo VARCHAR(100),
    recursos TEXT,
    vetor_especialidade VECTOR(3)
);

INSERT INTO frota_ia (modelo, recursos, vetor_especialidade) VALUES
('Caminhão Frigorífico Volvo', 'Câmara fria regulada, amortecedores de impacto pneumáticos', '[0.98, 0.95, 0.05]'),
('Furgão Elétrico Cargo', 'Focado em entregas urbanas rápidas sem refrigeração', '[0.10, 0.20, 0.99]');

-- O cliente pesquisou por: "Preciso de transporte congelado e estável para medicamentos"
-- Embedding gerado pela IA da busca do usuário: [0.95, 0.92, 0.08]
SELECT modelo, recursos
FROM frota_ia
ORDER BY vetor_especialidade <=> '[0.95, 0.92, 0.08]'
LIMIT 1;

🔍 Explicação do Gabarito:

  • Trigger Relacional: Garante consistência a nível transacional no Postgres. Se o estoque cair abaixo de zero, a restrição de integridade ou o RAISE EXCEPTION desfazem qualquer inserção de pedido, protegendo o faturamento físico da empresa.
  • TTL do Redis: Permite liberar memória RAM automaticamente de localizações antigas sem necessidade de rotinas lentas de expiração via banco de dados relacional.
  • MongoDB Flexível: Permite aninhar dados de sensores distintos (temperatura, umidade, etc.) sem forçar uma coluna vazia ou nula para outros sensores mais simples.
  • Similaridade pgvector: Localizou o Caminhão Frigorífico Volvo como par ideal semanticamente devido ao ângulo quase idêntico entre o vetor de busca do cliente e o vetor de especialidade da frota.

🏭 PROJETO 01: MANUTRACK (GESTÃO DE MANUTENÇÃO INDUSTRIAL)

📘 TUTORIAL AUTOGUIADO PASSO A PASSO — DO ZERO AO DEPLOY NO WINDOWS 10/11

Setor Econômico: Indústria e Produção
Domínio: Chão de Fábrica, Manutenção Preventiva/Corretiva e Monitoramento de Equipamentos
Nível de Complexidade: 🟢 Nível 1: Essencial / Básico (4 Tabelas Relacionais)
Ambiente de Desenvolvimento: Windows 10/11 (PT-BR) + VS Code + Python 3.11+ (Venv)
Stack Principal: Flask 3.x + SQLAlchemy 2.0 + Pydantic v2 + SQLite (Dev) / PostgreSQL (Docker) + Jinja2/Bootstrap 5 + Pytest


flowchart LR
    A["⚙️ 1. Setup Windows/Venv"] --> B["🛢️ 2. Dual-Database"]
    B --> C["🧱 3. Modelos ORM (4 Tabelas)"]
    C --> D["📋 4. Schemas Pydantic"]
    D --> E["🌐 5. Endpoints REST (Flask)"]
    E --> F["🎨 6. Interface Web Jinja2"]
    F --> G["🧪 7. Testes Pytest (100% Verde)"]
    G --> H["📊 8. 10 Desafios SQL"]
    H --> I["🩺 9. Checklist & Troubleshooting"]

    style A fill:#e3f2fd,stroke:#1565c0
    style B fill:#fff8e1,stroke:#f57f17
    style C fill:#f3e5f5,stroke:#7b1fa2
    style D fill:#ede7f6,stroke:#5e35b1
    style E fill:#e0f2fe,stroke:#0284c7
    style F fill:#fce4ec,stroke:#c2185b
    style G fill:#dcfce7,stroke:#16a34a
    style H fill:#fef3c7,stroke:#d97706
    style I fill:#fee2e2,stroke:#ef4444

📂 0. Estrutura Completa de Pastas e Arquivos no VS Code

Antes de programar, crie exatamente a seguinte estrutura de diretórios no seu computador:

pi_01_manutrack/
├── app/
│   ├── __init__.py
│   ├── main.py
│   ├── core/
│   │   ├── __init__.py
│   │   └── database.py
│   ├── models/
│   │   ├── __init__.py
│   │   └── manutencao_models.py
│   ├── schemas/
│   │   ├── __init__.py
│   │   └── manutencao_schemas.py
│   ├── routers/
│   │   ├── __init__.py
│   │   └── manutencao_router.py
│   └── templates/
│       ├── base.html
│       └── index.html
├── tests/
│   ├── __init__.py
│   └── test_manutrack.py
├── .env
├── requirements.txt
└── README.md

⚙️ ETAPA 1: Preparação do Ambiente no Windows 10/11

Abra o VS Code na pasta pi_01_manutrack e abra o terminal integrado (**Ctrl + ** ou Terminal -> Novo Terminal` configurado como PowerShell).

1.1. Liberar Execução de Scripts no PowerShell (Se necessário)

Se for a primeira vez utilizando ambientes virtuais no Windows, execute:

Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned

1.2. Criar e Ativar o Ambiente Virtual (venv)

# 1. Criar o ambiente virtual isolado:
python -m venv venv

# 2. Ativar o venv (você verá o prefixo (venv) verde no terminal):
.\venv\Scripts\Activate.ps1

1.3. Criar o Arquivo de Dependências (requirements.txt)

Crie o arquivo requirements.txt na raiz com o conteúdo:

flask==3.0.3
sqlalchemy==2.0.35
pydantic==2.9.0
jinja2==3.1.4
pytest==8.3.0

Instale todas as dependências com um único comando:

pip install -r requirements.txt

1.4. Criar o Arquivo de Variáveis de Ambiente (.env)

Crie o arquivo .env na raiz:

# Ambiente de Desenvolvimento Local (Zero Configuração com SQLite):
DATABASE_URL=sqlite:///./manutrack.db

# Configuração Futura de Produção (PostgreSQL no Docker):
# DATABASE_URL=postgresql://postgres:postgres@localhost:5432/manutrack_prod

🛢️ ETAPA 2: Camada de Conexão e Banco Dual (app/core/database.py)

Crie o arquivo app/core/database.py:

import os
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, DeclarativeBase

DATABASE_URL = os.getenv("DATABASE_URL", "sqlite:///./manutrack.db")

# Ajuste específico para concorrência de threads no SQLite local
connect_args = {"check_same_thread": False} if DATABASE_URL.startswith("sqlite") else {}

engine = create_engine(
    DATABASE_URL,
    connect_args=connect_args,
    echo=False
)

SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

class Base(DeclarativeBase):
    pass

def get_db():
    """Gerador de sessão com fechamento garantido."""
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

🧱 ETAPA 3: Modelagem Relacional ORM (app/models/manutencao_models.py)

Crie o arquivo app/models/manutencao_models.py com as 4 entidades do domínio industrial:

from datetime import datetime
from sqlalchemy import Integer, String, DateTime, ForeignKey, Text
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.core.database import Base

class LinhaProducao(Base):
    __tablename__ = "linhas_producao"

    id_linha: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    nome: Mapped[str] = mapped_column(String(100), nullable=False)
    setor_fabrica: Mapped[str] = mapped_column(String(100), nullable=False)

    equipamentos: Mapped[list["Equipamento"]] = relationship(
        "Equipamento", back_populates="linha", cascade="all, delete-orphan"
    )

class TipoManutencao(Base):
    __tablename__ = "tipos_manutencao"

    id_tipo: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    nome: Mapped[str] = mapped_column(String(80), nullable=False)
    periodicidade_dias: Mapped[int] = mapped_column(Integer, default=30)

    ordens: Mapped[list["OrdemManutencao"]] = relationship("OrdemManutencao", back_populates="tipo")

class Equipamento(Base):
    __tablename__ = "equipamentos"

    id_equipamento: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    codigo_patrimonio: Mapped[str] = mapped_column(String(50), unique=True, nullable=False, index=True)
    nome: Mapped[str] = mapped_column(String(120), nullable=False)
    id_linha: Mapped[int] = mapped_column(Integer, ForeignKey("linhas_producao.id_linha"), nullable=False)
    status: Mapped[str] = mapped_column(String(30), default="OPERACIONAL")

    linha: Mapped["LinhaProducao"] = relationship("LinhaProducao", back_populates="equipamentos")
    ordens: Mapped[list["OrdemManutencao"]] = relationship(
        "OrdemManutencao", back_populates="equipamento", cascade="all, delete-orphan"
    )

class OrdemManutencao(Base):
    __tablename__ = "ordens_manutencao"

    id_ordem: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    id_equipamento: Mapped[int] = mapped_column(Integer, ForeignKey("equipamentos.id_equipamento"), nullable=False)
    id_tipo: Mapped[int] = mapped_column(Integer, ForeignKey("tipos_manutencao.id_tipo"), nullable=False)
    descricao_falha: Mapped[str] = mapped_column(Text, nullable=False)
    data_agendada: Mapped[datetime] = mapped_column(DateTime, default=datetime.now)
    responsavel_tecnico: Mapped[str] = mapped_column(String(100), nullable=False)
    status: Mapped[str] = mapped_column(String(30), default="AGENDADA")

    equipamento: Mapped["Equipamento"] = relationship("Equipamento", back_populates="ordens")
    tipo: Mapped["TipoManutencao"] = relationship("TipoManutencao", back_populates="ordens")

📋 ETAPA 4: Schemas de Validação Pydantic v2 (app/schemas/manutencao_schemas.py)

Crie o arquivo app/schemas/manutencao_schemas.py:

from pydantic import BaseModel, Field
from datetime import datetime

class LinhaProducaoCreate(BaseModel):
    nome: str = Field(..., min_length=3, max_length=100)
    setor_fabrica: str = Field(..., min_length=3, max_length=100)

class LinhaProducaoResponse(LinhaProducaoCreate):
    id_linha: int
    model_config = {"from_attributes": True}

class EquipamentoCreate(BaseModel):
    codigo_patrimonio: str = Field(..., min_length=3, max_length=50)
    nome: str = Field(..., min_length=3, max_length=120)
    id_linha: int
    status: str = "OPERACIONAL"

class EquipamentoResponse(EquipamentoCreate):
    id_equipamento: int
    model_config = {"from_attributes": True}

class OrdemManutencaoCreate(BaseModel):
    id_equipamento: int
    id_tipo: int
    descricao_falha: str = Field(..., min_length=5)
    responsavel_tecnico: str = Field(..., min_length=3)

class OrdemManutencaoResponse(OrdemManutencaoCreate):
    id_ordem: int
    status: str
    data_agendada: datetime
    model_config = {"from_attributes": True}

🌐 ETAPA 5: Rotas de API (app/routers/manutencao_router.py) & Inicialização (app/main.py)

5.1. Criar o Blueprint do Flask (app/routers/manutencao_router.py)

from flask import Blueprint, request, jsonify
from sqlalchemy.orm import Session
from sqlalchemy import select
from app.core.database import SessionLocal
from app.models.manutencao_models import LinhaProducao, TipoManutencao, Equipamento, OrdemManutencao
from app.schemas.manutencao_schemas import (
    LinhaProducaoCreate,
    EquipamentoCreate,
    OrdemManutencaoCreate
)

router = Blueprint("manutencao", __name__, url_prefix="/api")

# --- ROTAS DE LINHA DE PRODUÇÃO ---
@router.post("/linhas/")
def criar_linha():
    dados = request.get_json()
    if not dados:
        return jsonify({"erro": "Payload ausente"}), 400
    try:
        payload = LinhaProducaoCreate(**dados)
    except Exception as ex:
        return jsonify({"erro": str(ex)}), 400

    with SessionLocal() as db:
        nova = LinhaProducao(**payload.model_dump())
        db.add(nova)
        db.commit()
        db.refresh(nova)
        return jsonify({"id_linha": nova.id_linha, "nome": nova.nome, "setor_fabrica": nova.setor_fabrica}), 201

@router.get("/linhas/")
def listar_linhas():
    with SessionLocal() as db:
        linhas = db.scalars(select(LinhaProducao).order_by(LinhaProducao.nome.asc())).all()
        return jsonify([{"id_linha": l.id_linha, "nome": l.nome, "setor_fabrica": l.setor_fabrica} for l in linhas]), 200

# --- ROTAS DE EQUIPAMENTOS ---
@router.post("/equipamentos/")
def criar_equipamento():
    dados = request.get_json()
    if not dados:
        return jsonify({"erro": "Payload ausente"}), 400
    try:
        payload = EquipamentoCreate(**dados)
    except Exception as ex:
        return jsonify({"erro": str(ex)}), 400

    with SessionLocal() as db:
        if not db.get(LinhaProducao, payload.id_linha):
            return jsonify({"erro": "Linha de produção não encontrada."}), 404
        
        duplicado = db.scalar(select(Equipamento).where(Equipamento.codigo_patrimonio == payload.codigo_patrimonio))
        if duplicado:
            return jsonify({"erro": "Código de patrimônio já cadastrado."}), 400

        novo = Equipamento(**payload.model_dump())
        db.add(novo)
        db.commit()
        db.refresh(novo)
        return jsonify({
            "id_equipamento": novo.id_equipamento,
            "codigo_patrimonio": novo.codigo_patrimonio,
            "nome": novo.nome,
            "id_linha": novo.id_linha,
            "status": novo.status
        }), 201

@router.get("/equipamentos/")
def listar_equipamentos():
    with SessionLocal() as db:
        equipamentos = db.scalars(select(Equipamento).order_by(Equipamento.id_equipamento.asc())).all()
        return jsonify([{
            "id_equipamento": e.id_equipamento,
            "codigo_patrimonio": e.codigo_patrimonio,
            "nome": e.nome,
            "id_linha": e.id_linha,
            "status": e.status
        } for e in equipamentos]), 200

# --- ROTAS DE ORDENS DE MANUTENÇÃO ---
@router.post("/ordens/")
def criar_ordem():
    dados = request.get_json()
    if not dados:
        return jsonify({"erro": "Payload ausente"}), 400
    try:
        payload = OrdemManutencaoCreate(**dados)
    except Exception as ex:
        return jsonify({"erro": str(ex)}), 400

    with SessionLocal() as db:
        equipamento = db.get(Equipamento, payload.id_equipamento)
        if not equipamento:
            return jsonify({"erro": "Equipamento não encontrado."}), 404
        
        nova_os = OrdemManutencao(**payload.model_dump(), status="AGENDADA")
        db.add(nova_os)
        db.commit()
        db.refresh(nova_os)
        return jsonify({
            "id_ordem": nova_os.id_ordem,
            "id_equipamento": nova_os.id_equipamento,
            "status": nova_os.status
        }), 201

5.2. Criar o Ponto de Entrada da Aplicação (app/main.py)

from pathlib import Path
from flask import Flask, render_template
from sqlalchemy import select
from app.core.database import engine, Base, SessionLocal
from app.models.manutencao_models import LinhaProducao, TipoManutencao, Equipamento
from app.routers import manutencao_router

# Cria as tabelas automaticamente no banco SQLite ao iniciar
Base.metadata.create_all(bind=engine)

BASE_DIR = Path(__file__).resolve().parent
app = Flask(__name__, template_folder=str(BASE_DIR / "templates"))

# Conectar Blueprint de Rotas da API
app.register_blueprint(manutencao_router.router)

def seed_dados_iniciais():
    with SessionLocal() as db:
        if not db.scalar(select(LinhaProducao)):
            l1 = LinhaProducao(nome="Linha Alpha", setor_fabrica="Usinagem Pesada")
            l2 = LinhaProducao(nome="Linha Beta", setor_fabrica="Montagem Robótica")
            db.add_all([l1, l2])
            db.commit()

            t1 = TipoManutencao(nome="Preventiva Mensal", periodicidade_dias=30)
            t2 = TipoManutencao(nome="Corretiva Emergencial", periodicidade_dias=0)
            db.add_all([t1, t2])
            db.commit()

            e1 = Equipamento(codigo_patrimonio="EQ-1001", nome="Torno CNC 01", id_linha=l1.id_linha, status="OPERACIONAL")
            e2 = Equipamento(codigo_patrimonio="EQ-1002", nome="Robô Soldador", id_linha=l2.id_linha, status="MANUTENCAO")
            db.add_all([e1, e2])
            db.commit()

# Executa o seeder na inicialização
seed_dados_iniciais()

@app.get("/")
def painel_web():
    with SessionLocal() as db:
        equipamentos = db.scalars(select(Equipamento)).all()
        linhas = db.scalars(select(LinhaProducao)).all()
        return render_template("index.html", equipamentos=equipamentos, linhas=linhas)

if __name__ == "__main__":
    app.run(port=5000, debug=True)

🔍 Como Testar os Endpoints REST Localmente

  1. Iniciar o Servidor:
python -m app.main

O servidor estará rodando em: http://localhost:5000

  1. Teste 1: Cadastrar Linha de Produção (POST /api/linhas/) Execute no terminal (PowerShell ou cURL):
curl -X POST "http://localhost:5000/api/linhas/" -H "Content-Type: application/json" -d "{\"nome\": \"Linha Gamma\", \"setor_fabrica\": \"Galpao Sul\"}"

A resposta esperada é 201 Created com o id_linha gerado.

  1. Teste 2: Cadastrar Equipamento (POST /api/equipamentos/)
curl -X POST "http://localhost:5000/api/equipamentos/" -H "Content-Type: application/json" -d "{\"codigo_patrimonio\": \"EQ-2005\", \"nome\": \"Cabine de Pintura\", \"id_linha\": 1, \"status\": \"OPERACIONAL\"}"
  1. Teste 3: Validação de Erro 400 (Patrimônio Duplicado) Execute novamente a mesma requisição e veja o retorno com status 400 Bad Request informando a duplicidade!

🎨 ETAPA 6: Interface Web com Jinja2 e Bootstrap 5

6.1. Criar app/templates/base.html

<!DOCTYPE html>
<html lang="pt-br">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>ManuTrack — Gestão de Manutenção</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
</head>
<body class="bg-light">
    <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
        <div class="container">
            <a class="navbar-brand fw-bold" href="/"><i class="bi bi-gear-wide-connected text-warning"></i> ManuTrack Industrial</a>
            <a href="/docs" target="_blank" class="btn btn-outline-warning btn-sm"><i class="bi bi-code-square"></i> Swagger API</a>
        </div>
    </nav>
    <div class="container py-4">
        {% block content %}{% endblock %}
    </div>
</body>
</html>

6.2. Criar app/templates/index.html

{% extends "base.html" %}
{% block content %}
<div class="d-flex justify-content-between align-items-center mb-4">
    <h2><i class="bi bi-speedometer2"></i> Painel de Monitoramento de Máquinas</h2>
    <span class="badge bg-primary fs-6">{{ equipamentos|length }} Equipamentos Cadastrados</span>
</div>

<div class="row">
    {% for eq in equipamentos %}
    <div class="col-md-4 mb-3">
        <div class="card shadow-sm border-0 h-100">
            <div class="card-body">
                <div class="d-flex justify-content-between">
                    <span class="badge bg-secondary">{{ eq.codigo_patrimonio }}</span>
                    {% if eq.status == 'OPERACIONAL' %}
                        <span class="badge bg-success"><i class="bi bi-check-circle"></i> OPERACIONAL</span>
                    {% elif eq.status == 'MANUTENCAO' %}
                        <span class="badge bg-warning text-dark"><i class="bi bi-tools"></i> EM MANUTENÇÃO</span>
                    {% else %}
                        <span class="badge bg-danger"><i class="bi bi-exclamation-triangle"></i> PARADO</span>
                    {% endif %}
                </div>
                <h5 class="card-title mt-2">{{ eq.nome }}</h5>
                <p class="text-muted small mb-0"><i class="bi bi-diagram-3"></i> Linha ID: {{ eq.id_linha }}</p>
            </div>
        </div>
    </div>
    {% endfor %}
</div>
{% endblock %}

🧪 ETAPA 7: Suíte de Testes Automatizados com Pytest (tests/test_manutrack.py)

Crie tests/test_manutrack.py:

import pytest
from app.main import app

@pytest.fixture
def client():
    """Fixture Pytest que fornece o cliente de teste nativo do Flask."""
    app.config["TESTING"] = True
    with app.test_client() as client:
        yield client

def test_ciclo_completo_manutrack(client):
    # 1. Cadastrar Linha
    res_linha = client.post("/api/linhas/", json={"nome": "Linha Teste", "setor_fabrica": "Setor A"})
    assert res_linha.status_code == 201
    id_linha = res_linha.get_json()["id_linha"]

    # 2. Cadastrar Equipamento
    res_eq = client.post("/api/equipamentos/", json={
        "codigo_patrimonio": "EQ-UNIT-01",
        "nome": "Prensa Hidráulica 10T",
        "id_linha": id_linha,
        "status": "OPERACIONAL"
    })
    assert res_eq.status_code == 201
    assert res_eq.get_json()["codigo_patrimonio"] == "EQ-UNIT-01"

    # 3. Listar Equipamentos
    res_list = client.get("/api/equipamentos/")
    assert res_list.status_code == 200
    assert any(e["codigo_patrimonio"] == "EQ-UNIT-01" for e in res_list.get_json())

    # 4. Teste de Validação de Erro (Linha Inexistente)
    res_erro = client.post("/api/equipamentos/", json={
        "codigo_patrimonio": "EQ-INVALIDO",
        "nome": "Máquina Fantasma",
        "id_linha": 9999,
        "status": "OPERACIONAL"
    })
    assert res_erro.status_code == 404

💻 Executar os Testes no Terminal do Windows:

pytest -v

Resultado Esperado:

tests/test_manutrack.py::test_ciclo_completo_manutrack PASSED [100%]
============================== 1 passed in 0.25s ==============================

📊 ETAPA 8: Bateria de 10 Desafios de SQL Corporativo (ManuTrack)

Pratique executando as queries no DBeaver ou terminal do banco:

🎯 Desafio 01: Listar equipamentos com status diferente de 'OPERACIONAL', ordenados pelo nome.

SELECT id_equipamento, codigo_patrimonio, nome, status FROM equipamentos WHERE status <> 'OPERACIONAL' ORDER BY nome ASC;

🔍 Explicação Técnica: Usa o operador de desigualdade <> e ordenação alfabética para listar máquinas em manutenção ou paradas.

🎯 Desafio 02: Buscar as 5 ordens de manutenção mais recentes que estejam com status 'AGENDADA'.

SELECT id_ordem, id_equipamento, data_agendada, responsavel_tecnico FROM ordens_manutencao WHERE status = 'AGENDADA' ORDER BY data_agendada DESC LIMIT 5;

🔍 Explicação Técnica: Combina filtro de status, ordenação decrescente por data e limitação de paginação com LIMIT.

🎯 Desafio 03: Calcular o total de equipamentos instalados em cada linha de produção.

SELECT id_linha, COUNT(*) AS total_equipamentos FROM equipamentos GROUP BY id_linha ORDER BY total_equipamentos DESC;

🔍 Explicação Técnica: Aplica função agregadora COUNT(*) agrupada por linha de fábrica para balanceamento de carga.

🎯 Desafio 04: Exibir o total de ordens concluídas por responsável técnico.

SELECT responsavel_tecnico, COUNT(*) AS ordens_concluidas FROM ordens_manutencao WHERE status = 'CONCLUIDA' GROUP BY responsavel_tecnico ORDER BY ordens_concluidas DESC;

🔍 Explicação Técnica: Agrupa apenas registros que atendem ao filtro WHERE status = 'CONCLUIDA'.

🎯 Desafio 05: Identificar linhas de produção que possuem mais de 3 equipamentos cadastrados.

SELECT id_linha, COUNT(*) AS qtd_maquinas FROM equipamentos GROUP BY id_linha HAVING COUNT(*) > 3;

🔍 Explicação Técnica: Usa HAVING para filtrar os grupos agregados após o cálculo do COUNT(*).

🎯 Desafio 06: Emitir relatório de equipamentos com o nome de sua respectiva Linha de Produção (INNER JOIN).

SELECT e.codigo_patrimonio, e.nome AS equipamento, l.nome AS linha_producao, l.setor_fabrica FROM equipamentos e INNER JOIN linhas_producao l ON e.id_linha = l.id_linha;

🔍 Explicação Técnica: Junção relacional clássica para enriquecer os dados técnicos com o setor fabril correspondente.

🎯 Desafio 07: Relatório completo de ordens de manutenção com nome da máquina, tipo de manutenção e setor.

SELECT o.id_ordem, e.nome AS equipamento, t.nome AS tipo_manutencao, l.setor_fabrica, o.data_agendada, o.status FROM ordens_manutencao o INNER JOIN equipamentos e ON o.id_equipamento = e.id_equipamento INNER JOIN linhas_producao l ON e.id_linha = l.id_linha INNER JOIN tipos_manutencao t ON o.id_tipo = t.id_tipo WHERE o.status = 'AGENDADA';

🔍 Explicação Técnica: Multi-JOIN unindo 4 tabelas relacionais para compor a ficha de serviço do operador.

🎯 Desafio 08: Identificar equipamentos que NUNCA tiveram nenhuma ordem de manutenção aberta (LEFT JOIN).

SELECT e.id_equipamento, e.codigo_patrimonio, e.nome FROM equipamentos e LEFT JOIN ordens_manutencao o ON e.id_equipamento = o.id_equipamento WHERE o.id_ordem IS NULL;

🔍 Explicação Técnica: Usa LEFT JOIN e IS NULL para encontrar registros pai sem nenhum registro correspondente na tabela filha.

🎯 Desafio 09: Listar equipamentos que possuem ordens de manutenção com periodicidade menor que 30 dias (Subquery).

SELECT codigo_patrimonio, nome FROM equipamentos WHERE id_equipamento IN (SELECT id_equipamento FROM ordens_manutencao o INNER JOIN tipos_manutencao t ON o.id_tipo = t.id_tipo WHERE t.periodicidade_dias < 30);

🔍 Explicação Técnica: Subconsulta com IN para filtrar equipamentos sujeitos a regimes de manutenção de alta frequência.

🎯 Desafio 10: Atualizar o status do equipamento para 'MANUTENCAO' com retorno do registro.

UPDATE equipamentos SET status = 'MANUTENCAO' WHERE id_equipamento = 1 AND status = 'OPERACIONAL' RETURNING id_equipamento, codigo_patrimonio, status;

🔍 Explicação Técnica: Operação DML atômica com cláusula RETURNING para notificar o frontend imediatamente sem novo SELECT.


🩺 ETAPA 9: Troubleshooting no Windows & Checklist de Entrega

🛠️ Resolução Rápida de Erros Frequentes:

  1. Erro de Execução no PowerShell (Activate.ps1 bloqueado):
    Execute: Set-ExecutionPolicy -Scope CurrentUser RemoteSigned
  2. Erro 400 Bad Request na API:
    Causa: Você enviou um payload JSON malformado ou com campos obrigatórios ausentes.
  3. Porta 5000 já em uso:
    Execute: python app/main.py --port 5001 ou encerre o processo anterior no Gerenciador de Tarefas.

✅ Checklist de Conclusão do Aluno:

  • Ambiente virtual venv configurado e ativado no Windows.
  • Banco de dados SQLite criado com as 4 tabelas relacionais.
  • Todos os endpoints REST testados com sucesso no terminal ou ferramenta HTTP.
  • Interface visual acessível no navegador (http://localhost:5000).
  • Suíte de testes automatizados executando pytest -v com 100% de sucesso.
  • 10 Desafios de SQL executados e compreendidos.

🏭 ManuTrack — Guia de Execução e README do Projeto

Setor: Indústria e Produção
Componente: Atividades de Projetos II / III
Classificação: 🟢 Nível 1: Essencial / Básico (3 a 4 Tabelas)
Repositório Template: https://github.com/fatec-gti/pi_01_manutrack


🚀 1. Como Executar o Projeto Localmente

Passo 1: Clonar o Repositório e Abrir no VS Code

git clone https://github.com/fatec-gti/pi_01_manutrack.git
code pi_01_manutrack

Passo 2: Criar o Ambiente Virtual e Instalar Dependências

python -m venv venv
# Windows (PowerShell):
.\venv\Scripts\Activate.ps1
# Linux/Mac:
source venv/bin/activate

pip install flask sqlalchemy jinja2 psycopg2-binary

Passo 3: Executar a Aplicação com SQLite (Zero Configuração)

python main.py

Acesse no navegador:

  • 🌐 Interface Web & API REST: http://localhost:5000

🐳 2. Executando com PostgreSQL no Docker Compose

Para subir o banco de dados oficial de produção:

docker compose up -d

Edite o arquivo .env para apontar para o PostgreSQL:

DATABASE_URL=postgresql://postgres:senha@localhost:5432/pi_01_manutrack_db

👥 3. Equipe de Desenvolvimento (Template de Entrega)

  • Analista de Sistemas / PO: [Nome do Estudante]
  • Engenheiro de Software: [Nome do Estudante]
  • DBA / Modelador de Dados: [Nome do Estudante]
  • Desenvolvedor Full-stack: [Nome do Estudante]

📦 PROJETO 02: STOCKFLOW (ALMOXARIFADO INDUSTRIAL & PCP)

📘 TUTORIAL AUTOGUIADO PASSO A PASSO — DO ZERO AO DEPLOY NO WINDOWS 10/11

Setor Econômico: Indústria e Produção
Domínio: Logística Interna, Almoxarifado de Matérias-Primas, Ordens de Produção e Curva ABC
Nível de Complexidade: 🔴 Nível 2: Intermediário (6 Tabelas Relacionais com Movimentações de Estoque)
Ambiente de Desenvolvimento: Windows 10/11 (PT-BR) + VS Code + Python 3.11+ (Venv)
Stack Principal: Flask 3.x + SQLAlchemy 2.0 + Pydantic v2 + SQLite (Dev) / PostgreSQL (Docker) + Jinja2/Bootstrap 5 + Pytest


flowchart LR
    A["⚙️ 1. Setup Windows/Venv"] --> B["🛢️ 2. Dual-Database"]
    B --> C["🧱 3. Modelos ORM (6 Tabelas)"]
    C --> D["📋 4. Schemas Pydantic"]
    D --> E["🌐 5. Endpoints REST (Flask)"]
    E --> F["🎨 6. Interface Web Jinja2"]
    F --> G["🧪 7. Testes Pytest (100% Verde)"]
    G --> H["📊 8. 10 Desafios SQL"]
    H --> I["🩺 9. Checklist & Troubleshooting"]

    style A fill:#e3f2fd,stroke:#1565c0
    style B fill:#fff8e1,stroke:#f57f17
    style C fill:#f3e5f5,stroke:#7b1fa2
    style D fill:#ede7f6,stroke:#5e35b1
    style E fill:#e0f2fe,stroke:#0284c7
    style F fill:#fce4ec,stroke:#c2185b
    style G fill:#dcfce7,stroke:#16a34a
    style H fill:#fef3c7,stroke:#d97706
    style I fill:#fee2e2,stroke:#ef4444

📂 0. Estrutura Completa de Pastas e Arquivos no VS Code

Crie exatamente a seguinte árvore de diretórios no seu computador:

pi_02_stockflow/
├── app/
│   ├── __init__.py
│   ├── main.py
│   ├── core/
│   │   ├── __init__.py
│   │   └── database.py
│   ├── models/
│   │   ├── __init__.py
│   │   └── estoque_models.py
│   ├── schemas/
│   │   ├── __init__.py
│   │   └── estoque_schemas.py
│   ├── routers/
│   │   ├── __init__.py
│   │   └── estoque_router.py
│   └── templates/
│       ├── base.html
│       └── index.html
├── tests/
│   ├── __init__.py
│   └── test_stockflow.py
├── .env
├── requirements.txt
└── README.md

⚙️ ETAPA 1: Preparação do Ambiente no Windows 10/11

Abra o VS Code na pasta pi_02_stockflow e abra o terminal integrado (Ctrl + `).

1.1. Criar e Ativar o Ambiente Virtual (venv)

# 1. Criar o ambiente virtual:
python -m venv venv

# 2. Ativar no Windows PowerShell:
.\venv\Scripts\Activate.ps1

1.2. Criar o Arquivo de Dependências (requirements.txt)

Crie o arquivo requirements.txt:

flask==3.0.3
sqlalchemy==2.0.35
pydantic==2.9.0
jinja2==3.1.4
pytest==8.3.0

Instale com:

pip install -r requirements.txt

1.3. Criar o Arquivo de Variáveis de Ambiente (.env)

DATABASE_URL=sqlite:///./stockflow.db

🛢️ ETAPA 2: Camada de Conexão Dual-Database (app/core/database.py)

Crie app/core/database.py:

import os
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, DeclarativeBase

DATABASE_URL = os.getenv("DATABASE_URL", "sqlite:///./stockflow.db")
connect_args = {"check_same_thread": False} if DATABASE_URL.startswith("sqlite") else {}

engine = create_engine(DATABASE_URL, connect_args=connect_args, echo=False)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

class Base(DeclarativeBase):
    pass

🧱 ETAPA 3: Modelagem Declarativa ORM (app/models/estoque_models.py)

Crie app/models/estoque_models.py com as 6 entidades do domínio de almoxarifado e PCP:

from datetime import date
from decimal import Decimal
from typing import Optional, List
from sqlalchemy import String, Integer, Date, Numeric, ForeignKey
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.core.database import Base

class Fornecedor(Base):
    __tablename__ = "fornecedores"

    id_fornecedor: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    razao_social: Mapped[str] = mapped_column(String(120), nullable=False)
    cnpj: Mapped[str] = mapped_column(String(20), unique=True, nullable=False)

    movimentos: Mapped[List["MovimentoEstoque"]] = relationship(back_populates="fornecedor")

class CategoriaInsumo(Base):
    __tablename__ = "categorias_insumo"

    id_categoria: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    nome: Mapped[str] = mapped_column(String(60), nullable=False)

    materias_primas: Mapped[List["MateriaPrima"]] = relationship(back_populates="categoria")

class MateriaPrima(Base):
    __tablename__ = "materias_primas"

    id_materia: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    codigo_sku: Mapped[str] = mapped_column(String(30), unique=True, nullable=False)
    nome: Mapped[str] = mapped_column(String(100), nullable=False)
    id_categoria: Mapped[int] = mapped_column(ForeignKey("categorias_insumo.id_categoria"), nullable=False)
    unidade_medida: Mapped[str] = mapped_column(String(10), default="UN")
    estoque_atual: Mapped[Decimal] = mapped_column(Numeric(10, 2), default=Decimal("0.00"))
    estoque_minimo: Mapped[Decimal] = mapped_column(Numeric(10, 2), default=Decimal("10.00"))
    custo_unitario: Mapped[Decimal] = mapped_column(Numeric(10, 2), nullable=False)

    categoria: Mapped["CategoriaInsumo"] = relationship(back_populates="materias_primas")
    movimentos: Mapped[List["MovimentoEstoque"]] = relationship(back_populates="materia")
    itens_ordem: Mapped[List["ItemOrdemProducao"]] = relationship(back_populates="materia")

class OrdemProducao(Base):
    __tablename__ = "ordens_producao"

    id_ordem: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    numero_lote: Mapped[str] = mapped_column(String(40), unique=True, nullable=False)
    data_inicio: Mapped[date] = mapped_column(Date, nullable=False)
    data_fim: Mapped[Optional[date]] = mapped_column(Date, nullable=True)
    status: Mapped[str] = mapped_column(String(20), default="PLANEJADA")

    itens: Mapped[List["ItemOrdemProducao"]] = relationship(back_populates="ordem", cascade="all, delete-orphan")

class ItemOrdemProducao(Base):
    __tablename__ = "itens_ordem_producao"

    id_item_op: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    id_ordem: Mapped[int] = mapped_column(ForeignKey("ordens_producao.id_ordem"), nullable=False)
    id_materia: Mapped[int] = mapped_column(ForeignKey("materias_primas.id_materia"), nullable=False)
    quantidade_planejada: Mapped[Decimal] = mapped_column(Numeric(10, 2), nullable=False)
    quantidade_consumida: Mapped[Decimal] = mapped_column(Numeric(10, 2), default=Decimal("0.00"))

    ordem: Mapped["OrdemProducao"] = relationship(back_populates="itens")
    materia: Mapped["MateriaPrima"] = relationship(back_populates="itens_ordem")

class MovimentoEstoque(Base):
    __tablename__ = "movimentos_estoque"

    id_movimento: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    id_materia: Mapped[int] = mapped_column(ForeignKey("materias_primas.id_materia"), nullable=False)
    id_fornecedor: Mapped[Optional[int]] = mapped_column(ForeignKey("fornecedores.id_fornecedor"), nullable=True)
    tipo_movimento: Mapped[str] = mapped_column(String(10), nullable=False) # ENTRADA ou SAIDA
    quantidade: Mapped[Decimal] = mapped_column(Numeric(10, 2), nullable=False)
    valor_unitario: Mapped[Decimal] = mapped_column(Numeric(10, 2), nullable=False)
    data_movimento: Mapped[date] = mapped_column(Date, nullable=False)

    materia: Mapped["MateriaPrima"] = relationship(back_populates="movimentos")
    fornecedor: Mapped[Optional["Fornecedor"]] = relationship(back_populates="movimentos")

📋 ETAPA 4: Schemas de Validação Pydantic v2 (app/schemas/estoque_schemas.py)

Crie app/schemas/estoque_schemas.py:

from decimal import Decimal
from typing import Optional
from pydantic import BaseModel, Field

class FornecedorCreate(BaseModel):
    razao_social: str = Field(..., min_length=3, max_length=120)
    cnpj: str = Field(..., min_length=14, max_length=20)

class FornecedorResponse(FornecedorCreate):
    id_fornecedor: int
    class Config:
        from_attributes = True

class MateriaPrimaCreate(BaseModel):
    codigo_sku: str = Field(..., min_length=3, max_length=30)
    nome: str = Field(..., min_length=3, max_length=100)
    id_categoria: int = Field(..., gt=0)
    unidade_medida: str = Field(default="UN")
    estoque_atual: Decimal = Field(default=Decimal("0.00"), ge=0)
    estoque_minimo: Decimal = Field(default=Decimal("10.00"), ge=0)
    custo_unitario: Decimal = Field(..., gt=0)

class MateriaPrimaResponse(MateriaPrimaCreate):
    id_materia: int
    class Config:
        from_attributes = True

class MovimentoEstoqueCreate(BaseModel):
    id_materia: int = Field(..., gt=0)
    id_fornecedor: Optional[int] = None
    tipo_movimento: str = Field(..., pattern="^(ENTRADA|SAIDA)$")
    quantidade: Decimal = Field(..., gt=0)
    valor_unitario: Decimal = Field(..., gt=0)

🌐 ETAPA 5: Endpoints REST (app/routers/estoque_router.py) & Inicialização (app/main.py)

5.1. Criar app/routers/estoque_router.py

from datetime import date
from flask import Blueprint, request, jsonify
from sqlalchemy.orm import Session
from sqlalchemy import select
from app.core.database import SessionLocal
from app.models.estoque_models import Fornecedor, CategoriaInsumo, MateriaPrima, MovimentoEstoque
from app.schemas.estoque_schemas import (
    FornecedorCreate,
    MateriaPrimaCreate,
    MovimentoEstoqueCreate
)

router = Blueprint("estoque", __name__, url_prefix="/api")

# --- FORNECEDORES ---
@router.post("/fornecedores/")
def criar_fornecedor():
    dados = request.get_json()
    if not dados:
        return jsonify({"erro": "Payload ausente"}), 400
    try:
        payload = FornecedorCreate(**dados)
    except Exception as ex:
        return jsonify({"erro": str(ex)}), 400

    with SessionLocal() as db:
        if db.scalar(select(Fornecedor).where(Fornecedor.cnpj == payload.cnpj)):
            return jsonify({"erro": "CNPJ já cadastrado."}), 400
        novo = Fornecedor(**payload.model_dump())
        db.add(novo)
        db.commit()
        db.refresh(novo)
        return jsonify({"id_fornecedor": novo.id_fornecedor, "razao_social": novo.razao_social, "cnpj": novo.cnpj}), 201

# --- MATÉRIAS-PRIMAS ---
@router.post("/materias-primas/")
def criar_materia_prima():
    dados = request.get_json()
    if not dados:
        return jsonify({"erro": "Payload ausente"}), 400
    try:
        payload = MateriaPrimaCreate(**dados)
    except Exception as ex:
        return jsonify({"erro": str(ex)}), 400

    with SessionLocal() as db:
        if db.scalar(select(MateriaPrima).where(MateriaPrima.codigo_sku == payload.codigo_sku)):
            return jsonify({"erro": "Código SKU já cadastrado."}), 400
        
        nova = MateriaPrima(**payload.model_dump())
        db.add(nova)
        db.commit()
        db.refresh(nova)
        return jsonify({
            "id_materia": nova.id_materia,
            "codigo_sku": nova.codigo_sku,
            "nome": nova.nome,
            "estoque_atual": float(nova.estoque_atual)
        }), 201

@router.get("/materias-primas/")
def listar_materias_primas():
    with SessionLocal() as db:
        materias = db.scalars(select(MateriaPrima).order_by(MateriaPrima.nome.asc())).all()
        return jsonify([{
            "id_materia": m.id_materia,
            "codigo_sku": m.codigo_sku,
            "nome": m.nome,
            "estoque_atual": float(m.estoque_atual),
            "estoque_minimo": float(m.estoque_minimo),
            "custo_unitario": float(m.custo_unitario)
        } for m in materias]), 200

# --- MOVIMENTAÇÃO COM ATUALIZAÇÃO ATÔMICA DE SALDO ---
@router.post("/movimentos/")
def registrar_movimento():
    dados = request.get_json()
    if not dados:
        return jsonify({"erro": "Payload ausente"}), 400
    try:
        payload = MovimentoEstoqueCreate(**dados)
    except Exception as ex:
        return jsonify({"erro": str(ex)}), 400

    with SessionLocal() as db:
        materia = db.get(MateriaPrima, payload.id_materia)
        if not materia:
            return jsonify({"erro": "Matéria-prima não encontrada."}), 404
        
        if payload.tipo_movimento == "SAIDA" and materia.estoque_atual < payload.quantidade:
            return jsonify({"erro": "Saldo insuficiente em estoque para saída."}), 400
        
        # Atualiza saldo de estoque
        if payload.tipo_movimento == "ENTRADA":
            materia.estoque_atual += payload.quantidade
        else:
            materia.estoque_atual -= payload.quantidade

        mov = MovimentoEstoque(**payload.model_dump(), data_movimento=date.today())
        db.add(mov)
        db.commit()
        return jsonify({"message": "Movimentação registrada com sucesso!", "novo_saldo": float(materia.estoque_atual)}), 201

5.2. Criar app/main.py

from pathlib import Path
from decimal import Decimal
from datetime import date
from flask import Flask, render_template
from sqlalchemy import select
from app.core.database import engine, Base, SessionLocal
from app.models.estoque_models import Fornecedor, CategoriaInsumo, MateriaPrima, OrdemProducao, ItemOrdemProducao, MovimentoEstoque
from app.routers import estoque_router

Base.metadata.create_all(bind=engine)

BASE_DIR = Path(__file__).resolve().parent
app = Flask(__name__, template_folder=str(BASE_DIR / "templates"))
app.register_blueprint(estoque_router.router)

def seed_dados_iniciais():
    with SessionLocal() as db:
        if not db.scalar(select(Fornecedor)):
            f1 = Fornecedor(razao_social="Aços Brasil S/A", cnpj="11.111.111/0001-11")
            f2 = Fornecedor(razao_social="Química Global Ltda", cnpj="22.222.222/0001-22")
            db.add_all([f1, f2])
            db.commit()

            c1 = CategoriaInsumo(nome="Metais")
            c2 = CategoriaInsumo(nome="Químicos")
            db.add_all([c1, c2])
            db.commit()

            mp1 = MateriaPrima(codigo_sku="MP-001", nome="Chapa de Aço 3mm", id_categoria=c1.id_categoria, unidade_medida="KG", estoque_atual=Decimal("150.0"), estoque_minimo=Decimal("500.0"), custo_unitario=Decimal("25.0"))
            mp2 = MateriaPrima(codigo_sku="MP-002", nome="Solvente Industrial", id_categoria=c2.id_categoria, unidade_medida="L", estoque_atual=Decimal("80.0"), estoque_minimo=Decimal("100.0"), custo_unitario=Decimal("45.0"))
            db.add_all([mp1, mp2])
            db.commit()

            mov1 = MovimentoEstoque(id_materia=mp1.id_materia, id_fornecedor=f1.id_fornecedor, tipo_movimento="ENTRADA", quantidade=Decimal("1000.0"), valor_unitario=Decimal("24.0"), data_movimento=date.today())
            mov2 = MovimentoEstoque(id_materia=mp2.id_materia, id_fornecedor=f2.id_fornecedor, tipo_movimento="ENTRADA", quantidade=Decimal("2000.0"), valor_unitario=Decimal("40.0"), data_movimento=date.today())
            db.add_all([mov1, mov2])
            db.commit()

seed_dados_iniciais()

@app.get("/")
def painel_web():
    with SessionLocal() as db:
        insumos = db.scalars(select(MateriaPrima)).all()
        return render_template("index.html", insumos=insumos)

if __name__ == "__main__":
    app.run(port=5000, debug=True)

🔍 Como Testar os Endpoints REST Localmente

  1. Iniciar o Servidor:
python -m app.main

Acesse em: http://localhost:5000

  1. Teste 1: Cadastrar Matéria-Prima (POST /api/materias-primas/)
curl -X POST "http://localhost:5000/api/materias-primas/" -H "Content-Type: application/json" -d "{\"codigo_sku\": \"MP-999\", \"nome\": \"Cobre Eletrolitico\", \"id_categoria\": 1, \"unidade_medida\": \"KG\", \"estoque_atual\": 250.0, \"estoque_minimo\": 50.0, \"custo_unitario\": 85.0}"
  • Resposta esperada: 201 Created.
  1. Teste 2: Registrar Movimentação de Entrada (POST /api/movimentos/)
curl -X POST "http://localhost:5000/api/movimentos/" -H "Content-Type: application/json" -d "{\"id_materia\": 1, \"id_fornecedor\": 1, \"tipo_movimento\": \"ENTRADA\", \"quantidade\": 50.0, \"valor_unitario\": 25.0}"
  • Resposta esperada: 201 Created com "novo_saldo": 200.0.

🎨 ETAPA 6: Interface Web com Jinja2 e Bootstrap 5

6.1. Criar app/templates/base.html

<!DOCTYPE html>
<html lang="pt-br">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>StockFlow — Almoxarifado Industrial</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
</head>
<body class="bg-light">
    <nav class="navbar navbar-expand-lg navbar-dark bg-primary">
        <div class="container">
            <a class="navbar-brand fw-bold" href="/"><i class="bi bi-boxes"></i> StockFlow PCP</a>
            <a href="/docs" target="_blank" class="btn btn-outline-light btn-sm"><i class="bi bi-code-square"></i> Swagger API</a>
        </div>
    </nav>
    <div class="container py-4">
        {% block content %}{% endblock %}
    </div>
</body>
</html>

6.2. Criar app/templates/index.html

{% extends "base.html" %}
{% block content %}
<div class="d-flex justify-content-between align-items-center mb-4">
    <h2><i class="bi bi-box-seam"></i> Posição de Estoque e Ponto de Reposição</h2>
    <span class="badge bg-dark fs-6">{{ insumos|length }} Itens Controlados</span>
</div>

<div class="card shadow-sm border-0">
    <div class="card-body p-0">
        <div class="table-responsive">
            <table class="table table-hover table-striped align-middle mb-0">
                <thead class="table-dark">
                    <tr>
                        <th>SKU</th>
                        <th>Descrição da Matéria-Prima</th>
                        <th>Unidade</th>
                        <th>Saldo Atual</th>
                        <th>Estoque Mínimo</th>
                        <th>Custo Unit.</th>
                        <th>Status</th>
                    </tr>
                </thead>
                <tbody>
                    {% for item in insumos %}
                    <tr>
                        <td class="fw-bold">{{ item.codigo_sku }}</td>
                        <td>{{ item.nome }}</td>
                        <td><span class="badge bg-secondary">{{ item.unidade_medida }}</span></td>
                        <td><strong>{{ item.estoque_atual }}</strong></td>
                        <td>{{ item.estoque_minimo }}</td>
                        <td>R$ {{ "%.2f"|format(item.custo_unitario) }}</td>
                        <td>
                            {% if item.estoque_atual < item.estoque_minimo %}
                                <span class="badge bg-danger"><i class="bi bi-exclamation-triangle-fill"></i> CRÍTICO (COMPRAR)</span>
                            {% else %}
                                <span class="badge bg-success"><i class="bi bi-check-circle"></i> REGULAR</span>
                            {% endif %}
                        </td>
                    </tr>
                    {% endfor %}
                </tbody>
            </table>
        </div>
    </div>
</div>
{% endblock %}

🧪 ETAPA 7: Suíte de Testes Automatizados com Pytest (tests/test_stockflow.py)

Crie tests/test_stockflow.py:

import pytest
from app.main import app
from app.core.database import Base, engine, SessionLocal
from app.models.estoque_models import CategoriaInsumo

@pytest.fixture
def client():
    app.config["TESTING"] = True
    with app.test_client() as client:
        yield client

def test_fluxo_estoque(client):
    # Setup de Categoria
    with SessionLocal() as db:
        if not db.get(CategoriaInsumo, 1):
            cat = CategoriaInsumo(nome="Polímeros")
            db.add(cat)
            db.commit()

    # 1. Cadastrar Matéria-Prima
    payload = {
        "codigo_sku": "MP-POL-01",
        "nome": "Polietileno de Alta Densidade",
        "id_categoria": 1,
        "unidade_medida": "KG",
        "estoque_atual": 100.0,
        "estoque_minimo": 30.0,
        "custo_unitario": 18.50
    }
    res = client.post("/api/materias-primas/", json=payload)
    assert res.status_code == 201
    id_mat = res.get_json()["id_materia"]

    # 2. Registrar Entrada
    mov_payload = {
        "id_materia": id_mat,
        "tipo_movimento": "ENTRADA",
        "quantidade": 50.0,
        "valor_unitario": 18.50
    }
    res_mov = client.post("/api/movimentos/", json=mov_payload)
    assert res_mov.status_code == 201
    assert res_mov.get_json()["novo_saldo"] == 150.0

Execute no terminal:

pytest -v

📊 ETAPA 8: Bateria de 10 Desafios de SQL Corporativo (StockFlow)

🎯 Desafio 01: Listar matérias-primas com estoque atual abaixo do estoque mínimo de segurança.

SELECT id_materia, codigo_sku, nome, estoque_atual, estoque_minimo FROM materias_primas WHERE estoque_atual < estoque_minimo ORDER BY (estoque_minimo - estoque_atual) DESC;

🔍 Explicação Técnica: Calcula o déficit de estoque e ordena pelos itens mais críticos para compra emergencial.

🎯 Desafio 02: Consultar ordens de produção abertas no mês corrente.

SELECT id_ordem, numero_lote, data_inicio, status FROM ordens_producao WHERE status = 'EM_PROCESSO' ORDER BY data_inicio ASC LIMIT 10;

🔍 Explicação Técnica: Filtra ordens em andamento no chão de fábrica limitando a 10 resultados para paginação.

🎯 Desafio 03: Calcular o valor financeiro total imobilizado em estoque por matéria-prima.

SELECT id_materia, nome, (estoque_atual * custo_unitario) AS valor_total_estoque FROM materias_primas ORDER BY valor_total_estoque DESC;

🔍 Explicação Técnica: Expressão aritmética calculando o capital de giro imobilizado em cada insumo industrial.

🎯 Desafio 04: Somar o valor total de compras realizadas por fornecedor.

SELECT f.razao_social, SUM(m.quantidade * m.valor_unitario) AS total_comprado FROM movimentos_estoque m INNER JOIN fornecedores f ON m.id_fornecedor = f.id_fornecedor WHERE m.tipo_movimento = 'ENTRADA' GROUP BY f.razao_social ORDER BY total_comprado DESC;

🔍 Explicação Técnica: Agregação com SUM() filtrando movimentações de entrada para compor o ranking de compras.

🎯 Desafio 05: Listar fornecedores com mais de R$ 50.000 em compras acumuladas.

SELECT f.razao_social, SUM(m.quantidade * m.valor_unitario) AS total FROM movimentos_estoque m INNER JOIN fornecedores f ON m.id_fornecedor = f.id_fornecedor WHERE m.tipo_movimento = 'ENTRADA' GROUP BY f.razao_social HAVING SUM(m.quantidade * m.valor_unitario) > 50000;

🔍 Explicação Técnica: Filtro agregado com HAVING para identificar fornecedores estratégicos Classe A.

🎯 Desafio 06: Relatório de insumos com nome da categoria e unidade de medida.

SELECT m.codigo_sku, m.nome, c.nome AS categoria, m.unidade_medida FROM materias_primas m INNER JOIN categorias_insumo c ON m.id_categoria = c.id_categoria;

🔍 Explicação Técnica: Junção relacional INNER JOIN trazendo a taxonomia dos insumos.

🎯 Desafio 07: Extrato detalhado de consumo de matérias-primas por Ordem de Produção.

SELECT op.numero_lote, mp.nome AS insumo, iop.quantidade_planejada, iop.quantidade_consumida FROM itens_ordem_producao iop INNER JOIN ordens_producao op ON iop.id_ordem = op.id_ordem INNER JOIN materias_primas mp ON iop.id_materia = mp.id_materia WHERE op.status = 'CONCLUIDA';

🔍 Explicação Técnica: Multi-JOIN para apuração de custo e perdas no processo produtivo.

🎯 Desafio 08: Identificar matérias-primas que nunca tiveram nenhuma movimentação de estoque registrada.

SELECT mp.id_materia, mp.nome FROM materias_primas mp LEFT JOIN movimentos_estoque me ON mp.id_materia = me.id_materia WHERE me.id_movimento IS NULL;

🔍 Explicação Técnica: LEFT JOIN para auditoria de itens sem giro (estoque morto).

🎯 Desafio 09: Buscar ordens de produção que consumiram insumos da categoria 'Químicos' (Subquery com EXISTS).

SELECT op.id_ordem, op.numero_lote FROM ordens_producao op WHERE EXISTS (SELECT 1 FROM itens_ordem_producao iop INNER JOIN materias_primas mp ON iop.id_materia = mp.id_materia INNER JOIN categorias_insumo ci ON mp.id_categoria = ci.id_categoria WHERE iop.id_ordem = op.id_ordem AND ci.nome = 'Químicos');

🔍 Explicação Técnica: Subconsulta correlacionada de alta performance com EXISTS para conformidade ambiental.

🎯 Desafio 10: Dar baixa automática no estoque de matéria-prima após conclusão de lote.

UPDATE materias_primas SET estoque_atual = estoque_atual - 25.5 WHERE id_materia = 4 AND estoque_atual >= 25.5 RETURNING id_materia, estoque_atual;

🔍 Explicação Técnica: DML seguro com checagem de saldo mínimo contra estoque negativo e retorno imediato.


🩺 ETAPA 9: Troubleshooting no Windows & Checklist de Entrega

🛠️ Resolução Rápida de Erros Frequentes:

  1. Erro Saldo insuficiente em estoque (400 Bad Request):
    Causa: Você tentou registrar uma movimentação de saída maior do que a quantidade disponível no banco.
  2. Erro no such table: materias_primas:
    Causa: A inicialização do banco não ocorreu. Certifique-se de chamar Base.metadata.create_all(bind=engine) no main.py.
  3. Porta 5000 já em uso:
    Execute: python app/main.py --port 5001 ou encerre o processo anterior no Gerenciador de Tarefas.

✅ Checklist de Conclusão do Aluno:

  • Ambiente virtual venv configurado e ativado no Windows.
  • Banco de dados SQLite criado com as 6 tabelas relacionais.
  • Endpoints de fornecedores, matérias-primas e movimentações testados localmente.
  • Interface visual listando os itens em estoque com indicador de status crítico (http://localhost:5000).
  • Suíte de testes pytest -v passando com 100% de sucesso.
  • 10 Desafios de SQL executados no DBeaver/pgAdmin.

📦 StockFlow — Guia de Execução e README do Projeto

Setor: Indústria e Produção
Componente: Atividades de Projetos II / III
Classificação: 🔴 Nível 2: Intermediário / Avançado (5 a 7 Tabelas)
Repositório Template: https://github.com/fatec-gti/pi_02_stockflow


🚀 1. Como Executar o Projeto Localmente

Passo 1: Clonar o Repositório e Abrir no VS Code

git clone https://github.com/fatec-gti/pi_02_stockflow.git
code pi_02_stockflow

Passo 2: Criar o Ambiente Virtual e Instalar Dependências

python -m venv venv
# Windows (PowerShell):
.\venv\Scripts\Activate.ps1
# Linux/Mac:
source venv/bin/activate

pip install flask sqlalchemy jinja2 psycopg2-binary

Passo 3: Executar a Aplicação com SQLite (Zero Configuração)

python main.py

Acesse no navegador:

  • 🌐 Interface Web & API REST: http://localhost:5000

🐳 2. Executando com PostgreSQL no Docker Compose

Para subir o banco de dados oficial de produção:

docker compose up -d

Edite o arquivo .env para apontar para o PostgreSQL:

DATABASE_URL=postgresql://postgres:senha@localhost:5432/pi_02_stockflow_db

👥 3. Equipe de Desenvolvimento (Template de Entrega)

  • Analista de Sistemas / PO: [Nome do Estudante]
  • Engenheiro de Software: [Nome do Estudante]
  • DBA / Modelador de Dados: [Nome do Estudante]
  • Desenvolvedor Full-stack: [Nome do Estudante]

🧾 PROJETO 03: PDVLITE (FRENTE DE CAIXA & VAREJO)

📘 TUTORIAL AUTOGUIADO PASSO A PASSO — DO ZERO AO DEPLOY NO WINDOWS 10/11

Setor Econômico: Comércio e Varejo
Domínio: Ponto de Venda (PDV), Emissão de Vendas de Balcão, Cupons e Formas de Pagamento
Nível de Complexidade: 🟢 Nível 1: Essencial / Básico (5 Tabelas Relacionais com Carrinho de Itens)
Ambiente de Desenvolvimento: Windows 10/11 (PT-BR) + VS Code + Python 3.11+ (Venv)
Stack Principal: Flask 3.x + SQLAlchemy 2.0 + Pydantic v2 + SQLite (Dev) / PostgreSQL (Docker) + Jinja2/Bootstrap 5 + Pytest


flowchart LR
    A["⚙️ 1. Setup Windows/Venv"] --> B["🛢️ 2. Dual-Database"]
    B --> C["🧱 3. Modelos ORM (5 Tabelas)"]
    C --> D["📋 4. Schemas Pydantic"]
    D --> E["🌐 5. Endpoints REST (Flask)"]
    E --> F["🎨 6. Interface Web Jinja2"]
    F --> G["🧪 7. Testes Pytest (100% Verde)"]
    G --> H["📊 8. 10 Desafios SQL"]
    H --> I["🩺 9. Checklist & Troubleshooting"]

    style A fill:#e3f2fd,stroke:#1565c0
    style B fill:#fff8e1,stroke:#f57f17
    style C fill:#f3e5f5,stroke:#7b1fa2
    style D fill:#ede7f6,stroke:#5e35b1
    style E fill:#e0f2fe,stroke:#0284c7
    style F fill:#fce4ec,stroke:#c2185b
    style G fill:#dcfce7,stroke:#16a34a
    style H fill:#fef3c7,stroke:#d97706
    style I fill:#fee2e2,stroke:#ef4444

📂 0. Estrutura Completa de Pastas e Arquivos no VS Code

Crie exatamente a seguinte estrutura de diretórios no seu computador:

pi_03_pdvlite/
├── app/
│   ├── __init__.py
│   ├── main.py
│   ├── core/
│   │   ├── __init__.py
│   │   └── database.py
│   ├── models/
│   │   ├── __init__.py
│   │   └── pdv_models.py
│   ├── schemas/
│   │   ├── __init__.py
│   │   └── pdv_schemas.py
│   ├── routers/
│   │   ├── __init__.py
│   │   └── pdv_router.py
│   └── templates/
│       ├── base.html
│       └── index.html
├── tests/
│   ├── __init__.py
│   └── test_pdvlite.py
├── .env
├── requirements.txt
└── README.md

⚙️ ETAPA 1: Preparação do Ambiente no Windows 10/11

Abra o VS Code na pasta pi_03_pdvlite e abra o terminal integrado (Ctrl + `).

1.1. Criar e Ativar o Ambiente Virtual (venv)

# 1. Criar o ambiente virtual:
python -m venv venv

# 2. Ativar no Windows PowerShell:
.\venv\Scripts\Activate.ps1

1.2. Criar o Arquivo de Dependências (requirements.txt)

Crie o arquivo requirements.txt:

flask==3.0.3
sqlalchemy==2.0.35
pydantic==2.9.0
jinja2==3.1.4
pytest==8.3.0

Instale as dependências:

pip install -r requirements.txt

1.3. Criar o Arquivo de Variáveis de Ambiente (.env)

DATABASE_URL=sqlite:///./pdvlite.db

🛢️ ETAPA 2: Camada de Conexão Dual-Database (app/core/database.py)

Crie app/core/database.py:

import os
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, DeclarativeBase

DATABASE_URL = os.getenv("DATABASE_URL", "sqlite:///./pdvlite.db")
connect_args = {"check_same_thread": False} if DATABASE_URL.startswith("sqlite") else {}

engine = create_engine(DATABASE_URL, connect_args=connect_args, echo=False)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

class Base(DeclarativeBase):
    pass

🧱 ETAPA 3: Modelagem Declarativa ORM (app/models/pdv_models.py)

Crie app/models/pdv_models.py com as 5 entidades relacionais:

from datetime import datetime
from decimal import Decimal
from typing import List
from sqlalchemy import String, Integer, DateTime, Numeric, Boolean, ForeignKey
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.core.database import Base

class Categoria(Base):
    __tablename__ = "categorias"

    id_categoria: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    nome: Mapped[str] = mapped_column(String(50), nullable=False)

    produtos: Mapped[List["Produto"]] = relationship(back_populates="categoria")

class FormaPagamento(Base):
    __tablename__ = "formas_pagamento"

    id_forma_pagto: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    nome: Mapped[str] = mapped_column(String(40), nullable=False)

    vendas: Mapped[List["Venda"]] = relationship(back_populates="forma_pagamento")

class Produto(Base):
    __tablename__ = "produtos"

    id_produto: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    codigo_barras: Mapped[str] = mapped_column(String(20), unique=True, nullable=False)
    descricao: Mapped[str] = mapped_column(String(100), nullable=False)
    id_categoria: Mapped[int] = mapped_column(ForeignKey("categorias.id_categoria"), nullable=False)
    preco_venda: Mapped[Decimal] = mapped_column(Numeric(10, 2), nullable=False)
    ativo: Mapped[bool] = mapped_column(Boolean, default=True)

    categoria: Mapped["Categoria"] = relationship(back_populates="produtos")
    itens_venda: Mapped[List["ItemVenda"]] = relationship(back_populates="produto")

class Venda(Base):
    __tablename__ = "vendas"

    id_venda: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    id_forma_pagto: Mapped[int] = mapped_column(ForeignKey("formas_pagamento.id_forma_pagto"), nullable=False)
    data_hora: Mapped[datetime] = mapped_column(DateTime, default=datetime.now)
    valor_total: Mapped[Decimal] = mapped_column(Numeric(10, 2), default=Decimal("0.00"))
    status: Mapped[str] = mapped_column(String(20), default="CONCLUIDA")

    forma_pagamento: Mapped["FormaPagamento"] = relationship(back_populates="vendas")
    itens: Mapped[List["ItemVenda"]] = relationship(back_populates="venda", cascade="all, delete-orphan")

class ItemVenda(Base):
    __tablename__ = "itens_venda"

    id_item: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    id_venda: Mapped[int] = mapped_column(ForeignKey("vendas.id_venda"), nullable=False)
    id_produto: Mapped[int] = mapped_column(ForeignKey("produtos.id_produto"), nullable=False)
    quantidade: Mapped[int] = mapped_column(Integer, nullable=False)
    preco_unitario: Mapped[Decimal] = mapped_column(Numeric(10, 2), nullable=False)

    venda: Mapped["Venda"] = relationship(back_populates="itens")
    produto: Mapped["Produto"] = relationship(back_populates="itens_venda")

📋 ETAPA 4: Schemas de Validação Pydantic v2 (app/schemas/pdv_schemas.py)

Crie app/schemas/pdv_schemas.py:

from decimal import Decimal
from typing import List
from pydantic import BaseModel, Field

class ProdutoCreate(BaseModel):
    codigo_barras: str = Field(..., min_length=3, max_length=20)
    descricao: str = Field(..., min_length=3, max_length=100)
    id_categoria: int = Field(..., gt=0)
    preco_venda: Decimal = Field(..., gt=0)

class ItemVendaInput(BaseModel):
    id_produto: int = Field(..., gt=0)
    quantidade: int = Field(..., gt=0)

class VendaCreate(BaseModel):
    id_forma_pagto: int = Field(..., gt=0)
    itens: List[ItemVendaInput]

🌐 ETAPA 5: Endpoints REST (app/routers/pdv_router.py) & Inicialização (app/main.py)

5.1. Criar app/routers/pdv_router.py

from decimal import Decimal
from flask import Blueprint, request, jsonify
from sqlalchemy.orm import Session
from sqlalchemy import select
from app.core.database import SessionLocal
from app.models.pdv_models import Categoria, FormaPagamento, Produto, Venda, ItemVenda
from app.schemas.pdv_schemas import ProdutoCreate, VendaCreate

router = Blueprint("pdv", __name__, url_prefix="/api")

# --- PRODUTOS ---
@router.post("/produtos/")
def cadastrar_produto():
    dados = request.get_json()
    if not dados:
        return jsonify({"erro": "Payload ausente"}), 400
    try:
        payload = ProdutoCreate(**dados)
    except Exception as ex:
        return jsonify({"erro": str(ex)}), 400

    with SessionLocal() as db:
        if not db.get(Categoria, payload.id_categoria):
            return jsonify({"erro": "Categoria inexistente."}), 404
        if db.scalar(select(Produto).where(Produto.codigo_barras == payload.codigo_barras)):
            return jsonify({"erro": "Código de barras já cadastrado."}), 400
        
        novo = Produto(**payload.model_dump())
        db.add(novo)
        db.commit()
        db.refresh(novo)
        return jsonify({
            "id_produto": novo.id_produto,
            "codigo_barras": novo.codigo_barras,
            "descricao": novo.descricao,
            "preco_venda": float(novo.preco_venda),
            "ativo": novo.ativo
        }), 201

@router.get("/produtos/")
def listar_produtos():
    with SessionLocal() as db:
        produtos = db.scalars(select(Produto).where(Produto.ativo == True).order_by(Produto.descricao.asc())).all()
        return jsonify([{
            "id_produto": p.id_produto,
            "codigo_barras": p.codigo_barras,
            "descricao": p.descricao,
            "preco_venda": float(p.preco_venda),
            "ativo": p.ativo
        } for p in produtos]), 200

# --- EMISSÃO DE VENDA ---
@router.post("/vendas/")
def emitir_venda():
    dados = request.get_json()
    if not dados:
        return jsonify({"erro": "Payload ausente"}), 400
    try:
        payload = VendaCreate(**dados)
    except Exception as ex:
        return jsonify({"erro": str(ex)}), 400

    with SessionLocal() as db:
        if not db.get(FormaPagamento, payload.id_forma_pagto):
            return jsonify({"erro": "Forma de pagamento inválida."}), 404
        if not payload.itens:
            return jsonify({"erro": "O carrinho de venda não pode estar vazio."}), 400

        total_calculado = Decimal("0.00")
        itens_para_salvar = []

        for item in payload.itens:
            prod = db.get(Produto, item.id_produto)
            if not prod or not prod.ativo:
                return jsonify({"erro": f"Produto ID {item.id_produto} não disponível."}), 404
            
            subtotal = prod.preco_venda * item.quantidade
            total_calculado += subtotal
            itens_para_salvar.append(ItemVenda(
                id_produto=prod.id_produto,
                quantidade=item.quantidade,
                preco_unitario=prod.preco_venda
            ))

        nova_venda = Venda(
            id_forma_pagto=payload.id_forma_pagto,
            valor_total=total_calculado,
            status="CONCLUIDA",
            itens=itens_para_salvar
        )
        db.add(nova_venda)
        db.commit()
        db.refresh(nova_venda)
        return jsonify({
            "id_venda": nova_venda.id_venda,
            "valor_total": float(nova_venda.valor_total),
            "status": nova_venda.status
        }), 201

5.2. Criar app/main.py

from pathlib import Path
from decimal import Decimal
from flask import Flask, render_template
from sqlalchemy import select
from app.core.database import engine, Base, SessionLocal
from app.models.pdv_models import Categoria, FormaPagamento, Produto, Venda, ItemVenda
from app.routers import pdv_router

Base.metadata.create_all(bind=engine)

BASE_DIR = Path(__file__).resolve().parent
app = Flask(__name__, template_folder=str(BASE_DIR / "templates"))
app.register_blueprint(pdv_router.router)

def seed_dados_iniciais():
    with SessionLocal() as db:
        if not db.scalar(select(Categoria)):
            c1 = Categoria(nome="Bebidas")
            c2 = Categoria(nome="Alimentos")
            c3 = Categoria(nome="Limpeza")
            db.add_all([c1, c2, c3])
            db.commit()

            fp1 = FormaPagamento(nome="Cartão Crédito")
            fp2 = FormaPagamento(nome="PIX")
            fp3 = FormaPagamento(nome="Dinheiro")
            db.add_all([fp1, fp2, fp3])
            db.commit()

            p1 = Produto(codigo_barras="789001", descricao="Refrigerante 2L", id_categoria=c1.id_categoria, preco_venda=Decimal("12.50"), ativo=True)
            p2 = Produto(codigo_barras="789002", descricao="Biscoito Recheado", id_categoria=c2.id_categoria, preco_venda=Decimal("4.50"), ativo=True)
            p3 = Produto(codigo_barras="789003", descricao="Detergente Neutro", id_categoria=c3.id_categoria, preco_venda=Decimal("3.20"), ativo=True)
            p4 = Produto(codigo_barras="789004", descricao="Suco Natural 1L", id_categoria=c1.id_categoria, preco_venda=Decimal("15.00"), ativo=True)
            p5 = Produto(codigo_barras="789005", descricao="Café Especial 500g", id_categoria=c2.id_categoria, preco_venda=Decimal("28.00"), ativo=True)
            db.add_all([p1, p2, p3, p4, p5])
            db.commit()

            v1 = Venda(id_forma_pagto=fp2.id_forma_pagto, valor_total=Decimal("40.00"), status="CONCLUIDA")
            db.add(v1)
            db.commit()

            iv1 = ItemVenda(id_venda=v1.id_venda, id_produto=p1.id_produto, quantidade=2, preco_unitario=Decimal("12.50"))
            iv2 = ItemVenda(id_venda=v1.id_venda, id_produto=p4.id_produto, quantidade=1, preco_unitario=Decimal("15.00"))
            db.add_all([iv1, iv2])
            db.commit()

seed_dados_iniciais()

@app.route("/")
def painel_pdv():
    with SessionLocal() as db:
        produtos = db.scalars(select(Produto).where(Produto.ativo == True)).all()
        vendas = db.scalars(select(Venda).order_by(Venda.id_venda.desc())).all()
        return render_template("index.html", produtos=produtos, vendas=vendas)

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5000, debug=True)

🔍 Como Executar e Testar a Aplicação

  1. Iniciar Servidor Web e API:
python -m app.main

O servidor estará ativo em: http://localhost:5000

  1. Testar Emissão de Venda com Múltiplos Itens via cURL / PowerShell (POST /api/vendas/):
curl -X POST http://localhost:5000/api/vendas/ `
  -H "Content-Type: application/json" `
  -d '{"id_forma_pagto": 2, "itens": [{"id_produto": 1, "quantidade": 2}, {"id_produto": 2, "quantidade": 3}]}'
  • Resposta esperada: 201 Created com o cálculo do valor total correto: (2 * 12.50) + (3 * 4.50) = 38.50.
  1. Acessar Interface Web: Abra o navegador em: http://localhost:5000 para visualizar o catálogo do PDV e os cupons emitidos.

🎨 ETAPA 6: Interface Web com Jinja2 e Bootstrap 5

6.1. Criar app/templates/base.html

<!DOCTYPE html>
<html lang="pt-br">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>PDVLite — Frente de Caixa</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
</head>
<body class="bg-light">
    <nav class="navbar navbar-expand-lg navbar-dark bg-success">
        <div class="container">
            <a class="navbar-brand fw-bold" href="/"><i class="bi bi-cart4"></i> PDVLite Frente de Caixa</a>
            <span class="badge bg-light text-dark">Flask 3.x & SQLAlchemy 2.0</span>
        </div>
    </nav>
    <div class="container py-4">
        {% block content %}{% endblock %}
    </div>
</body>
</html>

6.2. Criar app/templates/index.html

{% extends "base.html" %}
{% block content %}
<div class="row">
    <div class="col-md-7">
        <div class="card shadow-sm border-0 mb-4">
            <div class="card-header bg-dark text-white fw-bold">
                <i class="bi bi-tag"></i> Catálogo Rápido de Produtos
            </div>
            <div class="card-body p-0">
                <table class="table table-hover align-middle mb-0">
                    <thead class="table-light">
                        <tr>
                            <th>Cód. Barras</th>
                            <th>Descrição</th>
                            <th>Preço Unit.</th>
                            <th>Status</th>
                        </tr>
                    </thead>
                    <tbody>
                        {% for p in produtos %}
                        <tr>
                            <td><code>{{ p.codigo_barras }}</code></td>
                            <td class="fw-bold">{{ p.descricao }}</td>
                            <td>R$ {{ "%.2f"|format(p.preco_venda) }}</td>
                            <td><span class="badge bg-success">Disponível</span></td>
                        </tr>
                        {% endfor %}
                    </tbody>
                </table>
            </div>
        </div>
    </div>

    <div class="col-md-5">
        <div class="card shadow-sm border-0">
            <div class="card-header bg-primary text-white fw-bold">
                <i class="bi bi-receipt"></i> Histórico de Cupons Emitidos
            </div>
            <div class="card-body p-0">
                <ul class="list-group list-group-flush">
                    {% for v in vendas %}
                    <li class="list-group-item d-flex justify-content-between align-items-center">
                        <div>
                            <strong>Cupom #{{ v.id_venda }}</strong><br>
                            <small class="text-muted">{{ v.status }}</small>
                        </div>
                        <span class="badge bg-success fs-6">R$ {{ "%.2f"|format(v.valor_total) }}</span>
                    </li>
                    {% endfor %}
                </ul>
            </div>
        </div>
    </div>
</div>
{% endblock %}

🧪 ETAPA 7: Suíte de Testes Automatizados com Pytest (tests/test_pdvlite.py)

Crie tests/test_pdvlite.py:

import pytest
from app.main import app

@pytest.fixture
def client():
    app.config["TESTING"] = True
    with app.test_client() as client:
        yield client

def test_painel_home_status_code(client):
    res = client.get("/")
    assert res.status_code == 200
    assert b"PDVLite" in res.data

def test_emissao_venda_pdv(client):
    payload = {
        "id_forma_pagto": 2,
        "itens": [
            {"id_produto": 1, "quantidade": 2},
            {"id_produto": 2, "quantidade": 1}
        ]
    }
    res = client.post("/api/vendas/", json=payload)
    assert res.status_code == 201
    dados = res.get_json()
    assert "id_venda" in dados
    # (2 * 12.50) + (1 * 4.50) = 29.50
    assert float(dados["valor_total"]) == 29.5

Execute no terminal:

pytest -v

📊 ETAPA 8: Bateria de 10 Desafios de SQL Corporativo (PDVLite)

🎯 Desafio 01: Consultar produtos ativos com preço de venda inferior a R$ 20,00.

SELECT id_produto, codigo_barras, descricao, preco_venda FROM produtos WHERE ativo = 1 AND preco_venda < 20.00 ORDER BY preco_venda ASC;

🔍 Explicação Técnica: Filtro simples por predicado booleano e valor monetário.

🎯 Desafio 02: Listar as 10 últimas vendas concluídas no PDV.

SELECT id_venda, data_hora, valor_total, id_forma_pagto FROM vendas WHERE status = 'CONCLUIDA' ORDER BY data_hora DESC LIMIT 10;

🔍 Explicação Técnica: Ordenação cronológica decrescente para monitoramento em tempo real do operador.

🎯 Desafio 03: Totalizar vendas e faturamento consolidado por forma de pagamento.

SELECT id_forma_pagto, COUNT(*) AS qtd_vendas, SUM(valor_total) AS total_arrecadado FROM vendas WHERE status = 'CONCLUIDA' GROUP BY id_forma_pagto ORDER BY total_arrecadado DESC;

🔍 Explicação Técnica: Agrupamento por meio de pagamento para fechamento de caixa e conciliação bancária.

🎯 Desafio 04: Ranking dos produtos mais vendidos por faturamento total gerado.

SELECT p.descricao, SUM(iv.quantidade) AS total_unidades_vendidas, SUM(iv.quantidade * iv.preco_unitario) AS receita_total FROM itens_venda iv INNER JOIN produtos p ON iv.id_produto = p.id_produto GROUP BY p.descricao ORDER BY total_unidades_vendidas DESC;

🔍 Explicação Técnica: Agregação relacional unindo os itens de cupom com os cadastros de produto.

🎯 Desafio 05: Filtrar produtos que venderam mais de 100 unidades acumuladas.

SELECT p.descricao, SUM(iv.quantidade) AS unidades FROM itens_venda iv INNER JOIN produtos p ON iv.id_produto = p.id_produto GROUP BY p.descricao HAVING SUM(iv.quantidade) > 100;

🔍 Explicação Técnica: Filtro com HAVING identificando itens campeões de saída (High Turnover).

🎯 Desafio 06: Listar catálogo completo de produtos com a descrição da categoria.

SELECT p.codigo_barras, p.descricao, c.nome AS categoria, p.preco_venda FROM produtos p INNER JOIN categorias c ON p.id_categoria = c.id_categoria;

🔍 Explicação Técnica: INNER JOIN para exibição na tela de consulta de preços do caixa.

🎯 Desafio 07: Emitir o espelho completo do cupom de venda #101 com subtotal de cada item.

SELECT v.id_venda, v.data_hora, fp.nome AS forma_pagamento, p.descricao AS produto, iv.quantidade, iv.preco_unitario, (iv.quantidade * iv.preco_unitario) AS subtotal FROM vendas v INNER JOIN formas_pagamento fp ON v.id_forma_pagto = fp.id_forma_pagto INNER JOIN itens_venda iv ON v.id_venda = iv.id_venda INNER JOIN produtos p ON iv.id_produto = p.id_produto WHERE v.id_venda = 101;

🔍 Explicação Técnica: Multi-JOIN unindo 4 tabelas para impressão do cupom do cliente.

🎯 Desafio 08: Identificar produtos cadastrados que nunca foram vendidos.

SELECT p.id_produto, p.descricao FROM produtos p LEFT JOIN itens_venda iv ON p.id_produto = iv.id_produto WHERE iv.id_item IS NULL;

🔍 Explicação Técnica: LEFT JOIN com IS NULL para auditoria de itens encalhados na prateleira.

🎯 Desafio 09: Listar vendas cujo valor total superou o ticket médio geral do caixa.

SELECT id_venda, valor_total FROM vendas WHERE valor_total > (SELECT AVG(valor_total) FROM vendas WHERE status = 'CONCLUIDA');

🔍 Explicação Técnica: Subquery escalar no WHERE comparando cada transação com a média aritmética global.

🎯 Desafio 10: Cancelar cupom de venda por solicitação do gerente.

UPDATE vendas SET status = 'CANCELADA' WHERE id_venda = 101 AND status = 'CONCLUIDA' RETURNING id_venda, status, valor_total;

🔍 Explicação Técnica: DML com RETURNING garantindo estorno imediato com retorno do valor cancelado.


🩺 ETAPA 9: Troubleshooting no Windows & Checklist de Entrega

🛠️ Resolução Rápida de Erros Frequentes:

  1. Porta 5000 já em uso (OSError: [Errno 10048] address already in use):
    Causa: Uma instância anterior do Flask continua rodando em segundo plano.
    Solução: No PowerShell:
    Get-Process -Name python* | Stop-Process -Force
    
  2. Erro 400 Bad Request: O carrinho de venda não pode estar vazio:
    Causa: Você enviou um array itens: [] vazio. Adicione pelo menos um item com id_produto e quantidade.
  3. Erro 404 Not Found: Forma de pagamento inválida:
    Causa: O id_forma_pagto informado não existe na tabela formas_pagamento.

✅ Checklist de Conclusão do Aluno:

  • Ambiente virtual venv configurado e ativado no Windows.
  • Dependências instaladas (flask, sqlalchemy, pydantic, pytest).
  • Banco de dados SQLite criado com as 5 tabelas relacionais.
  • Emissão de venda com múltiplos itens testada via endpoint JSON /api/vendas/.
  • Interface visual listando produtos e histórico de cupons emitidos na porta 5000.
  • Suíte de testes pytest -v passando com 100% de sucesso via client.test_client().
  • 10 Desafios de SQL executados no DBeaver/pgAdmin.

🧾 PDVLite — Guia de Execução e README do Projeto

Setor: Comércio e Varejo
Componente: Atividades de Projetos II / III
Classificação: 🟢 Nível 1: Essencial / Básico (3 a 4 Tabelas)
Repositório Template: https://github.com/fatec-gti/pi_03_pdvlite


🚀 1. Como Executar o Projeto Localmente

Passo 1: Clonar o Repositório e Abrir no VS Code

git clone https://github.com/fatec-gti/pi_03_pdvlite.git
code pi_03_pdvlite

Passo 2: Criar o Ambiente Virtual e Instalar Dependências

python -m venv venv
# Windows (PowerShell):
.\venv\Scripts\Activate.ps1
# Linux/Mac:
source venv/bin/activate

pip install flask sqlalchemy jinja2 psycopg2-binary

Passo 3: Executar a Aplicação com SQLite (Zero Configuração)

python main.py

Acesse no navegador:

  • 🌐 Interface Web & API REST: http://localhost:5000

🐳 2. Executando com PostgreSQL no Docker Compose

Para subir o banco de dados oficial de produção:

docker compose up -d

Edite o arquivo .env para apontar para o PostgreSQL:

DATABASE_URL=postgresql://postgres:senha@localhost:5432/pi_03_pdvlite_db

👥 3. Equipe de Desenvolvimento (Template de Entrega)

  • Analista de Sistemas / PO: [Nome do Estudante]
  • Engenheiro de Software: [Nome do Estudante]
  • DBA / Modelador de Dados: [Nome do Estudante]
  • Desenvolvedor Full-stack: [Nome do Estudante]

🛒 PROJETO 04: SHOPFLOW (E-COMMERCE B2C & PEDIDOS)

📘 TUTORIAL AUTOGUIADO PASSO A PASSO — DO ZERO AO DEPLOY NO WINDOWS 10/11

Setor Econômico: Comércio e Varejo
Domínio: Comércio Eletrônico B2C, Catálogo Vitrine, Gestão de Clientes, Checkout e Rastreio Logístico
Nível de Complexidade: 🔴 Nível 2: Intermediário (5 Tabelas Relacionais com Pedidos N:N e Rastreio)
Ambiente de Desenvolvimento: Windows 10/11 (PT-BR) + VS Code + Python 3.11+ (Venv)
Stack Principal: Flask 3.x + SQLAlchemy 2.0 + Pydantic v2 + SQLite (Dev) / PostgreSQL (Docker) + Jinja2/Bootstrap 5 + Pytest


flowchart LR
    A["⚙️ 1. Setup Windows/Venv"] --> B["🛢️ 2. Dual-Database"]
    B --> C["🧱 3. Modelos ORM (5 Tabelas)"]
    C --> D["📋 4. Schemas Pydantic"]
    D --> E["🌐 5. Endpoints REST & Blueprint"]
    E --> F["🎨 6. Interface Web Jinja2"]
    F --> G["🧪 7. Testes Pytest (100% Verde)"]
    G --> H["📊 8. 10 Desafios SQL"]
    H --> I["🩺 9. Checklist & Troubleshooting"]

    style A fill:#e3f2fd,stroke:#1565c0
    style B fill:#fff8e1,stroke:#f57f17
    style C fill:#f3e5f5,stroke:#7b1fa2
    style D fill:#ede7f6,stroke:#5e35b1
    style E fill:#e0f2fe,stroke:#0284c7
    style F fill:#fce4ec,stroke:#c2185b
    style G fill:#dcfce7,stroke:#16a34a
    style H fill:#fef3c7,stroke:#d97706
    style I fill:#fee2e2,stroke:#ef4444

📂 0. Estrutura Completa de Pastas e Arquivos no VS Code

Crie exatamente a seguinte estrutura de diretórios no seu computador:

pi_04_shopflow/
├── app/
│   ├── __init__.py
│   ├── main.py
│   ├── core/
│   │   ├── __init__.py
│   │   └── database.py
│   ├── models/
│   │   ├── __init__.py
│   │   └── ecommerce_models.py
│   ├── schemas/
│   │   ├── __init__.py
│   │   └── ecommerce_schemas.py
│   ├── routers/
│   │   ├── __init__.py
│   │   └── ecommerce_router.py
│   └── templates/
│       ├── base.html
│       └── index.html
├── tests/
│   ├── __init__.py
│   └── test_shopflow.py
├── .env
├── requirements.txt
└── README.md

⚙️ ETAPA 1: Preparação do Ambiente no Windows 10/11

Abra o VS Code na pasta pi_04_shopflow e abra o terminal integrado (Ctrl + `).

1.1. Criar e Ativar o Ambiente Virtual (venv)

# 1. Criar o ambiente virtual:
python -m venv venv

# 2. Ativar no Windows PowerShell:
.\venv\Scripts\Activate.ps1

1.2. Criar o Arquivo de Dependências (requirements.txt)

Crie o arquivo requirements.txt:

flask==3.0.3
sqlalchemy==2.0.35
pydantic==2.9.0
jinja2==3.1.4
pytest==8.3.0

Instale as dependências:

pip install -r requirements.txt

1.3. Criar o Arquivo de Variáveis de Ambiente (.env)

DATABASE_URL=sqlite:///./shopflow.db
APP_ENV=development

🛢️ ETAPA 2: Configuração Dual-Database (app/core/database.py)

Crie app/core/database.py:

import os
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, DeclarativeBase

DATABASE_URL = os.getenv("DATABASE_URL", "sqlite:///./shopflow.db")
connect_args = {"check_same_thread": False} if DATABASE_URL.startswith("sqlite") else {}

engine = create_engine(DATABASE_URL, connect_args=connect_args, echo=False)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

class Base(DeclarativeBase):
    pass

def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

🧱 ETAPA 3: Modelagem Declarativa ORM (app/models/ecommerce_models.py)

Aqui mapeamos as 5 tabelas relacionais do ShopFlow com clientes, categorias, produtos, pedidos e itens do pedido.

Crie app/models/ecommerce_models.py:

from datetime import date
from typing import Optional, List
from decimal import Decimal
from sqlalchemy import String, Integer, Numeric, Date, ForeignKey, Boolean
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.core.database import Base

class Cliente(Base):
    __tablename__ = "clientes"

    id_cliente: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    nome: Mapped[str] = mapped_column(String(100), nullable=False)
    email: Mapped[str] = mapped_column(String(100), unique=True, nullable=False)
    cidade: Mapped[Optional[str]] = mapped_column(String(60), nullable=True)
    uf: Mapped[Optional[str]] = mapped_column(String(2), nullable=True)
    ativo: Mapped[bool] = mapped_column(Boolean, default=True)

    pedidos: Mapped[List["Pedido"]] = relationship(back_populates="cliente")

class Categoria(Base):
    __tablename__ = "categorias"

    id_categoria: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    nome: Mapped[str] = mapped_column(String(50), nullable=False)

    produtos: Mapped[List["Produto"]] = relationship(back_populates="categoria")

class Produto(Base):
    __tablename__ = "produtos"

    id_produto: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    nome: Mapped[str] = mapped_column(String(100), nullable=False)
    id_categoria: Mapped[int] = mapped_column(ForeignKey("categorias.id_categoria"), nullable=False)
    preco: Mapped[Decimal] = mapped_column(Numeric(10, 2), nullable=False)

    categoria: Mapped["Categoria"] = relationship(back_populates="produtos")
    itens_pedido: Mapped[List["ItemPedido"]] = relationship(back_populates="produto")

class Pedido(Base):
    __tablename__ = "pedidos"

    id_pedido: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    id_cliente: Mapped[int] = mapped_column(ForeignKey("clientes.id_cliente"), nullable=False)
    data_pedido: Mapped[date] = mapped_column(Date, default=date.today)
    valor_total: Mapped[Decimal] = mapped_column(Numeric(10, 2), default=Decimal("0.00"))
    codigo_rastreio: Mapped[Optional[str]] = mapped_column(String(50), nullable=True)
    status: Mapped[str] = mapped_column(String(20), default="PENDENTE") # PENDENTE, PAGO, ENVIADO, ENTREGUE, CANCELADO

    cliente: Mapped["Cliente"] = relationship(back_populates="pedidos")
    itens: Mapped[List["ItemPedido"]] = relationship(back_populates="pedido", cascade="all, delete-orphan")

class ItemPedido(Base):
    __tablename__ = "itens_pedido"

    id_item: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    id_pedido: Mapped[int] = mapped_column(ForeignKey("pedidos.id_pedido"), nullable=False)
    id_produto: Mapped[int] = mapped_column(ForeignKey("produtos.id_produto"), nullable=False)
    quantidade: Mapped[int] = mapped_column(Integer, nullable=False)
    preco_unitario: Mapped[Decimal] = mapped_column(Numeric(10, 2), nullable=False)

    pedido: Mapped["Pedido"] = relationship(back_populates="itens")
    produto: Mapped["Produto"] = relationship(back_populates="itens_pedido")

📋 ETAPA 4: Schemas de Validação Pydantic (app/schemas/ecommerce_schemas.py)

Crie app/schemas/ecommerce_schemas.py:

from decimal import Decimal
from typing import List, Optional
from pydantic import BaseModel, Field, EmailStr

# --- CLIENTE ---
class ClienteCreate(BaseModel):
    nome: str = Field(..., min_length=3, max_length=100)
    email: EmailStr
    cidade: str = Field(..., min_length=2, max_length=60)
    uf: str = Field(..., min_length=2, max_length=2)

class ClienteResponse(ClienteCreate):
    id_cliente: int
    ativo: bool
    class Config:
        from_attributes = True

# --- PRODUTO ---
class ProdutoCreate(BaseModel):
    nome: str = Field(..., min_length=3, max_length=100)
    id_categoria: int = Field(..., gt=0)
    preco: Decimal = Field(..., gt=0)

class ProdutoResponse(ProdutoCreate):
    id_produto: int
    class Config:
        from_attributes = True

# --- ITEM DE CHECKOUT ---
class ItemCheckout(BaseModel):
    id_produto: int = Field(..., gt=0)
    quantidade: int = Field(..., gt=0)

# --- PEDIDO COMPLETO ---
class PedidoCreate(BaseModel):
    id_cliente: int = Field(..., gt=0)
    itens: List[ItemCheckout]

class PedidoResponse(BaseModel):
    id_pedido: int
    id_cliente: int
    valor_total: Decimal
    status: str
    codigo_rastreio: Optional[str] = None
    class Config:
        from_attributes = True

🌐 ETAPA 5: Endpoints REST (app/routers/ecommerce_router.py) & Inicialização (app/main.py)

5.1. Criar app/routers/ecommerce_router.py

from decimal import Decimal
from datetime import date
from flask import Blueprint, request, jsonify
from sqlalchemy import select
from app.core.database import SessionLocal
from app.models.ecommerce_models import Cliente, Categoria, Produto, Pedido, ItemPedido
from app.schemas.ecommerce_schemas import ClienteCreate, ProdutoCreate, PedidoCreate

router = Blueprint("ecommerce", __name__, url_prefix="/api")

# --- CLIENTES ---
@router.route("/clientes/", methods=["POST"])
def cadastrar_cliente():
    dados = request.get_json() or {}
    try:
        dto = ClienteCreate(**dados)
    except Exception as e:
        return jsonify({"erro": str(e)}), 400

    with SessionLocal() as db:
        if db.scalar(select(Cliente).where(Cliente.email == dto.email)):
            return jsonify({"erro": "E-mail já cadastrado."}), 400
        novo = Cliente(**dto.model_dump())
        db.add(novo)
        db.commit()
        db.refresh(novo)
        return jsonify({
            "id_cliente": novo.id_cliente,
            "nome": novo.nome,
            "email": novo.email,
            "cidade": novo.cidade,
            "uf": novo.uf,
            "ativo": novo.ativo
        }), 201

# --- PRODUTOS ---
@router.route("/produtos/", methods=["POST"])
def cadastrar_produto():
    dados = request.get_json() or {}
    try:
        dto = ProdutoCreate(**dados)
    except Exception as e:
        return jsonify({"erro": str(e)}), 400

    with SessionLocal() as db:
        if not db.get(Categoria, dto.id_categoria):
            return jsonify({"erro": "Categoria inexistente."}), 404
        novo = Produto(**dto.model_dump())
        db.add(novo)
        db.commit()
        db.refresh(novo)
        return jsonify({
            "id_produto": novo.id_produto,
            "nome": novo.nome,
            "id_categoria": novo.id_categoria,
            "preco": float(novo.preco)
        }), 201

@router.route("/produtos/", methods=["GET"])
def listar_produtos():
    with SessionLocal() as db:
        prods = db.scalars(select(Produto).order_by(Produto.nome.asc())).all()
        return jsonify([{
            "id_produto": p.id_produto,
            "nome": p.nome,
            "id_categoria": p.id_categoria,
            "preco": float(p.preco)
        } for p in prods]), 200

# --- CHECKOUT DE PEDIDO ---
@router.route("/pedidos/checkout", methods=["POST"])
def checkout_pedido():
    dados = request.get_json() or {}
    try:
        dto = PedidoCreate(**dados)
    except Exception as e:
        return jsonify({"erro": str(e)}), 400

    with SessionLocal() as db:
        cliente = db.get(Cliente, dto.id_cliente)
        if not cliente or not cliente.ativo:
            return jsonify({"erro": "Cliente inválido ou inativo."}), 404
        if not dto.itens:
            return jsonify({"erro": "O pedido deve conter itens."}), 400

        total_calculado = Decimal("0.00")
        itens_db = []

        for item in dto.itens:
            prod = db.get(Produto, item.id_produto)
            if not prod:
                return jsonify({"erro": f"Produto ID {item.id_produto} não encontrado."}), 404
            
            subtotal = prod.preco * item.quantidade
            total_calculado += subtotal
            itens_db.append(ItemPedido(
                id_produto=prod.id_produto,
                quantidade=item.quantidade,
                preco_unitario=prod.preco
            ))

        novo_pedido = Pedido(
            id_cliente=dto.id_cliente,
            data_pedido=date.today(),
            valor_total=total_calculado,
            status="PENDENTE",
            itens=itens_db
        )
        db.add(novo_pedido)
        db.commit()
        db.refresh(novo_pedido)
        return jsonify({
            "id_pedido": novo_pedido.id_pedido,
            "id_cliente": novo_pedido.id_cliente,
            "valor_total": float(novo_pedido.valor_total),
            "status": novo_pedido.status,
            "codigo_rastreio": novo_pedido.codigo_rastreio
        }), 201

5.2. Criar app/main.py

from pathlib import Path
from decimal import Decimal
from datetime import date
from flask import Flask, render_template
from sqlalchemy import select
from app.core.database import engine, Base, SessionLocal
from app.models.ecommerce_models import Cliente, Categoria, Produto, Pedido, ItemPedido
from app.routers import ecommerce_router

Base.metadata.create_all(bind=engine)

BASE_DIR = Path(__file__).resolve().parent
app = Flask(__name__, template_folder=str(BASE_DIR / "templates"))
app.register_blueprint(ecommerce_router.router)

def seed_dados_iniciais():
    with SessionLocal() as db:
        if not db.scalar(select(Cliente)):
            c1 = Cliente(nome="Maria Santos", email="maria@email.com", cidade="Campinas", uf="SP", ativo=True)
            c2 = Cliente(nome="João Silva", email="joao@email.com", cidade="Curitiba", uf="PR", ativo=True)
            c3 = Cliente(nome="Ana Costa", email="ana@email.com", cidade="Santos", uf="SP", ativo=True)
            c4 = Cliente(nome="Pedro Lima", email="pedro@email.com", cidade="Belo Horizonte", uf="MG", ativo=True)
            db.add_all([c1, c2, c3, c4])
            db.commit()

            cat1 = Categoria(nome="Eletrônicos")
            cat2 = Categoria(nome="Moda")
            cat3 = Categoria(nome="Casa")
            db.add_all([cat1, cat2, cat3])
            db.commit()

            p1 = Produto(nome="Notebook Ultra", id_categoria=cat1.id_categoria, preco=Decimal("4500.00"))
            p2 = Produto(nome="Fone Bluetooth", id_categoria=cat1.id_categoria, preco=Decimal("250.00"))
            p3 = Produto(nome="Camisa Polo", id_categoria=cat2.id_categoria, preco=Decimal("120.00"))
            db.add_all([p1, p2, p3])
            db.commit()

            ped1 = Pedido(id_pedido=501, id_cliente=c1.id_cliente, data_pedido=date(2026, 8, 1), valor_total=Decimal("4750.00"), status="ENTREGUE")
            ped2 = Pedido(id_pedido=502, id_cliente=c2.id_cliente, data_pedido=date(2026, 8, 10), valor_total=Decimal("250.00"), status="PAGO")
            ped3 = Pedido(id_pedido=503, id_cliente=c1.id_cliente, data_pedido=date(2026, 8, 15), valor_total=Decimal("120.00"), status="ENTREGUE")
            db.add_all([ped1, ped2, ped3])
            db.commit()

            ip1 = ItemPedido(id_pedido=ped1.id_pedido, id_produto=p1.id_produto, quantidade=1, preco_unitario=Decimal("4500.00"))
            ip2 = ItemPedido(id_pedido=ped1.id_pedido, id_produto=p2.id_produto, quantidade=1, preco_unitario=Decimal("250.00"))
            ip3 = ItemPedido(id_pedido=ped2.id_pedido, id_produto=p2.id_produto, quantidade=1, preco_unitario=Decimal("250.00"))
            ip4 = ItemPedido(id_pedido=ped3.id_pedido, id_produto=p3.id_produto, quantidade=1, preco_unitario=Decimal("120.00"))
            db.add_all([ip1, ip2, ip3, ip4])
            db.commit()

seed_dados_iniciais()

@app.route("/")
def vitrine_ecommerce():
    with SessionLocal() as db:
        produtos = db.scalars(select(Produto)).all()
        pedidos = db.scalars(select(Pedido).order_by(Pedido.id_pedido.desc())).all()
        return render_template("index.html", produtos=produtos, pedidos=pedidos)

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5000, debug=True)

🔍 Como Executar e Testar a Aplicação

  1. Iniciar Servidor Web e API:
python -m app.main

O servidor estará ativo em: http://localhost:5000

  1. Testar Checkout de Pedido via cURL / PowerShell (POST /api/pedidos/checkout):
curl -X POST http://localhost:5000/api/pedidos/checkout `
  -H "Content-Type: application/json" `
  -d '{"id_cliente": 1, "itens": [{"id_produto": 1, "quantidade": 1}, {"id_produto": 2, "quantidade": 2}]}'
  • Resposta esperada: 201 Created com "valor_total": 5000.0.
  1. Acessar Interface Web: Abra o navegador em: http://localhost:5000 para visualizar a vitrine e o painel de pedidos.

🎨 ETAPA 6: Interface Web com Jinja2 e Bootstrap 5

6.1. Criar app/templates/base.html

<!DOCTYPE html>
<html lang="pt-br">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>ShopFlow — E-commerce B2C</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
</head>
<body class="bg-light">
    <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
        <div class="container">
            <a class="navbar-brand fw-bold" href="/"><i class="bi bi-bag-heart-fill text-danger"></i> ShopFlow Loja Virtual</a>
            <span class="badge bg-secondary">Flask 3.x & SQLAlchemy 2.0</span>
        </div>
    </nav>
    <div class="container py-4">
        {% block content %}{% endblock %}
    </div>
</body>
</html>

6.2. Criar app/templates/index.html

{% extends "base.html" %}
{% block content %}
<div class="d-flex justify-content-between align-items-center mb-4">
    <h2><i class="bi bi-grid-3x3-gap"></i> Vitrine de Produtos em Destaque</h2>
    <span class="badge bg-danger fs-6">{{ produtos|length }} Itens Disponíveis</span>
</div>

<div class="row mb-5">
    {% for p in produtos %}
    <div class="col-md-4 mb-3">
        <div class="card shadow-sm border-0 h-100">
            <div class="card-body text-center">
                <i class="bi bi-laptop display-4 text-secondary"></i>
                <h5 class="card-title mt-3">{{ p.nome }}</h5>
                <h4 class="text-success fw-bold">R$ {{ "%.2f"|format(p.preco) }}</h4>
                <button class="btn btn-dark w-100 mt-2"><i class="bi bi-cart-plus"></i> Comprar Agora</button>
            </div>
        </div>
    </div>
    {% endfor %}
</div>

<h3><i class="bi bi-truck"></i> Painel de Pedidos Recentes</h3>
<div class="card shadow-sm border-0">
    <div class="card-body p-0">
        <table class="table table-hover align-middle mb-0">
            <thead class="table-dark">
                <tr>
                    <th>Pedido #</th>
                    <th>Data</th>
                    <th>Valor Total</th>
                    <th>Rastreio</th>
                    <th>Status</th>
                </tr>
            </thead>
            <tbody>
                {% for ped in pedidos %}
                <tr>
                    <td class="fw-bold">#{{ ped.id_pedido }}</td>
                    <td>{{ ped.data_pedido }}</td>
                    <td class="text-success fw-bold">R$ {{ "%.2f"|format(ped.valor_total) }}</td>
                    <td><code>{{ ped.codigo_rastreio or 'AGUARDANDO ENVIO' }}</code></td>
                    <td><span class="badge bg-primary">{{ ped.status }}</span></td>
                </tr>
                {% endfor %}
            </tbody>
        </table>
    </div>
</div>
{% endblock %}

🧪 ETAPA 7: Suíte de Testes Automatizados com Pytest (tests/test_shopflow.py)

Crie tests/test_shopflow.py:

import pytest
from app.main import app

@pytest.fixture
def client():
    app.config["TESTING"] = True
    with app.test_client() as client:
        yield client

def test_vitrine_home_status_code(client):
    res = client.get("/")
    assert res.status_code == 200
    assert b"ShopFlow" in res.data

def test_checkout_ecommerce(client):
    payload = {
        "id_cliente": 1,
        "itens": [
            {"id_produto": 1, "quantidade": 1},
            {"id_produto": 2, "quantidade": 2}
        ]
    }
    res = client.post("/api/pedidos/checkout", json=payload)
    assert res.status_code == 201
    dados = res.get_json()
    assert "id_pedido" in dados
    # (1 * 4500.00) + (2 * 250.00) = 5000.00
    assert float(dados["valor_total"]) == 5000.0

Execute no terminal:

pytest -v

📊 ETAPA 8: Bateria de 10 Desafios de SQL Corporativo (ShopFlow)

🎯 Desafio 01: Buscar clientes ativos residentes no estado de São Paulo (SP).

SELECT id_cliente, nome, email, cidade, uf FROM clientes WHERE uf = 'SP' AND ativo = 1 ORDER BY nome ASC;

🔍 Explicação Técnica: Segmentação geográfica com filtro por estado e status ativo.

🎯 Desafio 02: Consultar os 10 pedidos mais recentes com status 'ENTREGUE'.

SELECT id_pedido, id_cliente, data_pedido, valor_total FROM pedidos WHERE status = 'ENTREGUE' ORDER BY data_pedido DESC LIMIT 10;

🔍 Explicação Técnica: Monitoramento de entregas bem-sucedidas no e-commerce.

🎯 Desafio 03: Total de faturamento e volume de compras por cliente (LTV).

SELECT c.nome, SUM(p.valor_total) AS total_gasto, COUNT(p.id_pedido) AS total_pedidos FROM pedidos p INNER JOIN clientes c ON p.id_cliente = c.id_cliente WHERE p.status <> 'CANCELADO' GROUP BY c.nome ORDER BY total_gasto DESC;

🔍 Explicação Técnica: Cálculo do Customer Lifetime Value unindo pedidos concluídos.

🎯 Desafio 04: Faturamento acumulado por estado (UF) de entrega.

SELECT c.uf, COUNT(p.id_pedido) AS total_pedidos, SUM(p.valor_total) AS faturamento_uf FROM pedidos p INNER JOIN clientes c ON p.id_cliente = c.id_cliente GROUP BY c.uf ORDER BY faturamento_uf DESC;

🔍 Explicação Técnica: Agrupamento demográfico para planejamento logístico e distribuição de estoques.

🎯 Desafio 05: Filtrar clientes VIP com faturamento superior a R$ 3.000,00.

SELECT c.nome, SUM(p.valor_total) AS faturamento FROM pedidos p INNER JOIN clientes c ON p.id_cliente = c.id_cliente GROUP BY c.nome HAVING SUM(p.valor_total) > 3000.00;

🔍 Explicação Técnica: Filtro pós-agregação com HAVING para segmentação de campanhas de CRM.

🎯 Desafio 06: Listar pedidos com os dados de contato do cliente.

SELECT p.id_pedido, p.data_pedido, c.nome AS cliente, c.email, p.valor_total, p.status FROM pedidos p INNER JOIN clientes c ON p.id_cliente = c.id_cliente;

🔍 Explicação Técnica: INNER JOIN trazendo os dados do titular da compra para emissão de Nota Fiscal.

🎯 Desafio 07: Detalhamento de itens e subtotais do pedido #501.

SELECT p.id_pedido, c.nome AS cliente, pr.nome AS produto, ip.quantidade, ip.preco_unitario, (ip.quantidade * ip.preco_unitario) AS subtotal FROM pedidos p INNER JOIN clientes c ON p.id_cliente = c.id_cliente INNER JOIN itens_pedido ip ON p.id_pedido = ip.id_pedido INNER JOIN produtos pr ON ip.id_produto = pr.id_produto WHERE p.id_pedido = 501;

🔍 Explicação Técnica: Multi-JOIN unindo 4 tabelas para compor o espelho do pedido no painel do usuário.

🎯 Desafio 08: Identificar produtos do catálogo que nunca foram comprados.

SELECT pr.id_produto, pr.nome FROM produtos pr LEFT JOIN itens_pedido ip ON pr.id_produto = ip.id_produto WHERE ip.id_item IS NULL;

🔍 Explicação Técnica: LEFT JOIN identificando produtos com taxa zero de conversão.

🎯 Desafio 09: Listar clientes que já compraram produtos da categoria 'Eletrônicos' (Subquery com IN).

SELECT id_cliente, nome FROM clientes WHERE id_cliente IN (SELECT p.id_cliente FROM pedidos p INNER JOIN itens_pedido ip ON p.id_pedido = ip.id_pedido INNER JOIN produtos pr ON ip.id_produto = pr.id_produto INNER JOIN categorias cat ON pr.id_categoria = cat.id_categoria WHERE cat.nome = 'Eletrônicos');

🔍 Explicação Técnica: Subquery aninhada para disparo de e-mails segmentados de lançamentos de tecnologia.

🎯 Desafio 10: Atualizar pedido para 'ENVIADO' com registro do código de rastreio dos Correios.

UPDATE pedidos SET status = 'ENVIADO', codigo_rastreio = 'BR123456789SP' WHERE id_pedido = 502 AND status = 'PAGO' RETURNING id_pedido, status, codigo_rastreio;

🔍 Explicação Técnica: Transição de status logístico atômica com retorno dos dados para notificação imediata do cliente.


🩺 ETAPA 9: Troubleshooting no Windows & Checklist de Entrega

🛠️ Resolução Rápida de Erros Frequentes:

  1. Porta 5000 já em uso (OSError: [Errno 10048] address already in use):
    Causa: Uma instância anterior do Flask continua rodando em segundo plano.
    Solução: No PowerShell:
    Get-Process -Name python* | Stop-Process -Force
    
  2. Erro 404 Not Found: Cliente inválido ou inativo:
    Causa: O id_cliente fornecido no checkout não existe no banco de dados.
  3. Erro 400 Bad Request: E-mail já cadastrado:
    Causa: Violação da restrição de unicidade no campo email. Cadastre um e-mail único.

✅ Checklist de Conclusão do Aluno:

  • Ambiente virtual venv configurado e ativado no Windows.
  • Dependências instaladas (flask, sqlalchemy, pydantic, pytest).
  • Banco de dados SQLite criado com as 5 tabelas relacionais.
  • Checkout transacional com múltiplos itens testado via endpoint JSON /api/pedidos/checkout.
  • Interface visual listando vitrine de produtos e painel de pedidos recentes na porta 5000.
  • Suíte de testes pytest -v passando com 100% de sucesso via client.test_client().
  • 10 Desafios de SQL executados no DBeaver/pgAdmin.

🛒 ShopFlow — Guia de Execução e README do Projeto

Setor: Comércio e Varejo
Componente: Atividades de Projetos II / III
Classificação: 🔴 Nível 2: Intermediário / Avançado (5 a 7 Tabelas)
Repositório Template: https://github.com/fatec-gti/pi_04_shopflow


🚀 1. Como Executar o Projeto Localmente

Passo 1: Clonar o Repositório e Abrir no VS Code

git clone https://github.com/fatec-gti/pi_04_shopflow.git
code pi_04_shopflow

Passo 2: Criar o Ambiente Virtual e Instalar Dependências

python -m venv venv
# Windows (PowerShell):
.\venv\Scripts\Activate.ps1
# Linux/Mac:
source venv/bin/activate

pip install flask sqlalchemy jinja2 psycopg2-binary

Passo 3: Executar a Aplicação com SQLite (Zero Configuração)

python main.py

Acesse no navegador:

  • 🌐 Interface Web & API REST: http://localhost:5000

🐳 2. Executando com PostgreSQL no Docker Compose

Para subir o banco de dados oficial de produção:

docker compose up -d

Edite o arquivo .env para apontar para o PostgreSQL:

DATABASE_URL=postgresql+psycopg2://postgres:secretpassword@localhost:5432/shopflow_db


🍃 3. Executando MongoDB + PostgreSQL no Docker Compose

O ShopFlow suporta Arquitetura Poliglota (PostgreSQL para transações financeiras + MongoDB para catálogo dinâmico de produtos):

services:
  postgres:
    image: postgres:17-alpine
    container_name: postgres_shopflow
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: secretpassword
      POSTGRES_DB: shopflow_db
    ports:
      - "5432:5432"

  mongodb:
    image: mongo:7.0
    container_name: mongo_shopflow
    environment:
      MONGO_INITDB_ROOT_USERNAME: root
      MONGO_INITDB_ROOT_PASSWORD: mongosecret
    ports:
      - "27017:27017"

👥 4. Equipe de Desenvolvimento (Template de Entrega)

  • Analista de Sistemas / PO: [Nome do Estudante]
  • Engenheiro de Software: [Nome do Estudante]
  • DBA / Modelador de Dados: [Nome do Estudante]
  • Desenvolvedor Full-stack: [Nome do Estudante]

🅿️ PROJETO 05: PARKFLOW (ESTACIONAMENTO ROTATIVO & MOBILIDADE)

📘 TUTORIAL AUTOGUIADO PASSO A PASSO — DO ZERO AO DEPLOY NO WINDOWS 10/11

Setor Econômico: Serviços e Mobilidade Urbana
Domínio: Estacionamento Rotativo, Vagas por Setor, Tarifação por Tempo e Check-in/Check-out
Nível de Complexidade: 🟢 Nível 1: Essencial / Básico (5 Tabelas Relacionais com Tarifação Temporal)
Ambiente de Desenvolvimento: Windows 10/11 (PT-BR) + VS Code + Python 3.11+ (Venv)
Stack Principal: Flask 3.x + SQLAlchemy 2.0 + Pydantic v2 + SQLite (Dev) / PostgreSQL (Docker) + Jinja2/Bootstrap 5 + Pytest


flowchart LR
    A["⚙️ 1. Setup Windows/Venv"] --> B["🛢️ 2. Dual-Database"]
    B --> C["🧱 3. Modelos ORM (5 Tabelas)"]
    C --> D["📋 4. Schemas Pydantic"]
    D --> E["🌐 5. Endpoints REST & Blueprint"]
    E --> F["🎨 6. Interface Web Jinja2"]
    F --> G["🧪 7. Testes Pytest (100% Verde)"]
    G --> H["📊 8. 10 Desafios SQL"]
    H --> I["🩺 9. Checklist & Troubleshooting"]

    style A fill:#e3f2fd,stroke:#1565c0
    style B fill:#fff8e1,stroke:#f57f17
    style C fill:#f3e5f5,stroke:#7b1fa2
    style D fill:#ede7f6,stroke:#5e35b1
    style E fill:#e0f2fe,stroke:#0284c7
    style F fill:#fce4ec,stroke:#c2185b
    style G fill:#dcfce7,stroke:#16a34a
    style H fill:#fef3c7,stroke:#d97706
    style I fill:#fee2e2,stroke:#ef4444

📂 0. Estrutura Completa de Pastas e Arquivos no VS Code

Crie exatamente a seguinte estrutura de diretórios no seu computador:

pi_05_parkflow/
├── app/
│   ├── __init__.py
│   ├── main.py
│   ├── core/
│   │   ├── __init__.py
│   │   └── database.py
│   ├── models/
│   │   ├── __init__.py
│   │   └── estacionamento_models.py
│   ├── schemas/
│   │   ├── __init__.py
│   │   └── estacionamento_schemas.py
│   ├── routers/
│   │   ├── __init__.py
│   │   └── estacionamento_router.py
│   └── templates/
│       ├── base.html
│       └── index.html
├── tests/
│   ├── __init__.py
│   └── test_parkflow.py
├── .env
├── requirements.txt
└── README.md

⚙️ ETAPA 1: Preparação do Ambiente no Windows 10/11

Abra o VS Code na pasta pi_05_parkflow e abra o terminal integrado (Ctrl + `).

1.1. Criar e Ativar o Ambiente Virtual (venv)

# 1. Criar o ambiente virtual:
python -m venv venv

# 2. Ativar no Windows PowerShell:
.\venv\Scripts\Activate.ps1

1.2. Criar o Arquivo de Dependências (requirements.txt)

Crie o arquivo requirements.txt:

flask==3.0.3
sqlalchemy==2.0.35
pydantic==2.9.0
jinja2==3.1.4
pytest==8.3.0

Instale com:

pip install -r requirements.txt

1.3. Criar o Arquivo de Variáveis de Ambiente (.env)

DATABASE_URL=sqlite:///./parkflow.db
APP_ENV=development

🛢️ ETAPA 2: Configuração Dual-Database (app/core/database.py)

Crie app/core/database.py:

import os
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, DeclarativeBase

DATABASE_URL = os.getenv("DATABASE_URL", "sqlite:///./parkflow.db")
connect_args = {"check_same_thread": False} if DATABASE_URL.startswith("sqlite") else {}

engine = create_engine(DATABASE_URL, connect_args=connect_args, echo=False)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

class Base(DeclarativeBase):
    pass

def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

🧱 ETAPA 3: Modelagem Declarativa ORM (app/models/estacionamento_models.py)

Aqui mapeamos as 5 tabelas relacionais do ParkFlow para gestão de vagas e cálculo de permanência.

Crie app/models/estacionamento_models.py:

from datetime import datetime, timezone
from typing import Optional, List
from decimal import Decimal
from sqlalchemy import String, Integer, Numeric, DateTime, ForeignKey, Boolean
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.core.database import Base

class TipoVeiculo(Base):
    __tablename__ = "tipos_veiculo"

    id_tipo_veiculo: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    descricao: Mapped[str] = mapped_column(String(40), nullable=False)

    precos: Mapped[List["TabelaPreco"]] = relationship(back_populates="tipo_veiculo")
    estadias: Mapped[List["Estadia"]] = relationship(back_populates="tipo_veiculo")

class TabelaPreco(Base):
    __tablename__ = "tabelas_preco"

    id_preco: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    id_tipo_veiculo: Mapped[int] = mapped_column(ForeignKey("tipos_veiculo.id_tipo_veiculo"), nullable=False)
    valor_primeira_hora: Mapped[Decimal] = mapped_column(Numeric(10, 2), nullable=False)
    valor_hora_adicional: Mapped[Decimal] = mapped_column(Numeric(10, 2), nullable=False)

    tipo_veiculo: Mapped["TipoVeiculo"] = relationship(back_populates="precos")

class Mensalista(Base):
    __tablename__ = "mensalistas"

    id_mensalista: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    nome: Mapped[str] = mapped_column(String(80), nullable=False)
    cpf: Mapped[str] = mapped_column(String(14), nullable=False, unique=True)
    telefone: Mapped[str] = mapped_column(String(20), nullable=False)
    ativo: Mapped[bool] = mapped_column(Boolean, default=True)

    estadias: Mapped[List["Estadia"]] = relationship(back_populates="mensalista")

class Vaga(Base):
    __tablename__ = "vagas"

    id_vaga: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    codigo_vaga: Mapped[str] = mapped_column(String(10), nullable=False, unique=True)
    status: Mapped[str] = mapped_column(String(20), default="LIVRE") # LIVRE / OCUPADA / MANUTENCAO

    estadias: Mapped[List["Estadia"]] = relationship(back_populates="vaga")

class Estadia(Base):
    __tablename__ = "estadias"

    id_estadia: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    placa_veiculo: Mapped[str] = mapped_column(String(10), nullable=False)
    modelo: Mapped[str] = mapped_column(String(50), nullable=False)
    id_tipo_veiculo: Mapped[int] = mapped_column(ForeignKey("tipos_veiculo.id_tipo_veiculo"), nullable=False)
    id_vaga: Mapped[int] = mapped_column(ForeignKey("vagas.id_vaga"), nullable=False)
    id_mensalista: Mapped[Optional[int]] = mapped_column(ForeignKey("mensalistas.id_mensalista"), nullable=True)
    data_hora_entrada: Mapped[datetime] = mapped_column(DateTime, default=lambda: datetime.now(timezone.utc))
    data_hora_saida: Mapped[Optional[datetime]] = mapped_column(DateTime, nullable=True)
    valor_cobrado: Mapped[Optional[Decimal]] = mapped_column(Numeric(10, 2), nullable=True)
    status: Mapped[str] = mapped_column(String(20), default="ABERTA") # ABERTA / PAGA / CANCELADA

    tipo_veiculo: Mapped["TipoVeiculo"] = relationship(back_populates="estadias")
    vaga: Mapped["Vaga"] = relationship(back_populates="estadias")
    mensalista: Mapped[Optional["Mensalista"]] = relationship(back_populates="estadias")

📋 ETAPA 4: Schemas de Validação Pydantic (app/schemas/estacionamento_schemas.py)

Crie app/schemas/estacionamento_schemas.py:

from datetime import datetime
from decimal import Decimal
from typing import Optional
from pydantic import BaseModel, Field

# --- ENTRADA DE VEÍCULO (CHECK-IN) ---
class CheckinCreate(BaseModel):
    placa_veiculo: str = Field(..., min_length=7, max_length=10)
    modelo: str = Field(..., min_length=2, max_length=50)
    id_tipo_veiculo: int = Field(..., gt=0)
    id_vaga: int = Field(..., gt=0)
    id_mensalista: Optional[int] = None

class EstadiaResponse(BaseModel):
    id_estadia: int
    placa_veiculo: str
    modelo: str
    id_vaga: int
    data_hora_entrada: datetime
    data_hora_saida: Optional[datetime] = None
    valor_cobrado: Optional[Decimal] = None
    status: str
    class Config:
        from_attributes = True

# --- SAÍDA DE VEÍCULO (CHECK-OUT) ---
class CheckoutRequest(BaseModel):
    valor_cobrado: Decimal = Field(..., ge=0)

🌐 ETAPA 5: Endpoints REST (app/routers/estacionamento_router.py) & Inicialização (app/main.py)

5.1. Criar app/routers/estacionamento_router.py

from datetime import datetime, timezone
from flask import Blueprint, request, jsonify
from sqlalchemy import select
from app.core.database import SessionLocal
from app.models.estacionamento_models import TipoVeiculo, Vaga, Estadia
from app.schemas.estacionamento_schemas import CheckinCreate, CheckoutRequest

router = Blueprint("estacionamento", __name__, url_prefix="/api")

# --- CHECK-IN (ENTRADA DE VEÍCULO) ---
@router.route("/checkin/", methods=["POST"])
def registrar_entrada():
    dados = request.get_json() or {}
    try:
        dto = CheckinCreate(**dados)
    except Exception as e:
        return jsonify({"erro": str(e)}), 400

    with SessionLocal() as db:
        vaga = db.get(Vaga, dto.id_vaga)
        if not vaga or not vaga.ativa:
            return jsonify({"erro": "Vaga não disponível ou inativa."}), 404
        
        ocupada = db.scalar(select(Estadia).where(Estadia.id_vaga == dto.id_vaga, Estadia.status == "ABERTA"))
        if ocupada:
            return jsonify({"erro": f"A vaga {vaga.numero_vaga} já está ocupada."}), 400

        nova_estadia = Estadia(
            placa_veiculo=dto.placa_veiculo,
            modelo=dto.modelo,
            id_tipo_veiculo=dto.id_tipo_veiculo,
            id_vaga=dto.id_vaga,
            id_mensalista=dto.id_mensalista,
            data_hora_entrada=datetime.now(timezone.utc),
            status="ABERTA"
        )
        db.add(nova_estadia)
        db.commit()
        db.refresh(nova_estadia)
        return jsonify({
            "id_estadia": nova_estadia.id_estadia,
            "placa_veiculo": nova_estadia.placa_veiculo,
            "modelo": nova_estadia.modelo,
            "id_vaga": nova_estadia.id_vaga,
            "data_hora_entrada": nova_estadia.data_hora_entrada.isoformat(),
            "status": nova_estadia.status
        }), 201

# --- LISTAGEM DE VEÍCULOS NO PÁTIO ---
@router.route("/patio/", methods=["GET"])
def listar_veiculos_no_patio():
    with SessionLocal() as db:
        estadias = db.scalars(select(Estadia).where(Estadia.status == "ABERTA").order_by(Estadia.data_hora_entrada.asc())).all()
        return jsonify([{
            "id_estadia": e.id_estadia,
            "placa_veiculo": e.placa_veiculo,
            "modelo": e.modelo,
            "id_vaga": e.id_vaga,
            "data_hora_entrada": e.data_hora_entrada.isoformat(),
            "status": e.status
        } for e in estadias]), 200

# --- CHECK-OUT (SAÍDA E PAGAMENTO) ---
@router.route("/checkout/<int:id_estadia>", methods=["PUT"])
def registrar_saida(id_estadia: int):
    dados = request.get_json() or {}
    try:
        dto = CheckoutRequest(**dados)
    except Exception as e:
        return jsonify({"erro": str(e)}), 400

    with SessionLocal() as db:
        estadia = db.get(Estadia, id_estadia)
        if not estadia or estadia.status != "ABERTA":
            return jsonify({"erro": "Estadia não encontrada ou já finalizada."}), 404

        estadia.data_hora_saida = datetime.now(timezone.utc)
        estadia.valor_cobrado = dto.valor_cobrado
        estadia.status = "PAGA"
        db.commit()
        db.refresh(estadia)
        return jsonify({
            "id_estadia": estadia.id_estadia,
            "placa_veiculo": estadia.placa_veiculo,
            "modelo": estadia.modelo,
            "id_vaga": estadia.id_vaga,
            "data_hora_saida": estadia.data_hora_saida.isoformat(),
            "valor_cobrado": float(estadia.valor_cobrado),
            "status": estadia.status
        }), 200

5.2. Criar app/main.py

from pathlib import Path
from decimal import Decimal
from datetime import datetime
from flask import Flask, render_template
from sqlalchemy import select
from app.core.database import engine, Base, SessionLocal
from app.models.estacionamento_models import TipoVeiculo, TabelaPreco, Vaga, Mensalista, Estadia
from app.routers import estacionamento_router

Base.metadata.create_all(bind=engine)

BASE_DIR = Path(__file__).resolve().parent
app = Flask(__name__, template_folder=str(BASE_DIR / "templates"))
app.register_blueprint(estacionamento_router.router)

def seed_dados_iniciais():
    with SessionLocal() as db:
        if not db.scalar(select(TipoVeiculo)):
            tv1 = TipoVeiculo(descricao="Carro Passeio")
            tv2 = TipoVeiculo(descricao="Motocicleta")
            tv3 = TipoVeiculo(descricao="SUV / Caminhonete")
            db.add_all([tv1, tv2, tv3])
            db.commit()

            tp1 = TabelaPreco(id_tipo_veiculo=tv1.id_tipo_veiculo, valor_primeira_hora=Decimal("15.00"), valor_hora_adicional=Decimal("10.00"))
            tp2 = TabelaPreco(id_tipo_veiculo=tv2.id_tipo_veiculo, valor_primeira_hora=Decimal("8.00"), valor_hora_adicional=Decimal("5.00"))
            db.add_all([tp1, tp2])
            db.commit()

            v1 = Vaga(numero_vaga="A-01", setor="Setor A", ativa=True)
            v2 = Vaga(numero_vaga="A-02", setor="Setor A", ativa=True)
            v3 = Vaga(numero_vaga="B-01", setor="Setor B", ativa=True)
            db.add_all([v1, v2, v3])
            db.commit()

            m1 = Mensalista(nome="Empresa Log VIP", ativo=True)
            db.add(m1)
            db.commit()

            e1 = Estadia(id_estadia=301, placa_veiculo="ABC-1234", modelo="Civic", id_tipo_veiculo=tv1.id_tipo_veiculo, id_vaga=v1.id_vaga, data_hora_entrada=datetime(2026, 8, 20, 8, 0, 0), status="ABERTA")
            e2 = Estadia(id_estadia=302, placa_veiculo="XYZ-9876", modelo="Corolla", id_tipo_veiculo=tv1.id_tipo_veiculo, id_vaga=v2.id_vaga, data_hora_entrada=datetime(2026, 8, 20, 7, 0, 0), data_hora_saida=datetime(2026, 8, 20, 14, 0, 0), valor_cobrado=Decimal("2500.00"), status="PAGA")
            e3 = Estadia(id_estadia=303, placa_veiculo="KTM-5555", modelo="Ninja", id_tipo_veiculo=tv2.id_tipo_veiculo, id_vaga=v3.id_vaga, id_mensalista=m1.id_mensalista, data_hora_entrada=datetime(2026, 8, 20, 9, 0, 0), data_hora_saida=datetime(2026, 8, 20, 11, 0, 0), valor_cobrado=Decimal("18.00"), status="PAGA")
            db.add_all([e1, e2, e3])
            db.commit()

seed_dados_iniciais()

@app.route("/")
def painel_patio():
    with SessionLocal() as db:
        vagas = db.scalars(select(Vaga)).all()
        estadias_abertas = db.scalars(select(Estadia).where(Estadia.status == "ABERTA")).all()
        return render_template("index.html", vagas=vagas, estadias_abertas=estadias_abertas)

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5000, debug=True)

🔍 Como Executar e Testar a Aplicação

  1. Iniciar Servidor Web e API:
python -m app.main

O servidor estará ativo em: http://localhost:5000

  1. Teste 1: Check-in de Veículo via cURL / PowerShell (POST /api/checkin/)
curl -X POST http://localhost:5000/api/checkin/ `
  -H "Content-Type: application/json" `
  -d '{"placa_veiculo": "BRA2E19", "modelo": "Jeep Compass Preto", "id_tipo_veiculo": 1, "id_vaga": 2}'
  • Resposta esperada: 201 Created com "status": "ABERTA".
  1. Teste 2: Check-out de Veículo via cURL / PowerShell (PUT /api/checkout/301)
curl -X PUT http://localhost:5000/api/checkout/301 `
  -H "Content-Type: application/json" `
  -d '{"valor_cobrado": 35.00}'
  • Resposta esperada: 200 OK com "status": "PAGA".
  1. Acessar Interface Web: Abra o navegador em: http://localhost:5000 para visualizar as vagas e veículos no pátio.

🎨 ETAPA 6: Interface Web com Jinja2 e Bootstrap 5

6.1. Criar app/templates/base.html

<!DOCTYPE html>
<html lang="pt-br">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>ParkFlow — Gestão de Estacionamento</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
</head>
<body class="bg-light">
    <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
        <div class="container">
            <a class="navbar-brand fw-bold" href="/"><i class="bi bi-p-circle-fill text-warning"></i> ParkFlow Rotativo</a>
            <span class="badge bg-warning text-dark">Flask 3.x & SQLAlchemy 2.0</span>
        </div>
    </nav>
    <div class="container py-4">
        {% block content %}{% endblock %}
    </div>
</body>
</html>

6.2. Criar app/templates/index.html

{% extends "base.html" %}
{% block content %}
<div class="d-flex justify-content-between align-items-center mb-4">
    <h2><i class="bi bi-car-front-fill"></i> Painel de Ocupação do Pátio</h2>
    <span class="badge bg-warning text-dark fs-6">{{ estadias_abertas|length }} Veículos Estacionados</span>
</div>

<div class="row mb-4">
    {% for est in estadias_abertas %}
    <div class="col-md-4 mb-3">
        <div class="card shadow-sm border-warning h-100">
            <div class="card-body">
                <div class="d-flex justify-content-between">
                    <span class="badge bg-dark fs-6">{{ est.placa_veiculo }}</span>
                    <span class="badge bg-warning text-dark">Vaga ID: {{ est.id_vaga }}</span>
                </div>
                <h5 class="card-title mt-2">{{ est.modelo }}</h5>
                <p class="text-muted small mb-0"><i class="bi bi-clock"></i> Entrada: {{ est.data_hora_entrada.strftime('%H:%M:%S') }}</p>
            </div>
        </div>
    </div>
    {% endfor %}
</div>
{% endblock %}

🧪 ETAPA 7: Suíte de Testes Automatizados com Pytest (tests/test_parkflow.py)

Crie tests/test_parkflow.py:

import pytest
from app.main import app

@pytest.fixture
def client():
    app.config["TESTING"] = True
    with app.test_client() as client:
        yield client

def test_painel_home_status_code(client):
    res = client.get("/")
    assert res.status_code == 200
    assert b"ParkFlow" in res.data

def test_ciclo_estadia(client):
    # 1. Check-in na Vaga 3 (B-01 livre)
    payload = {
        "placa_veiculo": "TST-9999",
        "modelo": "Carro Teste",
        "id_tipo_veiculo": 1,
        "id_vaga": 3
    }
    res_in = client.post("/api/checkin/", json=payload)
    assert res_in.status_code == 201
    dados_in = res_in.get_json()
    id_est = dados_in["id_estadia"]

    # 2. Check-out
    res_out = client.put(f"/api/checkout/{id_est}", json={"valor_cobrado": 20.0})
    assert res_out.status_code == 200
    assert res_out.get_json()["status"] == "PAGA"

Execute no terminal:

pytest -v

📊 ETAPA 8: Bateria de 10 Desafios de SQL Corporativo (ParkFlow)

🎯 Desafio 01: Listar todas as estadias em aberto ordenadas por ordem de entrada.

SELECT id_estadia, placa_veiculo, modelo, data_hora_entrada, id_vaga FROM estadias WHERE status = 'ABERTA' ORDER BY data_hora_entrada ASC;

🔍 Explicação Técnica: Consulta operacional do pátio para monitoramento da permanência dos veículos.

🎯 Desafio 02: Consultar as 10 estadias mais lucrativas finalizadas nos últimos 7 dias.

SELECT id_estadia, placa_veiculo, valor_cobrado, data_hora_saida FROM estadias WHERE status = 'PAGA' AND data_hora_saida >= date('now', '-7 days') ORDER BY valor_cobrado DESC LIMIT 10;

🔍 Explicação Técnica: Filtro por período recente e ordenação de receita decrescente.

🎯 Desafio 03: Total de faturamento e quantidade de atendimentos por tipo de veículo.

SELECT id_tipo_veiculo, COUNT(*) AS total_atendimentos, SUM(valor_cobrado) AS faturamento_tipo FROM estadias WHERE status = 'PAGA' GROUP BY id_tipo_veiculo ORDER BY faturamento_tipo DESC;

🔍 Explicação Técnica: Agrupamento por categoria veicular para análise de rentabilidade por metro quadrado.

🎯 Desafio 04: Total de estadias registradas por setor do estacionamento.

SELECT v.setor, COUNT(e.id_estadia) AS total_estadias FROM estadias e INNER JOIN vagas v ON e.id_vaga = v.id_vaga GROUP BY v.setor ORDER BY total_estadias DESC;

🔍 Explicação Técnica: INNER JOIN trazendo o setor da vaga para apurar a taxa de ocupação espacial.

🎯 Desafio 05: Filtrar setores com mais de 50 estadias acumuladas.

SELECT v.setor, COUNT(e.id_estadia) AS total FROM estadias e INNER JOIN vagas v ON e.id_vaga = v.id_vaga GROUP BY v.setor HAVING COUNT(e.id_estadia) > 50;

🔍 Explicação Técnica: Filtro agregado HAVING para identificação de áreas de alta rotatividade.

🎯 Desafio 06: Relatório de veículos estacionados com tipo e identificação da vaga.

SELECT e.placa_veiculo, e.modelo, tv.descricao AS tipo_veiculo, v.numero_vaga, v.setor FROM estadias e INNER JOIN tipos_veiculo tv ON e.id_tipo_veiculo = tv.id_tipo_veiculo INNER JOIN vagas v ON e.id_vaga = v.id_vaga WHERE e.status = 'ABERTA';

🔍 Explicação Técnica: Multi-JOIN unindo estadias, tipos de veículos e vagas para a guarita do operador.

🎯 Desafio 07: Extrato de pagamentos com tarifa aplicada e identificação de mensalistas.

SELECT e.id_estadia, e.placa_veiculo, m.nome AS mensalista, tp.valor_primeira_hora, e.valor_cobrado FROM estadias e INNER JOIN tabelas_preco tp ON e.id_tipo_veiculo = tp.id_tipo_veiculo LEFT JOIN mensalistas m ON e.id_mensalista = m.id_mensalista WHERE e.status = 'PAGA';

🔍 Explicação Técnica: LEFT JOIN para incluir tanto clientes rotativos avulsos quanto contratos corporativos.

🎯 Desafio 08: Identificar vagas ativas que estão LIVRES no momento (LEFT JOIN).

SELECT v.id_vaga, v.numero_vaga, v.setor FROM vagas v LEFT JOIN estadias e ON v.id_vaga = e.id_vaga AND e.status = 'ABERTA' WHERE e.id_estadia IS NULL AND v.ativa = 1;

🔍 Explicação Técnica: Consulta fundamental para o painel de entrada (Display de Vagas Livres).

🎯 Desafio 09: Listar estadias com valor cobrado superior ao ticket médio do estacionamento.

SELECT id_estadia, placa_veiculo, valor_cobrado FROM estadias WHERE valor_cobrado > (SELECT AVG(valor_cobrado) FROM estadias WHERE status = 'PAGA');

🔍 Explicação Técnica: Subquery escalar no predicado WHERE para detecção de pernoites e longas estadias.

🎯 Desafio 10: Registrar saída de veículo e valor cobrado de forma atômica com RETURNING.

UPDATE estadias SET data_hora_saida = CURRENT_TIMESTAMP, valor_cobrado = 25.00, status = 'PAGA' WHERE id_estadia = 301 AND data_hora_saida IS NULL RETURNING id_estadia, placa_veiculo, valor_cobrado;

🔍 Explicação Técnica: DML de check-out seguro que retorna os valores para impressão do recibo de pagamento.


🩺 ETAPA 9: Troubleshooting no Windows & Checklist de Entrega

🛠️ Resolução Rápida de Erros Frequentes:

  1. Porta 5000 já em uso (OSError: [Errno 10048] address already in use):
    Causa: Uma instância anterior do Flask continua rodando em segundo plano.
    Solução: No PowerShell:
    Get-Process -Name python* | Stop-Process -Force
    
  2. Erro 400 Bad Request: A vaga já está ocupada:
    Causa: Você tentou registrar entrada na vaga que já possui um veículo com status ABERTA. Selecione uma vaga livre.
  3. Erro 404 Not Found: Estadia não encontrada ou já finalizada:
    Causa: O id_estadia informado no check-out já teve sua saída registrada anteriormente.

✅ Checklist de Conclusão do Aluno:

  • Ambiente virtual venv configurado e ativado no Windows.
  • Dependências instaladas (flask, sqlalchemy, pydantic, pytest).
  • Banco de dados SQLite criado com as 5 tabelas relacionais.
  • Check-in e check-out testados via endpoints JSON /api/checkin/ e /api/checkout/.
  • Interface visual listando a ocupação atual do pátio na porta 5000.
  • Suíte de testes pytest -v passando com 100% de sucesso via client.test_client().
  • 10 Desafios de SQL executados no DBeaver/pgAdmin.

🅿️ ParkFlow — Guia de Execução e README do Projeto

Setor: Serviços e Tecnologia
Componente: Atividades de Projetos II / III
Classificação: 🟢 Nível 1: Essencial / Básico (3 a 4 Tabelas)
Repositório Template: https://github.com/fatec-gti/pi_05_parkflow


🚀 1. Como Executar o Projeto Localmente

Passo 1: Clonar o Repositório e Abrir no VS Code

git clone https://github.com/fatec-gti/pi_05_parkflow.git
code pi_05_parkflow

Passo 2: Criar o Ambiente Virtual e Instalar Dependências

python -m venv venv
# Windows (PowerShell):
.\venv\Scripts\Activate.ps1
# Linux/Mac:
source venv/bin/activate

pip install flask sqlalchemy jinja2 psycopg2-binary

Passo 3: Executar a Aplicação com SQLite (Zero Configuração)

python main.py

Acesse no navegador:

  • 🌐 Interface Web & API REST: http://localhost:5000

🐳 2. Executando com PostgreSQL no Docker Compose

Para subir o banco de dados oficial de produção:

docker compose up -d

Edite o arquivo .env para apontar para o PostgreSQL:

DATABASE_URL=postgresql://postgres:senha@localhost:5432/pi_05_parkflow_db

👥 3. Equipe de Desenvolvimento (Template de Entrega)

  • Analista de Sistemas / PO: [Nome do Estudante]
  • Engenheiro de Software: [Nome do Estudante]
  • DBA / Modelador de Dados: [Nome do Estudante]
  • Desenvolvedor Full-stack: [Nome do Estudante]

🛠️ PROJETO 06: SERVICEFLOW (ASSISTÊNCIA TÉCNICA & ORDENS DE SERVIÇO)

📘 TUTORIAL AUTOGUIADO PASSO A PASSO — DO ZERO AO DEPLOY NO WINDOWS 10/11

Setor Econômico: Serviços e Tecnologia
Domínio: Assistência Técnica Especializada, Service Desk, Alocação de Técnicos, Apontamento de Peças e SLA
Nível de Complexidade: 🔴 Nível 2: Intermediário (6 Tabelas Relacionais com Apontamento de Horas e Peças)
Ambiente de Desenvolvimento: Windows 10/11 (PT-BR) + VS Code + Python 3.11+ (Venv)
Stack Principal: Flask 3.x + SQLAlchemy 2.0 + Pydantic v2 + SQLite (Dev) / PostgreSQL (Docker) + Jinja2/Bootstrap 5 + Pytest


flowchart LR
    A["⚙️ 1. Setup Windows/Venv"] --> B["🛢️ 2. Dual-Database"]
    B --> C["🧱 3. Modelos ORM (6 Tabelas)"]
    C --> D["📋 4. Schemas Pydantic"]
    D --> E["🌐 5. Endpoints REST & Blueprint"]
    E --> F["🎨 6. Interface Web Jinja2"]
    F --> G["🧪 7. Testes Pytest (100% Verde)"]
    G --> H["📊 8. 10 Desafios SQL"]
    H --> I["🩺 9. Checklist & Troubleshooting"]

    style A fill:#e3f2fd,stroke:#1565c0
    style B fill:#fff8e1,stroke:#f57f17
    style C fill:#f3e5f5,stroke:#7b1fa2
    style D fill:#ede7f6,stroke:#5e35b1
    style E fill:#e0f2fe,stroke:#0284c7
    style F fill:#fce4ec,stroke:#c2185b
    style G fill:#dcfce7,stroke:#16a34a
    style H fill:#fef3c7,stroke:#d97706
    style I fill:#fee2e2,stroke:#ef4444

📂 0. Estrutura Completa de Pastas e Arquivos no VS Code

Crie exatamente a seguinte estrutura de diretórios no seu computador:

pi_06_serviceflow/
├── app/
│   ├── __init__.py
│   ├── main.py
│   ├── core/
│   │   ├── __init__.py
│   │   └── database.py
│   ├── models/
│   │   ├── __init__.py
│   │   └── servico_models.py
│   ├── schemas/
│   │   ├── __init__.py
│   │   └── servico_schemas.py
│   ├── routers/
│   │   ├── __init__.py
│   │   └── servico_router.py
│   └── templates/
│       ├── base.html
│       └── index.html
├── tests/
│   ├── __init__.py
│   └── test_serviceflow.py
├── .env
├── requirements.txt
└── README.md

⚙️ ETAPA 1: Preparação do Ambiente no Windows 10/11

Abra o VS Code na pasta pi_06_serviceflow e abra o terminal integrado (Ctrl + `).

1.1. Criar e Ativar o Ambiente Virtual (venv)

# 1. Criar o ambiente virtual:
python -m venv venv

# 2. Ativar no Windows PowerShell:
.\venv\Scripts\Activate.ps1

1.2. Criar o Arquivo de Dependências (requirements.txt)

Crie o arquivo requirements.txt:

flask==3.0.3
sqlalchemy==2.0.35
pydantic==2.9.0
jinja2==3.1.4
pytest==8.3.0

Instale as dependências:

pip install -r requirements.txt

1.3. Criar o Arquivo de Variáveis de Ambiente (.env)

DATABASE_URL=sqlite:///./serviceflow.db
APP_ENV=development

🛢️ ETAPA 2: Configuração Dual-Database (app/core/database.py)

Crie app/core/database.py:

import os
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, DeclarativeBase

DATABASE_URL = os.getenv("DATABASE_URL", "sqlite:///./serviceflow.db")
connect_args = {"check_same_thread": False} if DATABASE_URL.startswith("sqlite") else {}

engine = create_engine(DATABASE_URL, connect_args=connect_args, echo=False)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

class Base(DeclarativeBase):
    pass

def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

🧱 ETAPA 3: Modelagem Declarativa ORM (app/models/servico_models.py)

Aqui mapeamos as 6 tabelas relacionais do ServiceFlow para controle de Ordens de Serviço, técnicos e faturamento.

Crie app/models/servico_models.py:

from datetime import date
from typing import Optional, List
from decimal import Decimal
from sqlalchemy import String, Integer, Numeric, Date, Text, ForeignKey
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.core.database import Base

class Cliente(Base):
    __tablename__ = "clientes"

    id_cliente: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    nome: Mapped[str] = mapped_column(String(100), nullable=False)
    telefone: Mapped[Optional[str]] = mapped_column(String(20), nullable=True)
    email: Mapped[Optional[str]] = mapped_column(String(100), nullable=True)

    ordens: Mapped[List["OrdemServico"]] = relationship(back_populates="cliente")

class Tecnico(Base):
    __tablename__ = "tecnicos"

    id_tecnico: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    nome: Mapped[str] = mapped_column(String(100), nullable=False)
    especialidade: Mapped[Optional[str]] = mapped_column(String(50), nullable=True)

    ordens: Mapped[List["OrdemServico"]] = relationship(back_populates="tecnico")

class ServicoCatalogo(Base):
    __tablename__ = "servicos_catalogo"

    id_servico: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    descricao: Mapped[str] = mapped_column(String(100), nullable=False)
    preco_base: Mapped[Decimal] = mapped_column(Numeric(10, 2), default=Decimal("0.00"))

    itens_os: Mapped[List["ItemServicoOS"]] = relationship(back_populates="servico")

class OrdemServico(Base):
    __tablename__ = "ordens_servico"

    id_os: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    numero_os: Mapped[str] = mapped_column(String(30), unique=True, nullable=False)
    id_cliente: Mapped[int] = mapped_column(ForeignKey("clientes.id_cliente"), nullable=False)
    id_tecnico: Mapped[Optional[int]] = mapped_column(ForeignKey("tecnicos.id_tecnico"), nullable=True)
    equipamento: Mapped[str] = mapped_column(String(100), nullable=False)
    defeito_relatado: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
    data_abertura: Mapped[date] = mapped_column(Date, default=date.today)
    data_conclusao: Mapped[Optional[date]] = mapped_column(Date, nullable=True)
    prioridade: Mapped[str] = mapped_column(String(20), default="MEDIA") # BAIXA, MEDIA, ALTA, CRITICA
    valor_total: Mapped[Decimal] = mapped_column(Numeric(10, 2), default=Decimal("0.00"))
    laudo_tecnico: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
    status: Mapped[str] = mapped_column(String(20), default="ABERTA") # ABERTA, EM_ANDAMENTO, CONCLUIDA, CANCELADA

    cliente: Mapped["Cliente"] = relationship(back_populates="ordens")
    tecnico: Mapped[Optional["Tecnico"]] = relationship(back_populates="ordens")
    servicos_prestados: Mapped[List["ItemServicoOS"]] = relationship(back_populates="ordem", cascade="all, delete-orphan")
    pecas: Mapped[List["PecaUtilizada"]] = relationship(back_populates="ordem", cascade="all, delete-orphan")

class ItemServicoOS(Base):
    __tablename__ = "itens_servico_os"

    id_item_os: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    id_os: Mapped[int] = mapped_column(ForeignKey("ordens_servico.id_os"), nullable=False)
    id_servico: Mapped[int] = mapped_column(ForeignKey("servicos_catalogo.id_servico"), nullable=False)
    valor_cobrado: Mapped[Decimal] = mapped_column(Numeric(10, 2), nullable=False)

    ordem: Mapped["OrdemServico"] = relationship(back_populates="servicos_prestados")
    servico: Mapped["ServicoCatalogo"] = relationship(back_populates="itens_os")

class PecaUtilizada(Base):
    __tablename__ = "pecas_utilizadas"

    id_peca: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    id_os: Mapped[int] = mapped_column(ForeignKey("ordens_servico.id_os"), nullable=False)
    nome_peca: Mapped[str] = mapped_column(String(80), nullable=False)
    quantidade: Mapped[int] = mapped_column(Integer, nullable=False)
    valor_unitario: Mapped[Decimal] = mapped_column(Numeric(10, 2), nullable=False)

    ordem: Mapped["OrdemServico"] = relationship(back_populates="pecas")

📋 ETAPA 4: Schemas de Validação Pydantic (app/schemas/servico_schemas.py)

Crie app/schemas/servico_schemas.py:

from decimal import Decimal
from typing import Optional
from pydantic import BaseModel, Field

# --- ABERTURA DE ORDEM DE SERVIÇO ---
class OrdemServicoCreate(BaseModel):
    numero_os: str = Field(..., min_length=3, max_length=30)
    id_cliente: int = Field(..., gt=0)
    id_tecnico: Optional[int] = None
    equipamento: str = Field(..., min_length=3, max_length=100)
    defeito_relatado: Optional[str] = None
    prioridade: str = Field(default="MEDIA")

class OrdemServicoResponse(BaseModel):
    id_os: int
    numero_os: str
    equipamento: str
    prioridade: str
    valor_total: Decimal
    status: str
    class Config:
        from_attributes = True

# --- FECHAMENTO DE OS ---
class FechamentoOS(BaseModel):
    laudo_tecnico: str = Field(..., min_length=10)
    valor_total: Decimal = Field(..., gt=0)

🌐 ETAPA 5: Endpoints REST (app/routers/servico_router.py) & Inicialização (app/main.py)

5.1. Criar app/routers/servico_router.py

from datetime import date
from flask import Blueprint, request, jsonify
from sqlalchemy import select
from app.core.database import SessionLocal
from app.models.servico_models import Cliente, Tecnico, OrdemServico
from app.schemas.servico_schemas import OrdemServicoCreate, FechamentoOS

router = Blueprint("servico", __name__, url_prefix="/api")

# --- ABERTURA DE OS ---
@router.route("/os/", methods=["POST"])
def abrir_ordem_servico():
    dados = request.get_json() or {}
    try:
        dto = OrdemServicoCreate(**dados)
    except Exception as e:
        return jsonify({"erro": str(e)}), 400

    with SessionLocal() as db:
        if not db.get(Cliente, dto.id_cliente):
            return jsonify({"erro": "Cliente não encontrado."}), 404
        if dto.id_tecnico and not db.get(Tecnico, dto.id_tecnico):
            return jsonify({"erro": "Técnico informado não existe."}), 404
        if db.scalar(select(OrdemServico).where(OrdemServico.numero_os == dto.numero_os)):
            return jsonify({"erro": "Número de OS já existente."}), 400

        nova_os = OrdemServico(
            numero_os=dto.numero_os,
            id_cliente=dto.id_cliente,
            id_tecnico=dto.id_tecnico,
            equipamento=dto.equipamento,
            defeito_relatado=dto.defeito_relatado,
            prioridade=dto.prioridade,
            data_abertura=date.today(),
            status="ABERTA"
        )
        db.add(nova_os)
        db.commit()
        db.refresh(nova_os)
        return jsonify({
            "id_os": nova_os.id_os,
            "numero_os": nova_os.numero_os,
            "equipamento": nova_os.equipamento,
            "prioridade": nova_os.prioridade,
            "valor_total": float(nova_os.valor_total),
            "status": nova_os.status
        }), 201

@router.route("/os/", methods=["GET"])
def listar_ordens_servico():
    with SessionLocal() as db:
        ordens = db.scalars(select(OrdemServico).order_by(OrdemServico.id_os.desc())).all()
        return jsonify([{
            "id_os": o.id_os,
            "numero_os": o.numero_os,
            "equipamento": o.equipamento,
            "prioridade": o.prioridade,
            "valor_total": float(o.valor_total),
            "status": o.status
        } for o in ordens]), 200

# --- ENCERRAMENTO DE OS ---
@router.route("/os/<int:id_os>/concluir", methods=["PUT"])
def concluir_ordem_servico(id_os: int):
    dados = request.get_json() or {}
    try:
        dto = FechamentoOS(**dados)
    except Exception as e:
        return jsonify({"erro": str(e)}), 400

    with SessionLocal() as db:
        os_db = db.get(OrdemServico, id_os)
        if not os_db or os_db.status == "CONCLUIDA":
            return jsonify({"erro": "OS não encontrada ou já concluída."}), 404

        os_db.laudo_tecnico = dto.laudo_tecnico
        os_db.valor_total = dto.valor_total
        os_db.data_conclusao = date.today()
        os_db.status = "CONCLUIDA"
        db.commit()
        db.refresh(os_db)
        return jsonify({
            "id_os": os_db.id_os,
            "numero_os": os_db.numero_os,
            "equipamento": os_db.equipamento,
            "prioridade": os_db.prioridade,
            "valor_total": float(os_db.valor_total),
            "status": os_db.status
        }), 200

5.2. Criar app/main.py

from pathlib import Path
from decimal import Decimal
from datetime import date
from flask import Flask, render_template
from sqlalchemy import select
from app.core.database import engine, Base, SessionLocal
from app.models.servico_models import Cliente, Tecnico, ServicoCatalogo, OrdemServico, ItemServicoOS, PecaUtilizada
from app.routers import servico_router

Base.metadata.create_all(bind=engine)

BASE_DIR = Path(__file__).resolve().parent
app = Flask(__name__, template_folder=str(BASE_DIR / "templates"))
app.register_blueprint(servico_router.router)

def seed_dados_iniciais():
    with SessionLocal() as db:
        if not db.scalar(select(Cliente)):
            c1 = Cliente(nome="Tech Soluções", telefone="(11) 9999-8888", email="contato@tech.com")
            c2 = Cliente(nome="Comércio Alpha", telefone="(19) 9888-7777", email="adm@alpha.com")
            db.add_all([c1, c2])
            db.commit()

            t1 = Tecnico(nome="Eduardo Lima", especialidade="Eletrônica")
            t2 = Tecnico(nome="Beatriz Mello", especialidade="Redes")
            db.add_all([t1, t2])
            db.commit()

            sc1 = ServicoCatalogo(descricao="Troca de Fonte", preco_base=Decimal("150.00"))
            sc2 = ServicoCatalogo(descricao="Reparo Placa Mãe", preco_base=Decimal("350.00"))
            db.add_all([sc1, sc2])
            db.commit()

            os1 = OrdemServico(id_os=401, numero_os="OS-2026-001", id_cliente=c1.id_cliente, id_tecnico=t1.id_tecnico, equipamento="Servidor Dell R740", defeito_relatado="Não liga", data_abertura=date(2026, 8, 1), data_conclusao=date(2026, 8, 3), prioridade="CRITICA", valor_total=Decimal("1200.00"), status="CONCLUIDA")
            os2 = OrdemServico(id_os=405, numero_os="OS-2026-005", id_cliente=c2.id_cliente, id_tecnico=t1.id_tecnico, equipamento="Switch Cisco 48p", defeito_relatado="Portas inoperantes", data_abertura=date(2026, 8, 10), prioridade="ALTA", valor_total=Decimal("650.00"), status="EM_ANDAMENTO")
            db.add_all([os1, os2])
            db.commit()

            ios1 = ItemServicoOS(id_os=os2.id_os, id_servico=sc2.id_servico, valor_cobrado=Decimal("350.00"))
            db.add(ios1)

            p1 = PecaUtilizada(id_os=os2.id_os, nome_peca="Regulador de Tensão", quantidade=1, valor_unitario=Decimal("600.00"))
            db.add(p1)
            db.commit()

seed_dados_iniciais()

@app.route("/")
def painel_chamados():
    with SessionLocal() as db:
        ordens = db.scalars(select(OrdemServico).order_by(OrdemServico.id_os.desc())).all()
        return render_template("index.html", ordens=ordens)

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5000, debug=True)

🔍 Como Executar e Testar a Aplicação

  1. Iniciar Servidor Web e API:
python -m app.main

O servidor estará ativo em: http://localhost:5000

  1. Teste 1: Abertura de Nova OS via cURL / PowerShell (POST /api/os/)
curl -X POST http://localhost:5000/api/os/ `
  -H "Content-Type: application/json" `
  -d '{"numero_os": "OS-2026-999", "id_cliente": 1, "id_tecnico": 1, "equipamento": "Notebook ThinkPad T14", "defeito_relatado": "Teclado sem resposta", "prioridade": "ALTA"}'
  • Resposta esperada: 201 Created com "status": "ABERTA".
  1. Teste 2: Encerramento com Laudo Técnico via cURL / PowerShell (PUT /api/os/405/concluir)
curl -X PUT http://localhost:5000/api/os/405/concluir `
  -H "Content-Type: application/json" `
  -d '{"laudo_tecnico": "Troca de placa reguladora e testes de estresse concluídos.", "valor_total": 950.00}'
  • Resposta esperada: 200 OK com "status": "CONCLUIDA".
  1. Acessar Interface Web: Abra o navegador em: http://localhost:5000 para visualizar a fila de chamados técnicos.

🎨 ETAPA 6: Interface Web com Jinja2 e Bootstrap 5

6.1. Criar app/templates/base.html

<!DOCTYPE html>
<html lang="pt-br">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>ServiceFlow — Ordens de Serviço</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
</head>
<body class="bg-light">
    <nav class="navbar navbar-expand-lg navbar-dark bg-info bg-gradient">
        <div class="container">
            <a class="navbar-brand fw-bold text-dark" href="/"><i class="bi bi-tools"></i> ServiceFlow SLA</a>
            <span class="badge bg-dark text-white">Flask 3.x & SQLAlchemy 2.0</span>
        </div>
    </nav>
    <div class="container py-4">
        {% block content %}{% endblock %}
    </div>
</body>
</html>

6.2. Criar app/templates/index.html

{% extends "base.html" %}
{% block content %}
<div class="d-flex justify-content-between align-items-center mb-4">
    <h2><i class="bi bi-card-checklist"></i> Fila de Atendimento e Ordens de Serviço</h2>
    <span class="badge bg-dark fs-6">{{ ordens|length }} Chamados</span>
</div>

<div class="card shadow-sm border-0">
    <div class="card-body p-0">
        <table class="table table-hover align-middle mb-0">
            <thead class="table-dark">
                <tr>
                    <th>OS #</th>
                    <th>Equipamento</th>
                    <th>Prioridade</th>
                    <th>Abertura</th>
                    <th>Total</th>
                    <th>Status</th>
                </tr>
            </thead>
            <tbody>
                {% for o in ordens %}
                <tr>
                    <td class="fw-bold">{{ o.numero_os }}</td>
                    <td>{{ o.equipamento }}</td>
                    <td>
                        {% if o.prioridade == 'CRITICA' %}
                            <span class="badge bg-danger">CRÍTICA</span>
                        {% elif o.prioridade == 'ALTA' %}
                            <span class="badge bg-warning text-dark">ALTA</span>
                        {% else %}
                            <span class="badge bg-secondary">MÉDIA</span>
                        {% endif %}
                    </td>
                    <td>{{ o.data_abertura }}</td>
                    <td>R$ {{ "%.2f"|format(o.valor_total) }}</td>
                    <td>
                        {% if o.status == 'CONCLUIDA' %}
                            <span class="badge bg-success">CONCLUÍDA</span>
                        {% elif o.status == 'EM_ANDAMENTO' %}
                            <span class="badge bg-primary">EM ANDAMENTO</span>
                        {% else %}
                            <span class="badge bg-warning text-dark">ABERTA</span>
                        {% endif %}
                    </td>
                </tr>
                {% endfor %}
            </tbody>
        </table>
    </div>
</div>
{% endblock %}

🧪 ETAPA 7: Suíte de Testes Automatizados com Pytest (tests/test_serviceflow.py)

Crie tests/test_serviceflow.py:

import pytest
from app.main import app

@pytest.fixture
def client():
    app.config["TESTING"] = True
    with app.test_client() as client:
        yield client

def test_painel_home_status_code(client):
    res = client.get("/")
    assert res.status_code == 200
    assert b"ServiceFlow" in res.data

def test_abertura_e_fechamento_os(client):
    # 1. Abertura de OS
    payload = {
        "numero_os": "OS-UNIT-999",
        "id_cliente": 1,
        "equipamento": "Roteador Mikrotik",
        "prioridade": "ALTA"
    }
    res_abertura = client.post("/api/os/", json=payload)
    assert res_abertura.status_code == 201
    dados_os = res_abertura.get_json()
    id_os = dados_os["id_os"]

    # 2. Fechamento de OS
    concluir_payload = {
        "laudo_tecnico": "Atualização de Firmware e testes de portas.",
        "valor_total": 450.00
    }
    res_fechamento = client.put(f"/api/os/{id_os}/concluir", json=concluir_payload)
    assert res_fechamento.status_code == 200
    assert res_fechamento.get_json()["status"] == "CONCLUIDA"

Execute no terminal:

pytest -v

📊 ETAPA 8: Bateria de 10 Desafios de SQL Corporativo (ServiceFlow)

🎯 Desafio 01: Listar Ordens de Serviço abertas com prioridade 'ALTA' ou 'CRITICA'.

SELECT id_os, numero_os, id_cliente, data_abertura, prioridade, status FROM ordens_servico WHERE prioridade IN ('ALTA', 'CRITICA') AND status <> 'CONCLUIDA' ORDER BY data_abertura ASC;

🔍 Explicação Técnica: Filtro com operador IN para gestão da fila de atendimento prioritário e controle de SLA.

🎯 Desafio 02: Consultar as 5 OSs mais antigas ainda pendentes de resolução com o nome do técnico.

SELECT os.id_os, os.numero_os, os.data_abertura, t.nome AS responsavel_tecnico FROM ordens_servico os INNER JOIN tecnicos t ON os.id_tecnico = t.id_tecnico WHERE os.status = 'ABERTA' ORDER BY os.data_abertura ASC LIMIT 5;

🔍 Explicação Técnica: Controle de estouro de SLA na bancada de manutenção com INNER JOIN trazendo o responsável técnico.

🎯 Desafio 03: Calcular a média de valor de mão de obra e peças cobradas por OS.

SELECT AVG(valor_total) AS ticket_medio_os, SUM(valor_total) AS faturamento_total, COUNT(*) AS total_atendimentos FROM ordens_servico WHERE status = 'CONCLUIDA';

🔍 Explicação Técnica: Agregações financeiras para fechamento contábil da oficina técnica.

🎯 Desafio 04: Produtividade técnica: total de OSs concluídas e receita gerada por técnico.

SELECT t.nome, COUNT(os.id_os) AS total_os_atendidas, SUM(os.valor_total) AS receita_gerada FROM ordens_servico os INNER JOIN tecnicos t ON os.id_tecnico = t.id_tecnico WHERE os.status = 'CONCLUIDA' GROUP BY t.nome ORDER BY receita_gerada DESC;

🔍 Explicação Técnica: Ranking de desempenho e comissionamento técnico da equipe de campo.

🎯 Desafio 05: Filtrar técnicos com mais de 10 ordens de serviço executadas.

SELECT t.nome, COUNT(os.id_os) AS total FROM ordens_servico os INNER JOIN tecnicos t ON os.id_tecnico = t.id_tecnico GROUP BY t.nome HAVING COUNT(os.id_os) > 10;

🔍 Explicação Técnica: Filtro HAVING para identificação de técnicos especialistas de alta demanda.

🎯 Desafio 06: Painel operacional de OSs em andamento com dados do cliente e do técnico.

SELECT os.numero_os, c.nome AS cliente, t.nome AS tecnico, os.equipamento, os.prioridade, os.status FROM ordens_servico os INNER JOIN clientes c ON os.id_cliente = c.id_cliente INNER JOIN tecnicos t ON os.id_tecnico = t.id_tecnico WHERE os.status = 'EM_ANDAMENTO';

🔍 Explicação Técnica: Junção tripla para monitoramento em tempo real do Service Desk.

🎯 Desafio 07: Detalhamento completo de serviços e peças faturadas na OS #405.

SELECT os.numero_os, c.nome AS cliente, sc.descricao AS servico, ios.valor_cobrado AS valor_servico, pu.nome_peca, (pu.quantidade * pu.valor_unitario) AS total_pecas FROM ordens_servico os INNER JOIN clientes c ON os.id_cliente = c.id_cliente INNER JOIN itens_servico_os ios ON os.id_os = ios.id_os INNER JOIN servicos_catalogo sc ON ios.id_servico = sc.id_servico LEFT JOIN pecas_utilizadas pu ON os.id_os = pu.id_os WHERE os.id_os = 405;

🔍 Explicação Técnica: Multi-JOIN com LEFT JOIN permitindo faturamento mesmo de OSs que não utilizaram peças físicas.

🎯 Desafio 08: Identificar serviços do catálogo que nunca foram contratados em nenhuma OS.

SELECT sc.id_servico, sc.descricao FROM servicos_catalogo sc LEFT JOIN itens_servico_os ios ON sc.id_servico = ios.id_servico WHERE ios.id_item_os IS NULL;

🔍 Explicação Técnica: LEFT JOIN para auditoria e saneamento do portfólio de serviços.

🎯 Desafio 09: Listar OSs que demandaram peças de alto custo (> R$ 500,00) (Subquery com IN).

SELECT id_os, numero_os, equipamento FROM ordens_servico WHERE id_os IN (SELECT id_os FROM pecas_utilizadas WHERE valor_unitario > 500.00);

🔍 Explicação Técnica: Subquery aninhada para controle de aprovações gerenciais de compras de peças críticas.

🎯 Desafio 10: Encerrar ordem de serviço com laudo técnico e retorno do status atômico.

UPDATE ordens_servico SET status = 'CONCLUIDA', data_conclusao = CURRENT_DATE, laudo_tecnico = 'Troca do regulador de tensão efetuada com sucesso.' WHERE id_os = 405 AND status = 'EM_ANDAMENTO' RETURNING id_os, numero_os, status;

🔍 Explicação Técnica: DML de conclusão com cláusula RETURNING para notificação imediata do cliente por e-mail/webhook.


🩺 ETAPA 9: Troubleshooting no Windows & Checklist de Entrega

🛠️ Resolução Rápida de Erros Frequentes:

  1. Porta 5000 já em uso (OSError: [Errno 10048] address already in use):
    Causa: Uma instância anterior do Flask continua rodando em segundo plano.
    Solução: No PowerShell:
    Get-Process -Name python* | Stop-Process -Force
    
  2. Erro 400 Bad Request: Número de OS já existente:
    Causa: O numero_os é um campo único. Altere o código para cadastrar uma nova ordem.
  3. Erro 404 Not Found: Cliente não encontrado:
    Causa: O id_cliente passado não existe na tabela clientes.

✅ Checklist de Conclusão do Aluno:

  • Ambiente virtual venv configurado e ativado no Windows.
  • Dependências instaladas (flask, sqlalchemy, pydantic, pytest).
  • Banco de dados SQLite criado com as 6 tabelas relacionais.
  • Abertura e encerramento de OS testados via endpoints JSON /api/os/.
  • Interface visual listando as ordens de serviço por prioridade na porta 5000.
  • Suíte de testes pytest -v passando com 100% de sucesso via client.test_client().
  • 10 Desafios de SQL executados no DBeaver/pgAdmin.

🛠️ ServiceFlow — Guia de Execução e README do Projeto

Setor: Serviços e Tecnologia
Componente: Atividades de Projetos II / III
Classificação: 🔴 Nível 2: Intermediário / Avançado (5 a 7 Tabelas)
Repositório Template: https://github.com/fatec-gti/pi_06_serviceflow


🚀 1. Como Executar o Projeto Localmente

Passo 1: Clonar o Repositório e Abrir no VS Code

git clone https://github.com/fatec-gti/pi_06_serviceflow.git
code pi_06_serviceflow

Passo 2: Criar o Ambiente Virtual e Instalar Dependências

python -m venv venv
# Windows (PowerShell):
.\venv\Scripts\Activate.ps1
# Linux/Mac:
source venv/bin/activate

pip install flask sqlalchemy jinja2 psycopg2-binary

Passo 3: Executar a Aplicação com SQLite (Zero Configuração)

python main.py

Acesse no navegador:

  • 🌐 Interface Web & API REST: http://localhost:5000

🐳 2. Executando com PostgreSQL no Docker Compose

Para subir o banco de dados oficial de produção:

docker compose up -d

Edite o arquivo .env para apontar para o PostgreSQL:

DATABASE_URL=postgresql://postgres:senha@localhost:5432/pi_06_serviceflow_db

👥 3. Equipe de Desenvolvimento (Template de Entrega)

  • Analista de Sistemas / PO: [Nome do Estudante]
  • Engenheiro de Software: [Nome do Estudante]
  • DBA / Modelador de Dados: [Nome do Estudante]
  • Desenvolvedor Full-stack: [Nome do Estudante]

🌾 PROJETO 07: AGROSAFE (GESTÃO DE INSUMOS & RECEITUÁRIO AGRONÔMICO)

📘 TUTORIAL AUTOGUIADO PASSO A PASSO — DO ZERO AO DEPLOY NO WINDOWS 10/11

Setor Econômico: Agropecuária e Agronegócio
Domínio: Defensivos Químicos, Rastreabilidade de Lotes e Validades, Receituário Agronômico (ART) e Aplicações em Lavoura
Nível de Complexidade: 🔴 Nível 2: Intermediário (6 Tabelas Relacionais com Rastreabilidade Agro)
Ambiente de Desenvolvimento: Windows 10/11 (PT-BR) + VS Code + Python 3.11+ (Venv)
Stack Principal: Flask 3.x + SQLAlchemy 2.0 + Pydantic v2 + SQLite (Dev) / PostgreSQL (Docker) + Jinja2/Bootstrap 5 + Pytest


flowchart LR
    A["⚙️ 1. Setup Windows/Venv"] --> B["🛢️ 2. Dual-Database"]
    B --> C["🧱 3. Modelos ORM (6 Tabelas)"]
    C --> D["📋 4. Schemas Pydantic"]
    D --> E["🌐 5. Endpoints REST & Blueprint"]
    E --> F["🎨 6. Interface Web Jinja2"]
    F --> G["🧪 7. Testes Pytest (100% Verde)"]
    G --> H["📊 8. 10 Desafios SQL"]
    H --> I["🩺 9. Checklist & Troubleshooting"]

    style A fill:#e3f2fd,stroke:#1565c0
    style B fill:#fff8e1,stroke:#f57f17
    style C fill:#f3e5f5,stroke:#7b1fa2
    style D fill:#ede7f6,stroke:#5e35b1
    style E fill:#e0f2fe,stroke:#0284c7
    style F fill:#fce4ec,stroke:#c2185b
    style G fill:#dcfce7,stroke:#16a34a
    style H fill:#fef3c7,stroke:#d97706
    style I fill:#fee2e2,stroke:#ef4444

📂 0. Estrutura Completa de Pastas e Arquivos no VS Code

Crie exatamente a seguinte estrutura de diretórios no seu computador:

pi_07_agrosafe/
├── app/
│   ├── __init__.py
│   ├── main.py
│   ├── core/
│   │   ├── __init__.py
│   │   └── database.py
│   ├── models/
│   │   ├── __init__.py
│   │   └── agro_models.py
│   ├── schemas/
│   │   ├── __init__.py
│   │   └── agro_schemas.py
│   ├── routers/
│   │   ├── __init__.py
│   │   └── agro_router.py
│   └── templates/
│       ├── base.html
│       └── index.html
├── tests/
│   ├── __init__.py
│   └── test_agrosafe.py
├── .env
├── requirements.txt
└── README.md

⚙️ ETAPA 1: Preparação do Ambiente no Windows 10/11

Abra o VS Code na pasta pi_07_agrosafe e abra o terminal integrado (Ctrl + `).

1.1. Criar e Ativar o Ambiente Virtual (venv)

# 1. Criar o ambiente virtual:
python -m venv venv

# 2. Ativar no Windows PowerShell:
.\venv\Scripts\Activate.ps1

1.2. Criar o Arquivo de Dependências (requirements.txt)

Crie o arquivo requirements.txt:

flask==3.0.3
sqlalchemy==2.0.35
pydantic==2.9.0
jinja2==3.1.4
pytest==8.3.0

Instale as dependências:

pip install -r requirements.txt

1.3. Criar o Arquivo de Variáveis de Ambiente (.env)

DATABASE_URL=sqlite:///./agrosafe.db
APP_ENV=development

🛢️ ETAPA 2: Configuração Dual-Database (app/core/database.py)

Crie app/core/database.py:

import os
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, DeclarativeBase

DATABASE_URL = os.getenv("DATABASE_URL", "sqlite:///./agrosafe.db")
connect_args = {"check_same_thread": False} if DATABASE_URL.startswith("sqlite") else {}

engine = create_engine(DATABASE_URL, connect_args=connect_args, echo=False)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

class Base(DeclarativeBase):
    pass

def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

🧱 ETAPA 3: Modelagem Declarativa ORM (app/models/agro_models.py)

Aqui mapeamos as 6 tabelas relacionais do AgroSafe para rastreabilidade de defensivos, receituário agronômico e aplicações em campo.

Crie app/models/agro_models.py:

from datetime import date
from typing import Optional, List
from decimal import Decimal
from sqlalchemy import String, Integer, Numeric, Date, ForeignKey
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.core.database import Base

class Fabricante(Base):
    __tablename__ = "fabricantes"

    id_fabricante: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    razao_social: Mapped[str] = mapped_column(String(100), nullable=False)

    defensivos: Mapped[List["DefensivoQuimico"]] = relationship(back_populates="fabricante")

class CulturaAgricola(Base):
    __tablename__ = "culturas_agricolas"

    id_cultura: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    nome: Mapped[str] = mapped_column(String(50), nullable=False) # Soja, Milho, Algodão

    aplicacoes: Mapped[List["AplicacaoLavoura"]] = relationship(back_populates="cultura")

class DefensivoQuimico(Base):
    __tablename__ = "defensivos_quimicos"

    id_defensivo: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    nome_comercial: Mapped[str] = mapped_column(String(80), nullable=False)
    principio_ativo: Mapped[str] = mapped_column(String(80), nullable=False)
    classe_toxicologica: Mapped[str] = mapped_column(String(30), nullable=False) # Classe I, II, III, IV
    id_fabricante: Mapped[int] = mapped_column(ForeignKey("fabricantes.id_fabricante"), nullable=False)

    fabricante: Mapped["Fabricante"] = relationship(back_populates="defensivos")
    lotes: Mapped[List["LoteDefensivo"]] = relationship(back_populates="defensivo")

class LoteDefensivo(Base):
    __tablename__ = "lotes_defensivo"

    id_lote: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    id_defensivo: Mapped[int] = mapped_column(ForeignKey("defensivos_quimicos.id_defensivo"), nullable=False)
    numero_lote: Mapped[str] = mapped_column(String(40), nullable=False)
    data_validade: Mapped[date] = mapped_column(Date, nullable=False)
    saldo_estoque: Mapped[Decimal] = mapped_column(Numeric(10, 2), default=Decimal("0.00"))

    defensivo: Mapped["DefensivoQuimico"] = relationship(back_populates="lotes")
    aplicacoes: Mapped[List["AplicacaoLavoura"]] = relationship(back_populates="lote")

class ReceituarioAgronomico(Base):
    __tablename__ = "receituarios_agronomicos"

    id_receituario: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    numero_art: Mapped[str] = mapped_column(String(30), unique=True, nullable=False)
    engenheiro_responsavel: Mapped[str] = mapped_column(String(80), nullable=False)

    aplicacoes: Mapped[List["AplicacaoLavoura"]] = relationship(back_populates="receituario")

class AplicacaoLavoura(Base):
    __tablename__ = "aplicacoes_lavoura"

    id_aplicacao: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    id_lote: Mapped[int] = mapped_column(ForeignKey("lotes_defensivo.id_lote"), nullable=False)
    id_cultura: Mapped[int] = mapped_column(ForeignKey("culturas_agricolas.id_cultura"), nullable=False)
    id_receituario: Mapped[int] = mapped_column(ForeignKey("receituarios_agronomicos.id_receituario"), nullable=False)
    talhao_lavoura: Mapped[str] = mapped_column(String(40), nullable=False)
    data_prevista: Mapped[date] = mapped_column(Date, nullable=False)
    volume_aplicado_litros: Mapped[Optional[Decimal]] = mapped_column(Numeric(10, 2), nullable=True)
    responsavel_aplicador: Mapped[Optional[str]] = mapped_column(String(80), nullable=True)
    status: Mapped[str] = mapped_column(String(20), default="PENDENTE") # PENDENTE / EXECUTADA / CANCELADA

    lote: Mapped["LoteDefensivo"] = relationship(back_populates="aplicacoes")
    cultura: Mapped["CulturaAgricola"] = relationship(back_populates="aplicacoes")
    receituario: Mapped["ReceituarioAgronomico"] = relationship(back_populates="aplicacoes")

📋 ETAPA 4: Schemas de Validação Pydantic (app/schemas/agro_schemas.py)

Crie app/schemas/agro_schemas.py:

from datetime import date
from decimal import Decimal
from typing import Optional
from pydantic import BaseModel, Field

# --- AGENDAMENTO DE APLICAÇÃO ---
class AplicacaoCreate(BaseModel):
    id_lote: int = Field(..., gt=0)
    id_cultura: int = Field(..., gt=0)
    id_receituario: int = Field(..., gt=0)
    talhao_lavoura: str = Field(..., min_length=2, max_length=40)
    data_prevista: date
    volume_aplicado_litros: Decimal = Field(..., gt=0)
    responsavel_aplicador: str = Field(..., min_length=3, max_length=80)

class AplicacaoResponse(BaseModel):
    id_aplicacao: int
    talhao_lavoura: str
    data_prevista: date
    volume_aplicado_litros: Optional[Decimal]
    responsavel_aplicador: Optional[str]
    status: str
    class Config:
        from_attributes = True

# --- EXECUÇÃO DE APLICAÇÃO ---
class ExecucaoAplicacao(BaseModel):
    volume_real: Decimal = Field(..., gt=0)

🌐 ETAPA 5: Endpoints REST (app/routers/agro_router.py) & Inicialização (app/main.py)

5.1. Criar app/routers/agro_router.py

from flask import Blueprint, request, jsonify
from sqlalchemy import select
from app.core.database import SessionLocal
from app.models.agro_models import LoteDefensivo, CulturaAgricola, ReceituarioAgronomico, AplicacaoLavoura
from app.schemas.agro_schemas import AplicacaoCreate, ExecucaoAplicacao

router = Blueprint("agro", __name__, url_prefix="/api")

# --- AGENDAR APLICAÇÃO ---
@router.route("/aplicacoes/", methods=["POST"])
def agendar_aplicacao():
    dados = request.get_json() or {}
    try:
        dto = AplicacaoCreate(**dados)
    except Exception as e:
        return jsonify({"erro": str(e)}), 400

    with SessionLocal() as db:
        lote = db.get(LoteDefensivo, dto.id_lote)
        if not lote or lote.saldo_estoque < dto.volume_aplicado_litros:
            return jsonify({"erro": "Saldo insuficiente no lote de defensivo."}), 400
        if not db.get(CulturaAgricola, dto.id_cultura):
            return jsonify({"erro": "Cultura agrícola não cadastrada."}), 404
        if not db.get(ReceituarioAgronomico, dto.id_receituario):
            return jsonify({"erro": "Receituário agronômico (ART) não localizado."}), 404

        nova = AplicacaoLavoura(
            id_lote=dto.id_lote,
            id_cultura=dto.id_cultura,
            id_receituario=dto.id_receituario,
            talhao_lavoura=dto.talhao_lavoura,
            data_prevista=dto.data_prevista,
            volume_aplicado_litros=dto.volume_aplicado_litros,
            responsavel_aplicador=dto.responsavel_aplicador,
            status="PENDENTE"
        )
        db.add(nova)
        db.commit()
        db.refresh(nova)
        return jsonify({
            "id_aplicacao": nova.id_aplicacao,
            "talhao_lavoura": nova.talhao_lavoura,
            "data_prevista": nova.data_prevista.isoformat(),
            "volume_aplicado_litros": float(nova.volume_aplicado_litros),
            "responsavel_aplicador": nova.responsavel_aplicador,
            "status": nova.status
        }), 201

@router.route("/aplicacoes/", methods=["GET"])
def listar_aplicacoes():
    with SessionLocal() as db:
        aplicacoes = db.scalars(select(AplicacaoLavoura).order_by(AplicacaoLavoura.id_aplicacao.desc())).all()
        return jsonify([{
            "id_aplicacao": a.id_aplicacao,
            "talhao_lavoura": a.talhao_lavoura,
            "data_prevista": a.data_prevista.isoformat(),
            "volume_aplicado_litros": float(a.volume_aplicado_litros) if a.volume_aplicado_litros else None,
            "responsavel_aplicador": a.responsavel_aplicador,
            "status": a.status
        } for a in aplicacoes]), 200

# --- EXECUTAR E BAIXAR SALDO DO LOTE ---
@router.route("/aplicacoes/<int:id_aplicacao>/executar", methods=["PUT"])
def executar_aplicacao(id_aplicacao: int):
    dados = request.get_json() or {}
    try:
        dto = ExecucaoAplicacao(**dados)
    except Exception as e:
        return jsonify({"erro": str(e)}), 400

    with SessionLocal() as db:
        app_db = db.get(AplicacaoLavoura, id_aplicacao)
        if not app_db or app_db.status != "PENDENTE":
            return jsonify({"erro": "Aplicação não encontrada ou já executada."}), 404

        lote = db.get(LoteDefensivo, app_db.id_lote)
        if lote.saldo_estoque < dto.volume_real:
            return jsonify({"erro": "Saldo insuficiente no lote para execução real."}), 400

        # Baixa do estoque
        lote.saldo_estoque -= dto.volume_real
        app_db.volume_aplicado_litros = dto.volume_real
        app_db.status = "EXECUTADA"
        db.commit()
        db.refresh(app_db)
        return jsonify({
            "id_aplicacao": app_db.id_aplicacao,
            "talhao_lavoura": app_db.talhao_lavoura,
            "data_prevista": app_db.data_prevista.isoformat(),
            "volume_aplicado_litros": float(app_db.volume_aplicado_litros),
            "responsavel_aplicador": app_db.responsavel_aplicador,
            "status": app_db.status
        }), 200

5.2. Criar app/main.py

from pathlib import Path
from decimal import Decimal
from datetime import date
from flask import Flask, render_template
from sqlalchemy import select
from app.core.database import engine, Base, SessionLocal
from app.models.agro_models import Fabricante, CulturaAgricola, DefensivoQuimico, LoteDefensivo, ReceituarioAgronomico, AplicacaoLavoura
from app.routers import agro_router

Base.metadata.create_all(bind=engine)

BASE_DIR = Path(__file__).resolve().parent
app = Flask(__name__, template_folder=str(BASE_DIR / "templates"))
app.register_blueprint(agro_router.router)

def seed_dados_iniciais():
    with SessionLocal() as db:
        if not db.scalar(select(Fabricante)):
            f1 = Fabricante(razao_social="AgroQuímica Brasil S/A")
            db.add(f1)
            db.commit()

            c1 = CulturaAgricola(nome="Soja")
            c2 = CulturaAgricola(nome="Milho")
            db.add_all([c1, c2])
            db.commit()

            dq1 = DefensivoQuimico(nome_comercial="Fungicida Forte", principio_ativo="Azoxistrobina", classe_toxicologica="Classe I", id_fabricante=f1.id_fabricante)
            dq2 = DefensivoQuimico(nome_comercial="Herbicida Total", principio_ativo="Glifosato", classe_toxicologica="Classe III", id_fabricante=f1.id_fabricante)
            db.add_all([dq1, dq2])
            db.commit()

            l1 = LoteDefensivo(id_lote=11, id_defensivo=dq1.id_defensivo, numero_lote="LOTE-AGRO-01", data_validade=date(2026, 12, 31), saldo_estoque=Decimal("800.0"))
            l2 = LoteDefensivo(id_lote=12, id_defensivo=dq2.id_defensivo, numero_lote="LOTE-AGRO-02", data_validade=date(2026, 9, 30), saldo_estoque=Decimal("600.0"))
            db.add_all([l1, l2])
            db.commit()

            ra1 = ReceituarioAgronomico(numero_art="ART-2026-999", engenheiro_responsavel="Eng. Roberto Agro")
            db.add(ra1)
            db.commit()

            al1 = AplicacaoLavoura(id_lote=l1.id_lote, id_cultura=c1.id_cultura, id_receituario=ra1.id_receituario, talhao_lavoura="Talhão Norte 01", data_prevista=date(2026, 8, 25), volume_aplicado_litros=Decimal("120.0"), responsavel_aplicador="Tratorista José", status="EXECUTADA")
            al2 = AplicacaoLavoura(id_lote=l2.id_lote, id_cultura=c2.id_cultura, id_receituario=ra1.id_receituario, talhao_lavoura="Talhão Sul 02", data_prevista=date(2026, 8, 28), volume_aplicado_litros=Decimal("50.0"), responsavel_aplicador="Tratorista José", status="PENDENTE")
            db.add_all([al1, al2])
            db.commit()

seed_dados_iniciais()

@app.route("/")
def painel_agricola():
    with SessionLocal() as db:
        lotes = db.scalars(select(LoteDefensivo)).all()
        aplicacoes = db.scalars(select(AplicacaoLavoura).order_by(AplicacaoLavoura.id_aplicacao.desc())).all()
        return render_template("index.html", lotes=lotes, aplicacoes=aplicacoes)

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5000, debug=True)

🔍 Como Executar e Testar a Aplicação

  1. Iniciar Servidor Web e API:
python -m app.main

O servidor estará ativo em: http://localhost:5000

  1. Teste 1: Agendar Aplicação no Talhão via cURL / PowerShell (POST /api/aplicacoes/)
curl -X POST http://localhost:5000/api/aplicacoes/ `
  -H "Content-Type: application/json" `
  -d '{"id_lote": 11, "id_cultura": 1, "id_receituario": 1, "talhao_lavoura": "Talhão Leste 03", "data_prevista": "2026-09-20", "volume_aplicado_litros": 80.0, "responsavel_aplicador": "Tratorista Marcos"}'
  • Resposta esperada: 201 Created com "status": "PENDENTE".
  1. Teste 2: Executar Aplicação com Baixa de Estoque via cURL / PowerShell (PUT /api/aplicacoes/2/executar)
curl -X PUT http://localhost:5000/api/aplicacoes/2/executar `
  -H "Content-Type: application/json" `
  -d '{"volume_real": 50.0}'
  • Resposta esperada: 200 OK com "status": "EXECUTADA".
  1. Acessar Interface Web: Abra o navegador em: http://localhost:5000 para visualizar o monitoramento de talhões e pulverizações.

🎨 ETAPA 6: Interface Web com Jinja2 e Bootstrap 5

6.1. Criar app/templates/base.html

<!DOCTYPE html>
<html lang="pt-br">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>AgroSafe — Gestão de Defensivos</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
</head>
<body class="bg-light">
    <nav class="navbar navbar-expand-lg navbar-dark bg-success bg-gradient">
        <div class="container">
            <a class="navbar-brand fw-bold" href="/"><i class="bi bi-flower1"></i> AgroSafe Agrícola</a>
            <span class="badge bg-light text-dark">Flask 3.x & SQLAlchemy 2.0</span>
        </div>
    </nav>
    <div class="container py-4">
        {% block content %}{% endblock %}
    </div>
</body>
</html>

6.2. Criar app/templates/index.html

{% extends "base.html" %}
{% block content %}
<div class="d-flex justify-content-between align-items-center mb-4">
    <h2><i class="bi bi-geo-alt"></i> Monitoramento de Pulverizações e Talhões</h2>
    <span class="badge bg-success fs-6">{{ aplicacoes|length }} Aplicações Mapeadas</span>
</div>

<div class="card shadow-sm border-0">
    <div class="card-body p-0">
        <table class="table table-hover align-middle mb-0">
            <thead class="table-dark">
                <tr>
                    <th>Talhão</th>
                    <th>Data Prevista</th>
                    <th>Volume (L)</th>
                    <th>Responsável</th>
                    <th>Status</th>
                </tr>
            </thead>
            <tbody>
                {% for a in aplicacoes %}
                <tr>
                    <td class="fw-bold">{{ a.talhao_lavoura }}</td>
                    <td>{{ a.data_prevista }}</td>
                    <td>{{ a.volume_aplicado_litros }} L</td>
                    <td>{{ a.responsavel_aplicador }}</td>
                    <td>
                        {% if a.status == 'EXECUTADA' %}
                            <span class="badge bg-success"><i class="bi bi-check-circle"></i> EXECUTADA</span>
                        {% else %}
                            <span class="badge bg-warning text-dark"><i class="bi bi-hourglass-split"></i> PENDENTE</span>
                        {% endif %}
                    </td>
                </tr>
                {% endfor %}
            </tbody>
        </table>
    </div>
</div>
{% endblock %}

🧪 ETAPA 7: Suíte de Testes Automatizados com Pytest (tests/test_agrosafe.py)

Crie tests/test_agrosafe.py:

import pytest
from app.main import app

@pytest.fixture
def client():
    app.config["TESTING"] = True
    with app.test_client() as client:
        yield client

def test_painel_home_status_code(client):
    res = client.get("/")
    assert res.status_code == 200
    assert b"AgroSafe" in res.data

def test_ciclo_aplicacao_agro(client):
    # 1. Agendamento
    payload = {
        "id_lote": 11,
        "id_cultura": 1,
        "id_receituario": 1,
        "talhao_lavoura": "Talhão 05",
        "data_prevista": "2026-10-01",
        "volume_aplicado_litros": 100.0,
        "responsavel_aplicador": "Operador 01"
    }
    res_in = client.post("/api/aplicacoes/", json=payload)
    assert res_in.status_code == 201
    dados_app = res_in.get_json()
    id_app = dados_app["id_aplicacao"]

    # 2. Execução
    res_exec = client.put(f"/api/aplicacoes/{id_app}/executar", json={"volume_real": 100.0})
    assert res_exec.status_code == 200
    assert res_exec.get_json()["status"] == "EXECUTADA"

Execute no terminal:

pytest -v

📊 ETAPA 8: Bateria de 10 Desafios de SQL Corporativo (AgroSafe)

🎯 Desafio 01: Identificar lotes de defensivos que vencem nos próximos 30 dias.

SELECT id_lote, id_defensivo, numero_lote, data_validade, saldo_estoque FROM lotes_defensivo WHERE data_validade <= date('now', '+30 day') AND saldo_estoque > 0 ORDER BY data_validade ASC;

🔍 Explicação Técnica: Alerta de vencimento de defensivos químicos para priorização de aplicação antes do descarte.

🎯 Desafio 02: Listar as 10 próximas aplicações em lavoura com status 'PENDENTE'.

SELECT id_aplicacao, talhao_lavoura, data_prevista, responsavel_aplicador FROM aplicacoes_lavoura WHERE status = 'PENDENTE' ORDER BY data_prevista ASC LIMIT 10;

🔍 Explicação Técnica: Cronograma operacional de pulverizações do maquinário agrícola.

🎯 Desafio 03: Consumo total de defensivos em litros aplicado por cultura agrícola.

SELECT id_cultura, COUNT(*) AS total_aplicacoes, SUM(volume_aplicado_litros) AS volume_total_litros FROM aplicacoes_lavoura WHERE status = 'EXECUTADA' GROUP BY id_cultura ORDER BY volume_total_litros DESC;

🔍 Explicação Técnica: Apuração do custo agronômico por variedade de safra cultivada.

🎯 Desafio 04: Volume total de calda química aplicada em cada talhão da fazenda.

SELECT talhao_lavoura, COUNT(id_aplicacao) AS total_pulverizacoes, SUM(volume_aplicado_litros) AS calda_total FROM aplicacoes_lavoura WHERE status = 'EXECUTADA' GROUP BY talhao_lavoura ORDER BY calda_total DESC;

🔍 Explicação Técnica: Monitoramento da carga química por hectare para conformidade de certificações ESG.

🎯 Desafio 05: Filtrar talhões com mais de 5 pulverizações executadas.

SELECT talhao_lavoura, COUNT(id_aplicacao) AS total FROM aplicacoes_lavoura WHERE status = 'EXECUTADA' GROUP BY talhao_lavoura HAVING COUNT(id_aplicacao) > 5;

🔍 Explicação Técnica: HAVING para identificação de áreas de alta incidência de pragas e fungos.

🎯 Desafio 06: Listar defensivos com princípio ativo, classe toxicológica e fabricante.

SELECT dq.nome_comercial, dq.principio_ativo, dq.classe_toxicologica, f.razao_social AS fabricante FROM defensivos_quimicos dq INNER JOIN fabricantes f ON dq.id_fabricante = f.id_fabricante;

🔍 Explicação Técnica: Junção relacional para exibição da ficha técnica de segurança química (FISPQ).

🎯 Desafio 07: Rastreabilidade total: aplicação com defensivo, cultura, ART e volume.

SELECT al.id_aplicacao, al.talhao_lavoura, dq.nome_comercial AS defensivo, ca.nome AS cultura, ra.numero_art, al.volume_aplicado_litros, al.status FROM aplicacoes_lavoura al INNER JOIN lotes_defensivo ld ON al.id_lote = ld.id_lote INNER JOIN defensivos_quimicos dq ON ld.id_defensivo = dq.id_defensivo INNER JOIN culturas_agricolas ca ON al.id_cultura = ca.id_cultura INNER JOIN receituarios_agronomicos ra ON al.id_receituario = ra.id_receituario;

🔍 Explicação Técnica: Multi-JOIN unindo 5 tabelas para auditoria do Ministério da Agricultura (MAPA).

🎯 Desafio 08: Identificar lotes de defensivo em estoque que nunca foram utilizados.

SELECT ld.id_lote, ld.numero_lote FROM lotes_defensivo ld LEFT JOIN aplicacoes_lavoura al ON ld.id_lote = al.id_lote WHERE al.id_aplicacao IS NULL;

🔍 Explicação Técnica: LEFT JOIN identificando produtos sem giro no depósito de defensivos.

🎯 Desafio 09: Listar defensivos que já foram aplicados na cultura da 'Soja' (Subquery com IN).

SELECT id_defensivo, nome_comercial FROM defensivos_quimicos WHERE id_defensivo IN (SELECT ld.id_defensivo FROM lotes_defensivo ld INNER JOIN aplicacoes_lavoura al ON ld.id_lote = al.id_lote INNER JOIN culturas_agricolas ca ON al.id_cultura = ca.id_cultura WHERE ca.nome = 'Soja');

🔍 Explicação Técnica: Subquery aninhada para verificação do histórico fitossanitário da lavoura.

🎯 Desafio 10: Dar baixa no lote de defensivo após pulverização com retorno de saldo.

UPDATE lotes_defensivo SET saldo_estoque = saldo_estoque - 50.0 WHERE id_lote = 12 AND saldo_estoque >= 50.0 RETURNING id_lote, numero_lote, saldo_estoque;

🔍 Explicação Técnica: DML seguro com retorno atômico para atualização instantânea dos saldos de estoque.


🩺 ETAPA 9: Troubleshooting no Windows & Checklist de Entrega

🛠️ Resolução Rápida de Erros Frequentes:

  1. Porta 5000 já em uso (OSError: [Errno 10048] address already in use):
    Causa: Uma instância anterior do Flask continua rodando em segundo plano.
    Solução: No PowerShell:
    Get-Process -Name python* | Stop-Process -Force
    
  2. Erro 400 Bad Request: Saldo insuficiente no lote:
    Causa: O volume solicitado é maior do que o saldo atual cadastrado na tabela lotes_defensivo.
  3. Erro 404 Not Found: Receituário agronômico (ART) não localizado:
    Causa: O id_receituario informado não existe.

✅ Checklist de Conclusão do Aluno:

  • Ambiente virtual venv configurado e ativado no Windows.
  • Dependências instaladas (flask, sqlalchemy, pydantic, pytest).
  • Banco de dados SQLite criado com as 6 tabelas relacionais.
  • Agendamento e execução de aplicação testados via endpoints JSON /api/aplicacoes/.
  • Interface visual listando as aplicações nos talhões da lavoura na porta 5000.
  • Suíte de testes pytest -v passando com 100% de sucesso via client.test_client().
  • 10 Desafios de SQL executados no DBeaver/pgAdmin.

🌾 AgroSafe — Guia de Execução e README do Projeto

Setor: Agropecuária e Agronegócio
Componente: Atividades de Projetos II / III
Classificação: 🟢 Nível 1: Essencial / Básico (3 a 4 Tabelas)
Repositório Template: https://github.com/fatec-gti/pi_07_agrosafe


🚀 1. Como Executar o Projeto Localmente

Passo 1: Clonar o Repositório e Abrir no VS Code

git clone https://github.com/fatec-gti/pi_07_agrosafe.git
code pi_07_agrosafe

Passo 2: Criar o Ambiente Virtual e Instalar Dependências

python -m venv venv
# Windows (PowerShell):
.\venv\Scripts\Activate.ps1
# Linux/Mac:
source venv/bin/activate

pip install flask sqlalchemy jinja2 psycopg2-binary

Passo 3: Executar a Aplicação com SQLite (Zero Configuração)

python main.py

Acesse no navegador:

  • 🌐 Interface Web & API REST: http://localhost:5000

🐳 2. Executando com PostgreSQL no Docker Compose

Para subir o banco de dados oficial de produção:

docker compose up -d

Edite o arquivo .env para apontar para o PostgreSQL:

DATABASE_URL=postgresql://postgres:senha@localhost:5432/pi_07_agrosafe_db

👥 3. Equipe de Desenvolvimento (Template de Entrega)

  • Analista de Sistemas / PO: [Nome do Estudante]
  • Engenheiro de Software: [Nome do Estudante]
  • DBA / Modelador de Dados: [Nome do Estudante]
  • Desenvolvedor Full-stack: [Nome do Estudante]

🐂 PROJETO 08: CATTLEFLOW (PECUÁRIA DE CORTE & RASTREABILIDADE)

📘 TUTORIAL AUTOGUIADO PASSO A PASSO — DO ZERO AO DEPLOY NO WINDOWS 10/11

Setor Econômico: Agropecuária e Agronegócio
Domínio: Pecuária de Corte e Confinamento, Rastreabilidade Individual de Bovinos (Brinco), Manejo de Pastos e Histórico de Pesagens (GPD)
Nível de Complexidade: 🔴 Nível 2: Intermediário (5 Tabelas Relacionais com Histórico Temporal de Pesagens)
Ambiente de Desenvolvimento: Windows 10/11 (PT-BR) + VS Code + Python 3.11+ (Venv)
Stack Principal: Flask 3.x + SQLAlchemy 2.0 + Pydantic v2 + SQLite (Dev) / PostgreSQL (Docker) + Jinja2/Bootstrap 5 + Pytest


flowchart LR
    A["⚙️ 1. Setup Windows/Venv"] --> B["🛢️ 2. Dual-Database"]
    B --> C["🧱 3. Modelos ORM (5 Tabelas)"]
    C --> D["📋 4. Schemas Pydantic"]
    D --> E["🌐 5. Endpoints REST & Blueprint"]
    E --> F["🎨 6. Interface Web Jinja2"]
    F --> G["🧪 7. Testes Pytest (100% Verde)"]
    G --> H["📊 8. 10 Desafios SQL"]
    H --> I["🩺 9. Checklist & Troubleshooting"]

    style A fill:#e3f2fd,stroke:#1565c0
    style B fill:#fff8e1,stroke:#f57f17
    style C fill:#f3e5f5,stroke:#7b1fa2
    style D fill:#ede7f6,stroke:#5e35b1
    style E fill:#e0f2fe,stroke:#0284c7
    style F fill:#fce4ec,stroke:#c2185b
    style G fill:#dcfce7,stroke:#16a34a
    style H fill:#fef3c7,stroke:#d97706
    style I fill:#fee2e2,stroke:#ef4444

📂 0. Estrutura Completa de Pastas e Arquivos no VS Code

Crie exatamente a seguinte estrutura de diretórios no seu computador:

pi_08_cattleflow/
├── app/
│   ├── __init__.py
│   ├── main.py
│   ├── core/
│   │   ├── __init__.py
│   │   └── database.py
│   ├── models/
│   │   ├── __init__.py
│   │   └── pecuaria_models.py
│   ├── schemas/
│   │   ├── __init__.py
│   │   └── pecuaria_schemas.py
│   ├── routers/
│   │   ├── __init__.py
│   │   └── pecuaria_router.py
│   └── templates/
│       ├── base.html
│       └── index.html
├── tests/
│   ├── __init__.py
│   └── test_cattleflow.py
├── .env
├── requirements.txt
└── README.md

⚙️ ETAPA 1: Preparação do Ambiente no Windows 10/11

Abra o VS Code na pasta pi_08_cattleflow e abra o terminal integrado (Ctrl + `).

1.1. Criar e Ativar o Ambiente Virtual (venv)

# 1. Criar o ambiente virtual:
python -m venv venv

# 2. Ativar no Windows PowerShell:
.\venv\Scripts\Activate.ps1

1.2. Criar o Arquivo de Dependências (requirements.txt)

Crie o arquivo requirements.txt:

flask==3.0.3
sqlalchemy==2.0.35
pydantic==2.9.0
jinja2==3.1.4
pytest==8.3.0

Instale as dependências:

pip install -r requirements.txt

1.3. Criar o Arquivo de Variáveis de Ambiente (.env)

DATABASE_URL=sqlite:///./cattleflow.db
APP_ENV=development

🛢️ ETAPA 2: Configuração Dual-Database (app/core/database.py)

Crie app/core/database.py:

import os
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, DeclarativeBase

DATABASE_URL = os.getenv("DATABASE_URL", "sqlite:///./cattleflow.db")
connect_args = {"check_same_thread": False} if DATABASE_URL.startswith("sqlite") else {}

engine = create_engine(DATABASE_URL, connect_args=connect_args, echo=False)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

class Base(DeclarativeBase):
    pass

def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

🧱 ETAPA 3: Modelagem Declarativa ORM (app/models/pecuaria_models.py)

Aqui mapeamos as 5 tabelas relacionais do CattleFlow para rastreabilidade de animais, pesagens e vacinações.

Crie app/models/pecuaria_models.py:

from datetime import date
from typing import Optional, List
from decimal import Decimal
from sqlalchemy import String, Integer, Numeric, Date, ForeignKey, Boolean
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.core.database import Base

class Raca(Base):
    __tablename__ = "racas"

    id_raca: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    nome_raca: Mapped[str] = mapped_column(String(50), nullable=False) # Nelore, Angus, Braford

    animais: Mapped[List["AnimalBovino"]] = relationship(back_populates="raca")

class PastoPiquete(Base):
    __tablename__ = "pastos_piquetes"

    id_pasto: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    identificacao_pasto: Mapped[str] = mapped_column(String(40), nullable=False)
    area_hectares: Mapped[Decimal] = mapped_column(Numeric(10, 2), nullable=False)

    animais: Mapped[List["AnimalBovino"]] = relationship(back_populates="pasto")

class AnimalBovino(Base):
    __tablename__ = "animais_bovinos"

    id_animal: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    brinco_identificador: Mapped[str] = mapped_column(String(30), unique=True, nullable=False)
    id_raca: Mapped[int] = mapped_column(ForeignKey("racas.id_raca"), nullable=False)
    id_pasto: Mapped[int] = mapped_column(ForeignKey("pastos_piquetes.id_pasto"), nullable=False)
    sexo: Mapped[str] = mapped_column(String(1), nullable=False) # M ou F
    data_nascimento: Mapped[date] = mapped_column(Date, nullable=False)
    peso_atual: Mapped[Decimal] = mapped_column(Numeric(10, 2), nullable=False)
    ativo: Mapped[bool] = mapped_column(Boolean, default=True)

    raca: Mapped["Raca"] = relationship(back_populates="animais")
    pasto: Mapped["PastoPiquete"] = relationship(back_populates="animais")
    pesagens: Mapped[List["PesagemHistorico"]] = relationship(back_populates="animal", cascade="all, delete-orphan")
    vacinacoes: Mapped[List["VacinacaoSanitaria"]] = relationship(back_populates="animal", cascade="all, delete-orphan")

class PesagemHistorico(Base):
    __tablename__ = "pesagens_historico"

    id_pesagem: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    id_animal: Mapped[int] = mapped_column(ForeignKey("animais_bovinos.id_animal"), nullable=False)
    data_pesagem: Mapped[date] = mapped_column(Date, default=date.today)
    peso_kg: Mapped[Decimal] = mapped_column(Numeric(10, 2), nullable=False)
    ganho_diario_medio: Mapped[Optional[Decimal]] = mapped_column(Numeric(6, 3), nullable=True)

    animal: Mapped["AnimalBovino"] = relationship(back_populates="pesagens")

class VacinacaoSanitaria(Base):
    __tablename__ = "vacinacoes_sanitarias"

    id_vacinacao: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    id_animal: Mapped[int] = mapped_column(ForeignKey("animais_bovinos.id_animal"), nullable=False)
    nome_vacina: Mapped[str] = mapped_column(String(80), nullable=False)
    data_aplicacao: Mapped[date] = mapped_column(Date, default=date.today)
    dose_ml: Mapped[Optional[Decimal]] = mapped_column(Numeric(5, 2), nullable=True)
    veterinario_responsavel: Mapped[Optional[str]] = mapped_column(String(80), nullable=True)

    animal: Mapped["AnimalBovino"] = relationship(back_populates="vacinacoes")

📋 ETAPA 4: Schemas de Validação Pydantic (app/schemas/pecuaria_schemas.py)

Crie app/schemas/pecuaria_schemas.py:

from datetime import date
from decimal import Decimal
from typing import Optional
from pydantic import BaseModel, Field

# --- CADASTRO DE ANIMAL ---
class AnimalCreate(BaseModel):
    brinco_identificador: str = Field(..., min_length=3, max_length=30)
    id_raca: int = Field(..., gt=0)
    id_pasto: int = Field(..., gt=0)
    sexo: str = Field(..., min_length=1, max_length=1)
    data_nascimento: date
    peso_atual: Decimal = Field(..., gt=0)

class AnimalResponse(AnimalCreate):
    id_animal: int
    ativo: bool
    class Config:
        from_attributes = True

# --- REGISTRO DE PESAGEM (MANEJO) ---
class PesagemCreate(BaseModel):
    id_animal: int = Field(..., gt=0)
    peso_kg: Decimal = Field(..., gt=0)

🌐 ETAPA 5: Endpoints REST (app/routers/pecuaria_router.py) & Inicialização (app/main.py)

5.1. Criar app/routers/pecuaria_router.py

from datetime import date
from flask import Blueprint, request, jsonify
from sqlalchemy import select
from app.core.database import SessionLocal
from app.models.pecuaria_models import Raca, PastoPiquete, AnimalBovino, PesagemHistorico
from app.schemas.pecuaria_schemas import AnimalCreate, PesagemCreate

router = Blueprint("pecuaria", __name__, url_prefix="/api")

# --- ANIMAIS ---
@router.route("/animais/", methods=["POST"])
def cadastrar_animal():
    dados = request.get_json() or {}
    try:
        dto = AnimalCreate(**dados)
    except Exception as e:
        return jsonify({"erro": str(e)}), 400

    with SessionLocal() as db:
        if not db.get(Raca, dto.id_raca):
            return jsonify({"erro": "Raça não encontrada."}), 404
        if not db.get(PastoPiquete, dto.id_pasto):
            return jsonify({"erro": "Pasto/Piquete informado não existe."}), 404
        if db.scalar(select(AnimalBovino).where(AnimalBovino.brinco_identificador == dto.brinco_identificador)):
            return jsonify({"erro": "Brinco de identificação já cadastrado."}), 400

        novo = AnimalBovino(
            brinco_identificador=dto.brinco_identificador,
            id_raca=dto.id_raca,
            id_pasto=dto.id_pasto,
            sexo=dto.sexo,
            data_nascimento=dto.data_nascimento,
            peso_atual=dto.peso_atual,
            ativo=True
        )
        db.add(novo)
        db.commit()
        db.refresh(novo)
        return jsonify({
            "id_animal": novo.id_animal,
            "brinco_identificador": novo.brinco_identificador,
            "id_raca": novo.id_raca,
            "id_pasto": novo.id_pasto,
            "sexo": novo.sexo,
            "data_nascimento": novo.data_nascimento.isoformat(),
            "peso_atual": float(novo.peso_atual),
            "ativo": novo.ativo
        }), 201

@router.route("/animais/", methods=["GET"])
def listar_rebanho():
    with SessionLocal() as db:
        animais = db.scalars(select(AnimalBovino).where(AnimalBovino.ativo == True).order_by(AnimalBovino.id_animal.asc())).all()
        return jsonify([{
            "id_animal": a.id_animal,
            "brinco_identificador": a.brinco_identificador,
            "id_raca": a.id_raca,
            "id_pasto": a.id_pasto,
            "sexo": a.sexo,
            "data_nascimento": a.data_nascimento.isoformat(),
            "peso_atual": float(a.peso_atual),
            "ativo": a.ativo
        } for a in animais]), 200

# --- PESAGEM & GPD ---
@router.route("/pesagens/", methods=["POST"])
def registrar_pesagem():
    dados = request.get_json() or {}
    try:
        dto = PesagemCreate(**dados)
    except Exception as e:
        return jsonify({"erro": str(e)}), 400

    with SessionLocal() as db:
        animal = db.get(AnimalBovino, dto.id_animal)
        if not animal or not animal.ativo:
            return jsonify({"erro": "Animal não localizado ou inativo."}), 404

        # Atualiza peso atual do animal
        animal.peso_atual = dto.peso_kg

        nova_pesagem = PesagemHistorico(
            id_animal=dto.id_animal,
            data_pesagem=date.today(),
            peso_kg=dto.peso_kg
        )
        db.add(nova_pesagem)
        db.commit()
        return jsonify({"message": "Pesagem registrada com sucesso!", "novo_peso": float(dto.peso_kg)}), 201

5.2. Criar app/main.py

from pathlib import Path
from decimal import Decimal
from datetime import date
from flask import Flask, render_template
from sqlalchemy import select
from app.core.database import engine, Base, SessionLocal
from app.models.pecuaria_models import Raca, PastoPiquete, AnimalBovino, PesagemHistorico, VacinacaoSanitaria
from app.routers import pecuaria_router

Base.metadata.create_all(bind=engine)

BASE_DIR = Path(__file__).resolve().parent
app = Flask(__name__, template_folder=str(BASE_DIR / "templates"))
app.register_blueprint(pecuaria_router.router)

def seed_dados_iniciais():
    with SessionLocal() as db:
        if not db.scalar(select(Raca)):
            r1 = Raca(nome_raca="Nelore")
            r2 = Raca(nome_raca="Angus")
            r3 = Raca(nome_raca="Braford")
            db.add_all([r1, r2, r3])
            db.commit()

            p1 = PastoPiquete(identificacao_pasto="Pasto 01 (Mombaça)", area_hectares=Decimal("25.0"))
            p2 = PastoPiquete(identificacao_pasto="Pasto 02 (Brachiaria)", area_hectares=Decimal("35.0"))
            db.add_all([p1, p2])
            db.commit()

            a1 = AnimalBovino(id_animal=801, brinco_identificador="BR-NEL-1001", id_raca=r1.id_raca, id_pasto=p1.id_pasto, sexo="M", data_nascimento=date(2024, 1, 10), peso_atual=Decimal("480.00"), ativo=True)
            a2 = AnimalBovino(id_animal=802, brinco_identificador="BR-NEL-1002", id_raca=r1.id_raca, id_pasto=p1.id_pasto, sexo="F", data_nascimento=date(2024, 2, 15), peso_atual=Decimal("420.00"), ativo=True)
            a3 = AnimalBovino(id_animal=803, brinco_identificador="BR-ANG-2001", id_raca=r2.id_raca, id_pasto=p2.id_pasto, sexo="M", data_nascimento=date(2024, 3, 1), peso_atual=Decimal("510.00"), ativo=True)
            db.add_all([a1, a2, a3])
            db.commit()

            pes1 = PesagemHistorico(id_animal=a1.id_animal, data_pesagem=date(2026, 7, 1), peso_kg=Decimal("450.0"), ganho_diario_medio=Decimal("1.200"))
            pes2 = PesagemHistorico(id_animal=a1.id_animal, data_pesagem=date(2026, 8, 1), peso_kg=Decimal("480.0"), ganho_diario_medio=Decimal("1.000"))
            db.add_all([pes1, pes2])

            vac1 = VacinacaoSanitaria(id_animal=a1.id_animal, nome_vacina="Vacina Febre Aftosa Bivalente", data_aplicacao=date(2026, 5, 10), dose_ml=Decimal("2.0"), veterinario_responsavel="Dra. Julia Vet")
            db.add(vac1)
            db.commit()

seed_dados_iniciais()

@app.route("/")
def painel_rebanho():
    with SessionLocal() as db:
        animais = db.scalars(select(AnimalBovino).where(AnimalBovino.ativo == True)).all()
        pastos = db.scalars(select(PastoPiquete)).all()
        return render_template("index.html", animais=animais, pastos=pastos)

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5000, debug=True)

🔍 Como Executar e Testar a Aplicação

  1. Iniciar Servidor Web e API:
python -m app.main

O servidor estará ativo em: http://localhost:5000

  1. Teste 1: Cadastrar Animal Bovino via cURL / PowerShell (POST /api/animais/)
curl -X POST http://localhost:5000/api/animais/ `
  -H "Content-Type: application/json" `
  -d '{"brinco_identificador": "BR-NEL-9999", "id_raca": 1, "id_pasto": 1, "sexo": "M", "data_nascimento": "2024-05-10", "peso_atual": 465.50}'
  • Resposta esperada: 201 Created.
  1. Teste 2: Registrar Nova Pesagem via cURL / PowerShell (POST /api/pesagens/)
curl -X POST http://localhost:5000/api/pesagens/ `
  -H "Content-Type: application/json" `
  -d '{"id_animal": 801, "peso_kg": 495.00}'
  • Resposta esperada: 201 Created com "novo_peso": 495.0.
  1. Acessar Interface Web: Abra o navegador em: http://localhost:5000 para visualizar o censo do rebanho.

🎨 ETAPA 6: Interface Web com Jinja2 e Bootstrap 5

6.1. Criar app/templates/base.html

<!DOCTYPE html>
<html lang="pt-br">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>CattleFlow — Manejo de Rebanho</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
</head>
<body class="bg-light">
    <nav class="navbar navbar-expand-lg navbar-dark bg-success bg-gradient">
        <div class="container">
            <a class="navbar-brand fw-bold" href="/"><i class="bi bi-shield-check"></i> CattleFlow Pecuária</a>
            <span class="badge bg-light text-dark">Flask 3.x & SQLAlchemy 2.0</span>
        </div>
    </nav>
    <div class="container py-4">
        {% block content %}{% endblock %}
    </div>
</body>
</html>

6.2. Criar app/templates/index.html

{% extends "base.html" %}
{% block content %}
<div class="d-flex justify-content-between align-items-center mb-4">
    <h2><i class="bi bi-clipboard2-pulse"></i> Censo do Rebanho Bovino</h2>
    <span class="badge bg-success fs-6">{{ animais|length }} Cabeças Ativas</span>
</div>

<div class="card shadow-sm border-0">
    <div class="card-body p-0">
        <table class="table table-hover align-middle mb-0">
            <thead class="table-dark">
                <tr>
                    <th>Brinco</th>
                    <th>Sexo</th>
                    <th>Nascimento</th>
                    <th>Peso Atual (KG)</th>
                    <th>Pasto Alocado</th>
                    <th>Status</th>
                </tr>
            </thead>
            <tbody>
                {% for a in animais %}
                <tr>
                    <td><code>{{ a.brinco_identificador }}</code></td>
                    <td>
                        {% if a.sexo == 'M' %}
                            <span class="badge bg-primary">Macho</span>
                        {% else %}
                            <span class="badge bg-danger">Fêmea</span>
                        {% endif %}
                    </td>
                    <td>{{ a.data_nascimento }}</td>
                    <td class="fw-bold fs-6">{{ a.peso_atual }} kg</td>
                    <td>Pasto ID: {{ a.id_pasto }}</td>
                    <td><span class="badge bg-success">ATIVO</span></td>
                </tr>
                {% endfor %}
            </tbody>
        </table>
    </div>
</div>
{% endblock %}

🧪 ETAPA 7: Suíte de Testes Automatizados com Pytest (tests/test_cattleflow.py)

Crie tests/test_cattleflow.py:

import pytest
from app.main import app

@pytest.fixture
def client():
    app.config["TESTING"] = True
    with app.test_client() as client:
        yield client

def test_painel_home_status_code(client):
    res = client.get("/")
    assert res.status_code == 200
    assert b"CattleFlow" in res.data

def test_manejo_bovino(client):
    # 1. Cadastrar Animal
    payload = {
        "brinco_identificador": "BR-TEST-999",
        "id_raca": 1,
        "id_pasto": 1,
        "sexo": "M",
        "data_nascimento": "2024-06-01",
        "peso_atual": 430.00
    }
    res = client.post("/api/animais/", json=payload)
    assert res.status_code == 201
    id_animal = res.get_json()["id_animal"]

    # 2. Registrar Pesagem
    res_pes = client.post("/api/pesagens/", json={"id_animal": id_animal, "peso_kg": 460.00})
    assert res_pes.status_code == 201
    assert res_pes.get_json()["novo_peso"] == 460.0

Execute no terminal:

pytest -v

📊 ETAPA 8: Bateria de 10 Desafios de SQL Corporativo (CattleFlow)

🎯 Desafio 01: Identificar animais aptos para abate (peso >= 450 kg) ordenados por peso.

SELECT id_animal, brinco_identificador, sexo, peso_atual FROM animais_bovinos WHERE peso_atual >= 450.00 AND ativo = 1 ORDER BY peso_atual DESC;

🔍 Explicação Técnica: Filtro de seleção de lote de corte para despacho ao frigorífico.

🎯 Desafio 02: Consultar os 10 animais mais jovens nascidos no rebanho.

SELECT id_animal, brinco_identificador, data_nascimento, peso_atual FROM animais_bovinos WHERE ativo = 1 ORDER BY data_nascimento DESC LIMIT 10;

🔍 Explicação Técnica: Acompanhamento da taxa de natalidade e bezerros em fase de desmame.

🎯 Desafio 03: Média de peso atual e total de cabeças agrupados por raça bovina.

SELECT id_raca, COUNT(*) AS total_cabecas, AVG(peso_atual) AS peso_medio_raca FROM animais_bovinos WHERE ativo = 1 GROUP BY id_raca ORDER BY total_cabecas DESC;

🔍 Explicação Técnica: Estatísticas zootécnicas para avaliação de ganho genético entre raças.

🎯 Desafio 04: Calcular a taxa de lotação (Cabeças/Hectare) de cada piquete de pastagem.

SELECT p.identificacao_pasto, COUNT(a.id_animal) AS lotacao_cabecas, (COUNT(a.id_animal) / p.area_hectares) AS taxa_lotacao_ha FROM animais_bovinos a INNER JOIN pastos_piquetes p ON a.id_pasto = p.id_pasto WHERE a.ativo = 1 GROUP BY p.identificacao_pasto, p.area_hectares ORDER BY taxa_lotacao_ha DESC;

🔍 Explicação Técnica: Expressão agregada calculando a pressão de pastejo para manejo rotacionado.

🎯 Desafio 05: Filtrar pastos com superlotação (mais de 50 cabeças alocadas).

SELECT p.identificacao_pasto, COUNT(a.id_animal) AS total FROM animais_bovinos a INNER JOIN pastos_piquetes p ON a.id_pasto = p.id_pasto GROUP BY p.identificacao_pasto HAVING COUNT(a.id_animal) > 50;

🔍 Explicação Técnica: HAVING para emissão de alertas de remanejamento e conservação de forragem.

🎯 Desafio 06: Relatório de animais com nome da raça e identificação do piquete alocado.

SELECT a.brinco_identificador, r.nome_raca, p.identificacao_pasto, a.peso_atual FROM animais_bovinos a INNER JOIN racas r ON a.id_raca = r.id_raca INNER JOIN pastos_piquetes p ON a.id_pasto = p.id_pasto WHERE a.ativo = 1;

🔍 Explicação Técnica: Junção tripla para conferência do censo da fazenda em campo.

🎯 Desafio 07: Histórico de vacinações sanitárias aplicadas com identificação do médico veterinário.

SELECT a.brinco_identificador, vs.nome_vacina, vs.data_aplicacao, vs.dose_ml, vs.veterinario_responsavel FROM vacinacoes_sanitarias vs INNER JOIN animais_bovinos a ON vs.id_animal = a.id_animal ORDER BY vs.data_aplicacao DESC;

🔍 Explicação Técnica: Relatório de conformidade sanitária para emissão de Guia de Trânsito Animal (GTA).

🎯 Desafio 08: Identificar pastos ou piquetes que estão totalmente vazios (sem animais).

SELECT p.id_pasto, p.identificacao_pasto FROM pastos_piquetes p LEFT JOIN animais_bovinos a ON p.id_pasto = a.id_pasto WHERE a.id_animal IS NULL;

🔍 Explicação Técnica: LEFT JOIN identificando áreas em período de descanso e vedação da pastagem.

🎯 Desafio 09: Listar animais com peso superior à média geral de todo o rebanho ativo.

SELECT id_animal, brinco_identificador, peso_atual FROM animais_bovinos WHERE peso_atual > (SELECT AVG(peso_atual) FROM animais_bovinos WHERE ativo = 1);

🔍 Explicação Técnica: Subquery escalar no predicado WHERE para seleção de reprodutores de alta performance.

🎯 Desafio 10: Transferir animal para novo piquete de pasto com retorno imediato.

UPDATE animais_bovinos SET id_pasto = 5 WHERE id_animal = 801 RETURNING id_animal, brinco_identificador, id_pasto;

🔍 Explicação Técnica: Manejo de rotação de pastagem executado atomicamente com retorno dos dados de confirmação.


🩺 ETAPA 9: Troubleshooting no Windows & Checklist de Entrega

🛠️ Resolução Rápida de Erros Frequentes:

  1. Porta 5000 já em uso (OSError: [Errno 10048] address already in use):
    Causa: Uma instância anterior do Flask continua rodando em segundo plano.
    Solução: No PowerShell:
    Get-Process -Name python* | Stop-Process -Force
    
  2. Erro 400 Bad Request: Brinco de identificação já cadastrado:
    Causa: O número de brinco do animal é único. Verifique se o código do brinco já foi cadastrado anteriormente.
  3. Erro 404 Not Found: Pasto/Piquete informado não existe:
    Causa: O id_pasto informado no JSON não consta na tabela pastos_piquetes.

✅ Checklist de Conclusão do Aluno:

  • Ambiente virtual venv configurado e ativado no Windows.
  • Dependências instaladas (flask, sqlalchemy, pydantic, pytest).
  • Banco de dados SQLite criado com as 5 tabelas relacionais.
  • Cadastro de animal e registro de pesagens testados via endpoints JSON /api/animais/ e /api/pesagens/.
  • Interface visual listando o rebanho ativo com pesos atualizados na porta 5000.
  • Suíte de testes pytest -v passando com 100% de sucesso via client.test_client().
  • 10 Desafios de SQL executados no DBeaver/pgAdmin.

🐂 CattleFlow — Guia de Execução e README do Projeto

Setor: Agropecuária e Agronegócio
Componente: Atividades de Projetos II / III
Classificação: 🔴 Nível 2: Intermediário / Avançado (5 a 7 Tabelas)
Repositório Template: https://github.com/fatec-gti/pi_08_cattleflow


🚀 1. Como Executar o Projeto Localmente

Passo 1: Clonar o Repositório e Abrir no VS Code

git clone https://github.com/fatec-gti/pi_08_cattleflow.git
code pi_08_cattleflow

Passo 2: Criar o Ambiente Virtual e Instalar Dependências

python -m venv venv
# Windows (PowerShell):
.\venv\Scripts\Activate.ps1
# Linux/Mac:
source venv/bin/activate

pip install flask sqlalchemy jinja2 psycopg2-binary

Passo 3: Executar a Aplicação com SQLite (Zero Configuração)

python main.py

Acesse no navegador:

  • 🌐 Interface Web & API REST: http://localhost:5000

🐳 2. Executando com PostgreSQL no Docker Compose

Para subir o banco de dados oficial de produção:

docker compose up -d

Edite o arquivo .env para apontar para o PostgreSQL:

DATABASE_URL=postgresql://postgres:senha@localhost:5432/pi_08_cattleflow_db

👥 3. Equipe de Desenvolvimento (Template de Entrega)

  • Analista de Sistemas / PO: [Nome do Estudante]
  • Engenheiro de Software: [Nome do Estudante]
  • DBA / Modelador de Dados: [Nome do Estudante]
  • Desenvolvedor Full-stack: [Nome do Estudante]

💰 PROJETO 09: FINLITE (CONTAS A PAGAR/RECEBER & TESOURARIA)

📘 TUTORIAL AUTOGUIADO PASSO A PASSO — DO ZERO AO DEPLOY NO WINDOWS 10/11

Setor Econômico: Financeiro e Gestão Empresarial (ERP Finanças)
Domínio: Contas a Pagar e Receber, Fluxo de Caixa, Centros de Custo, Baixas/Liquidações e Conciliação Bancária
Nível de Complexidade: 🟢 Nível 1: Essencial / Básico (5 Tabelas Relacionais com Fluxo de Caixa)
Ambiente de Desenvolvimento: Windows 10/11 (PT-BR) + VS Code + Python 3.11+ (Venv)
Stack Principal: Flask 3.x + SQLAlchemy 2.0 + Pydantic v2 + SQLite (Dev) / PostgreSQL (Docker) + Jinja2/Bootstrap 5 + Pytest


flowchart LR
    A["⚙️ 1. Setup Windows/Venv"] --> B["🛢️ 2. Dual-Database"]
    B --> C["🧱 3. Modelos ORM (5 Tabelas)"]
    C --> D["📋 4. Schemas Pydantic"]
    D --> E["🌐 5. Endpoints REST & Blueprint"]
    E --> F["🎨 6. Interface Web Jinja2"]
    F --> G["🧪 7. Testes Pytest (100% Verde)"]
    G --> H["📊 8. 10 Desafios SQL"]
    H --> I["🩺 9. Checklist & Troubleshooting"]

    style A fill:#e3f2fd,stroke:#1565c0
    style B fill:#fff8e1,stroke:#f57f17
    style C fill:#f3e5f5,stroke:#7b1fa2
    style D fill:#ede7f6,stroke:#5e35b1
    style E fill:#e0f2fe,stroke:#0284c7
    style F fill:#fce4ec,stroke:#c2185b
    style G fill:#dcfce7,stroke:#16a34a
    style H fill:#fef3c7,stroke:#d97706
    style I fill:#fee2e2,stroke:#ef4444

📂 0. Estrutura Completa de Pastas e Arquivos no VS Code

Crie exatamente a seguinte estrutura de diretórios no seu computador:

pi_09_finlite/
├── app/
│   ├── __init__.py
│   ├── main.py
│   ├── core/
│   │   ├── __init__.py
│   │   └── database.py
│   ├── models/
│   │   ├── __init__.py
│   │   └── financeiro_models.py
│   ├── schemas/
│   │   ├── __init__.py
│   │   └── financeiro_schemas.py
│   ├── routers/
│   │   ├── __init__.py
│   │   └── financeiro_router.py
│   └── templates/
│       ├── base.html
│       └── index.html
├── tests/
│   ├── __init__.py
│   └── test_finlite.py
├── .env
├── requirements.txt
└── README.md

⚙️ ETAPA 1: Preparação do Ambiente no Windows 10/11

Abra o VS Code na pasta pi_09_finlite e abra o terminal integrado (Ctrl + `).

1.1. Criar e Ativar o Ambiente Virtual (venv)

# 1. Criar o ambiente virtual:
python -m venv venv

# 2. Ativar no Windows PowerShell:
.\venv\Scripts\Activate.ps1

1.2. Criar o Arquivo de Dependências (requirements.txt)

Crie o arquivo requirements.txt:

flask==3.0.3
sqlalchemy==2.0.35
pydantic==2.9.0
jinja2==3.1.4
pytest==8.3.0

Instale as dependências:

pip install -r requirements.txt

1.3. Criar o Arquivo de Variáveis de Ambiente (.env)

DATABASE_URL=sqlite:///./finlite.db
APP_ENV=development

🛢️ ETAPA 2: Configuração Dual-Database (app/core/database.py)

Crie app/core/database.py:

import os
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, DeclarativeBase

DATABASE_URL = os.getenv("DATABASE_URL", "sqlite:///./finlite.db")
connect_args = {"check_same_thread": False} if DATABASE_URL.startswith("sqlite") else {}

engine = create_engine(DATABASE_URL, connect_args=connect_args, echo=False)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

class Base(DeclarativeBase):
    pass

def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

🧱 ETAPA 3: Modelagem Declarativa ORM (app/models/financeiro_models.py)

Aqui mapeamos as 5 tabelas relacionais do FinLite para contas a pagar/receber, conciliação e tesouraria.

Crie app/models/financeiro_models.py:

from datetime import date
from typing import Optional, List
from decimal import Decimal
from sqlalchemy import String, Integer, Numeric, Date, ForeignKey
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.core.database import Base

class CentroCusto(Base):
    __tablename__ = "centros_custo"

    id_centro_custo: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    nome: Mapped[str] = mapped_column(String(60), nullable=False) # TI, Comercial, Produção, ADM

    titulos: Mapped[List["TituloFinanceiro"]] = relationship(back_populates="centro_custo")

class ContaBancaria(Base):
    __tablename__ = "contas_bancarias"

    id_conta: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    banco: Mapped[str] = mapped_column(String(50), nullable=False)
    agencia: Mapped[str] = mapped_column(String(10), nullable=False)
    numero_conta: Mapped[str] = mapped_column(String(20), nullable=False)
    saldo_atual: Mapped[Decimal] = mapped_column(Numeric(12, 2), default=Decimal("0.00"))

    baixas: Mapped[List["BaixaTitulo"]] = relationship(back_populates="conta")
    extratos: Mapped[List["ExtratoBancario"]] = relationship(back_populates="conta")

class TituloFinanceiro(Base):
    __tablename__ = "titulos_financeiros"

    id_titulo: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    descricao: Mapped[str] = mapped_column(String(100), nullable=False)
    tipo_operacao: Mapped[str] = mapped_column(String(10), nullable=False) # PAGAR ou RECEBER
    id_centro_custo: Mapped[int] = mapped_column(ForeignKey("centros_custo.id_centro_custo"), nullable=False)
    valor_original: Mapped[Decimal] = mapped_column(Numeric(10, 2), nullable=False)
    data_emissao: Mapped[date] = mapped_column(Date, default=date.today)
    data_vencimento: Mapped[date] = mapped_column(Date, nullable=False)
    status: Mapped[str] = mapped_column(String(20), default="ABERTO") # ABERTO, LIQUIDADO, CANCELADO

    centro_custo: Mapped["CentroCusto"] = relationship(back_populates="titulos")
    baixas: Mapped[List["BaixaTitulo"]] = relationship(back_populates="titulo", cascade="all, delete-orphan")

class BaixaTitulo(Base):
    __tablename__ = "baixas_titulos"

    id_baixa: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    id_titulo: Mapped[int] = mapped_column(ForeignKey("titulos_financeiros.id_titulo"), nullable=False)
    id_conta: Mapped[int] = mapped_column(ForeignKey("contas_bancarias.id_conta"), nullable=False)
    data_baixa: Mapped[date] = mapped_column(Date, default=date.today)
    valor_pago: Mapped[Decimal] = mapped_column(Numeric(10, 2), nullable=False)
    juros_multa: Mapped[Decimal] = mapped_column(Numeric(10, 2), default=Decimal("0.00"))
    desconto_obtido: Mapped[Decimal] = mapped_column(Numeric(10, 2), default=Decimal("0.00"))

    titulo: Mapped["TituloFinanceiro"] = relationship(back_populates="baixas")
    conta: Mapped["ContaBancaria"] = relationship(back_populates="baixas")

class ExtratoBancario(Base):
    __tablename__ = "extratos_bancarios"

    id_extrato: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    id_conta: Mapped[int] = mapped_column(ForeignKey("contas_bancarias.id_conta"), nullable=False)
    data_movimento: Mapped[date] = mapped_column(Date, default=date.today)
    descricao: Mapped[str] = mapped_column(String(100), nullable=False)
    valor_movimento: Mapped[Decimal] = mapped_column(Numeric(10, 2), nullable=False)
    tipo_movimento: Mapped[str] = mapped_column(String(1), nullable=False) # C (Crédito) ou D (Débito)

    conta: Mapped["ContaBancaria"] = relationship(back_populates="extratos")

📋 ETAPA 4: Schemas de Validação Pydantic (app/schemas/financeiro_schemas.py)

Crie app/schemas/financeiro_schemas.py:

from datetime import date
from decimal import Decimal
from typing import Optional
from pydantic import BaseModel, Field

# --- LANÇAMENTO DE TÍTULO ---
class TituloCreate(BaseModel):
    descricao: str = Field(..., min_length=3, max_length=100)
    tipo_operacao: str = Field(..., pattern="^(PAGAR|RECEBER)$")
    id_centro_custo: int = Field(..., gt=0)
    valor_original: Decimal = Field(..., gt=0)
    data_vencimento: date

class TituloResponse(TituloCreate):
    id_titulo: int
    data_emissao: date
    status: str
    class Config:
        from_attributes = True

# --- BAIXA / LIQUIDAÇÃO ---
class BaixaCreate(BaseModel):
    id_conta: int = Field(..., gt=0)
    valor_pago: Decimal = Field(..., gt=0)
    juros_multa: Decimal = Field(default=Decimal("0.00"), ge=0)
    desconto_obtido: Decimal = Field(default=Decimal("0.00"), ge=0)

🌐 ETAPA 5: Endpoints REST (app/routers/financeiro_router.py) & Inicialização (app/main.py)

5.1. Criar app/routers/financeiro_router.py

from datetime import date
from flask import Blueprint, request, jsonify
from sqlalchemy import select
from app.core.database import SessionLocal
from app.models.financeiro_models import CentroCusto, ContaBancaria, TituloFinanceiro, BaixaTitulo, ExtratoBancario
from app.schemas.financeiro_schemas import TituloCreate, BaixaCreate

router = Blueprint("financeiro", __name__, url_prefix="/api")

# --- LANÇAMENTO DE TÍTULO ---
@router.route("/titulos/", methods=["POST"])
def criar_titulo():
    dados = request.get_json() or {}
    try:
        dto = TituloCreate(**dados)
    except Exception as e:
        return jsonify({"erro": str(e)}), 400

    with SessionLocal() as db:
        if not db.get(CentroCusto, dto.id_centro_custo):
            return jsonify({"erro": "Centro de Custo não encontrado."}), 404

        novo = TituloFinanceiro(
            descricao=dto.descricao,
            tipo_operacao=dto.tipo_operacao,
            id_centro_custo=dto.id_centro_custo,
            valor_original=dto.valor_original,
            data_emissao=date.today(),
            data_vencimento=dto.data_vencimento,
            status="ABERTO"
        )
        db.add(novo)
        db.commit()
        db.refresh(novo)
        return jsonify({
            "id_titulo": novo.id_titulo,
            "descricao": novo.descricao,
            "tipo_operacao": novo.tipo_operacao,
            "id_centro_custo": novo.id_centro_custo,
            "valor_original": float(novo.valor_original),
            "data_emissao": novo.data_emissao.isoformat(),
            "data_vencimento": novo.data_vencimento.isoformat(),
            "status": novo.status
        }), 201

@router.route("/titulos/", methods=["GET"])
def listar_titulos():
    with SessionLocal() as db:
        titulos = db.scalars(select(TituloFinanceiro).order_by(TituloFinanceiro.data_vencimento.asc())).all()
        return jsonify([{
            "id_titulo": t.id_titulo,
            "descricao": t.descricao,
            "tipo_operacao": t.tipo_operacao,
            "id_centro_custo": t.id_centro_custo,
            "valor_original": float(t.valor_original),
            "data_emissao": t.data_emissao.isoformat(),
            "data_vencimento": t.data_vencimento.isoformat(),
            "status": t.status
        } for t in titulos]), 200

# --- BAIXA E ATUALIZAÇÃO DE SALDO BANCÁRIO ---
@router.route("/titulos/<int:id_titulo>/baixar", methods=["POST"])
def liquidar_titulo(id_titulo: int):
    dados = request.get_json() or {}
    try:
        dto = BaixaCreate(**dados)
    except Exception as e:
        return jsonify({"erro": str(e)}), 400

    with SessionLocal() as db:
        titulo = db.get(TituloFinanceiro, id_titulo)
        if not titulo or titulo.status != "ABERTO":
            return jsonify({"erro": "Título não encontrado ou já liquidado."}), 404

        conta = db.get(ContaBancaria, dto.id_conta)
        if not conta:
            return jsonify({"erro": "Conta bancária inexistente."}), 404

        # Atualiza saldo bancário
        if titulo.tipo_operacao == "PAGAR":
            conta.saldo_atual -= dto.valor_pago
            tipo_ext = "D"
        else:
            conta.saldo_atual += dto.valor_pago
            tipo_ext = "C"

        baixa = BaixaTitulo(
            id_titulo=id_titulo,
            id_conta=dto.id_conta,
            valor_pago=dto.valor_pago,
            juros_multa=dto.juros_multa,
            desconto_obtido=dto.desconto_obtido,
            data_baixa=date.today()
        )
        extrato = ExtratoBancario(
            id_conta=conta.id_conta,
            data_movimento=date.today(),
            descricao=f"Baixa: {titulo.descricao}",
            valor_movimento=dto.valor_pago,
            tipo_movimento=tipo_ext
        )
        titulo.status = "LIQUIDADO"

        db.add_all([baixa, extrato])
        db.commit()
        return jsonify({"message": "Título liquidado com sucesso!", "saldo_conta": float(conta.saldo_atual)}), 200

5.2. Criar app/main.py

from pathlib import Path
from decimal import Decimal
from datetime import date
from flask import Flask, render_template
from sqlalchemy import select
from app.core.database import engine, Base, SessionLocal
from app.models.financeiro_models import CentroCusto, ContaBancaria, TituloFinanceiro, BaixaTitulo, ExtratoBancario
from app.routers import financeiro_router

Base.metadata.create_all(bind=engine)

BASE_DIR = Path(__file__).resolve().parent
app = Flask(__name__, template_folder=str(BASE_DIR / "templates"))
app.register_blueprint(financeiro_router.router)

def seed_dados_iniciais():
    with SessionLocal() as db:
        if not db.scalar(select(CentroCusto)):
            cc1 = CentroCusto(nome="TI e Tecnologia")
            cc2 = CentroCusto(nome="Comercial e Vendas")
            cc3 = CentroCusto(nome="Administrativo")
            db.add_all([cc1, cc2, cc3])
            db.commit()

            cb1 = ContaBancaria(id_conta=1, banco="Banco do Brasil", agencia="1234-5", numero_conta="998877-0", saldo_atual=Decimal("50000.00"))
            cb2 = ContaBancaria(id_conta=2, banco="Itaú Unibanco", agencia="4321-0", numero_conta="112233-4", saldo_atual=Decimal("25000.00"))
            db.add_all([cb1, cb2])
            db.commit()

            t1 = TituloFinanceiro(id_titulo=901, descricao="Aluguel Galpão Industrial", tipo_operacao="PAGAR", id_centro_custo=cc3.id_centro_custo, valor_original=Decimal("3500.00"), data_emissao=date(2026, 8, 1), data_vencimento=date(2026, 8, 10), status="ABERTO")
            t2 = TituloFinanceiro(id_titulo=902, descricao="Recebimento Consultoria ERP", tipo_operacao="RECEBER", id_centro_custo=cc2.id_centro_custo, valor_original=Decimal("12000.00"), data_emissao=date(2026, 8, 5), data_vencimento=date(2026, 8, 25), status="ABERTO")
            t3 = TituloFinanceiro(id_titulo=903, descricao="Servidor Dedicado Cloud", tipo_operacao="PAGAR", id_centro_custo=cc1.id_centro_custo, valor_original=Decimal("2400.00"), data_emissao=date(2026, 7, 1), data_vencimento=date(2026, 7, 10), status="LIQUIDADO")
            db.add_all([t1, t2, t3])
            db.commit()

            b1 = BaixaTitulo(id_titulo=t3.id_titulo, id_conta=cb1.id_conta, data_baixa=date(2026, 7, 10), valor_pago=Decimal("2400.00"), juros_multa=Decimal("0.00"), desconto_obtido=Decimal("0.00"))
            db.add(b1)

            eb1 = ExtratoBancario(id_conta=cb1.id_conta, data_movimento=date(2026, 7, 10), descricao="Pagamento Servidor Dedicado Cloud", valor_movimento=Decimal("2400.00"), tipo_movimento="D")
            db.add(eb1)
            db.commit()

seed_dados_iniciais()

@app.route("/")
def painel_financeiro():
    with SessionLocal() as db:
        titulos = db.scalars(select(TituloFinanceiro).order_by(TituloFinanceiro.data_vencimento.asc())).all()
        contas = db.scalars(select(ContaBancaria)).all()
        return render_template("index.html", titulos=titulos, contas=contas)

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5000, debug=True)

🔍 Como Executar e Testar a Aplicação

  1. Iniciar Servidor Web e API:
python -m app.main

O servidor estará ativo em: http://localhost:5000

  1. Teste 1: Lançar Conta a Pagar via cURL / PowerShell (POST /api/titulos/)
curl -X POST http://localhost:5000/api/titulos/ `
  -H "Content-Type: application/json" `
  -d '{"descricao": "Licença Software Gestão", "tipo_operacao": "PAGAR", "id_centro_custo": 1, "valor_original": 1200.00, "data_vencimento": "2026-09-30"}'
  • Resposta esperada: 201 Created com "status": "ABERTO".
  1. Teste 2: Baixar Título com Atualização Bancária via cURL / PowerShell (POST /api/titulos/901/baixar)
curl -X POST http://localhost:5000/api/titulos/901/baixar `
  -H "Content-Type: application/json" `
  -d '{"id_conta": 1, "valor_pago": 3500.00, "juros_multa": 0.0, "desconto_obtido": 0.0}'
  • Resposta esperada: 200 OK com "saldo_conta": 46500.0.
  1. Acessar Interface Web: Abra o navegador em: http://localhost:5000 para visualizar o painel financeiro e saldos de contas.

🎨 ETAPA 6: Interface Web com Jinja2 e Bootstrap 5

6.1. Criar app/templates/base.html

<!DOCTYPE html>
<html lang="pt-br">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>FinLite — Gestão Financeira</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
</head>
<body class="bg-light">
    <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
        <div class="container">
            <a class="navbar-brand fw-bold text-success" href="/"><i class="bi bi-cash-stack"></i> FinLite Tesouraria</a>
            <span class="badge bg-success">Flask 3.x & SQLAlchemy 2.0</span>
        </div>
    </nav>
    <div class="container py-4">
        {% block content %}{% endblock %}
    </div>
</body>
</html>

6.2. Criar app/templates/index.html

{% extends "base.html" %}
{% block content %}
<div class="row mb-4">
    {% for c in contas %}
    <div class="col-md-6 mb-3">
        <div class="card shadow-sm border-0 bg-white">
            <div class="card-body">
                <h5 class="card-title text-muted"><i class="bi bi-bank"></i> {{ c.banco }} (Ag: {{ c.agencia }})</h5>
                <h3 class="fw-bold text-primary">R$ {{ "%.2f"|format(c.saldo_atual) }}</h3>
                <small class="text-secondary">Conta: {{ c.numero_conta }}</small>
            </div>
        </div>
    </div>
    {% endfor %}
</div>

<div class="card shadow-sm border-0">
    <div class="card-header bg-dark text-white fw-bold">
        <i class="bi bi-wallet2"></i> Livro de Contas a Pagar e Receber
    </div>
    <div class="card-body p-0">
        <table class="table table-hover align-middle mb-0">
            <thead class="table-light">
                <tr>
                    <th>Descrição</th>
                    <th>Tipo</th>
                    <th>Valor</th>
                    <th>Vencimento</th>
                    <th>Status</th>
                </tr>
            </thead>
            <tbody>
                {% for t in titulos %}
                <tr>
                    <td class="fw-bold">{{ t.descricao }}</td>
                    <td>
                        {% if t.tipo_operacao == 'PAGAR' %}
                            <span class="badge bg-danger">A PAGAR</span>
                        {% else %}
                            <span class="badge bg-success">A RECEBER</span>
                        {% endif %}
                    </td>
                    <td>R$ {{ "%.2f"|format(t.valor_original) }}</td>
                    <td>{{ t.data_vencimento }}</td>
                    <td>
                        {% if t.status == 'LIQUIDADO' %}
                            <span class="badge bg-secondary">LIQUIDADO</span>
                        {% else %}
                            <span class="badge bg-warning text-dark">ABERTO</span>
                        {% endif %}
                    </td>
                </tr>
                {% endfor %}
            </tbody>
        </table>
    </div>
</div>
{% endblock %}

🧪 ETAPA 7: Suíte de Testes Automatizados com Pytest (tests/test_finlite.py)

Crie tests/test_finlite.py:

import pytest
from app.main import app

@pytest.fixture
def client():
    app.config["TESTING"] = True
    with app.test_client() as client:
        yield client

def test_painel_home_status_code(client):
    res = client.get("/")
    assert res.status_code == 200
    assert b"FinLite" in res.data

def test_fluxo_financeiro(client):
    # 1. Lançar Título a Pagar
    payload = {
        "descricao": "Anúncios Google Ads",
        "tipo_operacao": "PAGAR",
        "id_centro_custo": 1,
        "valor_original": 800.00,
        "data_vencimento": "2026-09-15"
    }
    res = client.post("/api/titulos/", json=payload)
    assert res.status_code == 201
    id_tit = res.get_json()["id_titulo"]

    # 2. Baixar Título (utilizando a conta 1 populada no seed)
    res_baixa = client.post(f"/api/titulos/{id_tit}/baixar", json={"id_conta": 1, "valor_pago": 800.00})
    assert res_baixa.status_code == 200
    dados = res_baixa.get_json()
    assert "saldo_conta" in dados

Execute no terminal:

pytest -v

📊 ETAPA 8: Bateria de 10 Desafios de SQL Corporativo (FinLite)

🎯 Desafio 01: Identificar contas a pagar que estão em atraso (Vencimento < Hoje e Abertas).

SELECT id_titulo, descricao, valor_original, data_vencimento, status FROM titulos_financeiros WHERE tipo_operacao = 'PAGAR' AND status = 'ABERTO' AND data_vencimento < date('now') ORDER BY data_vencimento ASC;

🔍 Explicação Técnica: Alerta de inadimplência e juros para o setor de contas a pagar.

🎯 Desafio 02: Previsão de contas a receber nos próximos 30 dias.

SELECT id_titulo, descricao, valor_original, data_vencimento FROM titulos_financeiros WHERE tipo_operacao = 'RECEBER' AND status = 'ABERTO' AND data_vencimento BETWEEN date('now') AND date('now', '+30 day') ORDER BY data_vencimento ASC LIMIT 10;

🔍 Explicação Técnica: Projeção de entradas para cálculo de liquidez e capital de giro.

🎯 Desafio 03: Total financeiro agrupado por tipo de operação (PAGAR/RECEBER) e status.

SELECT tipo_operacao, status, COUNT(*) AS qtd_titulos, SUM(valor_original) AS total_financeiro FROM titulos_financeiros GROUP BY tipo_operacao, status ORDER BY tipo_operacao, status;

🔍 Explicação Técnica: Resumo executivo da carteira financeira para a diretoria.

🎯 Desafio 04: Despesas totais agrupadas por centro de custo da empresa.

SELECT cc.nome AS centro_custo, SUM(t.valor_original) AS total_despesas FROM titulos_financeiros t INNER JOIN centros_custo cc ON t.id_centro_custo = cc.id_centro_custo WHERE t.tipo_operacao = 'PAGAR' GROUP BY cc.nome ORDER BY total_despesas DESC;

🔍 Explicação Técnica: Análise orçamentária por departamento para gestão de custos corporativos.

🎯 Desafio 05: Filtrar centros de custo com despesas acumuladas superiores a R$ 10.000,00.

SELECT cc.nome AS centro_custo, SUM(t.valor_original) AS total_despesas FROM titulos_financeiros t INNER JOIN centros_custo cc ON t.id_centro_custo = cc.id_centro_custo WHERE t.tipo_operacao = 'PAGAR' GROUP BY cc.nome HAVING SUM(t.valor_original) > 10000.00;

🔍 Explicação Técnica: Filtro HAVING para identificação de áreas que demandam auditoria de gastos.

🎯 Desafio 06: Extrato de liquidações: títulos pagos com valor baixado e conta de débito.

SELECT t.descricao, t.tipo_operacao, t.valor_original, b.data_baixa, b.valor_pago, cb.banco FROM titulos_financeiros t INNER JOIN baixas_titulos b ON t.id_titulo = b.id_titulo INNER JOIN contas_bancarias cb ON b.id_conta = cb.id_conta;

🔍 Explicação Técnica: Junção tripla para conferência e conciliação de tesouraria.

🎯 Desafio 07: Livro razão bancário: movimentações financeiras com banco e conta.

SELECT eb.data_movimento, eb.descricao AS historico, eb.valor_movimento, eb.tipo_movimento, cb.banco, cb.numero_conta FROM extratos_bancarios eb INNER JOIN contas_bancarias cb ON eb.id_conta = cb.id_conta ORDER BY eb.data_movimento DESC;

🔍 Explicação Técnica: Relatório cronológico de extrato para auditoria contábil.

🎯 Desafio 08: Identificar títulos financeiros que ainda não possuem nenhuma baixa (Subquery NOT IN).

SELECT id_titulo, descricao, valor_original, tipo_operacao FROM titulos_financeiros WHERE id_titulo NOT IN (SELECT id_titulo FROM baixas_titulos);

🔍 Explicação Técnica: Auditoria de títulos em aberto sem movimentação financeira.

🎯 Desafio 09: Listar contas a pagar com valor acima da média das despesas da empresa.

SELECT id_titulo, descricao, valor_original FROM titulos_financeiros WHERE valor_original > (SELECT AVG(valor_original) FROM titulos_financeiros WHERE tipo_operacao = 'PAGAR');

🔍 Explicação Técnica: Subquery escalar para identificação de desembolsos vultosos que exigem dupla aprovação.

🎯 Desafio 10: Atualizar saldo bancário debitando valor de pagamento com retorno atômico.

UPDATE contas_bancarias SET saldo_atual = saldo_atual - 3500.00 WHERE id_conta = 1 AND saldo_atual >= 3500.00 RETURNING id_conta, banco, saldo_atual;

🔍 Explicação Técnica: DML seguro com retorno atômico garantindo que o saldo nunca fique negativo sem autorização de cheque especial.


🩺 ETAPA 9: Troubleshooting no Windows & Checklist de Entrega

🛠️ Resolução Rápida de Erros Frequentes:

  1. Porta 5000 já em uso (OSError: [Errno 10048] address already in use):
    Causa: Uma instância anterior do Flask continua rodando em segundo plano.
    Solução: No PowerShell:
    Get-Process -Name python* | Stop-Process -Force
    
  2. Erro 404 Not Found: Centro de Custo não encontrado:
    Causa: O id_centro_custo fornecido não existe na base de dados.
  3. Erro 400 Bad Request: Dados inválidos:
    Causa: O campo tipo_operacao deve ser exatamente "PAGAR" ou "RECEBER".

✅ Checklist de Conclusão do Aluno:

  • Ambiente virtual venv configurado e ativado no Windows.
  • Dependências instaladas (flask, sqlalchemy, pydantic, pytest).
  • Banco de dados SQLite criado com as 5 tabelas relacionais.
  • Lançamento e liquidação de títulos testados via endpoints JSON /api/titulos/.
  • Interface visual exibindo os saldos das contas bancárias e títulos em aberto na porta 5000.
  • Suíte de testes pytest -v passando com 100% de sucesso via client.test_client().
  • 10 Desafios de SQL executados no DBeaver/pgAdmin.

💰 FinLite — Guia de Execução e README do Projeto

Setor: Financeiro e Imobiliário
Componente: Atividades de Projetos II / III
Classificação: 🟢 Nível 1: Essencial / Básico (3 a 4 Tabelas)
Repositório Template: https://github.com/fatec-gti/pi_09_finlite


🚀 1. Como Executar o Projeto Localmente

Passo 1: Clonar o Repositório e Abrir no VS Code

git clone https://github.com/fatec-gti/pi_09_finlite.git
code pi_09_finlite

Passo 2: Criar o Ambiente Virtual e Instalar Dependências

python -m venv venv
# Windows (PowerShell):
.\venv\Scripts\Activate.ps1
# Linux/Mac:
source venv/bin/activate

pip install flask sqlalchemy jinja2 psycopg2-binary

Passo 3: Executar a Aplicação com SQLite (Zero Configuração)

python main.py

Acesse no navegador:

  • 🌐 Interface Web & API REST: http://localhost:5000

🐳 2. Executando com PostgreSQL no Docker Compose

Para subir o banco de dados oficial de produção:

docker compose up -d

Edite o arquivo .env para apontar para o PostgreSQL:

DATABASE_URL=postgresql://postgres:senha@localhost:5432/pi_09_finlite_db

👥 3. Equipe de Desenvolvimento (Template de Entrega)

  • Analista de Sistemas / PO: [Nome do Estudante]
  • Engenheiro de Software: [Nome do Estudante]
  • DBA / Modelador de Dados: [Nome do Estudante]
  • Desenvolvedor Full-stack: [Nome do Estudante]

🏢 PROJETO 10: IMOBIFLOW (GESTÃO E LOCAÇÃO DE IMÓVEIS)

📘 TUTORIAL AUTOGUIADO PASSO A PASSO — DO ZERO AO DEPLOY NO WINDOWS 10/11

Setor Econômico: Financeiro e Mercado Imobiliário
Domínio: Gestão e Locação de Imóveis, Contratos, Vistorias, Faturas de Aluguel e Repasses a Proprietários
Nível de Complexidade: 🔴 Nível 2: Intermediário (6 Tabelas Relacionais com Contratos e Repasses)
Ambiente de Desenvolvimento: Windows 10/11 (PT-BR) + VS Code + Python 3.11+ (Venv)
Stack Principal: Flask 3.x + SQLAlchemy 2.0 + Pydantic v2 + SQLite (Dev) / PostgreSQL (Docker) + Jinja2/Bootstrap 5 + Pytest


flowchart LR
    A["⚙️ 1. Setup Windows/Venv"] --> B["🛢️ 2. Dual-Database"]
    B --> C["🧱 3. Modelos ORM (6 Tabelas)"]
    C --> D["📋 4. Schemas Pydantic"]
    D --> E["🌐 5. Endpoints REST (Flask Blueprints)"]
    E --> F["🎨 6. Interface Web Jinja2"]
    F --> G["🧪 7. Testes Pytest (100% Verde)"]
    G --> H["📊 8. 10 Desafios SQL"]
    H --> I["🩺 9. Checklist & Troubleshooting"]

    style A fill:#e3f2fd,stroke:#1565c0
    style B fill:#fff8e1,stroke:#f57f17
    style C fill:#f3e5f5,stroke:#7b1fa2
    style D fill:#ede7f6,stroke:#5e35b1
    style E fill:#e0f2fe,stroke:#0284c7
    style F fill:#fce4ec,stroke:#c2185b
    style G fill:#dcfce7,stroke:#16a34a
    style H fill:#fef3c7,stroke:#d97706
    style I fill:#fee2e2,stroke:#ef4444

📂 0. Estrutura Completa de Pastas e Arquivos no VS Code

Crie exatamente a seguinte estrutura de diretórios no seu computador:

pi_10_imobiflow/
├── app/
│   ├── __init__.py
│   ├── main.py
│   ├── core/
│   │   ├── __init__.py
│   │   └── database.py
│   ├── models/
│   │   ├── __init__.py
│   │   └── imobiliaria_models.py
│   ├── schemas/
│   │   ├── __init__.py
│   │   └── imobiliaria_schemas.py
│   ├── routers/
│   │   ├── __init__.py
│   │   └── imobiliaria_router.py
│   └── templates/
│       ├── base.html
│       └── index.html
├── tests/
│   ├── __init__.py
│   └── test_imobiflow.py
├── .env
├── requirements.txt
└── README.md

⚙️ ETAPA 1: Preparação do Ambiente no Windows 10/11

Abra o VS Code na pasta pi_10_imobiflow e abra o terminal integrado (Ctrl + `).

1.1. Criar e Ativar o Ambiente Virtual (venv)

# 1. Criar o ambiente virtual:
python -m venv venv

# 2. Ativar no Windows PowerShell:
.\venv\Scripts\Activate.ps1

1.2. Criar o Arquivo de Dependências (requirements.txt)

Crie o arquivo requirements.txt:

flask==3.0.3
sqlalchemy==2.0.35
pydantic==2.9.0
jinja2==3.1.4
pytest==8.3.0

Instale as dependências:

pip install -r requirements.txt

1.3. Criar o Arquivo de Variáveis de Ambiente (.env)

DATABASE_URL=sqlite:///./imobiflow.db
APP_ENV=development

🛢️ ETAPA 2: Configuração Dual-Database (app/core/database.py)

Crie app/core/database.py:

import os
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, DeclarativeBase

DATABASE_URL = os.getenv("DATABASE_URL", "sqlite:///./imobiflow.db")
connect_args = {"check_same_thread": False} if DATABASE_URL.startswith("sqlite") else {}

engine = create_engine(DATABASE_URL, connect_args=connect_args, echo=False)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

class Base(DeclarativeBase):
    pass

def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

🧱 ETAPA 3: Modelagem Declarativa ORM (app/models/imobiliaria_models.py)

Aqui mapeamos as 6 tabelas relacionais do ImobiFlow para contratos de locação, vistorias e faturamento com repasse a proprietários.

Crie app/models/imobiliaria_models.py:

from datetime import date
from typing import Optional, List
from decimal import Decimal
from sqlalchemy import String, Integer, Numeric, Date, Text, ForeignKey, Boolean
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.core.database import Base

class Proprietario(Base):
    __tablename__ = "proprietarios"

    id_proprietario: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    nome: Mapped[str] = mapped_column(String(100), nullable=False)
    telefone: Mapped[Optional[str]] = mapped_column(String(20), nullable=True)

    imoveis: Mapped[List["Imovel"]] = relationship(back_populates="proprietario")

class Inquilino(Base):
    __tablename__ = "inquilinos"

    id_inquilino: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    nome: Mapped[str] = mapped_column(String(100), nullable=False)
    telefone: Mapped[Optional[str]] = mapped_column(String(20), nullable=True)
    email: Mapped[Optional[str]] = mapped_column(String(100), nullable=True)

    contratos: Mapped[List["ContratoLocacao"]] = relationship(back_populates="inquilino")

class Imovel(Base):
    __tablename__ = "imoveis"

    id_imovel: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    id_proprietario: Mapped[int] = mapped_column(ForeignKey("proprietarios.id_proprietario"), nullable=False)
    endereco: Mapped[str] = mapped_column(String(150), nullable=False)
    bairro: Mapped[str] = mapped_column(String(60), nullable=False)
    cidade: Mapped[str] = mapped_column(String(60), nullable=False)
    valor_aluguel_sugerido: Mapped[Decimal] = mapped_column(Numeric(10, 2), nullable=False)
    status: Mapped[str] = mapped_column(String(20), default="DISPONIVEL") # DISPONIVEL, ALUGADO, REFORMA

    proprietario: Mapped["Proprietario"] = relationship(back_populates="imoveis")
    contratos: Mapped[List["ContratoLocacao"]] = relationship(back_populates="imovel")

class ContratoLocacao(Base):
    __tablename__ = "contratos_locacao"

    id_contrato: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    id_imovel: Mapped[int] = mapped_column(ForeignKey("imoveis.id_imovel"), nullable=False)
    id_inquilino: Mapped[int] = mapped_column(ForeignKey("inquilinos.id_inquilino"), nullable=False)
    data_inicio: Mapped[date] = mapped_column(Date, nullable=False)
    data_fim: Mapped[date] = mapped_column(Date, nullable=False)
    valor_aluguel_pactuado: Mapped[Decimal] = mapped_column(Numeric(10, 2), nullable=False)
    dia_vencimento: Mapped[int] = mapped_column(Integer, default=10)
    taxa_adm_percentual: Mapped[Decimal] = mapped_column(Numeric(5, 2), default=Decimal("10.00")) # 10%
    status: Mapped[str] = mapped_column(String(20), default="ATIVO") # ATIVO, ENCERRADO, CANCELADO

    imovel: Mapped["Imovel"] = relationship(back_populates="contratos")
    inquilino: Mapped["Inquilino"] = relationship(back_populates="contratos")
    faturas: Mapped[List["FaturaAluguel"]] = relationship(back_populates="contrato", cascade="all, delete-orphan")
    vistorias: Mapped[List["VistoriaImovel"]] = relationship(back_populates="contrato")

class VistoriaImovel(Base):
    __tablename__ = "vistorias_imovel"

    id_vistoria: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    id_imovel: Mapped[int] = mapped_column(ForeignKey("imoveis.id_imovel"), nullable=False)
    id_contrato: Mapped[Optional[int]] = mapped_column(ForeignKey("contratos_locacao.id_contrato"), nullable=True)
    tipo_vistoria: Mapped[str] = mapped_column(String(20), nullable=False) # ENTRADA ou SAIDA
    data_vistoria: Mapped[date] = mapped_column(Date, default=date.today)
    laudo_texto: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
    aprovada: Mapped[bool] = mapped_column(Boolean, default=True)

    contrato: Mapped[Optional["ContratoLocacao"]] = relationship(back_populates="vistorias")

class FaturaAluguel(Base):
    __tablename__ = "faturas_aluguel"

    id_fatura: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    id_contrato: Mapped[int] = mapped_column(ForeignKey("contratos_locacao.id_contrato"), nullable=False)
    mes_referencia: Mapped[str] = mapped_column(String(7), nullable=False) # 2026-08
    valor_aluguel: Mapped[Decimal] = mapped_column(Numeric(10, 2), nullable=False)
    data_vencimento: Mapped[date] = mapped_column(Date, nullable=False)
    data_pagamento: Mapped[Optional[date]] = mapped_column(Date, nullable=True)
    valor_repasse_proprietario: Mapped[Decimal] = mapped_column(Numeric(10, 2), nullable=False)
    status: Mapped[str] = mapped_column(String(20), default="PENDENTE") # PENDENTE ou PAGA

    contrato: Mapped["ContratoLocacao"] = relationship(back_populates="faturas")

📋 ETAPA 4: Schemas de Validação Pydantic (app/schemas/imobiliaria_schemas.py)

Crie app/schemas/imobiliaria_schemas.py:

from datetime import date
from decimal import Decimal
from typing import Optional
from pydantic import BaseModel, Field

# --- NOVO CONTRATO ---
class ContratoCreate(BaseModel):
    id_imovel: int = Field(..., gt=0, example=1)
    id_inquilino: int = Field(..., gt=0, example=1)
    data_inicio: date = Field(..., example="2026-09-01")
    data_fim: date = Field(..., example="2027-08-31")
    valor_aluguel_pactuado: Decimal = Field(..., gt=0, example=2500.00)
    dia_vencimento: int = Field(default=10, ge=1, le=28)
    taxa_adm_percentual: Decimal = Field(default=Decimal("10.00"), ge=0, le=100)

class ContratoResponse(ContratoCreate):
    id_contrato: int
    status: str
    class Config:
        from_attributes = True

# --- FATURA & PAGAMENTO ---
class PagamentoFatura(BaseModel):
    data_pagamento: date = Field(default_factory=date.today)

🌐 ETAPA 5: Endpoints REST (app/routers/imobiliaria_router.py) & Inicialização (app/main.py)

5.1. Criar app/routers/imobiliaria_router.py

from datetime import date
from decimal import Decimal
from flask import Blueprint, request, jsonify
from sqlalchemy.orm import Session
from sqlalchemy import select
from app.core.database import SessionLocal
from app.models.imobiliaria_models import Imovel, Inquilino, ContratoLocacao, FaturaAluguel
from app.schemas.imobiliaria_schemas import ContratoCreate, PagamentoFatura

router = Blueprint("imobiliaria", __name__, url_prefix="/api")

# --- FORMALIZAR CONTRATO ---
@router.post("/contratos/")
def formalizar_contrato():
    dados = request.get_json() or {}
    try:
        payload = ContratoCreate(**dados)
    except Exception as err:
        return jsonify({"detail": str(err)}), 400

    with SessionLocal() as db:
        imovel = db.get(Imovel, payload.id_imovel)
        if not imovel or imovel.status != "DISPONIVEL":
            return jsonify({"detail": "Imóvel não encontrado ou indisponível para locação."}), 400
        if not db.get(Inquilino, payload.id_inquilino):
            return jsonify({"detail": "Inquilino não cadastrado."}), 404

        # Altera status do imóvel para ALUGADO
        imovel.status = "ALUGADO"

        novo_contrato = ContratoLocacao(**payload.model_dump(), status="ATIVO")
        db.add(novo_contrato)
        db.commit()
        db.refresh(novo_contrato)

        # Gera primeira fatura do aluguel com cálculo de repasse líquido
        taxa_adm = payload.valor_aluguel_pactuado * (payload.taxa_adm_percentual / Decimal("100.00"))
        repasse_liquido = payload.valor_aluguel_pactuado - taxa_adm
        fatura = FaturaAluguel(
            id_contrato=novo_contrato.id_contrato,
            mes_referencia=date.today().strftime("%Y-%m"),
            valor_aluguel=payload.valor_aluguel_pactuado,
            data_vencimento=date(date.today().year, date.today().month, payload.dia_vencimento),
            valor_repasse_proprietario=repasse_liquido,
            status="PENDENTE"
        )
        db.add(fatura)
        db.commit()

        return jsonify({
            "id_contrato": novo_contrato.id_contrato,
            "id_imovel": novo_contrato.id_imovel,
            "id_inquilino": novo_contrato.id_inquilino,
            "data_inicio": novo_contrato.data_inicio.isoformat(),
            "data_fim": novo_contrato.data_fim.isoformat(),
            "valor_aluguel_pactuado": float(novo_contrato.valor_aluguel_pactuado),
            "dia_vencimento": novo_contrato.dia_vencimento,
            "taxa_adm_percentual": float(novo_contrato.taxa_adm_percentual),
            "status": novo_contrato.status
        }), 201

@router.get("/contratos/")
def listar_contratos():
    with SessionLocal() as db:
        contratos = db.scalars(select(ContratoLocacao).order_by(ContratoLocacao.id_contrato.desc())).all()
        return jsonify([
            {
                "id_contrato": c.id_contrato,
                "id_imovel": c.id_imovel,
                "id_inquilino": c.id_inquilino,
                "data_inicio": c.data_inicio.isoformat(),
                "data_fim": c.data_fim.isoformat(),
                "valor_aluguel_pactuado": float(c.valor_aluguel_pactuado),
                "dia_vencimento": c.dia_vencimento,
                "taxa_adm_percentual": float(c.taxa_adm_percentual),
                "status": c.status
            }
            for c in contratos
        ]), 200

# --- LIQUIDAR FATURA DE ALUGUEL ---
@router.put("/faturas/<int:id_fatura>/pagar")
def pagar_fatura(id_fatura: int):
    dados = request.get_json() or {}
    try:
        payload = PagamentoFatura(**dados)
    except Exception as err:
        return jsonify({"detail": str(err)}), 400

    with SessionLocal() as db:
        fatura = db.get(FaturaAluguel, id_fatura)
        if not fatura or fatura.status == "PAGA":
            return jsonify({"detail": "Fatura não localizada ou já quitada."}), 404

        fatura.data_pagamento = payload.data_pagamento
        fatura.status = "PAGA"
        db.commit()
        return jsonify({
            "message": "Aluguel pago com sucesso!",
            "repasse_proprietario": float(fatura.valor_repasse_proprietario)
        }), 200

5.2. Criar app/main.py

from pathlib import Path
from decimal import Decimal
from datetime import date
from flask import Flask, render_template
from sqlalchemy.orm import Session
from sqlalchemy import select
from app.core.database import engine, Base, SessionLocal
from app.models.imobiliaria_models import Proprietario, Inquilino, Imovel, ContratoLocacao, VistoriaImovel, FaturaAluguel
from app.routers import imobiliaria_router

Base.metadata.create_all(bind=engine)

BASE_DIR = Path(__file__).resolve().parent
app = Flask(__name__, template_folder=str(BASE_DIR / "templates"))
app.register_blueprint(imobiliaria_router.router)

def seed_dados_iniciais():
    with SessionLocal() as db:
        if not db.scalar(select(Proprietario)):
            p1 = Proprietario(nome="Carlos Drumond", telefone="(11) 98888-1111")
            p2 = Proprietario(nome="Silvia Prado", telefone="(19) 97777-2222")
            db.add_all([p1, p2])
            db.commit()

            inq1 = Inquilino(nome="Lucas Oliveira", telefone="(11) 96666-3333", email="lucas@email.com")
            inq2 = Inquilino(nome="Juliana Paes", telefone="(19) 95555-4444", email="juliana@email.com")
            db.add_all([inq1, inq2])
            db.commit()

            i1 = Imovel(id_imovel=1, id_proprietario=p1.id_proprietario, endereco="Av. Paulista, 1000 Apto 82", bairro="Bela Vista", cidade="São Paulo", valor_aluguel_sugerido=Decimal("3500.00"), status="ALUGADO")
            i2 = Imovel(id_imovel=2, id_proprietario=p2.id_proprietario, endereco="Rua Cambuí, 450", bairro="Cambuí", cidade="Campinas", valor_aluguel_sugerido=Decimal("2800.00"), status="DISPONIVEL")
            db.add_all([i1, i2])
            db.commit()

            c1 = ContratoLocacao(id_contrato=1, id_imovel=i1.id_imovel, id_inquilino=inq1.id_inquilino, data_inicio=date(2026, 8, 1), data_fim=date(2027, 7, 31), valor_aluguel_pactuado=Decimal("3500.00"), dia_vencimento=10, taxa_adm_percentual=Decimal("10.00"), status="ATIVO")
            db.add(c1)
            db.commit()

            v1 = VistoriaImovel(id_imovel=i1.id_imovel, id_contrato=c1.id_contrato, tipo_vistoria="ENTRADA", data_vistoria=date(2026, 8, 1), laudo_texto="Pintura nova e instalações elétricas 100% funcionais.", aprovada=True)
            db.add(v1)

            fa1 = FaturaAluguel(id_fatura=1001, id_contrato=c1.id_contrato, mes_referencia="2026-08", valor_aluguel=Decimal("3500.00"), data_vencimento=date(2026, 8, 10), valor_repasse_proprietario=Decimal("3150.00"), status="PENDENTE")
            db.add(fa1)
            db.commit()

seed_dados_iniciais()

@app.route("/")
def painel_imoveis():
    with SessionLocal() as db:
        imoveis = db.scalars(select(Imovel)).all()
        contratos = db.scalars(select(ContratoLocacao).where(ContratoLocacao.status == "ATIVO")).all()
        return render_template("index.html", imoveis=imoveis, contratos=contratos)

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5000, debug=True)

🔍 Como Executar e Testar a API REST (Terminal & Navegador)

  1. Iniciar Servidor Flask:
python -m app.main
  1. Abra no navegador: http://localhost:5000/
  2. Teste 1: Formalizar Novo Contrato de Locação (POST /api/contratos/) Execute no PowerShell:
Invoke-RestMethod -Uri "http://localhost:5000/api/contratos/" -Method Post -ContentType "application/json" -Body '{"id_imovel": 2, "id_inquilino": 2, "data_inicio": "2026-09-01", "data_fim": "2027-08-31", "valor_aluguel_pactuado": 2800.00, "dia_vencimento": 10, "taxa_adm_percentual": 10.0}'
  • Resposta esperada: 201 Created com status ATIVO.
  1. Teste 2: Liquidar Fatura de Aluguel (PUT /api/faturas/1001/pagar) Execute no PowerShell:
Invoke-RestMethod -Uri "http://localhost:5000/api/faturas/1001/pagar" -Method Put -ContentType "application/json" -Body '{"data_pagamento": "2026-08-10"}'
  • Resposta esperada: 200 OK com "repasse_proprietario": 3150.0.

🎨 ETAPA 6: Interface Web com Jinja2 e Bootstrap 5

6.1. Criar app/templates/base.html

<!DOCTYPE html>
<html lang="pt-br">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>ImobiFlow — Locação de Imóveis</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
</head>
<body class="bg-light">
    <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
        <div class="container">
            <a class="navbar-brand fw-bold text-warning" href="/"><i class="bi bi-buildings-fill"></i> ImobiFlow Imobiliária</a>
            <span class="badge bg-warning text-dark"><i class="bi bi-server"></i> Flask 3.x API</span>
        </div>
    </nav>
    <div class="container py-4">
        {% block content %}{% endblock %}
    </div>
</body>
</html>

6.2. Criar app/templates/index.html

{% extends "base.html" %}
{% block content %}
<div class="d-flex justify-content-between align-items-center mb-4">
    <h2><i class="bi bi-houses"></i> Carteira de Imóveis e Locações</h2>
    <span class="badge bg-dark fs-6">{{ imoveis|length }} Imóveis Mapeados</span>
</div>

<div class="row">
    {% for im in imoveis %}
    <div class="col-md-6 mb-3">
        <div class="card shadow-sm border-0 h-100">
            <div class="card-body">
                <div class="d-flex justify-content-between">
                    <h5 class="card-title text-primary"><i class="bi bi-geo-alt"></i> {{ im.bairro }} - {{ im.cidade }}</h5>
                    {% if im.status == 'ALUGADO' %}
                        <span class="badge bg-success">ALUGADO</span>
                    {% else %}
                        <span class="badge bg-warning text-dark">DISPONÍVEL</span>
                    {% endif %}
                </div>
                <p class="card-text text-muted mb-1">{{ im.endereco }}</p>
                <h4 class="fw-bold text-dark mt-2">R$ {{ "%.2f"|format(im.valor_aluguel_sugerido) }} / mês</h4>
            </div>
        </div>
    </div>
    {% endfor %}
</div>
{% endblock %}

🧪 ETAPA 7: Suíte de Testes Automatizados com Pytest (tests/test_imobiflow.py)

Crie tests/test_imobiflow.py:

import pytest
from decimal import Decimal
from datetime import date
from app.main import app
from app.core.database import Base, engine, SessionLocal
from app.models.imobiliaria_models import Proprietario, Inquilino, Imovel

@pytest.fixture
def client():
    app.config["TESTING"] = True
    Base.metadata.create_all(bind=engine)
    with app.test_client() as client:
        yield client

def test_contrato_e_liquidacao_aluguel(client):
    with SessionLocal() as db:
        prop = Proprietario(nome="Proprietário Teste", telefone="(11) 9999-1111")
        inq = Inquilino(nome="Inquilino Teste", telefone="(11) 9999-2222")
        db.add_all([prop, inq])
        db.commit()

        imovel = Imovel(id_proprietario=prop.id_proprietario, endereco="Rua Teste, 100", bairro="Centro", cidade="São Paulo", valor_aluguel_sugerido=Decimal("2000.00"), status="DISPONIVEL")
        db.add(imovel)
        db.commit()
        id_im = imovel.id_imovel
        id_inq = inq.id_inquilino

    # 1. Formalizar Contrato
    payload = {
        "id_imovel": id_im,
        "id_inquilino": id_inq,
        "data_inicio": "2026-09-01",
        "data_fim": "2027-08-31",
        "valor_aluguel_pactuado": 2000.00,
        "dia_vencimento": 10,
        "taxa_adm_percentual": 10.0
    }
    res_c = client.post("/api/contratos/", json=payload)
    assert res_c.status_code == 201
    assert res_c.get_json()["status"] == "ATIVO"

    # 2. Pagar Fatura
    res_pag = client.put("/api/faturas/1001/pagar", json={"data_pagamento": "2026-08-10"})
    assert res_pag.status_code == 200

Execute no terminal:

pytest -v

📊 ETAPA 8: Bateria de 10 Desafios de SQL Corporativo (ImobiFlow)

🎯 Desafio 01: Listar todos os imóveis disponíveis para locação ordenados pelo menor valor.

SELECT id_imovel, endereco, bairro, cidade, valor_aluguel_sugerido FROM imoveis WHERE status = 'DISPONIVEL' ORDER BY valor_aluguel_sugerido ASC;

🔍 Explicação Técnica: Consulta rápida para atendimento ao cliente na recepção da imobiliária.

🎯 Desafio 02: Consultar os contratos ativos que vencem nos próximos 60 dias.

SELECT id_contrato, id_imovel, id_inquilino, data_fim, valor_aluguel_pactuado FROM contratos_locacao WHERE status = 'ATIVO' AND data_fim BETWEEN date('now') AND date('now', '+60 day') ORDER BY data_fim ASC LIMIT 10;

🔍 Explicação Técnica: Prospecção de renovação contratual para retenção de inquilinos.

🎯 Desafio 03: Faturamento consolidado por status de fatura com montante total e repasse aos donos.

SELECT status, COUNT(*) AS total_faturas, SUM(valor_aluguel) AS montante_aluguel, SUM(valor_repasse_proprietario) AS total_repassado FROM faturas_aluguel GROUP BY status ORDER BY montante_aluguel DESC;

🔍 Explicação Técnica: Apuração contábil da receita bruta de locação e comissões de administração.

🎯 Desafio 04: Patrimônio e potencial de aluguel mensal agrupados por proprietário.

SELECT p.nome AS proprietario, COUNT(i.id_imovel) AS total_imoveis, SUM(i.valor_aluguel_sugerido) AS potencial_aluguel FROM proprietarios p INNER JOIN imoveis i ON p.id_proprietario = i.id_proprietario GROUP BY p.nome ORDER BY potencial_aluguel DESC;

🔍 Explicação Técnica: Segmentação de investidores imobiliários para atendimento Private/VIP.

🎯 Desafio 05: Filtrar proprietários com carteira igual ou superior a 5 imóveis cadastrados.

SELECT p.nome AS proprietario, COUNT(i.id_imovel) AS total_imoveis FROM proprietarios p INNER JOIN imoveis i ON p.id_proprietario = i.id_proprietario GROUP BY p.nome HAVING COUNT(i.id_imovel) >= 5;

🔍 Explicação Técnica: Filtro HAVING para concessão de condições comerciais especiais de taxa de administração.

🎯 Desafio 06: Relatório de contratos ativos com endereço do imóvel, proprietário e inquilino.

SELECT c.id_contrato, i.endereco, p.nome AS proprietario, inq.nome AS inquilino, c.valor_aluguel_pactuado, c.data_inicio, c.data_fim FROM contratos_locacao c INNER JOIN imoveis i ON c.id_imovel = i.id_imovel INNER JOIN proprietarios p ON i.id_proprietario = p.id_proprietario INNER JOIN inquilinos inq ON c.id_inquilino = inq.id_inquilino WHERE c.status = 'ATIVO';

🔍 Explicação Técnica: Multi-JOIN quadruplo estruturando a ficha completa da locação.

🎯 Desafio 07: Detalhes da fatura #1001 com dados de repasse, partes contratuais e vistoria.

SELECT fa.id_fatura, fa.mes_referencia, fa.valor_aluguel, fa.valor_repasse_proprietario, inq.nome AS inquilino, p.nome AS proprietario, vi.tipo_vistoria, vi.aprovada AS vistoria_aprovada FROM faturas_aluguel fa INNER JOIN contratos_locacao cl ON fa.id_contrato = cl.id_contrato INNER JOIN inquilinos inq ON cl.id_inquilino = inq.id_inquilino INNER JOIN imoveis i ON cl.id_imovel = i.id_imovel INNER JOIN proprietarios p ON i.id_proprietario = p.id_proprietario LEFT JOIN vistorias_imovel vi ON cl.id_contrato = vi.id_contrato WHERE fa.id_fatura = 1001;

🔍 Explicação Técnica: Extrato detalhado com LEFT JOIN de vistoria para auditoria jurídica e contábil.

🎯 Desafio 08: Identificar inquilinos cadastrados que não possuem nenhum contrato vinculado.

SELECT inq.id_inquilino, inq.nome FROM inquilinos inq LEFT JOIN contratos_locacao cl ON inq.id_inquilino = cl.id_inquilino WHERE cl.id_contrato IS NULL;

🔍 Explicação Técnica: LEFT JOIN identificando leads cadastrados sem locação efetivada (CRM de Vendas).

🎯 Desafio 09: Listar imóveis com aluguel sugerido superior à média dos imóveis vagos.

SELECT id_imovel, endereco, valor_aluguel_sugerido FROM imoveis WHERE valor_aluguel_sugerido > (SELECT AVG(valor_aluguel_sugerido) FROM imoveis WHERE status = 'DISPONIVEL');

🔍 Explicação Técnica: Subquery escalar para precificação de mercado e imóveis de alto padrão.

🎯 Desafio 10: Baixar fatura de aluguel quitada com retorno imediato do comprovante (RETURNING).

UPDATE faturas_aluguel SET data_pagamento = CURRENT_DATE, status = 'PAGA' WHERE id_fatura = 1001 AND status = 'PENDENTE' RETURNING id_fatura, mes_referencia, valor_aluguel, status;

🔍 Explicação Técnica: DML seguro com retorno atômico para emissão automática do recibo de quitação.


🩺 ETAPA 9: Troubleshooting no Windows & Checklist de Entrega

🛠️ Resolução Rápida de Erros Frequentes:

  1. Erro Porta 5000 já em uso (OSError: [Errno 10048] [WinError 10048]):
    Causa: Uma instância anterior do Flask ainda está em execução no Windows.
    Solução: Encerre os processos Python no PowerShell:
    Get-Process python* | Stop-Process -Force
    
  2. Erro 400 Bad Request: Imóvel não encontrado ou indisponível:
    Causa: O imóvel informado já está com status "ALUGADO".
  3. Erro 404 Not Found: Fatura não localizada ou já quitada:
    Causa: O id_fatura passado já foi pago anteriormente.

✅ Checklist de Conclusão do Aluno:

  • Ambiente virtual venv configurado e ativado no Windows.
  • Banco de dados SQLite criado com as 6 tabelas relacionais.
  • Formalização de contratos e liquidação de faturas testados via PowerShell / navegador na porta 5000.
  • Interface visual listando a carteira de imóveis disponíveis e alugados.
  • Suíte de testes pytest -v passando com 100% de sucesso.
  • 10 Desafios de SQL executados no DBeaver/pgAdmin.

🏢 ImobiFlow — Guia de Execução e README do Projeto

Setor: Financeiro e Imobiliário
Componente: Atividades de Projetos II / III
Classificação: 🔴 Nível 2: Intermediário / Avançado (5 a 7 Tabelas)
Repositório Template: https://github.com/fatec-gti/pi_10_imobiflow


🚀 1. Como Executar o Projeto Localmente

Passo 1: Clonar o Repositório e Abrir no VS Code

git clone https://github.com/fatec-gti/pi_10_imobiflow.git
code pi_10_imobiflow

Passo 2: Criar o Ambiente Virtual e Instalar Dependências

python -m venv venv
# Windows (PowerShell):
.\venv\Scripts\Activate.ps1
# Linux/Mac:
source venv/bin/activate

pip install flask sqlalchemy jinja2 psycopg2-binary

Passo 3: Executar a Aplicação com SQLite (Zero Configuração)

python main.py

Acesse no navegador:

  • 🌐 Interface Web & API REST: http://localhost:5000

🐳 2. Executando com PostgreSQL no Docker Compose

Para subir o banco de dados oficial de produção:

docker compose up -d

Edite o arquivo .env para apontar para o PostgreSQL:

DATABASE_URL=postgresql://postgres:senha@localhost:5432/pi_10_imobiflow_db

👥 3. Equipe de Desenvolvimento (Template de Entrega)

  • Analista de Sistemas / PO: [Nome do Estudante]
  • Engenheiro de Software: [Nome do Estudante]
  • DBA / Modelador de Dados: [Nome do Estudante]
  • Desenvolvedor Full-stack: [Nome do Estudante]