import { useEffect, useState } from 'react'; import { useSearchParams } from 'react-router-dom'; import { listDocuments, downloadDocument } from '../../api/documents'; import { listCertificates } from '../../api/certificates'; import { signDocument, getSignatures, downloadSigned } from '../../api/signatures'; import { PenTool, FileText, Shield, CheckCircle, MapPin, Download } from 'lucide-react'; import toast from 'react-hot-toast'; import PasswordModal from '../../components/ui/PasswordModal'; import PdfSignaturePlacer from '../../components/ui/PdfSignaturePlacer'; import { useAuth } from '../../contexts/AuthContext'; const STEPS = ['Selección', 'Posición', 'Firmar']; export default function SignaturesPage() { const { user } = useAuth(); const [searchParams] = useSearchParams(); const [docs, setDocs] = useState([]); const [certs, setCerts] = useState([]); const [form, setForm] = useState({ documentId: searchParams.get('docId') || '', certificateId: '', reason: 'Firmado digitalmente', location: '', signatureStyle: user?.signaturePreferences?.signatureStyle || 'qr', qrPosition: user?.signaturePreferences?.qrPosition || 'left', qrBackground: user?.signaturePreferences?.qrBackground || 'transparent', qrBorder: user?.signaturePreferences?.qrBorder || 'none', signatureImageBase64: user?.signaturePreferences?.signatureImageBase64 || null, }); const [step, setStep] = useState(0); // 0=select, 1=place, 2=sign const [pdfBlob, setPdfBlob] = useState(null); const [sigPlacement, setSigPlacement] = useState(null); // { page, rect } const [signing, setSigning] = useState(false); const [result, setResult] = useState(null); const [sigs, setSigs] = useState([]); const [pendingSign, setPendingSign] = useState(null); useEffect(() => { Promise.all([ listDocuments({ limit: 100 }).then(({ data }) => setDocs(data.data.rows.filter(d => d.status !== 'signed'))), listCertificates().then(({ data }) => setCerts(data.data.filter(c => c.is_active))), ]).catch(() => toast.error('Error al cargar datos')); }, []); useEffect(() => { if (form.documentId) { getSignatures(form.documentId).then(({ data }) => setSigs(data.data || [])).catch(() => setSigs([])); } }, [form.documentId]); useEffect(() => { if (!user?.signaturePreferences) return; setForm((currentForm) => ({ ...currentForm, signatureStyle: user.signaturePreferences.signatureStyle || 'qr', qrPosition: user.signaturePreferences.qrPosition || 'left', qrBackground: user.signaturePreferences.qrBackground || 'transparent', qrBorder: user.signaturePreferences.qrBorder || 'none', signatureImageBase64: user.signaturePreferences.signatureImageBase64 || null, })); }, [user?.signaturePreferences]); const handleChange = (e) => setForm(f => ({ ...f, [e.target.name]: e.target.value })); // Step 0 → 1: load PDF for placement const handleGoToPlace = async () => { if (!form.documentId || !form.certificateId) { toast.error('Selecciona documento y certificado'); return; } try { const { data } = await downloadDocument(form.documentId); setPdfBlob(data); setSigPlacement(null); setStep(1); } catch { toast.error('No se pudo cargar el documento'); } }; // Step 1 → 2: placement confirmed const handlePlaced = (placement) => { setSigPlacement(placement); setStep(2); }; const doSign = async (certPassword = '', fromModal = false) => { if (!form.documentId || !form.certificateId) { toast.error('Faltan datos de documento o certificado. Vuelve al paso 1.'); setStep(0); return; } setSigning(true); const payload = { documentId: Number(form.documentId), certificateId: Number(form.certificateId), certPassword, reason: form.reason, location: form.location, page: sigPlacement?.page ?? 0, rect: sigPlacement?.rect ?? null, signatureStyle: form.signatureStyle, qrPosition: form.qrPosition, qrBackground: form.qrBackground, qrBorder: form.qrBorder, signatureImageBase64: form.signatureImageBase64, }; try { const { data } = await signDocument(payload); setResult(data.data); toast.success('¡Documento firmado exitosamente!'); setDocs(prev => prev.filter(d => d.id !== payload.documentId)); setForm(f => ({ ...f, documentId: '', certificateId: '' })); setSigPlacement(null); setPdfBlob(null); setStep(0); setPendingSign(null); } catch (err) { const code = err.response?.data?.code; if (code === 'CERT_PASSWORD_REQUIRED' && !fromModal) { setPendingSign(payload); } else if (code === 'CERT_PASSWORD_REQUIRED' && fromModal) { toast.error('Contraseña incorrecta. Intenta de nuevo.'); setPendingSign(payload); } else { toast.error(err.response?.data?.error || 'Error al firmar'); } } finally { setSigning(false); } }; const selectedCert = certs.find(c => c.id === Number(form.certificateId)); const selectedDoc = docs.find(d => d.id === Number(form.documentId)); return (
{pendingSign && ( doSign(pw, true)} onCancel={() => setPendingSign(null)} /> )}

Firmar Documento

Firma digital con certificado P12 — firma visible en el PDF

{/* Steps indicator */}
{STEPS.map((label, i) => (
{i < step ? '✓' : i + 1}
{label}
))}
{/* ── Step 0: Selección ── */} {step === 0 && (
{docs.length === 0 && No hay documentos pendientes.}
{certs.length === 0 && No tienes certificados activos.}

Estilo aplicado

La firma usará tu configuración guardada en el menú Estilo Firma.

Tipo: {form.signatureStyle}

{form.signatureStyle === 'qr' &&

Posición QR: {form.qrPosition === 'left' ? 'Izquierda' : 'Derecha'}

} {form.signatureStyle === 'qr' &&

Fondo: {form.qrBackground === 'transparent' ? 'Transparente' : 'Suave'}

} {form.signatureStyle === 'qr' &&

Borde: {form.qrBorder === 'none' ? 'Sin borde' : 'Con borde'}

}
{result && (

¡Firma aplicada!

Firma visible + firma criptográfica válida.

{result.signedFilename}
)} {sigs.length > 0 && (

Firmas del documento

{sigs.map(sig => (
{sig.signer_name}

{sig.signer_email}

{new Date(sig.signed_at).toLocaleString('es-EC')}
))}
)}

Proceso de firma

  1. Selecciona documento y certificado
  2. Elige dónde colocar la firma en el PDF
  3. Confirma — se pedirá contraseña si es necesario
  4. Descarga el PDF firmado con firma visible
)} {/* ── Step 1: Posicionamiento ── */} {step === 1 && pdfBlob && (

Haz clic donde quieres que aparezca la firma en {selectedDoc?.title}

)} {/* ── Step 2: Confirmar y firmar ── */} {step === 2 && (

Posición confirmada — listo para firmar

Documento: {selectedDoc?.title}

Certificado: {selectedCert?.alias}

Razón: {form.reason}

{form.location &&

Ubicación: {form.location}

}

Estilo visual: {form.signatureStyle}

{form.signatureStyle === 'qr' &&

Posición QR: {form.qrPosition === 'left' ? 'Izquierda' : 'Derecha'}

} {form.signatureStyle === 'qr' &&

Fondo: {form.qrBackground === 'transparent' ? 'Transparente' : 'Suave'}

} {form.signatureStyle === 'qr' &&

Borde: {form.qrBorder === 'none' ? 'Sin borde' : 'Con borde'}

}

Página: {(sigPlacement?.page ?? 0) + 1}

)}
); }