TOFY Contratos · gratuito e sem cadastro
Contrato de Locação Residencial
Pré-visualização
Modo de edição ativo: clique no texto para editar livremente. Os campos do formulário ficam congelados.
Precisa de ajuda?
Mensagem
${contractHTML} `; const blob = new Blob(['?', html], { type: 'application/msword;charset=utf-8' }); const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; const fileName = selectedTemplate?.arquivo || 'Contrato.doc'; link.download = fileName; document.body.appendChild(link); link.click(); document.body.removeChild(link); setTimeout(() => URL.revokeObjectURL(url), 100); showToast('Download iniciado!'); } function printDocument() { openAcceptModal(); } // ===== VALIDAÇÃO DE CAMPOS OBRIGATÓRIOS ===== function validateRequiredFields() { let required = [ { field: state.lessor.name, label: 'Nome do Locador' }, { field: state.tenants[0]?.name, label: 'Nome do Locatário' }, { field: state.property.address, label: 'Endereço do Imóvel' }, { field: state.dates.startDate, label: 'Data de Início' }, { field: state.financial.rentalValue, label: 'Valor do Aluguel' } ]; const template = getTemplate(state.contractType); if (!state.contractType) { required = [{ field: '', label: 'Contrato' }]; } else if (template?.requiredFields) { required = template.requiredFields.map(item => ({ field: getNestedValue(state, item.field), label: item.label })); } if (state.contractType === 'vehicle') { required = [ { field: state.seller.name, label: 'Nome do Vendedor' }, { field: state.buyer.name, label: 'Nome do Comprador' }, { field: state.vehicle.brand, label: 'Marca do Veículo' }, { field: state.vehicle.plate, label: 'Placa' }, { field: state.vehicle.value, label: 'Valor do Veículo' } ]; } if (state.contractType === 'service') { required = [ { field: state.provider.name, label: 'Nome do Prestador' }, { field: state.client.name, label: 'Nome do Tomador' }, { field: state.service.description, label: 'Descrição do Serviço' }, { field: state.service.deadline, label: 'Prazo' }, { field: state.service.value, label: 'Valor Total' } ]; } const isFilled = (v) => v !== undefined && v !== null && String(v).trim() !== ''; const filled = required.filter(r => isFilled(r.field)).length; const total = required.length; const percent = Math.round((filled / total) * 100); // Atualizar barra de progresso const fill = document.getElementById('progressFill'); const text = document.getElementById('progressText'); const btn = document.getElementById('unlockBtn'); if (fill && text && btn) { fill.style.width = percent + '%'; if (filled === total) { text.innerHTML = 'Contrato pronto. Revise antes de baixar.'; btn.innerHTML = ` Baixar contrato grátis `; btn.disabled = false; btn.classList.remove('disabled'); } else { const missing = required.filter(r => !isFilled(r.field)).map(r => r.label); const list = missing.length > 2 ? `${missing.slice(0,2).join(', ')} e +${missing.length-2}` : missing.join(', '); text.innerHTML = `${filled}/${total} · Faltam: ${list}`; btn.innerHTML = ` Complete os dados para baixar `; btn.disabled = true; btn.classList.add('disabled'); } } } // ===== MOBILE TOGGLE ===== function setupMobileToggle() { const toggle = document.getElementById('mobileToggle'); const sidebar = document.querySelector('.sidebar'); const preview = document.querySelector('.preview-area'); const toggleText = document.getElementById('toggleText'); let mobileMode = 'sidebar'; // 'sidebar' or 'preview' const apply = () => { if (window.innerWidth > 900) { sidebar.classList.remove('mobile-hidden'); preview.classList.remove('mobile-hidden'); return; } if (mobileMode === 'sidebar') { sidebar.classList.remove('mobile-hidden'); preview.classList.add('mobile-hidden'); toggleText.textContent = 'Ver contrato'; toggle.classList.remove('preview-mode'); } else { sidebar.classList.add('mobile-hidden'); preview.classList.remove('mobile-hidden'); toggleText.textContent = 'Preencher dados'; toggle.classList.add('preview-mode'); } }; toggle.addEventListener('click', () => { mobileMode = mobileMode === 'sidebar' ? 'preview' : 'sidebar'; apply(); }); window.addEventListener('resize', apply); apply(); } // ===== CHECKBOX DE ACEITE ===== function setupAcceptCheckbox() { const checkbox = document.getElementById('acceptCheckbox'); const btn = document.getElementById('acceptContinueBtn'); checkbox.addEventListener('change', () => { if (checkbox.checked) { btn.disabled = false; btn.classList.remove('disabled'); } else { btn.disabled = true; btn.classList.add('disabled'); } }); btn.addEventListener('click', () => { if (!checkbox.checked) return; openDownloadModal(); }); } // Click fora do modal fecha document.querySelectorAll('.modal-overlay').forEach(modal => { modal.addEventListener('click', e => { if (e.target === modal) { modal.classList.remove('show'); } }); }); // ESC fecha modal document.addEventListener('keydown', e => { if (e.key === 'Escape') { document.querySelectorAll('.modal-overlay').forEach(m => m.classList.remove('show')); } }); // ===== MÁSCARAS DE INPUT ===== function maskCPF(value) { const digits = String(value).replace(/\D/g, '').slice(0, 11); return digits .replace(/(\d{3})(\d)/, '$1.$2') .replace(/(\d{3})(\d)/, '$1.$2') .replace(/(\d{3})(\d{1,2})$/, '$1-$2'); } function maskCPFCNPJ(value) { const digits = String(value).replace(/\D/g, '').slice(0, 14); if (digits.length <= 11) return maskCPF(digits); return digits .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'); } function maskRG(value) { return String(value).replace(/[^0-9A-Za-z\.\-\s]/g, '').slice(0, 20); } function maskMoney(value) { const digits = String(value).replace(/\D/g, ''); if (!digits) return ''; const num = parseInt(digits, 10); return (num / 100).toFixed(2).replace('.', ',').replace(/\B(?=(\d{3})+(?!\d))/g, '.'); } function maskDay(value) { const digits = String(value).replace(/\D/g, '').slice(0, 2); if (!digits) return ''; const n = parseInt(digits, 10); if (n > 31) return '31'; return digits; } function maskNumber(value) { return String(value).replace(/\D/g, '').slice(0, 3); } function setupInputMasks() { // CPF do locador const cpfFields = ['lessorCPF', 'pixCPF', 'sellerCPF', 'buyerCPF', 'providerCPF', 'clientCPF', 'creditorCPF', 'debtorCPF']; cpfFields.forEach(id => { const el = document.getElementById(id); if (!el) return; el.addEventListener('input', (e) => { const cursorPos = e.target.selectionStart; const oldLen = e.target.value.length; e.target.value = ['providerCPF', 'clientCPF', 'creditorCPF', 'debtorCPF'].includes(id) ? maskCPFCNPJ(e.target.value) : maskCPF(e.target.value); const newLen = e.target.value.length; e.target.setSelectionRange(cursorPos + (newLen - oldLen), cursorPos + (newLen - oldLen)); }); el.setAttribute('inputmode', 'numeric'); el.setAttribute('maxlength', ['providerCPF', 'clientCPF', 'creditorCPF', 'debtorCPF'].includes(id) ? '18' : '14'); }); // Valores monetários ['rentalValue', 'cauationValue', 'vehicleValue', 'serviceValue', 'financeValue'].forEach(id => { const el = document.getElementById(id); if (!el) return; el.addEventListener('input', (e) => { e.target.value = maskMoney(e.target.value); }); el.setAttribute('inputmode', 'numeric'); }); // Dia do vencimento const dueDay = document.getElementById('dueDay'); if (dueDay) { dueDay.addEventListener('input', (e) => { e.target.value = maskDay(e.target.value); }); dueDay.setAttribute('inputmode', 'numeric'); } // Multa em % const latePenalty = document.getElementById('latePenalty'); if (latePenalty) { latePenalty.addEventListener('input', (e) => { e.target.value = maskNumber(e.target.value); }); latePenalty.setAttribute('inputmode', 'numeric'); } // Setup CPF dos locatários (delegação) document.getElementById('tenantsList').addEventListener('input', (e) => { if (e.target.dataset && e.target.dataset.tenant === 'cpf') { e.target.value = maskCPF(e.target.value); } }); } // ===== EDIÇÃO DIRETA NO CONTRATO ===== let isEditMode = false; function setupEditableContract() { const btn = document.getElementById('toggleEditBtn'); const preview = document.getElementById('contractPreview'); const hint = document.getElementById('editableHint'); btn.addEventListener('click', () => { isEditMode = !isEditMode; if (isEditMode) { preview.setAttribute('contenteditable', 'true'); preview.focus(); btn.classList.add('active'); btn.textContent = 'Salvar edições'; hint.classList.add('show'); showToast('Modo de edição ativo. Clique no texto para editar.'); } else { preview.setAttribute('contenteditable', 'false'); btn.classList.remove('active'); btn.textContent = 'Editar texto'; hint.classList.remove('show'); showToast('Edições salvas no contrato!'); } }); // Avisar antes de regenerar contrato se estiver no modo de edição preview.addEventListener('input', () => { if (isEditMode) { state._manuallyEdited = true; scheduleAutosave(); } }); } // Modificar generateContract para não sobrescrever edições manuais const originalGenerateContract = generateContract; generateContract = function() { if (isEditMode || state._manuallyEdited) { // Apenas valida sem regenerar validateRequiredFields(); return; } originalGenerateContract(); }; // ===== INICIALIZAÇÃO ===== document.addEventListener('DOMContentLoaded', async () => { await loadContractModels(); const restoredDraft = restoreDraft(); applySlugDefaultSelection(); renderModelPicker(); bindInputs(); if (!Array.isArray(state.tenants) || state.tenants.length === 0) { state.tenants = []; addTenant(); // primeiro locatário } syncFormFromState(); updateContractFormVisibility(); generateContract(); setupMobileToggle(); setupAcceptCheckbox(); setupInputMasks(); setupEditableContract(); setupPreviewCopyProtection(); validateRequiredFields(); if (restoredDraft) { showToast('Rascunho restaurado neste computador'); } }); window.addEventListener('beforeunload', saveDraftNow);