Gestiona el estado complejo de formularios multi-paso con useReducer - acciones explícitas como SET_FIELD, NEXT_STEP y VALIDATE hacen que las transiciones de estado sean predecibles, probables y fáciles de depurar sin necesidad de recurrir a bibliotecas externas.
Cuándo usarlo: Cuando tu formulario tiene 3+ pasos, validación entre pasos, o transiciones de estado lo suficientemente complejas como para que malabarear múltiples llamadas a useState sea propenso a errores.
Estado obsoleto en callbacks asincronos - Si lees state dentro de un setTimeout o await, obtienes el valor en el momento del envío, no el más reciente. Solución: Usa useRef para espejar el estado actual, o envía una acción desde el callback asincrónico en lugar de leer el estado.
Object spread solo crea copias superficiales - Los objetos anidados (como fields dentro de state) también deben extenderse: { ...state, fields: { ...state.fields, [name]: value } }. Solución: Siempre extiende en cada nivel de anidación que modifiques, o usa produce de Immer.
Olvidar limpiar errores al cambiar el campo - Los usuarios corrigen el error pero el mensaje persiste. Solución: Limpia el error específico del campo dentro de SET_FIELD, como se muestra en el ejemplo funcional.
El reductor debe ser puro - Sin llamadas a API, sin lecturas de localStorage, sin Date.now() dentro del reductor. Solución: Realiza efectos secundarios en el componente o manejador de eventos, luego envía el resultado.
La validación se ejecuta contra el estado obsoleto después del envío - dispatch no actualiza state de forma síncrona. Llamar a dispatch({ type: "SET_FIELD" }) y luego leer state.fields da el valor antiguo. Solución: Valida usando el valor que estás a punto de enviar, no el state actual.
Los reductores grandes se vuelven difíciles de leer - Una sentencia switch de 200 líneas es peor que 8 llamadas a useState. Solución: Extrae manejadores de casos en funciones nombradas: case "SET_FIELD": return handleSetField(state, action);.
Caso default faltante en switch - TypeScript no advertirá si te pierdes un caso a menos que agregues comprobación exhaustiva. Solución: Agregar default: { const _exhaustive: never = action; return state; } para detectar acciones no controladas en tiempo de compilación.