b21fb0f159
Clona o satélite do mercado adaptado ao scoreodonto (ESM, sem PluginManager): - backend/newwhats/: config (ENV), auth (x-nw-key), routes /nw/* (clinica, especialidades, dentistas, procedimentos, planos — multi-tenant por clinica_id), sync-knowledge (base de conhecimento odonto → motor), webhook-receiver (HMAC, enxuto), proxy REST /api/nw/v1/* → motor /api/ext/v1/*, manifest, INTEGRACAO-MOTOR.md. - server.js: registerNewwhats(app, pool) antes do listen. - Removidos (não úteis): media-transcribe (motor já transcreve), smart-router, personas, follow-up-detector, context-builder. - Config por ENV: NEWWHATS_URL/INTEGRATION_KEY/WEBHOOK_SECRET/CLINICA_ID. - ativo::int=1 normaliza boolean vs smallint entre tabelas. - Validado: buildKnowledgeText gera 1195 chars de dados reais; registro ok no express 5.2.1. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
99 lines
3.8 KiB
JavaScript
99 lines
3.8 KiB
JavaScript
// Rotas /nw/* — a Secretária IA do motor consulta os dados desta clínica.
|
|
// Adaptado do satélite mercado para o domínio ODONTO (scoreodonto).
|
|
// Todas as rotas exigem a integration_key (nwAuth).
|
|
import { Router } from 'express'
|
|
import { nwAuth } from './auth.js'
|
|
import { getConfig } from './config.js'
|
|
|
|
const money = (v) =>
|
|
v == null ? null : `R$ ${Number(v).toFixed(2).replace('.', ',')}`
|
|
|
|
// Resolve a clínica-alvo: query ?clinica_id= tem prioridade; senão a do config.
|
|
function resolveClinicaId(req) {
|
|
return String(req.query.clinica_id || getConfig().clinicaId || '').trim()
|
|
}
|
|
|
|
export function createRoutes(pool) {
|
|
const router = Router()
|
|
router.use(nwAuth)
|
|
|
|
const needClinica = (req, res) => {
|
|
const id = resolveClinicaId(req)
|
|
if (!id) res.status(400).json({ error: 'clinica_id ausente (configure NEWWHATS_CLINICA_ID ou passe ?clinica_id=).' })
|
|
return id
|
|
}
|
|
|
|
router.get('/ping', (_req, res) =>
|
|
res.json({ ok: true, service: 'scoreodonto', ts: new Date().toISOString() }))
|
|
|
|
// Dados da clínica
|
|
router.get('/clinica', async (req, res) => {
|
|
const id = needClinica(req, res); if (!id) return
|
|
try {
|
|
const { rows } = await pool.query(
|
|
`SELECT nome_fantasia, telefone, cidade, estado, logradouro, numero, bairro, cep
|
|
FROM clinicas WHERE id = $1`, [id])
|
|
res.json(rows[0] || null)
|
|
} catch (e) { res.status(500).json({ error: e.message }) }
|
|
})
|
|
|
|
// Especialidades atendidas
|
|
router.get('/especialidades', async (req, res) => {
|
|
const id = needClinica(req, res); if (!id) return
|
|
try {
|
|
const { rows } = await pool.query(
|
|
`SELECT nome, descricao, area FROM especialidades
|
|
WHERE clinica_id = $1 AND ativo::int = 1 ORDER BY ordem NULLS LAST, nome`, [id])
|
|
res.json(rows)
|
|
} catch (e) { res.status(500).json({ error: e.message }) }
|
|
})
|
|
|
|
// Dentistas + horários de atendimento
|
|
router.get('/dentistas', async (req, res) => {
|
|
const id = needClinica(req, res); if (!id) return
|
|
try {
|
|
const { rows: dentistas } = await pool.query(
|
|
`SELECT id, nome, especialidade FROM dentistas
|
|
WHERE clinica_id = $1 AND ativo::int = 1 ORDER BY ordem NULLS LAST, nome`, [id])
|
|
for (const d of dentistas) {
|
|
const { rows: hr } = await pool.query(
|
|
`SELECT dia_semana, hora_inicio, hora_fim FROM dentistas_horarios
|
|
WHERE dentista_id = $1 AND ativo::int = 1 ORDER BY dia_semana, hora_inicio`, [d.id])
|
|
d.horarios = hr
|
|
delete d.id
|
|
}
|
|
res.json(dentistas)
|
|
} catch (e) { res.status(500).json({ error: e.message }) }
|
|
})
|
|
|
|
// Procedimentos + valor particular + valores por plano
|
|
router.get('/procedimentos', async (req, res) => {
|
|
const id = needClinica(req, res); if (!id) return
|
|
try {
|
|
const { rows: procs } = await pool.query(
|
|
`SELECT id, nome, descricao, categoria, valorparticular FROM procedimentos
|
|
WHERE clinica_id = $1 AND ativo::int = 1 ORDER BY ordem NULLS LAST, nome`, [id])
|
|
for (const p of procs) {
|
|
const { rows: vp } = await pool.query(
|
|
`SELECT planonome, valor FROM procedimentos_valores_planos WHERE procedimentoid = $1`, [p.id])
|
|
p.valor_particular = money(p.valorparticular)
|
|
p.valores_planos = vp.map((x) => ({ plano: x.planonome, valor: money(x.valor) }))
|
|
delete p.id; delete p.valorparticular
|
|
}
|
|
res.json(procs)
|
|
} catch (e) { res.status(500).json({ error: e.message }) }
|
|
})
|
|
|
|
// Planos / convênios aceitos
|
|
router.get('/planos', async (req, res) => {
|
|
const id = needClinica(req, res); if (!id) return
|
|
try {
|
|
const { rows } = await pool.query(
|
|
`SELECT nome, tipo, descontopadrao FROM planos WHERE clinica_id = $1 ORDER BY nome`, [id])
|
|
res.json(rows)
|
|
} catch (e) { res.status(500).json({ error: e.message }) }
|
|
})
|
|
|
|
return router
|
|
}
|