Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f0ef20c41f | |||
| c252573824 |
+1
-1
@@ -273,7 +273,7 @@ body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.front-photo-frame .placeholder-svg {
|
||||
|
||||
+8
-74
@@ -46,12 +46,8 @@ export default function IdCardGenerator() {
|
||||
const [employeeId, setEmployeeId] = useState('');
|
||||
const [photoSrc, setPhotoSrc] = useState('');
|
||||
const [photoFile, setPhotoFile] = useState(null);
|
||||
const [photoProcessing, setPhotoProcessing] = useState(false);
|
||||
const [downloadStatus, setDownloadStatus] = useState({ processing: false, text: '⬇ Descargar' });
|
||||
|
||||
// Bloquea el botón Descargar hasta que nombre, puesto e ID estén llenos
|
||||
const isFormComplete = name.trim() && role.trim() && employeeId.trim();
|
||||
|
||||
// --- Estados Lote / Excel Masivo ───
|
||||
const [bulkEmployees, setBulkEmployees] = useState([]);
|
||||
const [bulkStatusText, setBulkStatusText] = useState('');
|
||||
@@ -72,65 +68,14 @@ export default function IdCardGenerator() {
|
||||
}
|
||||
}, [employeeId]);
|
||||
|
||||
const handlePhotoUpload = async (e) => {
|
||||
const handlePhotoUpload = (e) => {
|
||||
const file = e.target.files[0];
|
||||
if (!file) return;
|
||||
|
||||
if (file) {
|
||||
setPhotoFile(file);
|
||||
setPhotoSrc(URL.createObjectURL(file));
|
||||
setPhotoProcessing(true);
|
||||
|
||||
try {
|
||||
const editedBlob = await processPhotoWithAI(file);
|
||||
const editedUrl = URL.createObjectURL(editedBlob);
|
||||
setPhotoSrc(editedUrl);
|
||||
setPhotoFile(new File([editedBlob], file.name, { type: editedBlob.type || 'image/jpeg' }));
|
||||
} catch (error) {
|
||||
console.error('Error al procesar la foto con IA en n8n:', error);
|
||||
alert('No se pudo procesar la foto con IA. Se usará la foto original para la vista previa.');
|
||||
} finally {
|
||||
setPhotoProcessing(false);
|
||||
}
|
||||
};
|
||||
|
||||
const processPhotoWithAI = async (file) => {
|
||||
const N8N_WEBHOOK_URL = import.meta.env.VITE_N8N_WEBHOOK_URL;
|
||||
const TOKEN_SECRETO = import.meta.env.VITE_WEBHOOK_TOKEN;
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('edited', file, file.name);
|
||||
formData.append('name', name);
|
||||
formData.append('role', role);
|
||||
formData.append('employeeId', employeeId);
|
||||
formData.append('fechaProcesado', new Date().toISOString());
|
||||
|
||||
const response = await fetch(N8N_WEBHOOK_URL, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': TOKEN_SECRETO
|
||||
},
|
||||
body: formData
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`El servidor de n8n rechazó la petición (Código: ${response.status})`);
|
||||
}
|
||||
|
||||
const contentType = response.headers.get('content-type') || '';
|
||||
if (contentType.includes('application/json')) {
|
||||
const data = await response.json();
|
||||
const bin = data.edited || data.data || data.foto_processed_base64 || data.photo || data.binary || (Array.isArray(data) && (data[0]?.edited || data[0]?.data));
|
||||
const blobUrl = convertBinaryToBlobUrl(bin);
|
||||
if (!blobUrl) throw new Error('n8n no devolvió un binario válido.');
|
||||
const blob = await (await fetch(blobUrl)).blob();
|
||||
return blob;
|
||||
}
|
||||
|
||||
const blob = await response.blob();
|
||||
if (!blob || blob.size === 0) throw new Error('n8n devolvió un binario vacío.');
|
||||
return blob;
|
||||
};
|
||||
|
||||
const handleEmployeeIdChange = (e) => {
|
||||
const rawValue = e.target.value;
|
||||
const cleanValue = rawValue.replace(/[-\s]/g, '');
|
||||
@@ -272,7 +217,7 @@ export default function IdCardGenerator() {
|
||||
if (bulkEmployees.length === 0) return;
|
||||
|
||||
setBulkDownloadStatus({ processing: true, text: '🔄 Descargando...' });
|
||||
setBulkStatusText('Descargando retratos...');
|
||||
setBulkStatusText('Conectando con n8n y descargando retratos optimizados por IA...');
|
||||
|
||||
try {
|
||||
const resultadosN8n = await enviarDatosAn8n(bulkEmployees);
|
||||
@@ -308,7 +253,7 @@ export default function IdCardGenerator() {
|
||||
}
|
||||
});
|
||||
|
||||
setBulkStatusText('🎉 ¡Lote de carnets guardado correctamente!');
|
||||
setBulkStatusText('🎉 ¡Lote de carnets guardado correctamente y registrado en Supabase!');
|
||||
setBulkEmployees([]);
|
||||
|
||||
} catch (error) {
|
||||
@@ -449,8 +394,8 @@ export default function IdCardGenerator() {
|
||||
<label>Puesto</label>
|
||||
<input type="text" placeholder="Ej: Mercaderista" value={role} onChange={(e) => setRole(e.target.value)} />
|
||||
</div>
|
||||
<div className="upload-btn" onClick={() => !photoProcessing && fileInputRef.current.click()} style={{ opacity: photoProcessing ? 0.6 : 1, cursor: photoProcessing ? 'wait' : 'pointer' }}>
|
||||
{photoProcessing ? '⏳ Procesando foto con IA...' : 'Subir foto del colaborador'}
|
||||
<div className="upload-btn" onClick={() => fileInputRef.current.click()}>
|
||||
Subir foto del colaborador
|
||||
</div>
|
||||
<input type="file" accept="image/*" style={{ display: 'none' }} ref={fileInputRef} onChange={handlePhotoUpload} />
|
||||
</div>
|
||||
@@ -502,7 +447,7 @@ export default function IdCardGenerator() {
|
||||
|
||||
<div className="panel-section">
|
||||
<div className="panel-section-title">Exportar</div>
|
||||
<button className="export-btn blue" disabled={downloadStatus.processing || !isFormComplete} onClick={triggerDownload} style={{ opacity: (!isFormComplete || downloadStatus.processing) ? 0.6 : 1 }}>
|
||||
<button className="export-btn blue" disabled={downloadStatus.processing} onClick={triggerDownload}>
|
||||
{downloadStatus.text}
|
||||
</button>
|
||||
</div>
|
||||
@@ -516,18 +461,7 @@ export default function IdCardGenerator() {
|
||||
<div className="front-photo-frame" style={{ position: 'relative', overflow: 'hidden' }}>
|
||||
{photoSrc ? (
|
||||
<img src={photoSrc} alt="Foto Colaborador" style={{ width: '100%', height: '100%', objectFit: 'cover', display: 'block' }} />
|
||||
) : (null)}
|
||||
{photoProcessing && (
|
||||
<div style={{
|
||||
position: 'absolute', inset: 0, display: 'flex',
|
||||
alignItems: 'center', justifyContent: 'center',
|
||||
background: 'rgba(0,0,0,0.45)', color: '#fff',
|
||||
fontSize: '13px', fontWeight: '600', textAlign: 'center', padding: '8px'
|
||||
}}>
|
||||
⏳ Editando foto con IA...
|
||||
</div>
|
||||
)}
|
||||
{!photoSrc && (
|
||||
) : (
|
||||
<svg className="placeholder-svg" viewBox="0 0 28 28" fill="none" style={{ width: '60%', height: '60%', opacity: 0.4 }}>
|
||||
<circle cx="14" cy="10" r="5.5" stroke="currentColor" strokeWidth="1.5" />
|
||||
<path d="M3 24c0-6 22-6 22 0" stroke="currentColor" strokeWidth="1.5" />
|
||||
|
||||
@@ -221,7 +221,7 @@ export async function downloadIDCards(config) {
|
||||
`;
|
||||
|
||||
if (photoSrc) {
|
||||
photoTagHtml = `<div style="width:100%; height:100%; background-image:url('${photoSrc}'); background-size:cover; background-position:center; background-repeat:no-repeat;"></div>`;
|
||||
photoTagHtml = `<img src="${photoSrc}" crossorigin="anonymous" style="width:100%; height:100%; object-fit:cover; display:block;" />`;
|
||||
}
|
||||
|
||||
vFront.innerHTML = `
|
||||
@@ -356,7 +356,7 @@ export async function downloadBulkIDCards({ employees, baseUrl, onStateChange, o
|
||||
`;
|
||||
|
||||
if (validatedPhotoUrl) {
|
||||
photoTagHtml = `<div style="width:100%; height:100%; background-image:url('${validatedPhotoUrl}'); background-size:cover; background-position:center; background-repeat:no-repeat;"></div>`;
|
||||
photoTagHtml = `<img src="${validatedPhotoUrl}" crossorigin="anonymous" style="width:100%; height:100%; object-fit:cover; display:block;" />`;
|
||||
}
|
||||
|
||||
vFront.innerHTML = `
|
||||
|
||||
Reference in New Issue
Block a user