diff --git a/database-novo/migrations/20260521000009_profiles_registration_type_other.sql b/database-novo/migrations/20260521000009_profiles_registration_type_other.sql
new file mode 100644
index 0000000..86073c7
--- /dev/null
+++ b/database-novo/migrations/20260521000009_profiles_registration_type_other.sql
@@ -0,0 +1,21 @@
+-- ============================================================================
+-- Compliance CFP #5 — campo livre quando tipo de registro = 'outro'
+-- ----------------------------------------------------------------------------
+-- Migration 20260521000003 adicionou professional_registration_type com CHECK
+-- limitado a 8 valores (CRP/CRM/CRFa/CREFITO/CRESS/CRN/RMS/outro). Quando o
+-- profissional escolhe 'outro', precisa informar qual conselho/instituição
+-- (ex: associações privadas, conselhos não-listados).
+--
+-- Esta migration adiciona professional_registration_type_other (text livre),
+-- que só é preenchido quando type = 'outro'.
+-- ============================================================================
+
+BEGIN;
+
+ALTER TABLE public.profiles
+ ADD COLUMN IF NOT EXISTS professional_registration_type_other text;
+
+COMMENT ON COLUMN public.profiles.professional_registration_type_other IS
+ 'Nome livre do conselho/instituição quando professional_registration_type = ''outro''. Aparece em recibos/laudos no lugar do tipo padrão.';
+
+COMMIT;
diff --git a/src/layout/melissa/MelissaPerfil.vue b/src/layout/melissa/MelissaPerfil.vue
index 75db3db..a0e62a6 100644
--- a/src/layout/melissa/MelissaPerfil.vue
+++ b/src/layout/melissa/MelissaPerfil.vue
@@ -160,6 +160,7 @@ const form = reactive({
social_x: '',
// Registro profissional (CFP #5 — exigido pra emissão de recibos/laudos)
professional_registration_type: '',
+ professional_registration_type_other: '',
professional_registration_number: '',
professional_registration_uf: ''
});
@@ -372,7 +373,7 @@ async function loadProfile() {
const { data: prof, error: pErr } = await supabase
.from('profiles')
.select(
- 'role, full_name, avatar_url, phone, bio, nickname, work_description, work_description_other, site_url, social_instagram, social_youtube, social_facebook, social_x, social_custom, professional_registration_type, professional_registration_number, professional_registration_uf'
+ 'role, full_name, avatar_url, phone, bio, nickname, work_description, work_description_other, site_url, social_instagram, social_youtube, social_facebook, social_x, social_custom, professional_registration_type, professional_registration_type_other, professional_registration_number, professional_registration_uf'
)
.eq('id', user.id)
.maybeSingle();
@@ -392,6 +393,7 @@ async function loadProfile() {
form.social_facebook = prof.social_facebook ?? '';
form.social_x = prof.social_x ?? '';
form.professional_registration_type = prof.professional_registration_type ?? '';
+ form.professional_registration_type_other = prof.professional_registration_type_other ?? '';
form.professional_registration_number = prof.professional_registration_number ?? '';
form.professional_registration_uf = prof.professional_registration_uf ?? '';
customSocials.value = Array.isArray(prof.social_custom) ? prof.social_custom : [];
@@ -463,6 +465,11 @@ async function saveAll() {
social_custom: customSocials.value.filter((s) => s.name || s.url),
// Registro profissional (CFP) — null se vazio
professional_registration_type: String(form.professional_registration_type || '').trim() || null,
+ // type_other só preenchido quando type === 'outro' (limpa quando muda)
+ professional_registration_type_other:
+ form.professional_registration_type === 'outro'
+ ? (String(form.professional_registration_type_other || '').trim() || null)
+ : null,
professional_registration_number: String(form.professional_registration_number || '').trim() || null,
professional_registration_uf: String(form.professional_registration_uf || '').trim() || null
};
@@ -894,6 +901,20 @@ onBeforeUnmount(() => {
Conselho profissional ao qual você é vinculado.
+
+
+
+
+
+
+ Ex: APM, ABRAP, etc.
+
+
{
Aparecerá nos documentos como:
- {{ form.professional_registration_type }}
+ {{ form.professional_registration_type === 'outro'
+ ? (form.professional_registration_type_other || 'Conselho não informado')
+ : form.professional_registration_type }}
{{ form.professional_registration_number }}{{ form.professional_registration_uf ? '/' + form.professional_registration_uf : '' }}
diff --git a/src/services/DocumentGenerate.service.js b/src/services/DocumentGenerate.service.js
index 89128c5..142c2d0 100644
--- a/src/services/DocumentGenerate.service.js
+++ b/src/services/DocumentGenerate.service.js
@@ -148,7 +148,7 @@ export async function loadTherapistData() {
const { data: profile } = await supabase
.from('profiles')
- .select('full_name, phone, professional_registration_type, professional_registration_number, professional_registration_uf')
+ .select('full_name, phone, professional_registration_type, professional_registration_type_other, professional_registration_number, professional_registration_uf')
.eq('id', ownerId)
.single();
@@ -156,7 +156,10 @@ export async function loadTherapistData() {
const { data: userData } = await supabase.auth.getUser();
const email = userData?.user?.email || '';
- const tipo = profile?.professional_registration_type || '';
+ const tipoRaw = profile?.professional_registration_type || '';
+ const tipoOther = profile?.professional_registration_type_other || '';
+ // Quando type='outro', usa o nome livre do conselho/instituição
+ const tipo = tipoRaw === 'outro' ? tipoOther : tipoRaw;
const numero = profile?.professional_registration_number || '';
const uf = profile?.professional_registration_uf || '';
const registro = formatRegistroProfissional({ tipo, numero, uf });
@@ -173,7 +176,7 @@ export async function loadTherapistData() {
// o número/UF (sem prefixo) pra não duplicar com o "CRP" já no HTML.
// Quando o registro não é CRP, retorna vazio (template visualmente errado
// pede pra usar {{terapeuta_registro}}).
- terapeuta_crp: tipo === 'CRP' ? (uf ? `${numero}/${uf}` : numero) : ''
+ terapeuta_crp: tipoRaw === 'CRP' ? (uf ? `${numero}/${uf}` : numero) : ''
};
}
diff --git a/src/views/pages/account/ProfilePage.vue b/src/views/pages/account/ProfilePage.vue
index e23953f..c28734b 100644
--- a/src/views/pages/account/ProfilePage.vue
+++ b/src/views/pages/account/ProfilePage.vue
@@ -105,6 +105,7 @@ const form = reactive({
// Registro profissional (CFP #5 — exigido pra emissão de recibos/laudos)
professional_registration_type: '',
+ professional_registration_type_other: '',
professional_registration_number: '',
professional_registration_uf: '',
@@ -634,7 +635,7 @@ async function loadProfile() {
const { data: prof, error: pErr } = await supabase
.from('profiles')
.select(
- 'role, full_name, avatar_url, phone, bio, nickname, work_description, work_description_other, site_url, social_instagram, social_youtube, social_facebook, social_x, social_custom, language, timezone, notify_system_email, notify_reminders, notify_news, professional_registration_type, professional_registration_number, professional_registration_uf'
+ 'role, full_name, avatar_url, phone, bio, nickname, work_description, work_description_other, site_url, social_instagram, social_youtube, social_facebook, social_x, social_custom, language, timezone, notify_system_email, notify_reminders, notify_news, professional_registration_type, professional_registration_type_other, professional_registration_number, professional_registration_uf'
)
.eq('id', user.id)
.maybeSingle();
@@ -655,6 +656,7 @@ async function loadProfile() {
form.social_x = prof.social_x ?? '';
form.professional_registration_type = prof.professional_registration_type ?? '';
+ form.professional_registration_type_other = prof.professional_registration_type_other ?? '';
form.professional_registration_number = prof.professional_registration_number ?? '';
form.professional_registration_uf = prof.professional_registration_uf ?? '';
@@ -738,6 +740,11 @@ async function saveAll() {
// Registro profissional (CFP) — null se vazio
professional_registration_type: String(form.professional_registration_type || '').trim() || null,
+ // type_other só preenchido quando type === 'outro' (limpa quando muda)
+ professional_registration_type_other:
+ form.professional_registration_type === 'outro'
+ ? (String(form.professional_registration_type_other || '').trim() || null)
+ : null,
professional_registration_number: String(form.professional_registration_number || '').trim() || null,
professional_registration_uf: String(form.professional_registration_uf || '').trim() || null
};
@@ -747,7 +754,7 @@ async function saveAll() {
.update(profilePayload)
.eq('id', userId.value)
.select(
- 'id, role, full_name, avatar_url, phone, bio, nickname, work_description, work_description_other, site_url, social_instagram, social_youtube, social_facebook, social_x, social_custom, language, timezone, notify_system_email, notify_reminders, notify_news, professional_registration_type, professional_registration_number, professional_registration_uf, updated_at'
+ 'id, role, full_name, avatar_url, phone, bio, nickname, work_description, work_description_other, site_url, social_instagram, social_youtube, social_facebook, social_x, social_custom, language, timezone, notify_system_email, notify_reminders, notify_news, professional_registration_type, professional_registration_type_other, professional_registration_number, professional_registration_uf, updated_at'
)
.single();
@@ -1173,6 +1180,20 @@ onBeforeUnmount(() => {
Conselho profissional ao qual você é vinculado.
+
+
+
+
+
+
+
Ex: APM (Associação Paulista de Medicina), ABRAP, etc.
+
+
@@ -1212,7 +1233,9 @@ onBeforeUnmount(() => {
Aparecerá nos documentos como:
- {{ form.professional_registration_type }}
+ {{ form.professional_registration_type === 'outro'
+ ? (form.professional_registration_type_other || 'Conselho não informado')
+ : form.professional_registration_type }}
{{ form.professional_registration_number }}{{ form.professional_registration_uf ? '/' + form.professional_registration_uf : '' }}