swl-ses 3.3.2
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/CLAUDE.md +425 -0
- package/_userland/agentes/.gitkeep +0 -0
- package/_userland/habilidades/.gitkeep +0 -0
- package/agentes/accesibilidad-wcag-swl.md +683 -0
- package/agentes/arquitecto-swl.md +210 -0
- package/agentes/auto-evolucion-swl.md +408 -0
- package/agentes/backend-api-swl.md +442 -0
- package/agentes/backend-node-swl.md +439 -0
- package/agentes/backend-python-swl.md +469 -0
- package/agentes/backend-workers-swl.md +444 -0
- package/agentes/cloud-infra-swl.md +466 -0
- package/agentes/consolidador-swl.md +487 -0
- package/agentes/datos-swl.md +568 -0
- package/agentes/depurador-swl.md +301 -0
- package/agentes/devops-ci-swl.md +352 -0
- package/agentes/disenador-ui-swl.md +546 -0
- package/agentes/documentador-swl.md +323 -0
- package/agentes/frontend-angular-swl.md +603 -0
- package/agentes/frontend-css-swl.md +700 -0
- package/agentes/frontend-react-swl.md +672 -0
- package/agentes/frontend-swl.md +483 -0
- package/agentes/frontend-tailwind-swl.md +808 -0
- package/agentes/implementador-swl.md +235 -0
- package/agentes/investigador-swl.md +274 -0
- package/agentes/investigador-ux-swl.md +482 -0
- package/agentes/migrador-swl.md +389 -0
- package/agentes/mobile-android-swl.md +473 -0
- package/agentes/mobile-cross-swl.md +501 -0
- package/agentes/mobile-ios-swl.md +464 -0
- package/agentes/notificador-swl.md +886 -0
- package/agentes/observabilidad-swl.md +408 -0
- package/agentes/orquestador-swl.md +490 -0
- package/agentes/planificador-swl.md +222 -0
- package/agentes/producto-prd-swl.md +565 -0
- package/agentes/release-manager-swl.md +545 -0
- package/agentes/rendimiento-swl.md +691 -0
- package/agentes/revisor-codigo-swl.md +254 -0
- package/agentes/revisor-seguridad-swl.md +316 -0
- package/agentes/tdd-qa-swl.md +323 -0
- package/agentes/ux-disenador-swl.md +498 -0
- package/bin/swl-ses.js +119 -0
- package/comandos/swl/actualizar.md +117 -0
- package/comandos/swl/aprender.md +348 -0
- package/comandos/swl/auditar-deps.md +390 -0
- package/comandos/swl/autoresearch.md +346 -0
- package/comandos/swl/checkpoint.md +296 -0
- package/comandos/swl/compactar.md +283 -0
- package/comandos/swl/crear-skill.md +609 -0
- package/comandos/swl/discutir-fase.md +230 -0
- package/comandos/swl/ejecutar-fase.md +302 -0
- package/comandos/swl/evolucionar.md +377 -0
- package/comandos/swl/instalar.md +220 -0
- package/comandos/swl/mapear-codebase.md +205 -0
- package/comandos/swl/nuevo-proyecto.md +154 -0
- package/comandos/swl/planear-fase.md +221 -0
- package/comandos/swl/release.md +405 -0
- package/comandos/swl/salud.md +382 -0
- package/comandos/swl/verificar.md +292 -0
- package/habilidades/accesibilidad-a11y/SKILL.md +584 -0
- package/habilidades/angular-avanzado/SKILL.md +491 -0
- package/habilidades/angular-moderno/SKILL.md +326 -0
- package/habilidades/api-rest-diseno/SKILL.md +302 -0
- package/habilidades/api-rest-diseno/recursos/openapi-template.yaml +506 -0
- package/habilidades/aprendizaje-continuo/SKILL.md +369 -0
- package/habilidades/async-python/SKILL.md +474 -0
- package/habilidades/auth-patrones/SKILL.md +488 -0
- package/habilidades/auto-evolucion-protocolo/SKILL.md +376 -0
- package/habilidades/autoresearch/SKILL.md +248 -0
- package/habilidades/autoresearch/recursos/checklist-template.md +191 -0
- package/habilidades/autoresearch/scripts/calcular-score.js +88 -0
- package/habilidades/checklist-calidad/SKILL.md +247 -0
- package/habilidades/checklist-calidad/recursos/quality-report-template.md +148 -0
- package/habilidades/checklist-seguridad/SKILL.md +224 -0
- package/habilidades/checkpoints-verificacion/SKILL.md +309 -0
- package/habilidades/checkpoints-verificacion/recursos/checkpoint-templates.md +360 -0
- package/habilidades/ci-cd-pipelines/SKILL.md +583 -0
- package/habilidades/ci-cd-pipelines/recursos/github-actions-template.yaml +403 -0
- package/habilidades/cloud-aws/SKILL.md +497 -0
- package/habilidades/compactacion-contexto/SKILL.md +201 -0
- package/habilidades/contenedores-docker/SKILL.md +453 -0
- package/habilidades/contenedores-docker/recursos/dockerfile-template.dockerfile +160 -0
- package/habilidades/css-moderno/SKILL.md +463 -0
- package/habilidades/datos-etl/SKILL.md +486 -0
- package/habilidades/dependencias-auditoria/SKILL.md +293 -0
- package/habilidades/deprecacion-migracion/SKILL.md +485 -0
- package/habilidades/design-tokens/SKILL.md +519 -0
- package/habilidades/discutir-fase/SKILL.md +167 -0
- package/habilidades/diseno-responsivo/SKILL.md +326 -0
- package/habilidades/django-experto/SKILL.md +395 -0
- package/habilidades/doc-sync/SKILL.md +259 -0
- package/habilidades/ejecutar-fase/SKILL.md +199 -0
- package/habilidades/estructura-proyecto-claude/SKILL.md +459 -0
- package/habilidades/estructura-proyecto-claude/recursos/claude-md-template.md +261 -0
- package/habilidades/estructura-proyecto-claude/recursos/frontmatter-y-hooks-referencia.md +213 -0
- package/habilidades/estructura-proyecto-claude/recursos/mcp-json-template.json +77 -0
- package/habilidades/estructura-proyecto-claude/recursos/variantes-por-stack.md +177 -0
- package/habilidades/event-driven/SKILL.md +580 -0
- package/habilidades/extractor-de-aprendizajes/SKILL.md +234 -0
- package/habilidades/fastapi-experto/SKILL.md +368 -0
- package/habilidades/frontend-avanzado/SKILL.md +555 -0
- package/habilidades/git-worktrees-paralelo/SKILL.md +246 -0
- package/habilidades/iam-secretos/SKILL.md +511 -0
- package/habilidades/instalar-sistema/SKILL.md +140 -0
- package/habilidades/kubernetes-orquestacion/SKILL.md +549 -0
- package/habilidades/manejo-errores/SKILL.md +512 -0
- package/habilidades/mapear-codebase/SKILL.md +199 -0
- package/habilidades/microservicios/SKILL.md +473 -0
- package/habilidades/mobile-flutter/SKILL.md +566 -0
- package/habilidades/mobile-react-native/SKILL.md +493 -0
- package/habilidades/monitoring-alertas/SKILL.md +447 -0
- package/habilidades/node-experto/SKILL.md +521 -0
- package/habilidades/notificaciones-multicanal/SKILL.md +448 -0
- package/habilidades/notificaciones-multicanal/recursos/config-template.json +115 -0
- package/habilidades/nuevo-proyecto/SKILL.md +183 -0
- package/habilidades/patrones-python/SKILL.md +381 -0
- package/habilidades/performance-baseline/SKILL.md +243 -0
- package/habilidades/planear-fase/SKILL.md +184 -0
- package/habilidades/postgresql-experto/SKILL.md +379 -0
- package/habilidades/react-experto/SKILL.md +434 -0
- package/habilidades/react-optimizacion/SKILL.md +328 -0
- package/habilidades/release-semver/SKILL.md +226 -0
- package/habilidades/release-semver/scripts/generar-changelog.sh +238 -0
- package/habilidades/sql-optimizacion/SKILL.md +314 -0
- package/habilidades/tailwind-experto/SKILL.md +412 -0
- package/habilidades/tdd-workflow/SKILL.md +267 -0
- package/habilidades/testing-python/SKILL.md +350 -0
- package/habilidades/threat-model-lite/SKILL.md +218 -0
- package/habilidades/typescript-avanzado/SKILL.md +454 -0
- package/habilidades/ux-diseno/SKILL.md +488 -0
- package/habilidades/validacion-ci-sistema/SKILL.md +543 -0
- package/habilidades/validacion-ci-sistema/scripts/validar-sistema.sh +286 -0
- package/habilidades/verificar-trabajo/SKILL.md +208 -0
- package/habilidades/wireframes-flujos/SKILL.md +396 -0
- package/habilidades/workflow-claude-code/SKILL.md +359 -0
- package/hooks/calidad-pre-commit.js +578 -0
- package/hooks/escaneo-secretos.js +302 -0
- package/hooks/extraccion-aprendizajes.js +550 -0
- package/hooks/linea-estado.js +249 -0
- package/hooks/monitor-contexto.js +230 -0
- package/hooks/proteccion-rutas.js +249 -0
- package/manifiestos/hooks-config.json +41 -0
- package/manifiestos/modulos.json +318 -0
- package/manifiestos/perfiles.json +189 -0
- package/package.json +45 -0
- package/plantillas/PROJECT.md +122 -0
- package/plantillas/REQUIREMENTS.md +132 -0
- package/plantillas/ROADMAP.md +143 -0
- package/plantillas/STATE.md +109 -0
- package/plantillas/research/ARCHITECTURE.md +220 -0
- package/plantillas/research/FEATURES.md +175 -0
- package/plantillas/research/PITFALLS.md +299 -0
- package/plantillas/research/STACK.md +233 -0
- package/plantillas/research/SUMMARY.md +165 -0
- package/plugin.json +144 -0
- package/reglas/accesibilidad.md +269 -0
- package/reglas/api-diseno.md +400 -0
- package/reglas/arquitectura.md +183 -0
- package/reglas/cloud-infra.md +247 -0
- package/reglas/docs.md +245 -0
- package/reglas/estilo-codigo.md +179 -0
- package/reglas/git-workflow.md +186 -0
- package/reglas/performance.md +195 -0
- package/reglas/pruebas.md +159 -0
- package/reglas/seguridad.md +151 -0
- package/reglas/skills-estandar.md +473 -0
- package/scripts/actualizar.js +51 -0
- package/scripts/desinstalar.js +86 -0
- package/scripts/doctor.js +222 -0
- package/scripts/inicializar.js +89 -0
- package/scripts/instalador.js +333 -0
- package/scripts/lib/detectar-runtime.js +177 -0
- package/scripts/lib/estado.js +112 -0
- package/scripts/lib/hooks-settings.js +283 -0
- package/scripts/lib/manifiestos.js +138 -0
- package/scripts/lib/seguridad.js +160 -0
- package/scripts/publicar.js +209 -0
- package/scripts/validar.js +120 -0
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: tailwind-experto
|
|
3
|
+
description: Tailwind CSS v4 mejores prácticas. Cubre configuración CSS-first con @theme y @custom-variant, design tokens en Tailwind, patrones de componentes, responsive con breakpoints, dark mode, plugins custom, compilación JIT y anti-patrones como utility soup y @apply abuse.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Tailwind CSS v4 Experto
|
|
7
|
+
|
|
8
|
+
## Cambio Fundamental en v4
|
|
9
|
+
|
|
10
|
+
Tailwind v4 abandona el archivo `tailwind.config.js` en favor de **configuración
|
|
11
|
+
directa en CSS**. No más archivo de configuración JavaScript — todo se define
|
|
12
|
+
con `@theme` y directivas CSS nativas.
|
|
13
|
+
|
|
14
|
+
```css
|
|
15
|
+
/* app/globals.css — la config ahora está aquí */
|
|
16
|
+
@import "tailwindcss";
|
|
17
|
+
|
|
18
|
+
@theme {
|
|
19
|
+
--font-sans: 'Inter', system-ui, sans-serif;
|
|
20
|
+
--font-mono: 'JetBrains Mono', monospace;
|
|
21
|
+
|
|
22
|
+
--color-primario: oklch(56% 0.2 250);
|
|
23
|
+
--color-primario-oscuro: oklch(46% 0.2 250);
|
|
24
|
+
--color-secundario: oklch(70% 0.15 160);
|
|
25
|
+
|
|
26
|
+
--radius-sm: 0.25rem;
|
|
27
|
+
--radius-md: 0.375rem;
|
|
28
|
+
--radius-lg: 0.5rem;
|
|
29
|
+
--radius-xl: 1rem;
|
|
30
|
+
|
|
31
|
+
--spacing-18: 4.5rem;
|
|
32
|
+
--spacing-88: 22rem;
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## 1. Configuración @theme
|
|
39
|
+
|
|
40
|
+
### Tokens de color completos
|
|
41
|
+
|
|
42
|
+
```css
|
|
43
|
+
@theme {
|
|
44
|
+
/* Sistema de colores semántico */
|
|
45
|
+
--color-fondo: #ffffff;
|
|
46
|
+
--color-superficie: #f8fafc;
|
|
47
|
+
--color-borde: #e2e8f0;
|
|
48
|
+
--color-texto: #0f172a;
|
|
49
|
+
--color-texto-muted: #64748b;
|
|
50
|
+
|
|
51
|
+
/* Paleta de marca */
|
|
52
|
+
--color-marca-50: oklch(97% 0.01 250);
|
|
53
|
+
--color-marca-100: oklch(93% 0.03 250);
|
|
54
|
+
--color-marca-200: oklch(86% 0.06 250);
|
|
55
|
+
--color-marca-300: oklch(77% 0.10 250);
|
|
56
|
+
--color-marca-400: oklch(67% 0.15 250);
|
|
57
|
+
--color-marca-500: oklch(56% 0.20 250);
|
|
58
|
+
--color-marca-600: oklch(46% 0.20 250);
|
|
59
|
+
--color-marca-700: oklch(38% 0.18 250);
|
|
60
|
+
--color-marca-800: oklch(30% 0.14 250);
|
|
61
|
+
--color-marca-900: oklch(22% 0.10 250);
|
|
62
|
+
|
|
63
|
+
/* Estados */
|
|
64
|
+
--color-exito: oklch(65% 0.20 145);
|
|
65
|
+
--color-error: oklch(58% 0.24 27);
|
|
66
|
+
--color-advertencia: oklch(78% 0.18 80);
|
|
67
|
+
--color-info: oklch(62% 0.20 250);
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Tipografía y escalas
|
|
72
|
+
|
|
73
|
+
```css
|
|
74
|
+
@theme {
|
|
75
|
+
/* Escala tipográfica fluida */
|
|
76
|
+
--text-xs: clamp(0.70rem, 0.5vw + 0.5rem, 0.75rem);
|
|
77
|
+
--text-sm: clamp(0.85rem, 0.8vw + 0.5rem, 0.875rem);
|
|
78
|
+
--text-base: clamp(1rem, 1vw + 0.5rem, 1rem);
|
|
79
|
+
--text-lg: clamp(1.1rem, 1.5vw + 0.5rem, 1.125rem);
|
|
80
|
+
--text-xl: clamp(1.2rem, 2vw + 0.5rem, 1.25rem);
|
|
81
|
+
--text-2xl: clamp(1.4rem, 3vw + 0.5rem, 1.5rem);
|
|
82
|
+
--text-4xl: clamp(2rem, 5vw + 0.5rem, 2.25rem);
|
|
83
|
+
|
|
84
|
+
/* Line heights */
|
|
85
|
+
--leading-tight: 1.25;
|
|
86
|
+
--leading-snug: 1.375;
|
|
87
|
+
--leading-normal: 1.5;
|
|
88
|
+
--leading-relaxed: 1.625;
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## 2. @custom-variant
|
|
95
|
+
|
|
96
|
+
Define variantes personalizadas sin plugin:
|
|
97
|
+
|
|
98
|
+
```css
|
|
99
|
+
/* Variante para elementos con data attribute */
|
|
100
|
+
@custom-variant data-activo (&[data-activo='true']);
|
|
101
|
+
@custom-variant data-disabled (&[data-disabled='true']);
|
|
102
|
+
@custom-variant data-error (&[data-error='true']);
|
|
103
|
+
|
|
104
|
+
/* Uso en HTML */
|
|
105
|
+
/* <div data-activo="true" class="data-activo:bg-marca-100 data-activo:border-marca-500"> */
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
```css
|
|
109
|
+
/* Variante para grupos custom */
|
|
110
|
+
@custom-variant sidebar-collapsed (.sidebar-collapsed &);
|
|
111
|
+
@custom-variant print-hidden (@media print { & { display: none; } });
|
|
112
|
+
|
|
113
|
+
/* Uso */
|
|
114
|
+
/* <nav class="w-64 sidebar-collapsed:w-16 transition-all"> */
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## 3. Patrones de Componentes
|
|
120
|
+
|
|
121
|
+
### Patrón: clases base + variantes
|
|
122
|
+
|
|
123
|
+
```tsx
|
|
124
|
+
// components/Button.tsx
|
|
125
|
+
import { cva, type VariantProps } from 'class-variance-authority';
|
|
126
|
+
|
|
127
|
+
const buttonVariants = cva(
|
|
128
|
+
// Clases base siempre presentes
|
|
129
|
+
'inline-flex items-center justify-center gap-2 rounded-md font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-marca-500 disabled:pointer-events-none disabled:opacity-50',
|
|
130
|
+
{
|
|
131
|
+
variants: {
|
|
132
|
+
variante: {
|
|
133
|
+
primario: 'bg-marca-500 text-white hover:bg-marca-600 active:bg-marca-700',
|
|
134
|
+
secundario: 'bg-transparent border border-marca-500 text-marca-600 hover:bg-marca-50',
|
|
135
|
+
ghost: 'hover:bg-gray-100 text-gray-700',
|
|
136
|
+
destructivo: 'bg-error text-white hover:bg-red-700',
|
|
137
|
+
},
|
|
138
|
+
tamano: {
|
|
139
|
+
sm: 'h-8 px-3 text-sm',
|
|
140
|
+
md: 'h-10 px-4 text-base',
|
|
141
|
+
lg: 'h-12 px-6 text-lg',
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
defaultVariants: {
|
|
145
|
+
variante: 'primario',
|
|
146
|
+
tamano: 'md',
|
|
147
|
+
},
|
|
148
|
+
}
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
interface ButtonProps
|
|
152
|
+
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
|
153
|
+
VariantProps<typeof buttonVariants> {}
|
|
154
|
+
|
|
155
|
+
export function Button({ variante, tamano, className, ...props }: ButtonProps) {
|
|
156
|
+
return (
|
|
157
|
+
<button
|
|
158
|
+
className={buttonVariants({ variante, tamano, className })}
|
|
159
|
+
{...props}
|
|
160
|
+
/>
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Patrón: composición de layouts
|
|
166
|
+
|
|
167
|
+
```tsx
|
|
168
|
+
// Layout de dos columnas con sidebar
|
|
169
|
+
function DashboardLayout({ children }: { children: React.ReactNode }) {
|
|
170
|
+
return (
|
|
171
|
+
<div className="flex min-h-screen bg-fondo">
|
|
172
|
+
<aside className="w-64 shrink-0 border-r border-borde bg-superficie">
|
|
173
|
+
<NavSidebar />
|
|
174
|
+
</aside>
|
|
175
|
+
<main className="flex-1 overflow-auto p-6">
|
|
176
|
+
{children}
|
|
177
|
+
</main>
|
|
178
|
+
</div>
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Patrón: tarjeta con variantes
|
|
184
|
+
|
|
185
|
+
```tsx
|
|
186
|
+
const cardVariants = cva('rounded-lg border bg-superficie', {
|
|
187
|
+
variants: {
|
|
188
|
+
elevacion: {
|
|
189
|
+
none: 'shadow-none',
|
|
190
|
+
sm: 'shadow-sm',
|
|
191
|
+
md: 'shadow-md',
|
|
192
|
+
lg: 'shadow-lg ring-1 ring-black/5',
|
|
193
|
+
},
|
|
194
|
+
padding: {
|
|
195
|
+
none: 'p-0',
|
|
196
|
+
sm: 'p-4',
|
|
197
|
+
md: 'p-6',
|
|
198
|
+
lg: 'p-8',
|
|
199
|
+
},
|
|
200
|
+
},
|
|
201
|
+
defaultVariants: { elevacion: 'sm', padding: 'md' },
|
|
202
|
+
});
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
## 4. Responsive con Breakpoints
|
|
208
|
+
|
|
209
|
+
### Sistema de breakpoints (v4 por defecto)
|
|
210
|
+
|
|
211
|
+
| Prefijo | Ancho mínimo | Dispositivo típico |
|
|
212
|
+
|---------|-------------|-------------------|
|
|
213
|
+
| `sm:` | 640px | Móvil grande |
|
|
214
|
+
| `md:` | 768px | Tablet |
|
|
215
|
+
| `lg:` | 1024px | Laptop |
|
|
216
|
+
| `xl:` | 1280px | Desktop |
|
|
217
|
+
| `2xl:` | 1536px | Pantalla grande |
|
|
218
|
+
|
|
219
|
+
### Mobile-first (siempre)
|
|
220
|
+
|
|
221
|
+
```html
|
|
222
|
+
<!-- Correcto: móvil base, desktop override -->
|
|
223
|
+
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
224
|
+
|
|
225
|
+
<!-- MAL: desktop base, móvil override (requiere sobreescribir) -->
|
|
226
|
+
<div class="grid grid-cols-3 max-md:grid-cols-1">
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### Breakpoints personalizados en v4
|
|
230
|
+
|
|
231
|
+
```css
|
|
232
|
+
@theme {
|
|
233
|
+
--breakpoint-xs: 475px;
|
|
234
|
+
--breakpoint-3xl: 1920px;
|
|
235
|
+
}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
## 5. Dark Mode en v4
|
|
241
|
+
|
|
242
|
+
```css
|
|
243
|
+
/* globals.css */
|
|
244
|
+
@import "tailwindcss";
|
|
245
|
+
|
|
246
|
+
/* Modo oscuro por sistema */
|
|
247
|
+
@theme {
|
|
248
|
+
--color-fondo: #ffffff;
|
|
249
|
+
--color-texto: #0f172a;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
@media (prefers-color-scheme: dark) {
|
|
253
|
+
@theme {
|
|
254
|
+
--color-fondo: #0f172a;
|
|
255
|
+
--color-texto: #f1f5f9;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
```tsx
|
|
261
|
+
// Dark mode con clase manual (toggle)
|
|
262
|
+
<html className={tema === 'oscuro' ? 'dark' : ''}>
|
|
263
|
+
|
|
264
|
+
// En el CSS
|
|
265
|
+
@custom-variant dark (&:where(.dark, .dark *));
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
```html
|
|
269
|
+
<!-- Uso de variante dark -->
|
|
270
|
+
<div class="bg-white dark:bg-slate-900 text-slate-900 dark:text-slate-100">
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
---
|
|
274
|
+
|
|
275
|
+
## 6. Plugins Custom
|
|
276
|
+
|
|
277
|
+
```typescript
|
|
278
|
+
// plugins/animation-plugin.ts
|
|
279
|
+
import plugin from 'tailwindcss/plugin';
|
|
280
|
+
|
|
281
|
+
export const animacionesPlugin = plugin(
|
|
282
|
+
({ addUtilities, addComponents, theme }) => {
|
|
283
|
+
// Utilidades de animación custom
|
|
284
|
+
addUtilities({
|
|
285
|
+
'.animate-fade-in': {
|
|
286
|
+
animation: 'fadeIn 0.3s ease-in-out',
|
|
287
|
+
},
|
|
288
|
+
'.animate-slide-up': {
|
|
289
|
+
animation: 'slideUp 0.4s ease-out',
|
|
290
|
+
},
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
// Componente de badge
|
|
294
|
+
addComponents({
|
|
295
|
+
'.badge': {
|
|
296
|
+
display: 'inline-flex',
|
|
297
|
+
alignItems: 'center',
|
|
298
|
+
padding: `${theme('spacing.1')} ${theme('spacing.2')}`,
|
|
299
|
+
borderRadius: theme('borderRadius.full'),
|
|
300
|
+
fontSize: theme('fontSize.xs'),
|
|
301
|
+
fontWeight: '500',
|
|
302
|
+
},
|
|
303
|
+
});
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
// Keyframes del plugin
|
|
307
|
+
theme: {
|
|
308
|
+
extend: {
|
|
309
|
+
keyframes: {
|
|
310
|
+
fadeIn: {
|
|
311
|
+
from: { opacity: '0' },
|
|
312
|
+
to: { opacity: '1' },
|
|
313
|
+
},
|
|
314
|
+
slideUp: {
|
|
315
|
+
from: { transform: 'translateY(10px)', opacity: '0' },
|
|
316
|
+
to: { transform: 'translateY(0)', opacity: '1' },
|
|
317
|
+
},
|
|
318
|
+
},
|
|
319
|
+
},
|
|
320
|
+
},
|
|
321
|
+
}
|
|
322
|
+
);
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
---
|
|
326
|
+
|
|
327
|
+
## 7. Compilación JIT y Purging
|
|
328
|
+
|
|
329
|
+
Tailwind v4 usa JIT por defecto. Solo genera las clases que realmente usas:
|
|
330
|
+
|
|
331
|
+
```css
|
|
332
|
+
/* globals.css */
|
|
333
|
+
@import "tailwindcss";
|
|
334
|
+
|
|
335
|
+
/* Tailwind escanea automáticamente archivos .tsx, .ts, .jsx, .js, .html */
|
|
336
|
+
/* Para archivos especiales, agregar fuentes adicionales: */
|
|
337
|
+
@source "../node_modules/mi-libreria-ui/dist/**/*.js";
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
### Valores arbitrarios (escape hatch)
|
|
341
|
+
|
|
342
|
+
```html
|
|
343
|
+
<!-- Valor exacto que no está en el sistema -->
|
|
344
|
+
<div class="top-[117px] w-[calc(100%-2rem)] bg-[#1da1f2]">
|
|
345
|
+
|
|
346
|
+
<!-- Variable CSS arbitraria -->
|
|
347
|
+
<div class="text-(--color-personalizado)">
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
---
|
|
351
|
+
|
|
352
|
+
## 8. Anti-patrones
|
|
353
|
+
|
|
354
|
+
### Utility Soup — demasiadas clases en un elemento
|
|
355
|
+
|
|
356
|
+
```html
|
|
357
|
+
<!-- MAL: ilegible, difícil de mantener -->
|
|
358
|
+
<button class="inline-flex items-center justify-center gap-2 rounded-md bg-blue-600 px-4 py-2 text-sm font-medium text-white shadow-sm transition-colors hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 active:bg-blue-800 disabled:cursor-not-allowed disabled:opacity-50">
|
|
359
|
+
|
|
360
|
+
<!-- BIEN: extraer componente con cva() -->
|
|
361
|
+
<Button variante="primario" tamano="sm">Guardar</Button>
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
### @apply Abuse — recrear Sass con Tailwind
|
|
365
|
+
|
|
366
|
+
```css
|
|
367
|
+
/* MAL: el propósito de @apply es crear un nuevo .scss, no reutilizar utilidades */
|
|
368
|
+
.card {
|
|
369
|
+
@apply rounded-lg border border-gray-200 bg-white p-6 shadow-sm;
|
|
370
|
+
}
|
|
371
|
+
.card-header {
|
|
372
|
+
@apply border-b border-gray-200 pb-4 mb-4;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/* BIEN: si el patrón es reutilizable, hacer componente React/Vue/Angular */
|
|
376
|
+
/* Si es un patrón CSS puro, usar custom property o @layer componentes */
|
|
377
|
+
@layer componentes {
|
|
378
|
+
.card {
|
|
379
|
+
border-radius: var(--radius-lg);
|
|
380
|
+
border: 1px solid var(--color-borde);
|
|
381
|
+
background: var(--color-superficie);
|
|
382
|
+
padding: var(--spacing-6);
|
|
383
|
+
box-shadow: var(--shadow-sm);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
### Clases condicionales con interpolación de string
|
|
389
|
+
|
|
390
|
+
```tsx
|
|
391
|
+
// MAL: Tailwind JIT no puede detectar clases dinámicas construidas con interpolación
|
|
392
|
+
const color = 'azul';
|
|
393
|
+
<div className={`bg-${color}-500`}> // 'bg-azul-500' no existe en el output
|
|
394
|
+
|
|
395
|
+
// BIEN: objeto de mapeo completo
|
|
396
|
+
const colorMap = {
|
|
397
|
+
azul: 'bg-blue-500',
|
|
398
|
+
rojo: 'bg-red-500',
|
|
399
|
+
verde: 'bg-green-500',
|
|
400
|
+
} as const;
|
|
401
|
+
<div className={colorMap[color]}>
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
### Olvidar el orden de variantes
|
|
405
|
+
|
|
406
|
+
```html
|
|
407
|
+
<!-- MAL: dark: debe aplicarse DESPUÉS del estado base en la misma propiedad -->
|
|
408
|
+
<div class="dark:bg-gray-900 bg-white hover:dark:bg-gray-800 hover:bg-gray-50">
|
|
409
|
+
|
|
410
|
+
<!-- BIEN: agrupar por propiedad, modificadores al final -->
|
|
411
|
+
<div class="bg-white dark:bg-gray-900 hover:bg-gray-50 dark:hover:bg-gray-800">
|
|
412
|
+
```
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: tdd-workflow
|
|
3
|
+
description: Flujo completo de Test-Driven Development. Ciclo RED (el test falla) → GREEN (implementación mínima) → REFACTOR (limpieza). Incluye cobertura mínima obligatoria, tests de frontera, factories, fixtures y estrategias para diferentes tipos de código (APIs, services, componentes Angular).
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Habilidad: TDD Workflow Completo
|
|
7
|
+
|
|
8
|
+
## Propósito
|
|
9
|
+
|
|
10
|
+
TDD no es "escribir tests después" ni "escribir tests antes por costumbre". Es
|
|
11
|
+
un método de diseño donde los tests guían la API pública del código antes de
|
|
12
|
+
que exista la implementación. El resultado es código que hace exactamente lo
|
|
13
|
+
que los tests exigen — ni más, ni menos.
|
|
14
|
+
|
|
15
|
+
## Cuándo activar
|
|
16
|
+
|
|
17
|
+
- CONTEXT.md o PLAN.md indica que la fase requiere TDD
|
|
18
|
+
- Se implementa lógica de negocio crítica (cálculos, validaciones, permisos)
|
|
19
|
+
- El usuario pide explícitamente TDD
|
|
20
|
+
- Se trabaja en un módulo con historial de bugs
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## El ciclo fundamental RED → GREEN → REFACTOR
|
|
25
|
+
|
|
26
|
+
### Fase RED — El test debe fallar por la razón correcta
|
|
27
|
+
|
|
28
|
+
**Paso 1**: Escribir el test que describe el comportamiento esperado.
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
# RED: Este test falla porque calcular_descuento no existe todavía
|
|
32
|
+
def test_descuento_cliente_premium_es_15_porciento():
|
|
33
|
+
cliente = ClienteFactory(tipo="premium")
|
|
34
|
+
resultado = calcular_descuento(cliente, monto=100.0)
|
|
35
|
+
assert resultado == 15.0
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
**Verificar que el test falla BIEN**:
|
|
39
|
+
- Falla con `NameError` o `ImportError` si la función no existe: CORRECTO
|
|
40
|
+
- Falla con `AssertionError` si el comportamiento es incorrecto: CORRECTO
|
|
41
|
+
- Falla con `TypeError` si la firma es incorrecta: CORRECTO
|
|
42
|
+
- Pasa sin que exista implementación: SEÑAL DE ALARMA — el test no prueba nada
|
|
43
|
+
|
|
44
|
+
**NUNCA avanzar a GREEN si el test pasa en RED.**
|
|
45
|
+
|
|
46
|
+
### Fase GREEN — Implementación mínima
|
|
47
|
+
|
|
48
|
+
**Regla de oro**: Implementar solo lo que hace pasar el test. Nada más.
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
# GREEN: Implementación mínima que hace pasar el test
|
|
52
|
+
def calcular_descuento(cliente: Cliente, monto: float) -> float:
|
|
53
|
+
if cliente.tipo == "premium":
|
|
54
|
+
return monto * 0.15
|
|
55
|
+
return 0.0
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
**Anti-patrón GREEN**: implementar todos los casos de una vez sin tests que los
|
|
59
|
+
exijan. Si no hay un test para clientes "gold", no implementes el descuento gold.
|
|
60
|
+
|
|
61
|
+
**Verificar**: `pytest -v test_descuentos.py` pasa con el test nuevo.
|
|
62
|
+
|
|
63
|
+
### Fase REFACTOR — Limpieza sin cambiar comportamiento
|
|
64
|
+
|
|
65
|
+
**Qué refactorizar en esta fase**:
|
|
66
|
+
- Nombres de variables o funciones poco claros
|
|
67
|
+
- Duplicación de lógica (si ya existe en otro test)
|
|
68
|
+
- Magic numbers que deberían ser constantes
|
|
69
|
+
- Estructura de código que anticipa el próximo test
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
# REFACTOR: Extraer constante y mejorar legibilidad
|
|
73
|
+
DESCUENTO_POR_TIPO = {
|
|
74
|
+
"premium": 0.15,
|
|
75
|
+
"gold": 0.20,
|
|
76
|
+
"standard": 0.0,
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
def calcular_descuento(cliente: Cliente, monto: float) -> float:
|
|
80
|
+
tasa = DESCUENTO_POR_TIPO.get(cliente.tipo, 0.0)
|
|
81
|
+
return monto * tasa
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
**Verificar**: todos los tests siguen pasando después del refactor.
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## Tests de frontera (boundary tests)
|
|
89
|
+
|
|
90
|
+
Para toda función que procesa datos, escribir tests de:
|
|
91
|
+
|
|
92
|
+
| Tipo de frontera | Ejemplo |
|
|
93
|
+
|----------------|---------|
|
|
94
|
+
| Valor cero | `monto=0.0` |
|
|
95
|
+
| Valor negativo | `monto=-100.0` |
|
|
96
|
+
| Valor máximo | `monto=999_999_999.99` |
|
|
97
|
+
| String vacío | `nombre=""` |
|
|
98
|
+
| None / null | `cliente=None` |
|
|
99
|
+
| Lista vacía | `items=[]` |
|
|
100
|
+
| Un solo elemento | `items=[item]` |
|
|
101
|
+
| Muchos elementos | `items=lista_de_10000` |
|
|
102
|
+
| Valor fuera de dominio | `tipo="inexistente"` |
|
|
103
|
+
| Caracteres especiales | `nombre="<script>alert(1)</script>"` |
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## Factories y Fixtures
|
|
108
|
+
|
|
109
|
+
### Factories (para datos de test)
|
|
110
|
+
|
|
111
|
+
Las factories crean objetos con valores válidos por defecto. Los tests solo
|
|
112
|
+
sobreescriben lo que importa para ese test específico.
|
|
113
|
+
|
|
114
|
+
**Python con factory_boy**:
|
|
115
|
+
```python
|
|
116
|
+
import factory
|
|
117
|
+
from myapp.models import Cliente, Pedido
|
|
118
|
+
|
|
119
|
+
class ClienteFactory(factory.Factory):
|
|
120
|
+
class Meta:
|
|
121
|
+
model = Cliente
|
|
122
|
+
|
|
123
|
+
id = factory.Sequence(lambda n: f"cliente-{n}")
|
|
124
|
+
nombre = factory.Faker("name", locale="es_MX")
|
|
125
|
+
email = factory.Faker("email")
|
|
126
|
+
tipo = "standard" # default explícito
|
|
127
|
+
activo = True
|
|
128
|
+
|
|
129
|
+
# Uso en test
|
|
130
|
+
def test_descuento_premium():
|
|
131
|
+
# Solo especificar lo que importa para este test
|
|
132
|
+
cliente = ClienteFactory(tipo="premium")
|
|
133
|
+
assert calcular_descuento(cliente, 100.0) == 15.0
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
**TypeScript con factory functions**:
|
|
137
|
+
```typescript
|
|
138
|
+
// factories/user.factory.ts
|
|
139
|
+
export const createUser = (overrides: Partial<User> = {}): User => ({
|
|
140
|
+
id: 'user-1',
|
|
141
|
+
name: 'Test User',
|
|
142
|
+
email: 'test@example.com',
|
|
143
|
+
role: 'standard',
|
|
144
|
+
active: true,
|
|
145
|
+
...overrides,
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// Uso en test
|
|
149
|
+
it('should show admin panel for admin users', () => {
|
|
150
|
+
const user = createUser({ role: 'admin' });
|
|
151
|
+
// ...
|
|
152
|
+
});
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Fixtures (para estado persistente)
|
|
156
|
+
|
|
157
|
+
```python
|
|
158
|
+
# conftest.py
|
|
159
|
+
import pytest
|
|
160
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
161
|
+
|
|
162
|
+
@pytest.fixture
|
|
163
|
+
async def db_session():
|
|
164
|
+
"""Sesión de BD en transacción que hace rollback al terminar."""
|
|
165
|
+
async with AsyncSessionLocal() as session:
|
|
166
|
+
async with session.begin():
|
|
167
|
+
yield session
|
|
168
|
+
await session.rollback()
|
|
169
|
+
|
|
170
|
+
@pytest.fixture
|
|
171
|
+
async def cliente_premium(db_session: AsyncSession):
|
|
172
|
+
"""Cliente premium persistido en BD de test."""
|
|
173
|
+
cliente = ClienteFactory.build(tipo="premium")
|
|
174
|
+
db_session.add(cliente)
|
|
175
|
+
await db_session.flush()
|
|
176
|
+
return cliente
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## TDD por tipo de código
|
|
182
|
+
|
|
183
|
+
### Services (lógica de negocio)
|
|
184
|
+
|
|
185
|
+
```python
|
|
186
|
+
# Orden de tests para un service nuevo:
|
|
187
|
+
# 1. Caso feliz principal
|
|
188
|
+
# 2. Validaciones de input inválido
|
|
189
|
+
# 3. Casos de borde del dominio
|
|
190
|
+
# 4. Interacciones con dependencias (mocks)
|
|
191
|
+
|
|
192
|
+
@pytest.mark.asyncio
|
|
193
|
+
async def test_crear_pedido_valida_stock_disponible():
|
|
194
|
+
producto = ProductoFactory(stock=5)
|
|
195
|
+
with pytest.raises(StockInsuficienteError):
|
|
196
|
+
await PedidoService.crear(producto_id=producto.id, cantidad=10)
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### Endpoints FastAPI
|
|
200
|
+
|
|
201
|
+
```python
|
|
202
|
+
# Usar TestClient de FastAPI
|
|
203
|
+
from fastapi.testclient import TestClient
|
|
204
|
+
|
|
205
|
+
def test_endpoint_requiere_autenticacion():
|
|
206
|
+
response = client.get("/api/v1/pedidos")
|
|
207
|
+
assert response.status_code == 401
|
|
208
|
+
|
|
209
|
+
def test_endpoint_retorna_solo_pedidos_del_usuario(cliente_autenticado):
|
|
210
|
+
pedido_propio = PedidoFactory(usuario_id=cliente_autenticado.id)
|
|
211
|
+
pedido_ajeno = PedidoFactory(usuario_id="otro-usuario")
|
|
212
|
+
|
|
213
|
+
response = cliente_autenticado.get("/api/v1/pedidos")
|
|
214
|
+
ids = [p["id"] for p in response.json()["items"]]
|
|
215
|
+
|
|
216
|
+
assert pedido_propio.id in ids
|
|
217
|
+
assert pedido_ajeno.id not in ids # IDOR check
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### Componentes Angular
|
|
221
|
+
|
|
222
|
+
```typescript
|
|
223
|
+
// Usar TestBed + ComponentHarness
|
|
224
|
+
describe('PedidosComponent', () => {
|
|
225
|
+
it('should display empty state when no orders exist', async () => {
|
|
226
|
+
const mockService = { getPedidos: () => of({ items: [], total: 0 }) };
|
|
227
|
+
await TestBed.configureTestingModule({
|
|
228
|
+
providers: [{ provide: PedidosService, useValue: mockService }]
|
|
229
|
+
}).compileComponents();
|
|
230
|
+
|
|
231
|
+
const fixture = TestBed.createComponent(PedidosComponent);
|
|
232
|
+
fixture.detectChanges();
|
|
233
|
+
|
|
234
|
+
const emptyState = fixture.nativeElement.querySelector('[data-testid="empty-state"]');
|
|
235
|
+
expect(emptyState).toBeTruthy();
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
242
|
+
## Cobertura mínima obligatoria
|
|
243
|
+
|
|
244
|
+
| Tipo de módulo | Cobertura mínima |
|
|
245
|
+
|---------------|-----------------|
|
|
246
|
+
| Services (lógica crítica) | 90% |
|
|
247
|
+
| Endpoints (API) | 85% |
|
|
248
|
+
| Utilities / helpers | 95% |
|
|
249
|
+
| Componentes Angular | 75% |
|
|
250
|
+
| Modelos ORM | 70% |
|
|
251
|
+
|
|
252
|
+
**Verificar** con reporte de cobertura antes de marcar tarea como completada:
|
|
253
|
+
```bash
|
|
254
|
+
pytest --cov=src/services --cov-fail-under=90
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
---
|
|
258
|
+
|
|
259
|
+
## Anti-patrones TDD a evitar
|
|
260
|
+
|
|
261
|
+
| Anti-patrón | Descripción | Solución |
|
|
262
|
+
|-------------|-------------|---------|
|
|
263
|
+
| Test del mock | El test solo verifica que se llamó el mock, no el comportamiento real | Testear el efecto observable |
|
|
264
|
+
| Test omnibus | Un solo test que verifica 10 cosas a la vez | Un test, un comportamiento |
|
|
265
|
+
| Test frágil | Falla si cambias nombres internos sin cambiar comportamiento | Testear comportamiento, no implementación |
|
|
266
|
+
| Fixture global | Un fixture que modifica estado global compartido entre tests | Fixtures con scope limitado, rollback |
|
|
267
|
+
| Skip como solución | `@pytest.mark.skip` para tests que fallan | Arreglar el bug o eliminar el test |
|