"""
Project Together — powered by AI: PASSO 7 — Orientar e executar.

Fazer o trabalho acontecer.

PRIMEIRO O CRONOGRAMA, DEPOIS A IA (Eduardo, 2026-08-07)
Este passo já foi só "monta um prompt e pergunta à IA o que fazer agora". Era o passo
errado começando pelo lugar errado: o cronograma do Passo 4 JÁ TEM toda atividade, com
dono e com data — perguntar isso a uma IA é pedir que ela adivinhe o que está escrito no
quadro. Agora o passo abre DIRIGINDO (execution.py): lê o cronograma, mostra o que
atrasou e o que vence no horizonte, gera a planilha de cada dono, marca os cartões no
quadro e deixa um rascunho de e-mail pronto para cada pessoa. Só então ele entrega o
prompt — e o prompt vai com essa foto dentro ([INPUT SITUACAO_EXECUCAO]), para a IA
responder a única pergunta que os dados não respondem: o que fazer com as PESSOAS.

PREDITIVA: a IA lê o plano e o quadro e diz o que fazer AGORA — as próximas ações,
começando pelo caminho crítico. Cada ação vira uma TAREFA, subtarefa do seu pacote na
lista EAP → Cronograma (a decomposição do escopo): executar é mudar o STATUS do cartão
(Aberto → Em Progresso → Concluída), não movê-lo de lista.

ÁGIL: este passo é a DAILY SCRUM — rode todo dia útil da Sprint. A Sprint Planning (a
Meta, os itens e as primeiras tarefas) já aconteceu no Passo 5; aqui o time inspeciona
o progresso rumo à Meta da Sprint e replaneja o dia. Ele registra três coisas: o FOCO
de hoje (num cartão datado na trilha do curso), os IMPEDIMENTOS (cartões 🚧 na lista de
riscos, que é onde mora o que ameaça a entrega) e as TAREFAS novas que apareceram no
caminho — porque o plano da Sprint é do time e muda todo dia. O que NÃO muda no meio da
Sprint é o compromisso: item novo não entra aqui, vai para o Product Backlog e se
discute no Passo 9.

Executar não é só tocar tarefas: o Guia PMBOK chama a atenção para a LIDERANÇA — quem
precisa de apoio, quem está sobrecarregado. É a parte que mais falha, e por isso o
prompt pede ao menos uma ação de pessoas (preditiva) e nomeia os impedimentos (ágil).

O RITMO (modo CHAT): prompt do Passo 7 → IA → resposta ([P7]) na Caixa de entrada →
rode este passo.
"""
import re
from datetime import date

import entradas
import passos
import prompts
import quadro
import team_check
from env import get_int
from passos import titulo_base as _titulo_base, titulo_da_tarefa as _com_prefixo
from runner import ok, step, warn
from idioma import T, Tf

N = 7
c, ids, abordagem = passos.abrir(N)

# ── DIRIGIR A EXECUÇÃO — antes de qualquer IA ────────────────────────────────
# Só na PREDITIVA, e isso é método, não preguiça: no ágil este passo é a DAILY, e dirigir
# a execução com uma planilha semanal por e-mail é justamente o que a Daily veio
# substituir. Lá quem dirige é o time, todo dia, no Sprint Backlog — que já está no
# quadro, atualizado, à vista de todos.
foto_execucao = ""
if abordagem == "PREDITIVA":
    try:
        import execution
        foto_execucao = execution.dirigir(c, ids)
    except Exception as e:
        # Dirigir é o coração do passo, mas não pode ser o dono dele: sem cronograma
        # datado (o Passo 4 ainda não rodou), o aluno continua conseguindo executar pelo
        # caminho da IA. Avisa e segue.
        warn(Tf("não consegui ler o cronograma para dirigir a execução agora: {0}", e))

# ── O KICK-OFF SAI AQUI, ANTES DA IA (Eduardo, 18/08/2026, quadro 293/Ativalog) ──────
# Ele ficava no FIM do passo, depois do bloco colado — e por isso nao existia. Quem roda
# o Passo 7 pela primeira vez nao tem a resposta da IA no quadro ainda: o `ler_bloco`
# entrega o prompt e ENCERRA o script (é o desenho do modo CHAT), e tudo o que vinha
# depois nunca chegava a rodar. O aluno via o prompt, mas nao via o artefato do passo.
#
# E o Kick-Off nao precisava da IA para nada: ele e' lido do QUADRO — nome do projeto,
# justificativa, OKR, EAP, restricoes, riscos, marcos e orcamento —, tudo aprovado la'
# atras no Passo 5. Amarra-lo a uma ida e volta a IA era um acoplamento sem motivo.
#
# Segue saindo UMA VEZ: no agil este passo e' a Daily e roda todo dia util. Depois que o
# passo esta' concluido e o arquivo existe, quem regenera e' a tecla D do menu.
def _gerar_kickoff():
    """Gera o Kick-Off (.pptx) se ele ainda nao existe. Devolve True se gerou AGORA."""
    import os

    import artefatos
    import progresso as _prog
    try:
        # Qualquer .pptx serve como prova de que ele já existe: o Kick-Off é a única
        # apresentação que o curso gera. Casar pelo NOME exigiria remontar o nome do
        # arquivo (que é traduzido e leva o slug do projeto) só para uma comparação.
        if N in _prog.feitos() and any(a.lower().endswith(".pptx")
                                       for a in os.listdir(artefatos._saida_dir())):
            return False
    except Exception:
        pass                                  # na duvida, gerar: o custo e' reescrever
    try:
        cam, _av = artefatos.gerar_kickoff(ids, abordagem=abordagem, log=step)
        if cam:
            ok(T("Kick-Off do Projeto gerado em _saida/documentos/ (.pptx) — personalizado "
                 "com o seu projeto (nome, justificativa, objetivos, EAP, restrições e "
                 "riscos). Os slides de metodologia (PMBOK, os 10 passos) já vêm prontos."))
            return True
    except Exception as e:
        warn(Tf("não consegui gerar o Kick-Off do Projeto agora: {0}", e))
    return False


kickoff_novo = _gerar_kickoff()

# ── A IA NESTE PASSO É OPCIONAL (Eduardo, 19/08/2026) ───────────────────────────────
# "Acredito que nao seja necessario rodar a IA no passo 7. Recomendo deixar o passo 7
# concluido quando rodar o passo gerando o kickoff e possibilitando o envio dos emails."
#
# Ele tem razao, e o quadro 293 é a prova: o passo dirigiu a execucao (planilha por dono,
# etiqueta no quadro, rascunho de e-mail com anexo) e gerou o Kick-Off — tudo LIDO DO
# QUADRO — e mesmo assim morria no `ler_bloco`, deixando o Passo 7 como o unico buraco de
# uma trilha com 1-6 e 9 concluidos. Orientar e executar é fazer o trabalho acontecer: ele
# aconteceu. A leitura da IA continua valendo, mas como EXTRA — a unica pergunta que os
# dados nao respondem é o que fazer com as PESSOAS —, e não como pedágio.
bloco = passos.bloco_opcional(
    ids, N, extras={"SITUACAO_EXECUCAO": foto_execucao} if foto_execucao else None)

if bloco is None:
    # Concluir SEM a IA só quando o passo de fato produziu alguma coisa nesta rodada.
    # Na PREDITIVA a foto da execucao é o produto (o gerente acabou de dirigir); no ÁGIL
    # este passo é a DAILY, cujo conteudo (foco, impedimentos, tarefas) so' existe na
    # resposta — la' o unico entregavel sem IA é o Kick-Off, e ele sai UMA vez. Concluir
    # uma Daily vazia todo dia seria registrar reuniao que nao houve.
    if not (foto_execucao or kickoff_novo):
        raise SystemExit(
            Tf('\n Ainda não colei nenhuma resposta do Passo {0} no seu quadro — e nesta '
               'rodada\n   não havia o que dirigir nem Kick-Off novo a gerar.\n\n'
               '   O prompt do Passo {1} está no cartão "{2:02d} - Passo {3}" da lista '
               'Referências\n   e em _saida/prompt-passo-{4}.txt. Cole na sua IA, cole a '
               'resposta (começa\n   com [P{5}]) na "0. Caixa de entrada" e rode o Passo '
               '{6} de novo.', N, N, N, N, N, N, N))
    print(T("\n  A IA é OPCIONAL neste passo — e você acabou de ver por quê: o cronograma"))
    print(T("  do Passo 4 já diz quem faz o quê e até quando, e o Kick-Off é lido do seu"))
    print(T("  quadro. Nada disso precisa ser adivinhado por ninguém."))
    print(T("\n  Quer a leitura dela sobre as PESSOAS (quem precisa de apoio, quem está"))
    print(T("  sobrecarregado — a parte que os dados não respondem)? O prompt do Passo 7"))
    print(Tf('  está no cartão "{0:02d} - Passo {1}" da lista Referências e em', N, N))
    print(Tf("  _saida/prompt-passo-{0}.txt: cole na sua IA, cole a resposta na Caixa de", N))
    print(T("  entrada e rode este passo de novo — as ações dela viram tarefas no quadro."))
    if abordagem == "PREDITIVA":
        resumo = Tf("Execução dirigida pelo cronograma ({0}).", prompts.nome_abordagem(abordagem))
        conclusao = T("Passo 7 concluído — a execução está dirigida: cada dono tem a sua "
                      "planilha, o quadro está etiquetado com o que atrasou e o que vence, "
                      "os rascunhos de e-mail estão prontos e o Kick-Off do Projeto saiu.")
    else:
        resumo = T("Kick-Off do Projeto gerado.")
        conclusao = T("Passo 7 concluído — o Kick-Off do Projeto saiu, lido do seu quadro. "
                      "A Daily Scrum é do time: rode este passo com a resposta da IA (ou "
                      "sem ela, direto no Sprint Backlog) todo dia útil da Sprint.")
    passos.finalizar(N, ids, resumo=resumo, mensagem_conclusao=conclusao)
    raise SystemExit(0)

# "NÃO HÁ IMPEDIMENTO" — a frase que o PRÓPRIO prompt manda a IA escrever quando o quadro
# não mostra nenhum. Ela é a resposta "vazio", e não um impedimento: virar cartão 🚧 é o
# oposto do que ela diz. O prompt existe nos dois idiomas ("nenhum impedimento visivel no
# quadro hoje" / "no impediment visible on the board today"), então a marca também precisa
# — senão o pacote inglês abre um impedimento chamado "no impediment visible" todo dia.
_SEM_IMPEDIMENTO = ("nenhum impedimento", "no impediment")


def _e_sem_impedimento(texto):
    t = entradas._sem_acento(texto or "").strip().lower()
    return any(t.startswith(m) for m in _SEM_IMPEDIMENTO)

if abordagem == "PREDITIVA":
    acoes = entradas.itens(bloco)
    if not acoes:
        raise SystemExit(T("Não consegui ler nenhuma ação no bloco [P7]. Confira o formato "
                         "(cada ação/tarefa num •) e cole de novo."))
    # A ação de execução também tem dono, e ele sai da mesma lista fechada (team_check).
    team_check.conferir(acoes, ids, o_que=T("ação(ões)"))
    # Cada ação é uma TAREFA de execução: subtarefa do seu PACOTE na EAP → Cronograma
    # (a decomposição do escopo), NÃO um cartão numa lista de fluxo. Executar = mudar o
    # STATUS do cartão (Aberto → Em Progresso → Concluída), não movê-lo de lista.
    from pa_client import list_cards, criar_cartoes, update_card
    escopo_lid = ids["ESCOPO"]
    nos, _tf = quadro.arvore_da_eap(ids)          # já ignora as tarefas de exec. (marca P7)
    # nome do pacote (ou entrega) → (id, código da EAP): o id pendura a tarefa como
    # subtarefa; o código (1.2.3) vai no título dela, para a lista dizer de quem ela é.
    por_titulo = {entradas._sem_acento(no["titulo"]): (no["id"], no.get("codigo", ""))
                  for no in nos if no["tipo"] in ("pacote", "entrega")}
    # tarefas de execução que já existem (idempotente: re-rodar não duplica). A chave é o
    # título SEM prefixo — é o que reconhece as que nasceram antes desta versão e as
    # renomeia no lugar, em vez de criar uma segunda cópia já prefixada.
    existentes = {_titulo_base(c.get("titulo") or ""): c for c in list_cards(escopo_lid)
                  if quadro.MARCA_EXEC_P7 in (c.get("descricao") or "")}
    # A ATIVIDADE TEM DATA — e ela precisa estar NO CARTÃO, não só no texto (2026-07-30).
    # A IA já responde "ate: AAAA-MM-DD" desde sempre, mas isso ficava enterrado na
    # descrição: o cartão nascia sem data_vencimento. Consequência: a atividade não
    # aparecia no Gantt do ProjectAdm, não entrava no cronograma como 4º nível e o prazo
    # que o próprio aluno definiu era invisível para todo o resto do curso.
    _DATA = re.compile(r"(\d{4})-(\d{2})-(\d{2})")

    def _ate(a):
        m = _DATA.search(str(a.get("ate", "") or ""))
        return m.group(0) if m else None

    datadas = 0
    novos, atualizados, renomeadas, sem_pai, tocados = [], 0, 0, 0, []
    para_datar = []                     # (id, data) — aplicado depois de criar/atualizar
    # (card_id, responsavel) — a ATRIBUIÇÃO de verdade: a atividade vai para a conta da
    # pessoa no ProjectAdm, não só o nome dela na descrição (Eduardo, 07/08/2026).
    para_atribuir = []
    resp_novos = []                     # o responsável de cada cartão novo, na mesma ordem
    for a in acoes:
        base = (a.get("_", "")[:110]) or T("(ação sem descrição)")
        pacote = (a.get("pacote", "") or "").strip()
        pai, codigo = por_titulo.get(entradas._sem_acento(pacote), (None, ""))
        if pacote and not pai:
            sem_pai += 1
        titulo = _com_prefixo(base, codigo)
        ate = _ate(a)
        # Rótulos no idioma do aluno (06/08/2026): o cartão de ATIVIDADE da Daily nascia
        # com "Pacote:/Responsável:/Até:/Desbloqueia:" mesmo no pacote em inglês.
        desc = (f"{quadro.MARCA_EXEC_P7}\n"
                + Tf("Pacote: {0}", pacote or '?') + "\n"
                + Tf("Responsável: {0} · Até: {1}", a.get('responsavel', '?'), a.get('ate', '?')) + "\n"
                + Tf("Desbloqueia: {0}", a.get('desbloqueia', '—')))
        ja = existentes.get(base)
        if ja and ja.get("id"):
            campos = {}
            if entradas.texto_do_cartao(ja.get("descricao", "")) != desc.strip():
                campos["descricao"] = desc
            if (ja.get("titulo") or "").strip() != titulo:
                campos["titulo"] = titulo
                renomeadas += 1
            if ate and (ja.get("data_vencimento") or "")[:10] != ate:
                campos["data_vencimento"] = ate
                datadas += 1
            if campos:
                try:
                    update_card(ja["id"], **campos)
                except Exception as e:
                    step(Tf('  [aviso] não consegui atualizar "{0}": {1}', base[:34], e))
            atualizados += 1
            tocados.append(ja["id"])
            para_atribuir.append((ja["id"], a.get("responsavel", "")))
        else:
            novos.append((titulo, desc, pai))
            para_datar.append(ate)
            resp_novos.append(a.get("responsavel", ""))
    criados = criar_cartoes(escopo_lid, novos, log=step) if novos else []
    for cid, ate, resp in zip(criados, para_datar, resp_novos):
        if not cid:
            continue
        para_atribuir.append((cid, resp))
        if ate:
            try:
                update_card(cid, data_vencimento=ate)
                datadas += 1
            except Exception as e:
                step(Tf("  [aviso] não consegui datar uma tarefa nova: {0}", e))
    criados = [x for x in criados if x]
    tocados += criados
    # as tarefas de execuções anteriores (sem a alça do pacote) também ganham o código —
    # só as marcadas como tarefa do Passo 7: o PACOTE da EAP também é filho, e não é tarefa
    renomeadas += passos.normalizar_tarefas(
        escopo_lid, {str(pid): cod for pid, cod in
                     ((v[0], v[1]) for v in por_titulo.values())},
        marca=quadro.MARCA_EXEC_P7, log=step)
    n = atualizados + len(criados)
    ok(Tf('{0} tarefa(s) de execução na lista EAP → Cronograma, como subtarefas dos seus '
          'pacotes — cada uma com o código do pacote no título ("↳ 1.2 · …"), para você '
          'achá-la na lista sem abrir cartão nenhum. Para executar, marque cada cartão como '
          'CONCLUÍDO conforme entrega (e, se o seu quadro tiver o status habilitado, avance '
          'Aberto → Em Progresso → Concluída) — não precisa mover de lista. É o que o Passo 8 '
          '(Status Report) vai ler.', n))
    if datadas:
        ok(Tf("{0} atividade(s) ganharam a data de término no cartão — é o que as põe no Gantt "
              "do ProjectAdm e no seu cronograma como o 4º nível (as atividades de cada pacote "
              "de trabalho).", datadas))
    # A ATRIBUIÇÃO DE VERDADE: quem tem conta no ProjectAdm vira MEMBRO do cartão — a
    # atividade passa a aparecer para a pessoa no quadro dela, não só como texto na
    # descrição. Quem não tem conta segue pelo nome (e pelo e-mail do rascunho).
    atribuidos, sem_conta = team_check.atribuir_lote(ids, para_atribuir)
    if atribuidos:
        ok(Tf("{0} atividade(s) ATRIBUÍDAS à conta do responsável no ProjectAdm — elas "
              "passam a aparecer para a pessoa no quadro dela, não só no texto do cartão.",
              atribuidos))
    if sem_conta:
        step(Tf("sem conta ligada no ProjectAdm: {0}. Rode o Passo 1 para ligar a equipe "
                "às contas (ou deixe assim: o nome no cartão continua valendo).",
                ", ".join(sem_conta)))
    if renomeadas:
        ok(Tf("{0} tarefa(s) que já estavam no quadro ganharam o código do pacote no título "
              "(nenhuma cópia foi criada).", renomeadas))
    elos = passos.links(tocados)
    if elos:
        print(f"     ↳ {elos}")
    if sem_pai:
        warn(Tf("{0} tarefa(s) não casaram com um pacote pelo nome — ficaram no topo da lista, "
                "sem pai. Confira o campo 'pacote' na resposta da IA (use o mesmo nome do "
                "pacote na sua EAP).", sem_pai))
else:
    # ── DAILY SCRUM — a inspeção diária do progresso rumo à Meta da Sprint ───────
    from pa_client import criar_cartoes, list_cards
    sprint = max(1, get_int(c, "CURRENT_SPRINT", 1))
    hoje = date.today().isoformat()

    secs = entradas.secoes(bloco, ["FOCO", "IMPEDIMENTOS", "TAREFAS"])
    foco = entradas.itens(secs.get("foco", ""))
    impedimentos = entradas.itens(secs.get("impedimentos", ""))
    tarefas = entradas.itens(secs.get("tarefas", ""))
    if not (foco or impedimentos or tarefas):
        # Formato antigo (só tarefas em •) — a Daily ainda funciona, tudo vira tarefa.
        tarefas = entradas.itens(bloco)
    if not (foco or impedimentos or tarefas):
        raise SystemExit(T("Não achei FOCO, IMPEDIMENTOS nem TAREFAS no bloco [P7]. Confira "
                         "o formato (as três seções, cada item num •) e cole de novo."))
    team_check.conferir(tarefas, ids, o_que=T("tarefa(s) da Sprint"))

    meta_card = quadro.cartao_da_meta(ids, sprint) or quadro.cartao_da_meta(ids)
    meta_txt = (meta_card.get("titulo") or "") if meta_card else ""
    if not meta_card:
        warn(Tf("não achei a Meta da Sprint {0} no Sprint Backlog. A Daily inspeciona o "
                "progresso RUMO À META — sem ela, o dia não tem contra o que ser medido. Rode "
                "o Passo 5 (Sprint Planning) antes.", sprint))

    # 1) O FOCO DO DIA — um cartão datado na trilha do curso. Não vira cartão de trabalho:
    #    o trabalho já está no Sprint Backlog; isto é o registro da conversa de 15 minutos.
    n_foco = 0
    if foco:
        linhas = [Tf("Daily Scrum — Sprint {0} · {1}", sprint, hoje), ""]
        if meta_txt:
            linhas += [meta_txt, ""]
        linhas.append(T("FOCO DE HOJE:"))
        for f in foco:
            linhas.append(f"• {f.get('_','')}")
            det = " · ".join(f"{passos.rotulo_de_campo(k)}: {f[k]}"
                             for k in ("item", "responsavel") if f.get(k))
            if det:
                linhas.append(f"    {det}")
        passos.escrever_cartoes(ids, "CONCLUIDO", [(
            Tf("🗓️ Daily Scrum — Sprint {0} · {1}", sprint, hoje),
            "\n".join(linhas), None)], log=step)
        n_foco = len(foco)
        ok(Tf("Daily registrada: {0} foco(s) para hoje, no cartão 🗓️ da lista Concluído.", n_foco))

    # 2) OS IMPEDIMENTOS — cartões 🚧 na lista de riscos, que é onde já mora o que ameaça
    #    a entrega. Não inventamos lista nova: um impedimento é um risco que já aconteceu.
    n_imp = 0
    if impedimentos:
        cartoes_imp = []
        for i in impedimentos:
            texto = (i.get("_", "") or "").strip()
            if not texto or _e_sem_impedimento(texto):
                continue
            desc = Tf("Impedimento levantado na Daily de {0} (Sprint {1}).\n"
                      "Afeta: {2} · Quem remove: {3}"
                      "\n\nUm impedimento é o que trava o time e ele não resolve sozinho. "
                      "Marque este cartão como concluído quando o obstáculo cair — o tempo "
                      "que ele fica aberto é o custo real dele.",
                      hoje, sprint, i.get('afeta', '?'), i.get('quem remove', '?'))
            cartoes_imp.append((f"🚧 {texto[:100]}", desc, None))
        if cartoes_imp:
            n_imp = passos.escrever_cartoes(ids, "RISCOS", cartoes_imp, log=step)
            ok(Tf("{0} impedimento(s) 🚧 na lista Riscos e Premissas — com quem remove cada um. "
                  "Remover impedimento é trabalho do dia, não recado de reunião.", n_imp))
        else:
            ok(T("Nenhum impedimento hoje — o time segue sem obstáculo declarado."))

    # 3) AS TAREFAS NOVAS — o trabalho que só se enxerga fazendo. Subtarefa do seu item.
    n_tar = 0
    if tarefas:
        from pa_client import update_card
        por_cod = quadro.cartoes_por_codigo(quadro.itens_de_trabalho(ids["EXECUCAO"]))
        # chave = título SEM prefixo: é o que reconhece as tarefas das Dailies anteriores
        # (que nasceram sem o código do item) e as renomeia no lugar, sem duplicar.
        existentes = {_titulo_base(c_.get("titulo") or ""): c_
                      for c_ in list_cards(ids["EXECUCAO"])}
        novas, renomeadas, sem_item, tocadas = [], 0, 0, []
        for t in tarefas:
            base = (t.get("_", "") or "")[:110]
            if not base:
                continue
            cod = quadro.codigo_do_item(t.get("item", "") or "") or \
                (t.get("item", "") or "").strip().lower()
            pai = por_cod.get(cod)
            if not pai:
                sem_item += 1
            # o código vem do PAI de verdade (o cartão do Sprint Backlog), não do que a
            # IA escreveu no campo "item": é o que garante "↳ B3 ·" batendo com o item B3.
            titulo = _com_prefixo(base, quadro.codigo_do_item(pai.get("titulo", ""))
                                  if pai else "")
            ja = existentes.get(base)
            if ja and ja.get("id"):
                if (ja.get("titulo") or "").strip() != titulo:
                    try:
                        update_card(ja["id"], titulo=titulo)
                        renomeadas += 1
                    except Exception as e:
                        step(Tf('  [aviso] não consegui renomear "{0}": {1}', base[:34], e))
                tocadas.append(ja["id"])
                continue
            desc = Tf("Tarefa que apareceu na Daily de {0} (Sprint {1}).\n"
                      "Item: {2} · Responsável: {3} · Estimativa: {4}",
                      hoje, sprint, t.get('item', '?'), t.get('responsavel', '?'),
                      t.get('estimativa', t.get('pontos', '?')))
            novas.append((titulo, desc, pai["id"] if pai else None))
        criadas = [x for x in (criar_cartoes(ids["EXECUCAO"], novas, log=step)
                               if novas else []) if x]
        tocadas += criadas
        # as tarefas que já estavam no Sprint Backlog (desta Sprint ou da Planning) também
        # passam a mostrar o item a que pertencem — renomeadas no lugar, sem duplicar
        renomeadas += passos.normalizar_tarefas(
            ids["EXECUCAO"], {str(cd["id"]): cod.upper() for cod, cd in por_cod.items()},
            log=step)
        n_tar = len(criadas)
        if n_tar:
            ok(Tf('{0} tarefa(s) nova(s) no Sprint Backlog, como subtarefas do seu item e com '
                  'o código dele no título ("↳ B3 · …") — é assim que você acha a tarefa na '
                  'lista sem abrir cartão nenhum. Conforme trabalha, mude o status do cartão '
                  '(Aberto → Em Progresso) e, quando o ITEM inteiro atingir a Definição de '
                  'Pronto, ele vai para o Incremento na Sprint Review (Passo 8).', n_tar))
        if renomeadas:
            ok(Tf("{0} tarefa(s) de Dailies anteriores ganharam o código do item no título "
                  "(nenhuma cópia foi criada).", renomeadas))
        elos = passos.links(tocadas)
        if elos:
            print(f"     ↳ {elos}")
        if sem_item:
            warn(Tf("{0} tarefa(s) não casaram com nenhum item da Sprint pelo código (B1, B2…) "
                    "— ficaram sem item-pai. Confira o campo 'item'.", sem_item))

    n = n_foco + n_imp + n_tar

# O Kick-Off do Projeto (.pptx) — artefato deste passo — já saiu lá em cima, ANTES da
# ida à IA: ele é lido do quadro e não depende da resposta dela (ver _gerar_kickoff).

if abordagem == "AGIL":
    resumo = Tf("Daily Scrum da Sprint {0} ({1}): {2} foco(s), {3} impedimento(s), "
                "{4} tarefa(s) nova(s).", sprint, hoje, n_foco, n_imp, n_tar)
    # ONDE FICOU CADA COISA — a Daily escreve em TRÊS lugares do quadro, e quem acabou de
    # colar a resposta precisa saber em qual (Eduardo, 2026-07-27). Dizer isto toda vez é
    # mais barato do que ele procurar uma vez.
    conclusao = Tf(
        "Passo 7 concluído — a Daily de hoje está no quadro, em três lugares:\n"
        "  • o FOCO do dia: cartão \"🗓️ Daily Scrum — Sprint {0} · {1}\", na lista "
        "Concluído (curso);\n"
        "  • os IMPEDIMENTOS: cartões \"🚧 …\" na lista Riscos e Premissas (impedimento é "
        "risco que já aconteceu);\n"
        "  • as TAREFAS: subtarefas do seu item, na lista Sprint Backlog, com o código do "
        "item no título (\"↳ B3 · …\").\n"
        "    No ProjectAdm, a subtarefa também aparece dentro do cartão do item, na seção "
        "Sub-tarefas.\n"
        "Ela se repete: rode este passo TODO DIA ÚTIL da Sprint (15 minutos). Quando a "
        "Sprint chegar ao fim, o Passo 8 é a Sprint Review.", sprint, hoje)
else:
    resumo = Tf("{0} tarefa(s) de execução ({1}).", n, prompts.nome_abordagem(abordagem))
    conclusao = T("Passo 7 concluído — o trabalho de agora está no quadro, "
                  "com responsável e prazo.")

passos.finalizar(N, ids, resumo=resumo, mensagem_conclusao=conclusao)
