Solución comentada
Publicado el 6 de septiembre de 2026La solución
type SnakeCase<S extends string> = S extends `${infer H}${infer R}` ? H extends Uppercase<H> ? H extends Lowercase<H> ? `${H}${SnakeCase<R>}` : `_${Lowercase<H>}${SnakeCase<R>}` : `${H}${SnakeCase<R>}` : S
El error más común
type SnakeCase<S extends string> = S extends `${infer H}${infer R}` ? H extends Uppercase<H> ? `_${Lowercase<H>}${SnakeCase<R>}` : `${H}${SnakeCase<R>}` : S
"1" extends Uppercase<"1"> es cierto, porque Uppercase deja todo lo que no sea una letra minúscula exactamente como lo encontró. Así que cada dígito, espacio y símbolo se trata como mayúscula y recibe su propio guion bajo: "a1B" sale como "a_1_b". Ser mayúscula no es una prueba sino dos — igual a tu forma en mayúsculas *y* distinto de tu forma en minúsculas.
Línea por línea
`${infer H}${infer R}`Dos marcadores sin nada entre ellos separan exactamente un carácter. Es el equivalente a nivel de tipos de recorrer una cadena índice a índice.
H extends Lowercase<H> ? ... : ...La segunda prueba es la que excluye a los no-letras. Un dígito es igual tanto a su forma mayúscula como a la minúscula, así que cae en la rama que lo deja pasar sin cambios.
: SLa cadena vacía no coincide con ningún patrón de un carácter, así que cae al caso base y se devuelve a sí misma — que es también lo que detiene la recursión.
Para recordar
Uppercase y Lowercase son funciones totales: nunca fallan, simplemente no hacen nada a los caracteres sin caja. Cualquier prueba basada en una sola de ellas clasificará mal, y en silencio, cada dígito y símbolo que encuentres.

