Historial de cambios

GitHub

Cada cambio relevante, versión a versión. El mismo archivo se publica en el repositorio y está traducido a los 19 idiomas.

[1.133.1] — 2026-08-02

Corregido

  • #/funded (Empresas financiadas) ahora renderiza resultados — dos errores hacían que la tabla mostrara siempre “no hay empresas financiadas” incluso cuando el company-funded.mjs del padre devolvía una lista completa. (1) La vista leía los resultados bajo res.candidates, pero el padre los emite bajo companies (cada una { company, amount, round, funding: { sources: [{ source, url, observed_date }] } }); el cliente ahora lee la clave correcta y mapea la forma real de la evidencia. (2) La tabla de resultados pasaba sus celdas a UI.el('tr', {}, …) como argumentos variádicos, pero UI.el(tag, attrs, children) espera children como un único nodo o un array, así que solo se renderizaba la primera columna (Empresa) — las celdas ahora se pasan como un array. Verificado en un navegador real: 11 empresas de los cuatro feeds se renderizan con las columnas Empresa / Señal de financiación / Fuente / Fecha y enlaces de evidencia funcionales, cero errores de consola. Un resultado vacío ahora también muestra el diagnóstico por fuente, de modo que un día tranquilo de noticias se distingue de un feed bloqueado.
  • Guardas de regresión en tests/parity-routes-v1133.test.mjs: el script simulado del padre ahora emite la forma real de salida companies (el fixture original reflejaba la forma incorrecta candidates — la razón exacta por la que el error se publicó con la suite en verde), más comprobaciones estáticas de que funded.js lee res.companies (nunca res.candidates) y construye las filas de la tabla con hijos en forma de array (+1 → 2144).

[1.133.0] — 2026-08-01

Añadido

  • Descubrimiento de empresas financiadas (#/funded, paridad con el padre #2117) — una nueva vista de solo lectura que retransmite el company-funded.mjs del career-ops padre vía GET /api/company-funded: una lista de revisión previa de empresas recientemente financiadas, descubiertas a partir de feeds de financiación públicos y anclados a su host (TechCrunch, PR Newswire, The Guardian, Hacker News). El relé ejecuta el script con --json --dry-run (JSON a stdout, sin escrituras en disco), nunca hila la entrada del usuario hacia --sources, lleva limitación de tasa, y se activa por el usuario (un botón Descubrir, nunca al montar). Nuevo módulo de ruta server/lib/routes/funded.mjs + public/js/views/funded.js, bajo Búsqueda.
  • Resumen semanal de entrevistas (#/interview-digest, paridad con el padre #2129/#2130) — una nueva vista de solo lectura que retransmite el weekly-digest.mjs del padre, de cero-LLM, vía GET /api/interview/weekly-digest: un resumen mecánico de las notas de sesión de entrevista — con qué empresas y rondas entrevistaste esta semana, competencias recurrentes, y brechas abiertas (best-effort). El rango opcional ?from=&to= solo se hila cuando AMBOS son YYYY-MM-DD válidos; un rango vacío es un resumen available:true válido. Añadido a server/lib/routes/interview.mjs + public/js/views/interview-digest.js, bajo Analítica.
  • Ambos relés siguen el contrato establecido de degradación segura available:false cuando el script del padre está ausente (CI, instalaciones independientes). 26 nuevas claves i18n ×17 idiomas; suite aislada para CI tests/parity-routes-v1133.test.mjs (+5 → 2143).

Notas

  • El career-ops padre avanzó más allá de v1.24.0 con la página Follow-up Tracker de la app Next.js web/ (#1422) y el renderizado de PDF en el backend (#2182) — no portado: web-ui ya tiene su propio relé de seguimiento y sus propios runners de PDF, y el endurecimiento subyacente de followup-cadence.mjs llega gratis vía el relé shell-out. Los cambios de set-status.mjs / tracker-utils.mjs son internos de CLI y no se reflejan.

[1.132.0] — 2026-07-31

Cambiado

  • El subsistema de renderizado de resultados de #/scan se extrae a public/js/lib/scan-results.js (amortización de la deuda del contrato de tamaño de archivo — public/js/views/scan.js había crecido hasta ~1254 LOC). El subsistema — renderResults, buildChipRow, los constructores de fila/faceta, los pintores de opciones y el espejo del registro FALLBACK_SOURCES — pasa a una fábrica window.ScanResults.create(ctx) que encierra un objeto de contexto que le proporciona la vista. Sin cambio de comportamiento — las funciones se movieron literalmente y sus variables de cierre se recablearon a ctx.*; scan.js ahora tiene ~906 LOC (se prevé una segunda pasada de extracción hacia el objetivo de 800 LOC).
  • Nueva puerta de regresión en el navegador — tests/playwright-scan-filters.mjs siembra un data/last-scan.json prefabricado y ejercita cada filtro de #/scan, verificando recuentos de fila exactos, de modo que la extracción queda comprobada contra el comportamiento real del navegador.
  • Banner del README simplificado — el largo muro narrativo de “Última versión” por versión se retira en favor de un resumen de una línea + un enlace al registro de cambios completo (este archivo).

[1.131.2] — 2026-07-31

Cambiado

  • División de app.css en tres hojas de estilo ordenadas (deuda de contrato de tamaño de archivo — el archivo único había crecido hasta ~1990 LOC, muy por encima del límite estricto de 800 LOC). Ahora es app.css (~672 — accesibilidad, tokens de diseño/tema, barra lateral, contenido principal, botones, content-shell), components.css (~595 — tarjetas, grids, paginador, badges, tablas, formularios, log/consola, markdown, selector de idioma, filtro de chips, banner de conexión) y overlays.css (~737 — toast, cajón de notificaciones, modal, misceláneo/responsive, el espejo [dir="rtl"], docs-fab, usage-hud), cada una dentro del límite estricto.
    • El corte es contiguo y en orden, de modo que la cascada es byte a byte idéntica al archivo previo a la división; index.html carga las tres como <link> ordenados. Sin cambio de comportamiento, marcado ni i18n.
    • Los tests que verifican CSS ahora leen la concatenación mediante un helper compartido tests/helpers/css.mjs::loadAppCss(). El nuevo tests/css-modularization.test.mjs fija la división (los archivos existen · cada uno ≤ 800 LOC · el orden de enlaces de index.html) → suite 2138. Verificado en el navegador: las tres hojas de estilo se parsean y sus reglas se aplican.

[1.131.1] — 2026-07-31

Corregido

  • Consistencia del anclaje de host de los adaptadores en las dos fuentes de v1.130.0 (seguimientos de la revisión de código, defensa en profundidad; sin cambio de comportamiento para entradas válidas):
    • El adaptador a16z-speedrun-talent ahora revalida la anulación api: / a16z-speedrun-talent: en buildEndpoint (HTTPS + host exacto speedrun-talent-network.com) y recurre al feed canónico cuando falla — paridad con el adaptador cryptocurrencyjobs, de modo que un valor de host distinto nunca llega a la ranura de descarga (antes dependía únicamente de la comprobación assertSpeedrunUrl en el momento de la descarga). La comprobación de host exacto es ahora una única SPEEDRUN_TALENT_HOST_RE exportada, compartida por la comprobación y el adaptador.
    • El analizador de cryptocurrencyjobs — cleanUrl ahora usa la misma comprobación de host por coincidencia exacta que assertCryptocurrencyJobsUrl y la anulación del adaptador (antes era endsWith, que aceptaba subdominios). El analizador nunca es más permisivo que la comprobación SSRF: un enlace de elemento sub.cryptocurrencyjobs.co se descarta.
    • +2 tests → suite 2135.

[1.131.0] — 2026-07-31

Añadido

  • Tablero de pestañas de etapa CRM en #/tracker (portado desde la vista /pipeline de la app web del padre). La barra de chips de embudo y el desplegable de estado del tracker se sustituyen por una franja de pestañas de etapa: una pestaña All más una por cada estado canónico —Evaluated · Applied · Responded · Interview · Offer · Rejected · Discarded · SKIP · Hired— cada una mostrando un recuento en vivo de todo el historial, incluidas las etapas con recuento cero para que el embudo completo sea siempre visible (el aspecto CRM). La pestaña activa controla el filtro; volver a pulsarla la limpia de vuelta a All. Las filas conservan su tono de puntuación, legitimidad, PDF e informe, y la celda de empresa ahora muestra un logo de marca cuando los logos están activados (desactivado por defecto → cero peticiones adicionales).
    • Nueva ruta de solo lectura GET /api/tracker/stages devuelve el embudo canónico (etiquetas en orden) + un mapa de plegado de alias, obtenido de server/lib/states.mjs (templates/states.yml, con el fallback integrado) — así el cliente nunca fija de forma hardcodeada la whitelist de estados. La respuesta legacy de GET /api/tracker sin parámetros no cambia ({ rows } solamente).
    • Nueva librería de cliente pura y con tests unitarios public/js/lib/tracker-stages.js agrupa las filas según las etapas del servidor, tolerando negrita markdown perdida y alias localizados (p. ej. aplicado → Applied). Las pestañas son accesibles (role tablist/tab, aria-selected, área táctil ≥44 px, recuentos en el nombre accesible de cada pestaña). Sin nuevas claves i18n. Suite 2133.

[1.130.0] — 2026-07-31

Añadido

  • Dos nuevas fuentes de escaneo portadas del career-ops padre v1.24.0 (en proceso, sin nuevas dependencias; ambas aparecen en el filtro de Fuente de #/scan y en la landing de cvstart.org):
    • a16z Speedrun (a16z-speedrun-talent, #2231) — el feed JSON de todo el board de la red de talento de a16z Speedrun. Fijado al host speedrun-talent-network.com, solo HTTPS, paginación 0-indexada con tope de páginas, hilo de q/config por empresa, fail-soft.
    • Cryptocurrency Jobs (cryptocurrencyjobs) — la bolsa de empleo Web3 cryptocurrencyjobs.co, ingerida vía su feed público RSS 2.0 (sin autenticación). Decodificación de entidades XML en dos pasadas, ofertas solo remotas, empleador extraído de la cola del título "… at <Company>".
    • El registro total ahora es 72 fuentes = 67 en inglés + 5 en ruso (ALL_ADAPTERS = 67 adaptadores de portales en inglés).

Corregido

  • echojobs — los puestos híbridos siguen siendo distinguibles del remoto (refleja el #2258 del padre). Un marcador hybrid insensible a mayúsculas ahora produce "<Ciudad> · Híbrido" (o un simple Híbrido cuando no hay ciudad) y workplaceType: 'Hybrid', en vez de colapsarse en Remote.
  • radancy — marcado legacy de TalentBrew + transporte de fragmento de resultados JSON (refleja el a3e6df9 del padre), condicionado a un opts.fetchJson inyectable.

Notas

  • No portado — funcionalidades del padre exclusivas de CLI. La amplia superficie CLI/modos de career-ops v1.24.0 se mantiene fuera de web-ui, que es un visor + escritura ligera, no un host de modos: las tablas de cumplimiento/jurisdicción, la agenda de contactos + vCard, el debrief de transcripción de entrevistas / detección de plataforma de llamada, el cambio de estado del ledger, el registro de resultados, el triaje en dos pasadas, jd-similarity, el esquema versionado de artefacto de CV por postulación, la detección doctor de Playwright-MCP, y portals/fix-slugs.mjs. Los cambios de orquestación de escaneo que viven en el scan.mjs del padre —el escáner Playwright de Interamt.de, el barrido completo reverse-ATS de iCIMS, el filtro de elegibilidad por país remoto, el espaciado de búsquedas DNS, la deduplicación rltr de StepStone, y la columna de empresa normalizada en el historial de escaneo— no aplican: web-ui ejecuta los escáneres EN/RU en proceso y no invoca scan.mjs.
  • Ya cubierto. La corrección de plegado de acentos de role-matcher (#2209) se portó en v1.127.0, así que aquí es un no-op.

[1.129.1] — 2026-07-29

Corregido

  • Seguimientos de la revisión de IA sobre los ports web de v1.128/v1.129 (todos consultivos, corregidos en origen): precedencia de nivel en job-facets.js (un modificador explícito ahora gana a una palabra de gestión — Senior Engineering Manager → senior, antes lead); el fallback de states.mjs ya no se fija (una lectura correcta se memoiza, el fallback se devuelve sin caché, así un padre momentáneamente no disponible al arranque se relee después) y avisa (console.warn) ante un archivo presente pero malformado; score-tone.js — una fila sin puntuación es neutral (muted), no roja; domainFromName() omite slugs no-ASCII antes de /api/logo; +una comprobación de aislamiento en tests/states.test.mjs. +4 tests → 2073.

[1.129.0] — 2026-07-29

Añadido

  • Faceta de nivel + columna de antigüedad en #/scan — la librería job-facets.js de v1.128.0 ya está integrada en la UI de escaneo (antes solo lógica). Un nuevo desplegable Nivel clasifica el título de cada oferta en lead/staff/senior/mid/junior/becario (JobFacets.seniorityFromTitle) y se autocompleta con lo que hay en los resultados —como la faceta de País—; los títulos sin palabra de nivel siempre pasan. Se conserva en búsquedas guardadas, Restablecer y Aplicar. La tabla gana una columna Nivel (badge) y una columna Antigüedad sin tokens (hoy / Nd, de JobFacets.daysSince). 12 claves i18n ×17, +3 tests → 2069.

[1.128.0] — 2026-07-29

Añadido

  • Cuatro soluciones portadas de la propia web app del padre (../web/, Next.js), reimplementadas en JS vanilla/ESM, sin nuevas dependencias: (1) server/lib/states.mjs lee templates/states.yml en vivo como vocabulario canónico de estados del tracker (con fallback para CI) — elimina el re-sync manual del whitelist cada release; POST pliega alias (español/legacy) al rótulo canónico, el funnel de GET agrupa por estado canónico; (2) logos de empresa en filas con host ATS vía domainFromName() (~90 marcas→dominio) antes del avatar de letra; (3) score-tone.js — tono de puntuación de 4 niveles (≥4.2/3.8/3.0 + fallback de letra); (4) job-facets.js — facetas seniority/source/days. +21 tests.

Notas

  • No portado (solo concepto): la capa de acciones agénticas del padre (actions/registry.ts + api/assistant/route.ts) — plano para cuando docs-fab pase de chat de ayuda a copiloto. Sin nuevas fuentes (registro 70), sin cambios i18n/help.

[1.127.0] — 2026-07-29

Añadido

  • Tres nuevas fuentes de escaneo (paridad con career-ops v1.23.0) — el registro ahora trae 70 adaptadores (65 EN + 5 RU): Flowxtra (agregador global sin auth), VDAB (API por palabra clave del servicio público de empleo flamenco) e iCIMS (portales careers-<tenant>.icims.com, distinto de jibeapply). Además Cursor vuelve al roster de CLIs (parent #2115): cli-detect ahora detecta cursor (10 herramientas) y el roster se restaura en help/README/config ×17.

Corregido

  • agenticjobs pasó de scraping HTML a la API REST (#2167); Greenhouse recupera la ciudad del endpoint /offices cuando location.name es solo un modelo de trabajo (#2104); paridad de role-matcher (#1933/#2164/#2009: prefijo MTS, base product, plegado de acentos, desacuerdo sub-baseline).

Notas

  • No portado. El grueso de v1.23.0 es superficie CLI/dashboard sin uso en web-ui (batch-tailor, discover-ats, modos NL/PT, temas de PDF, dashboard Go, updater/doctor); los scripts relayados no requieren cambios. VERSION del padre → 1.23.0.

[1.126.1] — 2026-07-25

Corregido

  • Dos puntos de deriva del roster de CLI que el resync de v1.126.0 omitió — (1) el intro de la pestaña API keys de #/config (config.providerModelNote, i18n ×17) listaba solo 7 CLIs — ahora Antigravity y Grok Build se insertan tras OpenCode; (2) una segunda fila de tabla comparativa en la guía de ayuda (×17) y el help del sitio (construido en CI) aún decían Inside Claude Code / Codex / Cursor / Gemini CLI — el conjunto obsoleto con Cursor — ahora el roster completo. Ambos usaban separadores barra/punto medio que los patrones del barrido v1.126.0 no cubrían. Snapshot i18n regenerado; suite sigue en 1969.

[1.126.0] — 2026-07-25

Añadido

  • La pestaña de herramientas AI CLI ahora detecta las 8 CLIs de primera clase de career-ops — el roster de #/config se sincronizó con docs/SUPPORTED_CLIS.md del padre: server/lib/routes/cli-detect.mjs gana Grok Build CLI (grok) y Kimi CLI (kimi), y Antigravity ahora prueba primero su binario canónico agy. El escaneo de PATH de solo lectura reporta ahora 9 herramientas; sigue sin ejecutar ningún binario encontrado.

Cambiado

  • Resincronización de la documentación con career-ops.org/docs — se reconcilió cada superficie de docs con las páginas vivas del padre (leídas las 31). El roster canónico de asistentes IA (help ×17 + README ×17) lista ahora las 8 CLIs de primera clase — Claude Code, Codex, OpenCode, Antigravity CLI, Grok Build CLI, Qwen Code, Kimi, GitHub Copilot CLI — más Gemini CLI (wrapper heredado). Los paquetes de ayuda mantienen su estructura de 29 H2 / 105 H3.

[1.125.4] — 2026-07-23

Cambiado

  • dependencias de site (dependabot #151–#153) — sharp 0.34.5→0.35.3, svgo 4.0.1→4.0.2, fast-uri (dev) 3.1.3→3.1.4 en site/; build de Astro verde, sin impacto en SPA/servidor.

Notas

  • Barrido de paridad con el padre (career-ops 37d17ec..254764a, post-v1.22.0) — nada que portar: el guard de fila errónea de set-status (#2108) es solo CLI (en web-ui las filas del tracker se seleccionan explícitamente y ninguna ruta invoca set-status.mjs), el Risk Summary de modos localizados (#2109) toca archivos modes/<lang>/ que web-ui nunca lee (solo modes/*.md de nivel superior), la verificación de manifiesto de update-system (#2111) es solo del actualizador, y el resto son docs del padre (README turco, SIGNATURES ×4, SCRIPTS.md, acentos es). El VERSION del padre sigue en 1.22.0 — parentVersion sin cambios.

[1.125.3] — 2026-07-23

Corregido

  • Los prompts LLM en danés e hindi respondían en inglés (reportado por un usuario) — LOCALE_NAMES y los cinco bloques de SCAFFOLD_STRINGS en server/lib/prompts.mjs nunca se ampliaron para da ni hi, por lo que resolveLocale() caía a en y cada prompt de IA — deep research (en vivo y manual), modos, evaluación, entrevista, networking, CV Studio — perdía su directiva # Output language en esos dos idiomas. Ahora ambos son de primera clase: directiva de idioma + andamiaje localizado. La barrera de regresión en tests/locale-scaffold.test.mjs ahora recorre la lista canónica de 17 locales en lugar de una lista fija de 12, y una nueva verificación estructural falla si cualquier clave de andamiaje cae al inglés — un futuro locale que omita prompts.mjs ya no puede publicarse (+12 pruebas, la suite ahora 1969).

[1.125.2] — 2026-07-22

Corregido

  • Deep research con Gemini: HTTP 502 (MALFORMED_FUNCTION_CALL) (#145, contribución de @Alien10140) — el prompt en vivo de /api/deep pedía al modelo «Use WebFetch / WebSearch» y guardar el informe en un archivo, pero los proveedores de API sin herramientas no tienen canal de tools; Gemini respondía con una llamada a función en lugar de texto, lo que aparecía como un 502 vacío. buildDeepPrompt y bundleProjectContext aceptan ahora un flag headless: las ejecuciones en vivo (Anthropic/Gemini/cascada de respaldo) reciben un prompt sin herramientas que redacta el informe desde el contexto insertado, mientras que el prompt para copiar en Claude Code conserva sus instrucciones de herramientas. +1 test en tests/critical-fixes.test.mjs.

Cambiado

  • Modelos por defecto de Gemini actualizados más allá del obsoleto gemini-2.0-flash (#144, contribución de @Alien10140) — el desplegable de Configuración, el fallback del servidor en gemini.mjs (que discrepaba en silencio con la pista), la cadena de respaldo de OpenRouter, config.geminiModelHint ×17 y la guía de ayuda ×17 nombran ahora gemini-3.6-flash. La nueva puerta anti-deriva tests/gemini-default-model.test.mjs (+5 tests) fija todas las superficies al mismo literal — la suite llega a 1957 tests.

[1.125.1] — 2026-07-21

Corregido

  • SuccessFactors: los tenants RMK multimarca conservan su ruta de marca (padre #2099, tras v1.22.0) — las empresas holding que gestionan varias marcas adquiridas desde una única instancia RMK compartida las distinguen mediante un segmento de ruta (careers.nemetschek.com/Bluebeam/ frente a …/Vectorworks/); el adaptador solía colapsar la URL configurada a su origen, escaneando en silencio las ofertas de la marca matriz. El endpoint ahora conserva el prefijo de marca, eliminando solo un segmento final /search/ o /tile-search-results/ para que nada se duplique nunca sobre sí mismo; los tenants de dominio único quedan intactos byte a byte. Nuevo helper exportado resolveTenantBase + 1 bloque de test portado en tests/sources-successfactors.test.mjs.

[1.125.0] — 2026-07-21

Añadido

  • cvstart.org: sección “Fuentes de empleo” en la landing — una nueva sección entre las capturas de pantalla y la comparativa lista las 67 fuentes de escaneo como chips pulsables (62 portales/ATS en inglés + los 5 portales rusos bajo su propio subtítulo), cada uno enlazado al sitio público de la fuente. La lista se sincroniza con el registro de adaptadores en vivo durante el build (sync-assets.mjs → facts.sources), de modo que nunca puede desincronizarse de la app; un mapa de enlaces seleccionado en Sources.astro queda protegido por la nueva tests/site-sources.test.mjs. La navegación de cabecera ganó un ancla Fuentes; 4 nuevas claves i18n del sitio ×17. También se corrigió la lista inLanguage del JSON-LD de la landing, a la que todavía le faltaba hi.

[1.124.0] — 2026-07-21

Añadido

  • Cinco fuentes de escaneo (paridad con el padre v1.22.0, #1808/#1572/#2024/#2055) — Welcome to the Jungle (API JSON de todo el board), Agentic Engineering Jobs (portal de ingeniería agéntica/IA), Jobvite (ATS por tenant sin autenticación), Gem (ATS por tenant) y Alibaba Group (API JSON de carreras, patrón Meituan/Tencent). Cada una es un par fuente + adaptador anclado a su host, aislado para CI; el registro ahora ofrece 67 adaptadores (62 EN + 5 RU); el desplegable de origen de #/scan y su comprobación de desincronización quedan actualizados; cinco nuevas suites tests/sources-{wttj,agenticjobs,jobvite,gem,alibaba}.test.mjs.

Corregido

  • Arbeitsagentur: remoto a nivel nacional solo cuando homeofficetyp es VOLLSTAENDIG (padre #1981) — la consulta homeoffice=nv_true también devuelve puestos híbridos, así que ahora el filtro de remoto confirma cada resultado contra el endpoint de detalle de la oferta en lotes pequeños y falla de forma segura (un error de consulta conserva la ciudad real de la oferta, de modo que los filtros de ubicación siguen aplicándose).
  • SmartRecruiters: las URL públicas de las ofertas se construían sin /postings/ (padre #2047) — los enlaces ahora llevan a la página pública de la oferta en vez de un 404, para los tenants cuyo sitio público omite el segmento.

Notas

  • El padre v1.22.0 también incorporó cambios del lado del CLI que la interfaz web no invoca o ya cubre: la plantilla de CV en chino simplificado (zh-CN) + la tipografía del PDF, el modo /expand, los ajustes de caché de prompts de los proveedores (Gemini/OpenAI/Ollama), el desglose de tokens por paso (la interfaz web tiene su propio medidor de uso), la serialización con bloqueo de escritura del tracker (la interfaz web enruta las escrituras a través de withFileLock desde la v1.21), los indicadores visa_filter + fecha de publicación absoluta del CLI de escaneo (la interfaz web tiene su propio filtro “Publicado hace” por antigüedad), y la siembra de deduplicación de fuentes vistas (el escáner de la interfaz web mantiene su propia deduplicación de historial de escaneo).

[1.123.0] — 2026-07-17

Añadido

  • Fuente de escaneo Oracle Recruiting Cloud (paridad con el padre v1.21.0, #1929) — la API REST recruitingCEJobRequisitions sin autenticación de los portales de empleo de Oracle Fusion/ORC (JPMorgan Chase, Oracle, BNY Mellon, American Express, Honeywell…): anclada al host *.fa[.<región>][.ocs].oraclecloud.com, el número de sitio se resuelve a partir del careers_url de cada empresa seguida, paginación por offset con un tope duro de páginas, y cabeceras tipo navegador conscientes del WAF. El registro ahora ofrece 62 adaptadores (57 EN + 5 RU); el desplegable de origen de #/scan y su comprobación de desincronización quedan actualizados; nueva suite aislada para CI tests/sources-oraclecloud.test.mjs.

Corregido

  • Detector de reposts: los títulos base se mantienen distintos de sus variantes con sufijo especializado (padre #1922) — «Senior Analytics Engineer» ya no se agrupa con «Senior Analytics Engineer, People Analytics»: cuando los tokens de un título son un subconjunto estricto de los del otro y el token adicional es una especialización real (no una palabra genérica), ambos se tratan como vacantes publicables por separado. Las anotaciones de repost («(Repost)», «relisted») ahora se filtran como ruido léxico. +2 aserciones en tests/detect-reposts.test.mjs.

Notas

  • El padre v1.21.0 también incorporó cambios del lado del CLI que la interfaz web no invoca o ya cubre: el aviso de reaplicación a empresas repetidas (la interfaz web tiene su propio período de enfriamiento de reaplicación desde la v1.84.0), las opciones --format/--report de la carta de presentación, los modos de prompt de entrevista de señal de alerta / inteligencia del panel / no-show, la persistencia de señales de confianza y de salud de portales del escaneo (la interfaz web ejecuta su propio escáner en proceso con trust-validator y la página de salud de Portales), y las extensiones de stats/salary-gap (retransmitidas de solo lectura y con fallback silencioso).

[1.122.0] — 2026-07-16

Añadido

  • Hindi (हिन्दी) — el 17.º idioma — diccionario completo de la interfaz (~1110 claves), la guía de ayuda integrada completa (paridad de 29 H2 / 105 H3), README.hi.md, un nuevo CHANGELOG.hi.md (que empieza en la v1.122.0, siguiendo el precedente de de/it/tr), las páginas de landing + Metodología/Licencia/Historial de cambios/Ayuda de cvstart.org, el selector de idioma (🇮🇳), la autodetección del idioma del navegador, y una captura de pantalla del dashboard localizada. Todas las comprobaciones de paridad ×16 ahora se ejecutan ×17: paridad del diccionario i18n + snapshot, comprobaciones de paridad H2/H3 de la ayuda, paridad del CHANGELOG, check-i18n del sitio, y el barrido de locales de Playwright.

[1.121.0] — 2026-07-16

Añadido

  • cvstart.org: páginas de Metodología, Licencia e Historial de cambios — la landing suma tres secciones nuevas en los 16 idiomas, junto al bloque de Comparativa ya existente: /methodology/ (la rúbrica de puntuación de seis dimensiones de 0,0 a 5,0, el umbral de 4,0 para postular y las reglas que nunca se rompen — un resumen localizado de career-ops.org/methodology), /license/ (el texto canónico de la licencia MIT con la referencia a NOTICE.md), y /changelog/ (este mismo archivo, renderizado por locale a partir de los 16 CHANGELOG traducidos del repositorio). Nueva entrada Metodología en la cabecera y enlaces de Recursos en el pie; sync-assets.mjs ahora sincroniza el CHANGELOG ×16 y la LICENSE dentro del sitio en tiempo de compilación, de forma que las páginas nunca puedan desincronizarse del repositorio.
  • Enlaces a la metodología en toda la documentación — el README (los 16 idiomas), la lista canónica del §1 de la guía de ayuda integrada (los 16 idiomas) y la wiki ahora enlazan career-ops.org/methodology (además de la FAQ y el glosario) junto a las guías ya existentes de career-ops.org/docs.

Cambiado

  • Se refrescaron el banner de versión y los badges del README (1850 tests, versión v1.121.0) — el banner seguía anunciando la v1.119.5.

[1.120.0] — 2026-07-16

Añadido

  • El manifiesto de CareerOps (paridad con el padre v1.20.0) — el proyecto padre publicó el Manifiesto de CareerOps (MANIFESTO.md · career-ops.org/manifesto) y ahora lo muestra desde su README, el actualizador y el dashboard de Go. La interfaz web hace lo propio: un nuevo enlace en el pie de la barra lateral abre la página del manifiesto (nueva clave i18n footer.manifesto en los 16 locales), la guía de ayuda integrada suma el §29 «El manifiesto de CareerOps» en los 16 idiomas, el README explica qué es el manifiesto y cómo firmarlo, y el pie de la landing de cvstart.org también lo enlaza.

Notas

  • El padre v1.20.0 también corrigió la supresión de habilidades ya conocidas en el modo dirigido de upskill, silenció dotenv para que la salida stdout de scan --json siga siendo parseable, y arregló la plantilla HTML del CV para que la cabecera de un puesto se mantenga junto a sus puntos — superficies del lado del CLI en las que la interfaz web no interviene; no fue necesario ningún cambio de código en web-ui.

[1.119.5] — 2026-07-13

Corregido

  • El botón de idioma de la landing ya no se parte — con las banderas de v1.119.2 la etiqueta del selector en la cabecera (p. ej. «🇷🇺 Русский») podía romperse hasta en tres líneas en anchos de escritorio estrechos; la etiqueta del selector y todas las opciones del desplegable llevan ahora whitespace-nowrap — bandera + endónimo siempre en una línea. La lista de idiomas del pie pasó de una cuadrícula rígida de dos columnas a una fila envolvente de elementos de una línea — «🇧🇷 Português (Brasil)» tampoco se parte ya a mitad de nombre.

[1.119.4] — 2026-07-13

Cambiado

  • LICENSE nombra al autor — la línea de copyright ahora dice: Sergei Emelianov (Fighter90) https://sergey-cv.com and career-ops-ui contributors (texto MIT canónico intacto). Un nuevo NOTICE.md detalla el licenciamiento: quién posee el copyright, qué cubre exactamente la concesión MIT (código, docs, traducciones, la landing, la wiki), qué NO cubre (tus datos en runtime, el proyecto padre, el contenido de las bolsas de empleo, las marcas), la tabla de componentes de terceros (express/js-yaml — MIT; Astro/Tailwind — MIT; las fuentes Figtree y JetBrains Mono — SIL OFL 1.1; sharp — Apache-2.0) y una línea de atribución opcional.

[1.119.3] — 2026-07-13

Añadido

  • SECURITY.md — la política de seguridad a la que apuntaba CONTRIBUTING ahora existe: versiones soportadas, flujo de reporte privado (el repo tiene habilitado el private vulnerability reporting de GitHub — pestaña Security → «Report a vulnerability»), el modelo de amenazas para una app de un solo usuario en localhost (en alcance: XSS vía ofertas hostiles / SSRF / path traversal / fuga de secretos / debilitamiento de CSP; fuera de alcance: DoS a tu propio localhost y problemas del proyecto padre) y la línea base de hardening para revisores.

[1.119.2] — 2026-07-13

Añadido

  • CONTRIBUTING.md — la guía del contribuidor que la landing y el README enlazaban desde siempre ahora existe: instalación, mapa del proyecto, las reglas duras de seguridad/no-build, niveles de testing, el walkthrough de los «dos registros» para añadir una fuente de escaneo, el contrato i18n ×16, convenciones de commits/PR y el proceso de release.
  • Banderas de idiomas en la landing — el selector de idiomas de cvstart.org, la cuadrícula de idiomas del pie y el banner «lee en tu idioma» ahora muestran la bandera de cada locale junto a su endónimo (el mismo set de indicadores regionales que el <select> de idioma de la app; degrada a letras de región donde faltan los glifos de bandera).
  • Arreglos del pie de la landing — el enlace muerto a Discussions (la función no está habilitada en el repo) ahora apunta a la wiki del proyecto, y el pie acredita al autor: Sergei Emelianov (sergey-cv.com · LinkedIn).

[1.119.1] — 2026-07-13

Corregido

  • El filtro de fuentes de #/scan alcanzó al registro — la lista estática FALLBACK_SOURCES tras el desplegable Source (usada solo cuando GET /api/scan/sources no responde) llevaba rezagada en silencio desde v1.87.0: faltaban 20 proveedores en el fallback offline (Amazon, Avature, SAP SuccessFactors, Get on Board, Dassault Systèmes, beesite, HigherEdJobs, JibeApply (iCIMS), softgarden, Cornerstone, Phenom, Radancy, Deutsche Bahn, EchoJobs, TKMS, Heckler & Koch, Rheinmetall, LaraJobs y los nuevos Meituan / Tencent). Sincronizada con los 61 y ahora protegida por un test de deriva que rompe la CI si la lista del cliente diverge del registro del servidor (valores Y etiquetas). +1 test (1845).

[1.119.0] — 2026-07-13

Paridad con el career-ops padre v1.19.0 + refresco de la landing cvstart.org.

Añadido

  • 2 nuevos proveedores de escaneo — Meituan (zhaopin.meituan.com) y Tencent (careers.tencent.com): las APIs JSON públicas sin autenticación de las bolsas tech chinas, detectadas por host o seleccionadas con un provider: explícito, con búsqueda del lado servidor por palabra clave, paginación y deduplicación por URL — ahora 61 adapters (56 EN + 5 RU). +20 tests (1844).
  • Bloque de contribuidores en la landing — cvstart.org muestra los avatares de todas las personas que aportaron código (API /contributors de GitHub en tiempo de build, bots filtrados), localizado en los 16 idiomas, con enlace al grafo completo de contribuidores.
  • Contador de estrellas de GitHub en vivo en la landing — la insignia de la cabecera se refresca ahora del lado cliente desde la API de GitHub en cada visita (la instantánea de build queda como fallback), y una reconstrucción semanal programada de Pages mantiene frescos la instantánea y la lista de contribuidores; las llamadas a la API en CI van autenticadas con token.

Corregido

  • Las peticiones Workday CXS llevan cabeceras de navegador (padre #1813) — los tenants tras Cloudflare (visto en vivo: geico) responden 500 a peticiones sin UA/accept-language/origin/referer normales; el fetcher deriva ahora el origin y el slug del site desde la propia URL CXS. Las peticiones de Glints ganaron el mismo UA de navegador + origin/referer, ambos desde la constante compartida BROWSER_LIKE_USER_AGENT de http-json.mjs.

[1.118.4] — 2026-07-10

Corregido

  • Los escaneos de hh.ru devolvían 0 resultados desde una IP rusa (enlaces de subdominio regional) — desde una IP residencial rusa, hh.ru redirige la búsqueda (302) a un subdominio regional (sochi.hh.ru, spb.hh.ru, …) y devuelve los enlaces de vacantes en ese subdominio. El parser buscaba el enlace del título con el host fijo https://hh.ru/vacancy/ y no coincidía con ninguno de los regionales, así que un escaneo plenamente funcional registraba 0 en silencio. Ahora acepta cualquier host *.hh.ru (los anuncios en adsrv.hh.ru/click?… siguen excluidos — no tienen ruta /vacancy/<id>) y canonicaliza cada URL de resultado a https://hh.ru/vacancy/<id>. Verificado en vivo: 17 vacantes reales se parsean desde una página sochi.hh.ru que antes daba 0. +1 test (1824).

[1.118.3] — 2026-07-10

Corregido

  • hh.ru devolvía 0 resultados en silencio (interstitial de verificación de VPN) — hh.ru ahora redirige con 302 a las redes que marca como VPN/proxy (IPs de datacenter) a un interstitial /vpncheeck (“VPN мешает работе сайта”) que responde HTTP 200 sin ninguna tarjeta de vacante, por lo que el escaneo reportaba 0 sin error alguno. El escáner ahora detecta la redirección por la URL final de la respuesta, desactiva hh.ru durante el resto de la ejecución e imprime una pista honesta: el tráfico debe salir realmente por una IP residencial — un VPN/proxy a nivel de sistema puede seguir activo aunque el interruptor del navegador esté apagado. +1 test (1823).

[1.118.2] — 2026-07-10

Mantenimiento

  • Seguimiento del landing (#118) — site/README.md reconciliado con Astro 7 (la mejora de seguridad de #116), import sin uso eliminado y +4 guardas ejecutables para los scripts de build del landing: la puerta de paridad i18n falla de forma demostrable con un diccionario roto y sync-assets nunca escribe fuera de site/ — suite 1822. Dos alertas de CodeQL resueltas (una corregida en el código, otra descartada como comportamiento previsto del build).

[1.118.1] — 2026-07-10

Corregido

  • Escaneo de hh.ru desde fuera de Rusia — hh.ru ahora devuelve HTTP 451 (bloqueo legal regional) a las IP no rusas en sus páginas públicas de búsqueda. El escáner trata el 451 como el 403: tras el primer bloqueo hh.ru se desactiva para el resto de la pasada con un mensaje honesto en el log que apunta a una IP rusa / salida VPN, sin desperdiciar las consultas restantes ni las demás fuentes RU. Ayuda §7 actualizada en los 16 idiomas. +1 prueba (1818).

[1.118.0] — 2026-07-09

Paquete de paridad con el career-ops padre v1.18.0.

Añadido

  • 9 nuevos proveedores de escaneo — Cornerstone OnDemand (csod), Phenom (phenom), Radancy (radancy), Deutsche Bahn (deutschebahn), EchoJobs (echojobs), TKMS (tkms), Heckler & Koch (hecklerkoch), Rheinmetall (rheinmetall), LaraJobs (larajobs) — ahora 54 adaptadores. El adaptador de Lever además detecta tableros del tenant EU (jobs.eu.lever.co).
  • Estado Hired en el tracker (paridad con states.yml del padre): las ofertas aceptadas tienen su propio estado canónico, una insignia celebratoria y un banner de «trabajo conseguido» en #/tracker; el embudo y las conversiones lo cuentan como avanzado por todas las etapas.
  • Pestaña Histórico en #/stats — retransmisión de solo lectura del stats.mjs del padre (resumen histórico del tracker, tasas del embudo acumulado, totales del escáner, cobertura de portales) más observaciones de compensación de salary-gap.mjs (deseado vs anunciado vs real, por candidatura). Nuevas rutas GET /api/stats/lifetime y GET /api/stats/salary-gap — shell-outs de cero tokens, degradación segura {available:false} sin el proyecto padre.
  • 28 nuevas claves i18n en los 16 idiomas; guía de ayuda §14/§26 actualizada en todos.

Pruebas

  • +38 pruebas unitarias (tres suites de paridad de proveedores + rutas de relé/estado) — 1817 en total.

[1.117.2] — 2026-07-06

Arreglo de tracker vacío para los shell-outs de paridad. Los scripts del padre salen con código 1 y un JSON {error} estructurado cuando el tracker aún no tiene candidaturas; el tablero de seguimiento y la pestaña de patrones lo mostraban como «script-error». Ambas rutas ahora lo transmiten como un estado vacío sano (available:true, empty:true) y la UI muestra su mensaje honesto de «nada todavía». Verificado en vivo contra un padre real.

Nuevo: ninguno.

[1.117.1] — 2026-07-06

Endurecimiento de v1.117.0 (triaje de CodeQL). Los tres endpoints shell-out (GET /api/followup, POST /api/followup/seed, GET /api/stats/patterns) llevan ahora el limitador por IP compartido (generan un proceso hijo por petición; no-op en loopback). La extracción de texto por URL de Añadir al CV elimina etiquetas hasta punto fijo y luego borra todo </> restante — saneamiento demostrablemente completo para texto de prompt LLM. Sin cambios para entradas válidas.

Nuevo: ninguno.

[1.117.0] — 2026-07-06

Paquete de paridad con el padre — seis capacidades del career-ops padre llevadas a la UI. (1) Tablero de cadencia de seguimiento en #/followup con urgencia por candidatura (🔴/🟠/🟡/🔵) desde followup-cadence.mjs, más el botón Sembrar fechas (followup-seed.mjs --backfill). (2) Patrones de rechazo: cuarta pestaña de Estadísticas que ejecuta analyze-patterns.mjs (solo lectura) — mezcla de resultados, recomendaciones y tasa de avance por proveedor ATS. (3) Añadir al CV: una tarjeta de CV Studio convierte una URL o texto pegado en viñetas ATS basadas SOLO en esa fuente (solo sugerencias, sin escrituras; el fetch de URL está protegido contra SSRF). (4) 4 nuevos proveedores de escaneo — beesite, HigherEdJobs (RSS), JibeApply (iCIMS), softgarden — el registro ahora incluye 50 adaptadores (45 EN + 5 RU), todos en el desplegable de Scan. (5) Paso de pre-escaneo de descalificadores en el checklist de Apply. (6) Runner reconcile (/api/run/reconcile). Las rutas shell-out degradan con honestidad sin los scripts del padre.

  • Nuevo módulo server/lib/routes/followup.mjs (31.º) + nuevas rutas + 8 archivos source/adapter. Pruebas: 6 + 7 nuevas; suite 1737 → 1750. 41 claves i18n ×16. Ayuda §13/§17/§24/§26 ampliada ×16.

Nuevo: GET /api/followup, POST /api/followup/seed, GET /api/stats/patterns, POST /api/cv-studio/add-entry, POST /api/run/reconcile.

[1.116.0] — 2026-07-06

Medidor de uso rehecho + primer test end-to-end de widgets. El medidor de uso de IA (v1.114.0) queda fijado correctamente: ahora está fijado al fondo de la barra lateral izquierda (todo el ancho de la barra, con su misma superficie) y reserva abajo un espacio igual a su altura para que el menú nunca quede tapado — la navegación y el pie de versión siempre se desplazan libres por encima. Se actualiza en vivo (cada 15 s, y al enfocar la pestaña y al cambiar de ruta), y cada fila de ventana muestra ahora <tokens> · <coste estimado> real (las barras se escalan contra la ventana de 30 días) en vez de un “porcentaje” siempre al 100%. Además: una barrera typeof duradera en el importador de CV cierra en origen el falso positivo recurrente de type-confusion de CodeQL, y un nuevo test end-to-end de Playwright ejercita ambos widgets persistentes en un navegador real.

  • public/js/lib/usage-hud.js + app.css, server/lib/cv-import.mjs. Pruebas: tests/playwright-widgets.mjs (2 E2E) + tests/usage-hud.test.mjs (10). Ayuda §6 ampliada ×16.

Nuevo: ninguno.

[1.115.0] — 2026-07-06

Retoque de diseño (conservador, se mantiene la marca coral). Una pasada ligera de refinamiento sobre el sistema de diseño compartido — sin reestructurar, sin cambiar la paleta. Las tarjetas de métricas del panel ahora se elevan y toman un borde coral al pasar el ratón (como los mosaicos de acción rápida); las tarjetas de contenido se elevan un poco; los botones primary / dark / danger ganan una sombra en reposo y una ligera elevación al pasar el ratón; los números grandes se alinean con tabular-nums; y los controles interactivos reciben un halo coral suave tras el anillo de teclado de 2px. Todo el movimiento respeta prefers-reduced-motion, y el halo se limita a los controles — nunca un *:focus-visible global.

  • Solo CSS (public/css/app.css); sin cambios de marcado, i18n, rutas ni CSP. Pruebas: tests/design-polish-v1115.test.mjs (5). Verificado en vivo con Playwright.

Nuevo: ninguno.

[1.114.0] — 2026-07-06

Medidor de uso y coste de IA en la barra lateral (abajo a la izquierda). Una sección USO compacta ahora aparece al final de la barra lateral (una tarjeta fija abajo a la izquierda si no hay barra lateral; abajo a la derecha en RTL) en cada página. Muestra tu uso de tokens LLM en ventanas de 24h / 7d / 30d — cada una como <tokens> · <cuota%> con una barra verde (cuota del total histórico) — más un pie con el coste estimado de 24h. Los datos son el resumen de solo lectura GET /api/usage de data/llm-usage.jsonl (solo local), la misma fuente que la página #/usage; el coste es una estimación y las ejecuciones en modo manual son gratis y no se cuentan. Plegable — la cabecera alterna y el estado se conserva.

  • Nuevo widget cliente public/js/lib/usage-hud.js cargado desde index.html, montado en la barra lateral sobre el pie de versión (alternativa de esquina fija). Seguro para CSP; con tema y espejo RTL. Sin nueva ruta de servidor. Pruebas: tests/usage-hud.test.mjs (8). 3 nuevas claves i18n ×16.

Nuevo: ninguno.

[1.113.0] — 2026-07-06

Asistente flotante “Preguntar a la guía” en cada página. Un botón de chat con un robot y degradado ahora flota en la esquina inferior derecha (inferior izquierda en RTL) de cada página. Púlsalo para abrir un chat compacto que responde preguntas de uso basándose SOLO en la guía de ayuda en tu idioma — el mismo endpoint que la página #/docs-assistant (POST /api/docs-assistant/ask), así que nunca lee tu CV, perfil ni seguimiento. En vivo con una clave LLM; sin clave → un prompt listo para usar. La cabecera muestra un avatar de robot + estado en línea; los chips inician preguntas comunes; Escape o clic fuera lo cierra; se oculta en la página #/docs-assistant.

  • Nuevo widget cliente public/js/lib/docs-fab.js montado globalmente desde index.html; seguro para CSP; estilos con tema y espejo RTL en app.css. Sin nueva ruta de servidor. Pruebas: tests/docs-fab.test.mjs (8). 6 nuevas claves i18n ×16. Ayuda §1 ampliada en su sitio.

Nuevo: ninguno.

[1.112.0] — 2026-07-06

Consolidación de docs y QA. Sin cambios de código visibles. El documento de convenciones SDD (docs/sdd/CONVENTIONS.md) se actualiza a los 30 módulos de ruta actuales (antes 24) y a la base de pruebas actual; el prompt QA definitivo de todo el proyecto (qa/QA-REGRESSION-PROMPT.md) se consolida — mecánica de release actualizada (v1.111, parentVersion 1.17.0, publicación disparada por el release), la tabla de adiciones §14 corregida (Excluir de Scan reetiquetado v1.109.0) y ampliada con el cierre de CodeQL de v1.111 — para valer por sí solo como único prompt de regresión de toda la funcionalidad. Añade una prueba de cobertura para la rama de carga sobredimensionada.

Nuevo: ninguno.

[1.111.0] — 2026-07-06

Seguridad — cierre del backlog de CodeQL. Tres refuerzos de defensa en profundidad que cierran los hallazgos de análisis estático restantes en el origen en lugar de descartarlos. stripDangerousMarkdown ahora escapa el < de cualquier apertura de etiqueta peligrosa truncada (una carga que termina en <script/<iframe/…), de modo que su salida no contiene ninguna etiqueta peligrosa viva. La importación de CV lee el tamaño del búfer subido mediante una coerción explícita con Number() — una barrera contra la confusión de tipos. Las líneas de rol de los modos ahora son cadenas de plantilla interpoladas con String.replace en vez de funciones almacenadas, eliminando por completo la llamada de despacho dinámico. Sin cambios visibles para el usuario.

  • server/lib/security.mjs, server/lib/cv-import.mjs, server/lib/prompts.mjs. Pruebas: tests/security-hardening-v1111.test.mjs (7) + prueba de guarda v1108 actualizada. Sin cambios de i18n/ayuda/rutas.

Nuevo: ninguno.

[1.110.0] — 2026-07-06

Actualización de docs y QA (todos los idiomas). Sin cambios de código. El prompt de QA de todo el proyecto se actualiza a v1.109.0 con un nuevo §14 (v1.98→v1.109), y los prompts perennes de UX-audit y design-export ganan la superficie actual. Cada párrafo de ayuda añadido en v1.100–v1.109 está ahora traducido a los 16 idiomas.

Nuevo: ninguno.

[1.109.0] — 2026-07-06

Filtro Excluir en Scan + resumen del pipeline (paridad de diseño con la web principal). En #/scan, el cuadro Buscar trata las comas como O (“roles a encontrar”) y un nuevo campo Excluir oculta cualquier fila cuya empresa/rol/ubicación contenga alguna palabra separada por comas (p. ej. senior, staff); ambos se recuerdan en tus búsquedas guardadas. En #/pipeline, una tira de resumen compacta muestra tu pipeline de un vistazo — N en la bandeja, N en seguimiento y los recuentos de Applied / Responded / Interview / Offer del tracker, cada chip enlaza a #/tracker.

  • Solo cliente (sin nueva ruta/escrituras). public/js/views/scan.js + public/js/views/pipeline.js. Pruebas: tests/scan-pipeline-ui-v1109.test.mjs (2). 4 nuevas claves i18n ×16. Ayuda §7 + §8 ampliadas en su sitio.

Nuevo: ninguno.

[1.108.0] — 2026-07-06

Refuerzo de seguridad (triaje de CodeQL, ronda 2). Tres hallazgos más de baja severidad corregidos: el constructor de prompts resuelve la línea de rol del idioma por clave propia + typeof === function para que un idioma manipulado no despache a un método de prototipo (unvalidated-dynamic-method-call); el slug del nombre de archivo PDF se limita a 200 caracteres antes del regex para que una entrada de solo guiones no retroceda (ReDoS polinómico); y la importación de documentos coacciona un filename de tipo array (encabezado repetido) a cadena (type-confusion). Sin cambios de comportamiento para entrada válida.

  • server/lib/prompts.mjs, server/lib/routes/runners.mjs, server/lib/cv-import.mjs + tests/security-hardening-v1108.test.mjs (3). En v1.106–v1.108 el backlog de análisis estático pasó de 167 a ~14, con cada hallazgo realmente relevante para la seguridad corregido y el resto (falsos positivos protegidos/saneados + lint de nivel nota) descartado con justificación.

Nuevo: ninguno.

[1.107.0] — 2026-07-06

Refuerzo del saneador (defensa en profundidad XSS en reposo). stripDangerousMarkdown — que neutraliza HTML peligroso en el markdown de CV/oferta almacenado para que cualquier consumidor que evite el cliente con escape-al-renderizar siga seguro — ahora ejecuta su limpieza de etiquetas hasta un punto fijo (repetir hasta estabilizar) para que una eliminación que reforme una carga (p. ej. <scr<script></script>ipt>) se detecte, coincide con etiquetas de cierre de script/style/etc. con basura final (</script foo>) y elimina un abridor ejecutable sin cerrar. El comportamiento para markdown válido no cambia — solo elimina más.

  • server/lib/security.mjs: bucle de punto fijo (limitado a 8 pasadas) + patrones de cierre [^>]*> + eliminación de abridor sin cerrar. +3 casos de regresión en tests/cv-xss-bypasses.test.mjs. El límite XSS autoritativo sigue siendo el escape en salida (UI.md); esto refuerza la garantía en reposo y cierra los hallazgos de CodeQL correspondientes.

Nuevo: ninguno.

[1.106.0] — 2026-07-06

Refuerzo de seguridad (triaje de CodeQL). Se corrigieron tres hallazgos reales (aunque de baja severidad): la ruta de error de renderizado ahora escapa el mensaje de error antes de que llegue al DOM (un error del servidor puede reflejar entrada del usuario, así que se trata como no confiable — límite XSS), y las escrituras de propiedades de perfil/config rechazan las claves __proto__ / constructor / prototype (protecciones contra contaminación de prototipos por si acaso — las claves vienen de specs de campos fijos, no de entrada bruta). La mayoría de las alertas restantes son falsos positivos sobre lecturas/escrituras legítimas del escáner en data/* y sobre rutas que ya llevan el limitador propio; se descartaron con justificación.

  • public/js/router.js escapa err.message con UI.escapeHtml antes de innerHTML; server/lib/routes/content.mjs y server/lib/routes/config.mjs protegen las claves de prototipo. Sin cambios de comportamiento para entrada válida. Pruebas: tests/security-hardening-v1106.test.mjs (3). Sin nuevas claves i18n.

Nuevo: ninguno.

[1.105.0] — 2026-07-06

Página de uso y coste de IA. Una nueva página Uso de IA (barra lateral, junto a Salud) muestra cuántos tokens has gastado en generaciones de IA en vivo — evaluaciones, informes, chats — desglosado por proveedor en las últimas 24 horas, 7 días, 30 días y todo el tiempo, con un coste estimado en USD. Cada llamada en vivo añade un pequeño registro {provider, in, out} a data/llm-usage.jsonl (no se envía nada a ningún sitio); las ejecuciones sin clave (modo manual) no cuestan nada y no se registran.

  • Nuevo módulo de ruta (30.º) server/lib/routes/usage.mjs — GET /api/usage (agregados de solo lectura) + server/lib/llm-usage.mjs (recordUsage normaliza las formas de uso de Anthropic/OpenAI/Gemini y añade en modo best-effort; readUsage/aggregate agregan por ventana 24h/7d/30d/todo × proveedor) + server/lib/llm-pricing.mjs (una tabla editable de precios por proveedor $/1M tokens — los tokens son exactos, los dólares son precios de lista aproximados que puedes corregir; nunca se facturan). El registro se engancha en los puntos de despacho (runActiveProvider + routes/llm.mjs).
  • Nueva vista public/js/views/usage.js (#/usage, pestañas de ventana). Pruebas: tests/usage-routes.test.mjs. 17 nuevas claves i18n ×16 (usage.* + nav.usage). Ayuda §6 ampliada en su sitio.

Nuevo: server/lib/routes/usage.mjs; server/lib/llm-usage.mjs; server/lib/llm-pricing.mjs; public/js/views/usage.js.

[1.104.0] — 2026-07-06

Logos de empresa en la tabla de escaneo (respetando la privacidad). Un nuevo interruptor de Apariencia en Ajustes — Mostrar logos de empresa en la tabla de escaneo (desactivado por defecto) — dibuja el logo de cada empresa junto a su nombre en #/scan. El logo es el favicon de la empresa obtenido desde su propio dominio y con proxy en el servidor (GET /api/logo), de modo que ningún servicio de logos de terceros sabe qué empleadores estás viendo. Las ofertas en un portal de empleo compartido (Greenhouse, Lever, Ashby, …) muestran una insignia con una letra en lugar del icono del portal, y cualquier logo que no cargue recae en la misma insignia.

  • Nuevo módulo de ruta (29.º) server/lib/routes/logos.mjs — GET /api/logo?domain=. Valida el dominio (sin esquema/ruta/loopback), obtiene /favicon.ico a través del safeGet seguro contra SSRF (un nuevo modo binary devuelve los bytes crudos + content-type; el fijado de DNS, la validación de redirecciones y el límite de tamaño no cambian), hace sniffing de firmas de imagen para no servir nunca una página HTML de error como imagen, cachea aciertos y fallos en un LRU en memoria y no escribe nada en disco.
  • Nueva librería cliente public/js/lib/company-logo.js (window.CompanyLogo): desactivada por defecto vía flag en localStorage; omite hosts ATS compartidos en favor de un avatar-letra determinista; respaldo img.onerror seguro para CSP. Pruebas: tests/logo-routes.test.mjs. 5 nuevas claves i18n ×16 (appear.*). Ayuda §2 ampliada en su sitio.

Nuevo: server/lib/routes/logos.mjs; public/js/lib/company-logo.js.

[1.103.0] — 2026-07-06

Ajustes: “Herramientas CLI de IA” — cuáles están instaladas. career-ops se basa en Claude Code pero funciona con cualquier CLI de agente del estándar abierto de skills. Una nueva pestaña Herramientas CLI de IA en Ajustes (#/config) muestra cuáles — Claude Code, Codex, Gemini CLI, OpenCode, GitHub Copilot CLI, Qwen, Antigravity — están instaladas en la máquina que ejecuta el servidor, y sus rutas. Es un escaneo de solo lectura del PATH: solo comprueba si cada binario existe y nunca lo ejecuta (sin --version, sin ejecución), no escribe nada y no toca datos del usuario.

  • Nuevo módulo de ruta (28.º) server/lib/routes/cli-detect.mjs — GET /api/cli-detect. La detección resuelve la ruta de un binario desde una lista blanca fija de 7 entradas en process.env.PATH (shims .cmd/.exe/.bat en Windows; bit de ejecución en POSIX); un archivo hostil en PATH nunca puede ser ejecutado por esta ruta.
  • Nueva pestaña “Herramientas CLI de IA” en public/js/views/config.js (carga diferida, enlazable con #/config?tab=cli). Pruebas: tests/cli-detect-routes.test.mjs. 8 nuevas claves i18n ×16 (cli.* + config.tabCli). Ayuda §2 ampliada en su sitio.

Nuevo: server/lib/routes/cli-detect.mjs.

[1.102.0] — 2026-07-05

“Preguntar a la guía” — un chat fundamentado sobre la guía de ayuda integrada. Nueva página Preguntar a la guía 💬 (barra lateral, bajo Ayuda): escribe una pregunta como “¿Cómo escaneo portales de empleo?” y obtén una respuesta extraída solo de la guía de ayuda de la app en tu idioma — muestra qué secciones usó y nunca lee tu CV, perfil ni tu búsqueda de empleo. Es sobre cómo usar la app, no sobre ti. Con clave LLM responde en vivo; sin clave te entrega un prompt listo, ya relleno con las secciones de ayuda relevantes.

  • Nuevo módulo de ruta (27.º) server/lib/routes/docs-assistant.mjs — POST /api/docs-assistant/ask. Recuperación sin dependencias: la guía de tu idioma se divide en sus secciones ## y se puntúan por coincidencia de palabras clave con tu pregunta; las mejores se incluyen y el modelo debe responder desde ellas o decir que la guía no lo cubre (sin funciones/rutas inventadas). Cascada de proveedores compartida, respaldo manual, con límite de tasa, sin escrituras, no lee datos del usuario.
  • Nueva vista public/js/views/docs-assistant.js. Pruebas: tests/docs-assistant-routes.test.mjs. 14 nuevas claves i18n ×16 (docs.* + nav.docsAssistant). Ayuda §1 ampliada en su sitio.

Nuevo: server/lib/routes/docs-assistant.mjs; public/js/views/docs-assistant.js.

[1.101.0] — 2026-07-05

CV Studio: adapta tu CV + escribe una carta de presentación para un empleo concreto, con verificación tipo reclutador. Nueva tarjeta Adaptar a un empleo en #/cv-studio: pega una descripción de empleo (y, opcionalmente, un rol/titular objetivo) y CV Studio produce un CV adaptado a esa oferta más una carta de presentación a juego, y luego los pasa por una verificación antes de entregarlos — los error bloquean (se corrigen antes de que veas el resultado), los warn aconsejan. La mecánica está destilada de la práctica de coaching de carrera en reglas genéricas — el reclutador lee en segundos, así que lo relevante va arriba, el titular coincide con el rol de la vacante, los resultados llevan cifras concretas y la carta es un teaser breve con un único puente “requisito ↔ tu hecho que encaja”. Se basa solo en tu CV, perfil y two-pager y nunca inventa — sin empresas, roles ni historia codificados.

  • Nuevo endpoint POST /api/cv-studio/tailor (amplía el módulo cv-studio existente — sin 27.º módulo): buildTailorPrompt + una verificación genérica TAILOR_INSTRUCTIONS, basada en bundleProjectContext (CV + perfil + two-pager), cascada de proveedores compartida, respaldo manual sin clave, con límite de tasa, sin escrituras. El resultado se exporta a Markdown / PDF / DOCX con la barra compartida report-export.js.
  • Pruebas: +3 en tests/cv-studio-routes.test.mjs. 10 nuevas claves i18n ×16 (cvs.tailor*). Referencia genérica docs/prompts/resume-cover.md. Ayuda §24 ampliada en su sitio.

Nuevo: docs/prompts/resume-cover.md.

[1.100.0] — 2026-07-05

Two-pager: autorrelleno con IA desde tu CV + Vista previa + exportar a PDF/DOCX/Markdown. El two-pager (#/two-pager) recoge lo que de verdad quieres de tu próximo puesto, pero antes había que redactar cada campo a mano o copiar un prompt en otra herramienta. Ahora el ✨ asistente de relleno con IA se ejecuta en vivo con tu proveedor configurado: lee solo tu CV + perfil (vía bundleProjectContext, sin inventar nada), redacta todos los campos (quién soy / me gusta / imprescindibles / detesto / líneas rojas / innegociables / entorno objetivo) y rellena el formulario para que lo revises, edites y guardes. Sin clave de API vuelve al modal de copiar-el-prompt, igual que antes. Un nuevo botón 👁 Vista previa y exportar renderiza el two-pager como documento con una barra Descargar .md / Guardar como PDF / Guardar como DOCX / Copiar.

  • Exportación .docx sin dependencias. Nuevo server/lib/docx.mjs que genera un .docx Office Open XML mínimo pero válido (un ZIP DEFLATE de las cuatro partes OOXML, con CRC-32 por entrada) — sin nueva dependencia (las deps siguen siendo express + js-yaml). Nueva ruta POST /api/export/docx (server/lib/routes/export.mjs, el 26.º módulo de rutas; sin estado, limitado a 200 KB, sin escrituras / sin LLM / sin fetch de URL). Integrado en el public/js/lib/report-export.js compartido, así que el informe de mercado, el plan de carrera y la orientación profesional también ganan exportación a DOCX.
  • El autorrelleno en vivo usa la cascada de proveedores compartida (runActiveProvider / providerAvailable); el YAML devuelto se parsea y se ajusta de vuelta a la forma acotada del two-pager (parseYamlFields + normalizeTwoPager) — claves desconocidas descartadas, arrays/cadenas limitados. Se conserva el modo manual.
  • Pruebas: tests/export-routes.test.mjs. 4 claves i18n nuevas ×16 (export.saveDocx, twoPager.preview, twoPager.aiFilling, twoPager.aiFilled).

Nuevo: server/lib/docx.mjs; server/lib/routes/export.mjs.

[1.99.0] — 2026-07-05

Página de estado de portales (#/portals). El escáner vigila un conjunto de empresas en portals.yml; un slug de ATS puede romperse silenciosamente y ese empleador desaparece de todos los escaneos futuros. La nueva página Portals lista cada empresa vigilada y, al pulsar Check portal health, sondea cada careers_url a través del safeGet con DNS fijado (a prueba de SSRF) y marca las caídas (un 404 = descartada en silencio) — solo lectura. También refuerza el reportador de errores de la v1.98.0 tras la revisión: el búfer de errores ahora captura fallos de red del fetch, y el limpiador redacta claves de proveedor sin etiqueta.

Nuevo: server/lib/routes/portals.mjs; public/js/views/portals.js.

[1.98.0] — 2026-07-05

Reportador de errores integrado (paridad con el web web-v0.2.0 del proyecto padre). Un botón 🐞 Report a bug en el cajón de notificaciones reúne una instantánea de diagnóstico con piso de privacidad — versiones, tu pantalla, navegador, un resumen de comprobaciones de /api/health y los últimos 20 errores de un nuevo búfer circular del cliente — más una huella de deduplicación determinista (co-web-<base36>), te deja revisar el Markdown exacto y luego abre una incidencia de GitHub pre-rellenada. Nada se envía automáticamente; nunca lleva tu CV, perfil, respuestas, URLs de empleo ni claves. Nuevas libs logbuf.js + bug-report.js; 11 claves i18n ×16; tests/bug-report.test.mjs.

Nuevo: public/js/lib/logbuf.js; public/js/lib/bug-report.js.

[1.97.1] — 2026-07-05

Endurecimiento guiado por revisión y paridad de documentación (seguimiento de v1.97.0). Un barrido de los registros de revisión de IA sacó a la luz correcciones reales:

  • fit-score.js (insignia de encaje ◎ del escáner). salaryFloor() ya no asciende una tarifa inferior a la anual a un suelo anual falso — «at least 500 EUR/day», «$80/hr», «6000 monthly» ahora devuelven null en lugar de un factor descalificador de 500k/80k. La coincidencia de países ahora es por palabra completa (\b…\b), de modo que «Germany» ya no coincide con el adjetivo «German» (ni «Nigeria» dentro de «Nigerian») ni dispara una falsa violación de imprescindible-en-otro-sitio. +3 pruebas en tests/fit-score.test.mjs.
  • Paridad de documentación. Cada README localizado anuncia ahora 16 idiomas de forma coherente — el recuento/la lista de la fila de Ayuda (×13) y la prosa de la sección de Localización más la nota «añade la clave a los N archivos» (×8) seguían con los recuentos previos a v1.85 (8/9). El recuento de adaptadores de la ayuda integrada §17 se corrige a 46 adaptadores — 41 en inglés + 5 en ruso en los 16 paquetes.

Sin cambios de comportamiento más allá de la heurística de la insignia de encaje; sin nuevas rutas, claves ni adiciones de i18n.

[1.97.0] — 2026-07-05

Fuente de escáner Dassault Systèmes + un barrido de calidad en tres frentes.

  • Nueva fuente de escaneo — Dassault Systèmes (paridad con el career-ops principal, #1498). server/lib/sources/dassault.mjs + server/lib/portals/adapters/dassault.mjs replican el proveedor de «búsqueda de tarjetas» Exalead de coste cero en tokens del proyecto principal (el feed público tras 3ds.com/careers/jobs). Es un único endpoint global, así que se selecciona por proveedor (provider: dassault) o se detecta automáticamente a partir de un host 3ds.com, con el host anclado contra SSRF a www.3ds.com mediante redirect:'error'. El XML se analiza sin DOM (mapas <Meta> por cada <Hit>), la ciudad/país se extraen de la cadena de categoría localizada, y las ofertas solo se conservan cuando su URL pública está en *.3ds.com. El registro ahora incluye 46 adaptadores (41 EN + 5 RU); el recuento de ALL_ADAPTERS, las aserciones de id ordenado y del conjunto EN de /api/scan/sources pasan de 40 → 41. Suite tests/sources-dassault.test.mjs (10 casos).
  • Correcciones de robustez portadas del proyecto principal. El analizador de Avature ahora tolera dos variantes de marcado de inquilinos en producción (article--result con un sufijo de índice de posición + un ancla de título de JobDetail sin clase, #1541); Get on Board protege contra un published_at 0/negativo (se acabaron las fechas espurias de 1970); SuccessFactors limita la última página para que no pueda sobrepasar MAX_JOBS (#1528).
  • Correcciones de auditoría del servidor. safe-fetch ya no se cuelga ante una respuesta que supera el límite — la ruta de límite de tamaño ahora resuelve la promesa directamente en lugar de esperar un evento 'end' que un stream destruido nunca emite (corrige las obtenciones de páginas grandes en /api/pipeline/preview + auto-pipeline). El registro de actividad SSE stream.* vuelve a ser alcanzable (la comprobación de /api/stream/ se movió por encima de la guarda general de «omitir GET»).
  • Correcciones de auditoría de la SPA. El conmutador de pestañas de #/stats se protege contra una carrera de renderizado asíncrono — el resultado de una pestaña lenta ya no puede pisar una pestaña más nueva a la que el usuario ya cambió. Las confirmaciones de eliminación de mock interview y de networking ahora pasan un título + cuerpo adecuados (se acabó el diálogo con cuerpo vacío).
  • Correcciones de traducción. Se corrigieron valores del diccionario sin traducir — ucraniano config.modes* (Adaptive Framing / Exit Narrative / Location Policy), ruso eval.jdLbl («Job Description»), italiano dash.quick.contactoSub («referral» → «segnalazione») — además de la localización del texto fijo inglés **16 locales** en los CHANGELOG de ru/uk/ja/ko/zh-CN/zh-TW.

Nuevo: server/lib/sources/dassault.mjs; server/lib/portals/adapters/dassault.mjs.

[1.96.0] — 2026-07-04

Orientación profesional (Epic 27). Una nueva página #/orientation responde a la pregunta «¿qué direcciones encajan realmente conmigo?» — la lectura que obtendrías de un test vocacional, pero inferida a partir de tu propio CV y perfil en lugar de un cuestionario. Haz clic en Generar perfil y el modelo devuelve tus vectores de carrera con mejor ajuste (cuáles de los ocho arquetipos — Funcionalista, Administrador, Comunicador, Especialista, Analista, Innovador, Gestor, Emprendedor — encajan, con evidencia), una inclinación de tipo profesional, roles recomendados, fortalezas profesionales ligadas a tu CV, tendencias de estilo de trabajo y recomendaciones de desarrollo. Es una reflexión de IA sobre cómo se lee tu CV — no un test psicométrico: nunca inventa logros ni informa puntuaciones numéricas como si fueran medidas. Expórtalo a Markdown o PDF; no se escribe nada en el disco.

  • Nueva ruta server/lib/routes/orientation.mjs (24.º módulo de rutas) — POST /api/orientation/generate construye el prompt del perfil a partir de CV+perfil+two-pager+memoria mediante la cascada de proveedores compartida, con un fallback manual de copiar y pegar y sin escritura de archivos.
  • Reutiliza report-export.js para Markdown/PDF/copia, dentro del grupo de navegación Desarrollo.
  • Tests: tests/orientation-routes.test.mjs (enfoque de reflexión / sin puntuaciones inventadas, modo manual sembrado con CV/perfil). 7 nuevas claves i18n ×16 idiomas, Ayuda §28 ×16.

Nuevo: #/orientation; server/lib/routes/orientation.mjs.

[1.95.0] — 2026-07-04

Plan de carrera (Epic 26). Una nueva página #/career-plan convierte tu CV y tu perfil en un plan de desarrollo concreto y personalizado. Elige un horizonte (6/12/24 meses) y un enfoque opcional, y el modelo — leyendo tu CV, tu perfil, tu two-pager y tu nota de memoria — redacta una instantánea del punto de partida, un DAFO de fortalezas/crecimiento, objetivos como SMART / OKR / WOOP, trayectorias alternativas, un plan de competencias técnicas y blandas, una hoja de ruta mes a mes, métodos de seguimiento del progreso, escollos y palancas de apoyo. Planifica hacia adelante a partir de lo que tus materiales realmente muestran y nunca inventa datos sobre tu historial. Edítalo en línea, Guárdalo en la capa de usuario (config/career-plan.md) y expórtalo a Markdown o PDF.

  • Nueva ruta server/lib/routes/career-plan.mjs (23.º módulo de rutas) — GET/PUT /api/career-plan (escribe config/career-plan.md) + POST /api/career-plan/generate (cascada compartida de proveedores, modo manual como respaldo, sin fabricación). PATHS.careerPlan.
  • Reutiliza el helper compartido report-export.js (v1.94.0) para Markdown/PDF/copia, y un nuevo grupo de navegación Crecimiento.
  • Pruebas: tests/career-plan-routes.test.mjs (acotación, ida y vuelta GET/PUT, prompt sembrado con CV/perfil según el horizonte). 20 nuevas claves i18n en los 16 locales, Ayuda §27 en los 16.

Nuevo: #/career-plan; server/lib/routes/career-plan.mjs; PATHS.careerPlan.

[1.94.0] — 2026-07-04

Estadísticas, rediseñadas (Epic 25). La página #/stats es ahora una sección de Estadísticas con tres pestañas, con gráficas reales y muchos más datos. Una nueva pestaña de Informe de mercado le pide al modelo un análisis salarial y del mercado laboral de tus roles objetivo en la región y la moneda que elijas — resumen ejecutivo, salario por nivel con percentiles P10/P25/P75/P90, principales empleadores, una tabla de competencias demandadas, frecuencia de beneficios, el reparto presencial/híbrido/remoto, tendencias a 12–24 meses y orientación de negociación. Cada cifra se etiqueta como una estimación orientativa a partir del conocimiento del modelo, nunca presentada como datos extraídos. Una nueva pestaña de Mi pipeline grafica tu propio tracker: distribución de puntuaciones, embudo de estados, principales empresas y roles, postulaciones a lo largo del tiempo y tasas de conversión. La vista original de rol objetivo (vacantes/salario por país + tendencia de instantáneas guardadas) pasa a una tercera pestaña, ahora con un selector de moneda y un resumen de publicaciones por rol.

  • Exporta cualquier informe a Markdown o PDF, o cópialo — mediante el helper compartido report-export.js (descarga de blob Markdown; PDF a través del runner de PDF en línea existente).
  • Nueva ruta server/lib/routes/market.mjs (22.º módulo de rutas) — POST /api/stats/market construye un prompt de análisis de mercado a partir de tu CV/perfil (de modo que conoce tus roles objetivo), la región y la moneda, lo pasa por la cascada compartida de proveedores y recurre a un prompt para copiar y pegar cuando no hay clave. Sin escrituras en disco.
  • Pruebas: tests/market-routes.test.mjs (acotación de región/moneda, prompt con etiquetado de honestidad, modo manual sembrado con CV/perfil). 36 nuevas claves i18n en los 16 locales, Ayuda §26 en los 16.

Nuevo: #/stats rediseñada en pestañas; server/lib/routes/market.mjs; public/js/lib/report-export.js.

[1.93.0] — 2026-07-04

Capa de memoria (Epic 24). Una nueva página #/memory guarda una nota breve y editable de «recuerda esto sobre mí» que el asistente tiene en cuenta en cada tarea:

  • Una nota, en todas partes — al incrustarse en bundleProjectContext, la nota llega automáticamente a cada petición de IA (evaluar, mock interview, networking, CV Studio) en todos los proveedores. Escríbela una vez; dirige todo.
  • Orientación, no hechos — captura tus preferencias y cómo te gusta trabajar (tono, formato, deal-breakers, cadencia), nunca nuevas afirmaciones factuales sobre tu experiencia — esas siguen viviendo solo en tu CV, tu perfil y tu two-pager. Se guarda en la capa de usuario en config/memory.md, nunca sobrescrita por las actualizaciones.
  • Sugerir a partir de tus datos — POST /api/memory/suggest explora tu propio tracker de postulaciones en busca de patrones de comportamiento y redacta viñetas para que las revises y edites. Lee tu tracker; nunca inventa hechos, y no hace ninguna llamada en vivo.

Nuevo: server/lib/routes/memory.mjs (21.º módulo de rutas — GET/PUT /api/memory + POST /api/memory/suggest), public/js/views/memory.js, PATHS.memory, y un bloque config/memory.md añadido a bundleProjectContext. 11 nuevas claves i18n en los 16 locales. Pruebas: tests/memory-routes.test.mjs.

[1.92.0] — 2026-07-04

CV Studio (Epic 21). Una nueva página #/cv-studio dota a tu CV de tres herramientas honestas y mayormente locales:

  • Diagnóstico de currículum — una puntuación determinista de 0–100 con explicaciones por comprobación (impacto cuantificado, verbos débiles, palabras de moda, longitud, secciones esenciales, información de contacto). Puramente del lado del cliente (window.CvDiagnostics) — sin LLM, nada inventado, cada hallazgo explicado para que tú decidas qué cambiar.
  • Máscara de privacidad — redacta datos personales (email, teléfono, enlaces/handles, dirección postal y, opcionalmente, tu nombre → iniciales) antes de compartir tu CV como muestra o captura. Se ejecuta enteramente en el navegador (window.CvPrivacy); informa exactamente qué redactó y nunca almacena el original.
  • Hazlo humano / coincidir con tu voz — pega una línea o un párrafo rígido y reescríbelo con tu voz, anclado en el servidor en voice-dna.md y writing-samples/. Barandilla estricta: puede reordenar, condensar y re-vocalizar, pero nunca introduce un hecho, una métrica ni un logro que no esté ya en el texto. Se ejecuta en vivo a través de la cascada compartida de proveedores, o te devuelve un prompt para copiar y pegar sin clave.

Nuevo: server/lib/routes/cv-studio.mjs (20.º módulo de rutas — POST /api/cv-studio/humanize), public/js/views/cv-studio.js, public/js/lib/cv-diagnostics.js, public/js/lib/cv-privacy.js, PATHS.voiceDna + PATHS.writingSamplesDir. 29 nuevas claves i18n en los 16 locales. Pruebas: tests/cv-diagnostics.test.mjs, tests/cv-studio-routes.test.mjs. (La galería de plantillas, la exportación a Word y el archivo de PDF de la oferta quedan como trabajo de seguimiento de CV Studio.)

[1.91.0] — 2026-07-04

Networking e investigación profunda de empresas (Epic 16). Una nueva página #/networking convierte una empresa en un plan accionable para conseguir una entrevista, anclado en tu CV, perfil y two-pager:

  • Dossier de empresa — un resumen conciso de qué hace la empresa, señales recientes dignas de citar y ganchos de “por qué encajo” extraídos de tu trayectoria real.
  • A quién contactar — 3–5 personas objetivo (hiring manager, reclutador interno, un IC sénior del equipo, un contacto cálido/de la misma alma máter) con una cadena de búsqueda de LinkedIn concreta para encontrar a cada uno. Nunca inventa nombres reales.
  • La vía de presentación más cálida — la ruta cálida más realista para tu trayectoria (empleador/escuela/comunidad en común, un camino de segundo grado o un mensaje directo en frío de alta señal) y por qué.
  • Borradores de contacto — mensajes cortos y específicos para las personas principales, anclados en tus puntos de prueba reales.
  • En vivo o manual — se ejecuta en vivo a través de la cascada compartida de proveedores con cualquier clave, o te devuelve un prompt listo para copiar y pegar (fallback honesto, nada inventado). Guardar plan persiste un plan terminado en la capa de usuario (networking/net-{company}-{role}-{date}.md); la página lista, abre y elimina los planes guardados.

Nuevo: server/lib/routes/networking.mjs (19.º módulo de rutas), public/js/views/networking.js, PATHS.networkingDir. Reutiliza la cascada server/lib/llm-dispatch.mjs de v1.90.0. 24 nuevas claves i18n en los 16 locales. Pruebas: tests/networking-routes.test.mjs.

[1.90.0] — 2026-07-04

Mock Interview 2.0 (Epic 15). Una nueva página #/mock-interview convierte tu CV, perfil, two-pager y banco de historias en un ensayo de entrevista turno por turno:

  • Práctica conversacional — fija un puesto objetivo (+ empresa / JD opcionales) y el entrevistador abre con una pregunta enfocada. Cada respuesta que envías recibe una réplica estructurada: Feedback (fortalezas + la brecha STAR+R), una Puntuación (N/5) y una Siguiente pregunta que indaga en la parte más débil de tu última respuesta. Anclada en el servidor a tus materiales reales — nunca inventa experiencia que no tienes.
  • Consciente del banco de historias — interview-prep/story-bank.md se incrusta en el prompt (mismo nivel de confianza que cv.md), de modo que el feedback puede señalarte tus propias mejores historias.
  • En vivo o manual — con una clave de proveedor, el turno se ejecuta en vivo a través de la cascada compartida de proveedores (Anthropic → Gemini → OpenAI → Qwen → OpenRouter → GitHub Models); sin clave obtienes un prompt listo para copiar y pegar (fallback honesto, sin respuestas inventadas).
  • Sesiones guardadas — pulsa Save transcript para persistir una entrevista terminada en la capa de usuario (interview-prep/mock-{company}-{role}-{date}.md); la página lista, abre y elimina las sesiones guardadas.

Nuevo: server/lib/routes/interview.mjs (18.º módulo de rutas), public/js/views/mock-interview.js, server/lib/llm-dispatch.mjs (cascada compartida de proveedores), PATHS.storyBank, bundleProjectContext({ extraFiles }). 30 nuevas claves i18n en los 16 locales. Pruebas: tests/interview-routes.test.mjs.

[1.89.0] — 2026-07-04

Ajuste candidato-mercado — el two-pager (Epic 14). Una nueva página #/two-pager te permite capturar lo que tú realmente quieres de tu próximo puesto, inspirada en el «Mnookin two-pager» de Never Search Alone:

  • Constructor guiado — una narrativa en primera persona «Quién soy», una nota «Entorno objetivo» y cinco editores de listas de chips: loves, must-haves, hates, deal-breakers y non-negotiables. Se guarda en la capa de usuario del proyecto padre (config/two-pager.yml) vía PUT /api/two-pager — nunca sobrescrita por actualizaciones del sistema.
  • Asistente de relleno con IA (POST /api/two-pager/draft) — construye un prompt Mnookin listo para ejecutar con tu CV + perfil incrustados, para que lo corras en cualquier LLM y pegues el resultado de vuelta. Solo usa tus propios materiales; nada se inventa.
  • Insignia de encaje-con-lo-que-quieres — cada oferta en #/scan muestra ahora una puntuación de encaje ◎ N (del lado del cliente, vía window.FitScore) que contrasta el tipo de trabajo, país, salario mínimo y reubicación de la oferta con tu two-pager. Honesta por diseño: cuando una oferta no aporta señal comparable, no se muestra insignia (nunca un número inventado). Las violaciones de deal-breakers pesan más que los disgustos leves.
  • Alimenta cada evaluación — el two-pager guardado se incrusta en bundleProjectContext, de modo que todas las evaluaciones LLM posteriores combinan tus preferencias declaradas con el encaje CV-vs-oferta.

Nuevo: server/lib/routes/two-pager.mjs, public/js/views/two-pager.js, public/js/lib/fit-score.js, PATHS.twoPager. 27 nuevas claves i18n en los 16 locales. Pruebas: tests/two-pager-routes.test.mjs, tests/fit-score.test.mjs.

[1.88.0] — 2026-07-04

Pulido del issue #29 — huecos de i18n en Escaneo + higiene de la API.

  • Localizadas las últimas cadenas de Escaneo codificadas (hoja de ruta v1.69.4): las píldoras de resumen por fuente (N nuevas / M coincidentes), los avisos N nuevas ofertas y la insignia reloc ahora pasan por t() — 4 nuevas claves (scan.pillNew, scan.pillMatching, scan.newOffers, scan.relocBadge) en los 16 locales. Los usuarios que no usan inglés ya no ven texto suelto en inglés en el flujo principal de escaneo.
  • Deshabilitada la cabecera X-Powered-By (hoja de ruta v1.69.5): app.disable('x-powered-by') en createApp() — el servidor ya no anuncia Express. (El resto de ese conjunto ya se había entregado: parentVersion elimina su comentario de release-please, el interruptor de tema en modo claro, el cierre de modales al cambiar de ruta y la localización de «Score» (rep.score) en Informes.)

Pruebas: tests/scan-i18n-gaps.test.mjs + una aserción de ausencia de X-Powered-By en tests/security-headers.test.mjs.

[1.87.0] — 2026-07-04

4 nuevos proveedores de escaneo sin autenticación (paridad con career-ops padre v1.16.0). El registro del escáner crece de 41 → 45 adaptadores (40 EN + 5 RU) — todos públicos, sin autenticación, con host fijado, redirect:'error' (a salvo de SSRF), cada uno con una prueba aislada para CI:

  • Get on Board (getonbrd) — JSON:API público de todo el portal (tecnología LATAM/remoto), seleccionado por proveedor, paginado. server/lib/sources/getonbrd.mjs.
  • Amazon (amazon) — JSON de búsqueda público de amazon.jobs, detectado por host o provider: amazon, paginado por desplazamiento. server/lib/sources/amazon.mjs.
  • Avature (avature) — ATS *.avature.net por inquilino, analizado desde HTML, detectado por host o provider: avature. server/lib/sources/avature.mjs.
  • SAP SuccessFactors (successfactors) — lista de mosaicos RMK por inquilino (*.successfactors.eu/.com, jobs2web.com), analizada desde HTML. server/lib/sources/successfactors.mjs.

Cada uno incluye un sources/<slug>.mjs (con meta autodescubierto → menú desplegable #/scan) y un portals/adapters/<slug>.mjs en ALL_ADAPTERS (la regla de los dos registros) + tests/sources-<slug>.test.mjs. El recuento de ALL_ADAPTERS y las aserciones de id ordenado y del conjunto EN de /api/scan/sources subieron de 36→40; GET /api/scan/sources ahora lista 45.

[1.86.0] — 2026-07-03

Estadísticas por roles objetivo (#/stats) — estadísticas de vacantes y salarios del mercado para TUS roles objetivo. Una nueva página de Analítica lee tus roles objetivo del perfil (config/profile.yml → no codificados) y las ofertas del último escaneo, y luego muestra, por rol y país:

  • Vacantes por país y salario mediano por país (USD) — agregados en el cliente (public/js/lib/role-stats.js, reutilizando window.Countries) a partir de los datos dispersos que los escáneres ya recopilan. Los salarios en cualquier moneda se normalizan a USD mediante una tabla FX explícitamente aproximada, con una advertencia sobre el tamaño de la muestra — nunca se inventan.
  • Filtros de rol y país y gráficos de barras y tendencia con SVG en línea hechos a mano (sin nuevas dependencias, seguros para CSP — solo addEventListener).
  • Guardar instantánea (POST /api/stats/snapshot) persiste el agregado actual en data/role-stats.jsonl; el gráfico de tendencia (GET /api/stats/trend) sigue el recuento de vacantes a lo largo del tiempo — la vista de «dinámica». Híbrido honesto: las instantáneas provienen de datos de escaneo locales, actualizados bajo demanda.
  • Totalmente localizado en los 16 locales (26 nuevas claves i18n).

Nuevo: server/lib/routes/stats.mjs (16.º módulo de rutas), public/js/lib/role-stats.js, public/js/views/stats.js, PATHS.roleStats; pruebas role-stats.test.mjs (7) + stats-routes.test.mjs (5).

[1.85.0] — 2026-07-03

Locales alemán, italiano y turco (paridad de locales con career-ops padre v1.16.0). La interfaz ahora se distribuye en 16 idiomas — de 🇩🇪, it 🇮🇹 y tr 🇹🇷 se suman a los 13 existentes.

  • Traducción completa de la interfaz — las 730 claves i18n traducidas en public/js/lib/locales/i18n-dict.{de,it,tr}.js; el selector de idioma lista Deutsch / Italiano / Türkçe y la detección automática del idioma del navegador reconoce de/it/tr (public/js/lib/i18n.js).
  • Guía de ayuda integrada — docs/help/{de,it,tr}.md traducidos (estructura completa de 19 H2 / 75 H3), servidos por GET /api/help/:lang.
  • Documentación — se añaden README.{de,it,tr}.md y CHANGELOG.{de,it,tr}.md; la comprobación de paridad de locales del CHANGELOG ahora cubre 15 locales no EN.
  • Andamiaje de prompts — server/lib/prompts.mjs (LOCALE_NAMES + SCAFFOLD_STRINGS) localizado para los tres nuevos locales, de modo que la salida del LLM sigue el idioma de la interfaz.

Todas las comprobaciones de paridad (i18n-locale-files, i18n-coverage, check-changelog-parity, lang-switcher-rtl) extendidas al conjunto de 16 locales.

[1.84.0] — 2026-06-30

Cooldown de repostulación + compensación en pipeline.md (paridad con career-ops padre v1.15.0). Dos mejoras del escáner:

  • Cooldown de repostulación (#1201): el escaneo EN ahora omite roles en empresas a las que postulaste recientemente, para que los resultados se centren en NUEVAS ofertas. Configura ventanas por empresa en config/profile.yml bajo re_apply_windows: (last_apply_date, same_role_days, applied_to: [roles], cross_role_bucket opcional); la coincidencia de empresa es insensible a puntuación y basada en límites de palabra (server/lib/cooldown.mjs). Desactivado cuando la clave está ausente; el registro de escaneo muestra Cooldown skipped: N.
  • Compensación en pipeline.md (#1017): las ofertas escaneadas ahora persisten su salario como columna opcional al final (url | <salary>) en data/pipeline.md. La URL sigue siendo la clave de deduplicación (la columna | comp se elimina en la lectura), la celda se sanea (sin inyección de filas/columnas, fórmulas iniciales neutralizadas) y los pipelines con solo URL son retrocompatibles.

Incluye tests/cooldown.test.mjs + tests de compensación de pipeline. El número de fuentes se mantiene en 41 (ambas son mejoras de lógica de escaneo, no nuevos boards).

[1.83.0] — 2026-06-30

Detector de reposteos / empleos fantasma (paridad con career-ops padre v1.15.0). Un nuevo panel 🔁 Reposteos / empleos fantasma en #/scan señala los clústeres empresa+rol que fueron publicados de nuevo bajo distintas URLs en una ventana móvil de 90 días — señal de pipelines obsoletas y ofertas fantasma. Respaldado por un comparador difuso de títulos de rol (server/lib/role-matcher.mjs) y un detector de solo lectura (server/lib/detect-reposts.mjs) sobre data/scan-history.tsv, expuesto vía GET /api/scan/reposts. Además: parentVersion en /api/health ahora reporta solo el semver (se elimina el comentario # x-release-please-version de release-please). Incluye tests/detect-reposts.test.mjs. El número de fuentes se mantiene en 41 — los reposteos son una función de análisis, no un nuevo board.

[1.82.0] — 2026-06-30

Fuente de escaneo NoDesk (paridad con career-ops v1.15.0). El feed RSS de empleo remoto de NoDesk ahora es una fuente de primera clase — añade una entrada provider: nodesk y aparece en el desplegable Source de #/scan (41 adaptadores en total: 36 EN + 5 RU). Host fijado a nodesk.co con redirect:'error' (anti-SSRF); los títulos se dividen por Role at Company (NoDesk no tiene etiqueta de ubicación, por lo que la ubicación queda vacía); todas las filas son remotas. Incluye una suite CI aislada tests/sources-nodesk.test.mjs; suite de tests unitarios completa en verde con 1523.

[1.81.0] — 2026-06-29

Paridad con el career-ops padre — 13 nuevas fuentes de escaneo de bolsas de empleo. Incorpora el último lote de proveedores desde el main de Fighter90/career-ops al escáner en proceso. APIs públicas universales (seleccionadas por proveedor): Arbeitnow, Himalayas, Jobicy, Landing.jobs, 4 Day Week, The Muse, The Hub, Jobspresso (RSS) y Hacker News “Who is hiring?” (Algolia en dos pasos). Bolsas polacas (detectadas por host o provider:): JustJoin.it y NoFluffJobs (búsqueda POST). ATS por tenant (autodetectados desde careers_url): Pinpoint (<slug>.pinpointhq.com/postings.json) y Rippling (ats.rippling.com/<slug> → api.rippling.com). Cada fuente está fijada por host con redirect:'error' (anti-SSRF) y es seleccionable en el desplegable Source de #/scan — el registro incluye ahora 40 adaptadores de escáner (35 EN + 5 RU). Añade 13 conjuntos de tests CI aislados por fuente; suite de tests unitarios completa en verde con 1513 tests.

[1.80.0] — 2026-06-28

Cinco mejoras de escaneo (ideas de job-crawler, reimplementadas). (1) Fuente Teamtailor — sitios <slug>.teamtailor.com vía su feed público /jobs.rss, autodetectado desde careers_url (host fijado + redirect:'error'); el registro trae ahora 27 adaptadores. (2) Cuarentena de fuentes — una fuente con 404/410 permanente se guarda en data/scan-quarantine.json y se omite en escaneos posteriores (autorreparable: se reintenta a los 14 días). (3) Máx. por fuente — campo opcional en #/scan que limita los empleos por board (∞ por defecto). (4) Publicado en — filtro de antigüedad del lado del cliente (24h / 7d / 30d). (5) Búsquedas guardadas + ★ favoritos — nombra y reutiliza conjuntos de filtros y marca empleos, en localStorage con validación defensiva (una caché corrupta se reinicia limpia); la caché de resultados se reinicia antes de cada escaneo y se rellena en vivo.

[1.79.0] — 2026-06-28

Fuente de escaneo WeWorkRemotely (paridad con career-ops v1.14.0). El feed RSS de empleo remoto de We Work Remotely ahora es una fuente de primera clase — añade una entrada provider: weworkremotely y aparece en el desplegable Source de #/scan (26 adaptadores en total). Host fijado a weworkremotely.com con redirect:'error' (anti-SSRF); los títulos se dividen por Company: Role. Además: las palabras clave de title_filter ahora se recortan antes de la comprobación de longitud (parent #1261).

[1.78.2] — 2026-06-27

Refuerzo de i18n y UX (correcciones tras v1.78.1). El nombre accesible del logo ahora está localizado en los 13 idiomas (nav.logoHome). Enter en la búsqueda global estando ya en #/scan fuerza un re-render para no perder el término precargado (guard de misma ruta). health.title ahora está traducido al polaco (Kondycja) y al danés (Systemtilstand) — antes quedaba en inglés. Tests 1235 → 1238.

[1.78.1] — 2026-06-27

Correcciones de UX en Scan. La tabla de resultados de #/scan ahora se actualiza automáticamente durante el escaneo y una vez más al terminar, sin recargar. La búsqueda global de la barra superior muestra una pista Enter y, con una consulta que no es URL, salta a #/scan con el campo pre-rellenado (antes #/tracker). El logo ahora enlaza al panel (inicio).

[1.78.0] — 2026-06-27

Filtro geográfico en la página de Scan — filtra resultados por país, con banderas. Un nuevo desplegable País en #/scan lista cada país detectado en tus resultados (emoji de bandera + recuento), para quedarte solo con roles ligados a un país — junto al filtro Remote/Hybrid/Onsite, de modo que puedes buscar trabajo ligado a un país y remoto. Respaldado por un nuevo helper countries.js que mapea la ubicación de texto libre (nombres de país, alias y ~100 ciudades clave) a un país ISO + bandera; la detección es conservadora y nunca adivina.

[1.77.0] — 2026-06-27

Danés (Dansk) añadido como el 13.º idioma de la interfaz. Traducción completa de la UI, la guía de ayuda integrada (19 H2 / 75 H3), README y CHANGELOG. El danés se suma al selector de idiomas con banderas; la maquinaria i18n (ensamblador, auditoría, comprobaciones de paridad, snapshot) abarca ahora 13 locales.


career-ops-ui v1.267.1 · page 5/9 · mirror of the project changelog for cvstart.org