Guía de usuario
Editar esta página en GitHubAyuda — career-ops-ui
Recorrido completo por cada página, desde el momento en que arrancas
la app hasta conseguir una entrevista. Cada encabezado ## de abajo
corresponde a una entrada del sidebar o a una fase del workflow. Lee
de arriba a abajo en el primer arranque; después salta a la sección
concreta vía el índice del sidebar de ayuda.
Audiencia: cualquiera que acaba de poner este UI dentro de un checkout de
career-opsy ejecutóbash bin/start.sh. No se asume conocimiento previo de career-ops.
Sobre career-ops
career-ops es un sistema open-source de búsqueda de empleo que se ejecuta como slash-comandos dentro de cualquier CLI de codificación con IA (Claude Code, Gemini CLI, Codex, Qwen Code, OpenCode, GitHub Copilot CLI — otras CLIs compatibles con Claude también funcionan vía la misma superficie de slash-comandos). Modelo-agnóstico. Evalúa cada puesto contra tu CV con una rúbrica de seis dimensiones 0.0–5.0, genera CVs PDF adaptados, y registra cada candidatura localmente en tu máquina.
Referencia canónica (léela en orden en la primera instalación):
- What is career-ops — el sistema, los principios y el inventario de conceptos.
- Scan job portals — descubre vacantes; alimenta el Pipeline.
- Apply for a job — flujo de envío completo con la lectura de formularios de Playwright.
- Batch-evaluate offers
— puntúa 10+ JDs a la vez vía
batch-runner.sh. - Set up Playwright — instala Chromium + registra el MCP para PDF y form-fill.
Principios fundamentales (de career-ops.org/docs/introduction/what-is-career-ops):
- Open source, en serio — MIT, sin tier de pago, sin lista de espera, sin telemetría, sin cuentas. El sistema funciona sin niveles de pago, cuentas ni telemetría. Las contribuciones de código pasan revisión comunitaria antes del release.
- Soberanía de datos —
cv.md,config/profile.yml,data/,reports/,interview-prep/nunca salen de tu portátil salvo que los subas explícitamente. Lo ejecutas en local, conservando la soberanía total de tus datos. - Arquitectura AI-agnóstica — career-ops NO empaqueta un modelo. Funciona como comandos dentro de las CLIs de codificación con IA existentes. Cambia de proveedor (Anthropic ↔ Gemini ↔ OpenAI) y tu historial de evaluaciones se mantiene consistente.
- Envío controlado por humanos — career-ops redacta las respuestas y abre el formulario, pero tú haces clic en Submit. El sistema jamás auto-aplica. Aporta estructura y evaluación; el humano conserva la autoridad final sobre el envío.
- Búsqueda estructurada — pensado para una búsqueda activa y deliberada con muchas candidaturas; no es una herramienta de envío único ni un motor de recomendaciones. La instalación lleva ~15 minutos y asume soltura con la terminal.
Lo que career-ops NO es (no-objetivos explícitos):
- No es un auto-applier. No enviará formularios por ti.
- No es un reconstructor de currículum. Adapta por JD; no inventa experiencia.
- No es un optimizador de LinkedIn. Tu perfil es asunto tuyo.
- No es un sustituto de hoja de cálculo escondido tras un UI SaaS. Los datos son markdown plano en tu sistema de archivos.
Conceptos clave (inventario completo — cada artefacto que career-ops toca):
| Concepto | Qué es |
|---|---|
| Mode | Plantilla de prompt bajo modes/<slug>.md. Built-in: oferta, deep, apply, pipeline, batch, contacto, followup, interview-prep, patterns, project, training, ofertas, auto-pipeline, pdf, latex, scan, tracker. |
| Arquetipo | Un perfil de rol objetivo en config/profile.yml. La rúbrica pondera coincidencias de skills contra el arquetipo activo — el campo más importante. |
| Pipeline | data/pipeline.md — inbox de URLs de JD pendientes de evaluación. |
| Tracker | data/applications.md — tabla GFM histórica de cada evaluación + estado de candidatura. |
| Report | reports/<NNN>-<company>-<DATE>.md — evaluación A–F completa por JD, con puntuación + legitimidad en el header. |
| Scan history | data/scan-history.tsv — log append-only; previene duplicados entre scans. |
| Proof points | Bloques de evidencia STAR+R extraídos de cv.md, reutilizados a través de evaluación, respuestas de apply y preparación de entrevistas. |
| JD store | jds/jd-<date>-<ts>.txt — descripciones de empleo verbatim guardadas durante la evaluación para el audit trail. |
| Interview-prep | interview-prep/<company>-<role>.md — briefings de deep research y one-pagers por ronda. |
| Batch additions | batch/tracker-additions/*.tsv — filas pendientes encoladas por batch-runner.sh para su merge al tracker. |
career-ops vs career-ops-ui (esta app)
| career-ops (CLI) | career-ops-ui (esta app) | |
|---|---|---|
| Dónde corre | dentro de Claude Code / Gemini CLI / Codex / Qwen Code / OpenCode / GitHub Copilot CLI | http://127.0.0.1:4317 en tu navegador |
| Superficie | slash-comandos /career-ops <mode> |
sidebar con una página por workflow |
| Form-fill | sí, vía Playwright MCP | no — genera la checklist, tú la completas en el CLI |
generate-pdf.mjs |
📄 Generate PDF en #/cv, #/reports/:slug, #/evaluate, #/deep, #/interview-prep |
|
| Archivos de datos | compartidos con career-ops-ui | compartidos con career-ops |
career-ops-ui son puramente adiciones. Nada dentro de
career-ops/ cambia. Ambas superficies comparten el mismo cv.md,
config/profile.yml, portals.yml, data/, reports/,
interview-prep/, modes/.
Umbrales de acción por puntuación
Una vez que un JD tiene una evaluación, la puntuación determina qué hacer a continuación (tabla canónica de career-ops.org/docs/introduction/what-is-career-ops):
| Puntuación | Siguiente paso |
|---|---|
| ≥ 4.5 | Ejecuta /career-ops apply — fit alto, envía de inmediato. |
| 4.0 – 4.4 | Aplica, o /career-ops contacto para warm intro primero. |
| 3.5 – 3.9 | Ejecuta /career-ops deep — investiga la empresa / rol antes de decidir. |
| < 3.5 | Salta salvo que tengas un motivo personal específico. |
El #/dashboard y el #/tracker de career-ops-ui resaltan cada fila
en 4.0 o por encima para que puedas tomar acción sin re-ejecutar
nada.
Documentación externa
Referencia completa del motor career-ops subyacente (scanning, rúbrica de evaluación, procesamiento batch, flujo de apply, setup de Playwright) en career-ops.org/docs:
1. Inicio rápido — paso a paso de “crear CV” hasta “postulado y mensajeado”
Este es el playbook canónico botón-por-botón. Síguelo en orden la primera vez. Cada paso nombra la ruta exacta, el botón exacto, y qué verás al éxito. Las secciones 2–16 entran en profundidad en cada fase.
Preguntar a la guía. Abre Preguntar a la guía 💬 (barra lateral, bajo Ayuda) y escribe una pregunta — responde solo desde esta guía en tu idioma y nunca lee tu CV. El mismo asistente está a un toque desde cualquier página: un botón de chat con un robot flota en la esquina inferior derecha (inferior izquierda en idiomas de derecha a izquierda); púlsalo para preguntar sin salir de lo que estás haciendo.
Lanzamiento e inicialización con un solo comando. Desde una terminal puedes hacer todo el arranque sin tocar la interfaz:
career-ops-ui setup # instala dependencias → doctor → arranca el servidor career-ops-ui init # elige el proveedor LLM + pega su clave (eco suprimido) career-ops-ui doctor # vuelve a verificar cuando quieras (salida 0 ⇔ todo lo requerido en verde) career-ops-ui run # solo arranca el servidor en http://127.0.0.1:4317 career-ops-ui open # abre + TRAE AL FRENTE la pestaña del panel en tu navegadorTras
setup/runla pestaña del navegador se abre y se trae al frente automáticamente (v1.43.0);career-ops-ui openhace lo mismo bajo demanda, así nunca tienes que buscar la pestaña del panel.NO_OPEN=1desactiva la apertura automática en arranques headless/CI.
setupejecuta toda la cadena por sí mismo.initescribe la clave en elcareer-ops/.envdel proyecto padre a través de la misma ruta validada que usa la pestaña de claves API de#/config, y defineLLM_PROVIDER(auto|claude|gemini), que respetan las rutas en vivo de evaluación / profundo / modo / pipeline automático. Forma para CI:career-ops-ui init --provider claude --anthropic-key sk-ant-… --yes. ¿Prefieres la interfaz? Continúa con los pasos de abajo.
A. Configuración (haz esto una vez, ~5 minutos)
career-ops-ui debe estar en career-ops/web-ui/ (anidado dentro del proyecto career-ops padre). Lee tu cv.md, config/ y data/ de la carpeta padre mediante ../ y no funciona de forma independiente. Si career-ops-ui init no se encuentra después de un pull, ejecuta cd career-ops/web-ui && npm install && npx career-ops-ui init.
Paso 1 — Abre la app en http://127.0.0.1:4317. Si no está
corriendo, en una terminal ejecuta bash bin/start.sh desde la raíz
del repo. Carga el Dashboard (#/dashboard).
Paso 2 — Haz clic en ❤ Health en el sidebar izquierdo. Cada
check requerido debe estar en verde:
- Existen
cv.md,config/profile.yml,portals.yml - API key configurada (al menos una de
ANTHROPIC_API_KEY/GEMINI_API_KEY) - Playwright instalado (solo necesario si vas a usar Generate PDF)
Si algo está en rojo, la página te indica el archivo o variable de entorno exacta a arreglar. No continúes hasta que Health esté verde.
Paso 3 — Haz clic en ⚒ App settings en el sidebar. Aterrizas en
la pestaña API keys & runtime.
- Pega
ANTHROPIC_API_KEY(preferida — mejor scoring de formato largo) y/oGEMINI_API_KEY. Obtén las claves en https://console.anthropic.com/settings/keys o https://aistudio.google.com/apikey. - Haz clic en 💾 Save. Después haz clic en ▶ Test Anthropic (o Gemini) — un round-trip diminuto confirma que la clave funciona.
Paso 4 — Cambia a la pestaña Profile en la misma página. Es el
editor YAML directo para config/profile.yml. Edita como mínimo:
candidate.full_name— reemplaza cualquier placeholder (“Jane Smith”) por tu nombre realcandidate.email,linkedin,github— se usan en cover letterstarget.roles— los títulos de empleo a los que aplicarástarget.comp_total_min_usd— compensación total mínima; las ofertas por debajo se marcan en la sección D de cada evaluacióntarget.archetypes— los patrones de carrera que aceptas (el campo individual de mayor impacto)
Haz clic en 💾 Save. El servidor valida el YAML y estampa el
encabezado canónico # Career-Ops Profile Configuration.
B. CV (haz esto una vez, ~10 minutos)
Paso 5 — Haz clic en ✎ CV en el sidebar. Dos columnas: editor a
la izquierda, vista previa en vivo a la derecha.
Paso 6 — Elige una ruta para llenar el editor:
- Sube un currículum existente — haz clic en 📁 Upload CV,
elige cualquiera de
.docx / .doc / .odt / .rtf / .pdf / .html / .txt / .md. El servidor convierte a markdown vía pandoc o pdftotext, sanea XSS, y coloca el resultado en el editor. Revisa la conversión — los PDFs especialmente pueden perder fidelidad de layout. - Pega markdown directamente — el textarea es un editor markdown; el panel derecho es lo que verá el LLM (y tu futuro reclutador).
- Tips de tono: un bullet = un logro con métrica. Mantén por debajo de 1500 palabras. Secciones en este orden: Summary, Experience, Projects, Education, Skills.
Paso 7 — Haz clic en 💾 Save (arriba a la derecha de la página
CV). El servidor sanea (se eliminan <script> / javascript: /
manejadores inline) y escribe cv.md. Toast: “Saved”.
Paso 8 (opcional) — Haz clic en 📄 Generate PDF. Ejecuta
generate-pdf.mjs en el padre (Playwright requerido) y el PDF nuevo
se descarga automáticamente en tu navegador al terminar. La lista al
final de la página conserva cada archivo previamente generado.
C. Encontrar vacantes (~2 minutos por scan)
Paso 9 — Haz clic en 🌐 Scan en el sidebar. Confirma que
portals.yml lista los boards que te interesan (sección 5 de esta
ayuda). Pulsa el botón 🌐 Scan now. Un log SSE en vivo se va
mostrando mientras el scanner recorre Greenhouse / Ashby / Lever /
Workable / SmartRecruiters / Workday (boards en inglés) y hh.ru / Habr
Career (boards rusos, si están habilitados).
Paso 10 — Cuando termine el scan, revisa los resultados. Haz clic en cualquier tag de empresa para filtrar; haz clic en el icono ↗ para abrir la página de carreras de la empresa en una pestaña nueva. Cada vacante que sobrevivió al filtro de título queda encolada en el Pipeline.
D. Puntuar las ofertas (~30 segundos por JD)
Paso 11 — Haz clic en Pipeline en el sidebar. Ves cada URL que
el scanner encoló. Haz clic en una entrada para vista previa del JD
inline.
Paso 12 — Haz clic en ▶ Evaluate al lado de cualquier JD. Esto
salta a #/evaluate. Con una API key configurada, corre live; sin
ella, obtienes un prompt manual para pegar en tu propio LLM. El modo
live produce una puntuación 0–5 contra tu CV en las secciones A–G
(Rol / Empresa / Compensación / Riesgo / Stretch / Fit cultural /
Verdict). El guardado aterriza en reports/<date>-<slug>.md.
Paso 13 — Haz clic en Reports en el sidebar y revisa la última
evaluación. Cualquier cosa por debajo de tu comp_total_min_usd se
marca en rojo en la sección D. Cualquier cosa con Verdict: pursue
es tu short-list.
E. Decidir e investigar a fondo la empresa preseleccionada (~3 minutos)
Paso 14 — Elige una vacante que valga la pena perseguir. Haz clic
en Deep research en el sidebar. Introduce el nombre de la empresa
y el rol. El modelo produce un briefing de empresa de 7 secciones
(misión, news recientes, stack tecnológico, señales de contratación,
benchmarks de compensación, riesgos, ángulo recomendado). El guardado
aterriza en interview-prep/<company>-<role>.md.
F. Postular (~5 minutos por candidatura)
Paso 15 — Haz clic en Apply checklist en el sidebar. Pega la
URL de la vacante + JD. El helper genera una checklist de envío paso
a paso:
- Borrador de cover letter adaptada (usa tu
cv.md+profile.yml) - Keywords específicas a reflejar desde el JD
- Archivos a adjuntar (CV en PDF — ver paso 8)
- Dónde aplicar (la URL canónica de careers, no redirecciones de agregadores)
- Recordatorio: NUNCA auto-enviar — la revisión final y el envío son siempre manuales.
Paso 16 — Abre la página de careers en una pestaña nueva. Usa la apply checklist como tu lista de tareas. Envía a través del formulario real de la empresa. Adjunta el PDF que generaste en el paso 8.
Paso 17 — Contacta con un humano real. Abre el modo Outreach
(#/contacto en el sidebar). El modelo redacta un mensaje corto de
LinkedIn / email adaptado al briefing de empresa del paso 14.
Personaliza el opener (un detalle específico de tu briefing de deep
research). Envíalo.
G. Trackear y hacer follow-up (continuo)
Paso 18 — Haz clic en Tracker en el sidebar y añade una fila
para la candidatura: empresa, rol, puntuación, estado Applied,
enlace al reporte, enlace al briefing de deep research. La fecha se
autorellena.
Paso 19 — Una semana después: abre el modo Follow-up
(#/followup). Redacta un email cortés de check-in referenciando la
candidatura original. Envía. Actualiza el estado del tracker a
Followed up.
Paso 20 — Cuando te llegue una invitación a entrevista, ejecuta el
modo Interview prep (#/interview-prep). Genera preparación
dirigida para la empresa + etapa específica (system design /
behavioral / coding). Tira automáticamente del briefing de deep
research.
Paso 21 — ¿Conseguiste la oferta? Actualiza el estado del Tracker a
Offer y revisa la sección de comp de tu reporte de evaluación —
tu número mínimo de aceptación está justo ahí.
TL;DR — el orden del sidebar coincide con el workflow
Health → App settings → Profile → CV → Scan → Pipeline → Evaluate → Reports → Deep research → Apply checklist → Outreach → Tracker → Follow-up → Interview prep → Activity log
Eso es todo. 21 pasos, botón-por-botón, de cero a oferta.
Auto-pipeline de un clic (#/auto) — el atajo de 21 pasos
Si solo quieres puntuar rápido una vacante concreta, salta el recorrido manual. Barra lateral → ✨ Auto-pipeline (o el botón ✨ del Dashboard): pega la URL, pulsa Enter o ▶ Ejecutar pipeline completo, y el servidor corre toda la cadena en una pasada observable:
- Validar URL — comprobación SSRF-segura (
isValidJobUrl). - Obtener la descripción —
safeGet(DNS fijado) descarga + sanea la JD. - Evaluar contra tu CV — Anthropic → Gemini → prompt manual si no hay key.
- Guardar informe — escribe
reports/<slug>.mdcon score + legitimidad. - Añadir al tracker — añade una fila a
data/applications.md.
El feedback es un stepper vertical (lista ordenada, aria-current en el paso activo, región viva para lectores de pantalla). Al terminar, la tarjeta enlaza al informe (Ver informe · N/5) y al tracker. Un paso fallido se marca y el botón se rehabilita para reintentar sin recargar. ¿Sin API key? Modo manual: pasos 3–5 colapsan y obtienes un prompt para copiar. Enlazable: #/auto?url=<enc>&go=1 autoarranca.
CLI (v1.38.0). Un comando hace la cadena:
career-ops-ui setup. Verbos:career-ops-ui doctor(chequeo env/claves/tooling — mismo motor que Health; exit 1 si falla algo requerido),career-ops-ui run,career-ops-ui init(asistente proveedor+clave, v1.39.0). Proveedores (v1.39.0). La pestaña API-keys añade un selectLLM_PROVIDER(auto=Anthropic→Gemini ·claude·gemini) y un campoOPENAI_API_KEY(lado Codex/OpenCode CLI).career-ops-ui inites el asistente interactivo.Proveedores (v1.57.0). La eval en vivo headless abarca Anthropic → Gemini → OpenAI → Qwen → OpenRouter (orden
auto;LLM_PROVIDERfija uno). OpenRouter — unaOPENROUTER_API_KEYda acceso a más de 300 modelos; el desplegableOPENROUTER_MODELcarga el catálogo en vivo de OpenRouter (proxy del servidor, fallback curado offline). Corregido también: las claves pegadas con salto de línea / espacios se recortan antes de validar, así que/#/configya no muestra «validation failed» para ningún proveedor.
2. Ajustes de la app y claves API (#/config)
Novedades v1.55 → v1.56. Sin clave LLM, un banner rojo en cada pantalla explica que ⚡ Ejecutar en vivo está en modo prompt manual y enlaza aquí; con una clave se vuelve un chip discreto con el proveedor activo. Antes de cada botón ⚡ Ejecutar en vivo (
#/auto,#/evaluate,#/deep, modos) se muestra un coste estimado honesto (p. ej. “Coste estimado: OpenAI gpt-5-codex · ~$0.04/eval”, o sin coste de API en modo manual).#/scanoculta filtros secundarios tras un desplegable Filtros avanzados;#/trackerañade chips de embudo clicables + paginación de servidor opcional;#/pipelinevirtualiza más de 1000 filas.
Herramientas CLI de IA. La pestaña Herramientas CLI de IA muestra qué CLI de agente (Claude Code, Codex, Gemini, OpenCode, Copilot, Qwen, Antigravity) están instaladas en el servidor — un escaneo del PATH de solo lectura, sin ejecutarlas. Apariencia → Mostrar logos de empresa (desactivado por defecto) muestra el favicon de cada empresa en la tabla de escaneo, obtenido de su propio dominio (nunca un servicio de terceros).
Dos pestañas:
- API keys & runtime — edita el
.envdel proyecto padre desde el navegador (el mismo archivo que los scripts Node de career-ops leen al arrancar). La pestaña también ofrece selectores de modelo por proveedor —OPENAI_MODEL(OpenAI/Codex) junto aANTHROPIC_MODELyGEMINI_MODEL. - Profile — editor YAML directo para
config/profile.yml. El guardado estampa el encabezado canónico# Career-Ops Profile Configuration.
Un guardado en cualquiera de las pestañas se propaga al instante — sin reiniciar el servidor.
Configurar tu proveedor LLM (paso a paso). La ⚡ evaluación en vivo del web UI corre headless y usa una clave de API. Funciona vía “OR” — define cualquiera de estas y ya funciona; con varias definidas, auto las prefiere en este orden: Anthropic → Gemini → OpenAI → Qwen. (career-ops en sí es agnóstico de CLI — también lo ejecutas dentro de Claude Code, Codex, Gemini, OpenCode, Qwen, Copilot o Kimi; eso es independiente de esta clave headless.)
- Abre
#/config→ la pestaña API keys & runtime. - Elige tu proveedor en
LLM_PROVIDER:auto(usa la clave que esté definida), o fuerza uno conclaude/gemini/openai/qwen. - Rellena la clave + modelo del proveedor que elegiste:
- Anthropic — define
ANTHROPIC_API_KEY(console.anthropic.com), opcionalmenteANTHROPIC_MODEL(defaultclaude-sonnet-4-6). - Gemini — define
GEMINI_API_KEY(aistudio.google.com/apikey), opcionalmenteGEMINI_MODEL(defaultgemini-2.0-flash). - OpenAI — define
OPENAI_API_KEY(platform.openai.com), opcionalmenteOPENAI_MODEL(defaultgpt-5-codex). - Qwen — define
QWEN_API_KEY(Alibaba Model Studio / DashScope, dashscope.console.aliyun.com), opcionalmenteQWEN_MODEL(defaultqwen-max). Para el endpoint de China continental defineQWEN_BASE_URLen el.envcrudo.
- Anthropic — define
- Haz clic en Save. Las claves se escriben en el
.envdel proyecto padre; el cambio surte efecto al instante — sin reiniciar el servidor. - Verifica en
#/evaluate: pega una URL/descripción de vacante y pulsa ⚡ Run live. El encabezado del resultado muestra qué proveedor corrió (anthropic/gemini/openai/qwen). Sin ninguna clave definida → obtienes el prompt manual de copiar-pegar.
Los secretos se enmascaran tras guardar y nunca se registran. Los campos de id de modelo (*_MODEL) no son secretos.
Pestaña Profile
v1.32.0 — formulario por campos. La pestaña Profile ya no es un textarea de YAML crudo: ahora es un formulario con secciones plegables Candidato / Narrativa / Compensación. Al guardar se envían solo las 14 rutas escalares modeladas; el servidor fusiona en
config/profile.yml, así que tusarchetypes,proof_pointsy claves propias se conservan intactos. Compromiso: el guardado por campos re-serializa el YAML y pierde los comentarios#— usa el desplegable Advanced: edit raw YAML al final de la pestaña para preservarlos o editar arrays anidados. v1.35.0 — editores de arrays. Editores add/remove para Target roles y Superpowers (listas de texto), Archetypes (name/level/fit) y Proof points (name/url/hero-metric). Misma garantía merge-not-replace; vaciar una lista elimina la clave limpiamente. v1.54.3 — pestaña Modes como formulario estructurado.modes/_profile.mdya no es un editor de markdown por sección: ahora es un formulario derivado del esquema documentado. Las secciones de lista — Target Roles / Adaptive Framing / Comp Targets — son campos de línea repetibles (añadir/quitar filas); las secciones de prosa — Exit Narrative / Location Policy — son textareas etiquetadas; cualquier sección desconocida o no-lista cae a un textarea etiquetado verbatim. Guardar sigue fusionando por sección — preámbulo, secciones intactas y secciones propias se conservan byte a byte. Persiste el desplegable Advanced: raw markdown para editar el archivo completo: añadir/quitar secciones o editar el preámbulo.
- El textarea muestra el
config/profile.ymlactual verbatim. - Edita y haz clic en 💾 Save. El servidor valida el YAML (debe
ser un mapping, debe contener
candidate) y escribe el archivo. - Se añade un encabezado
# Career-Ops Profile Configurationsi no está. - El resumen de solo lectura en
#/profilees el compañero visual.
Claves reconocidas
| Clave | Para qué sirve | Dónde obtenerla |
|---|---|---|
ANTHROPIC_API_KEY |
Habilita llamadas live al SDK de Anthropic. Preferida cuando ambas Anthropic + Gemini están configuradas — mejor output estructurado largo para puntuación de JD y deep research. | https://console.anthropic.com/settings/keys |
ANTHROPIC_MODEL |
Sobrescribe el default claude-sonnet-4-6. Prueba claude-opus-4-7 para razonamiento más exigente, claude-haiku-4-5-20251001 para barato-y-rápido. |
— |
GEMINI_API_KEY |
Fallback cuando no hay clave Anthropic. La usa gemini-eval.mjs para el modo oferta. El free tier funciona para volumen bajo. |
https://aistudio.google.com/apikey |
GEMINI_MODEL |
Sobrescribe el modelo Gemini por defecto. | — |
(server uses default UA) |
Necesario al ejecutar scans en hh.ru desde fuera de Rusia (la API devuelve 403 ante User-Agents planos). Registra una app en https://dev.hh.ru/admin y usa su string UA. |
dev.hh.ru |
PORT |
Puerto de bind de Express. Default 4317. | — |
HOST |
Dirección de bind. Default 127.0.0.1. Poner 0.0.0.0 expone el UI en la LAN — sin auth gate todavía, ver el doc de Production-readiness. |
— |
Comportamiento
- Lectura (
GET /api/config) devuelve cada clave reconocida. Las claves secretas (ANTHROPIC_API_KEY,GEMINI_API_KEY) están enmascaradas — vessk-ant•••••••a1b2, nunca el valor completo. - Guardado (
POST /api/config) valida cada valor, escribe en<parent>/.env, e inmediatamente aplica al proceso en ejecución. No se requiere reinicio. - Un valor vacío borra la clave. Útil si quieres dejar de usar una IP rusa / VPN.
Botones smoke-test
Después de guardar, haz clic en ▶ Test Anthropic o ▶ Test Gemini — ambos disparan un prompt diminuto (≤256 tokens de output) para que gastes esencialmente nada mientras confirmas que la clave está conectada correctamente. Devuelve una muestra de ~200 caracteres en caso de éxito.
3. Perfil (#/profile — también accesible como #/settings)
Una vista de tarjeta resumen de solo lectura de config/profile.yml.
Para editar, ve a App settings → pestaña Profile (#/config
→ Profile). Los guardados aterrizan en el mismo archivo; esta página
re-parsea al recargar.
Los campos que más importan:
candidate.full_name— usado en cada prompt. Reemplaza el templateJane Smithantes de escanear nada de verdad, o tus cover letters generadas saldrán bajo el nombre placeholder.candidate.email,linkedin,github— referenciados en la generación de cover letters y en la apply checklist.target.roles— títulos de empleo aceptados. El filtro positivo del scanner los usa implícitamente (víaportals.yml::title_filter).target.comp_total_min_usd— compensación total mínima. La sección D de cada evaluación marca ofertas por debajo de esto.target.archetypes— el campo más importante. Estos son los patrones de carrera que aceptas (p. ej.Tech-Lead-Backend,Founding-Engineer,Data-Platform). Cada JD se mide contra ellos y el arquetipo de mejor fit aterriza en el header del reporte.
La página Health expone un check Profile customized que falla
mientras full_name coincida con un nombre placeholder conocido.
4. CV (#/cv)
Fuente única de verdad para cada evaluación, deep research, y cover
letter. Vive en cv.md en la raíz del proyecto padre.
Opciones de edición
- Pégalo directamente — el textarea de la izquierda es un editor markdown. El panel derecho refleja lo que el LLM (y tu futuro reclutador) verán.
- 📁 Upload CV — elige un archivo local en cualquiera de estos
formatos y el servidor te lo convierte a markdown:
- Formatos de texto —
.md,.markdown,.txt,.html,.htmse pasan a través (HTML va vía pandoc → GFM markdown). - Formatos de Office —
.docx,.doc,.odt,.rtfse convierten vía pandoc (brew install pandocen macOS,apt install pandocen Linux). - PDF —
.pdfse extrae vía pdftotext de Poppler (brew install poppler/apt install poppler-utils). - El markdown convertido aterriza en el editor; haz clic en 💾 Save para persistir. El resultado se sanea (mismo strip XSS que el pegado).
- Tope estricto: 10 MB por upload. Archivos más grandes → 413.
- Formatos de texto —
- Desde LinkedIn — la ruta más fácil: abre Claude Code en el
proyecto padre, ejecuta
/career-ops, pega tu URL de LinkedIn, y pideextract my CV from this and write it to cv.md.
Qué se sanea
Server-side, cada PUT a /api/cv pasa por stripDangerousMarkdown:
- Tags
<script>,<iframe>,<object>,<embed>,<svg>,<style>,<form>— eliminados por completo. - Manejadores de eventos inline (
onclick=,onerror=, etc.) — eliminados. - Esquemas URI
javascript:,vbscript:,data:text/html— neutralizados.
La respuesta incluye sanitized: true siempre que se haya eliminado
alguno de los anteriores, así sabes si la fuente tenía algo
desagradable.
Tamaño máximo de body: 1 MB. Cualquier cosa más grande devuelve 413.
Otros botones
- sync-check — ejecuta
cv-sync-check.mjsen el proyecto padre. Marca inconsistencias: un proyecto listado en tu CV pero no en los arquetipos dedata/applications.md, etc. - 📄 Generate PDF — streamea
generate-pdf.mjs. El output aterriza enoutput/*.pdf. Requiere Playwright (la página Health muestra si está instalado en elnode_modulesdel padre). Cuando termina la generación, el PDF más reciente se descarga automáticamente a tu carpeta de Descargas por defecto; la lista on-page conserva cada archivo previamente generado.
Tips de tono / formato
- Un bullet = un logro con métrica. “Reducí la latencia p99 en un 38%” le gana a “mejoré el rendimiento” en cada rúbrica de evaluación.
- Secciones en este orden: Summary (3–5 líneas), Experience (reverse-chronological), Projects (máximo 5), Education, Skills (deduplicadas, sin sopa de buzzwords).
- Mantén bajo 1500 palabras. La rúbrica de scoring usa información densa; un CV desbordante se penaliza por ruido.
5. Portales y fuentes (portals.yml)
La configuración del scanner vive en portals.yml en la raíz del
padre. Tres secciones importan. Las tres secciones del SPA (abajo)
coinciden 1:1 con el schema canónico de career-ops.org de
scan-job-portals.
Atajo: la URL
#/portalsahora resuelve directamente a App settings y (cuando hay una fuente regional configurada) salta al grupo Regional sources — así un enlace#/portalsmarcado o tecleado ya no da 404 (v1.42.0).
title_filter
title_filter:
positive: [backend, engineer, senior, tech lead, golang, php]
negative: [junior, intern, frontend, ios, android, java]
seniority_boost: [Senior, Staff, Lead, Principal]
Una vacante escaneada pasa cuando su título contiene al menos una keyword positiva Y ninguna de las keywords negativas. Ajusta ambas. Las keywords son substrings case-insensitive.
seniority_boost es la tercera clave de title-filter. Las keywords
listadas aquí no filtran nada — empujan los empleos coincidentes más
arriba en los resultados, así un “Senior Backend Engineer” queda por
encima de un “Engineer”. Default: ["Senior", "Staff", "Lead"].
Ajusta para que coincida con cómo se titulan tus roles objetivo.
Empieza con 3–5 keywords positivas por claridad; amplía después.
content_filter (opcional — web-ui 1.75.0, parent #974). Un hermano de nivel superior de location_filter con las mismas listas de keywords positive / negative, pero comparadas contra el texto de la descripción / snippet de una oferta en lugar de su ubicación:
content_filter:
positive: ["python", "machine learning"]
negative: ["security clearance", "on-site only"]
Semántica idéntica a location_filter: sin clave → todo pasa; una oferta con descripción vacía/ausente pasa (no se penaliza la falta de datos); un match negative → rechazada; positive vacía → pasa; positive no vacía → debe coincidir al menos una keyword (substring sin distinción de mayúsculas/minúsculas). Lo aplican tanto las pasadas ATS como las regionales. Solo se ven afectadas las fuentes que incluyen una descripción/snippet (p. ej. RSS) — todas las demás ofertas pasan — así que habilitarlo nunca descarta silenciosamente filas de fuentes que no llevan cuerpo. Úsalo para descartar una oferta que pasa el filtro de título pero cuyo cuerpo revela un deal-breaker.
location_filter (opcional — web-ui 1.33.0, parent #570)
location_filter:
allow:
- "Remote"
- "United States"
- "Atlanta"
block:
- "India"
- "London"
- "Germany"
Filtra las vacantes escaneadas por su ubicación (subcadena, sin distinguir mayúsculas), aplicado por el barrido ATS y el regional. Semántica idéntica al scan.mjs canónico de career-ops:
- Sin
location_filter→ todas las ubicaciones pasan (por defecto). - Ubicación vacía/ausente → pasa (no se penaliza el dato faltante).
- Coincidencia en
block→ rechazada (block tiene prioridad sobre allow). allowvacío → pasa (block ya filtró).allowno vacío → debe coincidir con al menos una palabra clave.
Clave de nivel superior en portals.yml (hermana de title_filter, no anidada en russian_portals).
search_queries
search_queries:
- name: "Greenhouse — Rails Engineer"
query: 'site:job-boards.greenhouse.io "Rails Engineer" OR "Ruby on Rails" remote'
enabled: true
- name: "Ashby — Senior Backend"
query: 'site:jobs.ashbyhq.com "Senior Backend" remote'
enabled: false
search_queries impulsa el scan AI-powered Option B (/career-ops scan dentro de Claude Code / Codex). NO los ejecuta el npm run scan in-process (que solo consulta APIs públicas de tableros). Úsalos
cuando quieras descubrir roles en empresas que aún no están en
tracked_companies. Pon enabled: false para mantener una entrada
sin ejecutarla.
tracked_companies
tracked_companies:
- { name: Stripe, enabled: true, careers_url: https://job-boards.greenhouse.io/stripe }
- { name: Linear, enabled: true, careers_url: https://jobs.ashbyhq.com/linear }
- { name: JetBrains, enabled: true, careers_url: https://jobs.lever.co/jetbrains }
Campos requeridos por entrada: name y careers_url. Opcionales:
api (endpoint explícito de Greenhouse / Ashby / Lever / Workable /
SmartRecruiters / Workday), enabled: true|false para incluir /
excluir sin borrar la entrada. El ATS scanner detecta el ATS desde el
patrón de URL (job-boards.greenhouse.io/<slug> → Greenhouse, etc.)
y consulta la boards-api pública de cada empresa directamente. Las
empresas sin un ATS reconocible se omiten (la tarjeta Active
Companies en /#/scan las muestra en gris con ○).
rss (RSS / Atom boards)
tracked_companies:
- { name: LaraJobs, enabled: true, provider: rss, rss: https://larajobs.com/feed }
- { name: WeWorkRemotely, enabled: true, provider: rss, rss: https://weworkremotely.com/remote-jobs.rss }
Apunta el escáner a cualquier portal de empleo que publique un feed RSS/Atom (LaraJobs, WeWorkRemotely, RemoteOK, golangprojects, …) añadiendo una entrada con provider: rss y una clave rss: (o feed_url:) — sin cambios de código. El adaptador RSS analiza cada <item> (CDATA + entidades HTML, títulos/empresas sin etiquetas), lo normaliza a una oferta y aplica el mismo flujo title_filter / location_filter + dedup + añadir a pipeline que las fuentes ATS. Luego RSS aparece como fuente seleccionable en el desplegable de filtro de #/scan. (web-ui v1.62.x)
russian_portals
russian_portals:
sources: ["hh", "habr", "trudvsem", "getmatch", "geekjob"] # o solo uno
area: 113 # 1=Moscú, 2=SPb, 113=Rusia, 1001=remoto
per_page: 50
only_remote: false
queries:
- "Senior PHP"
- "Senior Go"
- "Тимлид PHP"
queries son matches de substring case-insensitive contra los títulos
de vacantes en hh.ru y Habr Career. Cuida el solapamiento con la
lista negativa — si "Senior PHP" está en queries pero "php"
acaba en title_filter.negative, el scan devolverá cero resultados y
la consola te avisará del conflicto.
Configurar los portales rusos — guía detallada
v1.29.0 incluye 5 adaptadores rusos. Dos no requieren nada más allá del UA por defecto (habr-career, scraping HTML; trudvsem, API open-data gubernamental — sin key, sin barrera geográfica). Dos son scrapers HTML de portales técnicos (getmatch, geekjob — tampoco requieren key). Uno es la API canónica de hh.ru, que puede devolver 403 desde IPs fuera de Rusia salvo que configures la variable de entorno HH_USER_AGENT vía App settings → API keys & runtime (o ejecutes el servidor desde una IP rusa / VPN).
Inventario de fuentes
| Clave | Etiqueta | Tipo | Auth | Restricción geográfica |
|---|---|---|---|---|
hh |
hh.ru | JSON API | HH_USER_AGENT opcional |
IPs no-RU pueden recibir 403 |
habr |
Habr Career | HTML | ninguno | ninguna |
trudvsem |
Trudvsem | JSON API (open-data) | ninguno | ninguna |
getmatch |
GetMatch | HTML | ninguno | ninguna |
geekjob |
GeekJob | HTML | ninguno | ninguna |
Paso 1 — Abre portals.yml
El archivo vive en la raíz del proyecto padre career-ops/ (NO dentro de web-ui/). Si aún no existe, copia el ejemplo que viene con el proyecto padre:
# from the parent career-ops/ root (NOT web-ui/)
cp templates/portals.example.yml portals.yml
$EDITOR portals.yml
Paso 2 — Habilita las 5 fuentes
Añade o actualiza el bloque russian_portals listando todas las fuentes que quieres escanear. El orden no importa; el scanner las recorre en el orden del registry.
russian_portals:
sources: ["hh", "habr", "trudvsem", "getmatch", "geekjob"]
area: 113 # 1=Moscow, 2=SPb, 113=Russia, 1001=remote
per_page: 50 # how many vacancies per query per source
only_remote: false # set true to keep only remote postings
queries:
- "Senior PHP"
- "Senior Go"
- "Backend Senior"
- "Тимлид PHP"
Paso 3 — Ajusta queries y filtros
queries son las cadenas que el scanner usa para buscar en cada fuente. Cada query se ejecuta una vez por fuente — 4 queries × 5 fuentes = 20 llamadas por escaneo. Mantén la lista enfocada (3–7 queries) para que el escaneo no supere el minuto. area es el código de región de hh.ru (las demás fuentes lo ignoran). per_page limita cuántas vacantes devuelve cada fuente por query. only_remote: true filtra a remoto a nivel de adaptador (la tabla de resultados aún tiene su propio chip Remoto).
Errores comunes
Colisión con la lista negativa. Si una palabra de una query ("php", "senior") también está en title_filter.negative, todos los resultados se filtran antes de verlos. El scanner emite una advertencia stderr en tiempo de escaneo — busca la línea ⚠ config: query "Senior PHP" contains "php" which is in the negative list. Soluciona quitando la palabra de la lista negative:
title_filter:
positive: [backend, senior, lead, php, go, golang, python]
negative: [junior, intern, frontend, ios, android]
russian_portals:
queries:
- "Senior PHP" # OK — "php" no longer in negative list
- "Senior Go"
Desactivar temporalmente una fuente
Para deshabilitar una fuente sin borrar sus datos, simplemente quita su clave de sources:
russian_portals:
sources: ["hh", "habr", "trudvsem"] # only 3 of 5 sources will run
Verificar la configuración
Después de guardar portals.yml:
# 1. Save portals.yml.
# 2. In the SPA, switch to #/scan.
# 3. Click 🌐 Scan now.
# 4. Watch the SSE log for the per-source line per query:
# "Senior PHP"
# hh.ru 18
# habr 21
# trudvsem 3
# getmatch 0
# geekjob 2
# A value of 0 is normal for some queries — it just means that
# source had no matches. A "geo-blocked" or "timeout" line means
# the adapter reached the site but couldn't read results.
Flujo bootstrap CLI (scan-job-portals)
El setup canónico de career-ops (ejecutar desde la raíz del padre una sola vez):
cp templates/portals.example.yml portals.yml
$EDITOR portals.yml
Eso es todo el bootstrap. Edita las tres secciones (title_filter,
tracked_companies, search_queries, y opcionalmente
russian_portals), guarda, y estás listo para escanear.
Comportamiento de bootstrap del SPA
En el primer arranque el servidor añade un bloque russian_portals:
documentado a portals.yml si falta — idempotente (el segundo boot
es un no-op porque la línea literal russian_portals: ya está). Las
secciones en inglés NO se auto-inyectan; vienen del
templates/portals.example.yml que copiaste según el bootstrap
canónico de arriba.
6. Estado (#/health)
Cada gate de setup, en badges OK / OPTIONAL / FAIL. Lee esto antes de abrir cualquier issue de “no funciona”.
Uso y coste de IA. La página Uso de IA (💳, junto a Salud) muestra los tokens de generaciones de IA en vivo por proveedor en 24 h / 7 d / 30 d / todo, con un coste estimado en USD de una tabla de precios editable (nunca se factura). Un medidor USO compacto también está fijado al fondo de la barra lateral izquierda en cada página — los mismos totales de tokens de 24h/7d/30d y un coste estimado de 24 horas, actualizado en vivo; el menú siempre queda despejado por encima, y al pulsar su cabecera se pliega.
Checks requeridos (el sistema no puede funcionar sin estos)
Node version≥ 18 — el servidor usafetchynode:testnativos.Project root— queCAREER_OPS_ROOT(env o auto-detectado) exista.cv.md,config/profile.yml,portals.yml,data/applications.md,data/pipeline.md,modes/oferta.md.
Checks opcionales (solo warnings)
Profile customized—candidate.full_nameno es el placeholder del template.GEMINI_API_KEY/ANTHROPIC_API_KEY— configuradas en.env.(server uses default UA)— solo importa si escaneas hh.ru desde fuera de Rusia.Playwright (parent node_modules)— requerido para generación de PDF ycheck-liveness.mjs. Instálalo concd $CAREER_OPS_ROOT && npm install && npx playwright install chromium.Parent project dependencies—cd $CAREER_OPS_ROOT && npm installsi faltan.- Directorios
data/,reports/,output/,jds/— se auto-crean en la primera escritura.
Cuando el servidor está expuesto más allá de loopback (HOST=0.0.0.0)
las rutas absolutas y la versión Node exacta se reemplazan por
"hidden" en la respuesta para que un vecino curioso no pueda hacer
fingerprinting de tu instalación.
Botones de ejecución
- ▶ Doctor ejecuta
node doctor.mjsy muestra el output en un modal. - ▶ Verify pipeline ejecuta
node verify-pipeline.mjs.
7. Búsqueda (#/scan)
El scanner crawlea cada board habilitado, deduplica contra tu
historia, y escribe los hits a data/last-scan.json y
data/pipeline.md.
Buscar + Excluir. El cuadro Buscar trata las comas como O (“roles a encontrar”); el nuevo campo Excluir oculta filas que coincidan con cualquier palabra separada por comas. Ambos se guardan con tus búsquedas.
Scan de un clic (SPA)
🌐 Scan ejecuta cada fuente habilitada en una sola pasada:
- Greenhouse / Ashby / Lever / Workable / SmartRecruiters / Workday
(la pasada ATS) para cada empresa de
tracked_companiescon una URL ATS reconocible. - Los agregadores de v1.75.0 para cada entrada de
tracked_companiesque opte por uno: RemoteOK / Remotive / Working Nomads (feeds remotos de todo el board,provider: <slug>) e IBM / Arbeitsagentur / Glints / Jobstreet · SEEK (config-driven, bloque<provider>:por entrada). - API de hh.ru + HTML de Habr Career para cada query de
russian_portals.
Dos fases en un clic (v1.29.2). El único botón 🌐 Scan dispara TANTO el sweep ATS COMO el regional en un único stream SSE. En el log verás dos encabezados de fase, en orden:
▶ ATS scan (Greenhouse + Ashby + Lever)— boards ATS EN.▶ Regional scan (hh.ru + Habr Career)— 5 fuentes RU del registry.
Cada fase termina con un resumen ✓ done · NEW=N. Si solo ves la fase ATS, tu stand está en un build pre-v1.29.2 — actualiza. Antes de v1.29.2 el cliente SSE cerraba en el primer done y la fase regional se descartaba silenciosamente.
El log SSE en vivo se va mostrando en el panel derecho mientras corre
el scan. Haz clic en Stop (o simplemente navega fuera) para
abortar — el servidor cancela los requests HTTPS en vuelo vía
AbortController.
Filtrado de resultados
Debajo del log, la tabla de resultados renderiza filas de
data/last-scan.json.
v1.78.1 — autoactualización en vivo. La tabla de resultados ahora se actualiza automáticamente mientras corre un escaneo y una vez más justo al terminar — sin recargar a mano ni cambiar de página.
v1.80.0 — Máximo por fuente y cuarentena de fuentes. El campo Máximo por fuente junto al botón Escanear limita cuántos empleos aporta cada bolsa (vacío/0 = sin límite, el valor por defecto) — útil cuando una bolsa enorme dominaría de otro modo. Por separado, cualquier fuente que devuelva un 404 / 410 permanente se escribe en
data/scan-quarantine.jsony se omite en escaneos posteriores (autorreparación: se reintenta tras 14 días), de modo que los slugs muertos dejan de inundar el log. Desactívalo conscan_quarantine: falseenportals.yml.
Filtros:
- Texto libre — match de substring contra título / empresa.
- Dropdown Source — Arbeitsagentur / Ashby / BambooHR / Breezy HR / Comeet / GeekJob / Glints / Greenhouse / GetMatch / Habr Career / hh.ru / IBM / Jobstreet · SEEK / Lever / Personio / Recruitee / RemoteOK / Remotive / RSS / SmartRecruiters / SolidJobs / Teamtailor / Trudvsem / We Work Remotely / Workable / Workday / Working Nomads (auto-rellenado desde
GET /api/scan/sources). - Dropdown Remote / Hybrid / Onsite.
- Dropdown Country (v1.78.0) — un filtro de geografía poblado a partir de los países detectados en los resultados actuales, cada uno mostrado con su emoji de bandera y un recuento (p. ej.
🇩🇪 Germany (12)). Elige uno para quedarte solo con los puestos ligados a ese país. La detección mapea la ubicación en texto libre de una oferta (nombres de países/alias + ~100 grandes ciudades del mercado laboral) a un país; es conservadora y nunca adivina, así que una oferta cuya ubicación no pueda resolverse — o un anuncio puramente “Remote” — se queda bajo All countries. Combínalo con el desplegable de tipo de trabajo para encontrar puestos ligados a un país y remotos. - Dropdown Publicado en los últimos (v1.80.0) — un filtro de antigüedad del lado del cliente (Últimas 24 horas / 7 días / 30 días). Las filas cuyo
pubDatesea más antiguo se ocultan; las filas sin fecha listada pasan (no se penalizan los datos faltantes). - ★ Favoritos (v1.80.0) — haz clic en la ☆ de cualquier fila para marcar un empleo (guardado en
localStoragepor URL); marca ★ Favoritos en el panel de filtros para mostrar solo las filas marcadas. Las estrellas sobreviven a los escaneos y a las recargas. - Búsquedas guardadas (v1.80.0) — la barra encima de los filtros: pon nombre al conjunto de filtros actual y pulsa 💾 Guardar, luego vuelve a aplicarlo desde el desplegable o 🗑 Elimínalo. Se guarda en
localStorage; un valor corrupto/editado se restablece limpiamente a vacío. - Chips de stack (PHP / Go / Backend / Senior / …) —
auto-detectados por fila por
Skills.detectTechySkills.detectLevel. Intersección multi-select — seleccionar `PHP- Senior` muestra filas que tienen AMBAS.
- Chips dinámicos debajo de los chips de stack estáticos — top-25 tokens capitalizados más frecuentes de los títulos, así el UI se adapta a cualquier rol que de hecho escanees (marketing, design, finance…) en vez de quedar fijado al vocabulario de backend engineer.
Tarjeta Active Companies
Una tarjeta colapsable listando cada empresa de portals.yml con su
estado de scan:
- Tag verde ✓ — soporte API directo (Greenhouse / Ashby / Lever / Workable / SmartRecruiters / Workday).
- Tag gris ○ — fallback a prompt web-search (sin match de API).
Haz clic en el nombre de la empresa → rellena el filtro de
resultados arriba con ese nombre. Haz clic en el icono ↗ → abre
el careers_url de la empresa en una pestaña nueva.
Flujo CLI de scan (scan-job-portals)
Dos formas de escanear desde el lado CLI (ambas depositan URLs en el
mismo data/pipeline.md que el SPA lee):
Option A — script directo (~30 s, cero tokens de IA consumidos):
npm run scan # todos los boards Greenhouse/Ashby/Lever
npm run scan -- --dry-run # preview sin persistir
npm run scan -- --company Anthropic # acota a una empresa rastreada
Funciona solo para Greenhouse / Ashby / Lever / Workable / SmartRecruiters / Workday (URLs ATS reconocibles). No se consumen tokens de IA — consulta las APIs públicas de los tableros directamente.
Option B — scan AI-powered desde navegador:
/career-ops scan
Dentro de Claude Code / Codex / Cursor / Gemini CLI. Usa tokens del
modelo. Visita cada página de tracked_companies directamente y
puede descubrir boards sin API (career pages, ATS custom, portales
regionales). Más lento pero más amplio. Útil cuando una pasada ATS
no devuelve nada para un target que sabes que está contratando.
Output (ambos paths) — nuevos JD URLs añadidos a
data/pipeline.md, cada URL visitada loggeada a
data/scan-history.tsv (dedup entre todos los scans futuros),
resumen impreso: empresas escaneadas · empleos encontrados · filtrados
por título · duplicados omitidos · nuevas ofertas añadidas.
Umbrales de acción por puntuación (aplica después de que
/career-ops pipeline puntúe en batch las URLs nuevas):
| Puntuación | Siguiente paso recomendado |
|---|---|
| ≥ 4.5 | /career-ops apply — fit alto, envía de inmediato |
| 4.0 – 4.4 | aplica, o /career-ops contacto para warm intro |
| 3.5 – 3.9 | /career-ops deep — investiga primero |
| < 3.5 | salta salvo razón personal específica |
El #/dashboard y el #/tracker del SPA resaltan cada fila en 4.0
o por encima para que puedas tomar acción sin re-ejecutar nada.
Comandos de seguimiento
Después del scoring, los seguimientos canónicos son:
/career-ops apply— rellena candidatura con respuestas adaptadas/career-ops contacto— redacta outreach a LinkedIn / email/career-ops deep— investiga empresa / rol a fondo/career-ops tracker— visualiza el estado del pipeline
hh.ru — escaneado desde la web (desde julio de 2026 requiere IP rusa)
hh.ru se escanea leyendo su web pública de búsqueda (hh.ru/search/vacancy), igual que Habr Career — sin clave ni configuración. Sin embargo, desde julio de 2026 hh.ru devuelve HTTP 451 (bloqueo legal regional) a las IP de fuera de Rusia, así que el escaneo solo funciona desde una IP rusa — ejecuta el servidor desde Rusia o mediante una VPN con salida rusa. Al primer 451 (o un 403 anti-bot) el escáner desactiva hh.ru para el resto de la pasada y lo indica en el log, de modo que las demás fuentes rusas terminan igualmente. La API JSON (api.hh.ru) no se usa a propósito: devuelve 403 forbidden a todo cliente programático sea cual sea la IP o el User-Agent.
Incluso desde una red que parece correcta, hh.ru puede marcar la IP de salida como VPN/proxy (cualquier IP de datacenter/hosting cuenta) y redirigir el escaneo con 302 a un interstitial /vpncheeck (“VPN мешает работе сайта”) que devuelve HTTP 200 con cero vacantes. El escáner detecta esta redirección, desactiva hh.ru durante el resto de la ejecución y lo indica en el log. La solución está en la red: asegúrate de que el tráfico salga realmente por una IP residencial — un VPN o proxy a nivel de sistema suele seguir activo aunque el interruptor del navegador esté apagado (comprueba tu IP de salida real, p. ej. en api.ipify.org).
8. Vacantes (#/pipeline)
Inbox de URLs esperando ser evaluadas. Vive en data/pipeline.md.
Tira de resumen. Una tira compacta arriba muestra tu pipeline de un vistazo — cuántas URL hay en la bandeja, cuántas en seguimiento y los recuentos Applied/Responded/Interview/Offer, cada uno enlaza al tracker.
Añadir URLs
Tres formas:
- Tipea / pega una URL en el input + haz clic en + Add.
- Usa la búsqueda global de la barra superior (su badge muestra Enter):
pega cualquier enlace
http(s)://…y pulsa Enter para abrir el auto-pipeline; escribe cualquier otro texto y Enter salta a#/scancon ese término pre-rellenado (v1.78.1). Ctrl/Cmd+K sigue enfocando el cuadro donde el navegador lo permite. El logo de marca regresa al panel. - Ejecuta un Scan (ver arriba) — los hits frescos van al pipeline automáticamente.
Cada URL pasa por isValidJobUrl() del lado del servidor. Loopback
(localhost, 127.0.0.1), file://, javascript:, IP literales, y
strings con chars de template (<, >, ") — todos 400.
Panel de vista previa server-side
Haz clic en cualquier fila del pipeline para cargar una vista previa
a la derecha. La mayoría de los boards ATS no envían cabeceras CORS
así que el navegador no puede fetchearlos directamente; el servidor
proxea la petición, elimina <script> / <style> / tags HTML, y
devuelve hasta 8 KB de texto plano.
El proxy de preview camina las redirecciones manualmente con
validación SSRF por hop — cada cabecera Location re-pasa por
isValidJobUrl(), así un board hostil no puede hacerte rebotar a
loopback / IP privada / file://. Capado a 3 hops, timeout de 15
segundos.
Acciones de fila
- ▶ — salta a
#/evaluate?url=…con la URL pre-rellenada. - ✕ — elimina la URL de
data/pipeline.md.
Botones arriba a la derecha
- ⚡ Evaluate first — abre la primera URL encolada en la página Evaluate, lista para puntuar.
- Scan — vuelve al scanner si quieres más URLs.
9. Evaluar (#/evaluate)
Puntúa una sola Job Description contra cv.md y
config/profile.yml. Devuelve una evaluación estructurada A–G según
modes/oferta.md más una puntuación 0–5.
Input
Pega el JD en el textarea, o llega aquí desde #/pipeline con
?url=<href> — la página fetchea la URL a través del mismo proxy
SSRF-safe usado para previews del pipeline y pre-rellena el textarea.
Haz clic en 💾 Save JD para persistir el JD a
jds/jd-<date>-<ts>.txt para el audit trail (o pasa save: true en
la llamada API — mismo efecto).
Cadena de fallback
- Anthropic — preferida cuando
ANTHROPIC_API_KEYestá configurada. El servidor agrupacv.md,config/profile.yml,modes/_shared.mdymodes/oferta.mden un bloque<project_context>antes del prompt (cada archivo capado a 16 KB, prompt completo soft-cap a 200 KB). Devuelve markdown fundamentado directo a la página. - Gemini — cuando solo
GEMINI_API_KEYestá configurada. El servidor spawneagemini-eval.mjscon el JD como archivo temporal. El modelo del free tier (gemini-2.0-flash) está bien para scoring rutinario. - Manual — sin clave configurada. La página devuelve un prompt completamente formado que puedes pegar en Claude Code, ChatGPT, o cualquier otro LLM.
Secciones de salida (A-F canónicas de career-ops.org)
Realineación v1.15.0. Las letras de bloque ahora coinciden con el schema canónico de career-ops.org. Los reportes pre-v1.15 usaban A–G (con
C=Risks,F=Verdict,G=Legitimacy); seguimos renderizándolos tal cual por compatibilidad hacia atrás, pero los reportes nuevos emiten A–F con la semántica canónica de abajo. Score y Legitimacy ahora viven en el header del reporte (score: 4.2/5,legitimacy: High|Medium|Low).
A. Role Summary — recapitulación de 3 bullets (riesgos
mencionados inline).
B. CV Match — top 3 skills coincidentes + top 3 ausentes.
C. Strategy — recomendación: apply now / contacto first / deep
first / skip. Antes de v1.15 era Risks.
D. Compensation — relativa a tu target.comp_total_min_usd
(legacy) o compensation.target_range (canónico).
E. Personalization — ángulo para liderar, framing por arquetipo,
ganchos a mencionar en cover letter / outreach. Antes de v1.15 era
Application Strategy.
F. STAR stories — 1–3 bloques S-T-A-R listos-para-pegar adaptados
al rol. Antes de v1.15 era Verdict (puntuación cruda); la
puntuación ahora aparece en el header del reporte junto a
legitimacy.
Guardar el reporte
Haz clic en 💾 Save report (o usa el toggle de save en la llamada
API) para persistir el markdown a
reports/<date>-<company>-<role>.md. El header parseado del reporte
(Score / Legitimacy / URL) aparece en la página Reports y en el
Dashboard.
Evaluación en batch cuando tienes 10+ JDs
Para un solo JD esta página #/evaluate es la herramienta correcta.
Para 10+ URLs encoladas en el pipeline, el click-through por JD es
impráctico — salta a la subsección Batch evaluate del §14
(ejecutando ./batch/batch-runner.sh desde el padre), deja que
trabaje toda la noche, después vuelve a #/reports / #/tracker
para los resultados. Flujo completo:
batch-evaluate-offers.
10. Reportes (#/reports)
Navega cada evaluación guardada. Las tarjetas muestran título, fecha, flag de legitimidad, y puntuación (color-coded: verde ≥ 4.0, amarillo ≥ 3.0, rojo por debajo).
Haz clic en una tarjeta para leer el markdown completo. Paginación: 12 por página; controles abajo.
La vista de reporte individual también tiene:
- ← All reports — vuelve al grid.
- 🔗 Open JD — abre el job posting original en una pestaña nueva.
11. Tracker (#/tracker)
El CRM. Una fila por candidatura; vive en data/applications.md como
tabla GitHub-Flavored Markdown.
Flujo de estados
Evaluated → Applied → Responded → Interview → Offer /
Hired / Rejected / Discarded / SKIP.
Hired (v1.118.0) es el estado final feliz: la oferta fue aceptada. El tracker lo marca con una insignia celebratoria y lo recibe con un banner de «trabajo conseguido».
La whitelist de estados se aplica del lado del servidor; enviar
cualquier otra cosa en un POST /api/tracker cae por defecto a
Evaluated. La transición canónica Evaluated → Applied es
automática cuando confirmas Submitted. al final de /career-ops apply (ver §14).
Layout de columnas
| Columna | Qué es |
|---|---|
# |
Auto-numerado, con ceros a la izquierda (001, 002, …). |
Date |
Fecha ISO (YYYY-MM-DD). Default: hoy. |
Company |
Texto libre. Pipes (|) y newlines se escapan automáticamente. |
Role |
Lo mismo. |
Score |
Formato N/5 (p. ej. 4.2/5). |
Status |
Enum de la whitelist. |
PDF |
✅ una vez que generate-pdf.mjs tuvo éxito para esta fila. |
Report |
Enlace markdown al reports/*.md correspondiente. |
Notes |
Texto libre, capado a 200 chars. |
Filtros
- Dropdown Status.
- Dropdown Score —
≥ 4.0(alto),≥ 3.0(medio),< 3.0(bajo). - Search — match de substring a través de empresa + rol.
Cada filtro resetea el paginador a la página 1. 25 filas por página.
Botones de mantenimiento
- ▶ Normalize ejecuta
normalize-statuses.mjs— re-canonicaliza la ortografía de estados (applied→Applied,interview→Interview). - ▶ Dedup ejecuta
dedup-tracker.mjs— elimina duplicados case-insensitive por(empresa, rol). - ▶ Merge ejecuta
merge-tracker.mjs— trae entradas pendientes debatch/tracker-additions/*.tsv(donde el flujo batch del padre deposita candidaturas enviadas vía el helper Apply). Deduplica y archiva los archivos procesados abatch/tracker-additions/merged/. Ver batch-evaluate-offers para el flujo batch upstream.
Añadir filas
POST /api/tracker — body { company, role, score?, status?, url?, reportSlug?, notes?, date? }. Dedup por (empresa, rol)
case-insensitive. Desde el UI, la página Evaluate ofrece un botón
“Add to tracker” tras una puntuación exitosa.
12. Investigación (#/deep)
Genera un briefing estructurado de la empresa: snapshot, cultura de ingeniería, news recientes, sentiment de Glassdoor, proceso de entrevistas, palancas de negociación, tres preguntas inteligentes para hacerle al reclutador.
Input
Dos campos — nombre de empresa y (opcional) rol. La plantilla del
modo (modes/deep.md) es la que da forma a la estructura.
Rutas de salida
Misma cadena de fallback que Evaluate:
- Anthropic live (preferida) —
bundleProjectContextinlinea cv + profile +_shared.md+deep.md. Output: 10–30 KB de markdown fundamentado guardado ainterview-prep/<company>-<role>.md. - Gemini live — invocación de
gemini-eval.mjs. Mismo target de guardado. - Prompt manual — la página te entrega un prompt listo para Claude Code (que tiene WebFetch + WebSearch y puede hacer research real).
Tips
- Anthropic en
claude-sonnet-4-6típicamente devuelve ~13 KB de texto útil en 1–3 minutos por llamada. - El SDK de Anthropic no tiene web search built-in. Para roles donde necesitas news frescas + sentiment de Glassdoor, pega el prompt manual en Claude Code y deja que use su herramienta WebFetch.
- Las ejecuciones live se facturan; una llamada de deep-research con Sonnet 4.6 cuesta ≈ $0.30–0.50.
13. Prompts de modos (las siete páginas /#/<mode>)
Tablero de cadencia (v1.117.0). La página de seguimiento ahora abre con un tablero de cadencia determinista alimentado por el followup-cadence.mjs del padre: urgencia por candidatura (🔴 urgente / 🟠 atrasado / 🟡 en espera / 🔵 frío) con días al siguiente paso, más un botón Sembrar fechas de seguimiento que fija una primera fecha para cada fila Applied (followup-seed.mjs --backfill). Sin los scripts del padre, el tablero muestra un aviso honesto de “no disponible”.
Siete generadores de prompts: ideas de Project, planes de
Training, emails de Follow-up, evaluaciones Batch,
Outreach a reclutadores, one-pagers de Interview prep, y
retrospectivas de Patterns. Cada uno envuelve una plantilla
modes/<slug>.md específica:
| Página | Slug | Propósito |
|---|---|---|
#/project |
project |
Adaptar un proyecto del portfolio para un rol objetivo. |
#/training |
training |
Análisis de skill-gap → currículum. |
#/followup |
followup |
Borrador de email post-entrevista. |
#/batch |
batch |
Prompt de evaluación batch multi-JD. |
#/contacto |
contacto |
Mensaje de outreach a un reclutador / referido. |
#/interview-prep |
interview-prep |
One-pager de prep para una ronda específica. |
#/patterns |
patterns |
Análisis reflexivo “¿Qué patrones me hicieron exitoso?”. |
Forma compartida
Cada página tiene un pequeño formulario (los campos son específicos del modo), un botón ▶ Generate prompt (manual), y — cuando hay una clave Anthropic o Gemini presente — un botón ⚡ Run live que asciende a primary.
Hacer clic en ▶ Generate prompt devuelve el prompt ensamblado con
tus valores del formulario JSON-stringificados en un bloque
User-supplied context:, seguido por la plantilla modes/<slug>.md
verbatim. Copia y pega en tu LLM de elección.
Hacer clic en ⚡ Run live envía el mismo prompt a Anthropic (o
Gemini), con cv.md + profile.yml + _shared.md inlineados vía
bundleProjectContext. El resultado se renderiza en la página, es
copiable, y descargable como .md.
Las siete páginas son una allowlist explícita — los modos que tienen
una ruta dedicada (oferta → Evaluate, deep → Deep research) y los
modos que el proyecto padre solo soporta dentro de Claude Code
(apply, scan, pipeline, tracker, pdf, latex, ofertas,
auto-pipeline) deliberadamente se quedan fuera de este UI.
14. Checklist de aplicación (#/apply)
Una vez que has decidido aplicar, esta página Apply helper genera una
checklist de envío para el paso de candidatura real. NO
auto-rellena formularios — ese flujo se queda en /career-ops apply
dentro de Claude Code, que usa Playwright en el proyecto padre.
Modo checklist SPA (#/apply)
La checklist del SPA es para usuarios que prefieren rellenar el formulario a mano sin invocar Playwright. Cubre:
- Ejecuta
/career-ops apply <url>en Claude Code para leer el formulario vía Playwright (omite este paso si lo rellenas a mano). - Verifica que el posting siga vivo (
check-liveness.mjs). - Confirma que el CV es el último (
cv-sync-check.mjs, después PDF si la puntuación ≥ 4.0). - Adapta la cover letter / respuesta “Why us?” usando proof points
STAR+R de
cv.md. - Responde con honestidad las preguntas de EEO / sponsorship / start-date.
- Guarda las respuestas rellenadas en
interview-prep/{company}-{role}.mdantes de enviar. - NUNCA auto-enviar — tú (el humano) haces clic en el botón final.
- Tras enviar: añade una fila a
data/applications.md(o escribe un TSV abatch/tracker-additions/).
Rellenado manual vs asistido por Playwright
Dos rutas para el envío real:
- Manual — abre la página de careers en una pestaña normal del navegador, sigue la checklist SPA de arriba, copia/pega las respuestas. No se requiere Playwright. Úsalo cuando el formulario es corto o no tienes Chromium instalado.
- Asistido por Playwright — ejecuta
/career-ops apply <company>en Claude Code (proyecto padre). Playwright abre su propio navegador, lee cada campo del formulario, devuelve respuestas numeradas en borrador. Tú aún haces clic en Submit. Úsalo cuando el formulario es largo, dinámico, o quieres el audit trail de qué preguntas tuvieron qué respuestas.
Flujo CLI completo de apply (apply-for-a-job)
Prerrequisitos:
- Ejecuta
/career-ops pipelineprimero para que el JD tenga un reporte de evaluación bajoreports/. El comando apply depende de una evaluación existente; sin una, ejecuta el pipeline inicialmente. - Tener el reporte y el profile cargados.
- Recomendado: Playwright instalado (
npx playwright install chromium— ver Setup de Playwright abajo). Cae a WebFetch (preview de formulario solo texto, sin click-fill) cuando falta.
Flujo numerado (los 8 pasos canónicos):
-
Ejecuta el comando con el nombre de la empresa:
/career-ops apply <company>Ejemplo:
/career-ops apply Anthropic. Sin argumento, comparte un screenshot del formulario, el texto del formulario pegado, o la URL de la candidatura en el siguiente turno. -
Localiza el reporte. El sistema encuentra la evaluación correspondiente en
reports/(la creada por/career-ops pipelineo#/evaluatepreviamente). -
Abre el formulario. Playwright lanza una ventana del navegador automáticamente — tú NO la abres.
-
Lee los campos. El sistema lee y parsea cada campo del formulario (label, type, required, options para los selects).
-
Genera respuestas. career-ops crea respuestas adaptadas para cada campo basadas en tu profile, proof points, y el rol.
-
Devuelve lista numerada. Recibes respuestas ordenadas para coincidir con el layout del formulario — campos simples (name, email) primero, campos de texto libre (cover letter, “Why us?”) al final. Los items marcados apuntan a cosas que necesitan atención humana — salary anchor, detalles del currículum ausentes, preguntas opcionales.
-
Rellenado manual. Tú copias y pegas cada respuesta en el campo correspondiente. Este paso es manual, no automatizado. Revisas cada respuesta primero.
-
El usuario envía. Tú haces clic en Submit tú mismo. career-ops nunca hace clic en Submit. Confirma la finalización tipeando en el chat:
Submitted.
Actualizaciones automáticas al confirmar Submitted.:
- El estado cambia
Evaluated → Appliedendata/applications.md. - Las respuestas rellenadas persisten en la Section G del reporte para referencia futura.
Handoff al tracker:
/career-ops tracker
Monitorea el estado de todo tu pipeline, independientemente de la puntuación del rol.
Batch evaluate (batch-evaluate-offers)
Cuando tienes 10+ JDs para puntuar a la vez (el #/evaluate del SPA
uno-por-uno es impráctico para ese volumen), usa el batch runner
desde el CLI.
Archivo de entrada — batch/batch-input.tsv (tab-separado):
| Columna | Propósito |
|---|---|
id |
Número secuencial único |
url |
Enlace completo del job posting |
source |
Plataforma de origen (LinkedIn, Greenhouse, etc.) |
notes |
Detalle contextual opcional |
Fila de ejemplo:
1<TAB>https://jobs.example.com/senior<TAB>LinkedIn<TAB>
Flags de ./batch/batch-runner.sh:
--dry-run— Previsualiza ofertas pendientes sin evaluación. Ejecuta esto siempre primero para validar el TSV.--parallel N— Ejecuta N workers simultáneamente (1, 2, o 3 recomendados).--min-score X.X— Omite persistir ofertas que puntúen por debajo del umbral. Útil para quedarte solo con reportes de roles de alto fit.--retry-failed— Reprocesa solo las ofertas que dieron error en la ejecución anterior (fallos de red, rate limits).--max-retries N— Reintenta las ofertas fallidas hasta N veces (default: 2).--model NAME— Modelo Claude pasado aclaude -p --model(career-ops 1.8.0, #504). Sin valor = el modelo por defecto de tu suscripción Claude Max. Usa uno más barato para lotes grandes, p. ej.claude-sonnet-4-6. En#/config→ no; en#/batchaparece como el campo Model (web-ui 1.31.0).--start-from N— Omite los IDs de oferta por debajo de N (reanuda un lote parcialmente procesado). En#/batchaparece como el campo Desde # (web-ui 1.31.0).
Secuencia estándar:
-
Edita
batch/batch-input.tsv— una fila por JD. -
Dry-run (recomendado primero):
./batch/batch-runner.sh --dry-run -
Ejecuta — secuencial o en paralelo:
./batch/batch-runner.sh # uno a uno ./batch/batch-runner.sh --parallel 2 # dos concurrentes ./batch/batch-runner.sh --parallel 3 # tres concurrentes ./batch/batch-runner.sh --parallel 2 --min-score 4.0 # solo persistir high-fit -
Reintentar fallos (red / rate-limit):
./batch/batch-runner.sh --retry-failed --max-retries 3 -
Los reportes aterrizan en
reports/como{id}-{company}-{YYYY-MM-DD}.md. Las filas resumen se añaden abatch/tracker-additions/. -
Merge al tracker:
node merge-tracker.mjs # aplica las batch additions node merge-tracker.mjs --dry-run # previsualiza el mergeEl comando merge deduplica las entradas y archiva los archivos procesados a
batch/tracker-additions/merged/.
El SPA expone los reportes resultantes en #/reports (paginados,
con pill de score coloreada) y las filas del tracker bajo #/tracker
— exactamente como si hubieras añadido cada uno a través de
#/evaluate. Combínalo con el botón de mantenimiento ▶ Merge en
#/tracker si prefieres no bajar al CLI.
Setup de Playwright (set-up-playwright)
Requerido para dos features de career-ops:
- Form-fill en
/career-ops apply(paso 3 de arriba — Playwright abre el navegador, lee labels de campos, sugiere respuestas). - Generación de PDF vía
/career-ops pdfy el botón 📄 Generate PDF del SPA en#/cv/#/reports/:slug/#/evaluate/#/deep/#/interview-prep.
Fallback cuando falta Playwright: el flujo apply cae a WebFetch (preview de formulario solo texto, sin click-fill). La generación de PDF simplemente da error.
Setup principal (ejecutar desde la raíz del padre career-ops):
# Instalar Chromium para Playwright
npm install
npx playwright install chromium
# Registrar el MCP de Playwright para que Claude Code pueda manejar formularios
claude mcp add playwright npx @playwright/mcp@latest
# Verificar los tres componentes (Chromium, lib de Playwright, MCP)
npm run doctor
Registro MCP alternativo — añadir a
.claude/settings.local.json:
{
"mcpServers": {
"playwright": {
"command": "npx",
"args": ["-y", "@playwright/mcp@latest"]
}
}
}
Notas de comportamiento:
- Headless por defecto. Playwright opera silenciosamente. Para
ver el navegador en acción, dile a Claude
open up with playwright the browser and fill out the entire form. - Tres roles en un paquete — la instalación npm de Playwright te
da la librería de automatización del navegador, el motor de
renderizado de PDF para
/career-ops pdf, y (vía el MCP) el workflow de form-fill dentro de Claude Code. - Verifica antes de confiar en él —
npm run doctorconfirma que los tres están operativos. La página Health del SPA expone un checkPlaywright (parent node_modules)que falla rápido si falta.
15. Preparación para entrevistas
Esta es la fase post-research, pre-interview. Tres artefactos en esta app convergen:
- Archivos de deep-research guardados bajo
interview-prep/, uno por pareja company-role que ejecutaste. Navégalos desde la página Deep research o directamente vía/api/interview-prep. - Modo Patterns (
#/patterns) — genera un prompt auto-reflexivo: “a través de mis últimas N entrevistas / ofertas / rechazos, ¿qué patrones se mantienen?” Útil cuando hayas acumulado 5+ filas en el tracker. - Modo Interview-prep (
#/interview-prep) — pre-rellena un one-pager para una ronda upcoming específica (behavioral, technical, system design). El output va al mismo folderinterview-prep/.
Workflow recomendado
Para cada entrevista que tengas agendada:
- Re-ejecuta Deep (o abre el archivo guardado) el día anterior.
#/interview-prep— genera un one-pager para la ronda específica. Pégalo en tus notas.- Rondas de System design / coding — abre
#/trainingy pide un refresher de 30 minutos dirigido sobre el subsistema específico que el JD enfatiza. - Rondas de compensación — abre el archivo de deep-research, salta a “Negotiation leverage points.” Lleva 2–3 datos específicos (banda de Glassdoor, funding reciente, oferta comparable en otra empresa).
- Rondas behavioral — saca historias STAR+R de tu
cv.mdque aterrizan en la sección B del reporte de Evaluate original.
Tras la entrevista, inmediatamente:
- Actualiza la fila del tracker: estado →
Responded(despuésInterview,Offer, etc.). - Ejecuta
#/followuppara redactar el email de agradecimiento. - Si conseguiste inteligencia nueva (rango de compensación,
composición del equipo, sorpresa del stack tecnológico), edita el
interview-prep/<company>-<role>.mdguardado con## Post-round notesasí future-you lo tiene.
16. Activity log + Troubleshooting
Activity log (#/activity)
Audit trail de cada request state-changing que llega al servidor. Registra: adiciones al pipeline, escrituras al tracker, guardados de CV, guardados de JD, ejecuciones de evaluate, ejecuciones de deep-research, ejecuciones de scan, cambios de configuración, ejecuciones de modos.
Los secretos (ANTHROPIC_API_KEY, GEMINI_API_KEY) se redactan al
entrar; nunca verás un valor real de clave en data/activity.jsonl.
Filtra por prefijo de acción (pipeline., cv., evaluate,
scan., etc.). 25 filas por página; el servidor devuelve hasta los
500 eventos más recientes.
Troubleshooting
| Síntoma | Causa probable | Solución |
|---|---|---|
Página Health roja en cv.md |
Primer arranque, el archivo aún no existe | touch $CAREER_OPS_ROOT/cv.md y después refresca. |
Health roja en Profile customized |
candidate.full_name aún dice Jane Smith |
Edita config/profile.yml. |
hh.ru: HTTP 403 en el log del scan |
IP no rusa, sin (server uses default UA) |
Registra una app en dev.hh.ru/admin, usa una IP / VPN rusa. |
gemini-eval.mjs: ERR_MODULE_NOT_FOUND |
Dependencias del proyecto padre no instaladas | cd $CAREER_OPS_ROOT && npm install. |
| Errores al Generate PDF | Playwright no instalado en el padre | cd $CAREER_OPS_ROOT && npx playwright install chromium. |
/career-ops apply dice “no report found” |
El pipeline nunca puntuó este JD | Ejecuta /career-ops pipeline (o #/evaluate) primero; ver prerrequisitos del §14. |
batch-runner.sh: no such file |
Ejecutando desde el directorio incorrecto | cd $CAREER_OPS_ROOT antes de invocar ./batch/batch-runner.sh. |
El servidor reporta EADDRINUSE: 4317 |
Instancia vieja aún corriendo | pkill -f 'node server/index.mjs' y reinicia. |
| Llamada LLM live cuelga > 2 min | Prompt enorme o Anthropic lento | Revisa el flag de Anthropic en /api/health; el servidor pone soft-cap a los prompts en 200 KB y devuelve 413. |
Pipeline preview muestra (unsafe redirect) |
El posting redirigió a una IP privada / loopback | Es un feature de seguridad (REVIEW-B1). El target de redirect se rechaza y la URL original se mantiene sin cambios. |
| El texto de una fila del tracker rompe la tabla | Pipe en el nombre de empresa pre-v1.9.1 | Actualiza a v1.9.1+ — los pipes se escapan end-to-end (BF-1). |
npm test falla en clon fresco |
Los tests asumen el layout del proyecto padre | Usa CAREER_OPS_ROOT=$(mktemp -d) y bootstrapea fixtures. |
Para diagnóstico más profundo: ejecuta ▶ Doctor en la página Health, copia el output, y busca el issue en el tracker en https://github.com/Fighter90/career-ops-ui/issues.
17. Cómo añadir una nueva fuente de portal de empleo
career-ops-ui trata cada bolsa de empleo como un adapter — un único archivo bajo server/lib/sources/<slug>.mjs que sabe cómo obtener y normalizar los resultados de una bolsa concreta. A partir de v1.118.0 el registro server/lib/sources/ incluye 59 adapters — 54 en inglés + 5 rusos. El conjunto en inglés abarca los principales ATS (Greenhouse / Ashby / Lever / Workable / SmartRecruiters / Workday), agregadores de todo el board seleccionados por un provider: explícito (RemoteOK, Remotive, We Work Remotely, NoDesk, Get on Board, Amazon, …), y ATS por tenant autodetectados desde un host careers_url o una URL api: explícita (BambooHR, Personio, Recruitee, Teamtailor, Avature, SAP SuccessFactors, …). La lista completa nunca hace falta contarla a mano aquí — se autodescubre desde server/lib/sources/ y se muestra en vivo en el desplegable Source de #/scan. Consulta §5 para el YAML y docs/portals-examples.md para entradas listas para copiar y pegar.
v1.69.0 (P-14) — auto-descubrimiento drop-in. Añadir una 12.ª fuente es ahora una operación de soltar el archivo. El registro (
server/lib/sources/registry.mjs) ya no mantiene una lista a mano — al arrancar escanea esta carpeta (readdirSync+import()dinámico) y recoge el bloqueexport const metade cada*.mjs. Escribe el adapter, declara sumeta, y aparece inmediatamente en el escáner, en el desplegable de filtros de#/scany en el dispatcher RU — sin editarregistry.mjs. (Las fuentes RU siguen necesitando una línea en elportals.ymldel proyecto padre; ver Paso 5.)
Paso 1 — Escribe el adapter
Crea server/lib/sources/<slug>.mjs. Hay dos patrones según si la fuente
tiene API JSON o solo renderiza HTML:
Fuente con API (lo más limpio — úsalo cuando el sitio tenga un endpoint de datos abierto):
// server/lib/sources/example.mjs
const ENDPOINT = 'https://example.com/api/v1/vacancies';
const UA = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) ...';
// v1.69.0 (P-14) — self-describing metadata. The registry auto-discovers
// this block at boot; THIS is what registers the source (see Step 2).
export const meta = {
value: 'example', // ← must equal job.source written below
label: 'Example.com', // ← shown in the #/scan filter dropdown
region: 'ru', // ← 'en' (ATS sweep) | 'ru' (regional dispatcher)
configKey: 'example', // ← RU only; the key used in portals.yml
};
export async function searchExample(query, opts = {}) {
const { onlyRemote = false, fetchImpl = fetch, signal } = opts;
const res = await fetchImpl(`${ENDPOINT}?text=${encodeURIComponent(query)}`, {
signal,
headers: { 'User-Agent': UA, Accept: 'application/json' },
});
if (!res.ok) {
const err = new Error(`Example: HTTP ${res.status}`);
err.status = res.status;
throw err;
}
const data = await res.json();
return (data.items || []).map(normalizeExample);
}
function normalizeExample(item) {
return {
id: `example-${item.id}`,
title: item.title || '',
company: item.company?.name || '',
url: item.url || '',
salary: item.salary || '',
location: item.location || '',
isRemote: !!item.remote,
workplaceType: item.remote ? 'Remote' : 'Onsite',
relocates: false,
date: item.posted_at || '',
snippet: (item.description || '').slice(0, 240),
source: 'example', // ← must match the registry `value` exactly
};
}
Fuente HTML-scrape (cuando no hay API — ver
getmatch.mjs y
geekjob.mjs como ejemplos completos):
const BASE = 'https://example.com';
export async function searchExample(query, opts = {}) {
const { fetchImpl = fetch, signal } = opts;
const res = await fetchImpl(`${BASE}/vacancies?q=${encodeURIComponent(query)}`, {
signal,
headers: { 'User-Agent': UA, Accept: 'text/html' },
});
if (!res.ok) {
throw Object.assign(new Error(`Example: HTTP ${res.status}`), { status: res.status });
}
return parseExampleCards(await res.text());
}
export function parseExampleCards(html) {
// …regex-based card extraction. Return [] on parse failure (DON'T throw):
// a healthy 200 with no parseable cards is "no results", not "error",
// so the multi-source scanner can keep going.
}
Todo adapter DEBE cumplir tres contratos:
- Exportar un bloque
metaválido (ver Paso 2). Sin él, el registro omite el archivo en silencio (unconsole.warnal arrancar) y la fuente nunca aparece. - Aceptar
{ onlyRemote, fetchImpl, signal }enopts.fetchImples lo que hace los adapters testeables sin red;signales necesario para propagar la desconexión del cliente (REVIEW-B3). - Devolver registros con la forma común —
{ id, title, company, url, salary, location, isRemote, workplaceType, relocates, date, snippet, source }, dondesourcecoincide con elmeta.value.
Paso 2 — Declara el meta del adapter (auto-registro)
Este es el paso de registro completo. No editas registry.mjs.
Solo asegúrate de que el adapter exporta un bloque meta — el registro
lo auto-descubre al arrancar:
// at the top of server/lib/sources/example.mjs
export const meta = {
value: 'example', // job.source value AND #/scan option.value
label: 'Example.com', // display label in the dropdown
region: 'ru', // 'en' | 'ru'
configKey: 'example', // RU only — key in portals.yml::russian_portals.sources
};
Cómo valida el descubrimiento (un archivo que incumpla cualquier regla se omite, con un
aviso [sources/registry], para que una rama a medio migrar siga siendo diagnosticable):
value— cadena no vacía. DEBE coincidir conjob.sourcede tu adapter.label— cadena no vacía.region— exactamente'en'o'ru'; cualquier otro valor es rechazado.configKey— obligatorio pararegion: 'ru', ignorado para'en'.
region: 'en' se une al barrido ATS (auto-descubre desde los patrones de URL de tracked_companies);
region: 'ru' se une al dispatcher regional. La API pública
(SOURCES, SOURCES_BY_REGION, RU_CONFIG_KEYS, getRegionalSources) se
reconstruye desde cada meta descubierto, ordenando primero en y luego ru,
alfabéticamente por label dentro de cada región — así el orden del desplegable permanece
estable para los usuarios.
Paso 3 — Conecta al dispatcher (sólo RU)
Los adapters ATS EN se auto-descubren desde los patrones de URL de tracked_companies —
no hace falta ninguna conexión adicional. Para fuentes RU, abre
server/lib/ru-scanner.mjs, localiza
la tabla RU_DISPATCH y añade una fila:
import { searchExample } from './sources/example.mjs';
// …
const RU_DISPATCH = {
// …existing…
example: { label: 'example.com', search: searchExample },
};
El bucle del dispatcher llama a entry.search(query, opts) para cada clave
presente en cfg.sources. No se necesita ningún otro cambio de código.
Paso 4 — Test (mockeado, jamás en vivo)
Crea un archivo bajo tests/sources-<slug>.test.mjs. La red real está
prohibida en los tests (contrato CI-isolation):
import test from 'node:test';
import assert from 'node:assert/strict';
import { searchExample } from '../server/lib/sources/example.mjs';
test('searchExample normalizes one record', async () => {
const fetchImpl = async () =>
new Response(
JSON.stringify({ items: [{ id: 1, title: 'Backend Engineer' }] }),
{ status: 200, headers: { 'content-type': 'application/json' } }
);
const out = await searchExample('q', { fetchImpl });
assert.equal(out.length, 1);
assert.equal(out[0].source, 'example');
});
Paso 5 — Activa en tu portals.yml
El portals.yml del proyecto padre es la config del usuario. Añade el
configKey de la nueva fuente al array:
russian_portals:
sources: ["hh", "habr", "trudvsem", "getmatch", "geekjob", "example"]
area: 113
per_page: 50
only_remote: false
queries:
- "Senior PHP"
- "Senior Go"
Recarga #/scan en el navegador. El dropdown del filtro de fuente recoge
la nueva entrada automáticamente (única fuente de verdad vía
GET /api/scan/sources →
registry.mjs). El
botón 🌐 Scan ahora incluye la nueva fuente en cada barrido regional.
Adapters de referencia (úsalos como espejo para nuevas fuentes)
| Archivo del adapter | Tipo | Notas |
|---|---|---|
hh.mjs |
JSON API | Adapter de API RU canónico; fallback de UA según geo. |
trudvsem.mjs |
JSON API | Open-data del gobierno ruso; sin barrera de IP. |
habr.mjs |
HTML scrape | Tablón tech ruso; parser de tarjetas basado en regex. |
getmatch.mjs |
HTML scrape | Parser defensivo, [] si falla el parseo. |
geekjob.mjs |
HTML scrape | Mismo estilo defensivo que GetMatch. |
greenhouse.mjs |
JSON API | Adapter ATS EN canónico; usa el patrón de URL tracked_companies. |
Pitfalls comunes
- Olvidar la exportación
meta. Desde v1.69.0 el bloquemetaes lo único que registra una fuente. Sinmeta(o con uno mal formado) = el archivo se omite en silencio al arrancar con un único aviso[sources/registry] <file> has no valid \export const meta` — skipped`, y la fuente nunca llega al desplegable. Revisa el log del servidor si un adapter recién creado no aparece. - Desajuste del campo
source. El string que escribe tu adapter DEBE coincidir exactamente con elmeta.value. Si se desincronizan, el dropdown del filtro de#/scanmostrará la fuente pero al seleccionarla filtrará todas las filas (porque la comprobación de igualdad esr.source === fs). - Lanzar excepción al fallar el parseo. Los scrapers HTML DEBEN devolver
[]ante un 200 sano sin tarjetas parseables. Lanzar excepción rompe el bucle del dispatcher multi-fuente — una estructura HTML mala mata todas las demás fuentes para la misma consulta. - Olvidar
fetchImpl/signal. Sin ellos, tu adapter no puede probarse en unit tests sin tocar la red real, y las desconexiones del cliente no se propagan (el fetch en background sigue vivo después de que el usuario cierra la pestaña). - Confiar en
tracked_companiespara RU. Esa lista es sólo para fuentes ATS EN. Los adapters RU se autoabastecen desderussian_portals.queries— sin entradas por empresa.
18. Notificaciones (🔔 en la barra superior)
v1.58.34 — cada toast en la esquina inferior derecha se captura en un diario in-memory (tope 50, se descartan los más viejos). Haz clic en 🔔 en la barra superior para abrir el drawer Notificaciones y releer lo que te perdiste. El diario es por pestaña y por sesión — cerrar la pestaña lo borra.
El drawer solo se abre al hacer clic en la campana (o Enter / Space con foco). Nunca aparece solo. El badge rojo cuenta entradas no vistas; abrir el drawer lo reinicia.
Categorías
| Categoría | Cuándo dispara | Pista visual |
|---|---|---|
| Éxito | Guardado, Copiado, Actualizado, scan completo, CV importado, apply-checklist, perfil guardado |
borde izquierdo verde; toast verde |
| Error | URL inválida, errores de API con sufijo (MÉTODO /ruta · HTTP NNN), fallos de red, duplicados de pipeline-400, doctor/verify exit ≠ 0 |
borde izquierdo rojo; toast rojo; postfix en Detalles (U-4) |
| Info | Running doctor.mjs…, Refreshing…, Loading…, progreso de scan |
borde gris |
Cada entrada muestra hora local, mensaje humano y, si existe, el postfix técnico en monospace.
Qué NO es una notificación
- Modal de resultado de Doctor / verify (es modal, no toast).
- Líneas SSE en
#/scan/#/auto(van al cuerpo de la página). - Estados de spinner sin toast.
Teclado
- Clic o Enter / Space en la campana → abrir.
- Esc, ×, o nuevo clic en la campana → cerrar; el foco vuelve a la campana.
19. Localizar la aplicación a tu idioma
La interfaz se distribuye en 9 idiomas (English, Español, Français, Português, 한국어, 日本語, Русский, 简体中文, 繁體中文). Cada etiqueta en pantalla proviene de un diccionario de traducciones, y puedes añadir o corregir un idioma sin tocar la lógica de la app.
Dónde viven las traducciones. Desde la v1.60.0 cada idioma es su propio archivo en public/js/lib/locales/ — i18n-dict.en.js, i18n-dict.es.js, i18n-dict.ru.js, etc. — una lista simple de pares 'clave': 'texto'. Un i18n-dict.aliases.js compartido permite que claves que deben leerse igual (una etiqueta del menú y el título de su página) apunten a una sola traducción. i18n-dict.js los ensambla al cargar; nunca lo edites.
Corregir o añadir un texto. Abre el archivo de tu idioma, busca la clave (p. ej. 'nav.scan') y edita el texto. Para añadir una etiqueta nueva, agrega la misma clave a los 8 archivos de idioma con el valor traducido y úsala en la página con t('tu.clave'). Ejecuta npm test — falla si a algún idioma le falta la clave, así nada se publica a medio traducir.
Añadir un idioma nuevo. Copia i18n-dict.en.js a i18n-dict.<código>.js, traduce cada valor y registra el código en i18n.js (la lista de idiomas + autodetección del navegador), en el ensamblador i18n-dict.js, y añade una línea <script> en index.html. La lista completa — incluido el snapshot de tests y los archivos de ayuda / README — está en docs/LOCALIZATION.md.
Bueno saberlo. El selector de idioma está en el pie de la barra lateral; tu elección se recuerda por navegador. Los mensajes de diagnóstico del servidor permanecen en inglés a propósito (para que los logs sean consistentes) — solo se traduce la interfaz en pantalla.
Consulta docs/LOCALIZATION.md en el repositorio para la guía de localización completa, paso a paso.
20. Estadísticas por roles objetivo (#/stats)
La página Analytics → Estadísticas por rol objetivo convierte los escasos datos que tus escaneos ya recopilan en una radiografía del mercado para los roles que realmente estás buscando: número de vacantes y niveles salariales por país, además de una tendencia que puedes seguir en el tiempo. No se inventa nada: solo agrega lo que encontraron los escáneres, y es honesta sobre lo pequeña que es la muestra.
De dónde salen los números
- Los roles objetivo se leen de tu Perfil (
config/profile.yml→ target roles), nunca están codificados a mano. Configúralos primero en#/profile; sin roles, la página muestra un aviso de “define tus roles objetivo” en lugar de gráficos vacíos. - Las ofertas provienen de tu último escaneo (ejecuta uno primero en
#/scan). La ubicación de cada empleo se asigna a un país (el mismo detector que el filtro de país del escáner) y su cadena salarial se analiza y normaliza a USD mediante una tabla de tipos de cambio aproximada. - Todo se agrega en tu navegador: ningún dato sale de tu máquina, y lo único que la página llega a escribir es una instantánea que guardas explícitamente.
Cómo leer los gráficos
- Vacantes por país: cuántas ofertas coincidentes hay en cada país. Usa los filtros de Rol y País en la parte superior para acotar a un solo rol objetivo o a un solo país.
- Salario mediano por país (USD): el salario intermedio analizado por país. Solo se cuentan las ofertas con un salario interpretable; el tamaño de la muestra se muestra junto al gráfico, y los importes se convierten a tipos aproximados, así que léelo como indicativo, no exacto. Un
¥a secas (ambiguo entre el yen japonés y el yuan chino) se descarta en lugar de adivinarse, para evitar una gran distorsión de FX. - Cuando el escaneo actual no tiene salarios interpretables, el gráfico salarial lo indica en lugar de inventar cifras.
Guardar instantáneas y seguir la tendencia
- Haz clic en Guardar instantánea para añadir el agregado actual a
data/role-stats.jsonl. Cada instantánea recibe una marca de tiempo en el servidor; las instantáneas son lo único que escribe esta página y nunca tocan tu CV ni tu perfil. - El gráfico de tendencia traza el número de vacantes a lo largo de tus instantáneas guardadas: guarda una periódicamente (por ejemplo, tras cada escaneo semanal) para observar cómo evoluciona con el tiempo el mercado para tus roles objetivo.
21. Tu documento de dos páginas — encaje candidato-mercado (#/two-pager)
Casi todo career-ops-ui pregunta “¿esta oferta encaja con mi CV?”. El documento de dos páginas responde la otra mitad: “¿esta oferta encaja con lo que yo realmente quiero?”. Está inspirado en el “documento de dos páginas de Mnookin” de Never Search Alone — una declaración breve, en primera persona, de lo que te da energía, lo que exiges y lo que no aceptarás. Ábrelo desde Configuración → Documento de dos páginas 🎯.
Autorrelleno con IA + exportación (v1.100). El “✨ asistente de relleno IA” ahora rellena todos los campos en vivo desde tu CV (revisa y guarda); 👁 Vista previa y exportar renderiza el two-pager y lo exporta a Markdown, PDF o DOCX.
Lo que rellenas
- Quién soy — unas pocas frases en primera persona sobre tu trayectoria y el tipo de rol en el que rindes mejor.
- Entorno objetivo — el tamaño, la etapa y la cultura de empresa que buscas.
- Cinco listas de chips — escribe y pulsa Enter (o coma) para añadir cada elemento, haz clic en × para quitarlo:
- Lo que me encanta — energizantes (remoto, autonomía, greenfield, mentoría…).
- Imprescindibles — requisitos duros (un salario mínimo, un país, un stack…).
- Lo que odio — desgastantes (guardias, reuniones interminables, solo legacy…).
- Factores excluyentes — noes absolutos (solo presencial, sin patrocinio de visado, por debajo de una cifra…).
- Innegociables — límites (ubicación, remoto, salario mínimo…).
Haz clic en Guardar documento de dos páginas para conservarlo. Se escribe en la capa de usuario de tu proyecto padre career-ops en config/two-pager.yml, así que — como tu CV y tu perfil — nunca se sobrescribe cuando actualizas el sistema.
El asistente de relleno con IA
¿No sabes cómo formularlo? Haz clic en ✨ Asistente de relleno con IA. Construye un prompt listo para ejecutar (el formato Mnookin, con tu CV y tu perfil incrustados) y lo muestra en un diálogo. Ejecuta ese prompt en cualquier LLM y luego pega los campos YAML resultantes de vuelta en el formulario. El asistente solo usa tu propio CV y perfil — nunca inventa datos sobre ti, y no se hace ninguna llamada a API en vivo desde este botón.
La puntuación de encaje con lo que quieres
Una vez que hayas guardado un documento de dos páginas, cada oferta en #/scan gana una pequeña insignia ◎ N (0–100). Compara el tipo de trabajo (remoto/híbrido/presencial), el país, el salario mínimo y la reubicación de cada oferta con tu documento de dos páginas — una insignia verde significa buen encaje, roja significa que se activó un factor excluyente. Pasa el cursor por encima para ver los detalles (✓ lo que coincidió, ✗ qué factor excluyente se violó).
Es deliberadamente honesta: cuando una oferta no da ninguna señal comparable (por ejemplo, si tus preferencias son todas texto libre que una fila de escaneo no puede confirmar), no se muestra ninguna insignia en absoluto — el sistema nunca inventa un número. La violación de un factor excluyente duro pesa más que un odio blando hacia lo mismo. Más allá de la insignia, tu documento de dos páginas guardado se incrusta en cada evaluación con LLM, así que tus preferencias declaradas moldean también el veredicto escrito, no solo el encaje CV-vs-oferta.
22. Entrevista simulada (#/mock-interview)
Leer la preparación de entrevistas es una cosa; decir las respuestas en voz alta es otra. La página Entrevista simulada (ábrela desde Preparación de entrevistas → Entrevista simulada 🎤 en la barra lateral) ejecuta un ensayo turno a turno contra un puesto concreto, apoyado en tu propio CV, perfil, documento de dos páginas y banco de historias. No es una lista de preguntas enlatadas — el entrevistador reacciona a lo que realmente dices.
Iniciar una sesión
- Introduce un puesto objetivo (y opcionalmente una empresa). Pega también la descripción del puesto si la tienes — las preguntas se vuelven notablemente más afiladas.
- Haz clic en Iniciar entrevista. El entrevistador abre con una pregunta enfocada, adaptada al puesto y a tu trayectoria.
- Escribe tu respuesta y haz clic en Enviar respuesta. Repite todo lo que quieras — es una conversación, no un cuestionario fijo.
Qué te da cada turno
Después de cada respuesta, el entrevistador responde con tres partes:
- Comentarios — qué funcionó (fortalezas) y qué faltó, planteado en términos STAR+R (Situación, Tarea, Acción, Resultado, Reflexión). Nombra la dimensión concreta que te saltaste.
- Puntuación — un rápido
N/5con una justificación de una línea, para que puedas sentir el progreso a lo largo de una sesión. - Siguiente pregunta — un seguimiento que sondea deliberadamente la parte más débil de tu última respuesta.
Todo se apoya en tus materiales reales: cv.md, config/profile.yml, config/two-pager.yml y tu banco de historias STAR+R (interview-prep/story-bank.md) se incrustan todos en el prompt. El entrevistador presionará sobre lagunas genuinas pero nunca inventa experiencia que no tienes. Si no hay ninguna clave de LLM configurada, la página te entrega un prompt listo para ejecutar y pegar en cualquier asistente — el mismo recurso honesto que se usa en otras partes de la app.
Guardar y revisitar sesiones
Haz clic en Guardar transcripción para conservar un ensayo terminado. Se escribe en la capa de usuario de tu proyecto padre en interview-prep/mock-{company}-{role}-{date}.md, de modo que vive junto a tus demás notas de preparación de entrevistas y nunca se sobrescribe con las actualizaciones del sistema. La lista de Sesiones guardadas al final de la página te permite reabrir cualquier transcripción o eliminarla. Usa Nueva entrevista para empezar de cero con un puesto diferente.
23. Networking e investigación profunda de empresas (#/networking)
Postular por la puerta principal es solo la mitad del juego — la otra mitad es conocer a alguien, o al menos saber a quién contactar y qué decir. La página Networking (ábrela desde Investigación profunda → Networking 🤝 en la barra lateral) convierte una empresa en un plan concreto para conseguir una entrevista, apoyado en tu propio CV, tu perfil y tu two-pager.
Construir un plan
- Introduce una empresa (obligatorio) y, opcionalmente, un puesto y la descripción del puesto. La descripción afina los ganchos de “por qué encajo”.
- Haz clic en Construir plan. Con una clave de LLM se ejecuta en vivo y renderiza el plan en línea; sin clave te entrega un prompt listo para pegar en cualquier asistente (el mismo fallback honesto que se usa en toda la app — no se inventa nada).
Qué contiene el plan
El plan vuelve en cuatro secciones:
- Dossier de la empresa — un resumen compacto de lo que hace la empresa, señales recientes que vale la pena citar, y dos o tres ganchos de “por qué encajo” extraídos de tu trayectoria real.
- A quién contactar — de tres a cinco perfiles objetivo (el responsable de contratación del equipo, un reclutador interno, un ingeniero senior del equipo, un contacto cercano o de exalumnos) con una cadena de búsqueda de LinkedIn concreta para encontrar a cada uno. Nunca inventa nombres reales — te dice cómo encontrar a las personas adecuadas.
- Vía de presentación más cercana — la ruta cálida de entrada más realista para tu trayectoria: un empleador, universidad o comunidad en común; un contacto de segundo grado; o un mensaje en frío de alta señal cuando esa sea genuinamente la mejor opción.
- Borradores de contacto — mensajes cortos y específicos (de tres a cinco frases, sin relleno) para tus perfiles principales, apoyados en tus puntos de prueba reales para que no suenen genéricos.
Guardar y revisar planes
Haz clic en Guardar plan para conservar uno. Se escribe en la capa de usuario de tu proyecto padre en networking/net-{company}-{role}-{date}.md — tu propio archivo, nunca sobrescrito por las actualizaciones del sistema. La lista de Planes guardados al final de la página te permite reabrir o eliminar cualquier plan. Como los borradores y los perfiles se apoyan únicamente en tus materiales reales, trátalos como un primer borrador sólido para personalizar — no como un guion para enviar a ciegas.
24. CV Studio (#/cv-studio)
Añadir al CV (v1.117.0). Una nueva tarjeta convierte un proyecto, publicación o página de portafolio (URL o texto pegado) en viñetas listas para ATS basadas SOLO en esa fuente — métricas, empleadores o fechas ausentes se omiten, nunca se inventan. Tú revisas las sugerencias y pegas las aceptadas en el editor de CV; nada se escribe automáticamente y las URL pasan por el mismo validador anti-SSRF que el pipeline.
La página #/cv es donde escribes tu CV; CV Studio (ábrelo desde Setup → CV Studio 🎨 en la barra lateral) es donde lo afinas. Le ofrece a tu cv.md tres herramientas honestas, dos de las cuales nunca salen de tu navegador.
Adaptar a un empleo (v1.101). Pega una descripción de empleo y CV Studio produce un CV adaptado más una carta de presentación, pasados por una verificación tipo reclutador (los errores bloquean, las advertencias aconsejan), basado solo en tus materiales.
Diagnóstico del CV
En el momento en que abres la página, puntúa tu CV sobre 100 y enumera los hallazgos por comprobación, cada uno con una breve explicación para que seas tú quien decida qué cambiar (nunca reescribe en silencio):
- Longitud — ¿está el CV en un rango saludable de una a dos páginas?
- Impacto cuantificado — ¿qué proporción de tus viñetas incluye un número o métrica real? Los reclutadores los buscan de un vistazo.
- Verbos de acción fuertes — marca frases débiles como «responsable de» o «ayudé a».
- Palabras de moda — marca clichés vacíos («orientado a resultados», «jugador de equipo»).
- Secciones esenciales — comprueba que estén Resumen, Experiencia, Educación y Habilidades.
- Datos de contacto — se asegura de que haya un correo electrónico.
Esto se ejecuta por completo en tu navegador sin ningún LLM: los números son deterministas y nada se inventa.
Máscara de privacidad
Antes de compartir tu CV como muestra de escritura o captura de pantalla, la Máscara de privacidad oculta los datos de identificación personal: correo electrónico, teléfono, enlaces/identificadores y dirección postal, además de tu nombre → iniciales si lo activas y lo introduces. Activa o desactiva cada categoría, copia la versión enmascarada y compártela con seguridad. Todo ocurre íntegramente en el navegador, informa exactamente de cuántos elementos ocultó y nunca almacena ni transmite el original.
Hazlo humano (coincidencia de voz)
Pega una frase o un párrafo rígido —ese tipo de redacción genérica de IA que suena a plantilla— y Hazlo humano lo reescribe con tu voz. La reescritura se fundamenta en el servidor en tu voice-dna.md (cómo se lee tu escritura) y en tus writing-samples/ (tu prosa real). La regla estricta: puede reordenar, condensar y reajustar la voz, pero nunca introducirá un hecho, métrica o logro que no esté ya en el texto que pegaste. Con una clave de LLM lo reescribe en vivo; sin clave te entrega un prompt listo para pegar en cualquier asistente. Luego edita tu CV en la página #/cv como de costumbre: CV Studio sugiere, tú decides.
25. Memoria (#/memory)
Todas las demás páginas empiezan de cero cada vez. Memoria (ábrela desde Configuración → Memoria 🧠 en la barra lateral) es el único lugar donde le dices algo al asistente una sola vez y queda fijado. Contiene una nota breve y editable del tipo “recuerda esto sobre mí” que se inyecta en cada petición a la IA.
Para qué sirve
Úsala para preferencias duraderas y estilo de trabajo, por ejemplo:
- Los tipos de puestos y empresas que te interesan (y los que nunca quieres ver).
- Cómo te gusta que se redacten las respuestas — escuetas o detalladas, tono senior, sin relleno.
- Restricciones firmes que vale la pena repetir — solo remoto, un salario mínimo, sin guardias.
Limítala a preferencias y orientación. No es el lugar para hechos sobre tu experiencia — tus habilidades, empleadores y logros viven en tu CV, tu perfil y tu resumen de dos páginas, que siguen siendo las únicas fuentes de cualquier cosa que aparezca en tus CV y cartas de presentación. La nota de memoria moldea cómo trabaja el asistente contigo, nunca qué afirma sobre ti.
Cómo llega a todo
Cuando haces clic en Guardar memoria, la nota se escribe en la capa de usuario de tu proyecto padre, en config/memory.md, y se incorpora al contexto compartido del proyecto. Eso significa que viaja automáticamente con cada petición a la IA — evaluaciones, entrevistas simuladas, planes de networking, reescrituras en CV Studio — y a través de cada proveedor que hayas configurado. Escríbela una vez; no tienes que repetirte en cada página. Como tus demás archivos de la capa de usuario, nunca se sobrescribe cuando actualizas el sistema, y solo sale de tu máquina dentro de los prompts al LLM que decidas ejecutar.
Sugerir a partir de tus datos
¿No sabes qué escribir? ✨ Sugerir a partir de mis datos lee tu seguimiento de candidaturas y redacta un conjunto de puntos de comportamiento — los patrones de lo que persigues, aceptas y rechazas. Ejecuta el prompt que te da en cualquier LLM, revisa las sugerencias y pega una versión editada en la nota. Solo extrae de tu propio seguimiento y nunca inventa hechos; siempre revisas antes de que se guarde nada.
26. Estadísticas (#/stats)
Pestaña de patrones de rechazo (v1.117.0). Una cuarta pestaña ejecuta el analyze-patterns.mjs del padre (solo lectura) y muestra la mezcla de resultados, recomendaciones accionables y la tasa de avance por proveedor ATS (la señal de “monocultivo algorítmico” — Bommasani et al., FAccT 2026). Los proveedores bajo la muestra mínima llevan asterisco; sin el proyecto padre la pestaña lo indica honestamente.
La página Estadísticas reúne tres vistas en una sola sección: un informe de mercado generado por IA, analíticas de tu propio pipeline y la tendencia de vacantes para tus roles objetivo a partir de tus escaneos. Cambia entre ellas con las pestañas de la parte superior.
Informe de mercado
La pestaña Informe de mercado le pide al modelo un análisis salarial y de mercado laboral de tus roles objetivo — lee tu CV y tu perfil para saber qué roles y seniority cubrir. Escribe una Región / mercado (por ejemplo Russia, EU-remote, US o Germany), elige una Moneda y haz clic en Generar informe de mercado. Obtienes un informe estructurado con un resumen ejecutivo, salario por nivel (mediana más P10/P25/P75/P90), principales empleadores, una tabla de habilidades demandadas, frecuencia de beneficios, el reparto oficina/híbrido/remoto, tendencias de 12–24 meses incluido el impacto de la IA, y orientación de negociación. Cada cifra es una estimación orientativa del conocimiento de entrenamiento del modelo — no son datos scrapeados ni en vivo — y el informe lo indica; trata los números como rangos, no como cotizaciones. Sin una clave de API configurada obtienes un prompt para copiar y pegar en lugar de un informe fabricado. Usa Descargar .md, Guardar como PDF o Copiar para sacar el informe de la app.
Mi pipeline
La pestaña Mi pipeline grafica tu propio tracker de candidaturas — nada externo. Muestra cuántos roles has seguido, tu distribución de puntuaciones, el embudo de estados, tus principales empresas y roles, las candidaturas a lo largo del tiempo y las tasas de conversión (qué proporción de candidaturas alcanza Applied, Responded, Interview y Offer). Es el espejo honesto de tu búsqueda: solo refleja lo que ya está en data/applications.md.
Tendencia de roles objetivo
La pestaña Tendencia de roles objetivo es la vista original: recuento de vacantes y salario mediano por país para tus roles objetivo, agregados a partir de tu último escaneo, con un selector de moneda y un resumen de Vacantes por rol objetivo. Guardar snapshot registra el agregado actual para que puedas observar cómo se mueven los recuentos de vacantes con el tiempo, y la línea de tendencia relee esos snapshots. Los datos escasos son esperables y se etiquetan como indicativos — nunca se rellenan con números inventados.
Histórico y compensación
La pestaña Histórico (v1.118.0) retransmite, en solo lectura, dos scripts del padre de cero tokens: stats.mjs — el resumen histórico de tu tracker, las tasas del embudo acumulado (respuesta / entrevista / oferta), los totales del escáner y la cobertura de portales — y salary-gap.mjs — compensación deseada vs anunciada vs real por candidatura, consolidada desde los Machine Summary de los informes y data/salary-observations.tsv. Las muestras pequeñas se marcan como orientativas; sin el proyecto padre la pestaña muestra una nota honesta.
27. Plan de carrera (#/career-plan)
La página Plan de carrera convierte tu CV y tu perfil en un plan de desarrollo concreto y personalizado — del tipo que construirías con un coach de carrera, pero generado a partir de tus propios materiales y tuyo para editar.
Generar un plan
Elige un Horizonte (6, 12 o 24 meses), escribe opcionalmente un Enfoque (por ejemplo «pasar a un puesto de gestión», «trabajar en remoto» o «cambiarme a Go») y haz clic en Generar plan. El modelo lee tu CV, tu perfil, tu two-pager y tu nota de memoria (a través del contexto de proyecto compartido) y redacta un plan estructurado: una instantánea honesta de tu punto de partida, un DAFO de fortalezas y áreas de crecimiento, objetivos expresados como SMART / OKR / WOOP, trayectorias profesionales alternativas con sus contrapartidas, un plan de habilidades técnicas y blandas, una hoja de ruta mes a mes para el horizonte que elijas, cómo hacer seguimiento del progreso, los escollos probables y las acciones de apoyo. Cada recomendación se fundamenta en lo que tus materiales muestran realmente — planifica hacia adelante, nunca inventa hechos sobre tu historial. Si no hay ninguna clave de API configurada, obtienes en su lugar un prompt para copiar y pegar.
Editar y guardar
El plan aparece en un área de texto editable — ajusta lo que quieras y luego haz clic en Guardar plan. Se escribe en la capa de usuario de tu proyecto padre, en config/career-plan.md, de modo que sobrevive a las actualizaciones del sistema y solo se envía dentro de los prompts del LLM que decidas ejecutar. Vista previa renderiza tu Markdown para que puedas leerlo con formato antes de guardar.
Exportar
Usa Descargar .md, Guardar como PDF o Copiar para sacar el plan de la aplicación — los mismos controles de exportación que se usan en todos los informes de IA de la app. El PDF pasa por el generador de PDF en línea existente; el Markdown es una descarga directa.
28. Orientación profesional (#/orientation)
La página de Orientación profesional responde a la pregunta «¿qué direcciones encajan realmente conmigo?» — el tipo de lectura que obtendrías de un test vocacional, pero inferida a partir de tu propio CV y perfil en lugar de un cuestionario.
Qué produce
Haz clic en Generar perfil y el modelo lee tu CV, tu perfil, tu two-pager y tu nota de memoria y redacta un perfil de orientación profesional: tus vectores de carrera con mejor ajuste (cuáles de los ocho arquetipos — Funcionalista, Administrador, Comunicador, Especialista, Analista, Innovador, Gestor, Emprendedor — encajan mejor, con evidencia de tu CV), una inclinación de tipo profesional, un conjunto de roles recomendados, tus fortalezas profesionales ligadas a lo que muestra el CV, tendencias de estilo de trabajo («cómo se lee tu CV» en unos pocos ejes) y recomendaciones de desarrollo para ampliar tu ajuste.
Cómo se genera
Es una reflexión de IA sobre cómo se lee tu CV — no un test psicométrico. El prompt se fundamenta por completo en tus propios materiales: no inventa logros y nunca informa puntuaciones numéricas de test como si fueran medidas. Sin ninguna clave de API configurada obtienes un prompt para copiar y pegar y ejecutar en cualquier LLM en lugar de un perfil en vivo. No se escribe nada en el disco — el perfil se genera de nuevo cada vez.
Exportar
Usa Descargar .md, Guardar como PDF o Copiar para conservar el perfil — los mismos controles de exportación que se usan en los informes de IA de la app. El PDF pasa por el generador de PDF en línea existente; el Markdown es una descarga directa.