Files
HoruxDespachos/apps/web/components/sat/FielUploadModal.tsx
2026-04-27 22:09:36 -06:00

139 lines
4.3 KiB
TypeScript

'use client';
import { useState, useCallback } from 'react';
import { Button, Input, Label, Card, CardContent, CardDescription, CardHeader, CardTitle } from '@horux/shared-ui';
import { uploadFiel } from '@/lib/api/fiel';
import type { FielStatus } from '@horux/shared';
interface FielUploadModalProps {
onSuccess: (status: FielStatus) => void;
onClose: () => void;
contribuyenteId?: string | null;
}
export function FielUploadModal({ onSuccess, onClose, contribuyenteId }: FielUploadModalProps) {
const [cerFile, setCerFile] = useState<File | null>(null);
const [keyFile, setKeyFile] = useState<File | null>(null);
const [password, setPassword] = useState('');
const [error, setError] = useState('');
const [loading, setLoading] = useState(false);
const fileToBase64 = (file: File): Promise<string> => {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => {
const result = reader.result as string;
// Remove data URL prefix (e.g., "data:application/x-x509-ca-cert;base64,")
const base64 = result.split(',')[1];
resolve(base64);
};
reader.onerror = reject;
});
};
const handleSubmit = useCallback(async (e: React.FormEvent) => {
e.preventDefault();
setError('');
if (!cerFile || !keyFile || !password) {
setError('Todos los campos son requeridos');
return;
}
setLoading(true);
try {
const cerBase64 = await fileToBase64(cerFile);
const keyBase64 = await fileToBase64(keyFile);
const result = await uploadFiel({
cerFile: cerBase64,
keyFile: keyBase64,
password,
}, contribuyenteId);
if (result.status) {
onSuccess(result.status);
}
} catch (err: any) {
const msg = err.response?.data?.message || err.response?.data?.error || err.message || 'Error al subir la FIEL';
console.error('[FIEL Upload Frontend]', msg, err.response?.data);
setError(msg);
} finally {
setLoading(false);
}
}, [cerFile, keyFile, password, onSuccess]);
return (
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
<Card className="w-full max-w-md mx-4">
<CardHeader>
<CardTitle>Configurar FIEL (e.firma)</CardTitle>
<CardDescription>
Sube tu certificado y llave privada para sincronizar CFDIs con el SAT
</CardDescription>
</CardHeader>
<CardContent>
<form onSubmit={handleSubmit} className="space-y-4">
<div className="space-y-2">
<Label htmlFor="cer">Certificado (.cer)</Label>
<Input
id="cer"
type="file"
accept=".cer"
onChange={(e) => setCerFile(e.target.files?.[0] || null)}
className="cursor-pointer"
/>
</div>
<div className="space-y-2">
<Label htmlFor="key">Llave Privada (.key)</Label>
<Input
id="key"
type="file"
accept=".key"
onChange={(e) => setKeyFile(e.target.files?.[0] || null)}
className="cursor-pointer"
/>
</div>
<div className="space-y-2">
<Label htmlFor="password">Contrasena de la llave</Label>
<Input
id="password"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Ingresa la contrasena de tu FIEL"
/>
</div>
{error && (
<p className="text-sm text-red-500">{error}</p>
)}
<div className="flex gap-3 pt-4">
<Button
type="button"
variant="outline"
onClick={onClose}
className="flex-1"
>
Cancelar
</Button>
<Button
type="submit"
disabled={loading}
className="flex-1"
>
{loading ? 'Subiendo...' : 'Configurar FIEL'}
</Button>
</div>
</form>
</CardContent>
</Card>
</div>
);
}