diff --git a/src/layout/melissa/MelissaEventoPanel.vue b/src/layout/melissa/MelissaEventoPanel.vue
index 0d50c58..4013aca 100644
--- a/src/layout/melissa/MelissaEventoPanel.vue
+++ b/src/layout/melissa/MelissaEventoPanel.vue
@@ -30,6 +30,7 @@ const emit = defineEmits([
'faltou',
'cancelar',
'remarcar',
+ 'revogar-antecipacao',
'edit-sessao', // botão dedicado ao lado das horas → AgendaEventDialog
'edit-paciente', // botão "Editar" do grupo Outras opções → PatientCadastroDialog
'abrir-prontuario',
@@ -142,6 +143,13 @@ const isSessaoComPaciente = computed(
// caso tenha sido marcado por engano.
const isSessaoEncerrada = computed(() => statusSlug.value === 'cancelado' || statusSlug.value === 'faltou');
+// "Antecipação ativa": sessão ainda agendada (não rolou) com cobrança paga.
+// O paid não veio de Realizada — veio de "Antecipar pagamento" que adianta
+// o pagamento. Nesse estado, o botão "Antecipar pagamento" vira "Revogar
+// pagamento" pra desfazer caso o user tenha errado. Após Realizada, o paid
+// vira pagamento normal da sessão (estorno via /financeiro).
+const isAntecipacaoAtiva = computed(() => statusSlug.value === 'agendado' && ev.value.paymentState === 'paid');
+
// Estado de pagamento — vem anotado pelo useMelissaAgenda via bulk-query
// em financial_records. 'paid' | 'pending' | 'none'. Renderiza linha
// curta abaixo do horário pra sessão com paciente (espelha os 3 canais
@@ -490,6 +498,7 @@ function modalidadeIcon(mod) {
Lançamentos
Antecipar pagamento
+
diff --git a/src/layout/melissa/MelissaLayout.vue b/src/layout/melissa/MelissaLayout.vue
index 739c38c..029d610 100644
--- a/src/layout/melissa/MelissaLayout.vue
+++ b/src/layout/melissa/MelissaLayout.vue
@@ -879,6 +879,80 @@ async function confirmAnteciparPagamento() {
}
}
+// Revogar antecipação de pagamento (C12): desfaz o `onAnteciparPagamento`.
+// Cancela o record paid + nota de auditoria em notes. Só disponível pra
+// sessão em status='agendado' (após Realizada o paid vira pagamento normal
+// e estorno é via /financeiro).
+async function onRevogarAntecipacao() {
+ const ev = eventoSelecionado.value;
+ if (!ev?.id || eventoBusy.value) return;
+ const isVirtualId = typeof ev.id === 'string' && ev.id.startsWith('rec::');
+ if (isVirtualId) {
+ toast.add({ severity: 'warn', summary: 'Sessão virtual', detail: 'Sessão sem antecipação ativa.', life: 3000 });
+ return;
+ }
+ // Confirma com user — paid é sensível
+ const ok = await new Promise((resolve) => {
+ confirm.require({
+ message: 'Revogar o pagamento antecipado desta sessão? O lançamento financeiro será cancelado e poderá antecipar de novo.',
+ header: 'Revogar antecipação?',
+ icon: 'pi pi-exclamation-triangle',
+ acceptLabel: 'Revogar pagamento',
+ rejectLabel: 'Cancelar',
+ acceptClass: 'p-button-danger',
+ accept: () => resolve(true),
+ reject: () => resolve(false),
+ onHide: () => resolve(false)
+ });
+ });
+ if (!ok) return;
+
+ eventoBusy.value = true;
+ try {
+ // Acha o paid record vinculado
+ const { data: paidRec, error: fetchErr } = await supabase
+ .from('financial_records')
+ .select('id, notes, payment_method, final_amount, amount')
+ .eq('agenda_evento_id', ev.id)
+ .eq('status', 'paid')
+ .order('paid_at', { ascending: false })
+ .limit(1)
+ .maybeSingle();
+ if (fetchErr) throw fetchErr;
+ if (!paidRec?.id) {
+ toast.add({ severity: 'info', summary: 'Nada a revogar', detail: 'Esta sessão não tem pagamento antecipado.', life: 3500 });
+ return;
+ }
+ const today = new Date().toISOString().slice(0, 10);
+ const reason = `Antecipação revogada em ${today}`;
+ const noteEntry = `[${today}] ${reason}`;
+ const noteText = paidRec.notes ? `${paidRec.notes}\n${noteEntry}` : noteEntry;
+ const { error: cancelErr } = await supabase
+ .from('financial_records')
+ .update({
+ status: 'cancelled',
+ notes: noteText,
+ updated_at: new Date().toISOString()
+ })
+ .eq('id', paidRec.id);
+ if (cancelErr) throw cancelErr;
+
+ toast.add({
+ severity: 'success',
+ summary: 'Antecipação revogada',
+ detail: `Cobrança de R$ ${Number(paidRec.final_amount || paidRec.amount || 0).toFixed(2).replace('.', ',')} cancelada.`,
+ life: 4000
+ });
+ M.refetch();
+ refetchEventosHoje();
+ fecharEvento();
+ } catch (e) {
+ toast.add({ severity: 'error', summary: 'Erro', detail: e?.message || 'Falha ao revogar antecipação.', life: 5000 });
+ } finally {
+ eventoBusy.value = false;
+ }
+}
+
async function onVerLancamentos() {
const ev = eventoSelecionado.value;
if (!ev?.id) return;
@@ -2504,6 +2578,7 @@ function onKeydown(e) {
@revogar-sessao="onRevogarSessao"
@ver-lancamentos="onVerLancamentos"
@antecipar-pagamento="onAnteciparPagamento"
+ @revogar-antecipacao="onRevogarAntecipacao"
@edit-paciente="onEditPaciente"
@abrir-prontuario="onAbrirProntuario"
@whatsapp="onWhatsapp"