claude-code-arcane 2.2.0 → 2.3.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.
- package/CHANGELOG.md +22 -0
- package/README.md +56 -55
- package/agents/career/career-strategist.md +88 -0
- package/agents/career/cv-reviewer.md +81 -0
- package/agents/career/hiring-manager.md +68 -0
- package/agents/career/mock-interviewer.md +76 -0
- package/agents/freelance/client-screener.md +84 -0
- package/agents/freelance/discovery-call.md +65 -0
- package/agents/freelance/pipeline-strategist.md +99 -0
- package/agents/freelance/proposal-reviewer.md +82 -0
- package/dist/cli.js +52 -13
- package/docs/RELEASE-SETUP.md +1 -1
- package/docs/SKILLS-CATALOG.md +27 -3
- package/docs/USER-GUIDE.md +77 -72
- package/docs/agent-hierarchy.md +21 -1
- package/docs/presentations/build_arcane_deck.py +2 -2
- package/docs/presentations/build_arcane_deck_12.py +2 -2
- package/package.json +2 -2
- package/profiles/freelance.yaml +43 -0
- package/profiles/job-hunt.yaml +5 -2
- package/rules/drafter-reviewer.md +16 -3
- package/rules/freelance-guardrails.md +53 -0
- package/skills/career-registry/SKILL.md +95 -0
- package/skills/career-registry/scripts/career_registry.py +488 -0
- package/skills/client-screen/SKILL.md +83 -0
- package/skills/client-screen/references/red-flags.md +95 -0
- package/skills/cover-letter/SKILL.md +4 -3
- package/skills/cv-tailor/SKILL.md +11 -2
- package/skills/freelance-deliver/SKILL.md +90 -0
- package/skills/freelance-hunt/SKILL.md +126 -0
- package/skills/freelance-hunt/references/templates/Connects.md +39 -0
- package/skills/freelance-hunt/references/templates/Contrato.md +60 -0
- package/skills/freelance-hunt/references/templates/Propuesta.md +66 -0
- package/skills/freelance-kickoff/SKILL.md +94 -0
- package/skills/freelance-pipeline/SKILL.md +109 -0
- package/skills/freelance-pipeline/scripts/import_history.py +425 -0
- package/skills/freelance-profile/SKILL.md +93 -0
- package/skills/freelance-proposal/SKILL.md +112 -0
- package/skills/freelance-scan/SKILL.md +196 -0
- package/skills/freelance-scan/references/dedup.md +35 -0
- package/skills/freelance-scan/references/platforms.md +167 -0
- package/skills/freelance-scan/scripts/freelance_search.py +1077 -0
- package/skills/interview-prep/SKILL.md +11 -3
- package/skills/job-aplicar/SKILL.md +5 -1
- package/skills/job-hunt/SKILL.md +17 -0
- package/skills/job-outcome/SKILL.md +15 -1
- package/skills/job-upskill/SKILL.md +10 -2
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: freelance-proposal
|
|
3
|
+
description: "Write an Upwork proposal that survives the list view and price it above your net floor rate: hook in the first two lines, proof instead of adjectives, fixed vs hourly, risk buffer and platform fee. Reviewed by a fresh-context agent before spending Connects. Triggers: escribir propuesta upwork, cover letter upwork, cuanto cobrar por este proyecto, bid, cotizar proyecto freelance, propuesta freelance."
|
|
4
|
+
argument-hint: "[nota de aplicación | job-url]"
|
|
5
|
+
category: "career"
|
|
6
|
+
user-invocable: true
|
|
7
|
+
allowed-tools: Read, Glob, Grep, Write, Edit, WebFetch, Task
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Upwork Proposal - Propuesta y bid
|
|
11
|
+
|
|
12
|
+
Escribís la propuesta y le ponés precio. Dos cosas en una skill porque en Upwork van juntas: el bid es parte de la propuesta y una no se defiende sin la otra.
|
|
13
|
+
|
|
14
|
+
Rutas relativas al career workspace (`--workspace`, env `CAREER_WORKSPACE`, o `./career-workspace/`).
|
|
15
|
+
|
|
16
|
+
Oferta indicada: `$ARGUMENTS`
|
|
17
|
+
|
|
18
|
+
## Precondición: el screen
|
|
19
|
+
|
|
20
|
+
**No redactar una propuesta sin la nota de `/client-screen`.** Si no existe, pedirla y correr ese skill primero.
|
|
21
|
+
|
|
22
|
+
Si el screen dio riesgo alto o `NO POSTULARSE` y el usuario quiere postularse igual, se puede - **pero se le dice qué falló y se registra en la nota** (regla 1 de `freelance-guardrails`). Los Connects son plata: gastarlos a ciegas es distinto de gastarlos con los ojos abiertos.
|
|
23
|
+
|
|
24
|
+
## Inputs
|
|
25
|
+
|
|
26
|
+
- **El job post completo** y la nota de `03-Aplicaciones/` con el screen.
|
|
27
|
+
- **El perfil base** de `01-Perfiles/` - de ahí salen la prueba y el **piso de tarifa neto**.
|
|
28
|
+
- **La estimación** de `/estimate` si el alcance es grande.
|
|
29
|
+
- Trabajos previos comparables (`08-Contratos/` cerrados) para calibrar precio contra realidad, no contra deseo.
|
|
30
|
+
|
|
31
|
+
## Parte 1 - La propuesta
|
|
32
|
+
|
|
33
|
+
### Cómo la lee el cliente
|
|
34
|
+
|
|
35
|
+
Tiene 30 propuestas en una lista y ve **las dos primeras líneas de cada una**. Todo lo que no esté ahí, no existe todavía. Esa es la única restricción de formato que importa.
|
|
36
|
+
|
|
37
|
+
### Estructura
|
|
38
|
+
|
|
39
|
+
1. **Dos primeras líneas (el 80% del trabajo).** Algo que solo se sabe habiendo leído *este* post: el problema real detrás del pedido, una observación técnica concreta, o el resultado que podés traer. Si la apertura serviría para cualquier otro trabajo del rubro, está muerta.
|
|
40
|
+
2. **Prueba, 2-3 oraciones.** Un proyecto comparable con número o link. No adjetivos: "expert in React" es ruido, "migré un checkout de 40k usuarios a React, bajé el tiempo de carga 60%" es prueba.
|
|
41
|
+
3. **Cómo lo harías, 3-4 puntos.** Suficiente para mostrar que ya lo pensaste, no tanto que regales la consultoría.
|
|
42
|
+
4. **El precio, justificado.** Ver Parte 2. Un número suelto invita a regatear.
|
|
43
|
+
5. **Una pregunta concreta** sobre el proyecto. Sin pregunta no arranca conversación, y demuestra que leíste el post.
|
|
44
|
+
|
|
45
|
+
**150-300 palabras.** Arriba de 300 casi nunca se lee entera.
|
|
46
|
+
|
|
47
|
+
### Requisitos explícitos del post
|
|
48
|
+
|
|
49
|
+
Muchos posts piden algo puntual: empezar con una palabra clave, responder una pregunta específica, mandar un link. **Buscalo siempre y cumplilo.** Es descarte automático y es el error más barato de evitar.
|
|
50
|
+
|
|
51
|
+
### Anti-patrones (cortar siempre)
|
|
52
|
+
|
|
53
|
+
- "I have X years of experience in..." como apertura. El cliente no contrata experiencia, contrata la solución de un problema que ya tiene.
|
|
54
|
+
- "Dear Sir/Madam", "I hope this message finds you well".
|
|
55
|
+
- Recitar el perfil en prosa.
|
|
56
|
+
- Adjetivos sin evidencia: passionate, detail-oriented, fast learner.
|
|
57
|
+
- Frases que delatan plantilla o generación automática. Los clientes de Upwork las reconocen al instante y es la razón número uno de descarte.
|
|
58
|
+
- Prometer plazos que no podés cumplir para ganar el contrato.
|
|
59
|
+
|
|
60
|
+
## Parte 2 - El bid
|
|
61
|
+
|
|
62
|
+
### El piso es neto
|
|
63
|
+
|
|
64
|
+
El piso de tarifa de `01-Perfiles/` es **lo que te queda**, no lo que facturás. Upwork se queda una comisión, así que el bid tiene que cubrirla: con un piso de 40 USD/h y 10% de comisión, el bid mínimo es ~45.
|
|
65
|
+
|
|
66
|
+
**Si el bid calculado queda abajo del piso, decilo fuerte y no lo maquilles.** El trabajo correcto ahí es no postularse o cotizar lo que corresponde y perder la oferta - no bajar el piso. Bajar la tarifa es la palanca más fácil de ejecutar y la más difícil de revertir: baja el techo de todos los contratos siguientes.
|
|
67
|
+
|
|
68
|
+
### Fijo u hora
|
|
69
|
+
|
|
70
|
+
| Elegí fijo cuando | Elegí hora cuando |
|
|
71
|
+
|---|---|
|
|
72
|
+
| El alcance está escrito y acotado | El alcance es exploratorio o va a cambiar |
|
|
73
|
+
| Podés estimar con confianza media o alta | Hay dependencias del cliente que no controlás |
|
|
74
|
+
| Querés capturar valor arriba de tus horas | El cliente pide "iremos viendo" |
|
|
75
|
+
|
|
76
|
+
Alcance difuso + precio fijo es la combinación que más plata pierde. Si el screen marcó alcance difuso y el cliente insiste en fijo, el precio tiene que incluir el riesgo o el trabajo correcto es no tomarlo.
|
|
77
|
+
|
|
78
|
+
### El cálculo
|
|
79
|
+
|
|
80
|
+
1. Esfuerzo base de `/estimate`, en horas.
|
|
81
|
+
2. **Buffer de riesgo** según lo que dijo el screen: alcance claro y cliente con historial, +15%; alcance difuso o cliente nuevo, +40% o más. El buffer no es grasa, es el precio de la incertidumbre que el cliente te transfiere.
|
|
82
|
+
3. Horas no facturables del proyecto: reuniones, revisiones, ida y vuelta.
|
|
83
|
+
4. Multiplicar por la tarifa objetivo (no por el piso: el piso es el mínimo, no la meta).
|
|
84
|
+
5. Sumar la comisión de la plataforma.
|
|
85
|
+
6. Comparar contra el presupuesto del cliente. Si no cierra, es dato para la decisión, no para bajar el número.
|
|
86
|
+
|
|
87
|
+
Mostrar el desglose al usuario **antes** de escribirlo en la propuesta. Al cliente se le muestra el total y su razón, no la planilla.
|
|
88
|
+
|
|
89
|
+
## Proceso
|
|
90
|
+
|
|
91
|
+
1. Leer el post, el screen y el perfil. Verificar que el screen exista.
|
|
92
|
+
2. Calcular el bid (Parte 2) y mostrar el desglose. Si queda abajo del piso, avisar y parar a decidir.
|
|
93
|
+
3. Draftear la propuesta (Parte 1). Después cortar sin piedad todo lo genérico.
|
|
94
|
+
4. **Review con `proposal-reviewer`** (contexto fresco): pasale inline el post, el borrador y el bid. Aplicar sus reemplazos exactos e incorporar lo que corresponda. Una sola ronda. El que escribió leyó el post entero y ya está convencido de su propio gancho; el cliente ve dos líneas.
|
|
95
|
+
5. Con approval, guardar en la nota de `03-Aplicaciones/`, sección `## Propuesta`, con el bid y su justificación en `## Cotización`.
|
|
96
|
+
6. Si el usuario quiere ensayar la llamada, ofrecer `discovery-call`.
|
|
97
|
+
|
|
98
|
+
## Idioma
|
|
99
|
+
|
|
100
|
+
En el idioma del post. Si el post está en inglés, la propuesta va en inglés aunque la charla sea en español.
|
|
101
|
+
|
|
102
|
+
## Reglas
|
|
103
|
+
|
|
104
|
+
- Nunca inventar experiencia, clientes, plazos ni métricas.
|
|
105
|
+
- Nunca bidear abajo del piso neto sin decirlo explícitamente.
|
|
106
|
+
- Nunca enviar la propuesta: se muestra y el usuario copia y manda (regla 4 de `freelance-guardrails`).
|
|
107
|
+
- Nunca marcar `propuesta_enviada`: eso lo hace `/job-outcome` después del envío.
|
|
108
|
+
- Sin guiones largos en el texto de la propuesta ni de las notas.
|
|
109
|
+
|
|
110
|
+
## Handoff
|
|
111
|
+
|
|
112
|
+
Pedí aprobación (approval) antes de escribir la propuesta en la nota. Propuesta READY cuando pasó el review y el bid está sobre el piso. El siguiente paso es enviarla a mano y correr `/job-outcome` para registrar el envío y el gasto de Connects; si el cliente responde, `discovery-call` para ensayar y después `/contract-and-proposal-writer` para el SOW.
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: freelance-scan
|
|
3
|
+
description: "Find and score freelance projects across public keyless sources with dedup between runs, plus market intel on what other freelancers charge. Triggers: buscar proyectos freelance, scan de ofertas, scorear ofertas freelance, corrida de busqueda freelance, que ofertas me convienen, cuanto cobran otros freelancers, tarifa de mercado."
|
|
4
|
+
argument-hint: "[search | web | market | keys | sources | post pegado]"
|
|
5
|
+
category: "career"
|
|
6
|
+
user-invocable: true
|
|
7
|
+
allowed-tools: Read, Glob, Grep, Bash, Write, Edit, WebFetch, WebSearch
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Freelance Scan - Encontrar y scorear proyectos
|
|
11
|
+
|
|
12
|
+
Producís una **cola triageada** de proyectos: scoreados contra el perfil y el piso de tarifa, sin repetir lo que ya viste. No postulás nada acá - el objetivo es decidir dónde gastar el screen y el dinero de postularse.
|
|
13
|
+
|
|
14
|
+
Rutas relativas al career workspace (`--workspace`, env `CAREER_WORKSPACE`, o `./career-workspace/`).
|
|
15
|
+
|
|
16
|
+
Modo o búsqueda: `$ARGUMENTS`
|
|
17
|
+
|
|
18
|
+
## De dónde salen las ofertas
|
|
19
|
+
|
|
20
|
+
El script `scripts/freelance_search.py` (stdlib, sin dependencias) trae proyectos de fuentes **públicas y sin API key**. Eso es un requisito, no una casualidad: este skillset lo instala cualquiera, y una fuente que exige credenciales propias de cada usuario deja el skill roto para todos menos uno.
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
python .claude/skills/freelance-scan/scripts/freelance_search.py sources
|
|
24
|
+
python .claude/skills/freelance-scan/scripts/freelance_search.py search --query "unity developer" --pages 4
|
|
25
|
+
python .claude/skills/freelance-scan/scripts/freelance_search.py market --months 3
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
| Fuente | Alcance | Filtro freelance |
|
|
29
|
+
|---|---|---|
|
|
30
|
+
| **GetOnBrd** | LatAm y remoto | `modality` Freelance, filtrado del lado del cliente |
|
|
31
|
+
| **Himalayas** | Remoto global | `employmentType` Contractor, filtrado del lado del cliente |
|
|
32
|
+
|
|
33
|
+
**Las dos aceptan un parámetro de modalidad y lo ignoran.** Devuelven 200 y mandan full-time igual (se probaron 7 variantes de nombre en GetOnBrd y 4 en Himalayas). Y **la búsqueda de GetOnBrd ordena por relevancia, no filtra**: `query=unity` devuelve 21 páginas donde el fondo es SAP y COBOL. Por eso el filtro de modalidad *y* el de relevancia son del lado del cliente.
|
|
34
|
+
|
|
35
|
+
El script reporta `yield_by_source` en cada corrida, y hay que mostrárselo al usuario:
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
{'getonbrd': {'scanned': 103, 'freelance': 0, 'freelance_pero_irrelevante': 9}}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Eso se lee así: se leyeron 103 ofertas, 9 eran freelance, ninguna tenía que ver con lo buscado. **`freelance_pero_irrelevante` alto con `freelance: 0` significa que ese nicho no existe hoy en esa fuente** - no que la corrida falló. Decirlo explícitamente evita que el usuario repita la búsqueda pensando que se rompió algo.
|
|
42
|
+
|
|
43
|
+
Rendimiento medido: GetOnBrd ~10 freelance cada 200 escaneados antes del filtro de relevancia; Himalayas ~28 cada 120.
|
|
44
|
+
|
|
45
|
+
El match es por **límite de palabra**, no substring: en este dominio `go`, `ux`, `ai` y `qa` son búsquedas legítimas, y por substring `ux` matchearía dentro de "Linux" y `go` dentro de "Django". Costo aceptado y testeado: buscar `script` ya no encuentra "JavaScript". Los términos con símbolos (`c#`, `.net`, `node.js`, `react native`) caen a substring.
|
|
46
|
+
|
|
47
|
+
### Expansión por sinónimos
|
|
48
|
+
|
|
49
|
+
**Nadie titula una oferta "ecommerce".** La titula "Shopify". Buscar por categoría escondía trabajo real, medido sobre el pool completo:
|
|
50
|
+
|
|
51
|
+
| Buscás | Sin expansión | Con expansión |
|
|
52
|
+
|---|---|---|
|
|
53
|
+
| `website` | 0 | 7 |
|
|
54
|
+
| `backend` | 1 | 6 |
|
|
55
|
+
| `ecommerce` | 0 | 1 (el Shopify que estaba ahí) |
|
|
56
|
+
| `frontend` | 1 | 2 |
|
|
57
|
+
| `go` | 0 | 1 (la oferta dice "Golang") |
|
|
58
|
+
|
|
59
|
+
El mapa está en `SYNONYMS` dentro del script. Dos decisiones de diseño que importan:
|
|
60
|
+
|
|
61
|
+
- **La expansión es unidireccional**: `ecommerce → shopify`, nunca al revés. Quien ya busca preciso no quiere que le infles los resultados.
|
|
62
|
+
- **Se consulta el server una vez por término**, no solo se filtra distinto. El ranking de GetOnBrd depende de `query`, así que buscar "ecommerce" nunca trae el pool donde vive el de Shopify. El término del usuario se pagina hondo; cada sinónimo, una página. Tope de 6 términos extra, porque cada uno es una request.
|
|
63
|
+
|
|
64
|
+
El output trae `terms_searched` y `synonyms_applied`: **mostráselos al usuario**, si no no entiende por qué una búsqueda de "ecommerce" le trajo Shopify. Con `--no-expand` se apaga.
|
|
65
|
+
|
|
66
|
+
Si un nicho del usuario no está en el mapa, agregarlo es una línea - y conviene, porque el vocabulario de cada rubro es distinto.
|
|
67
|
+
|
|
68
|
+
## Modo `web` - llegar a los marketplaces sin credencial
|
|
69
|
+
|
|
70
|
+
Upwork, Workana, Freelancer.com, PeoplePerHour y Guru no tienen API pública. Pero **WebSearch sí los alcanza**, y la credencial es Claude Code, que ya tiene todo el que instaló el perfil.
|
|
71
|
+
|
|
72
|
+
### Cómo se corre
|
|
73
|
+
|
|
74
|
+
Disparar **varias `WebSearch` en un solo turno** (en paralelo, no una tras otra), combinando plataforma y término del stack del usuario:
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
WebSearch allowed_domains: ["upwork.com"] query: "<stack> freelance project hourly contract"
|
|
78
|
+
WebSearch allowed_domains: ["workana.com"] query: "proyecto <stack> desarrollo freelance"
|
|
79
|
+
WebSearch allowed_domains: ["freelancer.com", "peopleperhour.com", "guru.com"]
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Después pasar **todas** las URLs por el triage, que es la parte determinista:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
python .claude/skills/freelance-scan/scripts/freelance_search.py triage \
|
|
86
|
+
--url "https://..." --url "https://..."
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Devuelve `postings` (lo único que vale abrir), `descartados_landing` y `sin_clasificar`.
|
|
90
|
+
|
|
91
|
+
### Por qué el triage no es opcional
|
|
92
|
+
|
|
93
|
+
Medido sobre una corrida real: **de 13 URLs, 3 eran ofertas.** Las otras 10 eran landings de "Hire Golang developers", tablas de tarifas, gigs del Project Catalog y páginas de búsqueda. Sin filtrar, la cola es mitad basura y el usuario deja de mirarla.
|
|
94
|
+
|
|
95
|
+
### Los tres límites, y hay que decirlos
|
|
96
|
+
|
|
97
|
+
1. **La fecha no está verificada.** El índice no filtra por recencia: en la prueba real apareció una oferta de Upwork **posteada en 2021**. Siempre avisar que hay que mirar la fecha al abrir.
|
|
98
|
+
2. **No se puede leer la oferta.** `WebFetch` devuelve **403 en todas** estas plataformas, incluso en páginas de proyecto individuales. Presupuesto, cantidad de propuestas y reputación del cliente requieren que el usuario abra el link.
|
|
99
|
+
3. **El resumen de la búsqueda no es dato.** Es un modelo leyendo varias páginas; puede mezclar detalles de dos ofertas.
|
|
100
|
+
|
|
101
|
+
Por eso este modo entrega **links para abrir**, no filas para el registro. Nunca guardar en `seen_jobs.json` una oferta que vino de acá con fecha o presupuesto sin confirmar: un dato inventado en el registro contamina todas las decisiones que se apoyan en él.
|
|
102
|
+
|
|
103
|
+
## Modo `keys` - las fuentes que piden credencial propia
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
python .claude/skills/freelance-scan/scripts/freelance_search.py keys
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Lista qué fuentes necesitan API key, **cómo conseguir cada una**, y si está configurada en esta máquina.
|
|
110
|
+
|
|
111
|
+
**Upwork** tiene adaptador escrito. Se pide la key desde el API Center de la cuenta (freelancer o cliente, cualquier plan, sin cuenta de empresa) y contestan por mail en ~1 semana. Con `UPWORK_ACCESS_TOKEN` en el entorno, `search` la suma sola a la corrida y trae presupuesto, tarifa horaria y reputación del cliente, que es justo lo que el modo `web` no puede ver.
|
|
112
|
+
|
|
113
|
+
Sin la variable puesta **no se intenta y se avisa**, en vez de fallar en cada corrida hasta que el usuario aprenda a ignorar los errores.
|
|
114
|
+
|
|
115
|
+
Lo que ni con key se puede: **enviar propuestas**. Upwork no expone mutation para postularse ni para gastar Connects, a propósito, contra el auto-bidding. Postularse sigue siendo a mano siempre.
|
|
116
|
+
|
|
117
|
+
**Nunca scrapear ninguna plataforma ni sugerir herramientas que lo hagan.** Donde el `robots.txt` opina, opina en contra: PeoplePerHour prohíbe las URLs con filtros y Guru prohíbe `/api/`. Detalle por plataforma con los códigos medidos: `references/platforms.md`.
|
|
118
|
+
|
|
119
|
+
## Scoring
|
|
120
|
+
|
|
121
|
+
Cada oferta recibe un `match_score` 0-100. Tres componentes, y el tercero es el que la mayoría ignora:
|
|
122
|
+
|
|
123
|
+
### 1. Fit técnico (0-40)
|
|
124
|
+
|
|
125
|
+
Cuánto de lo que piden cubrís con evidencia real del perfil. Must-haves cubiertos pesan; nice-to-haves suman poco. Un must-have que no cubrís no se compensa con tres nice-to-haves.
|
|
126
|
+
|
|
127
|
+
### 2. Viabilidad económica (0-40)
|
|
128
|
+
|
|
129
|
+
El presupuesto contra tu piso neto, estimando el esfuerzo real del alcance descrito.
|
|
130
|
+
|
|
131
|
+
- Presupuesto que no llega al piso → **máximo 10 en este componente**, sin importar lo lindo que sea el proyecto.
|
|
132
|
+
- Presupuesto sin especificar → no asumas que es bueno. Puntuá con la incertidumbre y marcalo.
|
|
133
|
+
|
|
134
|
+
### 3. Probabilidad de que exista de verdad (0-20)
|
|
135
|
+
|
|
136
|
+
Una parte importante de las ofertas de marketplace no termina contratando a nadie. Señales de que esta sí:
|
|
137
|
+
|
|
138
|
+
- Cliente con pago verificado y contrataciones previas
|
|
139
|
+
- Oferta reciente y con pocas propuestas todavía
|
|
140
|
+
- Post específico, escrito por alguien que sabe lo que pide
|
|
141
|
+
- Cliente que respondió preguntas en el propio post
|
|
142
|
+
|
|
143
|
+
Señales contrarias: hire rate bajo con muchos posts, oferta vieja con decenas de propuestas, post genérico reutilizado.
|
|
144
|
+
|
|
145
|
+
### Umbrales
|
|
146
|
+
|
|
147
|
+
| Score | Acción |
|
|
148
|
+
|---|---|
|
|
149
|
+
| 70+ | Cola de screen: correr `/client-screen` |
|
|
150
|
+
| 50-69 | Segunda vuelta: solo si la cola alta está vacía |
|
|
151
|
+
| < 50 | Descartar y registrar el motivo en el estado de dedup |
|
|
152
|
+
|
|
153
|
+
Los umbrales se documentan en la corrida. Si el usuario los quiere mover, se mueven - explícitamente, no por acomodo.
|
|
154
|
+
|
|
155
|
+
## Modo `market` - qué cobran los demás
|
|
156
|
+
|
|
157
|
+
El hilo mensual de Hacker News "Ask HN: Freelancer? Seeking freelancer?" **no es una fuente de trabajo**: medido sobre 6 meses hay 113 freelancers ofreciéndose contra 2 ofertas. Pero eso mismo lo vuelve la mejor fuente gratuita de otra cosa: **cómo se posiciona y cuánto cobra gente con tu perfil**.
|
|
158
|
+
|
|
159
|
+
`market` devuelve los perfiles, las tarifas que mencionan (con la cita textual, sin inventar números), y un resumen con **n, mínimo, mediana y máximo por unidad**.
|
|
160
|
+
|
|
161
|
+
**Solo ~3% de los perfiles declara una tarifa parseable**: en la corrida de 6 meses fueron 3 tarifas sobre 119 perfiles (mediana 100 USD/hora). Siempre reportar el `n` al usuario - una mediana sobre 3 datos orienta, no decide. Los precios por paquete ("75-150 USD un diagnóstico") no entran al resumen a propósito: no son tarifa horaria y mezclarlos ensucia la mediana.
|
|
162
|
+
|
|
163
|
+
Sirve para dos decisiones concretas: el piso de tarifa que fija `/freelance-hunt setup` y usa `/freelance-profile`, y el bid de `/freelance-proposal`. Un tercer uso, menos obvio y bastante útil: leer cómo escriben su one-liner los que cobran más.
|
|
164
|
+
|
|
165
|
+
## Dedup entre corridas
|
|
166
|
+
|
|
167
|
+
Mismo mecanismo y mismo archivo que `/job-scrape`: `<workspace>/tools/job_scraper/seen_jobs.json`. Si `+job-hunt` está instalado, el estado es compartido y no hay dos archivos.
|
|
168
|
+
|
|
169
|
+
Claves: URL canónica sin parámetros de tracking, y `job_key` = `slug(cliente)|slug(titulo)`. Matching blando (mismo cliente + títulos parecidos) es **warning**, nunca dedup automático.
|
|
170
|
+
|
|
171
|
+
Flujo unidireccional: `seen_jobs` → nota → dashboard, nunca de vuelta. Playbook completo: `../job-scrape/references/dedup-playbook.md` si está instalado, o `references/dedup.md`.
|
|
172
|
+
|
|
173
|
+
Reposteos: es común que un cliente cierre y republique la misma oferta. Si el `job_key` matchea una entrada `descartado`, avisar que ya se descartó antes y por qué, en vez de volver a triagearla de cero. Un cliente que republica cinco veces sin cambiar el presupuesto es en sí una señal para `/client-screen`.
|
|
174
|
+
|
|
175
|
+
## Proceso
|
|
176
|
+
|
|
177
|
+
1. **Cargar el estado de dedup.** Validar que las entradas `nota_creada` apunten a notas existentes; reportar las rotas.
|
|
178
|
+
2. **Obtener las ofertas**: corrida del script, y/o las que el usuario pegue de plataformas cerradas.
|
|
179
|
+
3. **Dedupear.** Reportar cuántas se filtraron, para que se vea que el mecanismo trabaja.
|
|
180
|
+
4. **Scorear** las nuevas con los tres componentes. Mostrar la tabla en el chat: título, cliente, presupuesto, score y los tres subscores. Mostrar también el `yield_by_source` de la corrida.
|
|
181
|
+
5. **Triagear** con approval del usuario: qué pasa a screen, qué queda en segunda vuelta, qué se descarta y por qué.
|
|
182
|
+
6. **Registrar** en `seen_jobs.json`. Crear nota en `03-Aplicaciones/` (template `Propuesta`, `tipo: freelance`, `estado: interesado`) solo para las que pasan a screen - el motivo de descarte vive en el estado de dedup, no en una nota.
|
|
183
|
+
|
|
184
|
+
## Reglas
|
|
185
|
+
|
|
186
|
+
- **Nunca inventar ofertas, presupuestos ni datos de clientes.** Todo sale del post real o del script.
|
|
187
|
+
- **Nunca scrapear** Upwork, Freelancer.com, Fiverr ni ninguna plataforma cuyos términos lo prohíban, ni sugerir herramientas que lo hagan.
|
|
188
|
+
- **Nunca prender por defecto una fuente que exija credenciales por usuario.** Rompe el skill para todos los que instalen el perfil menos uno. Va en `KEYED_SOURCES`: se activa sola si la variable de entorno está, y si no, `keys` explica cómo conseguirla.
|
|
189
|
+
- **Nunca guardar en el registro un dato que vino del modo `web` sin confirmar.** Fecha y presupuesto ahí no están verificados; escribirlos como si lo estuvieran envenena todas las decisiones posteriores.
|
|
190
|
+
- Un presupuesto abajo del piso no se "compensa" con entusiasmo por el proyecto.
|
|
191
|
+
- En `market`, nunca reportar una mediana sin su `n`.
|
|
192
|
+
- Sin guiones largos en las notas. No crear notas para ofertas que no pasan el umbral.
|
|
193
|
+
|
|
194
|
+
## Handoff
|
|
195
|
+
|
|
196
|
+
Corrida COMPLETE cuando la cola quedó triageada y el estado de dedup actualizado. El siguiente paso es `/client-screen` sobre la primera de la cola alta. Si la corrida trajo poco o nada usable varias veces seguidas, el problema es el targeting: corré `/freelance-pipeline`.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Dedup entre corridas de /freelance-scan
|
|
2
|
+
|
|
3
|
+
Si `+job-hunt` está instalado, la fuente de verdad es `../job-scrape/references/dedup-playbook.md` y el archivo de estado es **el mismo**: no se crean dos. Este archivo es el resumen autosuficiente para quien instaló `+freelance` solo.
|
|
4
|
+
|
|
5
|
+
## Dónde vive
|
|
6
|
+
|
|
7
|
+
`<workspace>/tools/job_scraper/seen_jobs.json`, no entre las notas: es dato de máquina, y el vault de Obsidian no debería indexarlo.
|
|
8
|
+
|
|
9
|
+
## Matriz de fuente de verdad
|
|
10
|
+
|
|
11
|
+
| Dato | Fuente de verdad | Quién escribe |
|
|
12
|
+
|---|---|---|
|
|
13
|
+
| "Ya vi esta oferta" + triage + descartes pre-nota | `seen_jobs.json` | `/freelance-scan` |
|
|
14
|
+
| Estado del pipeline, `match_score` definitivo | Frontmatter de la nota en `03-Aplicaciones/` | `/client-screen` crea; `/job-outcome` actualiza |
|
|
15
|
+
| Vista humana de la cola | `00-Dashboard.md` | Derivado de las notas |
|
|
16
|
+
|
|
17
|
+
El flujo es **unidireccional**: `seen_jobs` → nota → dashboard. Cuando una entrada pasa a `nota_creada` se congela y conserva solo el puntero a la nota; el pipeline sigue en la nota y nunca se sincroniza de vuelta. Por eso `propuesta_enviada` no existe en este archivo.
|
|
18
|
+
|
|
19
|
+
## Claves de dedup
|
|
20
|
+
|
|
21
|
+
1. **URL canónica**: sin parámetros de tracking (`utm_*`, referidos), reducida al identificador de la oferta.
|
|
22
|
+
2. **`job_key`** = `slug(cliente)|slug(titulo)`: lowercase, sin acentos ni puntuación, sin sufijos legales (inc, llc, sa), sin calificadores del título entre paréntesis ("(remote)", "(urgent)"). Detecta la misma oferta con URLs distintas.
|
|
23
|
+
3. **Matching blando** (mismo cliente + alto solapamiento de tokens del título) es **warning**, nunca dedup automático: un cliente puede tener dos proyectos parecidos y legítimos.
|
|
24
|
+
|
|
25
|
+
## Reposteos: el caso propio de Upwork
|
|
26
|
+
|
|
27
|
+
Es común que un cliente cierre una oferta y la vuelva a publicar, a veces varias veces. Con URL nueva, la URL canónica no alcanza - lo detecta el `job_key`.
|
|
28
|
+
|
|
29
|
+
Cuando un `job_key` matchea una entrada en `descartado`, **no volver a triagear de cero**: avisar que ya se descartó antes y por qué, y dejar que el usuario decida si algo cambió (presupuesto corregido, alcance aclarado). Un cliente que republica la misma oferta cinco veces sin cambiar el presupuesto es en sí una señal para `/client-screen`.
|
|
30
|
+
|
|
31
|
+
## Mantenimiento
|
|
32
|
+
|
|
33
|
+
- **Punteros rotos:** al arrancar, validar que toda entrada `nota_creada` apunte a una nota que existe. Reportar las rotas, no borrarlas en silencio.
|
|
34
|
+
- **Poda (opt-in):** entradas `new` o `descartado` con `last_seen` de más de 90 días se pueden borrar, solo a pedido del usuario. Las `nota_creada` no se podan: son el historial contra el que se mide el pipeline.
|
|
35
|
+
- **Primer run:** el archivo arranca con el esqueleto vacío. Si ya hay notas en `03-Aplicaciones/` con `tipo: freelance`, ofrecer backfillearlas como `nota_creada`.
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# Plataformas freelance: qué se puede consumir y qué no
|
|
2
|
+
|
|
3
|
+
Estado verificado en agosto de 2026, corriendo las requests, no leyendo la documentación. Antes de agregar una fuente, verificar de nuevo: las plataformas cambian sin avisar.
|
|
4
|
+
|
|
5
|
+
## El criterio, y por qué es estricto
|
|
6
|
+
|
|
7
|
+
Este repo es público y lo instala cualquiera. Una fuente entra a la corrida automática solo si cumple **las tres**:
|
|
8
|
+
|
|
9
|
+
1. **Sin API key ni registro.** Si cada usuario tiene que pedir credenciales, el skill funciona para uno y está roto para el resto.
|
|
10
|
+
2. **Términos que permiten consumirla desde una herramienta local.** No alcanza con que técnicamente responda.
|
|
11
|
+
3. **Tiene trabajo freelance de verdad**, no solo empleo remoto full-time.
|
|
12
|
+
|
|
13
|
+
Que una API devuelva 200 no la habilita. Dos de las candidatas más recomendadas en internet fallan el punto 2 y quedaron afuera.
|
|
14
|
+
|
|
15
|
+
## Incluidas
|
|
16
|
+
|
|
17
|
+
### GetOnBrd (LatAm y remoto)
|
|
18
|
+
|
|
19
|
+
`https://www.getonbrd.com/api/v0` · sin key · comparte API con `/job-scrape`
|
|
20
|
+
|
|
21
|
+
- `/modalities` devuelve `1 Full time`, `2 Part time`, **`3 Freelance`**, `4 Internship`.
|
|
22
|
+
- **El filtro de modalidad no existe del lado del servidor.** Se probaron `modality`, `modality_id`, `modalities`, `modality[]`, `filter[modality]`, `modality=freelance` y `job_modality`: las siete devuelven 200 y el mismo resultado sin filtrar (48 de 50 full-time). Hay que filtrar en el cliente.
|
|
23
|
+
- **Usar `expand[]=company&expand[]=tags&expand[]=modality`.** Sin eso esos campos vienen como `{"data":{"id":...}}` y no hay nombre que mostrar. Lo aprendimos de `job-scrape/scripts/getonbrd_search.py`, que ya lo hacía.
|
|
24
|
+
- **Trampa:** con `expand[]` el id de la modalidad llega como string `"3"`; sin expand, como int `3`. Comparar el id se rompe en silencio al cambiar los parámetros. Filtrar por `attributes.locale_key == "freelance"`.
|
|
25
|
+
- Rendimiento medido: ~10 freelance cada 200 escaneados con `query=developer`.
|
|
26
|
+
|
|
27
|
+
### Himalayas (remoto global)
|
|
28
|
+
|
|
29
|
+
`https://himalayas.app/jobs/api` · sin key
|
|
30
|
+
|
|
31
|
+
- `employmentType` toma los valores `Full Time` y **`Contractor`**.
|
|
32
|
+
- **Tampoco filtra del lado del servidor:** se probaron `employmentType=Contractor`, en minúscula, `employment_type` y `type`. Las cuatro devuelven el mismo set mezclado y `totalCount` sin cambios (97.323).
|
|
33
|
+
- **`limit` se topea en 20** aunque pidas más. Paginar con `offset`.
|
|
34
|
+
- Rendimiento medido: ~28 contractor cada 120 escaneados. Sesgo a tener en cuenta: la cabeza del feed la domina una sola empresa de BPO con puestos no técnicos (transcripción, documentos, peritaje). Con filtro de query se acomoda, pero ese pool no es homogéneo y el usuario tiene que saberlo.
|
|
35
|
+
|
|
36
|
+
## Solo para inteligencia de mercado, no para buscar trabajo
|
|
37
|
+
|
|
38
|
+
### Hacker News: "Ask HN: Freelancer? Seeking freelancer?"
|
|
39
|
+
|
|
40
|
+
`https://hn.algolia.com/api/v1` · sin key · 10.000 requests por hora
|
|
41
|
+
|
|
42
|
+
El nombre engaña. Se midieron 6 hilos mensuales (marzo a agosto de 2026):
|
|
43
|
+
|
|
44
|
+
| Categoría | Comentarios |
|
|
45
|
+
|---|---|
|
|
46
|
+
| Freelancers ofreciéndose (`SEEKING WORK`) | 113 |
|
|
47
|
+
| Clientes buscando (`SEEKING FREELANCER`) | **2** |
|
|
48
|
+
| Sin seguir la convención | 6 |
|
|
49
|
+
|
|
50
|
+
Reproducible: `freelance_search.py market --months 6` devuelve ese conteo en `composition`.
|
|
51
|
+
|
|
52
|
+
Como cola de trabajo no sirve. Como muestra de **cómo se posiciona y cuánto cobra gente con tu perfil**, es lo mejor que hay gratis. Por eso vive en el subcomando `market` y no en `search`.
|
|
53
|
+
|
|
54
|
+
Rendimiento de la extracción de tarifas: **3 tarifas sobre 119 perfiles** (~3%), mediana 100 USD/hora. Poco, pero es dato real y con la cita textual al lado.
|
|
55
|
+
|
|
56
|
+
Detalles de implementación: los comentarios vienen con HTML escapado (`github/user`), y los que no siguen la convención se clasifican como `unknown` a propósito - adivinar de qué lado están mete ofertas falsas en la cola, que es peor que omitirlas.
|
|
57
|
+
|
|
58
|
+
## Excluidas, con motivo
|
|
59
|
+
|
|
60
|
+
### RemoteOK
|
|
61
|
+
|
|
62
|
+
Sus términos, que vienen **en el propio payload de la API**, exigen un backlink do-follow desde tu sitio mencionando RemoteOK como fuente, o suspenden el acceso.
|
|
63
|
+
|
|
64
|
+
Un CLI local no tiene sitio web. No podemos cumplirlo, y usarlo igual haría que le suspendan la IP al usuario. Además, de 100 ofertas revisadas **ninguna** tenía tag `contract`, `freelance` ni `part-time`. Falla el punto 2 y el punto 3.
|
|
65
|
+
|
|
66
|
+
### Remotive
|
|
67
|
+
|
|
68
|
+
Mismo problema y más restricciones: backlink do-follow obligatorio, **máximo 4 requests por día**, datos con **24 horas de retraso** deliberado, y prohibición explícita de usar los datos para captar emails. Ofrecen una API privada de pago desde 5.000 USD al mes.
|
|
69
|
+
|
|
70
|
+
Tiene `job_type` con valores `freelance` y `contract` reales, así que el punto 3 lo cumple. Los otros dos no.
|
|
71
|
+
|
|
72
|
+
### Upwork
|
|
73
|
+
|
|
74
|
+
- **API GraphQL** en `api.upwork.com/graphql` con OAuth 2.0. La key se pide desde el API Center de la cuenta (cualquier freelancer o cliente, cualquier plan - **no** hace falta cuenta de empresa) y según el propio soporte de Upwork **responden por mail en aproximadamente una semana**. El rechazo más común es data de cuenta incompleta.
|
|
75
|
+
- **Sí tiene búsqueda de ofertas** (`marketplaceJobPostingsSearch`) entre las operaciones de lectura, junto con perfiles y contratos. Lo que no tiene es escritura de propuestas.
|
|
76
|
+
- Límites si te la aprueban: 40.000 requests por día, 10 por segundo por IP.
|
|
77
|
+
- **No hay mutation para enviar una propuesta ni para gastar Connects.** Está cerrado a propósito para frenar auto-bidding.
|
|
78
|
+
- **Los RSS feeds murieron el 20 de agosto de 2024**, por la misma razón. Cualquier tutorial que los mencione está viejo.
|
|
79
|
+
- Scrapear viola los ToS y es exactamente lo que la plataforma cerró.
|
|
80
|
+
|
|
81
|
+
Por la aprobación manual por usuario **no entra a la corrida automática**: rompería el skill para todos menos quien tenga key. Si un usuario la consigue, puede escribir su propio adaptador siguiendo el patrón del script; el camino soportado es pegar la oferta a mano.
|
|
82
|
+
|
|
83
|
+
### Freelancer.com
|
|
84
|
+
|
|
85
|
+
API con OAuth 2.0 y credenciales por usuario (`Freelancer-Developer-OAuth-Client-Id`). No hay endpoint público de búsqueda de proyectos. Mismo problema estructural que Upwork.
|
|
86
|
+
|
|
87
|
+
Hay wrappers de terceros en RapidAPI que dicen exponer la búsqueda sin credenciales. No los usamos: son intermediarios no oficiales, se caen sin aviso y su forma de obtener los datos no es auditable.
|
|
88
|
+
|
|
89
|
+
### El resto de los marketplaces tipo Upwork
|
|
90
|
+
|
|
91
|
+
Distinción que importa: GetOnBrd y Himalayas son **job boards** con una modalidad freelance. Upwork es un **marketplace**: el cliente publica, vos ofertás, hay Connects y escrow. Ninguna de las fuentes incluidas es un marketplace, y no es por falta de ganas.
|
|
92
|
+
|
|
93
|
+
Probado en agosto de 2026, códigos reales:
|
|
94
|
+
|
|
95
|
+
| Plataforma | Endpoint probado | Resultado |
|
|
96
|
+
|---|---|---|
|
|
97
|
+
| Workana (LatAm) | `/jobs.rss`, `/jobs.rss?language=es`, `/api/v1/projects` | 404 en las tres. No hay feed público |
|
|
98
|
+
| Guru | `/rss/jobs/`, `api.guru.com` | 404 y DNS inexistente. Su `robots.txt` tiene `Disallow: /api/` explícito |
|
|
99
|
+
| PeoplePerHour | `/freelance-jobs.rss` | 404. Su `robots.txt` tiene `Disallow: /*?`, o sea que las URLs de búsqueda con filtros están prohibidas |
|
|
100
|
+
| Truelancer | `/api/projects` | 429 desde la primera request |
|
|
101
|
+
| Freelancermap (DACH) | `/project-rss.xml`, `/api/v1/projects` | 404 y **401**: la API existe pero pide credenciales |
|
|
102
|
+
| Malt (Europa) | `/api/missions` | **401**: misma historia |
|
|
103
|
+
| Codeable, Twine | `/api/projects`, `/api/jobs` | 404 |
|
|
104
|
+
|
|
105
|
+
Los dos 401 son el mismo problema estructural que Upwork: API real, credencial por usuario. No entran por el punto 1.
|
|
106
|
+
|
|
107
|
+
El resto no expone nada, y donde el `robots.txt` habla, habla en contra: PeoplePerHour prohíbe justo las URLs con filtros y Guru prohíbe `/api/`. Sacar los proyectos de ahí sería scrapear contra la política declarada del sitio.
|
|
108
|
+
|
|
109
|
+
**Conclusión para el diseño del perfil:** los marketplaces se usan a mano, en el navegador. El skill no busca ahí; ayuda con lo que pasa después de encontrar el posteo, que es donde se pierde la plata. Pegar la oferta a mano es el camino soportado, no un parche.
|
|
110
|
+
|
|
111
|
+
Nota lateral útil: Lemon.io y Proxify (marketplaces con screening) publican sus búsquedas en Working Nomads, 21 de 50 avisos entre las dos. No se puede filtrar por modalidad ahí, pero dice algo sobre estrategia: pasar el screening de una de esas una vez rinde más que ofertar de a una.
|
|
112
|
+
|
|
113
|
+
### Fiverr
|
|
114
|
+
|
|
115
|
+
Modelo invertido: no te postulás a trabajos, publicás gigs y el comprador te encuentra. Los "Buyer Requests" ya no existen como los conocía la gente. No hay cola que scorear.
|
|
116
|
+
|
|
117
|
+
### Toptal, Gun.io, Braintrust, Arc, Lemon.io
|
|
118
|
+
|
|
119
|
+
Verticales con screening previo. No hay board público consumible; el acceso se gana pasando su proceso de admisión. Fuera del alcance de un scanner.
|
|
120
|
+
|
|
121
|
+
### We Work Remotely
|
|
122
|
+
|
|
123
|
+
Tiene RSS y funciona (`/remote-jobs.rss`, 100 items), pero **el feed de la categoría contract redirige a sí mismo**: `/categories/remote-contract-jobs.rss` devuelve 301 apuntando a la misma URL. Sin categoría de contract usable, el feed general es empleo full-time y no aporta a este perfil.
|
|
124
|
+
|
|
125
|
+
### Arbeitnow
|
|
126
|
+
|
|
127
|
+
Sin key y funciona. Tiene `job_types` con `Contract` (7 de ~100), pero es sobre todo ATS alemán y austríaco con puestos permanentes. Marginal: si en algún momento el usuario apunta al mercado DACH, vale agregarlo.
|
|
128
|
+
|
|
129
|
+
### Jobicy
|
|
130
|
+
|
|
131
|
+
`https://jobicy.com/api/v2/remote-jobs` · sin key · responde 200 y pagina bien.
|
|
132
|
+
|
|
133
|
+
Pasa el punto 1 y, a diferencia de RemoteOK y Remotive, **pasa el punto 2**: su `friendlyNotice` pide crédito con link directo y que el botón de postularse apunte a la URL original del feed. Eso un CLI lo cumple, porque ya imprime fuente y URL, y no hay amenaza de suspensión.
|
|
134
|
+
|
|
135
|
+
Falla el punto 3, que es el que importa: sobre 100 avisos, `jobType` dio **99 Full-Time y 1 Part-Time, ningún contract ni freelance**. Pedir `jobType=contract` devuelve HTTP 400, o sea que el valor no existe. Además el pool está dominado por Customer Support (25) y Sales (19), con solo 10 de Software Engineering.
|
|
136
|
+
|
|
137
|
+
### Working Nomads
|
|
138
|
+
|
|
139
|
+
`https://www.workingnomads.com/api/exposed_jobs/` · sin key · 50 avisos por llamada.
|
|
140
|
+
|
|
141
|
+
**No tiene campo de tipo de contrato.** Las claves son `category_name`, `company_name`, `location`, `tags`, `title`, `url` y nada más, así que no hay forma de filtrar freelance: habría que adivinar por el título, y sobre 50 avisos solo 1 lo mencionaba.
|
|
142
|
+
|
|
143
|
+
Detalle que igual vale registrar: la cabeza del feed la dominan Proxify (15 de 50) y Lemon.io (6), que son marketplaces de devs freelance. O sea que buena parte de esos puestos *son* contract en la práctica, pero eso es inferencia nuestra y no dato, y esas dos ya están descartadas por screening previo. Adivinar la modalidad desde el nombre de la empresa mete ruido en la cola, que es peor que no traerla.
|
|
144
|
+
|
|
145
|
+
## El camino que sí llega a los marketplaces: WebSearch
|
|
146
|
+
|
|
147
|
+
Medido en agosto de 2026. Es la excepción al problema de credenciales, porque la credencial es Claude Code mismo: todo el que instala el perfil ya la tiene.
|
|
148
|
+
|
|
149
|
+
**Qué funciona.** `WebSearch` con `allowed_domains` devuelve URLs de proyectos reales en Upwork, Workana, Freelancer.com, PeoplePerHour y Guru. Tres búsquedas en paralelo en un turno traen candidatos de cinco plataformas a la vez, con títulos, y a veces con tarifa y stack en el resumen. Ejemplos que devolvió: `upwork.com/job/Golang-Developer_~0157b3a527d0809956/`, `workana.com/es/job/crear-tienda-shopify-ecommerce`.
|
|
150
|
+
|
|
151
|
+
**Qué no funciona, y es la mitad importante.** `WebFetch` devuelve **403 en todo**: el listado de Workana, la búsqueda de Upwork y hasta las páginas de proyecto individuales que el buscador ya tenía indexadas. Cloudflare bloquea el fetcher. O sea que se puede descubrir la oferta pero **no leerla**: presupuesto, cantidad de propuestas, país del cliente y si tiene el pago verificado siguen requiriendo abrir el link vos.
|
|
152
|
+
|
|
153
|
+
**Tres límites que hay que decir de frente:**
|
|
154
|
+
|
|
155
|
+
1. **Frescura.** El índice no filtra por fecha. Una de las ofertas de Upwork que devolvió estaba **posteada en julio de 2021**. Para un marketplace donde una oferta de tres días ya está fría, es un problema serio.
|
|
156
|
+
2. **Ruido de SEO.** Cerca de la mitad de los resultados no son ofertas: son landings de "Hire Golang developers", tablas de tarifas y gigs del Project Catalog. Hay que descartarlos por patrón de URL.
|
|
157
|
+
3. **El resumen no es dato.** Lo que devuelve la búsqueda es un modelo resumiendo varias páginas: puede mezclar detalles de dos ofertas. Sirve como pista para abrir, no como campo para guardar.
|
|
158
|
+
|
|
159
|
+
**Conclusión de diseño.** Da para descubrimiento y triage, no para screening ni para una cola confiable. Un skill que haga esto tiene que entregar links para abrir y decir explícitamente que la fecha no está verificada; guardar esos datos en el registro como si fueran ciertos sería peor que no tenerlos.
|
|
160
|
+
|
|
161
|
+
## Si querés agregar una fuente
|
|
162
|
+
|
|
163
|
+
1. **Correr la request y mirar el payload**, no la documentación. Las dos APIs incluidas mienten sobre sus propios filtros.
|
|
164
|
+
2. **Leer los términos, incluido lo que venga dentro de la respuesta.** RemoteOK y Remotive ponen ahí las condiciones que las descalifican.
|
|
165
|
+
3. **Medir el rendimiento freelance real** sobre una muestra y anotarlo acá. Una fuente con 2 ofertas útiles cada 500 no justifica la request.
|
|
166
|
+
4. **Confirmar que no necesita credenciales por usuario.** Si las necesita, va como opt-in documentado, no en `SOURCES`.
|
|
167
|
+
5. **Agregar los specs** en `tests/freelance/test_freelance_search.py` con un payload fijo. Nada de tests contra la red.
|