claude-code-arcane 1.2.0 → 1.4.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 +16 -0
- package/README.md +5 -2
- package/agents/ecommerce/ecommerce-architect.md +60 -0
- package/agents/engineering/dotnet-engineer.md +78 -0
- package/docs/SKILLS-CATALOG.md +45 -3
- package/docs/USER-GUIDE.md +2 -2
- package/package.json +1 -1
- package/profiles/backend-dotnet.yaml +54 -0
- package/profiles/ecommerce.yaml +48 -0
- package/profiles/job-hunt.yaml +35 -0
- package/profiles/unity-design.yaml +1 -0
- package/profiles/unity-dev.yaml +1 -0
- package/rules/dotnet-code.md +64 -0
- package/skills/cart-checkout/SKILL.md +85 -0
- package/skills/cart-checkout/references/checkout-flow-spec.md +50 -0
- package/skills/cold-outreach/SKILL.md +65 -0
- package/skills/cold-outreach/references/recruiter-playbook.md +65 -0
- package/skills/commerce-analytics/SKILL.md +85 -0
- package/skills/commerce-analytics/references/ga4-ecommerce-events.md +63 -0
- package/skills/commerce-analytics/scripts/ga4_ecommerce_validate.py +134 -0
- package/skills/commerce-data-model/SKILL.md +84 -0
- package/skills/commerce-data-model/references/data-model-spec.md +79 -0
- package/skills/cover-letter/SKILL.md +66 -0
- package/skills/cv-ats-export/SKILL.md +64 -0
- package/skills/cv-ats-export/scripts/cv_export.py +306 -0
- package/skills/cv-tailor/SKILL.md +70 -0
- package/skills/cv-tailor/references/ats-keywords.md +46 -0
- package/skills/dotnet-architecture/SKILL.md +66 -0
- package/skills/dotnet-architecture/references/anti-patterns.md +12 -0
- package/skills/dotnet-architecture/references/checklist.md +19 -0
- package/skills/dotnet-architecture/references/patterns.md +118 -0
- package/skills/dotnet-architecture/references/project-structure.md +78 -0
- package/skills/dotnet-best-practices/SKILL.md +76 -0
- package/skills/dotnet-best-practices/references/api-design.md +75 -0
- package/skills/dotnet-best-practices/references/architecture.md +62 -0
- package/skills/dotnet-best-practices/references/async.md +62 -0
- package/skills/dotnet-best-practices/references/database.md +69 -0
- package/skills/dotnet-best-practices/references/dependency-injection.md +73 -0
- package/skills/dotnet-best-practices/references/devops.md +76 -0
- package/skills/dotnet-best-practices/references/error-handling.md +72 -0
- package/skills/dotnet-best-practices/references/performance.md +63 -0
- package/skills/dotnet-best-practices/references/security.md +73 -0
- package/skills/dotnet-best-practices/references/testing.md +76 -0
- package/skills/dotnet-scaffold/SKILL.md +99 -0
- package/skills/install-mcp/SKILL.md +107 -0
- package/skills/install-mcp/references/manual-setup.md +92 -0
- package/skills/interview-prep/SKILL.md +69 -0
- package/skills/interview-prep/references/star-framework.md +42 -0
- package/skills/inventory-stock/SKILL.md +86 -0
- package/skills/inventory-stock/scripts/restock_forecast.py +173 -0
- package/skills/job-hunt/SKILL.md +92 -0
- package/skills/job-hunt/references/templates/Aplicacion.md +48 -0
- package/skills/job-hunt/references/templates/CV Custom.md +53 -0
- package/skills/job-hunt/references/templates/Contacto.md +30 -0
- package/skills/job-hunt/references/templates/Dashboard.md +45 -0
- package/skills/job-hunt/references/templates/Empresa.md +36 -0
- package/skills/job-hunt/references/templates/Entrevista.md +44 -0
- package/skills/job-hunt/references/templates/Perfil.md +38 -0
- package/skills/job-search/SKILL.md +83 -0
- package/skills/job-search/references/scoring-rubric.md +43 -0
- package/skills/linkedin-optimize/SKILL.md +79 -0
- package/skills/master-profile/SKILL.md +69 -0
- package/skills/merchant-feed/SKILL.md +85 -0
- package/skills/merchant-feed/references/feed-attribute-map.md +47 -0
- package/skills/merchant-feed/scripts/feed_validate.py +140 -0
- package/skills/network-map/SKILL.md +61 -0
- package/skills/network-map/scripts/network_map.py +109 -0
- package/skills/order-lifecycle/SKILL.md +86 -0
- package/skills/order-lifecycle/references/order-state-machine.md +53 -0
- package/skills/payments-architecture/SKILL.md +80 -0
- package/skills/personal-brand/SKILL.md +54 -0
- package/skills/personal-brand/references/post-pillars.md +66 -0
- package/skills/portfolio-site/SKILL.md +59 -0
- package/skills/product-listing/SKILL.md +84 -0
- package/skills/promotions-discounts/SKILL.md +80 -0
- package/skills/shipping-tax/SKILL.md +85 -0
- package/skills/storefront-architecture/SKILL.md +80 -0
- package/skills/subscription-billing/SKILL.md +81 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cover-letter
|
|
3
|
+
description: "Write concise, confident cover letters and application messages tailored to the company and the person who will read them. Triggers: cover letter, carta de presentacion, mensaje de aplicacion, application message, carta de motivacion, why I'm a fit."
|
|
4
|
+
argument-hint: "[application-note | company + role]"
|
|
5
|
+
category: "career"
|
|
6
|
+
user-invocable: true
|
|
7
|
+
allowed-tools: Read, Glob, Grep, Write, Edit, WebFetch
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Cover Letter — Mensaje de aplicación de élite
|
|
11
|
+
|
|
12
|
+
Escribís cover letters y mensajes de aplicación **concisos, seguros y custom** — que suenan a una persona que entendió el rol y la empresa, no a una plantilla. El objetivo: que el lector quiera leer el CV.
|
|
13
|
+
|
|
14
|
+
## Inputs
|
|
15
|
+
|
|
16
|
+
- **Rol y empresa** + el job description.
|
|
17
|
+
- **Perfil base / maestro** (`01-Perfiles/`) para los logros y el ángulo.
|
|
18
|
+
- **Research** de `04-Empresas/` y del contacto en `05-Contactos/` (a quién le escribís cambia el tono).
|
|
19
|
+
- El **canal**: cover letter formal (campo del ATS), mensaje corto de LinkedIn/email, o párrafo de "why you" en un form.
|
|
20
|
+
|
|
21
|
+
## Principios
|
|
22
|
+
|
|
23
|
+
- **Una página máximo; idealmente 150–250 palabras** para mensajes, 250–350 para carta formal.
|
|
24
|
+
- **El primer renglón gana o pierde.** Nada de "I am writing to apply for…". Abrí con un gancho específico: un logro relevante, un punto de conexión con la empresa, o por qué este rol te mueve.
|
|
25
|
+
- **Mostrá fit, no historia.** 1–2 logros que mapean directo a lo que piden, con número.
|
|
26
|
+
- **Específico de la empresa.** Una frase que demuestre que investigaste (producto, valor, problema que resuelven). Si la podés pegar en otra aplicación, no sirve.
|
|
27
|
+
- **Confiado, no arrogante; cálido, no servil.** Ni "would be honored", ni "I'm the best candidate".
|
|
28
|
+
- **CTA simple al final.** Disponibilidad / ganas de charlar, sin rogar.
|
|
29
|
+
|
|
30
|
+
## Estructura (carta formal)
|
|
31
|
+
|
|
32
|
+
1. **Hook** — gancho específico (logro o conexión real).
|
|
33
|
+
2. **Fit** — 1–2 logros mapeados a must-haves del JD, con métrica.
|
|
34
|
+
3. **Empresa** — por qué *esta* empresa/rol (research real).
|
|
35
|
+
4. **Cierre** — CTA breve + gracias.
|
|
36
|
+
|
|
37
|
+
## Estructura (mensaje corto / LinkedIn / email)
|
|
38
|
+
|
|
39
|
+
- Saludo personal (nombre del contacto si lo tenés).
|
|
40
|
+
- 1 oración: quién sos + el logro más relevante.
|
|
41
|
+
- 1 oración: por qué esta empresa/rol.
|
|
42
|
+
- 1 oración: CTA (charlar / te dejo el CV).
|
|
43
|
+
|
|
44
|
+
## Proceso
|
|
45
|
+
|
|
46
|
+
1. Leer JD + research. Identificar los 2 must-haves donde más brillás.
|
|
47
|
+
2. Elegir formato según canal.
|
|
48
|
+
3. Draftear. Después **cortar sin piedad** todo lo genérico.
|
|
49
|
+
4. Generar 2 variantes de apertura si el usuario quiere elegir.
|
|
50
|
+
5. Guardar en la nota de aplicación (`03-Aplicaciones/`, sección cover letter) o en `07-Recursos/` si es reutilizable.
|
|
51
|
+
|
|
52
|
+
## Idioma
|
|
53
|
+
|
|
54
|
+
Inglés para roles internacionales; español si la empresa es hispanohablante. Espejá el idioma del JD ante la duda.
|
|
55
|
+
|
|
56
|
+
## Anti-patrones (cortar siempre)
|
|
57
|
+
|
|
58
|
+
- "I am writing to express my interest in…" / "Por medio de la presente…".
|
|
59
|
+
- Recitar el CV en prosa.
|
|
60
|
+
- Adjetivos vacíos sin evidencia (passionate, hardworking, team player).
|
|
61
|
+
- Párrafos genéricos que sirven para cualquier empresa.
|
|
62
|
+
- Pedir disculpas o sonar desesperado.
|
|
63
|
+
|
|
64
|
+
## Handoff
|
|
65
|
+
|
|
66
|
+
Pedí aprobación (approval) antes de guardar la carta en la nota de aplicación o en `07-Recursos/`. Cuando la cover letter está READY, el siguiente paso es `/cold-outreach` si hay un contacto al que escribirle directo.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cv-ats-export
|
|
3
|
+
description: "Export a Markdown CV to an ATS-compliant PDF (single column, selectable text, A4) using headless Chrome/Edge — no Obsidian or design tool needed. Triggers: exportar CV a PDF, generar PDF del CV, CV ATS PDF, convertir CV markdown a PDF, imprimir CV."
|
|
4
|
+
argument-hint: "[cv-name | all] [--workspace <path>]"
|
|
5
|
+
category: "career"
|
|
6
|
+
user-invocable: true
|
|
7
|
+
allowed-tools: Read, Glob, Grep, Bash, Write
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# CV ATS Export — Markdown → PDF ATS-compliant
|
|
11
|
+
|
|
12
|
+
Convertís un CV en Markdown a un **PDF que pasa filtros ATS**: una sola columna, texto seleccionable (no imagen), A4, tipografía limpia. Usa Chrome o Edge en modo headless — no requiere Obsidian, LaTeX ni herramienta de diseño.
|
|
13
|
+
|
|
14
|
+
El motor es `scripts/cv_export.py`. La primera vez, instalalo en el workspace (`tools/cv_export.py`) para que quede versionado con los CVs; o corrélo directo desde el skill.
|
|
15
|
+
|
|
16
|
+
## Requisitos
|
|
17
|
+
|
|
18
|
+
- **Python 3** en el PATH.
|
|
19
|
+
- **Google Chrome o Microsoft Edge** instalado (el script autodetecta rutas estándar en Windows/macOS/Linux).
|
|
20
|
+
- CVs en Markdown dentro de `02-CVs/` del career workspace, con headers (`# Nombre`, `## Sección`), bullets y `---` como separadores.
|
|
21
|
+
|
|
22
|
+
## Uso
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
# Todos los CVs base (CV - *.md) del workspace
|
|
26
|
+
python scripts/cv_export.py
|
|
27
|
+
|
|
28
|
+
# Uno solo (con o sin .md)
|
|
29
|
+
python scripts/cv_export.py "CV - Acme - Backend"
|
|
30
|
+
|
|
31
|
+
# Workspace explícito
|
|
32
|
+
python scripts/cv_export.py --workspace ./career-workspace "CV - Acme - Backend"
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
El script:
|
|
36
|
+
1. Localiza el workspace (flag `--workspace`, env `CAREER_WORKSPACE`, o sube directorios buscando una carpeta `02-CVs`, o usa el cwd).
|
|
37
|
+
2. Lee el `.md`, separa frontmatter, convierte el cuerpo a HTML con el CSS ATS embebido.
|
|
38
|
+
3. Imprime a PDF con Chrome/Edge headless (`--headless=new --print-to-pdf`).
|
|
39
|
+
4. Guarda en `02-CVs/exports/`. El nombre sale del frontmatter `archivo_pdf` si existe; si no, del nombre del `.md` (sin el prefijo `CV - `).
|
|
40
|
+
|
|
41
|
+
## Markdown soportado
|
|
42
|
+
|
|
43
|
+
`# ## ### títulos` · `**negrita**` · `*itálica*` · `[texto](url)` · `- bullets` · `---` (regla horizontal). Un salto de línea simple dentro de un párrafo se renderiza como `<br>` (comportamiento tipo Obsidian).
|
|
44
|
+
|
|
45
|
+
## Por qué este pipeline (y no Word/Canva)
|
|
46
|
+
|
|
47
|
+
- **Texto seleccionable garantizado** → el ATS puede parsearlo (a diferencia de un PDF exportado como imagen).
|
|
48
|
+
- **Una columna, sin tablas** → no se rompe el parseo.
|
|
49
|
+
- **Reproducible y versionable** → el CV vive en Markdown (diffeable, en git), el PDF es un artefacto generado.
|
|
50
|
+
- **Mismo estilo siempre** → el CSS ATS está embebido en el script.
|
|
51
|
+
|
|
52
|
+
## Personalizar el estilo
|
|
53
|
+
|
|
54
|
+
El CSS ATS está en la constante `CSS` dentro de `scripts/cv_export.py` (tamaño de fuente, márgenes A4, estilos de h1/h2). Editá ahí si querés ajustar tipografía o espaciado — mantené una sola columna y texto real.
|
|
55
|
+
|
|
56
|
+
## Reglas
|
|
57
|
+
|
|
58
|
+
- No metas tablas, columnas ni imágenes en el CV: rompen el parseo ATS.
|
|
59
|
+
- Verificá el PDF generado: que el texto sea seleccionable y entre en 1–2 páginas.
|
|
60
|
+
- No commitees PDFs con datos sensibles a un remoto sin confirmación.
|
|
61
|
+
|
|
62
|
+
## Handoff
|
|
63
|
+
|
|
64
|
+
Confirmá (approval) antes de instalar `cv_export.py` en el workspace o sobrescribir PDFs. Cuando el PDF está READY y verificado, el siguiente paso suele ser `/cover-letter` para el mensaje de aplicación.
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
"""
|
|
2
|
+
cv_export.py — Export Markdown CVs to ATS-compliant PDF.
|
|
3
|
+
|
|
4
|
+
Converts a Markdown CV to styled HTML and prints it with headless Chrome/Edge
|
|
5
|
+
(selectable text, single column, A4). No Obsidian, LaTeX or design tool needed.
|
|
6
|
+
|
|
7
|
+
Usage (from anywhere; the script locates the career workspace):
|
|
8
|
+
python cv_export.py # all base CVs (CV - *.md)
|
|
9
|
+
python cv_export.py "CV - Acme - Backend" # a single CV (with/without .md)
|
|
10
|
+
python cv_export.py --workspace ./career-workspace "CV - Acme"
|
|
11
|
+
|
|
12
|
+
Workspace resolution order:
|
|
13
|
+
1. --workspace <path> flag
|
|
14
|
+
2. CAREER_WORKSPACE env var
|
|
15
|
+
3. walk up from the cwd looking for a folder containing "02-CVs" (or "02 - CVs")
|
|
16
|
+
4. the current working directory
|
|
17
|
+
|
|
18
|
+
Output path:
|
|
19
|
+
- If frontmatter has `archivo_pdf`, the PDF is written to that path
|
|
20
|
+
relative to the CVs folder (<workspace>/02-CVs/<archivo_pdf>).
|
|
21
|
+
- Otherwise it goes to <workspace>/02-CVs/exports/<name>.pdf, where
|
|
22
|
+
<name> is the frontmatter field `archivo_final` if present, else the
|
|
23
|
+
.md filename with the "CV - " prefix stripped.
|
|
24
|
+
|
|
25
|
+
Supported Markdown (what CVs use):
|
|
26
|
+
# ## ### headings · **bold** · *italic* · [text](url) · - bullets · ---
|
|
27
|
+
A single line break inside a paragraph becomes <br> (Obsidian-like).
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
import html
|
|
31
|
+
import os
|
|
32
|
+
import re
|
|
33
|
+
import subprocess
|
|
34
|
+
import sys
|
|
35
|
+
import tempfile
|
|
36
|
+
from pathlib import Path
|
|
37
|
+
|
|
38
|
+
# Possible names for the CVs folder (portable + Obsidian-style with spaces).
|
|
39
|
+
CV_DIR_NAMES = ["02-CVs", "02 - CVs"]
|
|
40
|
+
|
|
41
|
+
BROWSERS = [
|
|
42
|
+
# Windows
|
|
43
|
+
r"C:\Program Files\Google\Chrome\Application\chrome.exe",
|
|
44
|
+
r"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe",
|
|
45
|
+
r"C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe",
|
|
46
|
+
r"C:\Program Files\Microsoft\Edge\Application\msedge.exe",
|
|
47
|
+
# macOS
|
|
48
|
+
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
|
49
|
+
"/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
|
|
50
|
+
# Linux
|
|
51
|
+
"/usr/bin/google-chrome",
|
|
52
|
+
"/usr/bin/google-chrome-stable",
|
|
53
|
+
"/usr/bin/chromium",
|
|
54
|
+
"/usr/bin/chromium-browser",
|
|
55
|
+
"/usr/bin/microsoft-edge",
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
# ATS-friendly print style: single column, selectable text, A4.
|
|
59
|
+
CSS = """
|
|
60
|
+
@page { size: A4; margin: 18mm 16mm 18mm 16mm; }
|
|
61
|
+
body {
|
|
62
|
+
font-family: "Calibri", "Arial", "Helvetica", sans-serif;
|
|
63
|
+
font-size: 10.5pt; line-height: 1.35; color: #000; background: #fff;
|
|
64
|
+
margin: 0; padding: 0;
|
|
65
|
+
}
|
|
66
|
+
h1 { font-size: 18pt; font-weight: 700; margin: 0 0 2pt 0; padding: 0; }
|
|
67
|
+
h2 {
|
|
68
|
+
font-size: 12pt; font-weight: 700; text-transform: uppercase;
|
|
69
|
+
letter-spacing: 0.5pt; border-bottom: 0.5pt solid #000;
|
|
70
|
+
padding: 0 0 1pt 0; margin: 10pt 0 5pt 0;
|
|
71
|
+
page-break-after: avoid;
|
|
72
|
+
}
|
|
73
|
+
h3 { font-size: 11pt; font-weight: 700; margin: 6pt 0 2pt 0; padding: 0; page-break-after: avoid; }
|
|
74
|
+
p { margin: 2pt 0; page-break-inside: avoid; }
|
|
75
|
+
ul, ol { margin: 2pt 0 4pt 0; padding-left: 16pt; }
|
|
76
|
+
li { margin: 1pt 0; page-break-inside: avoid; }
|
|
77
|
+
strong { font-weight: 700; }
|
|
78
|
+
a { color: #000; text-decoration: none; }
|
|
79
|
+
hr { border: none; border-top: 0.5pt solid #000; margin: 6pt 0; }
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def find_cv_dir(start):
|
|
84
|
+
"""Walk up from `start` looking for a directory containing a CVs folder."""
|
|
85
|
+
cur = Path(start).resolve()
|
|
86
|
+
for base in [cur, *cur.parents]:
|
|
87
|
+
for name in CV_DIR_NAMES:
|
|
88
|
+
cand = base / name
|
|
89
|
+
if cand.is_dir():
|
|
90
|
+
return cand
|
|
91
|
+
return None
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def resolve_cv_dir(workspace_arg):
|
|
95
|
+
# 1. explicit --workspace
|
|
96
|
+
if workspace_arg:
|
|
97
|
+
ws = Path(workspace_arg).resolve()
|
|
98
|
+
for name in CV_DIR_NAMES:
|
|
99
|
+
if (ws / name).is_dir():
|
|
100
|
+
return ws / name
|
|
101
|
+
# maybe they passed the CVs dir directly
|
|
102
|
+
if ws.name in CV_DIR_NAMES and ws.is_dir():
|
|
103
|
+
return ws
|
|
104
|
+
print(f"[ERROR] No CVs folder ({' / '.join(CV_DIR_NAMES)}) under: {ws}")
|
|
105
|
+
sys.exit(1)
|
|
106
|
+
# 2. env var
|
|
107
|
+
env = os.environ.get("CAREER_WORKSPACE")
|
|
108
|
+
if env:
|
|
109
|
+
for name in CV_DIR_NAMES:
|
|
110
|
+
if (Path(env) / name).is_dir():
|
|
111
|
+
return Path(env).resolve() / name
|
|
112
|
+
# 3. walk up from cwd
|
|
113
|
+
found = find_cv_dir(Path.cwd())
|
|
114
|
+
if found:
|
|
115
|
+
return found
|
|
116
|
+
# 4. cwd fallback
|
|
117
|
+
print("[ERROR] Could not locate a CVs folder. Use --workspace <path>.")
|
|
118
|
+
sys.exit(1)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def strip_frontmatter(text):
|
|
122
|
+
"""Return (frontmatter_dict, body). Flat key: value frontmatter."""
|
|
123
|
+
fm = {}
|
|
124
|
+
if text.startswith("---"):
|
|
125
|
+
end = text.find("\n---", 3)
|
|
126
|
+
if end != -1:
|
|
127
|
+
for line in text[3:end].strip().splitlines():
|
|
128
|
+
if ":" in line:
|
|
129
|
+
k, _, v = line.partition(":")
|
|
130
|
+
fm[k.strip()] = v.strip().strip('"')
|
|
131
|
+
text = text[end + 4:]
|
|
132
|
+
return fm, text.lstrip("\n")
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def inline(s):
|
|
136
|
+
"""Inline markdown -> HTML (escape first, then links/bold/italic)."""
|
|
137
|
+
s = html.escape(s, quote=False)
|
|
138
|
+
s = re.sub(r"\[([^\]]+)\]\(([^)]+)\)", r'<a href="\2">\1</a>', s)
|
|
139
|
+
s = re.sub(r"\*\*(.+?)\*\*", r"<strong>\1</strong>", s)
|
|
140
|
+
s = re.sub(r"(?<!\*)\*([^*\n]+)\*(?!\*)", r"<em>\1</em>", s)
|
|
141
|
+
return s
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def md_to_html(md):
|
|
145
|
+
out = []
|
|
146
|
+
paragraph = []
|
|
147
|
+
in_list = False
|
|
148
|
+
|
|
149
|
+
def flush_paragraph():
|
|
150
|
+
if paragraph:
|
|
151
|
+
out.append("<p>" + "<br>".join(inline(l) for l in paragraph) + "</p>")
|
|
152
|
+
paragraph.clear()
|
|
153
|
+
|
|
154
|
+
def close_list():
|
|
155
|
+
nonlocal in_list
|
|
156
|
+
if in_list:
|
|
157
|
+
out.append("</ul>")
|
|
158
|
+
in_list = False
|
|
159
|
+
|
|
160
|
+
for raw in md.splitlines():
|
|
161
|
+
line = raw.rstrip()
|
|
162
|
+
stripped = line.strip()
|
|
163
|
+
|
|
164
|
+
if not stripped:
|
|
165
|
+
flush_paragraph()
|
|
166
|
+
close_list()
|
|
167
|
+
continue
|
|
168
|
+
|
|
169
|
+
m = re.match(r"^(#{1,3})\s+(.*)", stripped)
|
|
170
|
+
if m:
|
|
171
|
+
flush_paragraph()
|
|
172
|
+
close_list()
|
|
173
|
+
level = len(m.group(1))
|
|
174
|
+
out.append(f"<h{level}>{inline(m.group(2))}</h{level}>")
|
|
175
|
+
continue
|
|
176
|
+
|
|
177
|
+
if re.match(r"^(-{3,}|\*{3,})$", stripped):
|
|
178
|
+
flush_paragraph()
|
|
179
|
+
close_list()
|
|
180
|
+
out.append("<hr>")
|
|
181
|
+
continue
|
|
182
|
+
|
|
183
|
+
m = re.match(r"^[-*]\s+(.*)", stripped)
|
|
184
|
+
if m:
|
|
185
|
+
flush_paragraph()
|
|
186
|
+
if not in_list:
|
|
187
|
+
out.append("<ul>")
|
|
188
|
+
in_list = True
|
|
189
|
+
out.append(f"<li>{inline(m.group(1))}</li>")
|
|
190
|
+
continue
|
|
191
|
+
|
|
192
|
+
close_list()
|
|
193
|
+
paragraph.append(stripped)
|
|
194
|
+
|
|
195
|
+
flush_paragraph()
|
|
196
|
+
close_list()
|
|
197
|
+
return "\n".join(out)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def find_browser():
|
|
201
|
+
for path in BROWSERS:
|
|
202
|
+
if Path(path).exists():
|
|
203
|
+
return path
|
|
204
|
+
return None
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def export(md_path, browser, cv_dir, export_dir):
|
|
208
|
+
text = md_path.read_text(encoding="utf-8")
|
|
209
|
+
fm, body = strip_frontmatter(text)
|
|
210
|
+
|
|
211
|
+
if fm.get("archivo_pdf"):
|
|
212
|
+
pdf_path = cv_dir / fm["archivo_pdf"]
|
|
213
|
+
else:
|
|
214
|
+
name = fm.get("archivo_final") or md_path.stem.replace("CV - ", "")
|
|
215
|
+
pdf_path = export_dir / f"{name}.pdf"
|
|
216
|
+
pdf_path.parent.mkdir(parents=True, exist_ok=True)
|
|
217
|
+
|
|
218
|
+
doc = (
|
|
219
|
+
"<!DOCTYPE html><html><head><meta charset='utf-8'>"
|
|
220
|
+
f"<title>{html.escape(md_path.stem)}</title>"
|
|
221
|
+
f"<style>{CSS}</style></head><body>"
|
|
222
|
+
+ md_to_html(body)
|
|
223
|
+
+ "</body></html>"
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
with tempfile.NamedTemporaryFile(
|
|
227
|
+
"w", suffix=".html", delete=False, encoding="utf-8"
|
|
228
|
+
) as tmp:
|
|
229
|
+
tmp.write(doc)
|
|
230
|
+
tmp_path = Path(tmp.name)
|
|
231
|
+
|
|
232
|
+
try:
|
|
233
|
+
result = subprocess.run(
|
|
234
|
+
[
|
|
235
|
+
browser,
|
|
236
|
+
"--headless=new",
|
|
237
|
+
"--disable-gpu",
|
|
238
|
+
"--no-pdf-header-footer",
|
|
239
|
+
f"--print-to-pdf={pdf_path}",
|
|
240
|
+
tmp_path.as_uri(),
|
|
241
|
+
],
|
|
242
|
+
capture_output=True,
|
|
243
|
+
text=True,
|
|
244
|
+
timeout=120,
|
|
245
|
+
)
|
|
246
|
+
if result.returncode != 0 or not pdf_path.exists():
|
|
247
|
+
print(f"[ERROR] {md_path.name}: browser exit {result.returncode}")
|
|
248
|
+
print(result.stderr[-500:] if result.stderr else "(no stderr)")
|
|
249
|
+
return False
|
|
250
|
+
finally:
|
|
251
|
+
tmp_path.unlink(missing_ok=True)
|
|
252
|
+
|
|
253
|
+
size_kb = pdf_path.stat().st_size // 1024
|
|
254
|
+
print(f"[OK] {md_path.name} -> {pdf_path} ({size_kb} KB)")
|
|
255
|
+
return True
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def parse_args(argv):
|
|
259
|
+
workspace = None
|
|
260
|
+
names = []
|
|
261
|
+
i = 0
|
|
262
|
+
while i < len(argv):
|
|
263
|
+
a = argv[i]
|
|
264
|
+
if a == "--workspace":
|
|
265
|
+
i += 1
|
|
266
|
+
workspace = argv[i] if i < len(argv) else None
|
|
267
|
+
elif a.startswith("--workspace="):
|
|
268
|
+
workspace = a.split("=", 1)[1]
|
|
269
|
+
else:
|
|
270
|
+
names.append(a)
|
|
271
|
+
i += 1
|
|
272
|
+
return workspace, names
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def main():
|
|
276
|
+
workspace, names = parse_args(sys.argv[1:])
|
|
277
|
+
|
|
278
|
+
browser = find_browser()
|
|
279
|
+
if not browser:
|
|
280
|
+
print("[ERROR] Chrome/Edge not found in standard paths. Install one or edit BROWSERS.")
|
|
281
|
+
sys.exit(1)
|
|
282
|
+
|
|
283
|
+
cv_dir = resolve_cv_dir(workspace)
|
|
284
|
+
export_dir = cv_dir / "exports"
|
|
285
|
+
|
|
286
|
+
if names:
|
|
287
|
+
targets = []
|
|
288
|
+
for n in names:
|
|
289
|
+
p = cv_dir / (n if n.endswith(".md") else n + ".md")
|
|
290
|
+
if not p.exists():
|
|
291
|
+
print(f"[ERROR] Not found: {p}")
|
|
292
|
+
sys.exit(1)
|
|
293
|
+
targets.append(p)
|
|
294
|
+
else:
|
|
295
|
+
targets = sorted(cv_dir.glob("CV - *.md"))
|
|
296
|
+
|
|
297
|
+
if not targets:
|
|
298
|
+
print(f"[ERROR] No CVs (CV - *.md) found in {cv_dir}.")
|
|
299
|
+
sys.exit(1)
|
|
300
|
+
|
|
301
|
+
ok = all([export(t, browser, cv_dir, export_dir) for t in targets])
|
|
302
|
+
sys.exit(0 if ok else 1)
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
if __name__ == "__main__":
|
|
306
|
+
main()
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cv-tailor
|
|
3
|
+
description: "Tailor a CV to a specific job description: extract ATS keywords, decide what to emphasize vs downplay, and rewrite achievement bullets in the offer's vocabulary, starting from a role profile. Triggers: adaptar CV, custom CV, tailor resume, CV para esta oferta, optimizar CV para ATS, matchear CV con job description."
|
|
4
|
+
argument-hint: "[job-url | path-to-jd | application-note]"
|
|
5
|
+
category: "career"
|
|
6
|
+
user-invocable: true
|
|
7
|
+
allowed-tools: Read, Glob, Grep, Write, Edit, WebFetch
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# CV Tailor — Adaptar el CV a la oferta
|
|
11
|
+
|
|
12
|
+
Tomás un job description y el perfil base del usuario, y producís un **CV custom** que matchea con los filtros automáticos (ATS) y con la lectura humana del hiring manager. No reescribís la verdad — la reorganizás y la traducís al vocabulario de la oferta.
|
|
13
|
+
|
|
14
|
+
## Inputs
|
|
15
|
+
|
|
16
|
+
- **El job description** (URL → usá WebFetch, o texto pegado, o una nota de `03-Aplicaciones/`).
|
|
17
|
+
- **El perfil base**: `01-Perfiles/<Role>.md` (derivado del maestro vía `/master-profile derive`). Si no existe, sugerir crearlo.
|
|
18
|
+
- **Research del contacto/empresa** si existe en `04-Empresas/` y `05-Contactos/`.
|
|
19
|
+
|
|
20
|
+
## Proceso
|
|
21
|
+
|
|
22
|
+
### 1. Analizar la oferta
|
|
23
|
+
- Extraer **keywords literales** del JD: tecnologías, metodologías, responsabilidades, soft skills, seniority. Literal importa — el ATS hace matching de strings (ej: si piden "React.js" no asumas que "React" matchea igual).
|
|
24
|
+
- Detectar **must-have vs nice-to-have**.
|
|
25
|
+
- Detectar **señales de cultura / ángulo** (startup vs enterprise, code-forward, ownership, etc.).
|
|
26
|
+
- Ver `references/ats-keywords.md` para cómo extraer y mapear keywords.
|
|
27
|
+
|
|
28
|
+
### 2. Mapear contra el perfil
|
|
29
|
+
- Por cada must-have: ¿qué experiencia/proyecto del perfil lo cubre? Marcar gaps honestos.
|
|
30
|
+
- Decidir **qué resaltar** (lo que más alinea, arriba y con más espacio) y **qué bajar de volumen** (lo irrelevante para este rol).
|
|
31
|
+
|
|
32
|
+
### 3. Reescribir
|
|
33
|
+
- Reordenar experiencia y proyectos hacia el rol.
|
|
34
|
+
- Reescribir bullets de logros usando el vocabulario de la oferta, manteniendo número→contexto→resultado.
|
|
35
|
+
- Inyectar keywords de forma natural (en bullets reales, no en un "keyword stuffing" detectable).
|
|
36
|
+
- Ajustar el headline/summary al rol exacto.
|
|
37
|
+
|
|
38
|
+
### 4. Score before/after
|
|
39
|
+
- Calcular un **match rate** aproximado: % de keywords/requisitos del JD presentes en el CV, **antes** vs **después** del tailoring.
|
|
40
|
+
- Reportar el delta ("match 48% → 81%") y qué keywords se cubrieron.
|
|
41
|
+
- **Flag de keyword stuffing:** marcar cualquier inserción que a un revisor humano le suene forzada o poco natural — el objetivo es pasar el ATS *y* leer bien para una persona.
|
|
42
|
+
|
|
43
|
+
### 5. Producir el CV custom
|
|
44
|
+
- Crear/actualizar la nota en `02-CVs/CV - <Empresa> - <Rol>.md` usando el template `CV Custom` (`Templates/CV Custom.md`).
|
|
45
|
+
- Completar frontmatter (`perfil_base`, `aplicacion`, `empresa`, `archivo_pdf`).
|
|
46
|
+
- Documentar las decisiones (keywords, qué se resaltó/bajó) en las secciones del template — sirve para la entrevista y para iterar.
|
|
47
|
+
- El cuerpo del CV en sí debe quedar listo para `/cv-ats-export` (markdown limpio: `# nombre`, `## secciones`, bullets, `---`).
|
|
48
|
+
|
|
49
|
+
## Formato del CV (ATS-friendly)
|
|
50
|
+
|
|
51
|
+
- **Una columna.** Sin tablas, sin cajas de texto, sin columnas — los ATS las leen mal.
|
|
52
|
+
- Secciones estándar con headers claros: Summary, Experience, Skills, Projects, Education.
|
|
53
|
+
- Fechas consistentes (MM/YYYY).
|
|
54
|
+
- Sin gráficos ni imágenes para el contenido crítico.
|
|
55
|
+
- Verbos de acción + métricas.
|
|
56
|
+
|
|
57
|
+
## Idioma
|
|
58
|
+
|
|
59
|
+
CV en **inglés** para roles internacionales/remotos globales; **español** si la oferta/empresa es hispanohablante. Ante la duda, preguntar.
|
|
60
|
+
|
|
61
|
+
## Reglas
|
|
62
|
+
|
|
63
|
+
- Nunca inventar experiencia, fechas o métricas. Tailoring = reorganizar y traducir, no mentir.
|
|
64
|
+
- No keyword-stuffing: cada keyword debe vivir en un bullet verdadero.
|
|
65
|
+
- Un CV custom por postulación — no pisar el perfil base ni el maestro.
|
|
66
|
+
- Después de generar el `.md`, recordar al usuario correr `/cv-ats-export` para el PDF.
|
|
67
|
+
|
|
68
|
+
## Handoff
|
|
69
|
+
|
|
70
|
+
Pedí aprobación (approval) antes de escribir el CV custom. Cuando el `.md` está READY, el siguiente paso es `/cv-ats-export` para generar el PDF ATS y `/cover-letter` para el mensaje.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# ATS Keywords — Cómo extraer y mapear
|
|
2
|
+
|
|
3
|
+
Los Applicant Tracking Systems (Greenhouse, Lever, Workday, Workable, etc.) hacen un primer filtro automático antes de que un humano vea el CV. Entender cómo "leen" cambia el resultado.
|
|
4
|
+
|
|
5
|
+
## Cómo funciona un ATS (modelo mental)
|
|
6
|
+
|
|
7
|
+
- Parsea el CV a texto plano y lo indexa.
|
|
8
|
+
- Hace matching de **strings** y variantes contra la oferta y/o contra criterios del recruiter.
|
|
9
|
+
- Rankea o filtra candidatos por densidad/relevancia de keywords y por campos estructurados (título, fechas, ubicación).
|
|
10
|
+
- **Lo que rompe el parseo:** tablas, columnas, text boxes, headers/footers con datos, gráficos, iconos en lugar de texto, PDFs escaneados (imagen).
|
|
11
|
+
|
|
12
|
+
## Extracción de keywords del JD
|
|
13
|
+
|
|
14
|
+
1. **Hard skills / tecnologías** — lenguajes, frameworks, DBs, cloud, herramientas. Copiá la forma **literal** (React.js ≠ React ≠ ReactJS para un match exacto; cuando puedas, incluí la variante que usa la oferta).
|
|
15
|
+
2. **Responsabilidades / verbos** — "design REST APIs", "lead a team", "CI/CD pipelines".
|
|
16
|
+
3. **Metodologías** — Agile, Scrum, TDD, clean architecture.
|
|
17
|
+
4. **Seniority y años** — "5+ years", "senior", "lead".
|
|
18
|
+
5. **Soft skills explícitos** — communication, ownership, cross-functional.
|
|
19
|
+
6. **Señales de cultura / frases gatillo** — ej. "code-forward", "startup mentality", "high autonomy". Suelen indicar qué valoran y dan ángulo para el summary y la cover letter.
|
|
20
|
+
|
|
21
|
+
## Mapeo contra el perfil
|
|
22
|
+
|
|
23
|
+
| Keyword del JD | ¿Lo tengo? | Dónde lo demuestro (bullet/proyecto) | Acción |
|
|
24
|
+
|---|---|---|---|
|
|
25
|
+
| React.js | Sí | "Built React + TS frontends @ X" | resaltar arriba |
|
|
26
|
+
| Kubernetes | Parcial | exposición en CI/CD | mencionar honesto |
|
|
27
|
+
| Go | No | — | gap; no inventar |
|
|
28
|
+
|
|
29
|
+
- **Must-have que tenés** → arriba, con espacio, con la palabra exacta.
|
|
30
|
+
- **Must-have parcial** → mencionar con honestidad ("exposure to…").
|
|
31
|
+
- **Must-have que no tenés** → gap real; no lo inventes. Decidí si igual aplicás.
|
|
32
|
+
|
|
33
|
+
## Inyección natural (no stuffing)
|
|
34
|
+
|
|
35
|
+
- Cada keyword vive en un **bullet verdadero** con contexto y resultado.
|
|
36
|
+
- Una sección "Skills" lista las tecnologías (bien para el ATS), pero las importantes también deben aparecer en la experiencia (bien para el humano y para el ranking).
|
|
37
|
+
- Evitá listas de 40 tecnologías sin contexto: lee como relleno y diluye el ranking.
|
|
38
|
+
|
|
39
|
+
## Checklist de formato ATS-safe
|
|
40
|
+
|
|
41
|
+
- [ ] Una sola columna, sin tablas/text boxes.
|
|
42
|
+
- [ ] Headers de sección estándar (Experience, Skills, Education…).
|
|
43
|
+
- [ ] Fechas consistentes MM/YYYY.
|
|
44
|
+
- [ ] Texto seleccionable (no imagen) — el export de `/cv-ats-export` ya lo garantiza.
|
|
45
|
+
- [ ] Nombre del archivo profesional (`Nombre_Apellido_Empresa.pdf`).
|
|
46
|
+
- [ ] Contacto en el cuerpo, no en el header/footer del documento.
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: dotnet-architecture
|
|
3
|
+
description: "Arquitectura de backends .NET: Vertical Slice Architecture (features) y Clean Architecture (capas), con guía de cuándo usar cada una, estructura, patrones (MediatR/Result/outbox) y anti-patterns. Usar al diseñar o revisar la estructura de un proyecto ASP.NET Core."
|
|
4
|
+
category: "backend"
|
|
5
|
+
argument-hint: "[vertical-slice|clean|when-to-use]"
|
|
6
|
+
user-invocable: true
|
|
7
|
+
allowed-tools: Read, Glob, Grep, Bash, Write, Edit, Task
|
|
8
|
+
---
|
|
9
|
+
# dotnet-architecture — VSA & Clean Architecture para .NET
|
|
10
|
+
|
|
11
|
+
Dos enfoques production-ready para estructurar un backend ASP.NET Core. No son excluyentes: muchos proyectos
|
|
12
|
+
usan Clean Architecture en el borde y slices verticales adentro. Esta skill ayuda a **elegir** y a aplicar
|
|
13
|
+
cada uno correctamente.
|
|
14
|
+
|
|
15
|
+
## MANDATORY WORKFLOW
|
|
16
|
+
|
|
17
|
+
**No imponer arquitectura por moda. Elegir según complejidad del dominio y vida del proyecto.**
|
|
18
|
+
|
|
19
|
+
### Step 0: Elegir enfoque (`when-to-use`)
|
|
20
|
+
|
|
21
|
+
| Señal | Recomendación |
|
|
22
|
+
|-------|---------------|
|
|
23
|
+
| CRUD, prototipos, APIs chicas, dominio fino, equipo chico | **Vertical Slice** (o single project) |
|
|
24
|
+
| Dominio complejo, reglas de negocio ricas, long-lived, varios equipos | **Clean Architecture** |
|
|
25
|
+
| Necesitás aislar el dominio de frameworks/DB para test y reemplazo | **Clean Architecture** |
|
|
26
|
+
| Querés velocidad y bajo overhead de indirección | **Vertical Slice** |
|
|
27
|
+
|
|
28
|
+
> Evitá Clean Architecture para CRUD simple: los proyectos extra y la indirección cuestan más de lo que aportan
|
|
29
|
+
> cuando la lógica de negocio es delgada. Empezá con Vertical Slice y migrá si el dominio crece.
|
|
30
|
+
|
|
31
|
+
### Step 1: Aplicar la estructura
|
|
32
|
+
|
|
33
|
+
- **Vertical Slice:** un folder por feature/use-case; cada slice tiene su request, handler, validator y endpoint juntos. Ver `references/project-structure.md`.
|
|
34
|
+
- **Clean Architecture:** proyectos `Domain → Application → Infrastructure → Api`; las dependencias apuntan **hacia adentro**, nunca al revés. Ver `references/project-structure.md`.
|
|
35
|
+
|
|
36
|
+
### Step 2: Patrones idiomáticos
|
|
37
|
+
|
|
38
|
+
Leer `references/patterns.md`: MediatR/handlers por use-case, `IApplicationDbContext` (exponer EF Core sin repos
|
|
39
|
+
genéricos), pipeline behaviors (validación/logging/transacción), Result pattern en vez de excepciones para flujo,
|
|
40
|
+
y outbox pattern para consistencia entre DB y eventos.
|
|
41
|
+
|
|
42
|
+
### Step 3: Revisar contra anti-patterns + checklist
|
|
43
|
+
|
|
44
|
+
- `references/anti-patterns.md` — repos genéricos sobre EF Core, fat controllers, dependencias que apuntan hacia afuera, lógica de dominio en Infrastructure.
|
|
45
|
+
- `references/checklist.md` — verificación de cierre antes de dar la estructura por buena.
|
|
46
|
+
|
|
47
|
+
Antes de aplicar una reestructuración significativa, confirmar el approach con el usuario (Question → Decision → Approval). Si todo el checklist pasa → arquitectura **COMPLIANT**.
|
|
48
|
+
|
|
49
|
+
## Resumen de enfoques
|
|
50
|
+
|
|
51
|
+
| Aspecto | Vertical Slice | Clean Architecture |
|
|
52
|
+
|---------|----------------|--------------------|
|
|
53
|
+
| Eje de organización | Feature / use-case | Capa técnica |
|
|
54
|
+
| Proyectos | 1 (+ tests) | 4 (Domain/App/Infra/Api) |
|
|
55
|
+
| Acoplamiento entre features | Bajo (slices independientes) | Mediado por Application |
|
|
56
|
+
| Overhead inicial | Bajo | Alto |
|
|
57
|
+
| Mejor para | CRUD, features, velocidad | Dominios complejos, long-lived |
|
|
58
|
+
| Riesgo | Duplicación entre slices | Sobre-ingeniería |
|
|
59
|
+
|
|
60
|
+
## Próximos pasos
|
|
61
|
+
|
|
62
|
+
Definida la arquitectura → `/dotnet-scaffold` para generar el proyecto, y `/dotnet-best-practices` para revisar el código contra las 40 reglas.
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
_Inspirado en [ardalis/CleanArchitecture](https://github.com/ardalis/CleanArchitecture), [nadirbad/VerticalSliceArchitecture](https://github.com/nadirbad/VerticalSliceArchitecture) y la guía de Vertical Slice de [Milan Jovanović](https://www.milanjovanovic.tech/blog/vertical-slice-architecture-dotnet). Adaptado al formato Arcane._
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Anti-Patterns
|
|
2
|
+
|
|
3
|
+
- **`IRepository<T>` / `IUnitOfWork` genérico sobre EF Core** — el `DbContext` ya es repositorio (`DbSet<>`) y Unit of Work (`SaveChangesAsync`); envolverlo agrega indirección sin valor y te tapa LINQ/Include/proyecciones.
|
|
4
|
+
- **Fat controllers / endpoints con lógica de negocio** — el endpoint solo traduce HTTP ↔ request y mapea el resultado; las reglas van en el handler/dominio, no en el controlador.
|
|
5
|
+
- **Dependencias apuntando hacia afuera** — `Domain` referenciando `Infrastructure` o EF Core rompe la regla de dependencias: el dominio deja de ser testeable y reemplazable.
|
|
6
|
+
- **Application anémica que solo reenvía a repos** — si el handler no hace más que `return repo.Get(id)`, la capa no aporta nada; movés lógica de negocio real al dominio/handler o eliminás la indirección.
|
|
7
|
+
- **Filtrar entidades EF como respuestas de API** — exponer entidades de persistencia acopla el contrato HTTP al schema de la DB y arrastra lazy-loading/ciclos; devolvé DTOs en el borde.
|
|
8
|
+
- **Forzar Clean Architecture en un CRUD simple** — cuatro proyectos y pipeline behaviors para listar y guardar registros es sobre-ingeniería; empezá con Vertical Slice o single project.
|
|
9
|
+
- **Service locator estático / `BuildServiceProvider()` en `Program.cs`** — construir el provider a mano crea grafos duplicados y dependencias ocultas; registrá todo en el contenedor y dejá que resuelva por constructor.
|
|
10
|
+
- **Un `Services/` y `Controllers/` gigantes** — organizar por tipo técnico dispersa cada feature en cinco carpetas; organizá por feature/slice para que un caso de uso viva junto.
|
|
11
|
+
|
|
12
|
+
_Ref: https://www.milanjovanovic.tech/blog/vertical-slice-architecture-dotnet · https://github.com/ardalis/CleanArchitecture · https://learn.microsoft.com/en-us/ef/core/dbcontext-configuration/_
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Checklist
|
|
2
|
+
|
|
3
|
+
Verificá antes de dar la estructura por buena.
|
|
4
|
+
|
|
5
|
+
- [ ] Las dependencias apuntan hacia adentro: `Api → Infrastructure → Application → Domain`
|
|
6
|
+
- [ ] `Domain` no tiene `<ProjectReference>` ni paquetes de framework (sin EF, sin ASP.NET)
|
|
7
|
+
- [ ] No hay `IRepository<T>` / `IUnitOfWork` genérico envolviendo EF Core
|
|
8
|
+
- [ ] EF Core se expone vía `IApplicationDbContext` en Application, implementado en Infrastructure
|
|
9
|
+
- [ ] En Vertical Slice, cada slice es autocontenido (request + handler + validator + endpoint)
|
|
10
|
+
- [ ] Los slices no dependen entre sí (acoplamiento solo vía `Common`/`Infrastructure`)
|
|
11
|
+
- [ ] DTOs en el borde: ninguna entidad EF se devuelve como respuesta de API
|
|
12
|
+
- [ ] Cada handler tiene una sola responsabilidad (un caso de uso por archivo)
|
|
13
|
+
- [ ] `Program.cs` es solo composition root (DI + pipeline + map endpoints), sin lógica de negocio
|
|
14
|
+
- [ ] La validación corre en un pipeline behavior, no repetida en cada endpoint
|
|
15
|
+
- [ ] Fallos esperados se modelan con `Result<T>`/`ErrorOr`, no con excepciones de flujo
|
|
16
|
+
- [ ] Las migraciones EF Core están versionadas en git
|
|
17
|
+
- [ ] El enfoque elegido (VSA vs Clean) corresponde a la complejidad real del dominio
|
|
18
|
+
|
|
19
|
+
_Ref: https://github.com/ardalis/CleanArchitecture · https://github.com/nadirbad/VerticalSliceArchitecture · https://www.milanjovanovic.tech/blog/clean-architecture-folder-structure_
|