diff --git a/src/layout/melissa/composables/useMelissaAgenda.js b/src/layout/melissa/composables/useMelissaAgenda.js index bbc7420..9204c15 100644 --- a/src/layout/melissa/composables/useMelissaAgenda.js +++ b/src/layout/melissa/composables/useMelissaAgenda.js @@ -1529,14 +1529,26 @@ function _buildHandlers(deps) { } // 2) Devolver saldo ao pacote (se decidiu) + // Refetch sessions_used FRESH antes de decrementar pra evitar + // race condition com flows que rodaram entre _loadStatusChangeContext + // e este ponto (ex: Realizada+gerar imediatamente seguido de Agendada). if (decision.reverseRestoreSaldo && r.saldoConsumed && ctx.billingContract?.id) { try { - const newUsed = Math.max(0, (ctx.billingContract.sessions_used ?? 0) - 1); + const { data: freshContract, error: fetchErr } = await supabase + .from('billing_contracts') + .select('sessions_used, total_sessions, status') + .eq('id', ctx.billingContract.id) + .maybeSingle(); + if (fetchErr) throw fetchErr; + const currentUsed = freshContract?.sessions_used ?? 0; + const totalSessions = freshContract?.total_sessions ?? 0; + const newUsed = Math.max(0, currentUsed - 1); const patch = { sessions_used: newUsed }; // Se contrato estava 'completed' (atingiu total) e voltou abaixo, reativa. - if ((ctx.billingContract.sessions_used ?? 0) >= (ctx.billingContract.total_sessions ?? 0)) { + if (currentUsed >= totalSessions) { patch.status = 'active'; } + console.log('[Fase5/reverse] decrementando saldo:', { from: currentUsed, to: newUsed, contractId: ctx.billingContract.id }); const { error: dErr } = await supabase.from('billing_contracts').update(patch).eq('id', ctx.billingContract.id); if (dErr) throw dErr; } catch (e) { @@ -1564,9 +1576,7 @@ function _buildHandlers(deps) { // 1) Consumir saldo (pacote saldo + faltou/cancelado + decisão sim) // ⚠ billing_contracts NÃO tem coluna updated_at — passar esse campo // causa "column does not exist" silenciosamente em Promise.allSettled. - // Também precisa amarrar billing_contract_id no evento — sem isso, o - // reverse não detecta saldoConsumed depois (bug cascata descoberto - // durante teste C11/B: Falta+Descontar, depois Agendada não devolvia). + // Amarração de billing_contract_id no evento é feita em 1b) universal. if (decision.consumeSaldo && ctx.billingContract?.id) { tasks.push( supabase @@ -1576,7 +1586,15 @@ function _buildHandlers(deps) { }) .eq('id', ctx.billingContract.id) ); - // Amarra evento ao contrato pra rastreabilidade + reverse correto + } + + // 1b) Amarra evento ao contrato — universal pra forward em pacote. + // Antes só rodava em consumeSaldo / generatePackageCharge. Faltou+multa + // SEM consume era exceção: evento ficava sem billing_contract_id, + // impedindo o reverse de detectar o vínculo depois. Fix: amarrar + // sempre que há contract envolvido + status forward + eventoId real. + const isForwardStatus = novoStatus === 'realizado' || novoStatus === 'faltou' || novoStatus === 'cancelado'; + if (isForwardStatus && ctx.billingContract?.id && eventoId) { tasks.push( supabase .from('agenda_eventos')