Volver al blog
10 de noviembre de 2024

TypeScript: De '¿Para Qué?' a 'No Puedo Codear sin TS'

Cómo escribir miles de líneas de JavaScript me enseñó por qué TypeScript es innegociable para cualquier proyecto serio

TypeScript: De '¿Para Qué?' a 'No Puedo Codear sin TS'

🤦 La Fase de Luna de Miel con JavaScript

Empecé mi camino en programación con JavaScript. Lo amé. ¡Tan flexible! ¡Permisivo! ¡Sin paso de compilación! ¡Solo escribe código y corre!

Después construí Four-Points — mi sistema de gestión hotelera. Empecé en JavaScript puro. 5,000+ líneas después, aprendí una verdad dolorosa:

Flexibilidad en JavaScript significa "los bugs aparecerán en producción."

El clásico viaje del dev:

  1. "¿Qué lenguaje aprender?" → Meses de investigación
  2. Expertos: "Depende de tus objetivos"
  3. Tú: "Nah, quiero el MEJOR"
  4. Finalmente eliges JavaScript
  5. Oyes sobre TypeScript: "Nah, ya sé JS"
  6. Empiezas a construir cosas reales, llega la hora de migrar
  7. Chequeo de realidad: Nunca dejarás de aprender en esta carrera

Acostúmbrate. Todos pasamos por esto. 💀

💀 Los Puntos Dolorosos de JavaScript

1. La Ruleta del Runtime

// Esto parece fine...
function calculateTotal(items) {
  return items.reduce((sum, item) => sum + item.price, 0)
}

// Hasta que alguien pasa esto:
calculateTotal(undefined)  // 💥 Cannot read property 'reduce' of undefined
calculateTotal([{ name: 'Coffee' }])  // 💥 NaN (no tiene propiedad price)
calculateTotal('wrong')  // 💥 'wrong'[Symbol.iterator] is not a function

Cada función es una mina terrestre. No sabes que falla hasta que corre. En producción. Con usuarios reales.

2. La Pesadilla del Refactoring

// Renombra esta propiedad a través de 50 archivos
const user = {
  userName: 'john',  // Cambiado a 'username'
  email: 'john@example.com'
}

// Encuentra todos los lugares usando 'userName' manualmente
// ¿Te pierdes una referencia? Error de runtime en producción
// Buena suerte

Ningún IDE puede refactorizar JavaScript de forma fiable. ¿Find & replace? Espera que no tengas una variable llamada userName que no sea el objeto usuario.

3. El Juego de Adivinar Autocomplete

// ¿Qué propiedades tiene este objeto?
function updateUser(user) {
  user.  // ← IDE muestra... nada útil
}

Tu IDE no puede ayudarte. Necesitas:

  • Recordar qué propiedades existen
  • Mirar el código donde se crea el objeto
  • Console.log() e inspeccionar
  • Solo probar y ver qué falla

4. La Mentira de la Documentación

/**
 * Obtiene datos de usuario
 * @param {number} userId - El ID de usuario
 * @returns {Object} Objeto de usuario
 */
async function getUser(userId) {
  // Implementación
}

// 6 meses después, el código cambió pero los docs no
// Ahora acepta IDs de tipo string
// Retorna null en error
// La documentación miente
// Tú confías de todos modos
// 💥

Los comentarios JSDoc son mejores que nada, pero no se refuerzan. El código cambia, los docs no. Ahora son solo mentiras.

5. El Misterio de "En Mi Máquina Funciona"

// Dev envía esto a API:
const data = {
  userId: 123,
  amount: '50.00'  // Oops, string en vez de número
}

// Backend espera:
{
  userId: number,
  amount: number
}

// Funciona en dev (comparación laxa)
// Rompe en prod (validación estricta)
// Sin saber por qué hasta que debuggeas por 2 horas,
// mejor caso, o arruina el proyecto a propósito

🚀 El Despertar de TypeScript

Después del error 47 de runtime que TypeScript habría capturado, migré Four-Points a TypeScript. 2 semanas de dolor. Totalmente valió la pena.

Lo que Cambió Inmediatamente

Antes (JavaScript):

function createBooking(data) {
  return fetch('/api/bookings', {
    method: 'POST',
    body: JSON.stringify(data)
  })
}

// ¿Llamar con datos incorrectos? Descúbrelo en producción
createBooking({ 
  guestName: 'John',
  romNumber: 101  // Typo: debería ser 'roomNumber'
})

Después (TypeScript):

interface Booking {
  guestName: string
  roomNumber: number
  checkIn: Date
  checkOut: Date
}

function createBooking(data: Booking): Promise<Response> {
  return fetch('/api/bookings', {
    method: 'POST',
    body: JSON.stringify(data)
  })
}

// Intentar pasar datos incorrectos:
createBooking({ 
  guestName: 'John',
  romNumber: 101  // ← Línea roja antes de ejecutar
})
// Error: Object literal may only specify known properties, 
// and 'romNumber' does not exist in type 'Booking'

Aviso de los tipos antes de incluso ejecutar el código. Aqui ya empieza a currar TypeScript. Tienes que escribir más pero hoy tenemos la IA que te lo da todo mascado, eso si, lee lo que te da porque a veces incluso la obviedades se pelean con el backend.

💡 Mejores Prácticas que Aprendí a las malas

1. Tipa Todo, Incluso Cuando es Obvio

Malo:

// "Es obvio que esto retorna un string"
function getUserName(user) {
  return user.name
}

Bueno:

interface User {
  name: string
  email: string
}

function getUserName(user: User): string {
  return user.name
}

Tu yo del futuro te agradecerá. Confía en mí.

2. Evita any Como la Peste

// Tan bien como usar JavaScript
function processData(data: any): any {
  // TypeScript no puede ayudarte ahora
}

// En realidad tipalo
interface InputData {
  id: number
  values: string[]
}

interface ProcessedData {
  id: number
  result: number
}

function processData(data: InputData): ProcessedData {
  // Ahora TypeScript te cubre las espalda
}

Cada any es un potencial error de runtime. Si estás usando any, no estás usando TypeScript.

3. Usa Modo Estricto

// tsconfig.json
{
  "compilerOptions": {
    "strict": true,  // Innegociable
    "strictNullChecks": true,
    "noImplicitAny": true,
    "strictFunctionTypes": true
  }
}

El modo estricto es molesto hasta que te salva de un bug de producción. Entonces es tu mejor amigo.

4. Interfaces para Objetos, Types para Todo lo Demás

// Usa interfaces para objetos
interface User {
  id: number
  name: string
  email: string
}

// Usa types para uniones, intersecciones, utilidades
type Status = 'pending' | 'active' | 'inactive'
type UserWithStatus = User & { status: Status }
type PartialUser = Partial<User>

5. No Pelees con el Sistema de Tipos

Si TypeScript se queja, probablemente hay un problema real:

// TypeScript: "Oye, esto puede ser undefined"
function getFirstItem<T>(items: T[]): T {
  return items[0]  // ← ¿Y si el array está vacío?
}

// Escucha a TypeScript
function getFirstItem<T>(items: T[]): T | undefined {
  return items[0]  // ← Ahora maneja arrays vacíos correctamente
}

6. Usa Unknown en Vez de Any

// Malo
function parseJSON(json: string): any {
  return JSON.parse(json)
}

// Bueno
function parseJSON<T>(json: string): unknown {
  return JSON.parse(json)
}

// Te obliga a validar antes de usar
const data = parseJSON(jsonString)
if (isUser(data)) {  // Type guard
  console.log(data.name)  // Ahora es seguro
}

7. Crea Tipos Utilidad para Patrones Comunes

// Wrapper de respuesta de API
type ApiResponse<T> = {
  data: T | null
  error: string | null
  loading: boolean
}

// Uso
const userResponse: ApiResponse<User> = {
  data: { id: 1, name: 'John' },
  error: null,
  loading: false
}

🎯 Impacto Real en Four-Points

Antes de TypeScript:

  • ~15 errores de runtime por semana
  • 2-3 horas debuggeando problemas relacionados con tipos
  • Miedo a refactoring
  • Sorpresas de "funciona en dev" en prod

Después de TypeScript:

  • ~2 errores de runtime por semana (y son bugs de lógica real)
  • La mayoría de errores capturados durante desarrollo
  • Confianza refactorizando con soporte de IDE
  • Contratos de API forzados en tiempo de compilación

Ganancias Específicas:

1. Módulo de Cashier

interface CashierShift {
  id: number
  userId: number
  startTime: Date
  endTime: Date | null
  denominations: Denomination[]
  totalCash: number
  status: 'open' | 'closed'
}

// No puedes pasar estado incorrecto accidentalmente
// No puedes olvidar campos requeridos
// No puedes mezclar tipos

2. Integración de API

// Contrato del backend
interface CreateBookingRequest {
  roomNumber: number
  guestName: string
  checkIn: string  // fecha ISO
  checkOut: string  // fecha ISO
}

// Frontend llama
async function createBooking(data: CreateBookingRequest) {
  // TypeScript asegura que enviamos la forma correcta
}

3. Estado de Zustand

interface AppState {
  user: User | null
  bookings: Booking[]
  loading: boolean
  error: string | null
  
  // Acciones tipadas
  setUser: (user: User) => void
  addBooking: (booking: Booking) => void
  clearError: () => void
}

// El store está completamente tipado
const useStore = create<AppState>((set) => ({
  // Implementación
}))

🚧 Errores Comunes a Evitar

1. Aserciones de Tipo por Todos Lados

// Malo - bypass de seguridad de tipos
const user = data as User
const id = value as number

// Bueno - validar y luego narrowing
if (isUser(data)) {
  const user = data  // TypeScript sabe que es User
}

2. No Usar Generics

// Malo - duplicando lógica
function getFirstString(arr: string[]): string | undefined {
  return arr[0]
}
function getFirstNumber(arr: number[]): number | undefined {
  return arr[0]
}

// Bueno - una función, todos los tipos
function getFirst<T>(arr: T[]): T | undefined {
  return arr[0]
}

3. Ignorar Errores con @ts-ignore

// Malo - ocultando problemas reales
// @ts-ignore
const result = riskyOperation()

// Bueno - arreglar el problema real
const result: ResultType = riskyOperation()

📊 ¿Vale la Curva de Aprendizaje de TypeScript?

Respuesta corta: Absolutamente sí.

Respuesta larga:

Pros:

  • Captura bugs antes del runtime
  • Soporte IDE excelente (autocomplete, refactoring)
  • Código autodocumentado
  • Más fácil onboardear nuevos desarrolladores
  • Confianza al refactorizar
  • Mejor colaboración (contratos claros)

Contras:

  • Curva de aprendizaje inicial
  • A veces peleas con código válido
  • Paso de compilación (mínimo en setups modernos)
  • Más verboso (pero eso en realidad es bueno)

Los "cons" desaparecen después de una semana. Los pros se acumulan para siempre.

🎬 Mi Consejo

Para Proyectos Nuevos

Empieza con TypeScript. No debatas. No "pruebes JavaScript primero". Usa TypeScript desde la línea 1.

Para Proyectos Existentes

Migra gradualmente:

  1. Renombra .js a .ts
  2. Arregla errores un archivo a la vez
  3. Empieza con strict mode OFF
  4. Habilita strict mode archivo por archivo
  5. Celebra cuando todo esté tipado

Para Aprender

Si estás aprendiendo JavaScript, aun así aprende TypeScript:

  • Entiende fundamentos de JavaScript primero (1-2 meses)
  • Añade TypeScript inmediatamente después
  • Nunca vuelvas a JavaScript puro para proyectos serios

💭 Pensamientos Finales

Pasé 6 meses escribiendo JavaScript antes de cambiar a TypeScript. Esos 6 meses me enseñaron por qué existe TypeScript. Cada bug que arreglé habría sido capturado por TypeScript. Cada refactor que temí habría sido fácil con TypeScript.

TypeScript no se trata de ser hardcore o seguir tendencias. Se trata de enviar código confiable y dormir bien por la noche.

Si tu proyecto va a ser más que un experimento de fin de semana, usa TypeScript. Tu yo del futuro te lo agradecerá.

Sigue enviando. Tipa de forma segura. 🚀


P.D.: Sí, sé que algunas personas construyen proyectos enormes en JavaScript sin problemas. O son devs mucho mejores que yo, o aún no han llegado a los puntos dolorosos. No esperes el dolor. Usa TypeScript.

Volver al blog
TypeScript: De '¿Para Qué?' a 'No Puedo Codear sin TS' | bpstack