Construyendo un Nutritional Tracker: Parte 5 - El Formulario de Registro

4 min

Parte 5: El Formulario de Registro — Nutrition Tracker

Introducción

Hasta aquí, nuestra app valida datos, los persiste en el navegador y los testea automáticamente. El siguiente reto fue conectar todas las piezas en el formulario principal de registro, donde el usuario ingresa su consumo nutricional diario.


¿Qué integra el formulario?

  • Validación: Usa el esquema Zod y TypeScript para nunca aceptar datos inválidos.
  • Persistencia: Guarda registros automáticamente en localStorage.
  • UI React: Componentización, estados, feedback visual.
  • React Hook Form: Maneja el ciclo de vida del formulario y la comunicación eficiente con validadores.

Estructura básica del componente

import React from "react";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { RegisterInputSchema } from "@/lib/schemas/register.schema";
import { saveRegister } from "@/lib/storage/localStorage";

function RegistrationForm() {
  const { register, handleSubmit, formState: { errors }, reset } = useForm({
    resolver: zodResolver(RegisterInputSchema),
    defaultValues: {
      // ...valores iniciales
    },
  });

  const onSubmit = (data) => {
    const result = saveRegister(data);
    if (result.success) {
      reset();
      // Mostrar feedback de éxito
    } else {
      // Mostrar feedback de error y mensajes por campo
    }
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input {...register("food")} />
      {errors.food && <span>{errors.food.message}</span>}
      {/* repeat for the rest of the fields */}
      <button type="submit">Guardar</button>
    </form>
  );
}

export default RegistrationForm;

Explicaciones:

  • El formulario recibe funciones pre-configuradas de react-hook-form.
  • Al hacer submit, valida con Zod y guarda en localStorage solo si los datos son válidos.
  • El feedback muestra mensajes en los campos incorrectos, y uno global al guardar.

Diagrama de interacción: Form Submission

sequenceDiagram
    participant User
    participant Form
    participant Zod
    participant Storage
    User->>Form: Completa campos y envía
    Form->>Zod: Valida datos
    Zod-->>Form: ¿Válido?
    alt éxito
        Form->>Storage: Guarda registro
        Storage-->>Form: status success/error
        Form-->>User: Muestra feedback de éxito
    else error
        Zod-->>Form: Devuelve errores
        Form-->>User: Muestra feedback por campo y general
    end

Ventajas de este diseño

  • Escalable: Cambios en el modelo de datos se reflejan automáticamente en el formulario.
  • Type safe: Todos los datos están estrictamente tipados y validados sin duplicación.
  • UX clara: El usuario tiene feedback claro y los campos inválidos se resaltan.
  • Modularidad: Separar lógica de validación, persistencia y UI aumenta la mantenibilidad.

¿Cómo aseguramos que el formulario funciona? Tests y ejemplos prácticos

Aquí algunos ejemplos y conceptos de la suite de tests que validan el comportamiento real del formulario:

Test: El formulario bloquea datos inválidos

¿Por qué? Así prevenimos guardados no deseados y mostramos errores claros al usuario.

it("impide enviar si los campos obligatorios están vacíos", async () => {
  render(<RegistrationForm />);
  fireEvent.click(screen.getByRole("button", { name: /guardar registro/i }));
  await waitFor(() => {
    expect(screen.getByText(/usuario.*requerido/i)).toBeInTheDocument();
    expect(screen.getByText(/alimento.*requerido/i)).toBeInTheDocument();
  });
});

Test: Feedback visual al guardar

¿Por qué? El usuario necesita saber si sus datos se guardaron o si hubo error.

it("muestra mensaje de éxito al guardar", async () => {
  render(<RegistrationForm />);
  // ...llenado y envío...
  fireEvent.click(screen.getByRole("button", { name: /guardar registro/i }));
  await waitFor(() => {
    expect(screen.getByRole("alert")).toHaveTextContent(/guardado/i);
  });
});

Test: Persistencia y reseteo de campos

¿Por qué? Evita entradas duplicadas, ayuda en la UX y asegura que el storage funciona junto al formulario.

it("guarda registro y resetea campos manteniendo usuario", async () => {
  render(<RegistrationForm />);
  // ...llenar datos, seleccionar usuario...
  fireEvent.click(screen.getByRole("button", { name: /guardar registro/i }));
  await waitFor(() => {
    expect(screen.getByLabelText(/alimento/i).value).toBe("");
    expect(screen.getByLabelText(/usuario/i).value).not.toBe("");
  });
});

Test: Los datos correctos llegan al storage

¿Por qué? Verifica integración entre frontend y persistencia.

it("persiste correctamente en localStorage", async () => {
  render(<RegistrationForm />);
  // ...llenar datos y enviar...
  fireEvent.click(screen.getByRole("button", { name: /guardar registro/i }));
  await waitFor(() => {
    const raw = localStorage.getItem("nutrition-tracker-registers");
    expect(raw).toBeTruthy();
    const arr = JSON.parse(raw);
    expect(Array.isArray(arr)).toBe(true);
    expect(arr[arr.length - 1].food).toBe("Manzana");
  });
});

¿Por qué testear así?

  • Evitas que el usuario se frustre por errores inesperados.
  • Garantizas que el storage siempre reciba datos correctos.
  • El feedback en pantalla y el estado del formulario siempre reflejan la situación real

Estado de la aplicación en este punto

  • El formulario principal funciona: conecta modelo, validación y storage.
  • Los datos quedan guardados y pueden mostrarse visualmente o procesarse para reportes.
  • La próxima gran área de mejora es la experiencia visual y de estilos: el formulario funciona pero es minimalista y básico.
  • Los tests cubren casi todos los flujos posibles (errores, éxitos, reseteo, persistencia).

Próximo paso: Estilos y experiencia visual

  • Definir estrategia: CSS Modules, Tailwind, Styled Components, etc.
  • Separar los estilos en archivos o sistemas dedicados.
  • Mejorar feedback visual (colores, iconos, animaciones).
  • Hacer la app responsive.
  • Aplicar variables globales (tema, colores, fuentes).

Resumiendo

El foco hasta ahora fue robustez, validación y persistencia. El siguiente capítulo se centrará en transformar el formulario y la app en una experiencia visual moderna y amigable, aprovechando las bases técnicas que ya construimos.

Continúa leyendo: Parte 6: Estilos y UX en Nutrition Tracker → Guía para aplicar estilos, mejorar accesibilidad y crear una interfaz atractiva.


Recursos adicionales