Use cases

Contratos

Gere contratos profissionais com assinaturas eletrônicas e versionamento

Este guia mostra como gerar contratos legais profissionais usando RenderHub.

Visão Geral

Contratos são documentos legais que requerem atenção especial:

  • ✅ Contratos de serviço
  • ✅ Contratos de trabalho
  • ✅ Termos de uso e políticas
  • ✅ NDA (Non-Disclosure Agreement)
  • ✅ Contratos de locação

Estrutura de Dados

interface Contract {
  // Metadados
  id: string;
  number: string;
  type: 'service' | 'employment' | 'nda' | 'lease' | 'other';
  status: 'draft' | 'pending_signature' | 'signed' | 'expired' | 'terminated';

  // Partes
  parties: {
    contractor: {
      type: 'individual' | 'company';
      name: string;
      documentId: string; // CPF/CNPJ
      address: string;
      city: string;
      state: string;
      zipCode: string;
      representative?: string; // Para empresas
    };
    contractee: {
      type: 'individual' | 'company';
      name: string;
      documentId: string;
      address: string;
      city: string;
      state: string;
      zipCode: string;
      representative?: string;
    };
  };

  // Datas
  startDate: Date;
  endDate?: Date;
  signedAt?: Date;

  // Termos
  terms: {
    scope: string; // Escopo do trabalho/serviço
    deliverables?: string[]; // Entregas
    payment: {
      amount: number;
      currency: string;
      schedule: 'monthly' | 'quarterly' | 'one-time' | 'milestone';
      dueDay?: number;
    };
    termination?: string; // Condições de rescisão
    confidentiality?: string; // Cláusula de confidencialidade
    custom?: Array<{
      title: string;
      content: string;
    }>;
  };

  // Assinaturas
  signatures: {
    contractor?: {
      signedAt: Date;
      signature: string; // Base64 ou URL da assinatura
      ip: string;
    };
    contractee?: {
      signedAt: Date;
      signature: string;
      ip: string;
    };
  };

  // Controle de versão
  version: number;
  previousVersionId?: string;
}

Template HTML

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <style>
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }

    @page {
      margin: 2.5cm;
    }

    body {
      font-family: 'Times New Roman', serif;
      font-size: 12pt;
      line-height: 1.6;
      color: #000;
    }

    .header {
      text-align: center;
      margin-bottom: 40px;
      border-bottom: 2px solid #000;
      padding-bottom: 20px;
    }

    .contract-title {
      font-size: 16pt;
      font-weight: bold;
      text-transform: uppercase;
      margin-bottom: 10px;
    }

    .contract-number {
      font-size: 10pt;
      color: #666;
    }

    .parties {
      margin-bottom: 30px;
    }

    .party {
      margin-bottom: 20px;
    }

    .party-label {
      font-weight: bold;
      text-transform: uppercase;
      margin-bottom: 5px;
    }

    .party-info {
      margin-left: 20px;
      line-height: 1.4;
    }

    .clause {
      margin-bottom: 25px;
    }

    .clause-number {
      font-weight: bold;
      margin-bottom: 5px;
    }

    .clause-title {
      font-weight: bold;
      margin-bottom: 8px;
    }

    .clause-content {
      text-align: justify;
      margin-left: 20px;
    }

    .subcl ause {
      margin: 10px 0 10px 40px;
    }

    .signatures {
      margin-top: 60px;
      page-break-inside: avoid;
    }

    .signature-block {
      margin-top: 40px;
    }

    .signature-line {
      border-top: 1px solid #000;
      width: 300px;
      margin: 60px 0 5px 0;
    }

    .signature-label {
      font-size: 10pt;
    }

    .signature-image {
      max-width: 200px;
      max-height: 80px;
      margin-bottom: 10px;
    }

    .signature-info {
      font-size: 9pt;
      color: #666;
      margin-top: 5px;
    }

    .footer {
      margin-top: 40px;
      padding-top: 20px;
      border-top: 1px solid #ccc;
      font-size: 9pt;
      color: #666;
      text-align: center;
    }

    .watermark {
      position: fixed;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%) rotate(-45deg);
      font-size: 72pt;
      color: rgba(255, 0, 0, 0.1);
      z-index: -1;
    }
  </style>
</head>
<body>
  {{#if (eq status "draft")}}
  <div class="watermark">RASCUNHO</div>
  {{/if}}

  <div class="header">
    <div class="contract-title">
      {{typeLabel type}}
    </div>
    <div class="contract-number">Contrato Nº {{number}}</div>
  </div>

  <div class="parties">
    <div class="party">
      <div class="party-label">Contratante:</div>
      <div class="party-info">
        <strong>{{parties.contractor.name}}</strong>,
        {{#if (eq parties.contractor.type "company")}}
        empresa inscrita no CNPJ sob nº {{formatCNPJ parties.contractor.documentId}},
        {{#if parties.contractor.representative}}
        neste ato representada por {{parties.contractor.representative}},
        {{/if}}
        {{else}}
        inscrito(a) no CPF sob nº {{formatCPF parties.contractor.documentId}},
        {{/if}}
        com endereço em {{parties.contractor.address}},
        {{parties.contractor.city}} - {{parties.contractor.state}},
        CEP {{formatZipCode parties.contractor.zipCode}}.
      </div>
    </div>

    <div class="party">
      <div class="party-label">Contratado:</div>
      <div class="party-info">
        <strong>{{parties.contractee.name}}</strong>,
        {{#if (eq parties.contractee.type "company")}}
        empresa inscrita no CNPJ sob nº {{formatCNPJ parties.contractee.documentId}},
        {{#if parties.contractee.representative}}
        neste ato representada por {{parties.contractee.representative}},
        {{/if}}
        {{else}}
        inscrito(a) no CPF sob nº {{formatCPF parties.contractee.documentId}},
        {{/if}}
        com endereço em {{parties.contractee.address}},
        {{parties.contractee.city}} - {{parties.contractee.state}},
        CEP {{formatZipCode parties.contractee.zipCode}}.
      </div>
    </div>
  </div>

  <p style="margin-bottom: 30px; text-align: justify;">
    As partes acima qualificadas têm, entre si, justo e acertado o presente
    {{typeLabel type}}, que se regerá pelas cláusulas seguintes e pelas
    condições descritas no presente.
  </p>

  <div class="clause">
    <div class="clause-number">CLÁUSULA 1ª</div>
    <div class="clause-title">DO OBJETO</div>
    <div class="clause-content">
      {{terms.scope}}
    </div>
  </div>

  {{#if terms.deliverables}}
  <div class="clause">
    <div class="clause-number">CLÁUSULA 2ª</div>
    <div class="clause-title">DAS ENTREGAS</div>
    <div class="clause-content">
      O CONTRATADO se compromete a realizar as seguintes entregas:
      <ul style="margin-top: 10px;">
        {{#each terms.deliverables}}
        <li>{{this}}</li>
        {{/each}}
      </ul>
    </div>
  </div>
  {{/if}}

  <div class="clause">
    <div class="clause-number">CLÁUSULA {{clauseNumber "payment"}}</div>
    <div class="clause-title">DO PAGAMENTO</div>
    <div class="clause-content">
      Pelo serviço prestado, o CONTRATANTE pagará ao CONTRATADO o valor de
      <strong>{{formatCurrency terms.payment.amount terms.payment.currency}}</strong>
      {{#if (eq terms.payment.schedule "monthly")}}
      mensalmente, todo dia {{terms.payment.dueDay}} de cada mês.
      {{else if (eq terms.payment.schedule "one-time")}}
      em pagamento único.
      {{else if (eq terms.payment.schedule "milestone")}}
      conforme atingimento de marcos do projeto.
      {{/if}}
    </div>
  </div>

  <div class="clause">
    <div class="clause-number">CLÁUSULA {{clauseNumber "term"}}</div>
    <div class="clause-title">DO PRAZO</div>
    <div class="clause-content">
      O presente contrato terá vigência de <strong>{{formatDate startDate}}</strong>
      {{#if endDate}}
      até <strong>{{formatDate endDate}}</strong>
      {{else}}
      por prazo indeterminado
      {{/if}}.
    </div>
  </div>

  {{#if terms.confidentiality}}
  <div class="clause">
    <div class="clause-number">CLÁUSULA {{clauseNumber "confidentiality"}}</div>
    <div class="clause-title">DA CONFIDENCIALIDADE</div>
    <div class="clause-content">
      {{terms.confidentiality}}
    </div>
  </div>
  {{/if}}

  {{#if terms.termination}}
  <div class="clause">
    <div class="clause-number">CLÁUSULA {{clauseNumber "termination"}}</div>
    <div class="clause-title">DA RESCISÃO</div>
    <div class="clause-content">
      {{terms.termination}}
    </div>
  </div>
  {{/if}}

  {{#if terms.custom}}
  {{#each terms.custom}}
  <div class="clause">
    <div class="clause-number">CLÁUSULA {{clauseNumber @index}}</div>
    <div class="clause-title">{{uppercase title}}</div>
    <div class="clause-content">
      {{content}}
    </div>
  </div>
  {{/each}}
  {{/if}}

  <div class="clause">
    <div class="clause-number">CLÁUSULA FINAL</div>
    <div class="clause-title">DO FORO</div>
    <div class="clause-content">
      As partes elegem o foro da Comarca de {{parties.contractor.city}}/{{parties.contractor.state}}
      para dirimir quaisquer dúvidas ou litígios oriundos do presente contrato,
      com renúncia expressa a qualquer outro, por mais privilegiado que seja.
    </div>
  </div>

  <p style="margin: 40px 0 20px 0; text-align: justify;">
    E por estarem assim justos e contratados, firmam o presente instrumento, em duas vias de igual teor e forma.
  </p>

  <p style="text-align: right; margin-bottom: 60px;">
    {{parties.contractor.city}}, {{formatDate (or signedAt startDate)}}
  </p>

  <div class="signatures">
    <div class="signature-block">
      {{#if signatures.contractor.signature}}
      <img src="{{signatures.contractor.signature}}" class="signature-image">
      {{/if}}
      <div class="signature-line"></div>
      <div class="signature-label">
        <strong>{{parties.contractor.name}}</strong><br>
        Contratante
        {{#if signatures.contractor.documentId}}
        <br>CPF/CNPJ: {{signatures.contractor.documentId}}
        {{/if}}
      </div>
      {{#if signatures.contractor.signedAt}}
      <div class="signature-info">
        Assinado digitalmente em {{formatDateTime signatures.contractor.signedAt}}<br>
        IP: {{signatures.contractor.ip}}
      </div>
      {{/if}}
    </div>

    <div class="signature-block">
      {{#if signatures.contractee.signature}}
      <img src="{{signatures.contractee.signature}}" class="signature-image">
      {{/if}}
      <div class="signature-line"></div>
      <div class="signature-label">
        <strong>{{parties.contractee.name}}</strong><br>
        Contratado
        {{#if signatures.contractee.documentId}}
        <br>CPF/CNPJ: {{signatures.contractee.documentId}}
        {{/if}}
      </div>
      {{#if signatures.contractee.signedAt}}
      <div class="signature-info">
        Assinado digitalmente em {{formatDateTime signatures.contractee.signedAt}}<br>
        IP: {{signatures.contractee.ip}}
      </div>
      {{/if}}
    </div>
  </div>

  <div class="footer">
    Documento gerado eletronicamente em {{formatDateTime (now)}}<br>
    Versão {{version}} | Hash: {{documentHash}}
  </div>
</body>
</html>

Implementação

import { RenderHubClient } from '@renderhub/sdk';
import crypto from 'crypto';

export class ContractService {
  private renderHub: RenderHubClient;

  constructor() {
    this.renderHub = new RenderHubClient({
      apiKey: process.env.RENDERHUB_API_KEY!,
    });
  }

  async generateContract(contract: Contract): Promise<Buffer> {
    // 1. Calcular hash do documento
    const documentHash = this.calculateHash(contract);

    // 2. Preparar dados
    const templateData = {
      ...contract,
      documentHash,
      startDate: contract.startDate.toISOString().split('T')[0],
      endDate: contract.endDate?.toISOString().split('T')[0],
    };

    // 3. Gerar PDF
    const pdf = await this.renderHub.generatePDF('contract-v1', templateData);

    return pdf;
  }

  private calculateHash(contract: Contract): string {
    const data = JSON.stringify({
      number: contract.number,
      parties: contract.parties,
      terms: contract.terms,
      startDate: contract.startDate,
    });

    return crypto.createHash('sha256').update(data).digest('hex').substring(0, 16);
  }

  async createDraft(contractData: Partial<Contract>): Promise<Contract> {
    const contract: Contract = {
      id: crypto.randomUUID(),
      number: await this.generateContractNumber(),
      status: 'draft',
      version: 1,
      ...contractData as Contract,
    };

    await this.saveContract(contract);

    return contract;
  }

  async sendForSignature(contractId: string): Promise<void> {
    const contract = await this.getContract(contractId);

    if (contract.status !== 'draft') {
      throw new Error('Apenas contratos em rascunho podem ser enviados para assinatura');
    }

    // Gerar PDF
    const pdf = await this.generateContract(contract);

    // Enviar emails para assinatura
    await this.sendSignatureRequest(contract.parties.contractor.email, pdf);
    await this.sendSignatureRequest(contract.parties.contractee.email, pdf);

    // Atualizar status
    contract.status = 'pending_signature';
    await this.saveContract(contract);
  }

  private async generateContractNumber(): Promise<string> {
    const year = new Date().getFullYear();
    const count = await this.getContractCount(year);
    return `CTR-${year}-${String(count + 1).padStart(5, '0')}`;
  }

  private async saveContract(contract: Contract): Promise<void> {
    // Implementar salvamento no banco
    throw new Error('Not implemented');
  }

  private async getContract(id: string): Promise<Contract> {
    // Implementar busca no banco
    throw new Error('Not implemented');
  }

  private async sendSignatureRequest(email: string, pdf: Buffer): Promise<void> {
    // Implementar envio de email
    throw new Error('Not implemented');
  }

  private async getContractCount(year: number): Promise<number> {
    // Implementar contagem
    throw new Error('Not implemented');
  }
}

Integração com Assinatura Eletrônica

// Integração com DocuSign, ClickSign, etc
export class SignatureService {
  async sendToDocuSign(contract: Contract, pdf: Buffer): Promise<string> {
    // Implementar integração com DocuSign API
    const envelopeId = await docusign.createEnvelope({
      document: pdf,
      signers: [
        {
          email: contract.parties.contractor.email,
          name: contract.parties.contractor.name,
        },
        {
          email: contract.parties.contractee.email,
          name: contract.parties.contractee.name,
        },
      ],
    });

    return envelopeId;
  }
}

Próximos Passos

On this page