'use strict' const { Router } = require('express') const { query, queryOne } = require('../../database/postgres') const { nwAuth } = require('./auth') function cleanPhone(tel) { return String(tel || '').replace(/\D/g, '') } function cleanCpf(cpf) { return String(cpf || '').replace(/\D/g, '') } function createRoutes() { const router = Router() router.use(nwAuth) // ── GET /nw/ping ────────────────────────────────────────────────────────── router.get('/ping', (_req, res) => { res.json({ ok: true, system: 'alemao-conveniencias', ts: new Date().toISOString() }) }) // ── GET /nw/cliente?telefone= ───────────────────────────────────────────── // Retorna dados do cliente + tickets da rifa ativa + clube ativo + candidaturas router.get('/cliente', async (req, res) => { const { telefone } = req.query if (!telefone) return res.status(400).json({ error: '"telefone" é obrigatório' }) const clean = cleanPhone(telefone) try { const user = await queryOne( `SELECT id, nome, telefone, cpf, email, timestamp AS created_at FROM users WHERE REGEXP_REPLACE(telefone, '\\D', '', 'g') = $1`, [clean] ) if (!user) return res.json({ found: false }) const cpf = cleanCpf(user.cpf) // Tickets da rifa ativa const tickets = await query( `SELECT t.numero, r.titulo AS sorteio, t.data_compra FROM tickets t JOIN raffles r ON r.id = t.raffle_id WHERE REGEXP_REPLACE(t.usuario_telefone, '\\D', '', 'g') = $1 AND r.ativo = 1 ORDER BY t.numero ASC`, [clean] ) // Clube(s) ativo(s) const clubes = cpf ? await query( `SELECT cm.id, cm.status, cm.next_billing_at, cp.name AS plano, cp.price_monthly, bc.name AS clube, bc.slug AS clube_slug, bc.specialty FROM club_members cm JOIN club_plans cp ON cp.id = cm.plan_id JOIN benefit_clubs bc ON bc.id = cm.club_id WHERE cm.user_cpf = $1 AND cm.status IN ('active','pending_payment') ORDER BY cm.created_at DESC`, [cpf] ) : [] // Candidaturas ativas (últimas 3) const candidaturas = cpf ? await query( `SELECT ja.status, ja.applied_at, j.title AS vaga, j.work_mode, j.job_type, j.location FROM job_applications ja JOIN jobs j ON j.id = ja.job_id WHERE ja.applicant_cpf = $1 ORDER BY ja.applied_at DESC LIMIT 3`, [cpf] ) : [] res.json({ found: true, cliente: { ...user, tickets, clubes, candidaturas, }, }) } catch (err) { console.error('[nw/cliente]', err.message) res.status(500).json({ error: 'Erro interno' }) } }) // ── GET /nw/clube?cpf= ──────────────────────────────────────────────────── // Retorna plano ativo do cliente, clube, dependentes e parceiros próximos router.get('/clube', async (req, res) => { const { cpf } = req.query if (!cpf) return res.status(400).json({ error: '"cpf" é obrigatório' }) const cleanedCpf = cleanCpf(cpf) try { const memberships = await query( `SELECT cm.id, cm.status, cm.next_billing_at, cm.started_at, cp.name AS plano, cp.price_monthly, cp.max_dependents, cp.features, bc.id AS clube_id, bc.name AS clube, bc.slug AS clube_slug, bc.specialty, bc.conditions, bc.rules FROM club_members cm JOIN club_plans cp ON cp.id = cm.plan_id JOIN benefit_clubs bc ON bc.id = cm.club_id WHERE cm.user_cpf = $1 ORDER BY cm.status = 'active' DESC, cm.created_at DESC`, [cleanedCpf] ) if (!memberships.length) return res.json({ encontrado: false, memberships: [] }) // Para cada associação ativa, busca dependentes e parceiros const result = await Promise.all(memberships.map(async (m) => { const [dependentes, parceiros] = await Promise.all([ query( `SELECT nome, parentesco, cpf, nascimento FROM club_dependents WHERE member_id = $1 ORDER BY nome ASC`, [m.id] ), query( `SELECT name, specialty, phone, address, discount_percent, notes FROM club_partners WHERE club_id = $1 AND active = true ORDER BY name ASC LIMIT 10`, [m.clube_id] ), ]) return { ...m, dependentes, parceiros } })) res.json({ encontrado: true, memberships: result }) } catch (err) { console.error('[nw/clube]', err.message) res.status(500).json({ error: 'Erro interno' }) } }) // ── GET /nw/candidaturas?cpf= ───────────────────────────────────────────── // Retorna todas as candidaturas do candidato com status detalhado router.get('/candidaturas', async (req, res) => { const { cpf } = req.query if (!cpf) return res.status(400).json({ error: '"cpf" é obrigatório' }) const cleanedCpf = cleanCpf(cpf) try { const candidaturas = await query( `SELECT ja.id, ja.status, ja.applied_at, ja.cover_letter, j.title AS vaga, j.work_mode, j.job_type, j.location, j.category FROM job_applications ja JOIN jobs j ON j.id = ja.job_id WHERE ja.applicant_cpf = $1 ORDER BY ja.applied_at DESC`, [cleanedCpf] ) const statusLabels = { novo: 'Candidatura recebida', em_analise: 'Em análise pela empresa', aprovado: 'Aprovado! Aguarde contato', reprovado: 'Não selecionado desta vez', contratado: 'Contratado — parabéns!', } const enriched = candidaturas.map(c => ({ ...c, status_label: statusLabels[c.status] || c.status, })) res.json({ total: enriched.length, candidaturas: enriched }) } catch (err) { console.error('[nw/candidaturas]', err.message) res.status(500).json({ error: 'Erro interno' }) } }) // ── GET /nw/parceiros?clube=slug ────────────────────────────────────────── // Lista parceiros de um clube (para indicar onde o cliente pode usar o benefício) router.get('/parceiros', async (req, res) => { const { clube } = req.query if (!clube) return res.status(400).json({ error: '"clube" (slug) é obrigatório' }) try { const clubeData = await queryOne( `SELECT id, name, specialty FROM benefit_clubs WHERE slug = $1 AND active = true`, [clube] ) if (!clubeData) return res.json({ encontrado: false, parceiros: [] }) const parceiros = await query( `SELECT name, specialty, phone, address, discount_percent, notes FROM club_partners WHERE club_id = $1 AND active = true ORDER BY name ASC`, [clubeData.id] ) res.json({ encontrado: true, clube: { name: clubeData.name, specialty: clubeData.specialty }, parceiros, }) } catch (err) { console.error('[nw/parceiros]', err.message) res.status(500).json({ error: 'Erro interno' }) } }) // ── GET /nw/vagas ───────────────────────────────────────────────────────── // Lista vagas abertas (para responder perguntas sobre oportunidades de emprego) router.get('/vagas', async (_req, res) => { try { const vagas = await query( `SELECT title, category, work_mode, job_type, location, salary_min, salary_max, salary_hide, slots, published_at, expires_at FROM jobs WHERE active = true AND (expires_at IS NULL OR expires_at > NOW()) ORDER BY published_at DESC LIMIT 20` ) const workModeLabels = { presencial: 'Presencial', hibrido: 'Híbrido', remoto: 'Remoto' } const jobTypeLabels = { clt: 'CLT', pj: 'PJ', freelance: 'Freelance', estagio: 'Estágio', temporario: 'Temporário' } const enriched = vagas.map(v => ({ ...v, work_mode_label: workModeLabels[v.work_mode] || v.work_mode, job_type_label: jobTypeLabels[v.job_type] || v.job_type, salario: v.salary_hide || (!v.salary_min && !v.salary_max) ? 'A combinar' : v.salary_min && v.salary_max ? `R$ ${Number(v.salary_min).toLocaleString('pt-BR', { maximumFractionDigits: 0 })} – R$ ${Number(v.salary_max).toLocaleString('pt-BR', { maximumFractionDigits: 0 })}` : v.salary_min ? `A partir de R$ ${Number(v.salary_min).toLocaleString('pt-BR', { maximumFractionDigits: 0 })}` : `Até R$ ${Number(v.salary_max).toLocaleString('pt-BR', { maximumFractionDigits: 0 })}`, })) res.json({ total: enriched.length, vagas: enriched }) } catch (err) { console.error('[nw/vagas]', err.message) res.status(500).json({ error: 'Erro interno' }) } }) // ── GET /nw/promocoes ───────────────────────────────────────────────────── router.get('/promocoes', async (_req, res) => { try { const promos = await query( `SELECT id, nome, descricao, preco_original, preco_promocional FROM promotions WHERE ativo = 1 ORDER BY id ASC` ) res.json(promos) } catch (err) { console.error('[nw/promocoes]', err.message) res.status(500).json({ error: 'Erro interno' }) } }) // ── GET /nw/sorteio ─────────────────────────────────────────────────────── router.get('/sorteio', async (_req, res) => { try { const sorteio = await queryOne( "SELECT * FROM raffles WHERE ativo = 1 ORDER BY id DESC LIMIT 1" ) if (!sorteio) return res.json({ ativo: false }) const countRow = await queryOne( `SELECT COUNT(*) AS total FROM tickets WHERE raffle_id = $1 AND (status = 'sold' OR (status = 'reserved' AND reserved_until > NOW()))`, [sorteio.id] ) const total_vendidos = parseInt(countRow?.total ?? 0, 10) res.json({ ativo: true, sorteio: { id: sorteio.id, titulo: sorteio.titulo, descricao: sorteio.descricao, preco_numero: sorteio.preco_numero, total_numeros: sorteio.total_numeros, numeros_vendidos: total_vendidos, numeros_disponiveis: sorteio.total_numeros - total_vendidos, }, }) } catch (err) { console.error('[nw/sorteio]', err.message) res.status(500).json({ error: 'Erro interno' }) } }) // ── GET /nw/sorteio/tickets?telefone= ──────────────────────────────────── router.get('/sorteio/tickets', async (req, res) => { const { telefone } = req.query if (!telefone) return res.status(400).json({ error: '"telefone" é obrigatório' }) const clean = cleanPhone(telefone) try { const sorteio = await queryOne( "SELECT id, titulo FROM raffles WHERE ativo = 1 ORDER BY id DESC LIMIT 1" ) if (!sorteio) return res.json({ ativo: false, tickets: [] }) const tickets = await query( `SELECT t.numero, t.data_compra FROM tickets t WHERE t.raffle_id = $1 AND REGEXP_REPLACE(t.usuario_telefone, '\\D', '', 'g') = $2 AND (t.status = 'sold' OR (t.status = 'reserved' AND t.reserved_until > NOW())) ORDER BY t.numero ASC`, [sorteio.id, clean] ) res.json({ sorteio: sorteio.titulo, telefone, total: tickets.length, tickets, }) } catch (err) { console.error('[nw/sorteio/tickets]', err.message) res.status(500).json({ error: 'Erro interno' }) } }) return router } module.exports = { createRoutes }