Desenvolvedor focado programando no computador - sistema pomodoro tracker Python

Sistema Pomodoro com Tracker Automático e Bloqueio de Distrações: Produtividade Forçada por Infraestrutura (Python + /etc/hosts)

Seu timer pomodoro toca. Você olha. Cinco minutos depois, ainda está no celular. Mais vinte, já está no YouTube. Quando percebe, a sessão de foco virou sessão de scrolling e a tarefa original? Esquecida.

Se isso soa familiar, não é porque você é fraco. É porque confiar em força de vontade para manter o foco é como usar um guarda-chuva de papel num furacão. O que funciona de verdade é infraestrutura: um sistema que torna a distração difícil e o foco inevitável.

Este artigo mostra como construir, do zero em Python, um sistema Pomodoro com tracker automático e bloqueio ativo de distrações via hostfile. Sem app de terceiros, sem assinatura, sem dados saindo da sua máquina. Um timer que conta, registra, bloqueia e gera relatórios — tudo local.

O Problema Que Apps de Produtividade Não Resolvem

Vamos ser honestos: a maioria dos apps de produtividade são distrações disfarçadas de ferramentas. Você gasta mais tempo configurando tags, cores e integrações do que fazendo o trabalho real.

O segundo problema é mais sutil: apps de timer não impedem que você abra o Twitter. Eles contam o tempo, mas não criam fricção entre você e a tentação. É como colocar uma fita isolante na porta de um cassino e esperar que ninguém entre.

🔧 O perrengue que gerou isso: Numa madrugada de deploy, percebi que meu “timer pomodoro” do celular tinha mais notificações que o Slack. Cada 25 minutos viravam 40 porque eu atendia a notificação do próprio timer, abria o Instagram “rapidinho” e esquecia que existia código esperando. A solução? Parar de usar apps bonitos e construir algo que funciona no nível do sistema — não no nível da interface.

Arquitetura do Sistema: Timer + Tracker + Bloqueador

O sistema tem três componentes que operam em conjunto:

  • Timer Pomodoro: Ciclo 25/5 com controle de estado persistente em SQLite
  • Tracker Automático: Registra sessões, pausas e produtividade ao longo do tempo
  • Bloqueador de Distrações: Manipula o /etc/hosts (Linux/macOS) para redirecionar sites distrativos durante sessões de foco

Todos rodam num único script Python, sem dependências externas além da stdlib e sqlite3.

Componente 1: O Timer com Estado Persistente

A base do sistema é um timer que não perde estado se o processo morrer. Isso é crítico: se você reinicia o script no meio de um pomodoro, ele precisa saber em que ponto parou.

import sqlite3
import time
import os
from datetime import datetime, timedelta
from pathlib import Path

DB_PATH = Path.home() / ".pomodoro_tracker.db"

def init_db():
    """Cria tabelas se não existirem."""
    with sqlite3.connect(DB_PATH) as conn:
        conn.executescript("""
            CREATE TABLE IF NOT EXISTS sessions (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                start_time TEXT NOT NULL,
                end_time TEXT,
                duration_seconds INTEGER DEFAULT 0,
                session_type TEXT CHECK(session_type IN ('focus', 'break')) DEFAULT 'focus',
                completed INTEGER DEFAULT 0,
                interruptions INTEGER DEFAULT 0
            );
            CREATE TABLE IF NOT EXISTS config (
                key TEXT PRIMARY KEY,
                value TEXT NOT NULL
            );
        """)
        # Configurações padrão
        defaults = {
            'focus_duration': '1500',    # 25 min em segundos
            'break_duration': '300',      # 5 min
            'long_break': '900',          # 15 min
            'sessions_before_long': '4',
        }
        for k, v in defaults.items():
            conn.execute(
                "INSERT OR IGNORE INTO config (key, value) VALUES (?, ?)",
                (k, v)
            )

def get_config(key):
    with sqlite3.connect(DB_PATH) as conn:
        cur = conn.execute("SELECT value FROM config WHERE key = ?", (key,))
        row = cur.fetchone()
        return int(row[0]) if row else None

def start_session(session_type='focus'):
    """Registra o início de uma nova sessão."""
    now = datetime.now().isoformat()
    with sqlite3.connect(DB_PATH) as conn:
        cursor = conn.execute(
            "INSERT INTO sessions (start_time, session_type) VALUES (?, ?)",
            (now, session_type)
        )
        return cursor.lastrowid

def complete_session(session_id, duration):
    """Marca sessão como completada."""
    now = datetime.now().isoformat()
    with sqlite3.connect(DB_PATH) as conn:
        conn.execute(
            "UPDATE sessions SET end_time = ?, duration_seconds = ?, completed = 1 WHERE id = ?",
            (now, duration, session_id)
        )

def record_interruption(session_id):
    """Registra uma interrupção durante a sessão."""
    with sqlite3.connect(DB_PATH) as conn:
        conn.execute(
            "UPDATE sessions SET interruptions = interruptions + 1 WHERE id = ?",
            (session_id,)
        )

def recover_session():
    """Verifica se há sessão incompleta no banco."""
    with sqlite3.connect(DB_PATH) as conn:
        cur = conn.execute(
            "SELECT id, start_time FROM sessions WHERE completed = 0 AND end_time IS NULL ORDER BY id DESC LIMIT 1"
        )
        row = cur.fetchone()
        if row:
            session_id, start_time = row
            elapsed = (datetime.now() - datetime.fromisoformat(start_time)).total_seconds()
            remaining = get_config('focus_duration') - elapsed
            return session_id, max(0, remaining)
        return None, None

A função recover_session é o segredo: se o script cai, o próximo start verifica se havia algo em andamento e retoma de onde parou. Seu timer não esquece.

Timer pomodoro em tela de computador - produtividade com Python
O timer é só o começo — o tracker é o que transforma minutos em dados úteis.

Componente 2: O Loop do Pomodoro

Com a base de dados pronta, o ciclo pomodoro é um loop simples que alterna entre foco e pausa, com contagem regressiva visível:

import signal
import sys

interrupted = False

def handle_interrupt(signum, frame):
    global interrupted
    interrupted = True

signal.signal(signal.SIGINT, handle_interrupt)

def countdown(seconds, label="Foco"):
    """Contagem regressiva com display no terminal."""
    start = time.time()
    while seconds > 0:
        if interrupted:
            return False
        mins, secs = divmod(int(seconds), 60)
        elapsed = int(time.time() - start)
        print(f"\r⏱️  {label}: {mins:02d}:{secs:02d} | já passou {elapsed}s", end="", flush=True)
        time.sleep(1)
        seconds -= 1
    print()
    return True

def run_pomodoro_cycle():
    """Executa ciclos pomodoro até interrupção."""
    init_db()
    session_count = 0

    # Recupera sessão anterior se houver
    recovered_id, remaining = recover_session()
    if recovered_id and remaining is not None and remaining > 0:
        print(f"🔄 Sessão recuperada! Restam {int(remaining)}s")
        focus_duration = remaining
        current_id = recovered_id
    else:
        current_id = start_session('focus')
        focus_duration = get_config('focus_duration')

    while True:
        global interrupted
        interrupted = False

        # --- Fase de FOCO ---
        print(f"\n🎯 Sessão de FOCO #{session_count + 1} — {focus_duration // 60} minutos")
        print("Distrações bloqueadas. Mergulhe.")

        success = countdown(focus_duration, "Foco")

        if success:
            complete_session(current_id, focus_duration)
            session_count += 1
            print("✅ Sessão completada! Hora da pausa.")
        else:
            record_interruption(current_id)
            print("\n⚠️  Sessão interrompida. Vamos tentar de novo?")
            continue

        # --- Fase de PAUSA ---
        break_duration = get_config('break_duration')
        print(f"☕ Pausa de {break_duration // 60} minutos — levante, alongue, respire")
        countdown(break_duration, "Pausa")
        print("⏰ Pausa acabou. De volta ao trabalho!")

        current_id = start_session('focus')
        focus_duration = get_config('focus_duration')

if __name__ == '__main__':
    run_pomodoro_cycle()

Repare no tratamento de signal.SIGINT (Ctrl+C): em vez de matar o processo, ele marca a sessão como interrompida e registra no banco. Cada interrupção vira dado, não lixo.

Componente 3: Bloqueio Ativo de Distrações via /etc/hosts

Aqui é onde o sistema deixa de ser “só mais um timer” e vira infraestrutura de foco. Durante cada sessão de 25 minutos, o script modifica o /etc/hosts para redirecionar sites distrativos para 127.0.0.1.

import shutil
import subprocess
from datetime import datetime

# Lista de distrações (customize conforme seus vícios)
DISTRACTION_SITES = [
    "twitter.com", "x.com",
    "instagram.com",
    "tiktok.com",
    "reddit.com",
    "youtube.com",
    "facebook.com",
    "news.ycombinator.com",
]

HOSTS_FILE = "/etc/hosts"
BACKUP_SUFFIX = ".pomodoro_backup"

def backup_hosts():
    """Cria backup do hosts antes de modificar."""
    backup = HOSTS_FILE + BACKUP_SUFFIX
    if not os.path.exists(backup):
        shutil.copy2(HOSTS_FILE, backup)
        print(f"📋 Backup criado: {backup}")
    return backup

def block_sites():
    """Adiciona entradas de bloqueio ao /etc/hosts."""
    backup_hosts()

    with open(HOSTS_FILE, 'r') as f:
        current = f.read()

    # Verifica se já está bloqueado
    if "# POMODORO_BLOCK" in current:
        print("⚠️  Bloqueio já ativo. Pulando.")
        return

    block_entries = []
    for site in DISTRACTION_SITES:
        for prefix in ["", "www."]:
            block_entries.append(f"\n# POMODORO_BLOCK — {datetime.now().isoformat()}")
            block_entries.append(f"127.0.0.1 {prefix}{site}")

    with open(HOSTS_FILE, 'a') as f:
        f.write("\n".join(block_entries))

    # Flush DNS (Linux/macOS)
    try:
        subprocess.run(["sudo", "dscacheutil", "-flushcache"], capture_output=True)
    except FileNotFoundError:
        subprocess.run(["sudo", "systemctl", "restart", "systemd-resolved"], capture_output=True)

    print(f"🚫 {len(DISTRACTION_SITES)} sites bloqueados. Sem desculpa.")

def unblock_sites():
    """Remove bloqueios do /etc/hosts, restaurando backup."""
    backup = HOSTS_FILE + BACKUP_SUFFIX
    if os.path.exists(backup):
        shutil.copy2(backup, HOSTS_FILE)
        os.remove(backup)
        print("🔓 Bloqueio removido. Hosts restaurado.")
    else:
        # Remove apenas linhas do pomodoro
        with open(HOSTS_FILE, 'r') as f:
            lines = f.readlines()
        filtered = [l for l in lines if "POMODORO_BLOCK" not in l]
        with open(HOSTS_FILE, 'w') as f:
            f.writelines(filtered)
        print("🔓 Linhas de bloqueio removidas.")
Concentração profunda no trabalho - foco sem distrações
Quando o sistema bloqueia as distrações, sobra só o trabalho. E isso é bom.

Aviso importante: modificar /etc/hosts requer sudo. O script precisa rodar com privilégios elevados ou você pode configurar sudoers para permitir apenas essa operação específica sem senha:

# Adicione ao /etc/sudoers.d/pomodoro
seu_usuario ALL=(root) NOPASSWD: /usr/bin/python3 /caminho/pomodoro_tracker.py

Componente 4: Relatórios e Análise de Produtividade

De nada adianta timer e bloqueio se você não sabe quanto está produzindo. O sistema gera relatórios diários e semanais direto do SQLite:

def daily_report(date_str=None):
    """Relatório do dia: sessões, tempo total, interrupções."""
    if not date_str:
        date_str = datetime.now().strftime('%Y-%m-%d')

    with sqlite3.connect(DB_PATH) as conn:
        cur = conn.execute("""
            SELECT
                COUNT(*) as total_sessions,
                SUM(CASE WHEN completed = 1 THEN 1 ELSE 0 END) as completed,
                SUM(CASE WHEN completed = 1 THEN duration_seconds ELSE 0 END) as focus_seconds,
                SUM(interruptions) as total_interruptions
            FROM sessions
            WHERE start_time LIKE ?
        """, (f"{date_str}%",))

        row = cur.fetchone()
        total, completed, focus_secs, interrupts = row

        print(f"\n📊 Relatório de {date_str}")
        print(f"   Sessões iniciadas: {total}")
        print(f"   Sessões completadas: {completed}")
        print(f"   Tempo de foco: {focus_secs // 3600}h {(focus_secs % 3600) // 60}min")
        print(f"   Interrupções: {interrupts}")

        if total > 0:
            rate = (completed / total) * 100
            print(f"   Taxa de conclusão: {rate:.1f}%")

def weekly_trend():
    """Tendência dos últimos 7 dias."""
    with sqlite3.connect(DB_PATH) as conn:
        cur = conn.execute("""
            SELECT
                substr(start_time, 1, 10) as day,
                COUNT(*) as sessions,
                SUM(CASE WHEN completed = 1 THEN duration_seconds ELSE 0 END) as focus
            FROM sessions
            WHERE start_time >= date('now', '-7 days')
            GROUP BY day
            ORDER BY day
        """)
        rows = cur.fetchall()
        print("\n📈 Tendência semanal:")
        for day, sessions, focus in rows:
            focus_min = (focus or 0) // 60
            bar = "█" * (focus_min // 5)
            print(f"   {day}: {sessions} sessões | {focus_min}min {bar}")

Esses relatórios respondem perguntas que nenhum app genérico responde: em que dia da semana eu produzo mais? Quantas interrupções acontecem antes do almoço? Meu foco melhorou ou piorou nas últimas duas semanas?

Integrando Tudo: O Script Completo

Para rodar, basta juntar os componentes e chamar o loop principal:

if __name__ == '__main__':
    import argparse

    parser = argparse.ArgumentParser(description='Pomodoro Tracker com Bloqueio')
    parser.add_argument('--block', action='store_true', help='Ativar bloqueio de sites')
    parser.add_argument('--report', type=str, help='Relatório de uma data (YYYY-MM-DD)')
    parser.add_argument('--weekly', action='store_true', help='Tendência semanal')
    parser.add_argument('--unblock', action='store_true', help='Remover bloqueio')

    args = parser.parse_args()

    if args.report:
        daily_report(args.report)
    elif args.weekly:
        weekly_trend()
    elif args.unblock:
        unblock_sites()
    else:
        print("🚀 Pomodoro Tracker — Foco Forçado por Infraestrutura")
        print("=" * 55)

        if args.block:
            block_sites()

        try:
            run_pomodoro_cycle()
        finally:
            if args.block:
                unblock_sites()
                print("🔓 Sites desbloqueados. Até a próxima sessão!")

Modo de uso

Comando Função
python pomodoro_tracker.py --block Inicia pomodoro com bloqueio ativo
python pomodoro_tracker.py --report Relatório de hoje
python pomodoro_tracker.py --report 2026-06-10 Relatório de data específica
python pomodoro_tracker.py --weekly Tendência dos últimos 7 dias
python pomodoro_tracker.py --unblock Remove bloqueios manualmente

Por Que Isso Funciona (E Apps Não)

A diferença fundamental é custo cognitivo da distração. Num app de timer, abrir o Instagram custa zero — só um toque. Com o bloqueio via /etc/hosts, custa abrir o terminal, editar o arquivo, flush DNS. São 30 segundos de fricção. Trinta segundos é tempo suficiente para seu córtex prefrontal lembrar que você deveria estar trabalhando.

O segundo fator é o tracker automático. Quando você vê no relatório que teve 12 interrupções numa sessão de 25 minutos, não precisa de coach de produtividade para perceber que algo está errado. Os dados falam por si.

A Regra dos 20 Minutos

Se você é como eu, 25 minutos parece pouco quando está no flow. O sistema permite customizar: mude focus_duration no banco para 1800 (30 min) ou 3600 (1h). A única regra: nunca menos de 20 minutos. Pesquisas em neurociência cognitiva sugerem que leva em média 15-20 minutos para entrar em estado de flow profundo. Pomodoros de 10 minutos são só procrastinação sofisticada.

Limitações e Melhorias Futuras

Nada é perfeito. Aqui estão as arestas:

  • HTTPS e HSTS: Alguns sites redirecionam mesmo com DNS bloqueado. A solução é combinar com regras de iptables ou usar um proxy local.
  • Apps mobile: O bloqueio no /etc/hosts do desktop não afeta o celular. Para isso, um DNS local (Pi-hole ou AdGuard Home) na rede resolve.
  • Sessão compartilhada: Se outra pessoa usa o mesmo PC, o bloqueio afeta todos. A solução é criar um serviço systemd por usuário.

A próxima versão que quero implementar: detecção automática de janela ativa via xdotool (Linux) ou osascript (macOS), que pausa o timer quando você sai da janela de trabalho. Porque ser rastreado é útil — mas ser rastreado com contexto é poderoso.

Conclusão: Produtividade é Engenharia, Não Motivação

O maior mito da produtividade é que ela depende de motivação. Depende é de sistemas que funcionam mesmo quando você está desmotivado. Um timer que recupera estado. Um bloqueador que impede o caminho fácil. Um tracker que mostra a verdade sem filtro.

Não é sexy. Não tem animações bonitas. Mas funciona às 23h de uma terça-feira, quando você está cansado, o código tá bugado e a única coisa que seu cérebro quer é abrir o Reddit “só por 5 minutos”.

É esse tipo de sistema que salva seu sono.

💬 Qual automação de produtividade você quer ver no próximo artigo? Detector de contexto por janela ativa? Integração com calendário pra bloquear horários automaticamente? Um dashboard web pro tracker? Me conta nos comentários — o que te atrapalha mais eu transformo em código.

Posts Similares