HR ve İK Platformlarında KVKK Kabusu ve Veribenim Çözümü: Aday CV'leri, İşe Alım Verisi ve Otomatik Anonimleştirme

HR ve İK Platformlarında KVKK Kabusu ve Veribenim Çözümü: Aday CV'leri, İşe Alım Verisi ve Otomatik Anonimleştirme

HR ve İK Platformlarında KVKK Kabusu ve Veribenim Çözümü: Aday CV'leri, İşe Alım Verisi ve Otomatik Anonimleştirme

Giriş: ATS (Applicant Tracking System) + KVKK = Zaman Bombası

İşinizi düşünün. Bir ATS (Applicant Tracking System) platformu kullanıyorsunuz. Her gün yüzlerce, hatta binlerce başvuru geliyor. Her başvurunun içinde:

  • Özgeçmiş (CV): Adı, soyadı, telefonu, e-postası, eğitim ve iş deneyimi

  • Kapı yazısı (Cover Letter): Kişisel hedefler, motivasyonlar

  • Sosyal Medya Linkleri: LinkedIn profili, portfolyo siteleri

  • Fotoğraf: Birçok başvuruda CV'ye iliştirilen fotoğraf

  • Sağlık Bilgileri: Engelli işe alım kotası, sağlık yapısı

  • Eğitim Belgeleri: Diploma, sertifika taramaları

  • Referans Bilgileri: Eski işverenler, telefon numaraları

Peki, bunları ne kadar tutabilirsıniz?

KVKK kapsamında, bu veriler "özel nitelikli kişisel veriler" kategorisinedir. Türkiye'de işe alım süreci KVKK Madde 7 ve GDPR Madde 5(1)(e) altında ciddi bir kompliansı mekanizması gerektirir. Çoğu HR platformu bunu göz ardı eder — ve bu, yargıya kadar gidebilir.

Bu makale, HR ve İK platformlarının nasıl KVKK uyumlu hale gelebileceğini, Veribenim SDK'ları ile otomatik anonimleştirme ve veri yönetimi yapacağını teknik derinlikte anlatır.


1. CV'de Hangi Veriler "Özel Nitelikli" Sayılır?

KVKK Madde 6'ya göre, aşağıdaki veriler "hassas kişisel veriler" kategorisindedir:

Veri Türü

KVKK Tanımı

Yönetim Zorluk Seviyesi

Fotoğraf

Kişinin fiziki özelliklerini ortaya koyan görsel veri. Yüz tanıma kapsamında biyometrik.

🔴 Çok Yüksek

Sağlık Bilgisi

Engelli işe alım, kronik hastalık, tıbbi durum, nedeniyle.

🔴 Çok Yüksek

Etnik Köken

CV'de kimliğe işaret eden referans.

🔴 Çok Yüksek

Sendika Üyeliği

İnsan kaynakları formu alanında yer alan.

🟠 Yüksek

Politik Görüş

Sosyal medya linkleri üzerinden tersiyle.

🟠 Yüksek

E-posta & Telefon

Doğrudan kimlik.

🟡 Orta

Eğitim Tarihi

İndirekt yaş tahmini.

🟡 Orta

Sosyal Medya Profili

KVKK Madde 22 kapsamında "açık kaynak" ama rıza gerekli.

🟡 Orta

Ad-Soyad

Kişinin hemen tanınması.

🟢 Temel

⚖️ HUKUKİ NOT: KVKK Madde 6 kapsamında, "özel nitelikli kişisel veri" işlemek için açık rıza ve meşru hedef (işe alım için işe alım hedefi) gereklidir. GDPR'da bu "Special Categories of Personal Data" (Madde 9) olarak adlandırılır.


2. KVKK'ya Göre CV Saklama Süresi: 1 Yıl mı, 2 Yıl mı, Süresiz mi?

İşte kritik soru: "Bu CV'yi ne kadar tutmalıyım?"

KVKK Madde 7(1) açık cevap veriyor: Veriler "yalnızca meşru hedef için gerekli olduğu süre" saklanabilir.

İşe alım bağlamında:

✅ İşe Alınan Aday

İşe Alım Sonrası Veriler
├─ İş İlişkisi Süresi (yasal)
│  └─ Bordro, SSK, Vergi Müfettişliği
│
└─ İş İlişkisi BİTİŞ Sonrası
   ├─ 10 yıl (İş Kanunu Madde 53 - belge saklaması)
   │  └─ Bordro, yıllık izin, tazminat kayıtları
   │
   └─ Ancak CV ÖZÜ (fotoğraf, sağlık, sosyal medya) → ANONİMLEŞTİR

❌ İşe Alınmayan Aday

Başvurmuş Ama Seçilmemiş
├─ Saklama Süresi: 1-2 Yıl
│  (KVKK Rehberi: "Olası ilerideki açılışlar için")
│
└─ 2 Yıl Dolunca
   ├─ CV TAMAMEN AYNI SİL veya ANONİMLEŞTİR
   └─ Hukuki yükümlülük biterse de → İyi uygulama

💡 PRO TIP: Çoğu HR platformu "başvuruyu arşivde tut" düşüncesiyle CV'yi süresiz saklıyor. Bu KVKK ihlalidir. Veribenim ile otomatik olarak saklama süresi hesaplanır ve zamanlayıcı job süresi bitince anonimleştirme yapılır.


3. Sosyal Medya Araştırması: LinkedIn Profili KVKK İhlali Riski

Modern HR ekipleri linkedin.com üzerinde ek araştırma yapıyor. Bu tehlikeli.

🔴 Risk Senaryoları

Senaryo 1: Aday Koşulsuz Görünüş

HR: "Bu aday harika görünüyor. Ama LinkedIn'de fotoğrafının yanında
     'Müslüman İş Kadınları Kulübü' üyesi yazıyor. Din önyargısı oluştursa?"
→ KVKK Madde 6 İhlaline Gider

Senaryo 2: Yaş Tahmini

HR: "LinkedIn'de başlandı 1995. Bu bize 29-30 yaşında olduğunu söylüyor.
     Genç oyun ekibi istiyoruz, daha yaşlı birine almaycağız."
→ GDPR Article 5 + KVKK Madde 5 (Meşru hedef yok)

Senaryo 3: Gizli İş Mülakat

HR: "Bu aday X şirkette İnsan Kaynakları Müdürü. Ülkede sadece 3 tane var.
     İdentitesi garantili. Stalkılamış."
→ Data Sovereignty İhlali (KVKK Madde 8)

✅ KVKK Uyumlu Sosyal Medya Kontrol

Eğer gerekirse sosyal medya araştırması yapacaksanız:

  1. Rıza Al: "LinkedIn profilinizi kontrol etmek istiyoruz" yazılarında açık olun

  2. Objektif Kriterler: Yalnızca "teknik beceri kanıtı" ara. Din, cinsiyet, yaş çıkarımı yapma

  3. İzole Et: Sosyal medya bulguları ayrı dosyada tut. CV'ye bağlantı kurma

  4. Süre Sınırı: Sosyal medya araştırması sonrası bulgularını 30 gün içinde sil

⚖️ HUKUKİ NOT: GDPR Madde 22'de "sadece otomatik karar" hakkı vardır. İnsan + Makine Hybrid (HR meraklı + LinkedIn scraper bot) hukuki boşluk oluşturur. Veribenim çözümü: Sosyal medya araştırması loglanır, 30 gün sonra otomatik silinir.


4. İşe Alınmayan Adaylar için Saklama Süresi Farkı

Türk HR hukuku bu konuda tasvizdir: İş Kanunu ve KVKK farklı süreler söylüyor.

📊 Saklama Süresi Tablosu

Durum

KVKK Görüş

İş Kanunu

Uygulanan

Sebep

İşe alınan aday

İş ilişkisi + 10 yıl

10 yıl (Md. 53)

10 yıl

Vergi, SSK, tazminat

Red edilen aday

1-2 yıl (ilerideki pozisyonlar)

N/A

2 yıl

KVKK meşru hedef

Arşiv (müdür kararı)

Açık kaydında işareti gerekli

N/A

2 yıl maks

Kanuni güvenlik

Yeniden başvuranlar

Her başvuru için sürü sıfırlanır

N/A

2 yıl

KVKK meşru hedef yenilenme

💡 PRO TIP: Veribenim otomatik saklama süresi izler. Türk hukuku için: İşe alınan = 10 yıl, Red edilen = 2 yıl. Sistem takvime göre tüm alan anonimleştirmeyi otomatik yapar.


5. Nasıl Entegre Edilir? — PHP SDK ile Otomatik Anonimleştirme

Şimdi çözüm. Veribenim PHP SDK ile başvuru formundan anonimleştirmeye kadar nasıl otomatik akış yaratılır:

Adım 1: Başvuru Formu Consent Loglama

<?php
// /app/Services/ApplicationFormService.php

use Veribenim\\VeribenimClient;
use Illuminate\\Http\\UploadedFile;

class ApplicationFormService
{
    private VeribenimClient $veribenim;

    public function __construct()
    {
        $this->veribenim = new VeribenimClient(config('veribenim.token'));
    }

    /**
     * İş başvuru formundan gelen veriye consent işle
     *
     * @param array $formData [full_name, email, phone, position, cv_has_photo]
     * @param UploadedFile $cvFile
     * @return array ['success' => bool, 'application_id' => int|null, 'error' => string|null]
     */
    public function processApplication(array $formData, UploadedFile $cvFile): array
    {
        // 1. Zorunlu Consent Kontrolü
        if (empty($formData['kvkk_consent'])) {
            return [
                'success' => false,
                'error' => 'KVKK onayı zorunludur. İş başvurusunu tamamlayamazsınız.'
            ];
        }

        // 2. CV'de Fotoğraf Var mı? Uyarı Logla
        $cvHasPhoto = $this->detectPhotoInCv($cvFile);

        if ($cvHasPhoto) {
            // Fotoğraflı CV -> Ayrı consent gerekli
            if (empty($formData['photo_consent'])) {
                return [
                    'success' => false,
                    'error' => 'CV\\'nize fotoğraf eklemişsiniz. Fotoğraf işlenmesi için ayrı onay gereklidir.'
                ];
            }
        }

        // 3. Consent Log (KVKK Madde 5 - İzlenebilirlik)
        $consentLog = $this->veribenim->logFormConsent(
            formName: 'job_application',
            consented: true,
            consentText: sprintf(
                'Başvurduğum %s pozisyonu için verdiğim başvuru kapsamında ' .
                'kişisel verilerimin (ad, soyad, e-posta, telefon, özgeçmiş, eğitim ve iş deneyimi) ' .
                'yönetim amaçlı olarak %d yıl süreyle işlenmesini onaylıyorum. ' .
                'KVKK Madde 6 ve Aydınlatma Metni kapsamında onay veriyorum. ' .
                '%s',
                $formData['position'],
                2, // 2 yıl saklama süresi
                $cvHasPhoto ? 'CV\\'mde yer alan fotoğrafın da işlenmesini onaylıyorum.' : ''
            ),
            metadata: [
                'position'           => $formData['position'],
                'application_date'   => now()->toDateString(),
                'cv_uploaded'        => true,
                'cv_has_photo'       => $cvHasPhoto,
                'phone_provided'     => !empty($formData['phone']),
                'linkedin_provided'  => !empty($formData['linkedin_url']),
                'applicant_country'  => $this->detectCountry($formData['email']),
            ]
        );

        if (!$consentLog || empty($consentLog['id'])) {
            \\Log::error('[Veribenim] Consent log başarısız', $consentLog);
            return [
                'success' => false,
                'error' => 'Sistem hatası. Lütfen daha sonra tekrar deneyin.'
            ];
        }

        // 4. CV'yi Şifreli Depola (Zero-Knowledge)
        $cvPath = $this->storeCvEncrypted($cvFile, $formData['email']);

        if (!$cvPath) {
            return [
                'success' => false,
                'error' => 'CV yüklenemedi. Lütfen daha sonra deneyin.'
            ];
        }

        // 5. Başvuru Kaydını Oluştur (KVKK Anonimleştirme Tarihi ile)
        $application = Application::create([
            'company_id'          => auth('company')->id(),
            'applicant_full_name' => $formData['full_name'],
            'applicant_email'     => $formData['email'],
            'applicant_phone'     => $formData['phone'] ?? null,
            'position'            => $formData['position'],
            'cv_path'             => $cvPath,
            'cv_has_photo'        => $cvHasPhoto,
            'linkedin_url'        => $formData['linkedin_url'] ?? null,
            'consent_id'          => $consentLog['id'],
            'consent_logged_at'   => now(),
            'retention_until'     => now()->addYears(2), // KVKK Compliance
            'status'              => 'pending',
            'applied_at'          => now(),
        ]);

        // 6. İleri Araştırma Kural Sınırı Yaz (Sosyal Medya Araştırması)
        if (!empty($formData['linkedin_url'])) {
            $application->metadata()->create([
                'key'   => 'linkedin_research_deadline',
                'value' => now()->addDays(30)->toIso8601String(),
            ]);
        }

        \\Log::info('[Veribenim HR] Yeni başvuru işlendi', [
            'application_id' => $application->id,
            'position'       => $application->position,
            'retention_until' => $application->retention_until,
            'consent_id'     => $consentLog['id'],
        ]);

        return [
            'success' => true,
            'application_id' => $application->id,
            'message' => 'Başvurunuz alındı. 2-3 iş günü içinde geri dönüş yapacağız.'
        ];
    }

    /**
     * CV'de fotoğraf olup olmadığını algıla
     * PDDocument kullanarak basit kontrol
     */
    private function detectPhotoInCv(UploadedFile $file): bool
    {
        if ($file->getClientMimeType() !== 'application/pdf') {
            // PDF değilse, DOCX ise Word metadatasından kontrol
            return false; // Basitleştirilmiş
        }

        try {
            $pdf = \\PDFParser\\Parser::parse($file->getContent());
            // PDF'de image nesnesi varsa → fotoğraf var
            return $pdf->getPages()[0]->countImages() > 0;
        } catch (\\Exception $e) {
            return false;
        }
    }

    /**
     * CV dosyasını şifreli depola (Zero-Knowledge Storage)
     */
    private function storeCvEncrypted(UploadedFile $file, string $email): ?string
    {
        $filename = sprintf(
            'cv_%s_%s.%s',
            hash('sha256', $email),
            now()->timestamp,
            $file->getClientOriginalExtension()
        );

        // Encryptable storage
        $path = $file->storeAs(
            'cvs/encrypted',
            $filename,
            disk: 'encrypted'
        );

        return $path ?: null;
    }

    private function detectCountry(string $email): string
    {
        // Basit: tr domaininden mi
        return str_ends_with($email, '.tr') ? 'TR' : 'OTHER';
    }
}

Adım 2: Otomatik CV Anonimleştirme Job

<?php
// /app/Jobs/CvAnonymizationJob.php

namespace App\\Jobs;

use App\\Models\\Application;
use Veribenim\\VeribenimClient;
use Illuminate\\Bus\\Queueable;
use Illuminate\\Queue\\SerializesModels;

class CvAnonymizationJob implements ShouldQueue
{
    use Queueable, SerializesModels;

    private VeribenimClient $veribenim;

    public function __construct()
    {
        $this->veribenim = new VeribenimClient(config('veribenim.token'));
    }

    /**
     * Her gün çalışacak (Scheduler tarafından tetiklenir)
     * Retention süresi dolmuş CV'leri anonimleştir
     */
    public function handle(): void
    {
        \\Log::info('[CvAnonymizationJob] Başlıyor...');

        // 1. RESİ ALILMAMIŞ ADAYLAR → 2 YIL SONRA
        $this->anonymizeRejectedApplications();

        // 2. İŞE ALINANLAR → 10 YIL SONRA (İS İLİŞKİSİ BİTİŞ SONRASI)
        $this->anonymizeTerminatedEmployeeRecords();

        // 3. SOSYAL MEDYA ARAŞTIRMASI → 30 GÜN SONRA
        $this->clearLinkedInResearchData();

        \\Log::info('[CvAnonymizationJob] Tamamlandı.');
    }

    /**
     * Red edilen adayların CV'sini 2 yıl sonra sil
     */
    private function anonymizeRejectedApplications(): void
    {
        $expiredApplications = Application::where('retention_until', '<', now())
            ->whereIn('status', ['rejected', 'withdrawn', 'pending']) // İşe alınmamış
            ->whereNull('anonymized_at')
            ->get();

        \\Log::info("[CvAnonymizationJob] Red edilen/Bekleyen başvurular: {$expiredApplications->count()} adet");

        foreach ($expiredApplications as $application) {
            try {
                $this->anonymizeApplication(
                    $application,
                    reason: 'KVKK Madde 7(1) - Meşru hedef sona erdi (2 yıl retention)',
                    isEmployee: false
                );
            } catch (\\Exception $e) {
                \\Log::error("[CvAnonymizationJob] Başvuru #{$application->id} anonimleştirmede hata", [
                    'error' => $e->getMessage()
                ]);
            }
        }
    }

    /**
     * İşten ayrılan çalışanların CV'sini 10 yıl sonra sil
     */
    private function anonymizeTerminatedEmployeeRecords(): void
    {
        $expiredEmployees = Application::where('status', 'hired')
            ->whereNotNull('employee_termination_date')
            ->whereRaw('DATE_ADD(employee_termination_date, INTERVAL 10 YEAR) < NOW()')
            ->whereNull('anonymized_at')
            ->get();

        \\Log::info("[CvAnonymizationJob] Emekli çalışanlar: {$expiredEmployees->count()} adet");

        foreach ($expiredEmployees as $record) {
            try {
                $this->anonymizeApplication(
                    $record,
                    reason: 'İş Kanunu Md. 53 - 10 yıl saklama süresi doldu',
                    isEmployee: true
                );
            } catch (\\Exception $e) {
                \\Log::error("[CvAnonymizationJob] Çalışan #{$record->id} anonimleştirmede hata", [
                    'error' => $e->getMessage()
                ]);
            }
        }
    }

    /**
     * LinkedIn araştırması verilerini 30 gün sonra sil
     * (GDPR Right to Erasure Madde 17)
     */
    private function clearLinkedInResearchData(): void
    {
        $linkedinDataToDelete = Application::whereHas('metadata', function ($q) {
            $q->where('key', 'linkedin_research_deadline')
              ->whereRaw('CAST(value AS DATE) < NOW()');
        })->whereNull('linkedin_cleared_at')->get();

        foreach ($linkedinDataToDelete as $application) {
            $application->update([
                'linkedin_url'     => null, // Sil
                'linkedin_cleared_at' => now(),
            ]);

            $application->metadata()
                ->where('key', 'linkedin_research_deadline')
                ->delete();

            \\Log::info("[CvAnonymizationJob] LinkedIn verisi silindi - Başvuru #{$application->id}");
        }
    }

    /**
     * Ana anonimleştirme fonksiyonu
     */
    private function anonymizeApplication(
        Application $app,
        string $reason,
        bool $isEmployee = false
    ): void {
        // 1. CV DİSKİ SİL
        if ($app->cv_path && \\Storage::disk('encrypted')->exists($app->cv_path)) {
            \\Storage::disk('encrypted')->delete($app->cv_path);
            \\Log::info("[Veribenim HR] CV dosyası silindi - App #{$app->id}");
        }

        // 2. KİŞİSEL ALANLARI ANONİMLEŞTİR
        $anonymousSeed = hash('sha256', $app->id . config('app.key'));
        $anonymousId = substr($anonymousSeed, 0, 8);

        $app->update([
            'applicant_full_name'     => "ANONİM ADAY #{$anonymousId}",
            'applicant_email'         => null,
            'applicant_phone'         => null,
            'linkedin_url'            => null,
            'cv_path'                 => null,
            'cv_has_photo'            => false,
            'anonymized_at'           => now(),
            'anonymized_reason'       => $reason,
        ]);

        // 3. VERİBENİM ÜZERİNDEN DSAR KAYıT
        // (Veribenim Dashboard'da "Veri Silme Tarihçesi" görünsün)
        $this->veribenim->submitDsar(
            requestType: 'erasure',
            fullName: 'System Automated',
            email: 'system@' . config('app.domain'),
            description: sprintf(
                'Otomatik Anonimleştirme Tamamlandı\\n' .
                '- Başvuru ID: %d\\n' .
                '- Pozisyon: %s\\n' .
                '- Durum: %s\\n' .
                '- İşe Alındı: %s\\n' .
                '- Sebep: %s\\n' .
                '- KVKK Madde 7(1) / GDPR Article 5(1)(e) uyumlu',
                $app->id,
                $app->position,
                $app->status,
                $isEmployee ? 'Evet' : 'Hayır',
                $reason
            ),
            metadata: [
                'automation' => 'true',
                'job_name'   => self::class,
                'application_id' => (string) $app->id,
            ]
        );

        \\Log::info("[Veribenim HR] Anonimleştirme tamamlandı - App #{$app->id}", [
            'reason'         => $reason,
            'is_employee'    => $isEmployee,
            'dsar_submitted' => true,
        ]);
    }
}

Adım 3: Scheduler Konfigürasyonu

<?php
// /app/Console/Kernel.php

protected function schedule(Schedule $schedule)
{
    // Günlük anonimleştirme kontrol
    $schedule->job(new CvAnonymizationJob)
        ->dailyAt('02:00') // Gece 02:00, database trafiği düşük
        ->onOneServer();   // Tekrar çalışma riskini kaldır

    // Opsiyonel: Haftalık rapor
    $schedule->command('hr:anonymization-report')
        ->weeklyOn(1, '09:00') // Pazartesi 09:00
        ->sendOutputTo(storage_path('logs/anonymization-report.log'));
}

6. Laravel SDK ile ATS Entegrasyonu

Eğer ATS (Taleo, Workable, Lever vb.) ile entegrasyon yapıyorsanız:

<?php
// /app/Services/AtsSyncService.php
// Harici ATS -> Veribenim Consent Manager

use Veribenim\\Laravel\\VeribenimFacade as Veribenim;

class AtsSyncService
{
    /**
     * Harici ATS'den gelen başvuru verisini Veribenim'e senkronize et
     */
    public function syncApplicationFromAts(array $atsData): void
    {
        // ATS'de "başvuru onayı var" mi?
        if (empty($atsData['consent_given'])) {
            throw new \\Exception('ATS başvurusunda consent kaydı yok. Senkronizasyon iptal.');
        }

        // Veribenim consent log oluştur
        Veribenim::logFormConsent(
            formName: 'ats_imported_application',
            consented: true,
            consentText: sprintf(
                'ATS systemi üzerinden alınan başvuru. ' .
                'Orijinal rıza: %s. ' .
                'Pozisyon: %s',
                $atsData['consent_given'],
                $atsData['position']
            ),
            metadata: [
                'ats_application_id' => $atsData['id'],
                'ats_system'         => $atsData['ats_name'], // 'taleo', 'workable'
                'synced_at'          => now()->toIso8601String(),
            ]
        );
    }
}

7. React ile İş Başvuru Formu (Frontend)

Frontend tarafında, consent UI bileşeni:

// components/JobApplicationForm.tsx
import React, { useState } from 'react';
import { useVeribenim } from '@veribenim/react';

interface JobApplicationFormProps {
  position: string;
  companyName: string;
  onSuccess?: (applicationId: number) => void;
}

export function JobApplicationForm({
  position,
  companyName,
  onSuccess,
}: JobApplicationFormProps) {
  const { savePreferences } = useVeribenim();
  const [formData, setFormData] = useState({
    fullName: '',
    email: '',
    phone: '',
    linkedinUrl: '',
    cvFile: null as File | null,
    kvkkConsent: false,
    photoConsent: false,
  });

  const [cvHasPhoto, setCvHasPhoto] = useState(false);
  const [loading, setLoading] = useState(false);
  const [errors, setErrors] = useState<Record<string, string>>({});

  const handleCvUpload = async (file: File) => {
    setFormData((prev) => ({ ...prev, cvFile: file }));

    // Basit fotoğraf algılama (PDF metadata)
    if (file.type === 'application/pdf') {
      const hasPhoto = await detectPhotoInPdf(file);
      setCvHasPhoto(hasPhoto);
    }
  };

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    setErrors({});

    // Validation
    const newErrors: Record<string, string> = {};

    if (!formData.fullName.trim()) {
      newErrors.fullName = 'Ad-Soyad gereklidir';
    }
    if (!formData.email.trim()) {
      newErrors.email = 'E-posta gereklidir';
    }
    if (!formData.cvFile) {
      newErrors.cvFile = 'CV dosyası gereklidir';
    }
    if (!formData.kvkkConsent) {
      newErrors.kvkkConsent = 'KVKK onayı zorunludur';
    }
    if (cvHasPhoto && !formData.photoConsent) {
      newErrors.photoConsent = 'CV\\'nize fotoğraf eklenmişse, ayrı onay gereklidir';
    }

    if (Object.keys(newErrors).length > 0) {
      setErrors(newErrors);
      return;
    }

    setLoading(true);

    try {
      // Consent tercihleri kaydet
      await savePreferences({
        necessary: true,
        analytics: true,  // İş başvuru analitikleri
        marketing: false, // İşe alım platformu pazarlama yapamaz
        preferences: true,
      });

      // Form gönder
      const formDataToSend = new FormData();
      formDataToSend.append('full_name', formData.fullName);
      formDataToSend.append('email', formData.email);
      formDataToSend.append('phone', formData.phone);
      formDataToSend.append('linkedin_url', formData.linkedinUrl);
      formDataToSend.append('cv', formData.cvFile!);
      formDataToSend.append('kvkk_consent', String(formData.kvkkConsent));
      formDataToSend.append('photo_consent', String(formData.photoConsent));
      formDataToSend.append('cv_has_photo', String(cvHasPhoto));

      const response = await fetch('/api/apply', {
        method: 'POST',
        body: formDataToSend,
      });

      const result = await response.json();

      if (!response.ok) {
        throw new Error(result.error || 'Başvuru gönderilmesinde hata');
      }

      onSuccess?.(result.application_id);
      alert('Başvurunuz alındı! İyi şanslar.');
    } catch (err) {
      setErrors({
        submit: err instanceof Error ? err.message : 'Bilinmeyen hata',
      });
    } finally {
      setLoading(false);
    }
  };

  return (
    <form onSubmit={handleSubmit} className="job-application-form">
      <h2>
        {position} Pozisyonuna Başvuru
      </h2>

      {/* Ad-Soyad */}
      <div className="form-group">
        <label htmlFor="fullName">
          Ad-Soyad <span className="required">*</span>
        </label>
        <input
          id="fullName"
          type="text"
          value={formData.fullName}
          onChange={(e) => setFormData({ ...formData, fullName: e.target.value })}
          aria-invalid={!!errors.fullName}
          aria-describedby={errors.fullName ? 'fullName-error' : undefined}
        />
        {errors.fullName && (
          <p id="fullName-error" className="error">
            {errors.fullName}
          </p>
        )}
      </div>

      {/* E-posta */}
      <div className="form-group">
        <label htmlFor="email">
          E-posta <span className="required">*</span>
        </label>
        <input
          id="email"
          type="email"
          value={formData.email}
          onChange={(e) => setFormData({ ...formData, email: e.target.value })}
          aria-invalid={!!errors.email}
          aria-describedby={errors.email ? 'email-error' : undefined}
        />
        {errors.email && (
          <p id="email-error" className="error">
            {errors.email}
          </p>
        )}
      </div>

      {/* Telefon */}
      <div className="form-group">
        <label htmlFor="phone">
          Telefon (Opsiyonel)
        </label>
        <input
          id="phone"
          type="tel"
          value={formData.phone}
          onChange={(e) => setFormData({ ...formData, phone: e.target.value })}
          placeholder="+90 5XX XXX XXXX"
        />
      </div>

      {/* LinkedIn */}
      <div className="form-group">
        <label htmlFor="linkedinUrl">
          LinkedIn Profili (Opsiyonel)
        </label>
        <input
          id="linkedinUrl"
          type="url"
          value={formData.linkedinUrl}
          onChange={(e) => setFormData({ ...formData, linkedinUrl: e.target.value })}
          placeholder="<https://linkedin.com/in/>..."
        />
        <small>
          ⚠️ LinkedIn araştırması KVKK kapsamında 30 gün boyunca yapılır,
          sonra silinir.
        </small>
      </div>

      {/* CV Dosyası */}
      <div className="form-group">
        <label htmlFor="cvFile">
          Özgeçmiş (CV) <span className="required">*</span>
        </label>
        <input
          id="cvFile"
          type="file"
          accept=".pdf,.doc,.docx"
          onChange={(e) => handleCvUpload(e.target.files?.[0]!)}
          aria-invalid={!!errors.cvFile}
          aria-describedby={errors.cvFile ? 'cvFile-error' : undefined}
        />
        {errors.cvFile && (
          <p id="cvFile-error" className="error">
            {errors.cvFile}
          </p>
        )}
      </div>

      {/* Fotoğraflı CV Uyarısı */}
      {cvHasPhoto && (
        <div className="alert alert-warning">
          ⚠️ <strong>Fotoğraflı CV Tespit Edildi</strong>
          <p>
            CV'nize eklediğiniz fotoğraf KVKK kapsamında "özel nitelikli kişisel veri"
            sayılmaktadır. Bu başvuru için fotoğrafın işlenmesine açık onay vermeniz gereklidir.
          </p>
          <label>
            <input
              type="checkbox"
              checked={formData.photoConsent}
              onChange={(e) =>
                setFormData({ ...formData, photoConsent: e.target.checked })
              }
            />
            CV'mdeki fotoğrafın {companyName} tarafından işlenmesini onaylıyorum.
          </label>
          {errors.photoConsent && (
            <p className="error">{errors.photoConsent}</p>
          )}
        </div>
      )}

      {/* Ana KVKK Consent */}
      <div className="form-group consent-section">
        <label>
          <input
            type="checkbox"
            checked={formData.kvkkConsent}
            onChange={(e) => setFormData({ ...formData, kvkkConsent: e.target.checked })}
            required
          />
          <strong>KVKK Onayı (Zorunlu) *</strong>
        </label>

        <div className="consent-text">
          <p>
            {companyName} bünyesinde <strong>{position}</strong> pozisyonu için yaptığım
            başvuru kapsamında kişisel verilerimin (ad, soyad, e-posta, telefon, eğitim,
            iş deneyimi) <strong>2 yıl süreyle</strong> İnsan Kaynakları yönetimi amacıyla
            işlenmesini onaylıyorum.
          </p>
          <ul>
            <li>
              <strong>Saklama Süresi:</strong> Başvuru tarihi + 2 yıl
              (KVKK Madde 7)
            </li>
            <li>
              <strong>Verinin Silinmesi:</strong> 2 yıl dolduğunda
              otomatik anonimleştirme
            </li>
            <li>
              <strong>Sosyal Medya Araştırması:</strong> LinkedIn profili
              30 gün araştırıldıktan sonra silinir
            </li>
          </ul>
        </div>

        <p className="legal-note">
          Daha fazla bilgi için{' '}
          <a href="/kvkk-aydinlatma-metni" target="_blank" rel="noopener">
            KVKK Aydınlatma Metnini
          </a>
          {' '}ve{' '}
          <a href="/privacy-policy" target="_blank" rel="noopener">
            Gizlilik Politikamızı
          </a>
          {' '}inceleyebilirsiniz.
        </p>

        {errors.kvkkConsent && (
          <p className="error">{errors.kvkkConsent}</p>
        )}
      </div>

      {/* Genel Hata */}
      {errors.submit && (
        <div className="alert alert-danger">
          {errors.submit}
        </div>
      )}

      {/* Gönder Butonu */}
      <button
        type="submit"
        disabled={!formData.kvkkConsent || loading}
        className="btn-primary"
      >
        {loading ? 'Gönderiliyor...' : 'Başvuruyu Gönder'}
      </button>
    </form>
  );
}

// PDF'de fotoğraf algıla (Basit)
async function detectPhotoInPdf(file: File): Promise<boolean> {
  try {
    const arrayBuffer = await file.arrayBuffer();
    // PDF'de "Image" nesnesi var mı kontrol et
    const pdfText = new TextDecoder().decode(arrayBuffer);
    return /\\/XObject|\\/Image|\\/DCTDecode/.test(pdfText);
  } catch {
    return false;
  }
}

8. Data Sovereignty: Aday Verilerinin Üçüncü Taraf ATS'lere Gönderilmesi Riski

🌍 GLOBAL UYUM: KVKK Madde 8'de açık: "Kişisel veriler, yurt dışına aktarılamaz." GDPR'da Madde 44-50 kapsamında adequacy kararı gereklidir. Türkiye → USA → ATS = İHLAL.

Eğer Taleo (Oracle), Workable (EU), Lever (USA) gibi harici ATS kullanıyorsanız:

<?php
// /app/Policies/DataSovereigntyPolicy.php

class DataSovereigntyPolicy
{
    /**
     * Harici ATS'ye veri aktarırken kontrol
     */
    public function canTransferToAts(Application $application, string $atsVendor): bool
    {
        // 1. ATS'nin veri merkezi konumu
        $atsLocations = [
            'taleo'     => ['USA'],        // Oracle = USA → KVKK İHLALİ
            'workable'  => ['EU'],         // OK
            'lever'     => ['USA'],        // KVKK İHLALİ
            'freshteams' => ['EU'],        // Freshdesk EU
        ];

        $location = $atsLocations[$atsVendor] ?? null;

        if (!$location || in_array('USA', $location)) {
            // USA ATS = KVKK Madde 8 İhlali
            \\Log::error('[DataSovereignty] USA-tabanlı ATS reddedildi', [
                'ats' => $atsVendor,
                'application_id' => $application->id,
            ]);
            return false;
        }

        // 2. Aday consent kontrolü
        if (!$application->consent_id) {
            return false; // Consent yok = transfer yapma
        }

        return true;
    }
}

⚖️ HUKUKİ NOT: Türkiye'deki HR platformları, aday verilerini sadece Avrupa ve Türkiye merkezli ATS sistemlerine gönderebilir. USA tabanlı sistemler KVKK ihlalidir. Veribenim Data Sovereignty kontrolü otomatik yapar.


9. Automated Privacy: İşe Alım Takvimi ile Otomatik Veri Döngüsü

Veribenim'in Automated Privacy özelliği ile tüm veri yaşam döngüsü otomatik:

Gün 0:   Başvuru Yapıldı
         ↓
         Consent Log + CV Şifrelenmiş Depolan

Gün 7:   Seçim 1. Turda Başarısız
         ↓
         LinkedIn 30 Gün Countdown'ı Başlat

Gün 37:  LinkedIn Araştırması Süresi Doldu
         ↓
         LinkedIn URL Sil (Otomatik)

Gün 730: CV 2 Yıl Saklama Süresi Doldu
         ↓
         ⚙️ CvAnonymizationJob Çalıştı
         ✓ CV Dosyası Silindi
         ✓ Kişisel Alanları Anonimleştirdi
         ✓ Veribenim DSAR Kaydı Oluşturdu

10. Verininizi Bilin (DSAR): Aday Veri Erişim/Silme Talebi

KVKK Madde 11 ve GDPR Article 15-17 altında:

<?php
// /app/Http/Controllers/ApplicantPrivacyController.php

class ApplicantPrivacyController extends Controller
{
    /**
     * Aday: "Benim başvurumla ilgili verileri görüntülemek istiyorum"
     */
    public function submitAccessRequest(Request $request)
    {
        $request->validate([
            'email' => 'required|email',
            'position' => 'nullable|string',
        ]);

        // Veribenim üzerinden DSAR kaydı aç
        $dsar = Veribenim::submitDsar(
            requestType: 'access',
            fullName: auth()->user()?->name ?? 'Başvuru Sahibi',
            email: $request->email,
            description: sprintf(
                'Veribenim e-posta: %s adresinde %s pozisyonuna yaptığım başvuru ' .
                'hakkında tüm kişisel verilerime erişmek istiyorum. ' .
                'KVKK Madde 11/a kapsamında.',
                $request->email,
                $request->position ?? '(belirtilmemiş)'
            ),
        );

        return response()->json([
            'message' => 'Başvurunuz alındı. 30 gün içinde e-postanıza yanıt göndereceğiz.',
            'dsar_id' => $dsar['id'],
            'deadline' => now()->addDays(30)->format('d.m.Y'),
        ]);
    }

    /**
     * Aday: "Verilerimi sil"
     */
    public function submitErasureRequest(Request $request)
    {
        $request->validate([
            'email' => 'required|email',
            'reason' => 'nullable|in:no_longer_interested,data_deletion,other',
        ]);

        Veribenim::submitDsar(
            requestType: 'erasure',
            fullName: auth()->user()?->name ?? 'Başvuru Sahibi',
            email: $request->email,
            description: 'Başvurumla ilgili tüm kişisel verilerimin silinmesini talep ediyorum. ' .
                        'KVKK Madde 11/e kapsamında "Veri Silme Hakkı"nı kullanıyorum.'
        );

        return response()->json([
            'message' => 'Veri silme talebiniz alındı.',
            'deadline' => now()->addDays(30)->format('d.m.Y'),
        ]);
    }
}

11. Sonuç: Veribenim ile KVKK Uyumlu İşe Alım

Bu makale gösteriyor ki:

  1. CV Saklama Süresi: İşe alınamış = 2 yıl, İşe alınmış = 10 yıl (İş İlişkisi sonrası)

  2. Özel Nitelikli Veriler: Fotoğraf, sağlık, sosyal medya = Ayrı consent gerekli

  3. Sosyal Medya Araştırması: 30 gün sonra otomatik silin, stalk riski yoktur

  4. Data Sovereignty: USA-tabanlı ATS = KVKK ihlali

  5. Automated Privacy: Veribenim ile tüm anonimleştirme takvimlenmiş

  6. DSAR: Aday isterse veri erişim/silme 30 gün içinde yerine getirilir

Veribenim Compliance as a Service platformu ile, HR yöneticileri kod yazmadan, sadece sistem yapılandırması ile KVKK uyumlu kalmayı otomatikleştirebilir.


📚 Teknik Dokümantasyon: Tüm SDK referans dökümanlarına veribenim.com/documents adresinden ulaşabilirsiniz.

 

Sonraki Adım

Veribenim'in bu sektör için sunduğu çözümleri görmek ve KVKK/GDPR uyumlu pazarlama başlamak için aşağıdaki butona tıklayın.

Ücretsiz Hesap Oluştur

Benzer Makaleler

Bu sektörle ilgili daha fazla makale yakında yayımlanacak.

Tüm makaleleri görmek için bloğa dön →