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,328 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: react-optimizacion
|
|
3
|
+
description: Optimización de rendimiento en React. memo, useMemo, useCallback, code splitting, lazy loading, concurrent features, Suspense y patrones de estado eficiente.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Optimización de Rendimiento en React
|
|
7
|
+
|
|
8
|
+
## Principio de optimización prematura
|
|
9
|
+
|
|
10
|
+
**No optimices antes de medir.** La mayoría de los problemas de rendimiento en React
|
|
11
|
+
provienen de re-renders innecesarios o de carga inicial pesada. Primero identifica
|
|
12
|
+
el cuello de botella con React DevTools Profiler, luego aplica la solución correcta.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Re-renders: cuándo ocurren y cómo controlarlos
|
|
17
|
+
|
|
18
|
+
Un componente se re-renderiza cuando:
|
|
19
|
+
1. Su propio estado cambia (`useState`, `useReducer`).
|
|
20
|
+
2. Su componente padre se re-renderiza (incluso si las props no cambiaron).
|
|
21
|
+
3. Un contexto del que consume cambia.
|
|
22
|
+
|
|
23
|
+
### React.memo — evitar re-renders por props iguales
|
|
24
|
+
|
|
25
|
+
```tsx
|
|
26
|
+
import React, { memo } from 'react';
|
|
27
|
+
|
|
28
|
+
// Memorizar componente que recibe props estables
|
|
29
|
+
const FacturaCard = memo(function FacturaCard({
|
|
30
|
+
factura,
|
|
31
|
+
onCancelar,
|
|
32
|
+
}: {
|
|
33
|
+
factura: Factura;
|
|
34
|
+
onCancelar: (id: string) => void;
|
|
35
|
+
}) {
|
|
36
|
+
return (
|
|
37
|
+
<div className="border rounded-lg p-4">
|
|
38
|
+
<h3 className="font-semibold">{factura.folio}</h3>
|
|
39
|
+
<p>{factura.cliente}</p>
|
|
40
|
+
<button onClick={() => onCancelar(factura.id)}>Cancelar</button>
|
|
41
|
+
</div>
|
|
42
|
+
);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Comparación personalizada cuando la comparación shallow no es suficiente
|
|
46
|
+
const FacturaTable = memo(
|
|
47
|
+
function FacturaTable({ facturas }: { facturas: Factura[] }) {
|
|
48
|
+
return <table>...</table>;
|
|
49
|
+
},
|
|
50
|
+
(prev, next) => {
|
|
51
|
+
// true = NO re-renderizar
|
|
52
|
+
return prev.facturas.length === next.facturas.length &&
|
|
53
|
+
prev.facturas.every((f, i) => f.id === next.facturas[i].id);
|
|
54
|
+
}
|
|
55
|
+
);
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### useMemo — memorizar cálculos costosos
|
|
59
|
+
|
|
60
|
+
```tsx
|
|
61
|
+
import { useMemo } from 'react';
|
|
62
|
+
|
|
63
|
+
function ResumenFacturas({ facturas }: { facturas: Factura[] }) {
|
|
64
|
+
// Solo recalcular cuando facturas cambia
|
|
65
|
+
const estadisticas = useMemo(() => ({
|
|
66
|
+
total: facturas.reduce((sum, f) => sum + f.monto, 0),
|
|
67
|
+
emitidas: facturas.filter(f => f.estatus === 'emitida').length,
|
|
68
|
+
canceladas: facturas.filter(f => f.estatus === 'cancelada').length,
|
|
69
|
+
promedio: facturas.length > 0
|
|
70
|
+
? facturas.reduce((sum, f) => sum + f.monto, 0) / facturas.length
|
|
71
|
+
: 0,
|
|
72
|
+
}), [facturas]);
|
|
73
|
+
|
|
74
|
+
// MAL: calcular en el render sin useMemo (se recalcula en CADA re-render del padre)
|
|
75
|
+
// const total = facturas.reduce((sum, f) => sum + f.monto, 0);
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
<div>
|
|
79
|
+
<p>Total: ${estadisticas.total}</p>
|
|
80
|
+
<p>Emitidas: {estadisticas.emitidas}</p>
|
|
81
|
+
</div>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### useCallback — estabilizar referencias de funciones
|
|
87
|
+
|
|
88
|
+
```tsx
|
|
89
|
+
import { useCallback } from 'react';
|
|
90
|
+
|
|
91
|
+
function ListaFacturas() {
|
|
92
|
+
const [facturas, setFacturas] = useState<Factura[]>([]);
|
|
93
|
+
|
|
94
|
+
// Sin useCallback, esta función tiene nueva referencia en cada render
|
|
95
|
+
// lo que causa que FacturaCard (con memo) se re-renderice de todas formas
|
|
96
|
+
const handleCancelar = useCallback((id: string) => {
|
|
97
|
+
setFacturas(prev => prev.map(f =>
|
|
98
|
+
f.id === id ? { ...f, estatus: 'cancelada' } : f
|
|
99
|
+
));
|
|
100
|
+
}, []); // Sin dependencias porque usa el setter funcional de useState
|
|
101
|
+
|
|
102
|
+
const handleActualizar = useCallback((id: string, datos: Partial<Factura>) => {
|
|
103
|
+
setFacturas(prev => prev.map(f =>
|
|
104
|
+
f.id === id ? { ...f, ...datos } : f
|
|
105
|
+
));
|
|
106
|
+
}, []);
|
|
107
|
+
|
|
108
|
+
return (
|
|
109
|
+
<div>
|
|
110
|
+
{facturas.map(factura => (
|
|
111
|
+
<FacturaCard
|
|
112
|
+
key={factura.id}
|
|
113
|
+
factura={factura}
|
|
114
|
+
onCancelar={handleCancelar} // Referencia estable → memo funciona
|
|
115
|
+
/>
|
|
116
|
+
))}
|
|
117
|
+
</div>
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## Code Splitting y Lazy Loading
|
|
125
|
+
|
|
126
|
+
### React.lazy + Suspense
|
|
127
|
+
|
|
128
|
+
```tsx
|
|
129
|
+
import React, { lazy, Suspense } from 'react';
|
|
130
|
+
import { Routes, Route } from 'react-router-dom';
|
|
131
|
+
|
|
132
|
+
// Lazy imports — cada módulo se convierte en un chunk separado
|
|
133
|
+
const FacturasPage = lazy(() => import('./pages/FacturasPage'));
|
|
134
|
+
const ReportesPage = lazy(() => import('./pages/ReportesPage'));
|
|
135
|
+
const ConfiguracionPage = lazy(() => import('./pages/ConfiguracionPage'));
|
|
136
|
+
|
|
137
|
+
function App() {
|
|
138
|
+
return (
|
|
139
|
+
<Suspense fallback={<PageLoader />}>
|
|
140
|
+
<Routes>
|
|
141
|
+
<Route path="/facturas" element={<FacturasPage />} />
|
|
142
|
+
<Route path="/reportes" element={<ReportesPage />} />
|
|
143
|
+
<Route path="/configuracion" element={<ConfiguracionPage />} />
|
|
144
|
+
</Routes>
|
|
145
|
+
</Suspense>
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Lazy loading de componentes pesados
|
|
151
|
+
|
|
152
|
+
```tsx
|
|
153
|
+
import { lazy, Suspense, useState } from 'react';
|
|
154
|
+
|
|
155
|
+
// Cargar gráfica solo cuando el usuario la solicita
|
|
156
|
+
const GraficaVentas = lazy(() => import('./GraficaVentas'));
|
|
157
|
+
|
|
158
|
+
function Dashboard() {
|
|
159
|
+
const [mostrarGrafica, setMostrarGrafica] = useState(false);
|
|
160
|
+
|
|
161
|
+
return (
|
|
162
|
+
<div>
|
|
163
|
+
<button onClick={() => setMostrarGrafica(true)}>
|
|
164
|
+
Ver gráfica de ventas
|
|
165
|
+
</button>
|
|
166
|
+
{mostrarGrafica && (
|
|
167
|
+
<Suspense fallback={<div className="h-64 animate-pulse bg-gray-100" />}>
|
|
168
|
+
<GraficaVentas />
|
|
169
|
+
</Suspense>
|
|
170
|
+
)}
|
|
171
|
+
</div>
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## Concurrent Features
|
|
179
|
+
|
|
180
|
+
### useTransition — marcar actualizaciones como no urgentes
|
|
181
|
+
|
|
182
|
+
```tsx
|
|
183
|
+
import { useTransition, useState } from 'react';
|
|
184
|
+
|
|
185
|
+
function BuscadorFacturas() {
|
|
186
|
+
const [query, setQuery] = useState('');
|
|
187
|
+
const [resultados, setResultados] = useState<Factura[]>([]);
|
|
188
|
+
const [isPending, startTransition] = useTransition();
|
|
189
|
+
|
|
190
|
+
const handleBusqueda = (valor: string) => {
|
|
191
|
+
// Actualización urgente: el input responde inmediatamente
|
|
192
|
+
setQuery(valor);
|
|
193
|
+
|
|
194
|
+
// Actualización no urgente: los resultados pueden esperar
|
|
195
|
+
startTransition(() => {
|
|
196
|
+
const filtrados = buscarFacturas(valor);
|
|
197
|
+
setResultados(filtrados);
|
|
198
|
+
});
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
return (
|
|
202
|
+
<div>
|
|
203
|
+
<input
|
|
204
|
+
value={query}
|
|
205
|
+
onChange={e => handleBusqueda(e.target.value)}
|
|
206
|
+
placeholder="Buscar facturas..."
|
|
207
|
+
/>
|
|
208
|
+
{isPending && <span className="text-gray-400">Buscando...</span>}
|
|
209
|
+
<ListaResultados resultados={resultados} />
|
|
210
|
+
</div>
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### useDeferredValue — versión diferida de un valor
|
|
216
|
+
|
|
217
|
+
```tsx
|
|
218
|
+
import { useDeferredValue, memo } from 'react';
|
|
219
|
+
|
|
220
|
+
function ListaFiltrada({ items }: { items: string[] }) {
|
|
221
|
+
const [filtro, setFiltro] = useState('');
|
|
222
|
+
// Versión diferida del filtro — permite que el input sea fluido
|
|
223
|
+
const filtroDeferred = useDeferredValue(filtro);
|
|
224
|
+
|
|
225
|
+
// isStale detecta si estamos mostrando resultado desactualizado
|
|
226
|
+
const isStale = filtro !== filtroDeferred;
|
|
227
|
+
|
|
228
|
+
const itemsFiltrados = useMemo(
|
|
229
|
+
() => items.filter(item => item.includes(filtroDeferred)),
|
|
230
|
+
[items, filtroDeferred]
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
return (
|
|
234
|
+
<div>
|
|
235
|
+
<input value={filtro} onChange={e => setFiltro(e.target.value)} />
|
|
236
|
+
<ul style={{ opacity: isStale ? 0.6 : 1 }}>
|
|
237
|
+
{itemsFiltrados.map(item => <li key={item}>{item}</li>)}
|
|
238
|
+
</ul>
|
|
239
|
+
</div>
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
## Virtualización para listas largas
|
|
247
|
+
|
|
248
|
+
```tsx
|
|
249
|
+
import { useVirtualizer } from '@tanstack/react-virtual';
|
|
250
|
+
import { useRef } from 'react';
|
|
251
|
+
|
|
252
|
+
function ListaVirtualizada({ facturas }: { facturas: Factura[] }) {
|
|
253
|
+
const contenedorRef = useRef<HTMLDivElement>(null);
|
|
254
|
+
|
|
255
|
+
const virtualizer = useVirtualizer({
|
|
256
|
+
count: facturas.length,
|
|
257
|
+
getScrollElement: () => contenedorRef.current,
|
|
258
|
+
estimateSize: () => 80, // altura estimada en px por fila
|
|
259
|
+
overscan: 5, // filas extra fuera del viewport
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
return (
|
|
263
|
+
<div ref={contenedorRef} style={{ height: '600px', overflow: 'auto' }}>
|
|
264
|
+
<div style={{ height: `${virtualizer.getTotalSize()}px`, position: 'relative' }}>
|
|
265
|
+
{virtualizer.getVirtualItems().map(virtualItem => (
|
|
266
|
+
<div
|
|
267
|
+
key={virtualItem.key}
|
|
268
|
+
style={{
|
|
269
|
+
position: 'absolute',
|
|
270
|
+
top: 0,
|
|
271
|
+
left: 0,
|
|
272
|
+
width: '100%',
|
|
273
|
+
transform: `translateY(${virtualItem.start}px)`,
|
|
274
|
+
}}
|
|
275
|
+
>
|
|
276
|
+
<FacturaCard factura={facturas[virtualItem.index]} />
|
|
277
|
+
</div>
|
|
278
|
+
))}
|
|
279
|
+
</div>
|
|
280
|
+
</div>
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
---
|
|
286
|
+
|
|
287
|
+
## Optimización de Context
|
|
288
|
+
|
|
289
|
+
El contexto causa re-render en TODOS los consumidores cuando cambia.
|
|
290
|
+
Dividir en contextos más pequeños o usar selectores:
|
|
291
|
+
|
|
292
|
+
```tsx
|
|
293
|
+
// MAL: un solo contexto con todo el estado
|
|
294
|
+
const AppContext = createContext<AppState>({...});
|
|
295
|
+
|
|
296
|
+
// BIEN: separar por dominio y frecuencia de cambio
|
|
297
|
+
const AuthContext = createContext<AuthState>({...}); // Cambia raramente
|
|
298
|
+
const UIContext = createContext<UIState>({...}); // Cambia frecuentemente
|
|
299
|
+
const FacturasContext = createContext<FacturasState>({...}); // Cambia al interactuar
|
|
300
|
+
|
|
301
|
+
// BIEN: con useMemo para estabilizar el valor del contexto
|
|
302
|
+
function AuthProvider({ children }: { children: React.ReactNode }) {
|
|
303
|
+
const [usuario, setUsuario] = useState<Usuario | null>(null);
|
|
304
|
+
|
|
305
|
+
const value = useMemo(() => ({
|
|
306
|
+
usuario,
|
|
307
|
+
login: async (creds: Credentials) => { ... },
|
|
308
|
+
logout: () => setUsuario(null),
|
|
309
|
+
}), [usuario]); // Solo se crea objeto nuevo cuando usuario cambia
|
|
310
|
+
|
|
311
|
+
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
|
|
312
|
+
}
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
---
|
|
316
|
+
|
|
317
|
+
## Cuándo NO usar memo/useMemo/useCallback
|
|
318
|
+
|
|
319
|
+
Estas optimizaciones tienen un costo: complejidad de código y overhead de comparación.
|
|
320
|
+
No aplicar cuando:
|
|
321
|
+
|
|
322
|
+
- El componente es simple y rápido de renderizar.
|
|
323
|
+
- Las props cambian en casi todos los re-renders de todas formas.
|
|
324
|
+
- El cálculo en `useMemo` es trivial (suma de 3 números).
|
|
325
|
+
- No hay evidencia medida de problema de rendimiento.
|
|
326
|
+
|
|
327
|
+
**Regla práctica**: medir primero con React DevTools Profiler. Si un componente
|
|
328
|
+
aparece en llamas largas o con muchos re-renders no esperados, entonces optimizar.
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: release-semver
|
|
3
|
+
description: Versionado semántico (SemVer). Cuándo bumpar major/minor/patch, changelogs convencionales, estrategia de tags y proceso de release completo.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Versionado Semántico (SemVer)
|
|
7
|
+
|
|
8
|
+
## Principio fundamental
|
|
9
|
+
|
|
10
|
+
Un número de versión tiene la forma `MAJOR.MINOR.PATCH` (ej. `2.4.1`). Cada segmento comunica
|
|
11
|
+
intención de compatibilidad a los consumidores del paquete o servicio.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Cuándo bumpar cada segmento
|
|
16
|
+
|
|
17
|
+
### PATCH — arreglos compatibles hacia atrás
|
|
18
|
+
|
|
19
|
+
Incrementa PATCH cuando:
|
|
20
|
+
- Se corrige un bug sin cambiar la interfaz pública.
|
|
21
|
+
- Se corrige documentación o typos en código que afectan comportamiento.
|
|
22
|
+
- Se mejoran mensajes de error sin cambiar el tipo de excepción.
|
|
23
|
+
- Se actualizan dependencias de parche (`requests 2.31.0 → 2.31.1`).
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
1.4.2 → 1.4.3 # fix: corrige cálculo incorrecto en factura
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### MINOR — funcionalidad nueva compatible hacia atrás
|
|
30
|
+
|
|
31
|
+
Incrementa MINOR cuando:
|
|
32
|
+
- Se agrega un endpoint, método o parámetro opcional nuevo.
|
|
33
|
+
- Se agrega una funcionalidad que los consumidores pueden ignorar.
|
|
34
|
+
- Se depreca algo (pero aún funciona).
|
|
35
|
+
- Se actualiza una dependencia minor que agrega capacidades.
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
1.4.3 → 1.5.0 # feat: agrega exportación a PDF
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Al subir MINOR, el PATCH siempre regresa a 0.
|
|
42
|
+
|
|
43
|
+
### MAJOR — cambios incompatibles hacia atrás (breaking changes)
|
|
44
|
+
|
|
45
|
+
Incrementa MAJOR cuando:
|
|
46
|
+
- Se elimina un endpoint, método o campo.
|
|
47
|
+
- Se cambia el tipo o contrato de un parámetro existente.
|
|
48
|
+
- Se cambia el comportamiento esperado de forma que rompe integraciones existentes.
|
|
49
|
+
- Se cambia la autenticación/autorización de forma incompatible.
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
1.5.0 → 2.0.0 # feat!: migra autenticación de JWT a OAuth2
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Al subir MAJOR, MINOR y PATCH regresan a 0.
|
|
56
|
+
|
|
57
|
+
### Versión 0.x.y — desarrollo inicial
|
|
58
|
+
|
|
59
|
+
Mientras la versión MAJOR sea `0`, la API se considera inestable. Cualquier MINOR puede
|
|
60
|
+
contener breaking changes. Cuando el software está listo para producción, se lanza `1.0.0`.
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## Commits Convencionales
|
|
65
|
+
|
|
66
|
+
Los commits convencionales generan el changelog automáticamente y determinan el bump.
|
|
67
|
+
|
|
68
|
+
### Formato
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
<tipo>[scope opcional][! para breaking]: <descripción corta>
|
|
72
|
+
|
|
73
|
+
[cuerpo opcional]
|
|
74
|
+
|
|
75
|
+
[footer opcional: BREAKING CHANGE: descripción]
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Tipos y su impacto en versión
|
|
79
|
+
|
|
80
|
+
| Tipo | Impacto versión | Descripción |
|
|
81
|
+
|------------|----------------|--------------------------------------------------|
|
|
82
|
+
| `fix` | PATCH | Corrección de bug |
|
|
83
|
+
| `feat` | MINOR | Nueva funcionalidad |
|
|
84
|
+
| `feat!` | MAJOR | Nueva funcionalidad con breaking change |
|
|
85
|
+
| `fix!` | MAJOR | Fix con breaking change |
|
|
86
|
+
| `refactor` | ninguno | Refactorización sin cambio de comportamiento |
|
|
87
|
+
| `chore` | ninguno | Tareas de mantenimiento (CI, deps, config) |
|
|
88
|
+
| `docs` | ninguno | Documentación únicamente |
|
|
89
|
+
| `test` | ninguno | Agregar o corregir tests |
|
|
90
|
+
| `perf` | PATCH | Mejora de rendimiento sin cambio de interfaz |
|
|
91
|
+
| `ci` | ninguno | Cambios en pipelines de CI/CD |
|
|
92
|
+
| `style` | ninguno | Formato, espacios, punto y coma (sin lógica) |
|
|
93
|
+
|
|
94
|
+
### Ejemplos de commits bien escritos
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
git commit -m "fix(auth): corrige expiración incorrecta de tokens de refresco"
|
|
98
|
+
git commit -m "feat(reportes): agrega exportación a Excel con formato condicional"
|
|
99
|
+
git commit -m "feat(api)!: cambia paginación de offset a cursor
|
|
100
|
+
|
|
101
|
+
BREAKING CHANGE: el parámetro 'page' fue reemplazado por 'cursor'.
|
|
102
|
+
Los clientes deben actualizar sus integraciones."
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## Proceso de release
|
|
108
|
+
|
|
109
|
+
### Paso 1 — Preparar la rama de release
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
# En main, crear rama release
|
|
113
|
+
git checkout -b release/2.1.0
|
|
114
|
+
|
|
115
|
+
# Actualizar versión en archivos del proyecto
|
|
116
|
+
# Python: pyproject.toml, __version__
|
|
117
|
+
# Node: package.json
|
|
118
|
+
# Java: pom.xml o build.gradle
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Paso 2 — Generar CHANGELOG
|
|
122
|
+
|
|
123
|
+
Usar `conventional-changelog` o `git-cliff` para generar el changelog automáticamente:
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
# Con git-cliff (recomendado)
|
|
127
|
+
git cliff --tag v2.1.0 --output CHANGELOG.md
|
|
128
|
+
|
|
129
|
+
# Con conventional-changelog
|
|
130
|
+
npx conventional-changelog -p conventionalcommits -i CHANGELOG.md -s
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Estructura esperada del CHANGELOG:
|
|
134
|
+
|
|
135
|
+
```markdown
|
|
136
|
+
## [2.1.0] - 2026-03-25
|
|
137
|
+
|
|
138
|
+
### Nuevas funcionalidades
|
|
139
|
+
- feat(reportes): agrega exportación a Excel (#234)
|
|
140
|
+
- feat(usuarios): agrega campo de foto de perfil (#241)
|
|
141
|
+
|
|
142
|
+
### Correcciones
|
|
143
|
+
- fix(auth): corrige expiración incorrecta de tokens (#238)
|
|
144
|
+
- fix(factura): redondeo incorrecto en descuentos (#240)
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Paso 3 — Commit de versión y tag
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
# Commit del bump de versión
|
|
151
|
+
git add pyproject.toml CHANGELOG.md
|
|
152
|
+
git commit -m "chore(release): v2.1.0"
|
|
153
|
+
|
|
154
|
+
# Crear tag anotado (no ligero)
|
|
155
|
+
git tag -a v2.1.0 -m "Release v2.1.0"
|
|
156
|
+
|
|
157
|
+
# Subir rama y tag
|
|
158
|
+
git push origin release/2.1.0
|
|
159
|
+
git push origin v2.1.0
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Paso 4 — Merge a main y develop
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
git checkout main
|
|
166
|
+
git merge --no-ff release/2.1.0
|
|
167
|
+
git push origin main
|
|
168
|
+
|
|
169
|
+
git checkout develop
|
|
170
|
+
git merge --no-ff release/2.1.0
|
|
171
|
+
git push origin develop
|
|
172
|
+
|
|
173
|
+
git branch -d release/2.1.0
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## Estrategia de tags
|
|
179
|
+
|
|
180
|
+
### Tags anotados vs. ligeros
|
|
181
|
+
|
|
182
|
+
Siempre usar **tags anotados** (`git tag -a`) para releases. Los tags ligeros son solo para
|
|
183
|
+
marcado temporal interno. Los tags anotados incluyen autor, fecha y mensaje — esto es esencial
|
|
184
|
+
para `git describe` y herramientas de release.
|
|
185
|
+
|
|
186
|
+
### Convención de nombres
|
|
187
|
+
|
|
188
|
+
```
|
|
189
|
+
v{MAJOR}.{MINOR}.{PATCH} # release estable: v2.1.0
|
|
190
|
+
v{MAJOR}.{MINOR}.{PATCH}-rc.{N} # release candidate: v2.1.0-rc.1
|
|
191
|
+
v{MAJOR}.{MINOR}.{PATCH}-beta.{N} # beta pública: v2.1.0-beta.2
|
|
192
|
+
v{MAJOR}.{MINOR}.{PATCH}-alpha.{N} # alpha interna: v2.1.0-alpha.1
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### Listar y filtrar tags
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
git tag --sort=-v:refname # Lista tags en orden semver descendente
|
|
199
|
+
git tag -l "v2.*" # Solo tags de la serie 2.x
|
|
200
|
+
git describe --tags --abbrev=0 # Último tag del commit actual
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## Herramientas recomendadas
|
|
206
|
+
|
|
207
|
+
| Herramienta | Uso |
|
|
208
|
+
|------------------------|----------------------------------------------|
|
|
209
|
+
| `git-cliff` | Generación de CHANGELOG desde commits |
|
|
210
|
+
| `commitizen` | Wizard interactivo para commits convencionales |
|
|
211
|
+
| `semantic-release` | Release automatizado desde CI |
|
|
212
|
+
| `bump2version` | Bump de versión en archivos Python |
|
|
213
|
+
| `conventional-commits` | Spec oficial de commits convencionales |
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
## Anti-patrones a evitar
|
|
218
|
+
|
|
219
|
+
- **Nunca reutilizar un tag**: Si el release `v2.1.0` se publicó, no se puede mover ese tag.
|
|
220
|
+
Si hay un error, publicar `v2.1.1`.
|
|
221
|
+
- **Nunca usar tags ligeros en releases**: No aportan metadata y dificultan trazabilidad.
|
|
222
|
+
- **Nunca saltarse MAJOR por miedo**: Si hay breaking change, es MAJOR. Ocultarlo como MINOR
|
|
223
|
+
rompe la confianza de los consumidores.
|
|
224
|
+
- **No mezclar tipos de commit en uno solo**: Un commit = un cambio lógico = un tipo.
|
|
225
|
+
- **No escribir changelogs manuales sin estructura**: Usar el formato estándar para que las
|
|
226
|
+
herramientas puedan procesarlos.
|