Documentos Pacientes, Template Documentos Pacientes Saas, Documentos prontuários, Documentos Externos, Visualização Externa, Permissão de Visualização, Render Otimização

This commit is contained in:
Leonardo
2026-03-30 14:08:19 -03:00
parent 0658e2e9bf
commit d088a89fb7
112 changed files with 115867 additions and 5266 deletions
+199
View File
@@ -0,0 +1,199 @@
/**
* Validadores e formatadores centralizados — AgenciaPsi
*
* Nomenclatura alinhada ao schema do banco:
* nome_completo, cpf, cpf_responsavel, telefone, telefone_alternativo,
* telefone_parente, telefone_responsavel, email_principal, email_alternativo, cep
*
* Regra do banco: CPF é armazenado como 11 dígitos (sem máscara).
* Telefones são armazenados como dígitos apenas.
*/
// ─── Utilidade base ────────────────────────────────────────────────────────────
/** Remove tudo que não for dígito */
export function digitsOnly(v) {
return String(v ?? '').replace(/\D/g, '')
}
// ─── CPF ───────────────────────────────────────────────────────────────────────
/**
* Valida CPF (com ou sem máscara).
* Retorna false para sequências repetidas (111.111.111-11) e para dígitos inválidos.
*/
export function isValidCPF(v) {
const d = digitsOnly(v)
if (d.length !== 11) return false
if (/^(\d)\1+$/.test(d)) return false // sequências iguais
const calcDV = (base) => {
let sum = 0
for (let i = 0; i < base.length; i++) sum += Number(base[i]) * (base.length + 1 - i)
const mod = sum % 11
return mod < 2 ? 0 : 11 - mod
}
const dv1 = calcDV(d.slice(0, 9))
if (Number(d[9]) !== dv1) return false
const dv2 = calcDV(d.slice(0, 10))
if (Number(d[10]) !== dv2) return false
return true
}
/** Formata CPF para exibição: 000.000.000-00 */
export function fmtCPF(v) {
const d = digitsOnly(v).slice(0, 11)
if (!d) return ''
return d
.replace(/^(\d{3})(\d)/, '$1.$2')
.replace(/^(\d{3})\.(\d{3})(\d)/, '$1.$2.$3')
.replace(/\.(\d{3})(\d)/, '.$1-$2')
}
/** Gera um CPF válido (útil para testes/seed) */
export function generateCPF() {
const randInt = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min
const n = Array.from({ length: 9 }, () => randInt(0, 9))
const calcDV = (base) => {
let sum = 0
for (let i = 0; i < base.length; i++) sum += base[i] * (base.length + 1 - i)
const mod = sum % 11
return mod < 2 ? 0 : 11 - mod
}
const d1 = calcDV(n)
const d2 = calcDV([...n, d1])
const cpf = [...n, d1, d2].join('')
if (/^(\d)\1+$/.test(cpf)) return generateCPF()
return cpf
}
// ─── CNPJ ──────────────────────────────────────────────────────────────────────
/**
* Valida CNPJ (com ou sem máscara).
* Rejeita sequências repetidas (00.000.000/0000-00).
*/
export function isValidCNPJ(v) {
const d = digitsOnly(v)
if (d.length !== 14) return false
if (/^(\d)\1+$/.test(d)) return false
const calcDV = (base, weights) => {
let sum = 0
for (let i = 0; i < base.length; i++) sum += Number(base[i]) * weights[i]
const mod = sum % 11
return mod < 2 ? 0 : 11 - mod
}
const w1 = [5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2]
const w2 = [6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2]
if (Number(d[12]) !== calcDV(d.slice(0, 12), w1)) return false
if (Number(d[13]) !== calcDV(d.slice(0, 13), w2)) return false
return true
}
/** Formata CNPJ para exibição: 00.000.000/0000-00 */
export function fmtCNPJ(v) {
const d = digitsOnly(v).slice(0, 14)
if (!d) return ''
return d
.replace(/^(\d{2})(\d)/, '$1.$2')
.replace(/^(\d{2})\.(\d{3})(\d)/, '$1.$2.$3')
.replace(/\.(\d{3})(\d)/, '.$1/$2')
.replace(/(\d{4})(\d)/, '$1-$2')
}
// ─── RG ────────────────────────────────────────────────────────────────────────
/** Formata RG para exibição: 00.000.000-0 */
export function fmtRG(v) {
if (!v) return ''
const d = digitsOnly(v).slice(0, 9)
if (!d) return ''
return d
.replace(/^(\d{2})(\d)/, '$1.$2')
.replace(/^(\d{2})\.(\d{3})(\d)/, '$1.$2.$3')
.replace(/\.(\d{3})(\d)/, '.$1-$2')
}
// ─── Telefone ──────────────────────────────────────────────────────────────────
/**
* Valida telefone brasileiro (com ou sem máscara, com ou sem DDD).
* Aceita 10 dígitos (fixo) ou 11 dígitos (celular).
*/
export function isValidPhone(v) {
const d = digitsOnly(v)
return d.length === 10 || d.length === 11
}
/**
* Formata telefone para exibição.
* 11 dígitos → (XX) XXXXX-XXXX (celular)
* 10 dígitos → (XX) XXXX-XXXX (fixo)
*/
export function fmtPhone(v) {
const d = digitsOnly(v)
if (!d) return ''
if (d.length === 11) return d.replace(/^(\d{2})(\d{5})(\d{4})$/, '($1) $2-$3')
if (d.length === 10) return d.replace(/^(\d{2})(\d{4})(\d{4})$/, '($1) $2-$3')
return d
}
// ─── Email ─────────────────────────────────────────────────────────────────────
/** Valida email (formato básico) */
export function isValidEmail(v) {
const s = String(v ?? '').trim()
if (!s) return false
return /.+@.+\..+/.test(s)
}
// ─── CEP ───────────────────────────────────────────────────────────────────────
/** Valida CEP brasileiro: 8 dígitos */
export function isValidCEP(v) {
const d = digitsOnly(v)
return d.length === 8
}
/** Formata CEP para exibição: 00000-000 */
export function fmtCEP(v) {
const d = digitsOnly(v).slice(0, 8)
if (!d) return ''
return d.replace(/^(\d{5})(\d)/, '$1-$2')
}
// ─── Sanitização para o banco ──────────────────────────────────────────────────
/**
* Converte valor formatado para apenas dígitos antes de salvar no banco.
* Retorna null para valores vazios.
*/
export function sanitizeDigits(v) {
const d = digitsOnly(v)
return d || null
}
/**
* Converte data de DD/MM/YYYY ou DD-MM-YYYY para YYYY-MM-DD (formato ISO para o banco).
* Retorna null se inválido.
*/
export function toISODate(v) {
if (!v) return null
const s = String(v).trim()
const match = s.match(/^(\d{2})[/\-](\d{2})[/\-](\d{4})$/)
if (!match) return null
const [, dd, mm, yyyy] = match
const date = new Date(`${yyyy}-${mm}-${dd}`)
if (isNaN(date.getTime())) return null
return `${yyyy}-${mm}-${dd}`
}