Talento Rosimar: leer hojas de vida en el navegador
Un sistema de talento humano para una distribuidora de alimentos. La parte difícil es el lector: convierte PDF, Word, escaneos y fotos en formularios que una persona solo tiene que revisar.
- Cliente
- Distribuciones Rosimar S.A.S., Barranquilla
- Mi parte
- Desarrollo del sistema, en un equipo de dos. 50 de los 119 commits del repositorio1 son míos.
- Periodo
- 2026, en curso
- Stack
- React, TypeScript, pdf.js, Tesseract.js, mammoth.js, Express, MariaDB
- Código
- Privado
El problema
Rosimar recibe hojas de vida en todos los formatos: PDF con texto, escaneos, fotos tomadas con el celular y documentos de Word. Pasar a mano nombres, contacto, experiencia y estudios es lento y se presta a errores. El sistema tenía que leer esos documentos sin pagar por cada uno y sin depender de un modelo de lenguaje externo.
También tenía que seguir funcionando sin conexión. Por eso el reconocimiento de texto corre dentro del navegador (es una PWA) y los archivos de Tesseract se sirven desde la propia aplicación, no desde un CDN.
Cómo funciona
El lector es un proceso determinista de cuatro etapas. No usa ningún modelo de lenguaje y no tiene costo por documento.
- 1
Extracción
pdf.js para PDF con texto, Tesseract.js (WASM) para fotos y escaneos, mammoth.js para Word.
- 2
Maquetación
Reconstruye renglones y detecta columnas por el canal vertical vacío, para no mezclar una barra lateral con el contenido.
- 3
Segmentación
Reconoce encabezados con un léxico en español e inglés tolerante a errores de OCR. Si no hay títulos, clasifica por contenido.
- 4
Extractores
Uno por campo: nombres, contacto, documento, fechas, experiencia, estudios, idiomas. Usan los municipios del DANE y un diccionario de cargos.
Lo que medí y lo que no
La primera cifra que tuve fue 165 de 165 campos correctos2. Era cierta, pero engañosa: ese banco son diez PDF generados por código, todos con capa de texto perfecta, y esa ruta no es la que usa Rosimar. Lo que llega de verdad son escaneos y fotos, y esa ruta no estaba medida.
Armé un segundo banco con 40 hojas de vida colombianas sintéticas convertidas en escaneos degradados (ruido, inclinación, poca luz, recompresión), un tercero con 17 fotos reales y un cuarto con 12 contratos. Última medición documentada: 30 de septiembre de 2026.
| Banco | Documentos | Resultado |
|---|---|---|
| PDF digitales, con capa de texto | 10 | 165 de 165 campos2 |
| Escaneos sintéticos | 40 | 89,8 %3 (63,4 %4 en la primera medición) |
| Contratos | 12 | 90,0 %3 |
| Fotos reales | 17 | 75,9 %3 |
De las 17 fotos, solo 8 son hojas de vida; el resto son contratos, certificados y formularios manuscritos.
5/14
Regresión del clasificador: 19,6 %
Seis de cada nueve hojas de vida salían como documento no estructurado y llegaban vacías al formulario.
Datos de la gráfica
| # | Precisión global (%) | % |
|---|---|---|
| 1 | Primera medición | 63,4 % |
| 2 | Umbral local (Sauvola) | 68,5 % |
| 3 | Detección de polaridad | 72,5 % |
| 4 | Reconstrucción del correo | 73,9 % |
| 5 | Regresión del clasificador | 19,6 % |
| 6 | Clasificador corregido | 64,1 % |
| 7 | Preprocesado por texto | 68,4 % |
| 8 | Teléfono por fragmento | 69,1 % |
| 9 | Detección de orientación | 73,3 % |
| 10 | Hojas sin encabezados | 75,5 % |
| 11 | Cuarta tanda | 76,0 % |
| 12 | Sexta tanda | 75,8 % |
| 13 | Decimosexta, base | 78,2 % |
| 14 | Decimosexta, final | 89,8 % |
Lo que apareció al medir la ruta real
- pdf.js 6 usa una función de JavaScript que Chrome 141 y anteriores no tienen, así que los escaneos no se podían leer. Se resolvió con un respaldo de doce líneas.
- El umbral global de Otsu borraba los escaneos difíciles. Con un umbral local de Sauvola, un documento pasó de 8 a 2.528 caracteres leídos4.
- Tesseract lee mal la arroba: 20 de los 40 documentos4 se quedaban sin correo hasta que se reconstruyó el dato.
- Un cambio en el clasificador de documentos hizo caer la precisión global de 73,9 % a 19,6 %4. Se detectó al volver a correr el banco.
- Un promedio de 83 % de similitud en los nombres de las fotos escondía que solo dos de las 175 tenían nombre que comprobar, y en esas dos el lector fallaba. Después se corrigió.
Límites
- Las fotos reales (75,9 %) siguen lejos de los escaneos sintéticos (89,8 %3).
- Los formularios manuscritos no se leen bien. Hay un piloto con TrOCR que todavía no se probó contra ese banco.
- En un portátil con Ryzen 7 cada documento tarda entre 16 segundos (fotos) y 58 segundos (contratos)6.
- Los escaneos son sintéticos: se parecen a los reales, pero no los reemplazan.
Me quedo con una idea de este proyecto: un 100 % sobre el banco equivocado es peor que un 75,9 % honesto, porque el primero dice que no hay nada que arreglar.
Fuentes de las cifras
- 1
Autoría de los commits de cv-parser
git shortlog de rosimar1/cv-parser (privado) · 9 de octubre de 2026
Fabian Gonzalez (mi usuario de git) 50, otro desarrollador del equipo 41, Claude 27 e ImgBot 1: 119 en total.
- 2
Banco de precisión del lector sobre PDF digitales
rosimar1/cv-parser, README y docs/MEDICION_LECTOR.md (privado) · agosto de 2026
Son diez PDF generados con jsPDF, todos con capa de texto perfecta: esta medición no pasa por OCR. Se compara contra 165 campos de una verdad de referencia versionada.
- 3
Medición del lector sobre escaneos, contratos y fotos reales
rosimar1/cv-parser, docs/MEDICION_LECTOR.md, decimosexta tanda (privado) · 30 de septiembre de 2026
El 89,8 % de los escaneos se midió con lecturas en paralelo que después se descartaron; en contratos, la versión en serie dio 90,0 % y la paralela 89,9 %. De las 17 fotos reales, solo 8 son hojas de vida.
- 4
Evolución de la precisión sobre 40 escaneos sintéticos
rosimar1/cv-parser, docs/MEDICION_LECTOR.md, sección Resultado (privado) · agosto y septiembre de 2026
Incluye la regresión del clasificador (19,6 %) y los hitos posteriores del mismo banco. Los caracteres leídos (8 y 2.528) son del documento CV_04, perfil duro. Los 20 documentos sin correo salen de la sección sobre la arroba.
- 5
Espejismo del promedio de nombres en las fotos
rosimar1/cv-parser, docs/MEDICION_LECTOR.md, sexta tanda (privado) · septiembre de 2026
Solo 2 de los 17 documentos declaran nombres en su verdad de referencia; en esas dos el lector fallaba (0 % y 20 % de similitud) hasta que se corrigió.
- 6
Tiempo por documento
rosimar1/cv-parser, docs/MEDICION_LECTOR.md, decimosexta tanda (privado) · 30 de septiembre de 2026
15,7 s por foto y 57,8 s por contrato, en un portátil con Ryzen 7 5700U. Con el mismo código, un contrato tardó 48,8 s por la mañana y 56,9 s por la tarde.