@extends('layouts.app') @section('titulo', 'Caso ' . $caso->mai) @section('conteudo')
← Voltar à fila
Fichas / Caso {{ $caso->mai }}

Caso {{ $caso->mai }} · {{ $caso->nome_lido ?? 'sem nome' }} · Proc {{ $caso->proc_num }} · @if(in_array($caso->estado, ['por_rever', 'bloqueado'])) {{ $caso->estado }} @elseif(in_array($caso->estado, ['aprovado', 'dry_run_ok', 'inserido_verificado'])) {{ $caso->estado }} @elseif(in_array($caso->estado, ['bloqueado', 'erro_tecnico'])) {{ $caso->estado }} @elseif(in_array($caso->estado, ['em_leitura', 'a_aplicar'])) {{ $caso->estado }} @else {{ $caso->estado }} @endif · versão {{ $caso->versao }}

@if($caso->bloqueio_motivo)
{{ $caso->bloqueio_motivo }}
@endif
@csrf

Cartões PSP na raiz da pasta (todos contam; desmarque só se houver um a mais)

{{-- Marca a presença deste bloco: uma checkbox desmarcada não é submetida --}} @foreach($caso->cartoes as $cartao) @endforeach
@foreach($caso->cartoes as $cartao) @endforeach
Número Categoria impressa Sufixo Validade Confiança
conta_como_especialidade ? 'checked' : '' }}> {{ $cartao->numero }} {{ $cartao->categoria_texto_exibicao }} {{ $cartao->categoria_sufixo }} {{ $cartao->validade ? \App\Support\Datas::paraUi($cartao->validade->format('Y-m-d')) : '—' }} @if($cartao->confianca === 'alta') alta @elseif($cartao->confianca === 'rever') rever @else {{ $cartao->confianca }} @endif @if($cartao->documento_id) @endif
Especialidades resultantes: {{ implode('/', \App\Services\Fichas\Extratores\CartaoPsp::ordenar($caso->cartoes->where('conta_como_especialidade', true)->pluck('categoria_sufixo')->filter()->unique()->all())) ?: '—' }}

Campos

@php $rotulos = [ 'nome' => 'Nome', 'nascimento' => 'Nascimento', 'ncc' => 'NCC', 'validade_cc' => 'Validade CC', 'niss' => 'NISS', 'filho_de' => 'Filho de', 'val_rc' => 'RC (val_rc)', 'naturalidade' => 'Naturalidade', 'concelho' => 'Concelho', 'distrito' => 'Distrito', 'nacionalidade' => 'Nacionalidade', 'morada' => 'Morada', 'iban' => 'IBAN', 'email' => 'Email', 'contacto' => 'Contacto', 'estado_civil' => 'Estado civil', 'ndependentes' => 'Nº dependentes', 'habilitacoes' => 'Habilitações', 'nome_emergencia' => 'Emergência (nome)', 'contacto_emergencia' => 'Emergência (contacto)', 'relacao_emergencia' => 'Emergência (relação)', ]; @endphp @foreach($camposTabela as $c) @php $ehData = in_array($c->campo, $camposData, true); $ehMorada = $c->campo === 'morada'; $valorUi = $ehData ? \App\Support\Datas::paraUi($c->valor_final) : $c->valor_final; $visionUi = $ehData && $c->valor_vision ? \App\Support\Datas::paraUi($c->valor_vision) : $c->valor_vision; $qwenUi = $ehData && $c->valor_qwen ? \App\Support\Datas::paraUi($c->valor_qwen) : $c->valor_qwen; // As leituras alternativas de morada (botão "usar") vinham em // maiúsculas, sem normalizar — o utilizador escolhia e o valor // guardado ficava fora do padrão da casa (feedback 25/09/2026). // Mostra sempre "original -> normalizada" e é a normalizada que // o botão "usar" grava. $moradaExtratora = $ehMorada ? new \App\Services\Fichas\Extratores\Morada : null; $visionMorada = $ehMorada && $visionUi ? $moradaExtratora->normalizar($visionUi) : null; $qwenMorada = $ehMorada && $qwenUi ? $moradaExtratora->normalizar($qwenUi) : null; $visionValorUsar = $visionMorada ? $visionMorada['normalizada'] : $visionUi; $qwenValorUsar = $qwenMorada ? $qwenMorada['normalizada'] : $qwenUi; $visionTexto = $visionMorada ? "{$visionMorada['original']} -> {$visionMorada['normalizada']}" : $visionUi; $qwenTexto = $qwenMorada ? "{$qwenMorada['original']} -> {$qwenMorada['normalizada']}" : $qwenUi; @endphp @php $vazio = ($c->valor_final ?? '') === ''; @endphp @endforeach
Campo Valor Origem Confiança
{{ $rotulos[$c->campo] ?? $c->campo }} @if($c->motivo_revisao)
{{ $c->motivo_revisao }}
@endif @if($c->confianca === 'rever')
@if($visionUi) @endif @if($qwenUi) @endif @if(! $valorUi && $c->campo !== 'val_rc') {{-- Regressão real (concelho/distrito, 24/09/2026): um campo 'rever' sem valor e sem leitura alternativa (nada para "[usar]") nunca tinha forma de sair de 'rever' — gravar a caixa vazia era ignorado (o texto não mudou) e Aprovar ficava desativado para sempre. --}} @endif
{{ $ehData ? 'Escreva a data (dd/mm/aaaa) ou use uma das leituras.' : 'Escreva o valor ou use uma das leituras.' }}
@endif
{{ match (true) { $c->origem === 'sem_portal' => 'sem submissão no portal', $c->origem === 'portal' && $vazio => 'portal (não respondido)', default => $c->origem, } }} @if($vazio && $c->confianca === 'alta') vazio @elseif($c->confianca === 'alta') alta @elseif($c->confianca === 'rever') rever @else {{ $c->confianca }} @endif @if($c->documento_id) @endif

Local de serviço / Zonas

@if(!$caso->condicoes_definidas_manualmente)
Este caso veio de um registo em lote — contrato/condições/App RH ficaram nos valores por omissão. Reveja antes de aprovar.
@endif
Zona 1 @if(!empty($zonas)) @else @endif Zona 2 @if(!empty($zonas)) @else @endif Nº de horas Período
Local de serviço {{ $caso->local_servico_ncontrato ? "nº contrato {$caso->local_servico_ncontrato}" : 'texto livre — sem afetação de equipa' }}

Contrato

Modalidade @if($errors->has('contrato_modalidade'))
{{ $errors->first('contrato_modalidade') }}
@endif
Data de início @if($errors->has('contrato_data_inicio'))
{{ $errors->first('contrato_data_inicio') }}
@endif
Duração Unidade
Renovável
Período de renovação Unidade
Nº renovações previstas
Motivo @if($errors->has('contrato_motivo_alinea'))
{{ $errors->first('contrato_motivo_alinea') }}
@endif

Condições

Valor:
Valor:
Dias de trabalho: @php $diasAtuais = $caso->condDiasTrabalhoArray(); @endphp @foreach(['Seg', 'Ter', 'Qua', 'Qui', 'Sex', 'Sáb', 'Dom'] as $i => $rotuloDia) @endforeach
Notas das condições:

App RH

Estado
@php $chkRotulos = [ 'checksums' => 'Checksums', 'luhn_cc' => 'Luhn do CC', 'rc' => 'RC vigente e finalidade', 'morada' => 'Morada normalizada', 'varrimento' => 'Varrimento completo', 'portal' => 'Portal dados', ]; $chkTextos = ['ok' => 'ok', 'pendente' => 'por verificar', 'falha' => 'falhou']; $portalNotas = [ 'nao_encontrado' => 'Portal dos dados: sem submissão no portal.', 'divergencia' => 'Portal dos dados: data de nascimento não bate — confirmar.', 'indisponivel' => 'Portal dos dados: indisponível — campos em branco.', 'ok_nif_diferente' => 'Portal dos dados: submissão encontrada pelo nome e nascimento, com NIF diferente — confirme os campos preenchidos.', 'ambiguo_nome' => 'Portal dos dados: várias submissões com o mesmo nome e nascimento — confirmar manualmente.', ]; @endphp
@foreach($chkRotulos as $chave => $rotulo) {{ $rotulo }} — {{ $chkTextos[$checklist[$chave]] }} @endforeach
@if($caso->portal_status && isset($portalNotas[$caso->portal_status]))
portal_status !== 'nao_encontrado') style="background:var(--blk-bg);color:var(--blk)" @endif>{{ $portalNotas[$caso->portal_status] }}
@endif @if($podeEditarSecoes) {{-- O formulário "portal-form" fica fora do formulário de edição (não pode haver formulários aninhados); o botão liga-se a ele pelo atributo form (revisão do Codex, 24/09/2026). --}}
@endif @if($camposTabela->where('confianca', 'rever')->count() > 0)
{{ $camposTabela->where('confianca', 'rever')->count() }} campo(s) por resolver
@endif

Resumo para aprovação

SecçãoResumo
Contrato{{ $resumoContrato }}
Condições{{ $resumoCondicoes }}
App RH{{ $resumoAppRh }}
@csrf
@csrf
@php($rotuloAplicar = 'Aplicar em '.strtoupper($ambiente ?? 'DEV')) @if($podeAplicarDev)
@csrf @if($ambiente === 'prod') @endif
@else @endif @if($podeReconciliar)
@csrf
@else @endif
@csrf
@if($podePdfEmail) Descarregar ficha PDF
email_enviado_em) onsubmit="return confirm('Esta ficha já foi enviada em {{ $caso->email_enviado_em->format('d/m/Y H:i') }}. Enviar novamente?')" @endif> @csrf {{-- email não é idempotente (docs/03-CONTRATO-WRAPPER-CT107.md): reenviar é sempre uma decisão explícita, nunca o efeito de um duplo clique — o controlador recusa sem este campo quando já há um envio anterior. --}} @if($caso->email_enviado_em) @endif
@else @endif @if($caso->email_enviado_em) email enviado {{ $caso->email_enviado_em->format('d/m') }} @endif @if($caso->estado === 'inserido_verificado')
@csrf
@else @endif
@if(!is_null($conferenciaLinhas))

Conferência pós-inserção

@if(empty($conferenciaLinhas)) @else @foreach($conferenciaLinhas as $linha) @endforeach @endif
Campo Manifesto Sabichão Estado
Sem campos para conferir.
{{ $linha['campo'] }} {{ $linha['manifesto'] }} {{ $linha['sabichao'] }} @if($linha['estado'] === 'ok') ok @elseif($linha['estado'] === 'equivalente') equivalente (confirmado pelo modelo) @elseif($linha['estado'] === 'diferente') diferente @endif
@endif

Histórico de execuções

@forelse($execucoes as $execucao) @empty @endforelse
Ação Alvo Exit code Quando
{{ $execucao->acao }} {{ $execucao->alvo }} @if($execucao->exit_code === null) sem resposta @elseif($execucao->exit_code === 0) 0 @else {{ $execucao->exit_code }} @endif {{ $execucao->created_at->format('d/m/Y H:i') }}
Sem execuções.

Documentos da pasta

@forelse($caso->documentos as $doc) @empty @endforelse
Ficheiro Tipo classificado Páginas
{{ $doc->ficheiro }} {{ $doc->tipo ?? 'outro' }} {{ $doc->paginas ?? 1 }} @for($p = 1; $p <= ($doc->paginas ?? 1); $p++) @endfor
Sem documentos lidos.
@if($podeEditarSecoes) @endif @endsection @push('head') @endpush