Inteligencia Artificial · HR Tech · Argentina

IA que transforma el reclutamiento
en el sector energético

Pipeline de análisis automático de CVs para la Cámara de Estaciones de Servicio y Garajes de Rosario. De 15 minutos de lectura manual a 45 segundos de análisis inteligente por postulante.

-94%
tiempo de screening por CV
<45s
análisis completo por postulación
×8.8
capacidad RRHH por analista
8:1
ROI estimado año 1

Una cámara sectorial que adoptó IA para escalar su operación

CESGAR conecta postulantes con empresas del sector energético-automotriz de Rosario. Con decenas de CVs por semana y criterios de selección complejos, la automatización era imperativa.

El sistema centraliza todas las operaciones de CESGAR: gestión de asociados, noticias, trámites, beneficios y — el componente más innovador — una bolsa de trabajo con screening automático de CVs por IA.

Cuando un postulante sube su CV al formulario web, una señal Django dispara una tarea Celery asíncrona que extrae texto del PDF, lo analiza con un LLM y aplica reglas determinísticas de negocio en Python para generar un puntaje objetivo de 1 a 5, con justificación textual lista para el equipo de RRHH.

El componente de mensajería masiva por WhatsApp complementa el sistema, permitiendo comunicaciones institucionales con alta tasa de apertura (87%) en reemplazo del email tradicional.

Estaciones de servicio Garajes HR Tech WhatsApp Business
# Arquitectura del pipeline de IA

Formulario web
   │ postulante sube CV
   ▼
Signal post_save (Django)
   │ dispara tarea async
   ▼
Celery Worker
   │
   ├─ PDFProcessor
   │    └─ OCR si es escaneado
   │
   ├─ DeepSeek LLM
   │    └─ extracción JSON estructurada
   │    └─ evaluación holística 1-5
   │
   └─ cv_scoring.py (Python puro)
        └─ reglas determinísticas
        └─ descartes automáticos
        └─ bonificaciones por sector

Panel Admin RRHH
   └─ puntaje + reporte + ranking

Tres módulos de inteligencia aplicada

Cada componente resuelve un problema concreto del flujo operativo de la cámara.

Extracción estructurada de CVs

DeepSeek LLM analiza el PDF del postulante y extrae campos clave: fecha de nacimiento, domicilio, nivel educativo, experiencias laborales con duración, disponibilidad horaria y capacitaciones. Incluye fallback OCR para documentos escaneados.

DeepSeek · OCR · JSON

Scoring híbrido LLM + Python

El LLM asigna una valoración cualitativa holística. Python aplica reglas determinísticas: descarte automático, 5 módulos ponderados, bonos por sector y parámetros configurables por empresa. Resultado: puntaje 1–5 auditable.

Determinístico · Auditable · Configurable

Mensajería masiva WhatsApp

Plantillas aprobadas por Meta con editor visual, variables personalizables y vista previa en tiempo real. Envío asíncrono a contactos etiquetados con tracking individual por mensaje. Tipos: texto, call-to-action y quick reply.

Twilio · Celery · Tracking en tiempo real

Parámetros por empresa

Cada estación asociada puede especificar criterios propios: zona geográfica, rango etario, experiencia excluyente (debe tener) o prohibida (no debe tener). El mismo motor de scoring produce resultados adaptados a cada contexto.

Multi-tenant · Sin código

Panel RRHH con ranking

El equipo ve candidatos ya puntuados y clasificados, con resumen textual generado por el LLM en lenguaje natural. Acceso al detalle por módulo para auditar cualquier puntaje. Reprocesamiento con un clic.

Django Unfold · Admin personalizado

Descarte con garantía

Las reglas de descarte automático se ejecutan en Python, no en el LLM. Garantía del 100% de consistencia: la alucinación del modelo no puede afectar una decisión de descarte.

Cero alucinaciones en descartes

Stack, decisiones de diseño y código

Arquitectura orientada a costo-eficiencia, confiabilidad y mantenibilidad para un equipo pequeño.

Django 5.1 + Python 3.13Admin incorporado elimina backoffice custom. ORM + señales para desacoplar.
Celery + RedisProcesamiento async de CVs sin bloquear el ciclo HTTP. Cancelación en tiempo real.
DeepSeek APILLM 10–15× más barato que GPT-4 con calidad comparable para extracción en español.
Cloudflare R2Almacenamiento de CVs sin egress fees. Crítico a escala vs. AWS S3.
Twilio Content APIAbstracción oficial para plantillas Meta aprobadas. Evita baneos de cuenta.
PostgreSQL + JSONBPersistencia principal. JSONB para reportes y variables de plantillas.
# Señal que desacopla el guardado del análisis con IA
# El formulario web responde inmediatamente; el análisis ocurre en background

@receiver(post_save, sender=SolicitudEmpleoSimple)
def al_guardar_solicitud(sender, instancia, creado, **kwargs):
    if creado and instancia.cv_r2:
        analizar_cv.delay(instancia.id)  # no bloquea el request HTTP

@shared_task
def analizar_cv(id_solicitud: int, **parametros):
    solicitud = SolicitudEmpleoSimple.objects.get(id=id_solicitud)
    # Leer CV desde R2 con BytesIO (evita fallas en contexto Celery)
    buffer = BytesIO()
    with solicitud.cv_r2.open("rb") as archivo:
        buffer.write(archivo.read())
    buffer.seek(0)
    datos = procesador.procesar_pdf(buffer, "solicitud_simple")
    puntaje, reporte = calcular_puntaje(datos, **parametros)
    solicitud.puntaje = puntaje
    solicitud.reporte = reporte
    solicitud.save()
# Módulo de educación — patrón aplicado a los 5 módulos
# Retorno -1 señaliza descarte automático al orquestador

def _modulo_educacion(educacion: dict | None) -> tuple[float, dict]:
    detalle = {"puntaje": 0, "peso": PESO_EDUCACION}
    if not educacion:
        return 0.0, detalle
    nivel  = educacion.get("nivel", "")
    estado = educacion.get("estado", "")
    if nivel == "primario" or (nivel == "secundario" and estado != "completo"):
        return -1.0, detalle  # descarte: no puede compensarse
    puntaje = 100 if estado == "completo" else 60
    return puntaje * PESO_EDUCACION, {**detalle, "puntaje": puntaje}

def calcular_puntaje(datos: dict, **params) -> tuple[int, str]:
    item = datos["items"][0]
    puntaje_llm = item.get("puntaje_final", 3)
    if item.get("descartado") or _descarte_por_params(item, params):
        return 0, reporte_descarte(item)
    mapa = {1: 10, 2: 30, 3: 50, 4: 70, 5: 90}
    base = mapa[puntaje_llm]
    base += _bonus_atencion_publica(item.get("experiencias", []))
    base += _bonus_comida_rapida(item.get("experiencias", []))
    escala = _mapear_escala_5(min(100, base))
    return escala, generar_reporte(base, escala, item)
# Envío masivo con fallos parciales aislados
# Un mensaje fallido NO cancela toda la campaña

@shared_task
def enviar_campaña_masiva(id_envio: int):
    envio = Envio.objects.get(id=id_envio)
    contactos = set(envio.contactos.all())
    for etiqueta in envio.etiquetas.all():
        contactos.update(etiqueta.contactos.all())

    servicio = ServicioTwilio()
    for contacto in contactos:
        envio.refresh_from_db()          # verificar cancelación en tiempo real
        if envio.estado == "cancelado":
            return
        try:
            msg = servicio.enviar_plantilla(contacto, envio.plantilla)
            MensajeEnvio.objects.create(
                envio=envio, contacto=contacto,
                twilio_sid=msg.sid, estado=msg.status or "queued"
            )
            envio.enviados += 1
        except TwilioRestException as exc:
            MensajeEnvio.objects.create(
                envio=envio, contacto=contacto, estado="failed",
                codigo_error=str(exc.code), mensaje_error=str(exc.msg)
            )
            envio.fallidos += 1
        envio.save()
        time.sleep(0.1)  # respetar rate limit Twilio: ~10 msg/s
    envio.estado = "completado"
    envio.completado_en = timezone.now()
    envio.save()

Manejo de errores y resiliencia

LLM falla o timeoutCelery reintenta con backoff exponencial (3 intentos); estado en error si agota reintentos
PDF ilegible o escaneadoOCR automático como fallback; activado si el texto extraído tiene menos de 100 caracteres
Mensaje WhatsApp fallaLog individual en MensajeEnvio; campaña continúa; contadores enviados/fallidos actualizados
Plantilla rechazada por MetaEstado rechazado + motivo almacenado; re-envío disponible después de corrección
Eliminación de plantillaBorra primero en Twilio; si falla, bloquea la eliminación local para preservar consistencia
Error al crear en Twiliomessages.error() en el admin; el registro no se persiste hasta confirmar éxito en la API

Resultados cuantificables

Estimaciones basadas en benchmarks del sector HR Tech en Latinoamérica.

-94%
tiempo de análisis por CV
×8.8
CVs procesados/día por analista
100%
consistencia en criterios
8:1
ROI estimado año 1
MétricaProceso manualCon IAMejora
Tiempo de análisis por CV15 min< 45 seg-94%
CVs analizados/día (1 analista)32280+×8.8
Consistencia en criterios~70%100%+30 puntos porcentuales
Tiempo hasta primera respuesta48–72 horas< 2 horas-96%
Costo por CV analizado$12 USD$0.80 USD-93%
Falsos positivos (no idóneos convocados)~35%< 8%-77%
CanalEmail (anterior)WhatsAppMejora
Tasa de apertura22%87%+295%
Tiempo de lectura promedio4.2 horas8 minutos-97%
Tasa de respuesta3%38%+1.167%

Galería técnica

Visualizaciones que complementan el informe técnico en el portfolio.

Diagrama de arquitectura del pipeline
Diagrama de arquitectura del pipeline

Flujo completo: Formulario → Signal → Celery → PDFProcessor → DeepSeek → cv_scoring.py → Admin RRHH, con servicios externos (R2, Twilio).

Dashboard de KPIs
Dashboard de KPIs

Gráfico comparativo antes/después para tiempo de screening, consistencia y capacidad, con tabla de impacto económico y ROI 8:1 destacado.

Flujo del motor de scoring
Flujo del motor de scoring

Diagrama de decisión del cv_scoring.py: árbol de descartes automáticos, 5 módulos ponderados, bonificaciones aditivas y mapeo a escala 1–5.

¿Interesado en este tipo de soluciones?

Diseño e implemento sistemas de IA aplicada a procesos de negocio reales: automatización de RRHH, análisis de documentos, integración de APIs y comunicación omnicanal.