Saltar al contenido

Cómo crear un formulario de solicitud de presupuesto en React Native

Un formulario de presupuesto en React Native y Expo con opciones de servicio, presupuesto y fecha límite que te envía cada solicitud por correo y se la confirma automáticamente al cliente.

Actualizado:

El vídeo está en inglés; los pasos de abajo son el tutorial completo.

En este tutorial crearás una pantalla de solicitud de presupuesto en React Native: el cliente elige un servicio y un presupuesto, añade una fecha límite y una descripción, y tú recibes un correo estructurado. Una respuesta automática le confirma la solicitud al cliente.

Necesitas: una cuenta gratuita de Frontmail, un proyecto de Expo o React Native y unos quince minutos.

1. Conecta tu buzón y crea las plantillas

  1. Servicios de correo → Añadir servicio: conecta tu buzón o proveedor, Marcar como predeterminado, Verificar y guardar.
  2. Plantillas → Desde la galería → Solicitud de presupuesto (categoría Negocios) → Usar plantilla.
  3. Desde la galería → Respuesta automática: gracias por tu mensaje → Usar plantilla. Define valores por defecto para company_name y response_time y elígela en la plantilla de presupuesto en Ajustes → Entrega → Plantilla de respuesta automática.

Parámetros de la plantilla de presupuesto: name, email, service, details (obligatorios) y phone, company, budget, deadline (opcionales, la fecha límite como YYYY-MM-DD). service y budget solo aceptan los valores de la pestaña Parámetros. El código de ejemplo usa los de la plantilla en inglés; si creas la plantilla en español, son Sitio web, Tienda online, App móvil, Consultoría, Otro y Menos de 5000 €, 5000–20 000 €, 20 000–50 000 €, Más de 50 000 €. Usa en la app exactamente los mismos textos.

2. Permite las apps móviles y activa la CAPTCHA

  • Seguridad → Apps móviles → Permitir apps móviles → Guardar.
  • Plantilla de presupuesto → Ajustes → Protección → Exigir CAPTCHA (Turnstile): la app usa el widget compartido de Frontmail, sin necesidad de claves.

3. Instala

npx expo install @frontmail/react-native @react-native-async-storage/async-storage react-native-webview

4. Crea el formulario

Para que el ejemplo no necesite dependencias extra, las opciones son simples botones tipo chip y la fecha límite se escribe como YYYY-MM-DD. Cámbialos por tus componentes de selección favoritos: lo único que importa son los valores que pasas a send.

import { useRef, useState } from 'react';
import { Button, Pressable, Text, TextInput, View } from 'react-native';
import { FrontmailProvider, TurnstileWebView, useSendEmail, type TurnstileWebViewHandle } from '@frontmail/react-native';

const options = { publicKey: 'pk_…' };
const SERVICES = ['Website', 'E-shop', 'Mobile app', 'Consulting', 'Other'];
const BUDGETS = ['Under $5k', '$5k–20k', '$20k–50k', 'Over $50k'];

export default function App() {
  return (
    <FrontmailProvider options={options}>
      <QuoteForm />
    </FrontmailProvider>
  );
}

function Choice({ values, value, onChange }: { values: string[]; value?: string; onChange(v?: string): void }) {
  return (
    <View style={{ flexDirection: 'row', flexWrap: 'wrap', gap: 8 }}>
      {values.map((v) => (
        <Pressable key={v} onPress={() => onChange(v === value ? undefined : v)} accessibilityState={{ selected: v === value }}>
          <Text style={{ padding: 8, borderWidth: 1, borderRadius: 16, fontWeight: v === value ? '700' : '400' }}>{v}</Text>
        </Pressable>
      ))}
    </View>
  );
}

function QuoteForm() {
  const { send, status, error } = useSendEmail(null, 'tpl_…');
  const [name, setName] = useState('');
  const [email, setEmail] = useState('');
  const [service, setService] = useState<string | undefined>(SERVICES[0]);
  const [budget, setBudget] = useState<string>();
  const [deadline, setDeadline] = useState('');
  const [details, setDetails] = useState('');
  const [token, setToken] = useState<string>();
  const turnstile = useRef<TurnstileWebViewHandle>(null);

  const onSend = async () => {
    await send({ name, email, service, budget, deadline, details }, { turnstileToken: token });
    setToken(undefined);
    turnstile.current?.reset(); // a token works only once
  };

  if (status === 'sent' || status === 'held') return <Text>Thanks! We’ve emailed you a confirmation.</Text>;

  return (
    <View style={{ gap: 12, padding: 16 }}>
      <TextInput placeholder="Name" value={name} onChangeText={setName} />
      <TextInput placeholder="Email" value={email} onChangeText={setEmail} keyboardType="email-address" autoCapitalize="none" />
      <Text>What do you need?</Text>
      <Choice values={SERVICES} value={service} onChange={setService} />
      <Text>Budget (optional)</Text>
      <Choice values={BUDGETS} value={budget} onChange={setBudget} />
      <TextInput placeholder="Deadline (YYYY-MM-DD, optional)" value={deadline} onChangeText={setDeadline} />
      <TextInput placeholder="Project details" value={details} onChangeText={setDetails} multiline />
      <TurnstileWebView ref={turnstile} onToken={setToken} />
      <Button title="Request a quote" disabled={!token || !service || status === 'sending'} onPress={onSend} />
      {error && <Text>{error.message}</Text>}
    </View>
  );
}

Los valores opcionales vacíos (sin presupuesto, fecha límite vacía) se omiten automáticamente en el correo.

5. Pruébalo

Ejecuta la app y envía una solicitud con tu propio correo. Recibirás la solicitud y la confirmación; ambas aparecen en Historial. Los errores de validación llegan como invalid_template_params, con el campo que ha fallado en error.details.

Una solicitud con confirmación consume dos créditos. Cuando se acaban los créditos, las solicitudes se aceptan como held y se envían en cuanto recargas; el hook devuelve status === 'held'.

Próximos pasos

  • Guarda una lista de todas las personas que han preguntado: activa Recogida de contactos en los ajustes de la plantilla.
  • Pasa { locale: 'de' } como segundo argumento de send para usar una variante de idioma de la plantilla.
Todos los tutoriales