# Épico 6 — Seleção de Clínica — Especificação (v1.3)

**Documentos deste épico:** [01 - Definição](./01%20-%20Defini%C3%A7%C3%A3o%20v1.3.md) (v1.3) · [02 - Especificação](./02%20-%20Especifica%C3%A7%C3%A3o%20v1.3.md) (v1.3) · [03 - Protótipo](./03%20-%20Prot%C3%B3tipo%20v1.1.md) (v1.1, código executável em `03 - Protótipo v1.1.html`) · [04 - Mapeamento de Banco de Dados](./04%20-%20Mapeamento%20de%20Banco%20de%20Dados%20v1.3.md) (v1.3). Este épico faz parte do documento guarda-chuva [Check-in de Usuários](../00%20-%20Guarda-chuva%20v2.20.md).

---

## SEÇÃO 2 — ESPECIFICAÇÃO

### Histórias de Usuário

- **US-CIU-006:** Como profissional com acesso a mais de uma clínica, quero escolher em qual clínica vou trabalhar logo após o login, para acessar rapidamente o contexto certo.
- **US-CIU-007:** Como profissional com um convite pendente de uma nova clínica, quero aceitar esse convite na mesma tela onde escolho minha clínica, sem repetir a leitura de regras que já conheço, para agilizar meu acesso sem uma tela extra.

### Descrição Funcional Detalhada

#### Lista de Clínicas

Exibe um item por clínica acessível (`ClienteEmpresa` **ativa**, `RemovidoEm` nulo) do profissional autenticado — agregando todos os vínculos ativos (`UsuarioCliente.Situacao = 'V'`) e, para cada um, todas as `ClienteEmpresa` associadas via `ClientePerfilAcessoUsuario` — nome da clínica (`ClienteEmpresa.Nome`) e logo (`ClienteEmpresa.CaminhoLogo`, quando presente). Uma `ClienteEmpresa` desativada some da lista, mesmo com Perfil de acesso associado — confirmado pelo solicitante. Cada item é clicável e dispara a seleção daquela clínica específica (RN-CIU-015). **[PROPOSTA — critério de ordenação não fornecido pelo solicitante]** lista ordenada alfabeticamente por nome da clínica, na ausência de um critério mais específico (ex.: última clínica acessada).

Por construção, todo item que entra nesta lista já tem, no momento em que a lista é carregada, um registro em `ClientePerfilAcessoUsuario` — ou seja, já tinha Perfil de acesso associado. A seleção sem Perfil de acesso descrita em CA-006.3/EX-E6-01 abaixo não é, portanto, o caso comum: é uma condição de corrida — o administrador removeu o Perfil de acesso daquela clínica (via gestão de usuários da clínica, fora do escopo deste épico) no intervalo entre a lista ser carregada e o profissional clicar no item. O mesmo tipo de condição de corrida já é tratado, para a clínica inteira ser removida, em EX-E6-05; CA-006.3/EX-E6-01 é o equivalente para a associação de Perfil de acesso especificamente.

Motivador: é a lista normal de navegação do profissional que atende em mais de uma clínica — existia antes deste épico (E1, roteamento por múltiplos vínculos) e é apenas formalizada aqui.

#### Card de Convite Pendente

Um card por convite pendente (`UsuarioCliente.Situacao = 'C'`), visualmente destacado da lista de clínicas (RN-CIU-025) — borda ou fundo diferenciado (token `--color-highlight-*`, reservado para estados que pedem atenção, não para navegação normal), nome da(s) clínica(s) já associadas ao Perfil de acesso do convite (ou do Cliente convidante, como fallback, enquanto nenhuma `ClienteEmpresa` está associada — CIU-E3), botão primário "Aceitar". Fica acima ou separado da lista, nunca misturado aos itens de seleção.

Motivador: o card representa uma ação pendente do profissional (decisão a tomar), não uma opção de navegação — misturá-lo à lista faria o profissional confundir "aceitar convite" com "selecionar clínica" (mesmo motivador já registrado em RN-CIU-025 / Seção 1).

Ao clicar em "Aceitar": chama o endpoint de aceite (Seção "Integração com Backend"), atualiza `Situacao` para 'V', remove o card da área de destaque e insere a(s) clínica(s) associada(s) ao Perfil de acesso do convite na lista normal — sem selecionar nenhuma delas. O profissional permanece na tela, agora com um ou mais itens a mais na lista, conforme a quantidade de `ClienteEmpresa` associadas ao Perfil de acesso do convite. Quando o convite não tinha nenhuma `ClienteEmpresa` associada (RN-CIU-011, em CIU-E3, permite deixar essa seleção pendente), nenhum item novo entra na lista — comportamento visual desse caso ainda **[A DEFINIR]** (ver 01 - Definição, RN-CIU-025 e lista de itens [PROPOSTA]).

#### Seleção de Clínica

Clicar num item da lista dispara a verificação de Perfil de acesso (RN-CIU-015) para aquela clínica. Dois desfechos:

- **Perfil de acesso encontrado:** navega para a tela inicial do profissional naquela clínica (cockpit — CM_CockPit_do_medico, ou equivalente de cada perfil profissional).
- **Perfil de acesso ausente:** permanece nesta tela, exibindo mensagem orientativa (MSG-I6-01) — mesmo texto e tratamento usados no E3 para o caso equivalente pós-aceite do vínculo (RN-CIU-015). Como descrito em "Lista de Clínicas" acima, este desfecho só ocorre por condição de corrida (Perfil de acesso removido entre o carregamento da lista e o clique) — todo item chega à lista já com Perfil de acesso associado.

Motivador: a verificação de Perfil de acesso é sempre a mesma, o momento em que roda é que muda — antes (E3, aceite via tela cheia) ou aqui (na seleção) (ver RN-CIU-025).

### Critérios de Aceitação

**CA-006.1**: Dado que estou autenticado e tenho acesso a mais de uma clínica, quando o roteamento pós-login me traz a esta tela, então vejo a lista de clínicas acessíveis, uma por item.

**CA-006.2**: Dado que estou nesta tela, quando seleciono uma clínica da lista e tenho Perfil de acesso associado a ela, então sou levado à tela inicial do meu perfil profissional naquela clínica.

**CA-006.3**: Dado que estou nesta tela, quando seleciono uma clínica da lista cujo Perfil de acesso foi removido depois que a lista foi carregada (condição de corrida — ver "Lista de Clínicas", Descrição Funcional), então permaneço na tela e vejo a mensagem orientativa MSG-I6-01.

**CA-006.4**: Dado que tenho acesso a exatamente 1 clínica e nenhum convite pendente, quando faço login, então não vejo esta tela — vou direto ao cockpit (RN-CIU-009).

**CA-007.1**: Dado que tenho ao menos um vínculo ativo e um convite pendente de outra clínica, quando chego a esta tela, então vejo um card em destaque com o nome da clínica convidante e um botão "Aceitar", separado da lista normal.

**CA-007.2**: Dado que vejo o card de convite pendente, quando clico em "Aceitar", então o vínculo passa a `Situacao = 'V'`, o card sai da área de destaque e a(s) clínica(s) associada(s) ao Perfil de acesso do convite passam a constar na lista normal.

**CA-007.3**: Dado que acabei de aceitar um convite pelo card, quando a ação é concluída, então **não** sou levado automaticamente para nenhuma das clínicas recém-aceitas — preciso selecionar uma delas na lista, como qualquer outra.

**CA-007.4**: Dado que tenho exatamente 1 vínculo ativo e um convite pendente simultâneo, quando faço login, então vejo esta tela (não o cockpit direto) — com a clínica do vínculo ativo na lista e o card do convite em destaque.

**CA-007.5**: Dado que o card de convite pendente ainda não foi aceito, quando verifico meu Perfil de acesso para a clínica em vias de aceitar, então nenhuma verificação de Perfil de acesso é feita — ela só ocorre depois, quando eu selecionar essa clínica já aceita na lista.

### Fluxos de Exceção

**EX-E6-01 — Seleção sem Perfil de acesso associado**

Gatilho: profissional seleciona uma clínica da lista para a qual não existe registro em `ClientePerfilAcessoUsuario` (mesma verificação de RN-CIU-015, reaproveitada do E3) — na prática, uma condição de corrida: todo item chega à lista já com Perfil de acesso associado (ver "Lista de Clínicas", Descrição Funcional), então isso só acontece se o administrador removeu essa associação depois de a lista ter sido carregada. Comportamento: permanece na tela de seleção, exibe mensagem orientativa MSG-I6-01 associada ao item da lista. Recuperação: profissional aguarda o administrador da clínica configurar seu Perfil de acesso, ou seleciona outra clínica da lista.

**EX-E6-02 — Falha ao aceitar convite pelo card**

Gatilho: back-end retorna erro ao tentar atualizar `Situacao` de 'C' para 'V' no aceite do card. Comportamento: toast de erro MSG-E6-01; o card permanece na área de destaque, inalterado. Recuperação: profissional tenta "Aceitar" novamente.

**EX-E6-03 — Convite expira enquanto o profissional está na tela**

Gatilho: profissional tenta aceitar um card cujo convite expirou (RN-CIU-024, em CIU-E3) entre o carregamento da tela e o clique em "Aceitar". Comportamento: toast de erro MSG-E6-02; o card sai da área de destaque (o convite não existe mais como pendente). Recuperação: profissional contata o administrador da clínica convidante para receber um novo convite — mesma orientação do E3 (RN-CIU-024).

**EX-E6-04 — Falha ao carregar a lista de clínicas e convites**

Gatilho: erro de rede ou falha do back-end ao buscar vínculos ativos e convites pendentes do profissional autenticado. Comportamento: estado de erro na tela (mensagem MSG-E6-03 + ação "Tentar novamente"), sem lista nem cards exibidos. Recuperação: profissional aciona "Tentar novamente"; se persistir, orientação a contatar o suporte.

**EX-E6-05 — Clínica removida entre o carregamento da lista e a seleção**

**[PROPOSTA — cenário não descrito pelo solicitante]** Gatilho: profissional seleciona uma clínica cujo registro em `ClienteEmpresa` foi removido (`RemovidoEm` preenchido) no intervalo entre carregar a lista e clicar no item. Comportamento proposto: toast de erro MSG-E6-04, lista recarregada automaticamente sem o item removido. Recuperação: profissional seleciona outra clínica da lista atualizada. Sinalizado como proposta por ser um cenário de borda não descrito pelo solicitante — a confirmar se vale a pena tratar nesta primeira versão ou se é aceitável deixar como erro genérico de seleção.

### Validações de Campos

Esta tela não tem campos de entrada de dados — apenas seleção de itens de lista e um botão de aceite por card. Não há, portanto, validações de campo a documentar (diferente de E2/E3, que têm formulários).

### Mapeamento de Componentes de Interface

|Componente|Variante|Estados|Tokens aplicados|Uso nesta tela|
|---|---|---|---|---|
|Card|Seleção (clínica)|default, hover, focus, loading|`--radius-l` (12px), `--shadow-level-light`, `--color-base-pure` bg, `--spacing-m` padding|Um por clínica com vínculo ativo, na lista normal|
|Card|Destaque (convite pendente)|default, loading (durante aceite)|`--radius-l` (12px), borda `--border-medium` em `--color-highlight-pure` (#FFC400), `--color-highlight-light` bg (#FFF8E1)|Um por convite pendente, separado da lista|
|Button|Primary|default, hover, loading, disabled|`--btn-height` (40px), `--color-primary-pure` bg (#00E676), `--color-base-pure` texto, `--radius-m` (8px)|Botão "Aceitar" no card de convite (MSG-B6-01)|
|Banner/Ícone informativo|Info|default|`--color-secondary-pure` (#40C4FF)|Mensagem de bloqueio sem Perfil de acesso (MSG-I6-01), associada ao item da lista|
|Toast|Error|default|`--color-error-pure` (#F44336)|Falhas de aceite de convite, expiração, carregamento (MSG-E6-01 a 04)|
|Skeleton|Lista|loading|`--color-neutral-lighter` (#E5EAF0)|Estado de carregamento inicial da tela, antes da lista e dos cards chegarem|

### Integração com Backend

**GET /api/v1/checkin/clinic-selection**
Função: retorna, para o profissional autenticado, a lista de clínicas acessíveis e os convites pendentes.
Request: nenhum parâmetro (usuário identificado pelo token de sessão).
Response: `{ clinicasAtivas: [{ usuarioClienteId, clienteBDId, clienteEmpresaId, nome, caminhoLogo }], convites: [{ usuarioClienteId, clienteBDId, clienteNome, clienteLogo, clienteEmpresas: [{ clienteEmpresaId, nome, caminhoLogo }] }] }` — `clienteEmpresas` vem vazio enquanto o convite ainda não tem Perfil de acesso/clínica associados (CIU-E3); nesse caso o front-end usa `clienteNome`/`clienteLogo` (Cliente) como fallback no card. O campo é chamado `convites` (não `convitesPendentes`) de propósito — no login (E1, `/api/auth/login`) `convitesPendentes` é um número (contagem), usado só para decidir o desvio de roteamento; aqui é uma lista completa de objetos, e reaproveitar o mesmo nome para um formato diferente criaria ambiguidade entre os dois documentos.
Códigos: 200 (sucesso, listas podem vir vazias apenas para `convites`); 401 (não autenticado); 500 (falha ao carregar — EX-E6-04).
Tabelas envolvidas: UsuarioCliente, ClientePerfilAcessoUsuario, ClienteEmpresa (IpSeguranca).

**POST /api/v1/checkin/clinic-selection/{usuarioClienteId}/accept-invite**
Função: aceita o convite pendente de um card, sem selecionar nenhuma clínica.
Request: nenhum corpo — `usuarioClienteId` no path identifica o vínculo.
Response: `{ usuarioClienteId, clienteEmpresas: [{ clienteEmpresaId, nome, caminhoLogo }] }` (uma ou mais clínicas, conforme associadas ao Perfil de acesso do convite — agora prontas para entrar na lista normal; vem vazio quando o convite não tinha nenhuma `ClienteEmpresa` associada — RN-CIU-011, em CIU-E3).
Códigos: 200 (sucesso); 404 (convite não encontrado ou já resolvido); 409 (convite expirado — EX-E6-03, retorna MSG-E6-02); 500 (falha ao atualizar — EX-E6-02).
Tabelas envolvidas: UsuarioCliente (atualiza `Situacao`), ClientePerfilAcessoUsuario e ClienteEmpresa (para montar o array `clienteEmpresas` da resposta), UsuarioClienteConvite (deleta o registro — mesma tabela e mecanismo do E3, RN-CIU-024).

**POST /api/v1/checkin/clinic-selection/{usuarioClienteId}/{clienteEmpresaId}/select**
Função: seleciona a clínica específica para a sessão de trabalho, rodando a verificação de Perfil de acesso.
Request: nenhum corpo — `usuarioClienteId` e `clienteEmpresaId` no path.
Response, com Perfil de acesso: `{ perfilEncontrado: true, redirecionarPara: "<rota do cockpit do perfil>" }`. Sem Perfil de acesso: `{ perfilEncontrado: false, mensagem: "MSG-I6-01" }`.
Códigos: 200 (sucesso, com ou sem perfil — o corpo diferencia); 404 (clínica removida ou vínculo inválido — EX-E6-05); 401 (não autenticado).
Tabelas envolvidas: UsuarioCliente, ClientePerfilAcesso, ClientePerfilAcessoUsuario (filtrado também por ClienteEmpresaId) (IpSeguranca) — mesma verificação de RN-CIU-015, em CIU-E3.

Nota sobre padronização de nomes: os três endpoints acima e o E3 (`GET /api/convites/validar`, 02 - Especificação) usam o mesmo par de nomes para o mesmo conceito — array `clienteEmpresas`, item com campo `clienteEmpresaId` — eliminando a inconsistência que existia entre `clienteEmpresas: [{ id, ... }]` (E3), `clienteEmpresas: [{ clienteEmpresaId, ... }]` (este GET) e `clinicas: [{ clienteEmpresaId, ... }]` (este accept-invite, agora renomeado para `clienteEmpresas`).

### Catálogo de Mensagens do Sistema e Termos de Interface (i18n)

**Erros:**

|ID|Contexto|pt-BR|en-US|es-419|
|---|---|---|---|---|
|MSG-E6-01|Falha ao aceitar convite pelo card|"Não foi possível aceitar o convite. Tente novamente."|"Could not accept the invitation. Please try again."|"No fue posible aceptar la invitación. Inténtelo de nuevo."|
|MSG-E6-02|Convite expirado ao tentar aceitar|"Este convite expirou. Entre em contato com o administrador da clínica para receber um novo convite."|"This invitation has expired. Contact the clinic's administrator to receive a new invitation."|"Esta invitación ha expirado. Comuníquese con el administrador de la clínica para recibir una nueva invitación."|
|MSG-E6-03|Falha ao carregar lista de clínicas e convites|"Não foi possível carregar suas clínicas. Tente novamente."|"Could not load your clinics. Please try again."|"No fue posible cargar sus clínicas. Inténtelo de nuevo."|
|MSG-E6-04|Clínica removida entre carregar a lista e selecionar **[PROPOSTA]**|"Esta clínica não está mais disponível."|"This clinic is no longer available."|"Esta clínica ya no está disponible."|

**Informativas:**

|ID|Contexto|pt-BR|en-US|es-419|
|---|---|---|---|---|
|MSG-I6-01|Perfil de acesso ausente na clínica selecionada|"Você ainda não tem um perfil de acesso configurado nesta clínica. Aguarde ou entre em contato com o administrador."|"You don't have an access profile configured at this clinic yet. Wait or contact the administrator."|"Aún no tiene un perfil de acceso configurado en esta clínica. Espere o comuníquese con el administrador."|

**Textos orientadores:**

|ID|Contexto|pt-BR|en-US|es-419|
|---|---|---|---|---|
|MSG-T6-01|Título da área de destaque de convites|"Convite pendente"|"Pending invitation"|"Invitación pendiente"|
|MSG-T6-02|Título da lista de clínicas|"Selecione uma clínica"|"Select a clinic"|"Seleccione una clínica"|

**Botões/links:**

|ID|Contexto|pt-BR|en-US|es-419|
|---|---|---|---|---|
|MSG-B6-01|Botão (card de convite)|"Aceitar"|"Accept"|"Aceptar"|
|MSG-B6-02|Ação de recuperação (erro de carregamento)|"Tentar novamente"|"Try again"|"Intentar de nuevo"|

### Conformidade SBIS (detalhamento)

**NGS1.03.01 — Impedir acesso por pessoas não autorizadas** (estágio 1, obrigatório) — a seleção de clínica só concede acesso à tela inicial do profissional quando há Perfil de acesso associado (RN-CIU-015); sem ele, o profissional permanece bloqueado nesta tela, com orientação (MSG-I6-01).

**NGS1.03.03 — Gerenciamento de perfis** (estágio 1, obrigatório) — este épico consome a verificação de Perfil de acesso a cada seleção de clínica, não apenas uma vez no aceite do vínculo (E3) — reforça o controle a cada troca de contexto de trabalho.

**ECF.17.19 — Mensagens do sistema** (estágio 1, obrigatório) — todas as mensagens exibidas ao profissional estão catalogadas acima, em linguagem não técnica, em português do Brasil, com suporte multilíngue para en-US e es-419.

---

## Histórico de Versões

- **v1.0** (29/08/2026) — Arquivo criado pela divisão do documento único `(CIU-E6) Seleção de Clínica v1.0.md` em arquivos por seção (ver Skill Designer, "Organização Física: Pasta por Funcionalidade", Lição #18). Conteúdo sem alteração de substância em relação à v1.0 original — apenas reorganização física (o protótipo HTML já era um arquivo separado desde a v1.0 original, só foi renomeado para a convenção nova). Histórico de revisões anterior a esta divisão (v0.1 a v1.0) preservado integralmente no documento original arquivado.
- **v1.1** (14/09/2026) — Lista de Clínicas e card de convite pendente passam a operar por `ClienteEmpresa` (clínica), não por `Cliente` (tenant): cada item da lista é uma clínica acessível, e o aceite de um convite pode inserir mais de um item de uma vez, conforme a quantidade de `ClienteEmpresa` associadas ao Perfil de acesso do convite. Endpoints atualizados (`GET .../clinic-selection`, `.../accept-invite`, `.../select` — este último agora recebe `clienteEmpresaId` no path). Ver RN-CIU-009 (CIU-E1) e RN-CIU-011/015 (CIU-E3) para a regra completa.
- **v1.2** (14/09/2026) — quatro correções sobre a v1.1: (1) esclarecida a aparente contradição entre "Lista de Clínicas" (todo item já tem Perfil de acesso por construção) e CA-006.3/EX-E6-01 (seleção sem Perfil de acesso): o segundo caso é uma condição de corrida — Perfil de acesso removido entre carregar a lista e selecionar — mesmo padrão já usado em EX-E6-05 para a clínica inteira ser removida; texto de ambos os pontos explicitado; (2) `Tabelas envolvidas` do endpoint `accept-invite` estava incompleta — incluía apenas `UsuarioCliente` e `UsuarioClienteConvite`, mas a resposta monta um array de `ClienteEmpresa` que depende de `ClientePerfilAcessoUsuario` e `ClienteEmpresa`; ambas adicionadas; (3) padronização de nomes entre E3 e E6: o array de clínicas de um convite agora se chama `clienteEmpresas` com campo `clienteEmpresaId` nos três lugares onde aparece (E3 `GET /convites/validar`, este `GET .../clinic-selection`, este `POST .../accept-invite` — que usava `clinicas`, renomeado); (4) o array de convites pendentes de `GET .../clinic-selection`, antes `convitesPendentes`, renomeado para `convites` — no login (E1) `convitesPendentes` é um número, e reaproveitar o nome aqui para uma lista de objetos criava um conflito de tipos entre os dois documentos. RN-CIU-025 (01 - Definição) também ganhou, nesta mesma revisão, uma nota sobre o caso de borda de convite aceito sem nenhuma `ClienteEmpresa` associada — refletida aqui na Descrição Funcional e no endpoint `accept-invite`.
- **v1.3** (14/09/2026) — "Lista de Clínicas" passa a filtrar apenas `ClienteEmpresa` ativas (`RemovidoEm` nulo) — consequência da confirmação do solicitante de que uma clínica desativada tira o acesso de todos os usuários hoje associados a ela. Ver RN-CIU-025 (Seção 1) para a regra completa.
