Remitt

Plataforma de Comparación de Remesas Internacionales

El Desafío

Desarrollé una plataforma completa de comparación de remesas desde cero utilizando React 17 con JavaScript, Express.js y MongoDB. La aplicación integra la API de TransferWise para obtener datos en tiempo real de más de 20 proveedores financieros, incluyendo bancos tradicionales y fintechs como Wise, Western Union, Remitly y WorldRemit. Implementé un sistema de autenticación con registro de usuarios, notificaciones por email con Nodemailer, y una interfaz responsive con modo oscuro/claro usando Tailwind CSS.

Stack Tecnológico

Frontend

React 17
JavaScript
Tailwind CSS
Context API

Backend

Express.js
Axios
Nodemailer
OAuth2

Database

MongoDB

Tools

TransferWise API

Desafíos Técnicos Superados

Integración de API Externa Compleja

Desafío:

Integrar la API de TransferWise para obtener datos en tiempo real de múltiples proveedores de remesas con diferentes formatos de respuesta y manejo de errores.

Solución:

Creé un servicio de API personalizado con Axios que maneja las consultas a la API de TransferWise, implementé manejo de errores robusto y validación de datos antes de mostrar resultados al usuario.

Resultado:

Sistema confiable que procesa consultas de remesas en tiempo real con más de 20 proveedores financieros y maneja errores de API de forma elegante.

Optimización de Experiencia de Usuario

Desafío:

Crear una interfaz intuitiva que permita a usuarios no técnicos comparar fácilmente opciones complejas de remesas con diferentes comisiones y tasas de cambio.

Solución:

Implementé un formulario simplificado con selectores de países con banderas, validación en tiempo real, estados de carga con spinner personalizado, y resultados visuales claros mostrando comisiones y cantidades recibidas.

Resultado:

Interfaz que reduce la complejidad de comparar remesas a unos pocos clicks, con feedback visual claro y navegación intuitiva.

Gestión de Datos de Países y Proveedores

Desafío:

Manejar una base de datos extensa de países con sus monedas, banderas en base64, y múltiples proveedores financieros con diferentes URLs y configuraciones.

Solución:

Creé archivos JSON estructurados para países y proveedores, implementé un sistema de mapeo de datos que conecta países con monedas y proveedores con sus URLs correspondientes.

Resultado:

Sistema escalable que maneja más de 100 países y 20+ proveedores financieros con datos actualizados y fácil mantenimiento.

Funcionalidades Destacadas

Motor de Comparación en Tiempo Real

Sistema que consulta múltiples proveedores financieros simultáneamente, calcula comisiones y tasas de cambio actuales, y presenta los resultados ordenados por mejor valor para el usuario.

Selector de Países Avanzado

Componente con más de 100 países, banderas en base64, nombres en español e inglés, y búsqueda inteligente que facilita la selección del país origen y destino.

Sistema de Temas Dinámico

Implementación de modo oscuro/claro con Context API de React, persistencia de preferencias del usuario, y transiciones suaves entre temas con iconos adaptativos.

Sistema de Registro y Notificaciones

Registro de usuarios con validación de email, sistema de notificaciones automáticas por correo electrónico usando Nodemailer y OAuth2, y gestión de sesiones.

Diseño Responsive Completo

Interfaz adaptativa que funciona perfectamente en móviles, tablets y desktop, con navegación hamburguesa, cards responsivas y optimización para diferentes tamaños de pantalla.

Resultados del Proyecto

3 semanas
Tiempo de Desarrollo
20+
Proveedores Financieros Integrados
100+
Países Soportados
React, Express.js, MongoDB, Tailwind CSS
Tecnologías Principales

Código Destacado

Integración de API Externa con Manejo de Errores

Servicio robusto que maneja consultas a la API de TransferWise con validación de datos y manejo de errores elegante.

// Servicio de API para consultas de remesas
const axios = require('axios');

class RemittanceService {
  async getRates(fromCountry, toCountry, amount) {
    try {
      const response = await axios.get(`${API_BASE_URL}/rates`, {
        params: { from: fromCountry, to: toCountry, amount }
      });
      
      return this.validateAndFormatRates(response.data);
    } catch (error) {
      console.error('API Error:', error.message);
      throw new Error('Error al obtener tasas de cambio');
    }
  }
  
  validateAndFormatRates(data) {
    return data.providers
      .filter(provider => provider.rate > 0)
      .sort((a, b) => a.totalCost - b.totalCost);
  }
}

Selector de Países con Banderas Base64

Componente interactivo con más de 100 países, banderas en base64 y búsqueda inteligente.

// Componente de selector de países con banderas
const CountrySelector = ({ countries, onSelect, placeholder }) => {
  const [searchTerm, setSearchTerm] = useState('');
  
  const filteredCountries = countries.filter(country =>
    country.name.toLowerCase().includes(searchTerm.toLowerCase())
  );
  
  return (
    <div className="relative">
      <input
        type="text"
        placeholder={placeholder}
        value={searchTerm}
        onChange={(e) => setSearchTerm(e.target.value)}
        className="w-full p-3 border rounded-lg focus:ring-2 focus:ring-blue-500"
      />
      <div className="absolute z-10 w-full mt-1 bg-white border rounded-lg shadow-lg">
        {filteredCountries.map(country => (
          <div
            key={country.code}
            onClick={() => onSelect(country)}
            className="flex items-center p-3 hover:bg-gray-100 cursor-pointer"
          >
            <img 
              src={`data:image/png;base64,${country.flag}`}
              alt={country.name}
              className="w-6 h-4 mr-3"
            />
            <span>{country.name}</span>
          </div>
        ))}
      </div>
    </div>
  );
};

Sistema de Temas con Context API

Sistema completo de temas con persistencia en localStorage y transiciones suaves.

// Context para manejo de temas
const ThemeContext = createContext();

export const ThemeProvider = ({ children }) => {
  const [theme, setTheme] = useState(() => {
    const savedTheme = localStorage.getItem('theme');
    return savedTheme || 'light';
  });
  
  useEffect(() => {
    document.documentElement.classList.toggle('dark', theme === 'dark');
    localStorage.setItem('theme', theme);
  }, [theme]);
  
  const toggleTheme = () => {
    setTheme(prev => prev === 'light' ? 'dark' : 'light');
  };
  
  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  );
};

// Hook personalizado para usar el tema
export const useTheme = () => {
  const context = useContext(ThemeContext);
  if (!context) {
    throw new Error('useTheme must be used within ThemeProvider');
  }
  return context;
};
Cristian Perdomo - Desarrollador Full Stack