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.
- Funcionalidades
- Stack
- Arquitectura
- Cómo correrlo
- Datos demo (seed)
- Estructura del proyecto
- Endpoints API
- Modelo de datos
- Decisiones de diseño
- Deploy en producción
- Roadmap
- Troubleshooting
- 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
| 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) |
┌────────────────┐ 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.
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 --buildServicios 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 |
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.seedCredenciales demo:
email: demo@hiremind.ai
password: demopass123
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
| 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.
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.
- 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_metaen 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
skillscon 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 = truey la app no se rompe.
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 |
- Crear cuenta en https://neon.tech → New Project → región más cercana
- Copiar la
Connection string(formatopostgresql://user:pass@ep-xxx.neon.tech/dbname?sslmode=require) - Guardarla — la usaremos como
DATABASE_URLen Render
El backend acepta tanto
postgresql://(Neon/Heroku) comopostgresql+psycopg://. El normalizador encore/config.pyañade el driver automáticamente.
- Crear cuenta en https://render.com → New → Web Service → conectar el repo
hiremind-ai - Configurar:
- Root Directory:
backend - Runtime: Docker
- Plan: Free
- Root Directory:
- Environment variables:
DATABASE_URL= (string de Neon)JWT_SECRET= (genera uno fuerte, ej.openssl rand -hex 32)OPENAI_API_KEY= tu key rotadaOPENAI_MODEL=gpt-4o-miniCORS_ORIGINS=https://hiremind-ai.vercel.app(temporal; se actualiza tras desplegar Vercel)
- Click Create Web Service. Render builda con tu
Dockerfile, correalembic upgrade heady arranca uvicorn en el puerto que asigna. - Cuando termine, copia la URL pública (algo como
https://hiremind-backend.onrender.com)
- https://vercel.com → Import Project → conectar el repo
hiremind-ai - Configurar:
- Root Directory:
frontend - Framework Preset: Vite (detectado automáticamente)
- Build Command:
npm run build - Output Directory:
dist
- Root Directory:
- Environment variables:
VITE_API_URL= URL pública del backend en Render (sin slash final)
- Deploy → Vercel asigna una URL tipo
https://hiremind-ai.vercel.app
Vuelve a Render → tu Web Service → Environment → edita CORS_ORIGINS con la URL real de Vercel y guarda. El servicio reinicia automáticamente.
Desde el shell de Render (Shell tab):
python -m scripts.seedLogin con demo@hiremind.ai / demopass123.
- 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_textqueda cacheado en BD (la app sigue funcionando, solo no podrás re-descargar el original). Para persistencia real, migrarcore/storage.pya Cloudflare R2 (10 GB gratis) o S3. - Migraciones: se aplican automáticamente al startup (
entrypoint.sh). Si añades una nueva, basta con push amain.
- 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)
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.