import React, { useEffect, useMemo, useState } from 'react'; import { AlertTriangle, Send, X } from 'lucide-react'; import { CountryConfig, PeriodRange, ReconciliationRow } from '../types'; import { supabase } from '../lib/supabase'; import { ensureFreshSupabaseSession } from '../lib/sessionHealth'; const REPORT_BAMBOO_URL = (import.meta.env.VITE_REPORTAR_BAMBOO_URL as string | undefined) || ''; interface BambooCorrectionModalProps { row: ReconciliationRow; country: CountryConfig; periodRange: PeriodRange; operatorName: string; operatorEmail: string; executionId?: string; reportUrl?: string; onClose: () => void; onSuccess: (rowId: string) => void; onShowToast: (message: string, type: 'success' | 'error' | 'info') => void; } const REASONS = [ 'Nombre mal escrito en BambooHR', 'Apodo o nombre de uso', 'Nombre incompleto / nombres adicionales', 'Otro', ]; function parseJsonSafely(text: string) { try { return text ? JSON.parse(text) : null; } catch { return null; } } export default function BambooCorrectionModal({ row, country, periodRange, operatorName, operatorEmail, executionId, reportUrl, onClose, onSuccess, onShowToast, }: BambooCorrectionModalProps) { const suggested = row.bestBambooCandidate; const [bambooName, setBambooName] = useState(suggested?.employeeName || ''); const [employeeNumber, setEmployeeNumber] = useState(suggested?.employeeNumber || ''); const [reason, setReason] = useState(REASONS[0]); const [comment, setComment] = useState(''); const [confirmed, setConfirmed] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false); useEffect(() => { setBambooName(suggested?.employeeName || ''); setEmployeeNumber(suggested?.employeeNumber || ''); setReason(REASONS[0]); setComment(''); setConfirmed(false); }, [row.id, suggested?.employeeName, suggested?.employeeNumber]); const recipientLabel = useMemo(() => { if (country.code === 'TT') return 'Laurele Nicome-Jack'; if (country.code === 'PA') return 'Delarys Peñalba y Ariadne Cortéz'; return 'Pablo Gamboa Cú, Sara Meyer Lima y María Morales Adler'; }, [country.code]); const sourceNames = useMemo( () => Array.from( new Set( [row.employee, row.bankNameFile, row.bankAccountHolder] .map((value) => String(value || '').trim()) .filter(Boolean), ), ), [row.employee, row.bankNameFile, row.bankAccountHolder], ); const handleSubmit = async () => { if (!REPORT_BAMBOO_URL) { onShowToast('Falta configurar VITE_REPORTAR_BAMBOO_URL.', 'error'); return; } if (!bambooName.trim()) { onShowToast('Escribe el nombre correcto del empleado en BambooHR.', 'error'); return; } if (!confirmed) { onShowToast('Confirma que se trata de la misma persona antes de enviar el caso.', 'error'); return; } if (!supabase) { onShowToast('Supabase no está configurado en esta aplicación.', 'error'); return; } setIsSubmitting(true); try { const session = await ensureFreshSupabaseSession(300); const accessToken = session?.access_token; if (!accessToken) { throw new Error('Tu sesión expiró. Inicia sesión nuevamente antes de reportar el caso.'); } const correctionPayload = { country: country.code, country_name: country.name, source_name: row.employee, source_names: sourceNames, bank_name_file: row.bankNameFile || '', bank_account_holder: row.bankAccountHolder || '', bank_account: row.account || '', bank_amount: row.amountBank, currency: country.currency, bamboo_name: bambooName.trim(), bamboo_employee_number: employeeNumber.trim(), reason, comment: comment.trim(), confirmed_same_person: true, execution_id: executionId || '', report_url: reportUrl || '', period_label: periodRange.label, period_start: periodRange.start, period_end: periodRange.end, requested_by_name: operatorName, requested_by_email: operatorEmail, }; // FormData evita una preflight CORS innecesaria al llamar el webhook de n8n. // El JWT sigue viajando únicamente por HTTPS y n8n lo valida contra Supabase. const formData = new FormData(); formData.append('payload', JSON.stringify(correctionPayload)); formData.append('access_token', accessToken); const response = await fetch(REPORT_BAMBOO_URL, { method: 'POST', body: formData, }); const responseText = await response.text(); const payload = parseJsonSafely(responseText); if (!response.ok || payload?.ok === false) { const message = payload?.message || payload?.error || payload?.errors?.[0] || responseText || 'No se pudo enviar el caso a RRHH.'; throw new Error(String(message)); } onSuccess(row.id); onShowToast( `Caso enviado a RRHH de ${country.name}. La equivalencia quedó registrada para futuros cruces.`, 'success', ); onClose(); } catch (error) { const message = error instanceof Error ? error.message : 'No se pudo enviar el caso a RRHH.'; onShowToast(message, 'error'); } finally { setIsSubmitting(false); } }; return (

Nombre detectado

{row.employee}

Cuenta bancaria

{row.account || '—'}

Se notificará a

{recipientLabel}

{suggested?.employeeName && (
Candidato sugerido por el cruce:{' '} {suggested.employeeName} {suggested.employeeNumber ? ` · Employee Number ${suggested.employeeNumber}` : ''}. Verifícalo manualmente antes de confirmar.
)}