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 (
Firma digital con certificado P12 — firma visible en el PDF
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'}
}Firma visible + firma criptográfica válida.
{result.signedFilename}
{sig.signer_email}
{new Date(sig.signed_at).toLocaleString('es-EC')}Haz clic donde quieres que aparezca la firma en {selectedDoc?.title}
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}