'use client'; import { useState } from 'react'; import { Header } from '@/components/layouts/header'; import { Card, CardContent, CardDescription, CardHeader, CardTitle, Button, Input, Label } from '@horux/shared-ui'; import { useTimbres } from '@/lib/hooks/use-facturacion'; import { apiClient } from '@/lib/api/client'; import { useQuery, useQueryClient } from '@tanstack/react-query'; import { useContribuyenteStore } from '@/stores/contribuyente-store'; import { Shield, Upload, Check, AlertCircle, Receipt, Palette, Image, Building2, FileText } from 'lucide-react'; function CustomizationSection() { const queryClient = useQueryClient(); const [logoUploading, setLogoUploading] = useState(false); const [colorSaving, setColorSaving] = useState(false); const [color, setColor] = useState('#75A4FF'); const [msg, setMsg] = useState<{ type: 'success' | 'error'; text: string } | null>(null); const { data: customization } = useQuery({ queryKey: ['facturapi-customization'], queryFn: () => apiClient.get('/facturacion/customization').then(r => r.data), }); useState(() => { if (customization?.color) setColor(`#${customization.color}`); }); const handleLogoUpload = async (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file) return; // Validar tipo y tamaño if (!file.type.startsWith('image/')) { setMsg({ type: 'error', text: 'Solo se permiten imágenes (PNG, JPG)' }); return; } if (file.size > 2 * 1024 * 1024) { setMsg({ type: 'error', text: 'El logo no debe superar 2MB' }); return; } setLogoUploading(true); setMsg(null); const reader = new FileReader(); reader.onload = async () => { const base64 = (reader.result as string).split(',')[1]; try { await apiClient.post('/facturacion/logo', { logo: base64 }); queryClient.invalidateQueries({ queryKey: ['facturapi-customization'] }); setMsg({ type: 'success', text: 'Logo subido correctamente' }); } catch { setMsg({ type: 'error', text: 'Error al subir logo' }); } finally { setLogoUploading(false); } }; reader.readAsDataURL(file); }; const handleColorSave = async () => { setColorSaving(true); setMsg(null); try { await apiClient.put('/facturacion/color', { color: color.replace('#', '') }); queryClient.invalidateQueries({ queryKey: ['facturapi-customization'] }); setMsg({ type: 'success', text: 'Color actualizado' }); } catch { setMsg({ type: 'error', text: 'Error al actualizar color' }); } finally { setColorSaving(false); } }; return ( Personalización de Factura Logo y color que aparecerán en los PDFs de tus facturas {/* Logo */}
{customization?.logoUrl && ( Logo )}

PNG o JPG, máximo 2MB. Recomendado: fondo transparente, 400x400px

{/* Color */}
setColor(e.target.value)} className="h-10 w-14 rounded cursor-pointer border" /> setColor(e.target.value)} placeholder="#75A4FF" className="w-32 font-mono" maxLength={7} />
{/* Mensaje */} {msg && (
{msg.text}
)} ); } export default function CsdConfigPage() { const { selectedContribuyenteId, selectedContribuyenteRfc, selectedContribuyenteNombre } = useContribuyenteStore(); const { data: orgStatus, isLoading } = useQuery({ queryKey: ['facturapi-org-contrib', selectedContribuyenteId], queryFn: () => selectedContribuyenteId ? apiClient.get(`/contribuyentes/${selectedContribuyenteId}/facturapi/status`).then(r => r.data) : apiClient.get('/facturacion/org/status').then(r => r.data), }); const { data: timbres } = useTimbres(); const queryClient = useQueryClient(); const [uploading, setUploading] = useState(false); const [cerFile, setCerFile] = useState(''); const [keyFile, setKeyFile] = useState(''); const [password, setPassword] = useState(''); const [message, setMessage] = useState<{ type: 'success' | 'error'; text: string } | null>(null); const handleFileChange = (setter: (v: string) => void) => (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file) return; const reader = new FileReader(); reader.onload = () => { const base64 = (reader.result as string).split(',')[1]; setter(base64); }; reader.readAsDataURL(file); }; const handleCreateOrg = async () => { try { if (selectedContribuyenteId) { await apiClient.post(`/contribuyentes/${selectedContribuyenteId}/facturapi/org`); } else { await apiClient.post('/facturacion/org'); } queryClient.invalidateQueries({ queryKey: ['facturapi-org-contrib'] }); setMessage({ type: 'success', text: 'Organización creada en Facturapi' }); } catch (err: any) { setMessage({ type: 'error', text: err.response?.data?.message || 'Error al crear organización' }); } }; const handleUploadCsd = async (e: React.FormEvent) => { e.preventDefault(); if (!cerFile || !keyFile || !password) { setMessage({ type: 'error', text: 'Todos los campos son requeridos' }); return; } setUploading(true); setMessage(null); try { if (selectedContribuyenteId) { await apiClient.post(`/contribuyentes/${selectedContribuyenteId}/facturapi/csd`, { cerFile, keyFile, password }); } else { await apiClient.post('/facturacion/csd', { cerFile, keyFile, password }); } queryClient.invalidateQueries({ queryKey: ['facturapi-org-contrib'] }); setMessage({ type: 'success', text: 'CSD subido correctamente. Ya puedes emitir facturas.' }); setCerFile(''); setKeyFile(''); setPassword(''); } catch (err: any) { setMessage({ type: 'error', text: err.response?.data?.message || 'Error al subir CSD' }); } finally { setUploading(false); } }; if (isLoading) { return ( <>

Cargando...

); } return ( <>
{/* Show which contribuyente or prompt to select */} {!selectedContribuyenteId && (

Selecciona un contribuyente en el header para ver y configurar su CSD.

)} {selectedContribuyenteId && ( CSD de: {selectedContribuyenteNombre} {selectedContribuyenteRfc} )} {/* Estado de la organización */} {selectedContribuyenteId && Organización Facturapi Necesaria para emitir facturas electrónicas (CFDI) {!orgStatus?.configured ? (

No hay organización configurada para este tenant.

) : (
ID Organización {orgStatus.orgId}
CSD {orgStatus.hasCsd ? '✓ Configurado' : '✗ Pendiente'}
)}
} {/* Subir CSD */} {selectedContribuyenteId && orgStatus?.configured && !orgStatus.hasCsd && ( Subir Certificado de Sello Digital (CSD) El CSD es diferente a la FIEL. Se usa exclusivamente para timbrar facturas.
setPassword(e.target.value)} required />
)} {/* CSD ya configurado */} {selectedContribuyenteId && orgStatus?.configured && orgStatus.hasCsd && (

CSD Configurado

Tu Certificado de Sello Digital está activo. Puedes emitir facturas.

)} {/* Carta Manifiesto */} {selectedContribuyenteId && orgStatus?.configured && ( Carta Manifiesto Firma requerida por el SAT/RMF para emitir CFDI. Usa tu e.firma (FIEL).