#!/usr/bin/env python3
# Enquadramento da demanda 39 — OS CURSOS PRESENCIAIS COM INSCRICAO.
# A chave sai do LIVRO DA PROPRIA FABRICA (⛔ nao ha' Jira neste produto: a regra 3 e' do
# maissaude) e e' tomada NO INSTANTE DA ENTREGA — ha' outros enquadradores a correr em
# paralelo e CS-70..CS-87 foram consumidos durante esta sessao.
import json, subprocess, sys
sys.path.insert(0, "/srv/fab/bin")
from fabcomum import bd

c = bd()
usadas = [r[0] for r in c.execute("SELECT chave FROM card WHERE chave LIKE 'CS-%'")] + \
         [r[0] for r in c.execute("SELECT chave FROM demanda WHERE chave LIKE 'CS-%'")]
proximo = max(int(x.split("-")[1]) for x in usadas) + 1
K = [f"CS-{proximo + i}" for i in range(7)]          # 0 = epic, 1..6 = cards
EPIC, C1, C2, C3, C4, C5, C6 = K
print("chaves tomadas:", "epic", EPIC, "· cards", ", ".join(K[1:]))

J = "cd backend && npx jest {} --runInBand --ci"
def t(ficheiro, nome):
    """Aceite de UM teste do jest, com a trava do vacuo: `-t` que ⛔ nao casa com nome
    nenhum sai 0 e daria VERDE sem correr nada. ⟹ exige-se a linha do sumario."""
    return (f"cd backend && npx jest test/{ficheiro} --runInBand --ci -t '{nome}'"
            " 2>&1 | tee /dev/stderr | grep -qE '^Tests:.* [1-9][0-9]* passed'")
def g(ficheiro, nome):
    return f"cd frontend && npx playwright test e2e/{ficheiro} --reporter=line -g '{nome}'"

cards = [
 # ── 1 · o curso existe ──────────────────────────────────────────────────────────────
 {"chave": C1, "territorio": "backend",
  "titulo":
    "O CURSO PRESENCIAL PASSA A EXISTIR, E E' COISA PROPRIA (⛔ nao uma `Aula` com etiqueta: a "
    "aula tem duracao multipla de 20 imutavel, blocos, questoes e link de transmissao): `model "
    "Curso` com OS CINCO CAMPOS QUE O DONO NOMEOU — o que e' · quando · onde · LIMITE DE VAGAS · "
    "nucleo (regra 52) — mais migracao e `POST`/`GET`/`PATCH /cursos`. SO' A SECRETARIA anuncia "
    "(regra 57) e os outros tres levam 403 DO SERVIDOR; 0 VAGAS e DATA NO PASSADO sao RECUSADOS "
    "com a frase que diz O QUE ACONTECEU E O QUE FAZER (⛔ nunca «dados invalidos») e ⛔ NADA fica "
    "gravado; o medico PENDENTE le' 200 COM LISTA VAZIA e ⛔ nao 403 (regra 46, mesmo gesto de "
    "`aula.controlador.ts:80-92`). ⛔ NENHUMA inscricao, ⛔ NENHUM ponto e ⛔ NADA da pergunta 94 "
    "neste card (criterios 1, 3 e 6)",
  "escopo_paths": ["backend/prisma/schema.prisma", "backend/prisma/migrations/",
                   "backend/src/modules/curso/", "backend/src/app.module.ts",
                   "backend/test/curso.e2e-spec.ts"],
  "aceites": [
    J.format("test/curso.e2e-spec.ts"),
    t("curso.e2e-spec.ts", "a secretaria anuncia tres cursos: um em Cardiologia, um em Pediatria e um sem nucleo"),
    t("curso.e2e-spec.ts", "curso com 0 vagas e recusado com frase que diz o que fazer, e nada fica gravado"),
    t("curso.e2e-spec.ts", "curso com data no passado e recusado do mesmo modo e a lista continua com tres"),
    t("curso.e2e-spec.ts", "so a secretaria anuncia: coordenador, medico e especialista levam 403"),
    t("curso.e2e-spec.ts", "o medico ATIVO ve os tres cursos e o PENDENTE le 200 com lista vazia"),
    t("curso.e2e-spec.ts", "o curso sem nucleo le-se sem nucleo, sem erro e sem inventar nucleo nenhum"),
    t("curso.e2e-spec.ts", "o limite de vagas fica gravado tal e qual e um curso nunca nasce com zero"),
    t("curso.e2e-spec.ts", "editar o curso guarda e o id nao muda"),
    t("curso.e2e-spec.ts", "o nucleo arquivado nao se oferece num curso novo e continua a ler-se no curso velho"),
    "cd backend && grep -q 'model Curso' prisma/schema.prisma",
    "cd backend && ! grep -rniE 'fila|desisten|cancelar|promocao' src/modules/curso/",
    "cd backend && ! grep -rniE 'pontos?_|ranking|somar' src/modules/curso/",
    "cd backend && ! grep -rniE 'maissaude|mais-saude|mais saude' src/modules/curso/",
    J.format("test/aula.e2e-spec.ts test/nucleo.e2e-spec.ts test/parametro.e2e-spec.ts"),
    J.format(""),
  ], "depende_de": []},

 # ── 2 · a folha de presencas ────────────────────────────────────────────────────────
 {"chave": C2, "territorio": "backend",
  "titulo":
    "A FOLHA DE PRESENCAS, UM A UM E POSTERIOR AO CURSO (regra 52): a linha de inscricao nasce a "
    "CARREGAR A CONFIRMACAO (`compareceu` · `confirmadoPorId` · `confirmadoEm`) e a secretaria "
    "marca cada inscrito por `POST /cursos/:cursoId/inscricoes/:id/compareceu` e "
    "`.../nao-compareceu` com **200 e ⛔ nao 201** («⛔ nao nasce recurso nenhum, decide-se um que "
    "ja' existe», `vinculo.controlador.ts:47`); A LINHA ⛔ NUNCA SE APAGA (`:65`), ⛔ NAO EXISTE "
    "rota que marque a lista inteira (sao TRES rotas, contadas), e ⛔ NAO se grava ponto nenhum — o "
    "FACTO fica guardado para a demanda 29 o somar (precedente medido: `RespostaQuestao` ⛔ nao tem "
    "coluna de pontos, schema:387). ⚠️ A linha nasce SEM estado proprio de inscricao e O ACTO DO "
    "MEDICO SE INSCREVER ⛔ NAO E' DESTE CARD (espera a pergunta 94): as linhas da prova semeiam-se "
    "pelo `PrismaService`, como o `vinculo.e2e-spec.ts` ja' faz (criterios 4 e 5)",
  "escopo_paths": ["backend/prisma/schema.prisma", "backend/prisma/migrations/",
                   "backend/src/modules/inscricao/", "backend/src/app.module.ts",
                   "backend/test/presenca.e2e-spec.ts"],
  "aceites": [
    J.format("test/presenca.e2e-spec.ts"),
    t("presenca.e2e-spec.ts", "com tres inscritos a secretaria marca dois COMPARECEU e um NAO COMPARECEU, um a um"),
    t("presenca.e2e-spec.ts", "cada confirmacao grava quem assinou e quando"),
    t("presenca.e2e-spec.ts", "confirmar responde 200 e nao 201, como o vinculo ja faz"),
    t("presenca.e2e-spec.ts", "nenhuma linha e apagada e reabrir a lista mostra os mesmos dois e um"),
    t("presenca.e2e-spec.ts", "corrigir a marca troca a marca e reescreve quem assinou e quando, sem apagar a linha"),
    t("presenca.e2e-spec.ts", "so a secretaria confirma: coordenador, medico e especialista levam 403"),
    t("presenca.e2e-spec.ts", "inscrito por confirmar le-se por confirmar, e nao como falta"),
    t("presenca.e2e-spec.ts", "a lista de inscritos de um curso que nao existe da 404, e nao lista vazia"),
    t("presenca.e2e-spec.ts", "a presenca confirmada nao grava ponto nenhum: a linha nao tem numero de pontos"),
    "cd backend && grep -q 'model InscricaoCurso' prisma/schema.prisma",
    "cd backend && test $(grep -rE '@(Get|Post|Put|Patch|Delete)\\(' src/modules/inscricao/ | wc -l) -eq 3",
    "cd backend && ! grep -rniE 'pontos?_|ranking|somar' src/modules/inscricao/",
    "cd backend && ! grep -rniE 'fila|desisten|cancelar|promocao' src/modules/inscricao/",
    J.format("test/curso.e2e-spec.ts test/vinculo.e2e-spec.ts"),
    J.format(""),
  ], "depende_de": [C1]},

 # ── 3 · a chave nova de parametro ───────────────────────────────────────────────────
 {"chave": C3, "territorio": "backend",
  "titulo":
    "NASCE A CHAVE `pontos_curso_presencial` (inteiro >= 0, A MESMA FORMA de "
    "`pontos_por_responder`), com a omissao **10 semeada SO' NA MIGRACAO** — ⛔ NENHUM numero em "
    "codigo (regra 53). A secretaria muda-a de 10 para 25 por `PUT "
    "/parametros/pontos_curso_presencial` SEM DEPLOY, os outros tres papeis levam 403, zero e "
    "legitimo, negativo ⛔ nao, e o que ja' esta' gravado ⛔ nao se recalcula. ⚠️ `CHAVES_PARAMETRO` "
    "era fechada POR AMBITO DO CS-10 («⛔ nao se criam nem se apagam chaves aqui»), ⛔ nao por "
    "proibicao para sempre — e o CS-35 ja' abriu a quinta em `epic/CS-32`. ⟹ ⛔ NENHUM aceite "
    "deste card CONTA chaves: medem-se OS NOMES, e as quatro antigas ficam intactas (criterio 5)",
  "escopo_paths": ["backend/src/modules/parametro/", "backend/prisma/migrations/",
                   "backend/test/parametro.e2e-spec.ts"],
  "aceites": [
    J.format("test/parametro.e2e-spec.ts"),
    t("parametro.e2e-spec.ts", "a chave pontos_curso_presencial existe num banco recem-migrado e vale 10"),
    t("parametro.e2e-spec.ts", "a secretaria muda de 10 para 25 sem deploy e a leitura seguinte da 25"),
    t("parametro.e2e-spec.ts", "valor negativo e recusado com frase que diz o que fazer e o valor guardado nao muda"),
    t("parametro.e2e-spec.ts", "valor com casas decimais e recusado: esta chave e inteira, como pontos_por_responder"),
    t("parametro.e2e-spec.ts", "zero e legitimo e nao e erro: comparecer pode nao valer nada"),
    t("parametro.e2e-spec.ts", "so a secretaria muda: coordenador, medico e especialista levam 403"),
    t("parametro.e2e-spec.ts", "as quatro chaves antigas continuam la, com os mesmos nomes e os mesmos limites"),
    t("parametro.e2e-spec.ts", "correr a migracao duas vezes nao sobrescreve o que a secretaria mudou"),
    "cd backend && grep -q \"'pontos_curso_presencial'\" src/modules/parametro/chaves.ts",
    "cd backend && grep -q 'pontos_por_responder' src/modules/parametro/chaves.ts && grep -q 'pontos_extra_por_acertar' src/modules/parametro/chaves.ts && grep -q 'dias_gravacao' src/modules/parametro/chaves.ts && grep -q 'multiplicador_gravada' src/modules/parametro/chaves.ts",
    "cd backend && grep -rqE 'pontos_curso_presencial[^0-9]*10' prisma/migrations/*/migration.sql",
    "cd backend && test $(grep -rn 'pontos_curso_presencial' src/ | grep -cE '[0-9]') -eq 0",
    J.format("test/participacao.e2e-spec.ts test/gravacao.e2e-spec.ts"),
    J.format(""),
  ], "depende_de": []},

 # ── 4 · a porta e a lista ───────────────────────────────────────────────────────────
 {"chave": C4, "territorio": "frontend",
  "titulo":
    "A PORTA «Cursos» ENTRA NA FONTE UNICA E O MEDICO CHEGA A' LISTA PELO MENU (⛔ nao pelo "
    "endereco escrito a' mao): `/cursos` mostra o que e' · quando · onde · O LIMITE DE VAGAS (⛔ NAO "
    "vagas restantes: isso espera a pergunta 94) · o nucleo, filtra por CHIP DE NUCLEO do M3 com ✓ "
    "(regra 83: 1 de 3 · limpar ⟹ 3 de 3; sem nucleo = «Geral») e o VALOR DA PRESENCA CONFIRMADA "
    "VEM DO SERVIDOR (`GET /parametros/pontos_curso_presencial`: 10 ⟹ 10, 25 ⟹ 25, ⛔ zero numeros "
    "em codigo). Porta para SECRETARIA e MEDICO (regra 112), com `icone: undefined` pelo precedente "
    "EXACTO de «UPAs», «Nucleos» e «Sistema» — ⛔ nao ha' nome de icone para cursos no conjunto "
    "FECHADO e ⛔ nao se inventa peca num card de ecra. 🩸 AS TRES PROVAS QUE A PORTA NOVA PARTE SAO "
    "ACTUALIZADAS, ⛔ nao contornadas: `navegacao.spec.ts` (portas + orfas = TODAS as page.tsx), "
    "`alcance.spec.ts` (ESPERADO alcancavel POR CLIQUE + ANCORAS + ESTADOS, cada um com "
    "ficheiro:linha) e `botoes.spec.ts` (⛔ zero botao mudo: `add`, `filter_list` e `chevron_right` "
    "ja' existem). Os SEIS ESTADOS (regra 84), foco de teclado visivel em todos (criterios 1, 3, 5 e 6)",
  "escopo_paths": ["frontend/src/app/cursos/", "frontend/src/lib/navegacao.ts",
                   "frontend/e2e/cursos.spec.ts", "frontend/e2e/navegacao.spec.ts",
                   "frontend/e2e/alcance.spec.ts"],
  "aceites": [
    "cd frontend && npx playwright test e2e/cursos.spec.ts --reporter=line",
    g("cursos.spec.ts", "o medico chega a Cursos pelo menu e ve 3 de 3"),
    g("cursos.spec.ts", "o chip Cardiologia deixa 1 de 3 e limpar o filtro volta a 3 de 3"),
    g("cursos.spec.ts", "o curso sem nucleo mostra-se como Geral e tem chip proprio"),
    g("cursos.spec.ts", "a lista mostra o limite de vagas e nunca vagas restantes"),
    g("cursos.spec.ts", "o medico PENDENTE ve o vazio com a frase do que ali vai estar, e nao um erro"),
    g("cursos.spec.ts", "o valor da presenca confirmada vem do servidor: com 10 le-se 10 e com 25 le-se 25"),
    g("cursos.spec.ts", "os seis estados da lista de cursos: vazio, a carregar, erro, sem permissao, sucesso e parcial"),
    g("cursos.spec.ts", "o 403 do servidor mostra o cadeado e nao deixa ver o dado por baixo"),
    g("cursos.spec.ts", "o vazio do filtro diz como se limpa o filtro"),
    g("cursos.spec.ts", "o foco de teclado e visivel em cada alvo da lista e o alvo mede 48dp"),
    g("cursos.spec.ts", "com prefers-reduced-motion reduce as duracoes sao zero e o estado final e o mesmo pixel"),
    "cd frontend && grep -q \"caminho: '/cursos'\" src/lib/navegacao.ts",
    "cd frontend && ! grep -rqE '#[0-9a-fA-F]{3,8}|shimmer|animate-pulse|<progress' src/app/cursos/",
    "cd frontend && npx playwright test e2e/navegacao.spec.ts e2e/alcance.spec.ts --reporter=line",
    "cd frontend && npx playwright test e2e/botoes.spec.ts e2e/icones.spec.ts e2e/calendario.spec.ts --reporter=line",
    "cd frontend && npx playwright test --reporter=line",
  ], "depende_de": [C1, C3]},

 # ── 5 · anunciar ────────────────────────────────────────────────────────────────────
 {"chave": C5, "territorio": "frontend",
  "titulo":
    "A SECRETARIA ANUNCIA UM CURSO PELO ECRA: `/cursos/novo` — ORFA DELIBERADA (e' ACTO, letra por "
    "letra o motivo de `/secretaria/upas/nova`), chega-se pelo botao em `/cursos` e o botao SO' "
    "aparece a' SECRETARIA. Os CINCO campos (o que e' · quando · onde · limite de vagas · nucleo "
    "ESCOLHIDO DE UMA LISTA, ⛔ nao texto livre — regra 102); 0 VAGAS e DATA NO PASSADO levam a "
    "frase que diz O QUE ACONTECEU E O QUE FAZER (⛔ nunca «campo invalido», regra 85), ⛔ nada fica "
    "gravado e a lista continua com 3; o botao A TRABALHAR ⛔ NAO ENCOLHE e o rotulo FICA NO SITIO; "
    "e um papel que ⛔ nao cadastra leva o **403 DO SERVIDOR** — ⛔ nao basta o botao escondido. Os "
    "SEIS ESTADOS e as tres provas de navegacao ACTUALIZADAS (criterios 1 e 6)",
  "escopo_paths": ["frontend/src/app/cursos/", "frontend/src/lib/navegacao.ts",
                   "frontend/e2e/cursos-anunciar.spec.ts", "frontend/e2e/navegacao.spec.ts",
                   "frontend/e2e/alcance.spec.ts"],
  "aceites": [
    "cd frontend && npx playwright test e2e/cursos-anunciar.spec.ts --reporter=line",
    g("cursos-anunciar.spec.ts", "a secretaria anuncia tres cursos e os tres aparecem na lista: 3 de 3"),
    g("cursos-anunciar.spec.ts", "o nucleo escolhe-se de uma lista e nunca se escreve a mao"),
    g("cursos-anunciar.spec.ts", "um curso pode ficar sem nucleo e le-se como Geral"),
    g("cursos-anunciar.spec.ts", "0 vagas leva a frase que diz o que aconteceu e o que fazer, e nada fica gravado"),
    g("cursos-anunciar.spec.ts", "data no passado leva a mesma forma de recusa e a lista continua com tres"),
    g("cursos-anunciar.spec.ts", "o botao de anunciar mostra-se so a secretaria e o 403 do servidor aparece a quem forca o endereco"),
    g("cursos-anunciar.spec.ts", "o botao a trabalhar mantem o rotulo no sitio e nao muda de largura"),
    g("cursos-anunciar.spec.ts", "os seis estados do ecra de anunciar: vazio, a carregar, erro, sem permissao, sucesso e parcial"),
    g("cursos-anunciar.spec.ts", "o foco de teclado e visivel em cada campo e em cada botao, e o alvo mede 48dp"),
    g("cursos-anunciar.spec.ts", "com prefers-reduced-motion reduce as duracoes sao zero e o estado final e o mesmo pixel"),
    "cd frontend && ! grep -rqE '#[0-9a-fA-F]{3,8}|shimmer|animate-pulse' src/app/cursos/",
    "cd frontend && grep -q \"caminho: '/cursos/novo'\" src/lib/navegacao.ts",
    "cd frontend && npx playwright test e2e/cursos.spec.ts e2e/navegacao.spec.ts e2e/alcance.spec.ts --reporter=line",
    "cd frontend && npx playwright test e2e/botoes.spec.ts --reporter=line",
    "cd frontend && npx playwright test --reporter=line",
  ], "depende_de": [C4]},

 # ── 6 · confirmar, em pagina ────────────────────────────────────────────────────────
 {"chave": C6, "territorio": "frontend",
  "titulo":
    "🩸 CONFIRMAR PRESENCA E' PAGINA E ⛔ NAO DIALOGO — regra 86 a' letra («⛔ ACEITAR MEDICO, "
    "CONFIRMAR PRESENCA E APROVAR CERTIFICADO ⛔ NAO SAO DIALOGOS: SAO PAGINAS»): "
    "`/cursos/[id]/presencas`, orfa deliberada (DETALHE — clica-se o curso em `/cursos`), SO' da "
    "SECRETARIA. Lista os INSCRITOS e marca UM A UM COMPARECEU ou ⛔ NAO COMPARECEU — ⛔ NAO EXISTE "
    "botao que marque a lista inteira; cada linha diz QUEM ASSINOU E QUANDO, ⛔ nenhuma linha "
    "desaparece, reabrir mostra os MESMOS 2 e 1, o «⛔ nao compareceu» conta **0 e ⛔ NUNCA numero "
    "negativo** (⛔ sem castigo na cara do produto, regra 84) e o valor do que compareceu vem do "
    "PARAMETRO. O PARCIAL — o estado mais importante deste produto — e' a lista com uns "
    "confirmados e outros AINDA POR CONFIRMAR, com o total em tabular AO LADO e ⛔ nunca EM VEZ. "
    "Os SEIS ESTADOS e as tres provas de navegacao ACTUALIZADAS (criterios 4, 5 e 6)",
  "escopo_paths": ["frontend/src/app/cursos/", "frontend/src/lib/navegacao.ts",
                   "frontend/e2e/presencas.spec.ts", "frontend/e2e/navegacao.spec.ts",
                   "frontend/e2e/alcance.spec.ts"],
  "aceites": [
    "cd frontend && npx playwright test e2e/presencas.spec.ts --reporter=line",
    g("presencas.spec.ts", "com tres inscritos a secretaria marca dois COMPARECEU e um NAO COMPARECEU, um a um"),
    g("presencas.spec.ts", "nao existe botao nenhum que marque a lista inteira"),
    g("presencas.spec.ts", "confirmar presenca e pagina e nao dialogo: nenhum dialogo no acto"),
    g("presencas.spec.ts", "cada linha confirmada diz quem assinou e quando, e nenhuma linha desaparece"),
    g("presencas.spec.ts", "reabrir a folha mostra os mesmos dois e um"),
    g("presencas.spec.ts", "o marcado como nao compareceu conta 0, nunca um numero negativo e nunca a vermelho"),
    g("presencas.spec.ts", "o que compareceu vale o que o parametro diz: com 10 le-se 10 e com 25 le-se 25"),
    g("presencas.spec.ts", "o estado PARCIAL: uns confirmados e outros por confirmar, com o total ao lado e nunca em vez"),
    g("presencas.spec.ts", "os seis estados da folha de presencas: vazio, a carregar, erro, sem permissao, sucesso e parcial"),
    g("presencas.spec.ts", "quem nao confirma leva o cadeado do 403 do servidor e nao ve o dado por baixo"),
    g("presencas.spec.ts", "o foco de teclado e visivel em cada botao de confirmacao e o alvo mede 48dp"),
    g("presencas.spec.ts", "com prefers-reduced-motion reduce as duracoes sao zero e o estado final e o mesmo pixel"),
    "cd frontend && ! grep -rqiE 'marcar todos|confirmar todos|todos compareceram' src/app/cursos/",
    "cd frontend && ! grep -rqE '#[0-9a-fA-F]{3,8}|shimmer|animate-pulse' src/app/cursos/",
    "cd frontend && npx playwright test e2e/cursos.spec.ts e2e/cursos-anunciar.spec.ts e2e/navegacao.spec.ts e2e/alcance.spec.ts --reporter=line",
    "cd frontend && npx playwright test e2e/botoes.spec.ts --reporter=line",
    "cd frontend && npx playwright test --reporter=line",
  ], "depende_de": [C2, C5]},
]

destino = "/srv/produtos/capacitasalvador/escopo/ENQUADRAMENTO-D39.cards.json"
open(destino, "w", encoding="utf-8").write(json.dumps(cards, ensure_ascii=False, indent=2))
print("cards escritos em", destino)

if "--so-json" in sys.argv:
    sys.exit(0)
r = subprocess.run(["fab", "enquadrar", "39", "--chave", EPIC, "--cards", json.dumps(cards, ensure_ascii=False)],
                   text=True)
sys.exit(r.returncode)
