claude-code-arcane 1.4.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/README.md +5 -4
  3. package/dist/cli.js +2 -0
  4. package/docs/SKILLS-CATALOG.md +30 -3
  5. package/docs/USER-GUIDE.md +2 -2
  6. package/docs/coding-standards.md +26 -0
  7. package/package.json +2 -1
  8. package/profiles/job-hunt.yaml +12 -2
  9. package/profiles/second-brain.yaml +37 -0
  10. package/rules/drafter-reviewer.md +39 -0
  11. package/rules/vault-conventions.md +144 -0
  12. package/skills/brain-dump/SKILL.md +53 -0
  13. package/skills/cold-outreach/SKILL.md +10 -10
  14. package/skills/cold-outreach/references/recruiter-playbook.md +5 -5
  15. package/skills/cover-letter/SKILL.md +8 -8
  16. package/skills/cv-ats-export/SKILL.md +19 -6
  17. package/skills/cv-ats-export/scripts/cv_export.py +49 -4
  18. package/skills/cv-ats-export/scripts/verify_pdf.py +90 -0
  19. package/skills/cv-tailor/SKILL.md +7 -7
  20. package/skills/cv-tailor/references/ats-keywords.md +9 -9
  21. package/skills/hub-note/SKILL.md +68 -0
  22. package/skills/interview-prep/SKILL.md +7 -7
  23. package/skills/interview-prep/references/star-framework.md +10 -10
  24. package/skills/job-aplicar/SKILL.md +88 -0
  25. package/skills/job-hunt/SKILL.md +20 -13
  26. package/skills/job-hunt/references/templates/Aplicacion.md +6 -2
  27. package/skills/job-hunt/references/templates/CV Custom.md +1 -1
  28. package/skills/job-hunt/references/templates/Contacto.md +1 -1
  29. package/skills/job-hunt/references/templates/Dashboard.md +19 -15
  30. package/skills/job-hunt/references/templates/Perfil.md +2 -2
  31. package/skills/job-outcome/SKILL.md +55 -0
  32. package/skills/job-scrape/SKILL.md +95 -0
  33. package/skills/job-scrape/references/dedup-playbook.md +25 -0
  34. package/skills/job-scrape/references/getonbrd-cli.md +62 -0
  35. package/skills/job-scrape/references/getonbrd-urls.md +37 -0
  36. package/skills/job-scrape/references/linkedin-cli.md +64 -0
  37. package/skills/job-scrape/references/linkedin-urls.md +42 -0
  38. package/skills/job-scrape/references/seen_jobs.schema.json +68 -0
  39. package/skills/job-scrape/scripts/getonbrd_search.py +501 -0
  40. package/skills/job-scrape/scripts/linkedin_search.py +428 -0
  41. package/skills/job-search/SKILL.md +22 -19
  42. package/skills/job-search/references/scoring-rubric.md +8 -8
  43. package/skills/job-upskill/SKILL.md +58 -0
  44. package/skills/linkedin-optimize/SKILL.md +5 -5
  45. package/skills/master-profile/SKILL.md +17 -17
  46. package/skills/network-map/SKILL.md +7 -7
  47. package/skills/obsidian-bases/SKILL.md +85 -0
  48. package/skills/obsidian-bases/references/bases-syntax.md +123 -0
  49. package/skills/obsidian-canvas/SKILL.md +91 -0
  50. package/skills/obsidian-markdown/SKILL.md +110 -0
  51. package/skills/pdf-generator/scripts/fill_inspect.py +2 -0
  52. package/skills/pdf-generator/scripts/render_body.py +2 -0
  53. package/skills/personal-brand/SKILL.md +10 -10
  54. package/skills/personal-brand/references/post-pillars.md +14 -14
  55. package/skills/portfolio-site/SKILL.md +11 -11
  56. package/skills/review-dump/SKILL.md +108 -0
  57. package/skills/review-monthly/SKILL.md +56 -0
  58. package/skills/review-weekly/SKILL.md +91 -0
  59. package/skills/second-brain/SKILL.md +211 -0
  60. package/skills/second-brain/hooks/hooks.json +16 -0
  61. package/skills/second-brain/hooks/validate-note.sh +85 -0
  62. package/skills/second-brain/references/cross-project.md +57 -0
  63. package/skills/second-brain/references/templates/Atomic.md +22 -0
  64. package/skills/second-brain/references/templates/Daily.md +25 -0
  65. package/skills/second-brain/references/templates/Dashboard.base +70 -0
  66. package/skills/second-brain/references/templates/Dump.md +11 -0
  67. package/skills/second-brain/references/templates/Hub.md +22 -0
  68. package/skills/second-brain/references/templates/Monthly.md +29 -0
  69. package/skills/second-brain/references/templates/Weekly.md +29 -0
  70. package/skills/second-brain/references/templates/codebases.md +37 -0
  71. package/skills/second-brain/references/templates/hot.md +37 -0
  72. package/skills/second-brain/references/templates/vault-CLAUDE.md +127 -0
  73. package/skills/vault-audit/SKILL.md +72 -0
  74. package/skills/vault-audit/scripts/vault_audit.py +539 -0
  75. package/skills/vault-clip/SKILL.md +73 -0
  76. package/skills/vault-recall/SKILL.md +82 -0
  77. package/skills/vault-recall/scripts/vault_index.py +626 -0
  78. package/skills/vault-tidy/SKILL.md +76 -0
  79. package/skills/xlsx-generator/scripts/formula_check.py +2 -0
  80. package/skills/xlsx-generator/scripts/libreoffice_recalc.py +2 -0
  81. package/skills/xlsx-generator/scripts/xlsx_add_column.py +2 -0
  82. package/skills/xlsx-generator/scripts/xlsx_insert_row.py +2 -0
  83. package/skills/xlsx-generator/scripts/xlsx_reader.py +2 -0
  84. package/skills/zettel/SKILL.md +66 -0
@@ -0,0 +1,88 @@
1
+ ---
2
+ name: job-aplicar
3
+ description: "Orchestrate a full job application from a URL or note: fit score, application note, tailored CV, cover letter, fresh-context review and ATS-verified PDF. Sending is always manual. Triggers: aplicar a esta oferta, postularme a, armar la postulacion, pipeline de aplicacion, preparar CV y cover para una oferta, aplicar a este trabajo."
4
+ argument-hint: "<url | nota de aplicación>"
5
+ category: "career"
6
+ user-invocable: true
7
+ allowed-tools: Read, Glob, Grep, Bash, Write, Edit, WebFetch, WebSearch, Task
8
+ ---
9
+
10
+ # /job-aplicar - Pipeline completo de una postulación
11
+
12
+ Orquesta de punta a punta: evaluar fit → nota de aplicación → CV custom → cover → reviewer → PDF ATS → dashboard. Adaptado del `/apply` de MadsLorentzen/ai-job-search a los skills y reglas de este skillset. **El envío final es siempre manual del usuario; este skill nunca marca `estado: aplicado`** (eso lo registra `/job-outcome` después de enviar).
13
+
14
+ Argumento: `$ARGUMENTS` (URL de la oferta, o nombre/wikilink de una nota de `03-Aplicaciones/`).
15
+
16
+ Rutas relativas al career workspace (`--workspace`, env `CAREER_WORKSPACE`, o `./career-workspace/`). Usa los skills `cv-tailor`, `cover-letter` y `cv-ats-export`, la regla `drafter-reviewer` y, si está instalado, los CLIs y el estado de dedup de `job-scrape`.
17
+
18
+ ## Paso 0 - Resolver argumento y detectar re-entrada
19
+
20
+ - Si es nombre/wikilink: cargar la nota de `03-Aplicaciones/`.
21
+ - Si es URL: canonicalizarla (reglas en `.claude/skills/job-scrape/references/dedup-playbook.md`) y buscar nota existente por `link_oferta` (Grep). Si existe, modo re-entrada.
22
+ - **Detectar desde los artefactos qué pasos ya están hechos** (no hay archivo de estado; los artefactos SON el estado):
23
+ 1. ¿Existe la nota? 2. ¿Tiene `match_score`? 3. ¿`cv_usado` apunta a un CV .md existente en `02-CVs/`? 4. ¿La nota tiene sección "Cover letter" con contenido? 5. ¿Hay resumen de decisiones de review en `## Notas`? 6. ¿Existe el PDF en `02-CVs/exports/` (o `archivo_pdf`)? 7. ¿Hay fila en el dashboard?
24
+ - Informar "retomo desde el paso X" y saltar lo ya hecho.
25
+
26
+ ## Paso 1 - Obtener el JD
27
+
28
+ - URL de GetOnBoard → `python .claude/skills/job-scrape/scripts/getonbrd_search.py detail "<url>" --format plain` (si reporta CERRADA, avisar y preguntar si igual seguir).
29
+ - URL de LinkedIn → `python .claude/skills/job-scrape/scripts/linkedin_search.py detail "<url>" --format plain`.
30
+ - Otra fuente, o si `job-scrape` no está instalado → WebFetch.
31
+ - Registrar `fuente` (getonbrd / linkedin / otro).
32
+
33
+ ## Paso 2 - Score
34
+
35
+ Aplicar la rúbrica de `.claude/skills/job-search/references/scoring-rubric.md` (si el skill job-search no está instalado, pedir instalarlo o scorear a criterio declarando la limitación) contra el perfil correspondiente de `01-Perfiles/` (elegir por stack del JD; preguntar si es ambiguo). Presentar: score 0-100, breakdown por dimensión, gaps reales, red flags.
36
+
37
+ ## Paso 3 - CONFIRMACIÓN 1: ¿proceder?
38
+
39
+ Pedir approval explícito antes de crear cualquier archivo:
40
+ - **Proceder** → paso 4.
41
+ - **Descartar** → si existe `<workspace>/tools/job_scraper/seen_jobs.json`, registrar `status: descartado` + motivo; terminar. No crear nada.
42
+
43
+ ## Paso 4 - Nota de aplicación
44
+
45
+ Crear (o completar) la nota desde el template `Templates/Aplicacion.md` del workspace: frontmatter completo (`estado: interesado`, `match_score`, `link_oferta` canónica, `perfil`, `fuente`, `salario_rango`, `modalidad`, `ubicacion`, `prioridad`, `contacto_principal` si se conoce), secciones "Por qué me interesa" y "Match con mi perfil" (alineaciones + gaps reales), línea en Timeline. En el idioma de las notas del workspace, sin guiones largos.
46
+
47
+ ## Paso 5 - CV custom
48
+
49
+ Usar el skill **cv-tailor** con el JD y el perfil base: genera `02-CVs/CV - <Empresa> - <Rol>.md`, con keywords ATS de la oferta y delta de match reportado. Setear `cv_usado` en la nota de aplicación.
50
+
51
+ ## Paso 6 - Cover letter
52
+
53
+ Usar el skill **cover-letter** según el canal de aplicación (formulario ATS → carta 250-350 palabras; LinkedIn/email → mensaje 150-250). Guardar en la sección "Cover letter (v1)" de la nota.
54
+
55
+ ## Paso 7 - Reviewer (regla drafter-reviewer, obligatorio)
56
+
57
+ Seguir `.claude/rules/drafter-reviewer.md`: lanzar UN agente general-purpose de contexto fresco con el JD + borradores inline; lectura acotada al perfil; research web breve de la empresa. Aplicar la Parte A (reemplazos exactos) e incorporar lo relevante de la Parte B. Una sola ronda. Never stuff keywords: los gaps se reconocen en el cover, no se rellenan en el CV. Normalizar guiones largos del texto sugerido.
58
+
59
+ ## Paso 8 - CONFIRMACIÓN 2: aprobar CV final
60
+
61
+ Mostrar el CV final (y cover) con las decisiones del review aplicadas. Solo con approval explícito, exportar:
62
+
63
+ ```
64
+ python .claude/skills/cv-ats-export/scripts/cv_export.py "CV - <Empresa> - <Rol>"
65
+ ```
66
+
67
+ (o la copia instalada en `<workspace>/tools/`). La verificación ATS corre automática ([ATS OK] esperado; si [ATS WARN], corregir y re-exportar). Registrar la ruta del PDF en la nota (sección "Archivo final exportado" del CV custom).
68
+
69
+ ## Paso 9 - Dashboard
70
+
71
+ Agregar/actualizar la fila de la oferta en la sección de aplicaciones activas de `00-Dashboard.md`, respetando las columnas exactas del header, escapando `\|` en wikilinks con alias. Estado ⬜ (interesado).
72
+
73
+ ## Paso 10 - Cierre
74
+
75
+ - Resumen de 3-5 decisiones de tailoring en `## Notas` de la nota (qué se enfatizó, ángulo de empresa, gaps reconocidos).
76
+ - Si existe seen_jobs: entrada → `nota_creada` (crearla si no existía).
77
+ - Recordatorio al usuario: **enviar es manual**; el PDF y el cover están listos, el link de aplicación está en la nota y el dashboard. Al enviar, correr `/job-outcome <empresa>` para marcar `aplicado`.
78
+
79
+ ## Qué NO hace
80
+
81
+ - No envía la postulación ni marca `aplicado` (ToS de los portales + control de calidad humano).
82
+ - No exporta PDF sin la confirmación 2.
83
+ - No toca otras notas ni otras filas del dashboard.
84
+ - No inventa experiencia: los gaps se reconocen, no se rellenan.
85
+
86
+ ## Handoff
87
+
88
+ Con el PDF verificado y el cover aprobados, el paquete queda READY para enviar a mano. Después de enviar: `/job-outcome <empresa>`. Si hay un contacto directo en la empresa, `/cold-outreach` para el mensaje al recruiter/hiring manager.
@@ -7,9 +7,9 @@ user-invocable: true
7
7
  allowed-tools: Read, Glob, Grep, Bash, Write, Edit
8
8
  ---
9
9
 
10
- # Job Hunt — Career Workspace Orchestrator
10
+ # Job Hunt - Career Workspace Orchestrator
11
11
 
12
- Eres el punto de entrada de la suite de búsqueda laboral. Tu trabajo es: detectar (o crear) el **career workspace**, entender en qué etapa está el usuario, y rutear al skill correcto. No hacés todo vos — orquestás.
12
+ Eres el punto de entrada de la suite de búsqueda laboral. Tu trabajo es: detectar (o crear) el **career workspace**, entender en qué etapa está el usuario, y rutear al skill correcto. No hacés todo vos - orquestás.
13
13
 
14
14
  ## Idioma
15
15
 
@@ -32,26 +32,29 @@ career-workspace/
32
32
  07-Recursos/ cover letters reutilizables, links
33
33
  portfolio/ datos source-of-truth del portfolio web
34
34
  Templates/ plantillas con frontmatter
35
- tools/ cv_export.py (lo instala /cv-ats-export)
35
+ tools/ cv_export.py + verify_pdf.py (los instala /cv-ats-export)
36
+ y job_scraper/seen_jobs.json (memoria de dedup de /job-scrape)
36
37
  ```
37
38
 
38
39
  Es **portable y agnóstico de Obsidian**: usa `[[wikilinks]]` (funcionan en Obsidian si el usuario lo usa, y son legibles en texto plano), frontmatter YAML, y prefijos numéricos para ordenar carpetas. No requiere Obsidian instalado.
39
40
 
41
+ Estados de una aplicación (frontmatter `estado`): `interesado → aplicado → entrevista → oferta`, con cierres `contratado | rechazado | sin_respuesta | declinada | descartado`. Los mueve `/job-outcome`; ninguna otra skill marca `aplicado`.
42
+
40
43
  ## Modos
41
44
 
42
- ### `setup` — Crear el workspace
45
+ ### `setup` - Crear el workspace
43
46
  1. Preguntar dónde crear el workspace (default `./career-workspace/`) y el/los rol(es) objetivo.
44
47
  2. Crear el árbol de carpetas completo.
45
48
  3. Copiar los 7 templates desde `references/templates/` a `Templates/` del workspace.
46
49
  4. Crear `00-Dashboard.md` desde `references/templates/Dashboard.md`.
47
50
  5. Sugerir el siguiente paso: `/master-profile` para construir el perfil maestro.
48
51
 
49
- ### `status` — Dónde estoy
52
+ ### `status` - Dónde estoy
50
53
  1. Leer `00-Dashboard.md` y escanear `03-Aplicaciones/`.
51
54
  2. Resumir: aplicaciones activas por prioridad, próximas entrevistas, ofertas que cierran pronto, gaps (aplicaciones sin CV/cover letter).
52
55
  3. Recomendar la acción de mayor impacto.
53
56
 
54
- ### `next` — Qué hago ahora
57
+ ### `next` - Qué hago ahora
55
58
  Según el estado del workspace, recomendar el skill siguiente (ver routing).
56
59
 
57
60
  ## Routing
@@ -61,6 +64,10 @@ Según el estado del workspace, recomendar el skill siguiente (ver routing).
61
64
  | No hay workspace | `/job-hunt setup` |
62
65
  | Falta perfil maestro | `/master-profile` |
63
66
  | Encontrar/scorear ofertas, plan de búsqueda | `/job-search` |
67
+ | Corrida de búsqueda con CLIs y dedup entre corridas | `/job-scrape` |
68
+ | Postulación completa (nota + CV + cover + review + PDF) | `/job-aplicar` |
69
+ | Registrar resultado/avance de una postulación | `/job-outcome` |
70
+ | Gaps de skills agregados + plan de estudio | `/job-upskill` |
64
71
  | Adaptar CV a una oferta | `/cv-tailor` |
65
72
  | Exportar CV a PDF ATS | `/cv-ats-export` |
66
73
  | Escribir cover letter / mensaje de aplicación | `/cover-letter` |
@@ -71,17 +78,17 @@ Según el estado del workspace, recomendar el skill siguiente (ver routing).
71
78
 
72
79
  ## Workflow estándar para una nueva oportunidad
73
80
 
74
- 1. `/job-search` → encontrar y scorear la oferta, crear nota en `03-Aplicaciones/` + `04-Empresas/`.
81
+ 1. `/job-scrape` → corrida de búsqueda con dedup (o `/job-search` para una oferta/búsqueda puntual): nota en `03-Aplicaciones/` + `04-Empresas/`.
75
82
  2. Identificar contacto objetivo → nota en `05-Contactos/`.
76
- 3. `/cv-tailor` → CV custom partiendo del perfil base + research.
77
- 4. `/cv-ats-export` → PDF ATS a `02-CVs/exports/`.
78
- 5. `/cover-letter` → mensaje de aplicación custom.
79
- 6. `/cold-outreach` → si hay contacto al que escribirle directo.
80
- 7. Cuando haya entrevista → `/interview-prep`.
83
+ 3. `/job-aplicar` → pipeline completo de la postulación: CV custom + cover + review (regla drafter-reviewer) + PDF ATS verificado. (A mano: `/cv-tailor` → `/cv-ats-export` → `/cover-letter`.)
84
+ 4. `/cold-outreach` → si hay contacto al que escribirle directo.
85
+ 5. Enviar la postulación (**siempre manual**) → `/job-outcome <empresa>` para marcar `aplicado`; registrar ahí cada avance.
86
+ 6. Cuando haya entrevista → `/interview-prep`.
87
+ 7. Con rechazos acumulados → `/job-upskill` para el plan de estudio según gaps reales.
81
88
 
82
89
  ## Reglas
83
90
 
84
- - Nunca reorganizar carpetas ni renombrar notas sin preguntar — rompe `[[wikilinks]]` en silencio.
91
+ - Nunca reorganizar carpetas ni renombrar notas sin preguntar - rompe `[[wikilinks]]` en silencio.
85
92
  - Nunca commitear datos sensibles (CV con teléfono/dirección, salarios, emails de recruiters) a un remoto sin confirmación explícita.
86
93
  - Si una categoría necesita un template que no existe, proponerlo antes de inventarlo.
87
94
  - Preservar los prefijos numéricos al crear archivos dentro de las carpetas ordenadas.
@@ -4,7 +4,8 @@ empresa:
4
4
  perfil:
5
5
  cv_usado:
6
6
  estado: interesado
7
- score:
7
+ match_score:
8
+ fuente:
8
9
  fecha_aplicacion:
9
10
  fecha_actualizacion: {{date:YYYY-MM-DD}}
10
11
  link_oferta:
@@ -14,6 +15,7 @@ modalidad:
14
15
  ubicacion:
15
16
  prioridad: media
16
17
  ---
18
+ %% estado: interesado -> aplicado -> entrevista -> oferta | cierres: contratado / rechazado / sin_respuesta / declinada / descartado (los mueve /job-outcome) %%
17
19
 
18
20
  # {{title}}
19
21
 
@@ -29,6 +31,8 @@ prioridad: media
29
31
  - Perfil base: [[]]
30
32
  - Qué de mi experiencia alinea con lo que piden:
31
33
  -
34
+ - Gaps reales (alimentan /job-upskill):
35
+ -
32
36
 
33
37
  ## A quién escribirle / contacto principal
34
38
  - [[]]
@@ -39,7 +43,7 @@ prioridad: media
39
43
  - [[]]
40
44
 
41
45
  ## Timeline / seguimiento
42
- - {{date:YYYY-MM-DD}} — creada la nota
46
+ - {{date:YYYY-MM-DD}} - creada la nota
43
47
 
44
48
  ## Entrevistas
45
49
  -
@@ -9,7 +9,7 @@ archivo_pdf:
9
9
  archivo_final:
10
10
  ---
11
11
 
12
- # CV custom — {{title}}
12
+ # CV custom - {{title}}
13
13
 
14
14
  ## Contexto de la postulación
15
15
  - **Aplicación:** [[]]
@@ -21,7 +21,7 @@ ultima_interaccion: {{date:YYYY-MM-DD}}
21
21
 
22
22
 
23
23
  ## Historial de interacciones
24
- - {{date:YYYY-MM-DD}} — creada la nota
24
+ - {{date:YYYY-MM-DD}} - creada la nota
25
25
 
26
26
  ## Mensajes intercambiados
27
27
 
@@ -2,7 +2,7 @@
2
2
  tipo: dashboard
3
3
  ---
4
4
 
5
- # Dashboard — Búsqueda laboral
5
+ # Dashboard - Búsqueda laboral
6
6
 
7
7
  > Índice del workspace. Cuando haya datos (aplicaciones, entrevistas), estas secciones pueden convertirse en queries dinámicas con Bases/Dataview si usás Obsidian.
8
8
 
@@ -16,6 +16,9 @@ tipo: dashboard
16
16
  **Cerradas / descartadas:**
17
17
  -
18
18
 
19
+ ## Leyenda de estados
20
+ ⬜ interesado · ✅ aplicado · 📅 entrevista/oferta · ❌ rechazado/sin_respuesta/declinada · 🚫 descartado/cerrada
21
+
19
22
  ## Próximas entrevistas
20
23
  -
21
24
 
@@ -26,20 +29,21 @@ tipo: dashboard
26
29
  -
27
30
 
28
31
  ## Accesos rápidos por carpeta
29
- - `01-Perfiles/` — perfiles profesionales base + perfil maestro
30
- - `02-CVs/` — versiones de CV (incluye CVs custom por postulación)
31
- - `03-Aplicaciones/` — una nota por postulación
32
- - `04-Empresas/` — research de empresas
33
- - `05-Contactos/` — recruiters, referidos, hiring managers
34
- - `06-Entrevistas/` — prep y feedback por ronda
35
- - `07-Recursos/` — cover letters reutilizables, links útiles
36
- - `portfolio/` — source-of-truth del portfolio web
37
- - `Templates/` — plantillas con frontmatter
32
+ - `01-Perfiles/` - perfiles profesionales base + perfil maestro
33
+ - `02-CVs/` - versiones de CV (incluye CVs custom por postulación)
34
+ - `03-Aplicaciones/` - una nota por postulación
35
+ - `04-Empresas/` - research de empresas
36
+ - `05-Contactos/` - recruiters, referidos, hiring managers
37
+ - `06-Entrevistas/` - prep y feedback por ronda
38
+ - `07-Recursos/` - cover letters reutilizables, links útiles
39
+ - `portfolio/` - source-of-truth del portfolio web
40
+ - `Templates/` - plantillas con frontmatter
41
+ - `tools/` - cv_export.py, verify_pdf.py y job_scraper/seen_jobs.json
38
42
 
39
43
  ## Workflow estándar para una nueva oportunidad
40
- 1. `/job-search` — encontrar y scorear la oferta → nota en `03-Aplicaciones/` + `04-Empresas/`.
44
+ 1. `/job-scrape` - corrida de búsqueda con dedup (o `/job-search` para una oferta puntual) → nota en `03-Aplicaciones/` + `04-Empresas/`.
41
45
  2. Identificar contacto objetivo → nota en `05-Contactos/`.
42
- 3. `/cv-tailor` — CV custom desde el perfil base + research.
43
- 4. `/cv-ats-export` — PDF ATS a `02-CVs/exports/`.
44
- 5. `/cover-letter` — mensaje de aplicación custom.
45
- 6. `/interview-prep` — cuando haya entrevista.
46
+ 3. `/job-aplicar` - pipeline completo: CV custom + cover + review + PDF ATS verificado.
47
+ 4. Enviar (siempre manual) → `/job-outcome <empresa>` para marcar `aplicado` y registrar avances.
48
+ 5. `/interview-prep` - cuando haya entrevista.
49
+ 6. `/job-upskill` - con rechazos acumulados, plan de estudio según gaps reales.
@@ -13,10 +13,10 @@ regla_seniority:
13
13
  fuentes: []
14
14
  ---
15
15
 
16
- # Perfil — {{title}}
16
+ # Perfil - {{title}}
17
17
 
18
18
  ## Resumen
19
- > 2–3 líneas: quién sos para este rol y qué te hace fuerte.
19
+ > 2-3 líneas: quién sos para este rol y qué te hace fuerte.
20
20
 
21
21
  ## Stack principal
22
22
  -
@@ -0,0 +1,55 @@
1
+ ---
2
+ name: job-outcome
3
+ description: "Record the outcome or progress of a job application (interview, offer, hired, rejected, no reply) updating the application note and the dashboard. Triggers: me respondieron de, registrar resultado, me rechazaron, tengo entrevista con, recibi una oferta, no me contestaron, actualizar estado de la postulacion."
4
+ argument-hint: "[empresa]"
5
+ category: "career"
6
+ user-invocable: true
7
+ allowed-tools: Read, Glob, Grep, Write, Edit
8
+ ---
9
+
10
+ # /job-outcome - Registrar resultado de una aplicación
11
+
12
+ Registrá qué pasó con una postulación: avances (entrevista agendada/hecha, oferta recibida) o resoluciones (contratado, rechazado, sin respuesta, oferta declinada, descartado). Adaptado del `/outcome` de MadsLorentzen/ai-job-search al esquema de notas del career workspace (rutas relativas al workspace: `--workspace`, env `CAREER_WORKSPACE`, o `./career-workspace/`).
13
+
14
+ Empresa/oferta indicada: `$ARGUMENTS`
15
+
16
+ ## Pasos
17
+
18
+ 1. **Localizar la nota.** Buscar en `03-Aplicaciones/` la nota cuyo nombre o frontmatter `empresa` matchee (case-insensitive) con `$ARGUMENTS`. Si no se pasó argumento o hay ambigüedad, listar las aplicaciones con estado abierto (`interesado`, `aplicado`, `entrevista`, `oferta`) y preguntar cuál.
19
+
20
+ 2. **Recolectar qué pasó.** Preguntar al usuario y clasificar:
21
+ - **Progreso:** entrevista agendada / entrevista realizada / oferta recibida → estados `entrevista` u `oferta`.
22
+ - **Resolución:** `contratado` · `rechazado` · `sin_respuesta` · `declinada` (la oferta la rechazó el usuario) · `descartado` (el usuario perdió interés / la oferta cerró).
23
+ Registrar fecha, feedback textual recibido y lecciones aprendidas. **Registrar datos, no interpretarlos:** citar el feedback tal como llegó, sin suavizarlo ni analizarlo acá.
24
+
25
+ 3. **Actualizar la nota de aplicación**, con approval del usuario sobre lo recolectado (idempotente - agregar, nunca duplicar ni sobrescribir):
26
+ - Frontmatter: `estado` al nuevo valor y `fecha_actualizacion` a hoy. Compatibilidad: si la nota vieja usa `score:` en vez de `match_score:`, no renombrar el campo, solo tocar `estado`.
27
+ - `## Timeline / seguimiento`: agregar línea `- YYYY-MM-DD - <qué pasó>`.
28
+ - Si hubo entrevista: entrada en `## Entrevistas` (fecha, etapa, entrevistador si se sabe, y link a la nota de `06-Entrevistas/` si existe).
29
+ - Feedback y lecciones van en `## Notas`.
30
+
31
+ 4. **Actualizar el dashboard** (`00-Dashboard.md`): buscar la fila de la oferta y actualizar la columna **Estado** con el nuevo estado (usar la leyenda de emojis de abajo, la misma del template Dashboard) y la columna de nota si aporta (ej. "Rechazada 2026-07-13").
32
+
33
+ 5. **Sugerir siguiente paso** según el caso:
34
+ - `entrevista` → ofrecer `/interview-prep`.
35
+ - `rechazado` con feedback → anotar la lección en la nota; si se acumulan 3+ resoluciones, ofrecer `/job-upskill` y revisar los criterios de búsqueda contra los resultados reales.
36
+ - `sin_respuesta` a los 10-14 días → ofrecer follow-up con `/cold-outreach`.
37
+ - `contratado` → 🎉 y actualizar el resto de las aplicaciones abiertas (¿retirarse de procesos?).
38
+
39
+ 6. **Confirmar** al usuario qué se registró (nota, campos tocados, fila del dashboard).
40
+
41
+ ## Vocabulario de `estado` (frontmatter)
42
+
43
+ `interesado → aplicado → entrevista → oferta` y cierra en: `contratado | rechazado | sin_respuesta | declinada | descartado`
44
+
45
+ Emojis del dashboard: ⬜ interesado · ✅ aplicado · 📅 entrevista/oferta · ❌ rechazado/sin_respuesta/declinada · 🚫 descartado/cerrada
46
+
47
+ ## Reglas
48
+
49
+ - No editar en masa notas existentes: solo la nota de la aplicación en cuestión y su fila del dashboard.
50
+ - No inventar datos: si falta información (fecha, feedback), preguntar o dejar el campo como está.
51
+ - Sin guiones largos en todo texto agregado.
52
+
53
+ ## Handoff
54
+
55
+ Registro COMPLETE cuando nota y dashboard quedaron consistentes. Según el resultado, el siguiente paso es `/interview-prep` (entrevista agendada), `/cold-outreach` (follow-up) o `/job-upskill` (rechazos acumulados).
@@ -0,0 +1,95 @@
1
+ ---
2
+ name: job-scrape
3
+ description: "Search job boards (bundled LinkedIn + GetOnBoard CLIs) with dedup memory across runs, score new openings against a role profile and offer to create application notes. Triggers: buscar ofertas nuevas, scrape de ofertas, correr los CLIs de busqueda, buscar trabajo en LinkedIn o GetOnBoard, nueva corrida de busqueda, ofertas con dedup."
4
+ argument-hint: "[perfil] [--jobage N]"
5
+ category: "career"
6
+ user-invocable: true
7
+ allowed-tools: Read, Glob, Grep, Bash, Write, Edit
8
+ ---
9
+
10
+ # /job-scrape - Búsqueda de ofertas con memoria
11
+
12
+ Corre los CLIs de búsqueda bundleados (LinkedIn + GetOnBoard, en `scripts/`) con los criterios de un perfil, deduplica contra `seen_jobs.json` y contra las notas existentes, scorea lo nuevo y ofrece crear notas de aplicación. Adaptado del skill job-scraper de MadsLorentzen/ai-job-search.
13
+
14
+ Flags y datos que devuelve cada script: `references/linkedin-cli.md` y `references/getonbrd-cli.md`.
15
+
16
+ Argumentos: `$ARGUMENTS` (perfil, opcional; `--jobage N` para override de recencia, default 7 días).
17
+
18
+ Todas las rutas de notas son relativas al **career workspace** (flag `--workspace`, env `CAREER_WORKSPACE`, o `./career-workspace/`; lo crea `/job-hunt setup`). El estado de dedup vive en `<workspace>/tools/job_scraper/seen_jobs.json`.
19
+
20
+ ## Requisitos
21
+
22
+ - **Python 3** en el PATH (los scripts usan solo la stdlib: cero dependencias, sin build, sin venv).
23
+ - Career workspace con al menos un perfil en `01-Perfiles/`.
24
+ - Opcional: skill `job-search` instalado (aporta la rúbrica de scoring completa).
25
+
26
+ ## Pasos
27
+
28
+ 1. **Perfil y criterios.** Si `$ARGUMENTS` no matchea una nota de `01-Perfiles/` (excluir `_index`), listar los perfiles y preguntar cuál usar. Leer del frontmatter: `keywords`, `keywords_excluir`, `match_score_minimo`, `salario_minimo_usd_mes`, `modalidad`, `seniority`, `regla_seniority`, `mercado_objetivo`.
29
+
30
+ 2. **Cargar estado.** Leer `<workspace>/tools/job_scraper/seen_jobs.json` (si no existe, crearlo con el esqueleto de `references/seen_jobs.schema.json`; si hay notas previas en `03-Aplicaciones/`, ofrecer backfillearlas como `nota_creada`). Validar que toda entrada `nota_creada` apunte a una nota existente en `03-Aplicaciones/`; reportar punteros rotos sin frenar.
31
+
32
+ 3. **Derivar queries.** 3-5 queries con forma de rol desde las keywords del perfil (ej. "fullstack developer", "backend developer"), no fragmentos de skill sueltos.
33
+
34
+ 4. **CONFIRMACIÓN: correr los CLIs.** Antes de la primera corrida de la sesión, mostrar el warning y pedir approval explícito (mismo criterio que `/job-search` con Apify):
35
+
36
+ > ⚠️ El CLI de LinkedIn lee páginas públicas de LinkedIn. El acceso automatizado va **contra sus Terms of Service**, y el uso es a tu propia responsabilidad: personal, volumen bajo, nunca comercial ni barridos masivos. GetOnBoard usa su API pública v0 y no tiene esta restricción.
37
+ > ¿Corro ambos CLIs, solo GetOnBoard, o ninguno?
38
+
39
+ Si el usuario elige solo GetOnBoard, saltear LinkedIn y declarar la cobertura reducida en el resumen final.
40
+
41
+ 5. **Ejecutar los CLIs** (tolerar el fallo de uno y reportar la degradación):
42
+ ```
43
+ python .claude/skills/job-scrape/scripts/linkedin_search.py search -q "<query>" -l "<location>" --remote remote --jobage <N> --format json
44
+ python .claude/skills/job-scrape/scripts/getonbrd_search.py search -q "<query>" --jobage <N> --brief
45
+ ```
46
+ La `<location>` de LinkedIn sale del `mercado_objetivo` del perfil; si es worldwide, usar además `-l "Remote"`. Volumen bajo: máximo ~5 queries por CLI por corrida.
47
+
48
+ **`--brief` no es opcional en el barrido:** los JD completos son ~80% del payload de GetOnBoard (~58 KB por query), y el quick score del paso 8 solo necesita los campos estructurados + el arranque de la description. El JD completo se trae en el paso 9, solo para las `high`. Si el CLI de LinkedIn emite un `warning` `NO_CARDS_PARSED` en stderr, la respuesta traía contenido pero el markup cambió: reportarlo como degradación de esa fuente, no como "no hay ofertas".
49
+
50
+ 6. **Normalizar y dedup intra-batch.** Canonicalizar URLs (sin utm/tracking; LinkedIn → `jobs/view/<id>`; GetOnBoard `/empleos/` → `/jobs/`), calcular `job_key = slug(empresa)|slug(titulo)` (reglas exactas en `references/dedup-playbook.md`). Misma oferta en ambas fuentes = una entrada con dos `sources[]`.
51
+
52
+ 7. **Dedup contra historial:**
53
+ - `seen_jobs.json`: entradas `nota_creada` o `descartado` se omiten de la tabla (contarlas aparte); entradas `new` re-vistas actualizan `last_seen` y se muestran marcadas "vista antes, sin decisión".
54
+ - `03-Aplicaciones/`: Grep por `link_oferta` con las URLs canónicas y por nombre `<Empresa> - <Rol>`. Si hay match no registrado en seen_jobs, registrarlo como `nota_creada` (autocuración).
55
+ - Matching blando (misma empresa + títulos muy parecidos): mostrar warning "posible duplicado de [[nota]]", no dedup automático.
56
+
57
+ 8. **Quick score (todas las nuevas)** usando solo datos del listing:
58
+ - `low`: keyword de `keywords_excluir` en el título (o en la description truncada de GetOnBoard); seniority fuera de banda (aplicando `regla_seniority` del perfil si existe); `salary_max < salario_minimo_usd_mes`; o elegibilidad bloqueada (countries/remote_zone excluye el país del usuario según `mercado_objetivo`).
59
+ - `high`: título matchea keywords del perfil + seniority en banda + sin bloqueos.
60
+ - `medium`: el resto. Campos null = desconocido, no penalizar.
61
+
62
+ 9. **Score completo solo para las `high`**, con la rúbrica de `.claude/skills/job-search/references/scoring-rubric.md` (si el skill job-search no está instalado, quedarse con el quick score y avisar). El JD completo se trae con `detail` de la fuente que corresponda (`detail <slug>` en GetOnBoard, `detail <id>` en LinkedIn), con un **cap de ~10 fetches por corrida sumando ambas fuentes** (ToS, volumen bajo), priorizando por quick score y recencia. Si las `high` exceden el cap, scorear las que entren y listar el resto con su quick score, declarando el corte.
63
+
64
+ 10. **Presentar tabla** ordenada por score: Título | Empresa | Fuente(s) | Salario | Score | Veredicto. Preguntar si alguna se descarta ya (registrar `status: descartado` + `descarte_motivo`).
65
+
66
+ 11. **Persistir** todas las entradas nuevas/actualizadas en `seen_jobs.json` y actualizar el campo `actualizado`. JSON válido siempre (es un data file con schema).
67
+
68
+ 12. **Ofrecer crear notas** para las de score ≥ `match_score_minimo`, con approval explícito **por oferta** (o "todas") antes de escribir. Por cada confirmada:
69
+ - Nota en `03-Aplicaciones/<Empresa> - <Rol>.md` desde el template `Templates/Aplicacion.md` del workspace, frontmatter completo (`estado: interesado`, `match_score`, `fuente`, `link_oferta`, `salario_rango`, `modalidad`, `ubicacion`, `prioridad`) y sección "Match con mi perfil" con alineaciones y **gaps reales** (alimentan `/job-upskill`).
70
+ - Fila en `00-Dashboard.md`, sección de aplicaciones activas, respetando las columnas exactas del header y escapando `\|` en wikilinks con alias.
71
+ - Entrada seen_jobs → `status: nota_creada` + `nota`.
72
+
73
+ 13. **Resumen final:** contadores (encontradas / ya vistas / nuevas / descartadas / notas creadas), fuentes efectivamente consultadas y cortes aplicados (cap de `detail`, fuente degradada), y sugerencia de `/job-aplicar` para las top.
74
+
75
+ ## Qué NO hace
76
+
77
+ - No corre el CLI de LinkedIn sin la confirmación del paso 4.
78
+ - No crea notas sin confirmación individual.
79
+ - No genera CVs ni covers (eso es `/job-aplicar`).
80
+ - No marca `estado: aplicado` nunca.
81
+ - No edita notas existentes ni re-scorea ofertas ya con nota.
82
+ - No borra filas del dashboard ni entradas de seen_jobs.
83
+ - No se corre en loop ni excede los caps de volumen.
84
+
85
+ ## Reglas
86
+
87
+ - Respetar la matriz de fuente de verdad de `references/dedup-playbook.md`: seen_jobs → nota → dashboard, unidireccional.
88
+ - Notas nuevas en el idioma del workspace, sin guiones largos, con wikilinks.
89
+ - Volumen bajo siempre en LinkedIn (el acceso automatizado va contra sus ToS; uso personal y puntual), y con el warning + approval del paso 4 en la primera corrida de la sesión. El profile pre-aprueba el permiso de Bash, no la decisión: la confirmación la pide el skill.
90
+ - `--brief` en el barrido de GetOnBoard; el JD completo solo vía `detail` para las `high`.
91
+ - Si ambos CLIs fallan, frenar y reportar (no degradar a scraping manual sin avisar).
92
+
93
+ ## Handoff
94
+
95
+ Al cerrar la corrida, las ofertas con nota creada quedan READY para el pipeline de postulación: correr `/job-aplicar <nota|url>` con la mejor. Para gaps recurrentes detectados en los scores, `/job-upskill`.
@@ -0,0 +1,25 @@
1
+ # seen_jobs - estado de deduplicacion de /job-scrape
2
+
3
+ `seen_jobs.json` registra todas las ofertas vistas por `/job-scrape` para no volver a presentarlas en corridas siguientes. Schema formal en `seen_jobs.schema.json` (junto a este archivo). Vive en `<workspace>/tools/job_scraper/` (no entre las notas) porque es dato de maquina, no nota: el vault/Obsidian no lo indexa.
4
+
5
+ ## Matriz de fuente de verdad (regla anti-divergencia)
6
+
7
+ | Dato | Fuente de verdad | Quien escribe |
8
+ |---|---|---|
9
+ | "Ya vi esta oferta" + triage + descartes pre-nota | `seen_jobs.json` | `/job-scrape` (y `/job-aplicar` al crear o descartar) |
10
+ | Estado del pipeline (`interesado -> aplicado -> ...`), match_score definitivo | Frontmatter de la nota en `03-Aplicaciones/` | `/job-aplicar` crea; `/job-outcome` actualiza |
11
+ | Vista humana de la cola | `00-Dashboard.md` | Derivado de las notas; `/job-aplicar` y `/job-outcome` fila a fila |
12
+
13
+ El flujo es **unidireccional**: seen_jobs -> nota -> dashboard. Cuando una entrada pasa a `status: nota_creada` se congela (solo conserva el puntero `nota`); el pipeline sigue en la nota y NUNCA se sincroniza de vuelta. Por eso no existe el estado `aplicado` en este archivo.
14
+
15
+ ## Claves de dedup
16
+
17
+ 1. **URL canonica** (por fuente): sin utm/tracking params; LinkedIn reducido a `linkedin.com/jobs/view/<id>`; GetOnBoard `/empleos/` -> `/jobs/`.
18
+ 2. **`job_key`** = `slug(empresa)|slug(titulo)`: lowercase, sin acentos, sin puntuacion (conserva `.` interno: coderslab.io, .net), sin sufijos legales (inc, llc, sa...), sin calificadores del titulo entre parentesis ("(remote)", "(latam)"). Detecta la misma oferta publicada en dos fuentes con URLs distintas; se agrega a `sources[]` de la entrada existente en vez de duplicar.
19
+ 3. Matching blando (misma empresa + alto solapamiento de tokens del titulo) = **warning** en `/job-scrape`, nunca dedup automatico (una empresa puede tener dos roles parecidos legitimos, o el mismo rol con titulo traducido).
20
+
21
+ ## Mantenimiento
22
+
23
+ - **Punteros rotos:** `/job-scrape` valida al arrancar que toda entrada `nota_creada` apunte a una nota existente y reporta las rotas.
24
+ - **Poda (opt-in):** entradas `new`/`descartado` con `last_seen` de mas de 90 dias se pueden borrar. Solo a pedido del usuario, nunca automatico. Las `nota_creada` no se podan (son el historial de dedup contra aplicaciones reales).
25
+ - **Primer run:** el archivo arranca vacio (esqueleto del schema). Si ya existen notas en `03-Aplicaciones/`, `/job-scrape` ofrece backfillearlas como `nota_creada` (con `quick_score: null` y `url: "nota://<archivo>"` si la nota no tiene `link_oferta`).
@@ -0,0 +1,62 @@
1
+ # GetOnBoard Search CLI
2
+
3
+ Busca ofertas en GetOnBoard (job board tech de LATAM) via su API pública v0. Sin auth, sin API key, **cero dependencias**: solo la stdlib de Python 3. Espejo del contrato de `linkedin_search.py` para que `/job-scrape` pueda usar ambos de forma intercambiable.
4
+
5
+ El script es `scripts/getonbrd_search.py`. Construido a medida siguiendo el patrón `/add-portal` de MadsLorentzen/ai-job-search (investigar endpoint → scaffold con el mismo contrato → test en vivo), y portado de TypeScript a Python por la regla de lenguaje por capa (`docs/coding-standards.md`); la salida de las dos versiones se comparó campo por campo sobre las mismas queries: identica.
6
+
7
+ ## Uso
8
+
9
+ ### Buscar ofertas
10
+
11
+ ```bash
12
+ python .claude/skills/job-scrape/scripts/getonbrd_search.py search -q "<keywords>" [flags]
13
+ ```
14
+
15
+ Flags:
16
+ - `-q / --query <texto>` - **requerido**, minimo 3 chars. La API hace AND de todos los terminos.
17
+ - `--jobage <dias>` - publicada en los ultimos N dias (filtro client-side sobre `published_at`).
18
+ - `--remote <modo>` - `remote` | `hybrid` | `onsite` (filtro client-side).
19
+ - `--page <n>` - paginacion (20 resultados por pagina).
20
+ - `-n / --limit <n>` - tope de resultados (`0` no emite ninguno).
21
+ - `--brief` - solo en `json`: trunca cada `description` a 300 chars y marca `description_truncated`. Los JD completos son ~80% del payload, asi que para triage conviene `--brief` y despues `detail` del shortlist.
22
+ - `--format json|table|plain` - default `json`.
23
+
24
+ Sin flag `--location`: GetOnBoard es LATAM/remoto por naturaleza; filtrar con `--remote` y leer `countries` en el JSON.
25
+
26
+ ### Detalle de una oferta
27
+
28
+ ```bash
29
+ python .claude/skills/job-scrape/scripts/getonbrd_search.py detail <slug|url> [--format json|plain]
30
+ ```
31
+
32
+ Acepta el `ID` (slug) de los resultados de search o cualquier URL de getonbrd.com (`/jobs/...` o `/empleos/...`, con o sin categoria). Dos capas:
33
+ 1. **Search API** (queries progresivas derivadas del slug): datos completos, incluye `applications_count`.
34
+ 2. **Microdata fallback** (pagina SSR, schema.org JobPosting): para ofertas viejas o fuera del indice. Detecta y marca ofertas **CERRADAS**.
35
+
36
+ ## Datos que devuelve (JSON)
37
+
38
+ `id` (slug), `title`, `company`, `location`, `date` (ISO), `url`, `seniority` (Junior/Semi Senior/Senior/Expert), `remote`, `remote_modality` (`remote_local`/`hybrid`/`no_remote`...), `remote_zone`, `countries`, `salary_min_usd_month`/`salary_max_usd_month` (USD/mes cuando esta publicado), `applications_count` (util para la dimension "calidad de la oferta" de la rubrica), `description` (texto plano).
39
+
40
+ ## Ejemplos
41
+
42
+ ```bash
43
+ # Fullstack, ultimas 2 semanas
44
+ python .claude/skills/job-scrape/scripts/getonbrd_search.py search -q "fullstack developer" --jobage 14 --format table
45
+
46
+ # Triage barato: estructurado completo, JD truncado
47
+ python .claude/skills/job-scrape/scripts/getonbrd_search.py search -q "fullstack developer" --jobage 14 --brief
48
+
49
+ # .NET remoto
50
+ python .claude/skills/job-scrape/scripts/getonbrd_search.py search -q ".NET developer" --remote remote --format table
51
+
52
+ # JD completo de una oferta
53
+ python .claude/skills/job-scrape/scripts/getonbrd_search.py detail "https://www.getonbrd.com/jobs/programming/full-stack-developer-buildwithin-remote-b1ef" --format plain
54
+ ```
55
+
56
+ ## Notas
57
+
58
+ - API publica en beta (`api/v0`); si cambia, el fallback de microdata sigue funcionando.
59
+ - Errores a **stderr** como `{ "error": "...", "code": "..." }` con exit code `1`.
60
+ - Volumen bajo por cortesia; la API es publica pero no esta pensada para bulk scraping.
61
+ - Referencia de endpoints: `getonbrd-urls.md`.
62
+ - Tests de los parsers: `tests/job_scrape/test_getonbrd_search.py` en el repo de Arcane.
@@ -0,0 +1,37 @@
1
+ # GetOnBoard API v0 - referencia de endpoints (verificada 2026-07-13)
2
+
3
+ Base: `https://www.getonbrd.com/api/v0` - sin autenticacion para endpoints publicos. Formato JSON:API.
4
+
5
+ ## Search de ofertas
6
+
7
+ ```
8
+ GET /search/jobs?query=<q>&per_page=<n>&page=<n>&expand[]=seniority&expand[]=company
9
+ ```
10
+
11
+ - `query`: requerido, min 3 chars. Hace AND de todos los terminos (queries largas sobre-restringen).
12
+ - `per_page` / `page`: paginacion; `meta: { page, per_page, total_pages }` en la respuesta.
13
+ - `expand[]`: resuelve las referencias `seniority` y `company` inline (sin expand vienen solo como `{id, type}`).
14
+
15
+ Respuesta por job (`data[]`): `id` = slug, `links.public_url`, y en `attributes`:
16
+ `title`, `description` (HTML), `functions` (HTML), `desirable` (HTML), `benefits` (HTML), `remote` (bool), `remote_modality` (`remote_local` | `hybrid` | `no_remote` | ...), `remote_zone`, `countries[]`, `min_salary`/`max_salary` (USD/mes, null si no publican), `published_at` (unix), `applications_count`, `seniority.data.attributes.name`, `company.data.attributes.name`, `lang`, `perks[]`.
17
+
18
+ ## Catalogos publicos
19
+
20
+ ```
21
+ GET /seniorities -> Junior=2, Semi Senior=3, Senior=4, Expert=5, Sin experiencia=1
22
+ GET /categories -> categorias (programming, design-ux, etc.)
23
+ ```
24
+
25
+ ## Lo que NO es publico
26
+
27
+ - `GET /jobs/<slug>` devuelve **401** (endpoint privado, requiere API key de cuenta).
28
+ Por eso `detail` del script usa search + filtro por id exacto, con fallback a microdata.
29
+
30
+ ## Paginas SSR (fallback de detail)
31
+
32
+ - `https://www.getonbrd.com/jobs/<slug>` redirige 301 a `/jobs/<categoria>/<slug>`.
33
+ - La pagina renderiza microdata schema.org JobPosting via atributos `itemprop`:
34
+ `title`, `hiringOrganization > name`, `datePosted`, `baseSalary > minValue/maxValue`,
35
+ `description`, `qualifications` (seniority), `address`.
36
+ - Ofertas cerradas: pierden el `itemtype="http://schema.org/JobPosting"` pero conservan
37
+ los `itemprop`; la pagina contiene el texto "Closed job" / "No longer accepting".