Skip to content

SebastianLevano/hiremind-ai

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

5 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

HireMind AI

Plataforma ATS inteligente impulsada por IA para candidatos. Sube tu CV, pega una oferta laboral y recibe un análisis ATS, skills detectadas, comparación contra la oferta y feedback estructurado generado con OpenAI.

Python FastAPI React TypeScript Postgres Docker OpenAI


Tabla de contenidos


Funcionalidades

  • Auth JWT — registro, login, sesión persistida en localStorage, ruta protegida con expiración automática
  • Upload de CV PDF/DOCX con extracción de texto (pypdf, python-docx)
  • Detección de skills con catálogo curado de ~150 tecnologías
  • ATS scoring determinístico 0-100 con desglose por contacto / secciones / longitud / skills
  • Comparación CV vs Job Description con keywords ausentes
  • Feedback con IA — prompt estructurado a OpenAI con respuesta JSON y fallback robusto
  • Dashboard con score promedio, sparkline de evolución, top skills agregadas, análisis recientes
  • Historial con filtrado por usuario y borrado en cascade
  • UX pulida con toasts, ErrorBoundary, página 404, formularios validados con Zod

Stack

Capa Tecnologías
Frontend React 18, TypeScript 5, Vite, Tailwind CSS, React Router 6, TanStack Query, Zustand, React Hook Form + Zod, react-hot-toast, react-markdown
Backend Python 3.12, FastAPI, SQLAlchemy 2, Alembic, Pydantic v2, passlib[bcrypt], python-jose, pypdf, python-docx, openai
Base de datos PostgreSQL 16
Infraestructura Docker, Docker Compose, volúmenes nombrados
IA OpenAI API (gpt-4o-mini por defecto)

Arquitectura

┌────────────────┐    HTTPS/JSON    ┌──────────────────┐    SQL    ┌──────────────┐
│  React + Vite  │ ───────────────▶ │  FastAPI (Py)    │ ────────▶ │  PostgreSQL  │
│  Tailwind CSS  │ ◀─── JWT ──────  │  Auth · CV · AI  │ ◀──────── │  (users,cvs, │
└────────────────┘                  └────────┬─────────┘            │  analyses)   │
                                             │                      └──────────────┘
                                             │ HTTPS
                                             ▼
                                     ┌──────────────────┐
                                     │   OpenAI API     │
                                     └──────────────────┘
                                             ▲
                                             │ archivos
                                     ┌──────────────────┐
                                     │ /storage volume  │
                                     │ (docker volume)  │
                                     └──────────────────┘

Backend con capas claras: api → services → repositories → models. La capa services encapsula lógica de dominio (ATS scoring, skill detection, feedback IA) sin tocar HTTP. Los repositories aíslan SQLAlchemy del resto. La autorización se hace en cada endpoint vía get_current_user, y consultas que cruzan recursos (ej. analyses → cvs) siempre filtran por user_id para evitar IDOR.

Cómo correrlo

Requisitos: Docker Desktop.

git clone <repo-url> hiremind && cd hiremind
cp .env.example .env
# Edita .env y pon tu OPENAI_API_KEY real
docker compose up --build

Servicios disponibles:

Servicio URL
Frontend http://localhost:5173
Backend API http://localhost:8000
Swagger UI http://localhost:8000/docs
Health check http://localhost:8000/health
PostgreSQL localhost:5432

Datos demo (seed)

Para probar la app sin subir CVs reales, hay un script idempotente que crea un usuario demo, un CV de ejemplo y un análisis pre-calculado (sin llamada a OpenAI):

docker compose run --rm backend python -m scripts.seed

Credenciales demo:

email:    demo@hiremind.ai
password: demopass123

Estructura del proyecto

HireMindAI/
├── docker-compose.yml
├── .env.example
├── README.md
├── backend/
│   ├── Dockerfile
│   ├── requirements.txt
│   ├── alembic.ini
│   ├── migrations/versions/        # 0001_initial_users, 0002_cvs, 0003_analyses
│   ├── scripts/seed.py
│   └── app/
│       ├── main.py                 # FastAPI app + middleware + routers
│       ├── core/
│       │   ├── config.py           # pydantic-settings
│       │   ├── security.py         # bcrypt + JWT
│       │   ├── deps.py             # get_db, get_current_user
│       │   ├── storage.py          # rutas de archivos en /storage
│       │   └── logging.py          # dictConfig
│       ├── db/                     # Base, engine, SessionLocal
│       ├── models/                 # User, CV, Analysis (SQLAlchemy 2)
│       ├── schemas/                # Pydantic v2 DTOs
│       ├── repositories/           # acceso DB encapsulado
│       ├── services/
│       │   ├── cv_parser.py        # pypdf + python-docx
│       │   ├── skill_detector.py   # catálogo + matching regex
│       │   ├── ats_scorer.py       # heurística 0-100
│       │   └── ai_feedback.py      # OpenAI + JSON estructurado
│       └── api/                    # auth, cvs, analyses
└── frontend/
    ├── Dockerfile
    ├── package.json
    ├── vite.config.ts / tsconfig.json / tailwind.config.ts
    └── src/
        ├── App.tsx                 # Providers (Query, Toaster, Router)
        ├── routes.tsx              # rutas + ProtectedRoute
        ├── api/                    # client + endpoints (auth, cvs, analyses)
        ├── store/auth.ts           # Zustand
        ├── pages/                  # Login, Register, Dashboard, Upload, NewAnalysis, AnalysisDetail, ...
        ├── components/             # ScoreGauge, ScoreBreakdown, Sparkline, SkillChip, FileDropzone, NavBar, ErrorBoundary, ...
        └── lib/toast.ts

Endpoints API

Método Ruta Auth Descripción
GET /health Health check + estado DB
POST /auth/register Crear usuario
POST /auth/login OAuth2 form → JWT
GET /auth/me Usuario actual
POST /cvs Subir CV (multipart, ≤5MB, PDF/DOCX)
GET /cvs Listar CVs del usuario
GET /cvs/{id} Detalle con texto extraído
DELETE /cvs/{id} Borrar CV (cascade a análisis)
POST /analyses Crear análisis (cv_id + JD opcional)
GET /analyses Listar análisis
GET /analyses/{id} Detalle (gauge, skills, feedback)
DELETE /analyses/{id} Borrar análisis

Documentación interactiva en http://localhost:8000/docs.

Modelo de datos

users
  id              UUID PK
  email           UNIQUE INDEX
  password_hash   bcrypt
  full_name
  created_at

cvs
  id              UUID PK
  user_id         FK → users.id ON DELETE CASCADE, INDEX
  original_filename, mime_type, storage_path
  extracted_text  TEXT (cache de la extracción)
  created_at

analyses
  id                 UUID PK
  cv_id              FK → cvs.id ON DELETE CASCADE, INDEX
  job_description    TEXT NULL
  ats_score          INT (0-100)
  skills_detected    JSONB
  missing_keywords   JSONB
  ai_feedback        TEXT (markdown)
  ai_meta            JSONB (model, breakdown, improvements, keyword_match_pct, fallback flag)
  created_at

Migraciones gestionadas con Alembic. Cascade DB garantiza que borrar un CV elimine sus análisis sin dejar huérfanos.

Decisiones de diseño

  • Sync sobre async — el análisis llama a OpenAI sincrónicamente. Un job runner (Celery/Redis) sería sobre-ingeniería para el MVP; cuando el tiempo medio supere ~15s, se mueve a async sin tocar el frontend (mismo endpoint, mismo contrato).
  • JSON en ai_meta en lugar de columnas extra — mantiene el modelo estable mientras experimentamos con qué metadatos guardar (improvements, breakdown, model usado, flag de fallback).
  • Score determinístico + IA — el ATS score es 100% reproducible (heurísticas), la IA aporta cualitativo. Esto evita que un usuario vea scores diferentes cada vez que reanaliza el mismo CV.
  • Volumen local en lugar de S3 — para MVP local y demos. Migrar a S3/MinIO requiere cambiar solo core/storage.py.
  • Catálogo de skills en código — más rápido y libre de coste. Si crece, se mueve a tabla skills con admin UI.
  • Fallback de IA — si OpenAI falla (timeout, JSON inválido, sin API key), se devuelve un feedback genérico con ai_meta.fallback = true y la app no se rompe.

Deploy en producción

Stack recomendado (todo free para portafolio):

Pieza Plataforma Plan free Notas
Frontend Vite Vercel ilimitado Auto-deploy en cada push
Backend FastAPI Render (Web Service desde Docker) 750h/mes Se duerme tras 15 min sin tráfico
PostgreSQL Neon 0.5 GB Serverless, autoescala

Paso 1 — Neon (base de datos)

  1. Crear cuenta en https://neon.techNew Project → región más cercana
  2. Copiar la Connection string (formato postgresql://user:pass@ep-xxx.neon.tech/dbname?sslmode=require)
  3. Guardarla — la usaremos como DATABASE_URL en Render

El backend acepta tanto postgresql:// (Neon/Heroku) como postgresql+psycopg://. El normalizador en core/config.py añade el driver automáticamente.

Paso 2 — Render (backend)

  1. Crear cuenta en https://render.comNew → Web Service → conectar el repo hiremind-ai
  2. Configurar:
    • Root Directory: backend
    • Runtime: Docker
    • Plan: Free
  3. Environment variables:
    • DATABASE_URL = (string de Neon)
    • JWT_SECRET = (genera uno fuerte, ej. openssl rand -hex 32)
    • OPENAI_API_KEY = tu key rotada
    • OPENAI_MODEL = gpt-4o-mini
    • CORS_ORIGINS = https://hiremind-ai.vercel.app (temporal; se actualiza tras desplegar Vercel)
  4. Click Create Web Service. Render builda con tu Dockerfile, corre alembic upgrade head y arranca uvicorn en el puerto que asigna.
  5. Cuando termine, copia la URL pública (algo como https://hiremind-backend.onrender.com)

Paso 3 — Vercel (frontend)

  1. https://vercel.comImport Project → conectar el repo hiremind-ai
  2. Configurar:
    • Root Directory: frontend
    • Framework Preset: Vite (detectado automáticamente)
    • Build Command: npm run build
    • Output Directory: dist
  3. Environment variables:
    • VITE_API_URL = URL pública del backend en Render (sin slash final)
  4. Deploy → Vercel asigna una URL tipo https://hiremind-ai.vercel.app

Paso 4 — Cerrar el círculo CORS

Vuelve a Render → tu Web Service → Environment → edita CORS_ORIGINS con la URL real de Vercel y guarda. El servicio reinicia automáticamente.

Paso 5 — Seed en producción (opcional)

Desde el shell de Render (Shell tab):

python -m scripts.seed

Login con demo@hiremind.ai / demopass123.

Notas operativas

  • Cold starts: el backend Render free se duerme tras 15 min. Primera petición ~30 s. Para mantenerlo caliente, configura un ping cada 14 min desde UptimeRobot o un cron de GitHub Actions.
  • Archivos efímeros: el plan free de Render no tiene disco persistente. Los archivos PDF/DOCX se pierden en cada redeploy, pero extracted_text queda cacheado en BD (la app sigue funcionando, solo no podrás re-descargar el original). Para persistencia real, migrar core/storage.py a Cloudflare R2 (10 GB gratis) o S3.
  • Migraciones: se aplican automáticamente al startup (entrypoint.sh). Si añades una nueva, basta con push a main.

Roadmap

  • Fase 0 — Bootstrap (Docker Compose + skeletons)
  • Fase 1 — Auth JWT
  • Fase 2 — Upload + extracción de CV
  • Fase 3 — Análisis ATS + IA
  • Fase 4 — Dashboard + historial (sparkline, top skills, breakdown, delete)
  • Fase 5 — Pulido (toasts, ErrorBoundary, 404, health DB, seed, README)

Siguientes mejoras (post-MVP):

  • Tests (pytest backend, Vitest frontend, Playwright E2E)
  • CI/CD con GitHub Actions
  • Storage cloud (S3/MinIO)
  • Procesamiento async (Celery) si los análisis crecen en latencia
  • Observabilidad (Sentry + Prometheus + structlog)
  • Roles (recruiter) y multi-tenancy
  • OAuth social, recuperación de contraseña
  • Internacionalización
  • Build de producción del frontend (multi-stage con nginx)

Troubleshooting

error: password cannot be longer than 72 bytes Si actualizas bcrypt a 4.1+, hay incompatibilidad con passlib 1.7.4. El requirements.txt pinea bcrypt==4.0.1. Si ves este error, rebuild el backend: docker compose up --build backend.

El frontend no llega al backend (CORS) Verifica que CORS_ORIGINS en .env incluye http://localhost:5173.

El feedback con IA es genérico Asegúrate de que OPENAI_API_KEY está configurada en .env y reinicia: docker compose restart backend. Si ves ai_meta.fallback: true en la respuesta, revisa los logs (docker compose logs backend) para el motivo real.

La migración no se aplica El backend ejecuta alembic upgrade head antes de uvicorn (ver docker-compose.yml). Si quieres forzar desde cero: docker compose down -v borra el volumen Postgres.

Cambiar puerto Edita docker-compose.yml (5173:5173, 8000:8000) y, si tocas el backend, también VITE_API_URL en .env.


Creado por Sebastian Levano - Stack 100% libre y reproducible con un solo docker compose up.

About

Plataforma ATS inteligente con IA — React + FastAPI + PostgreSQL + OpenAI

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors