Skill de Refactorización
Usa este skill al refactorizar funcionalidades para alinearlas con patrones establecidos, eliminar duplicación y mejorar la reutilización de código.
Para saber si una UI pertenece a pattern-library o a una composición local, usa el skill design-system.
Análisis Previo a la Refactorización
1. Identificar Funcionalidades Similares
- Encuentra funcionalidades comparables en la base de código con funcionalidad similar
- Compara estructuras de directorios, patrones de componentes y uso de utilidades
- Busca funcionalidades con funcionalidad similar pero implementaciones diferentes
- Usa búsqueda semántica para encontrar funcionalidades relacionadas: "funcionalidades con patrones de tabla/cuadrícula de datos similares"
2. Encontrar Oportunidades de Duplicación
Busca patrones de código duplicado:
- Componentes: Busca archivos de componentes similares entre funcionalidades (mismo nombre o funcionalidad similar)
- Hooks: Verifica lógica duplicada de hooks (hooks de tabla, hooks de filtro, hooks de agregación)
- Utilidades: Encuentra funciones de utilidad duplicadas (agregaciones, formateadores, validadores, transformadores)
- Estilos: Identifica patrones de prop
sxduplicados o estilos en línea - Constantes: Verifica constantes u objetos de configuración duplicados
- Tipos: Busca definiciones de tipos o interfaces duplicadas
3. Verificar Utilidades Compartidas
Antes de crear nuevas utilidades, verifica las existentes:
- Busca en directorios
@/util/utilidades específicas de dominio - Revisa
@/util/datespara funciones de formato de fechas - Revisa
@/util/numberspara funciones de formato de números - Revisa
@/components/para componentes reutilizables - Revisa
pattern-librarypara componentes del sistema de diseño - Usa grep/búsqueda para encontrar implementaciones existentes antes de crear nuevas
Proceso de Refactorización
Paso 1: Extraer Código Compartido
Al encontrar duplicados:
-
Crea utilidades compartidas en los directorios
@/util/apropiados:// Ejemplo: util/[domain]/[category].ts export const sharedFunction = () => { ... }; export const sharedConstant = { ... }; -
Actualiza todas las funcionalidades para importar desde la ubicación compartida:
// Archivo específico de funcionalidad - reexportar compartido export { sharedFunction, sharedConstant } from "@/util/[domain]/[category]"; -
Elimina implementaciones duplicadas de los directorios de funcionalidades
-
Actualiza todas las importaciones en la base de código
Paso 2: Consolidar Componentes
Al encontrar componentes duplicados:
- Identifica la versión más completa/rica en funcionalidades (verifica props, funcionalidad, pruebas)
- Extrae a una ubicación compartida (
@/components/o ubicación independiente de funcionalidad) - Haz las props flexibles para soportar todos los casos de uso (usa tipos unión, props opcionales o genéricos)
- Actualiza importaciones en todas las funcionalidades que usan el componente
- Elimina archivos de componentes duplicados
- Verifica que todos los usos sigan funcionando correctamente
Paso 3: Alinear Patrones de Carga
Para estados de carga:
- Verifica archivos
loading.tsx- deben ser esqueletos simples, no diseños de página completos - Elimina cargadores de esqueleto a nivel de componente - confía en
loading.tsxa nivel de página para la carga inicial - Durante recargas (refetches): Muestra datos existentes, no esqueletos (a menos que sea explícitamente necesario para UX)
- Coincide con patrones de funcionalidades similares en la base de código
- Usa las convenciones de Next.js App Router -
loading.tsxmaneja la carga inicial, los componentes manejan estados de recarga
Paso 4: Consolidar Estilos
Para estilos:
- Extrae patrones de estilo reutilizables a componentes compartidos o utilidades de estilo
- Elimina props
sxen línea cuando exista un componente compartido - Usa componentes del sistema de diseño de
pattern-libraryen lugar de estilos personalizados - Verifica objetos de estilo duplicados que podrían ser utilidades compartidas o extensiones de tema
- Sigue patrones de Material-UI - usa valores del tema, evita números mágicos
Paso 5: Verificar Alineación de Patrones
Asegura la consistencia con funcionalidades establecidas:
- Compara estructuras de directorios - deben seguir patrones similares (
_components/,_hooks/,_utils/) - Verifica patrones de hooks - hooks más simples están bien si los requisitos de la funcionalidad difieren
- Verifica el flujo de datos - debe coincidir con funcionalidades similares cuando sea posible
- Revisa la composición de componentes - debe usar patrones similares (wrappers, composición, etc.)
- Verifica convenciones de nomenclatura - sigue patrones establecidos (PascalCase para componentes, camelCase para hooks)
Paso 6: Consolidación de Funciones de Utilidad
Para funciones de utilidad:
- Verifica si la función existe primero en utilidades compartidas (busca en la base de código)
- Compara implementaciones - usa la versión más completa/robusta
- Consolida duplicados - mueve a una ubicación compartida, actualiza todas las importaciones
- Documenta diferencias - si las implementaciones difieren intencionalmente, documenta por qué
- Preserva la funcionalidad - asegura que la versión consolidada maneje todos los casos de uso
Paso 7: Limpiar Comentarios
Después de la refactorización, elimina comentarios innecesarios:
- Elimina notas de migración - Comentarios que explican de dónde se movió el código (ej., "isCollecting ahora se exporta desde Table/utils.ts")
- Elimina comentarios obvios - Comentarios que solo repiten lo que hace el código (ej., "// Reexportar agregaciones compartidas" cuando el código muestra claramente las reexportaciones)
- Mantén comentarios explicativos - Comentarios que explican POR QUÉ se hace algo (ej., "// Usar setTimeout para asegurar que el estado del formulario se actualice")
- Mantén comentarios de lógica compleja - Comentarios que explican lógica de negocio no obvia o casos límite
- Elimina comentarios TODO/FIXME - A menos que sean elementos de trabajo futuro legítimos
Ejemplos de comentarios innecesarios:
// ❌ Innecesario - obvio por el código
// Reexportar agregaciones compartidas desde util
export { latest, totalRange } from "@/util/keywords/gridAggregations";
// ❌ Innecesario - nota de migración, ya no es necesaria
// isCollecting ahora se exporta desde Table/utils.ts para evitar duplicación
// Impórtalo desde allí: import { isCollecting } from "../KeywordListDetail/Table/utils";
Ejemplos de buenos comentarios:
// ✅ Bueno - explica POR QUÉ
// Usar setTimeout para asegurar que el estado del formulario se actualice después del procesamiento CSV
setTimeout(() => {
void handleSubmit(onSubmit)();
}, 100);
// ✅ Bueno - explica lógica compleja
// Una palabra clave se actualiza si:
// 1. Su valor lastUpdated cambió (el indicador más fiable), O
// 2. Ya no está en estado de recolección Y hemos sondeado suficientes veces (fallback)
Paso 8: Mejoras de Calidad de Código
Aplica patrones de calidad de código consistentes en todo el código refactorizado:
Comprobaciones de Null/Undefined
Simplifica las comprobaciones de null y undefined usando igualdad débil:
// ❌ Verboso
if (value !== undefined && value !== null) { ... }
if (value !== undefined && value !== null && value !== "") { ... }
// ✅ Simplificado
if (value != null) { ... }
if (value != null && value !== "") { ... }
Nota: Usa != null (igualdad débil) para comprobar tanto null como undefined en una sola comprobación. Esto es idiomático en TypeScript/JavaScript y más conciso.
Importaciones Solo de Tipo
Usa import type para importaciones solo de tipo para mejorar el rendimiento de compilación y hacer la intención clara:
// ❌ Importación en tiempo de ejecución para uso solo de tipo
import { SomeType } from "app-api/types/domain";
// ✅ Importación solo de tipo
import type { SomeType } from "app-api/types/domain";
Aplica a:
- Definiciones de tipo de
app-api - Importaciones de interfaces
- Tipos de utilidad solo de tipo
- Cualquier importación usada solo en anotaciones de tipo
Eliminar Aserciones de Tipo Innecesarias
Elimina aserciones de tipo cuando TypeScript pueda inferir el tipo correctamente:
// ❌ Aserción innecesaria
const items = data.map((item) => item as SomeType);
// ✅ Deja que TypeScript infiera
const items = data.map((item) => item);
Cuándo mantener aserciones: Solo cuando TypeScript genuinamente no pueda inferir el tipo, o al estrechar tipos de una manera que TypeScript no pueda verificar.
Eliminar Coalescencia Nula Innecesaria
Elimina ?? null cuando el valor ya sea nullable o TypeScript infiera el tipo correcto:
// ❌ Innecesario - valueGetter ya devuelve nullable
valueGetter: (value, row) => {
return row.someField ?? null;
};
// ✅ Simplificado
valueGetter: (value, row) => {
return row.someField;
};
Cuándo mantener: Cuando se convierta explícitamente undefined a null para compatibilidad con API o cuando el sistema de tipos lo requiera.
Eliminar Props No Usadas
Elimina props de interfaces de componentes e implementaciones cuando no se usen:
// ❌ Prop no usada
interface ComponentProps {
item: Item;
unusedField: string; // Nunca se usa en el componente
}
// ✅ Interfaz limpia
interface ComponentProps {
item: Item;
}
Agregar Tipos Explícitos a Callbacks
Agrega anotaciones de tipo explícitas a parámetros de callback cuando TypeScript no pueda inferirlos:
// ❌ any implícito
const items = ids.map((id) => { ... });
// ✅ Tipo explícito
const items = ids.map((id: string | number) => { ... });
Aplica a: Métodos de array (map, filter, forEach), manejadores de eventos y otros callbacks donde los tipos no sean claros por contexto.
Extraer Patrones JSX Repetidos
Extrae patrones JSX repetidos en componentes reutilizables:
// ❌ Patrón repetido
{
isAggregation ? (
<Typography
variant="body2"
fontWeight="medium"
fontStyle="italic"
color="text.secondary"
>
{formattedValue}
</Typography>
) : (
<Typography variant="body2" fontWeight="medium">
{formattedValue}
</Typography>
);
}
// ✅ Componente extraído
<AggregationTypography
formattedValue={formattedValue}
isAggregation={isAggregation}
/>;
Usar Funciones Fábrica para Implementaciones Variadas
Cuando la lógica es similar pero necesita manejar diferentes estructuras de datos, usa funciones fábrica:
// ✅ Patrón de función fábrica
export const createIntentOperators = <T extends { intent?: Intent | Intent[] }>(
normalizeToArray: (value: T) => Intent[]
) => {
return {
// Operadores que trabajan con array normalizado
};
};
// Uso en diferentes funcionalidades
const operators1 = createIntentOperators<Feature1Type>((item) => [item.intent]);
const operators2 = createIntentOperators<Feature2Type>(
(item) => item.intents || []
);
Beneficios: Reutiliza la lógica central mientras permite que diferentes estructuras de datos se manejen adecuadamente.
Refuerzo de Revisión de Código (comentarios recurrentes)
Aplica estos cuando la refactorización toque cuadrículas (grids), paneles (dashboards), fetches o formas compartidas de "fila + métricas" (se alinea con comentarios repetidos en PR de Moz Pro UI).
DataGrid: alternancia de panel de detalle
- Prefiere
DetailPanelToggledepattern-librarypara expandir/colapsar en una columna de panel de detalle en lugar de unIconButton+@mui/iconsad hoc en una celda personalizada. - Conecta accesibilidad: pasa
buttonTabIndex={0}cuando la celda deba poder recibir foco mediante tabulación (verapp/(default)/competitive-research/top-competing-content/_module/components/Table/columns.tsx,SerpFeaturesCell). - Diseño / centrado: usa
sxenBox/Stack(odirection/spacingdeStackcuando ese sea el patrón establecido). No reemplacessxcon props de estilo de sistema heredadas de MUI enBox(ej.,display,alignItemscomo props de nivel superior)—MUI está deprecando ese enfoque en favor desx.
Tipado: evita formas de fila object / anónimas
- No uses
readonly object[]parabrand_definitions, fragmentos de fila o listas adyacentes a API cuando exista un tipo estructural o se pueda definir una vez. - Define una interfaz "como" compartida (ej.,
BrandDefinitionLike) en el móduloutilsde la funcionalidad; expórtala y un helper de línea de visualización único (ej.,getBrandDefinitionDisplayLine) en lugar de duplicargetBrandNamecon casts dentro de celdas. - Elimina casts redundantes (
as BrandDefinitionLike[]) después de tipar los parámetros. - Reutiliza helpers de dominio de
*_module/utils/dataHelperscuando la forma sea unBrandDefinitionreal; usa el helper más flexible solo donde las cargas útiles de fila de prompt difieran.
Typography vs Box (pattern-library)
- Para texto (estados vacíos, "—" en línea, tramos enfatizados), prefiere
Typographydepattern-libraryconcomponent="span"cuando sea apropiado—noBoxfingiendo ser copia. - API de
Typography: usavariant,color,fontWeight,componentdonde expresen semántica (estos no están deprecados). Box/ wrappers de diseño: prefieresxpara espaciado, diseño flexible, dimensionamiento y valores de tema. Evita refactorizaciones que muevan estilos desxa props shorthand de estilo deBox; eso entra en conflicto con la guía actual de MUI.
Límites de componentes: props vs estado interno
- Deriva booleanos de
datadentro del componente cuando el padre no los necesite para diseño o estado de URL (ej., "tiene términos de marca" debrand_termsde la marca principal). - Mantén alternancias solo de UI internas (ej., cambiar entre dos modos de gráfico en una tarjeta) a menos que otro hermano o ruta deba manejar el mismo estado—entonces documenta por qué es necesario elevarlo.
Flujo de control y JSX
- Prefiere
returntemprano para ramas de carga/vacío en lugar de un gran ternario asignado a una variable inmediatamente antes de un soloreturn, a menos que la memoización requiera genuinamente una expresión. - Comparadores de ordenación: nunca codifiques un centinela mágico (ej.,
5) vinculado a un máximo de dominio asumido; usaNumber.POSITIVE_INFINITY/ explícito "ordenar al final" para null/undefined para que el comportamiento se mantenga correcto si los límites cambian.
Fetches y paginación
- Evita un
limitpequeño fijo (ej., 100) para listas a escala de panel cuando el backend esté paginado y los proyectos puedan exceder una página. - Itera con un tamaño de página estable hasta que una página devuelva menos filas que el límite o cero filas, o sigue el contrato documentado de la acción (ver docs de acción
moz-pro-service/ Bruno para ellimitmáximo). - Expone
totalo totales derivados de manera consistente si los llamadores necesitan conteos.
Envoltorios de presentación: no repitas lo obvio
- Elimina campos de estado alternativo redundantes cuando la
Cardpadre ya establezca el título visible (ej., evita duplicarcardContentTitleque coincida con el título de la tarjeta). - Comprobaciones de null: si una prop está tipada como
string, no hagas una comprobación de null en ella; si puede ser null, el tipo debería reflejarlo.