import { useState } from 'react' import type { DatosGenerales } from '../types' import { parsearTextoIne } from '../lib/ocrIne' import type { SugerenciasIne } from '../lib/ocrIne' type CamposAplicables = Partial> interface Props { /** Imagen (JPG/PNG) de la credencial INE ya cargada */ archivo: File /** Aplica un campo sugerido al formulario de datos generales */ onAplicar: (campos: CamposAplicables) => void } type Estado = | { fase: 'inactivo' } | { fase: 'procesando'; progreso: number } | { fase: 'listo'; sugerencias: SugerenciasIne } | { fase: 'error'; mensaje: string } interface SugerenciaVisible { clave: keyof CamposAplicables | 'sexo' etiqueta: string valor: string /** Solo estos campos tienen destino en el formulario */ aplicable: boolean } function aVisibles(s: SugerenciasIne): SugerenciaVisible[] { const v: SugerenciaVisible[] = [] if (s.curp) v.push({ clave: 'curp', etiqueta: 'CURP', valor: s.curp, aplicable: true }) if (s.nombre) v.push({ clave: 'nombreCompleto', etiqueta: 'Nombre', valor: s.nombre, aplicable: true }) if (s.fechaNacimiento) v.push({ clave: 'fechaNacimiento', etiqueta: 'Fecha de nacimiento', valor: s.fechaNacimiento, aplicable: true }) if (s.sexo) v.push({ clave: 'sexo', etiqueta: 'Sexo', valor: s.sexo === 'H' ? 'Hombre' : 'Mujer', aplicable: false }) return v } /** * OCR del INE con tesseract.js (import dinámico para no inflar el bundle). * El idioma español se sirve local desde /client/tessdata (base path /client/). */ export default function BotonOcrIne({ archivo, onAplicar }: Props) { const [estado, setEstado] = useState({ fase: 'inactivo' }) const [aplicados, setAplicados] = useState([]) const leer = async () => { setEstado({ fase: 'procesando', progreso: 0 }) setAplicados([]) try { const { createWorker } = await import('tesseract.js') const worker = await createWorker('spa', undefined, { langPath: `${import.meta.env.BASE_URL}tessdata`, gzip: false, logger: (m: { status: string; progress: number }) => { if (m.status === 'recognizing text') { setEstado({ fase: 'procesando', progreso: Math.round(m.progress * 100) }) } }, }) try { const { data } = await worker.recognize(archivo) const sugerencias = parsearTextoIne(data.text) if (aVisibles(sugerencias).length === 0) { setEstado({ fase: 'error', mensaje: 'No se detectaron datos legibles en la imagen. Intenta con una foto más nítida.', }) } else { setEstado({ fase: 'listo', sugerencias }) } } finally { await worker.terminate() } } catch { setEstado({ fase: 'error', mensaje: 'No fue posible ejecutar el OCR. Verifica tu conexión e inténtalo de nuevo.' }) } } const aplicar = (s: SugerenciaVisible) => { if (!s.aplicable) return onAplicar({ [s.clave]: s.valor } as CamposAplicables) setAplicados((prev) => [...prev, s.clave]) } return (

Lectura automática (OCR)

{estado.fase === 'procesando' && (

Reconociendo texto de la credencial… {estado.progreso}%

)} {estado.fase === 'error' && (

{estado.mensaje}

)} {estado.fase === 'listo' && (

Sugerencias detectadas — nada se sobreescribe sin tu confirmación:

    {aVisibles(estado.sugerencias).map((s) => (
  • {s.etiqueta} {s.valor} {s.aplicable && (aplicados.includes(s.clave) ? ( ✓ ) : ( ))}
  • ))}
)}
) }