fix(comunicacao-interna): remove duplicação de agenda — fonte única (jornada real)

A seção "Agenda/Horários de atendimento" do modal era uma 3ª fonte de horário que a
Secretária NÃO lia (ela agenda por clinicas_horarios + dentistas_horarios). Removida:
- Modal cuida só de contato (endereço + números + janelas de envio). Para dentista,
  mostra o STATUS da jornada real e um botão que abre "Horários de Funcionamento"
  (HorariosConfig, aba dentistas) por cima; ao fechar, recarrega.
- Completude do DENTISTA passa a exigir a jornada REAL configurada (dentistas_horarios),
  não mais a agenda do modal. GET recomputa `completo` ao vivo + devolve jornada_configurada.
- Backend: comunicacaoCompleto(p, ehDentista, jornadaOk) + helper jornadaConfigurada.

Verificado: dentista com jornada → completo; query bate com dentistas_horarios real.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
VPS 4 Builder
2026-07-08 00:14:32 +02:00
parent e8d1a5547e
commit e9f8a24a93
2 changed files with 53 additions and 32 deletions
+20 -7
View File
@@ -7556,22 +7556,30 @@ async function resolveOrigem(req, cep, clinicaId) {
// ─── Comunicação interna (perfil de contato de não-pacientes) ────────────────────
// Regra de completude que destrava o gate: endereço + ≥1 número (com descrição e ≥1
// janela de horário) + disponibilidade de agenda decidida.
function comunicacaoCompleto(p, ehDentista) {
function comunicacaoCompleto(p, ehDentista, jornadaOk) {
const e = p.endereco || {};
const nums = Array.isArray(p.numeros) ? p.numeros : [];
const ag = p.agenda || {};
const enderecoOk = !!(e.cidade && e.estado && (e.cep || e.logradouro));
const numerosOk = nums.length >= 1 && nums.every((n) =>
n && String(n.numero || '').replace(/\D/g, '').length >= 10 && String(n.descricao || '').trim() &&
Array.isArray(n.janelas) && n.janelas.length >= 1);
// Agenda só é exigida de DENTISTA (quem atende); demais papéis não precisam.
const agendaOk = !ehDentista || ag.aberta === false || (ag.aberta === true && Array.isArray(ag.horarios) && ag.horarios.length >= 1);
return enderecoOk && numerosOk && agendaOk;
// A agenda saiu do modal (fonte única = dentistas_horarios). Do DENTISTA exige-se a
// jornada REAL configurada em "Horários de Funcionamento".
const jornadaExigidaOk = !ehDentista || !!jornadaOk;
return enderecoOk && numerosOk && jornadaExigidaOk;
}
async function papelNaClinica(usuarioId, clinicaId) {
const { rows } = await pool.query('SELECT role FROM vinculos WHERE usuario_id=$1 AND clinica_id=$2', [usuarioId, clinicaId]);
return rows[0]?.role || '';
}
// Jornada de atendimento do dentista já configurada? (fonte real usada pela Secretária)
async function jornadaConfigurada(usuarioId, clinicaId) {
const { rows: dr } = await pool.query('SELECT id FROM dentistas WHERE usuario_id=$1 AND clinica_id=$2 LIMIT 1', [usuarioId, clinicaId]);
const dentId = dr[0]?.id;
if (!dentId) return false;
const { rows } = await pool.query('SELECT 1 FROM dentistas_horarios WHERE dentista_id=$1 AND COALESCE(ativo,1) <> 0 LIMIT 1', [dentId]);
return rows.length > 0;
}
// Clínica do usuário: param explícito → primeiro vínculo.
async function resolveClinicaUsuario(userId, clinicaIdParam) {
if (clinicaIdParam) return String(clinicaIdParam);
@@ -7626,7 +7634,11 @@ app.get('/api/comunicacao/:usuarioId', authGuard, async (req, res) => {
if (cr[0] && (cr[0].cidade || cr[0].logradouro)) { p.endereco = { ...cr[0], ...(p.endereco || {}) }; enderecoDaClinica = true; }
}
const role = await papelNaClinica(alvo, clinicaId);
res.json({ usuario_id: alvo, clinica_id: clinicaId, ...p, endereco_da_clinica: enderecoDaClinica, role });
const ehDentista = role === 'dentista';
const jornadaOk = ehDentista ? await jornadaConfigurada(alvo, clinicaId) : true;
// `completo` recomputado AO VIVO (a jornada pode ter sido configurada fora do modal).
const completoLive = comunicacaoCompleto({ endereco: p.endereco, numeros: p.numeros }, ehDentista, jornadaOk);
res.json({ usuario_id: alvo, clinica_id: clinicaId, ...p, completo: completoLive, endereco_da_clinica: enderecoDaClinica, role, jornada_configurada: jornadaOk });
} catch (e) { res.status(500).json({ error: e.message }); }
});
@@ -7641,7 +7653,8 @@ app.put('/api/comunicacao/:usuarioId', authGuard, async (req, res) => {
const numeros = Array.isArray(b.numeros) ? b.numeros : [];
const agenda = b.agenda && typeof b.agenda === 'object' ? b.agenda : {};
const ehDentista = (await papelNaClinica(alvo, clinicaId)) === 'dentista';
const completo = comunicacaoCompleto({ endereco, numeros, agenda }, ehDentista);
const jornadaOk = ehDentista ? await jornadaConfigurada(alvo, clinicaId) : true;
const completo = comunicacaoCompleto({ endereco, numeros }, ehDentista, jornadaOk);
await pool.query(
`INSERT INTO pessoa_comunicacao (usuario_id, clinica_id, endereco, numeros, agenda, completo, updated_at)
VALUES ($1,$2,$3,$4,$5,$6,NOW())