# Guía Frontend — Integración con el Backend

App: Expo Router + React Native + TypeScript.
Backend: Express en `http://localhost:3000` (ya está listo, no hay que tocarlo).

> Esta guía está pensada para probar en **emulador de Android**.
> Si prueban en otro entorno, sólo cambien la `API_URL` (sección 1).

---

## 0. Endpoints del backend

| Método | Endpoint | Para qué |
|---|---|---|
| POST | `/api/auth/register` | Crear cuenta |
| POST | `/api/auth/login` | Iniciar sesión y obtener JWT |
| GET  | `/api/auth/me` | Datos del usuario (requiere token) |
| POST | `/api/tracking/gps-update` | Recibe posición del camión, regresa ETA + notificaciones |

---

## 1. Configurar la URL del backend

Crear `frontend/src/config/api.ts`:

```ts
// IPs según el entorno:
//   Android Emulator → http://10.0.2.2:3000        (default aquí)
//   iOS simulator    → http://localhost:3000
//   Dispositivo real → http://<IP-de-tu-laptop>:3000   (ej. 192.168.1.20)
//   Expo Go web      → http://localhost:3000
export const API_URL = "http://10.0.2.2:3000";
```

> `10.0.2.2` es la dirección especial del emulador de Android para alcanzar el `localhost` de la laptop. No usen `localhost` desde el emulador, no funciona.

> Si prueban en un celular físico, sáquen la IP de la laptop con `ipconfig` (Windows) y pongan algo como `http://192.168.1.20:3000`. La laptop y el celular tienen que estar en el **mismo Wi-Fi**.

---

## 2. Cliente HTTP simple

Crear `frontend/src/lib/api.ts`:

```ts
import { API_URL } from "../config/api";

let authToken: string | null = null;

export const setAuthToken = (token: string | null) => {
  authToken = token;
};

export const apiFetch = async <T>(
  path: string,
  options: RequestInit = {},
): Promise<T> => {
  const headers: Record<string, string> = {
    "Content-Type": "application/json",
    ...(options.headers as Record<string, string>),
  };

  if (authToken) {
    headers.Authorization = `Bearer ${authToken}`;
  }

  const res = await fetch(`${API_URL}${path}`, { ...options, headers });
  const body = await res.json().catch(() => ({}));

  if (!res.ok) {
    throw new Error(body.error ?? `HTTP ${res.status}`);
  }

  return body as T;
};
```

---

## 3. Servicios por feature

### `frontend/src/services/auth.service.ts`

```ts
import { apiFetch, setAuthToken } from "../lib/api";

export interface AuthUser {
  id: number;
  email: string;
  name: string;
}

export const register = (data: { name: string; email: string; password: string }) =>
  apiFetch<{ id: number; email: string; name: string }>("/api/auth/register", {
    method: "POST",
    body: JSON.stringify(data),
  });

export const login = async (data: { email: string; password: string }) => {
  const res = await apiFetch<{ user: AuthUser; token: string }>("/api/auth/login", {
    method: "POST",
    body: JSON.stringify(data),
  });
  setAuthToken(res.token);
  return res;
};

export const getMe = () =>
  apiFetch<AuthUser & { role: string }>("/api/auth/me");
```

### `frontend/src/services/tracking.service.ts`

```ts
import { apiFetch } from "../lib/api";

export type NotificationType =
  | "ROUTE_START"
  | "TRUCK_PROXIMITY"
  | "ROUTE_COMPLETED"
  | "DELAY"
  | "MECHANICAL_FAILURE";

export interface GpsUpdatePayload {
  truckId: string;
  routeId: string;
  lat: number;
  lng: number;
  speed: number;
  status: "EN_RUTA" | "DETENIDO" | "FINALIZADO" | "FALLA";
  positionId?: number;
  timestamp?: string;
}

export interface EtaResult {
  etaMinutes: number;
  arrivalWindow: { from: string; to: string };
  message: string;
}

export interface BackendNotification {
  userId: number;
  type: NotificationType;
  title: string;
  body: string;
}

export interface GpsUpdateResponse {
  message: string;
  truck: { truckId: number; routeId: string; status: string };
  eta: EtaResult;
  notifications: BackendNotification[];
}

export const sendGpsUpdate = (payload: GpsUpdatePayload) =>
  apiFetch<GpsUpdateResponse>("/api/tracking/gps-update", {
    method: "POST",
    body: JSON.stringify(payload),
  });
```

---

## 4. Mapear los tipos del backend al componente `AlertItem`

`AlertItem` ya acepta `started | near | danger | completed`. Crear `frontend/src/lib/notification-mapper.ts`:

```ts
import type { NotificationType } from "../services/tracking.service";

export const notificationTypeToAlertType = (
  type: NotificationType,
): "started" | "near" | "danger" | "completed" => {
  switch (type) {
    case "ROUTE_START":         return "started";
    case "TRUCK_PROXIMITY":     return "near";
    case "ROUTE_COMPLETED":     return "completed";
    case "DELAY":               return "danger";
    case "MECHANICAL_FAILURE":  return "danger";
  }
};
```

---

## 5. Pantalla de Login (`src/app/login.tsx`)

```tsx
import { useState } from "react";
import { View, Text, StyleSheet, Alert } from "react-native";
import { useRouter } from "expo-router";
import InputField from "../components/InputField";
import PrimaryButton from "../components/PrimaryButton";
import { login } from "../services/auth.service";

export default function LoginScreen() {
  const [email, setEmail] = useState("ana@test.com");
  const [password, setPassword] = useState("123456");
  const [loading, setLoading] = useState(false);
  const router = useRouter();

  const handleLogin = async () => {
    try {
      setLoading(true);
      await login({ email, password });
      router.replace("/");
    } catch (err) {
      Alert.alert("Error", err instanceof Error ? err.message : "Login failed");
    } finally {
      setLoading(false);
    }
  };

  return (
    <View style={styles.container}>
      <Text style={styles.title}>Iniciar sesión</Text>
      <InputField placeholder="Email" value={email} onChangeText={setEmail} />
      <InputField placeholder="Password" value={password} onChangeText={setPassword} secureTextEntry />
      <PrimaryButton title={loading ? "Cargando..." : "Entrar"} onPress={handleLogin} />
    </View>
  );
}

const styles = StyleSheet.create({
  container: { flex: 1, padding: 24, justifyContent: "center" },
  title: { fontSize: 24, fontWeight: "bold", marginBottom: 20 },
});
```

---

## 6. Pantalla principal con ETA en tiempo real (`src/app/index.tsx`)

```tsx
import { useEffect, useState } from "react";
import { ScrollView, RefreshControl, View } from "react-native";
import EtaCard from "../components/EtaCard";
import {
  sendGpsUpdate,
  type GpsUpdateResponse,
  type BackendNotification,
} from "../services/tracking.service";

// Para la demo: el frontend simula el avance del camión.
// En producción esto vendría de un GPS real → backend → app vía websockets/polling.
const SIMULATION_STEPS = [
  { positionId: 1, lat: 20.5111, lng: -100.9037, speed: 0,  timestamp: "2026-05-22T06:00:00Z" },
  { positionId: 2, lat: 20.5185, lng: -100.8450, speed: 45, timestamp: "2026-05-22T06:12:00Z" },
  { positionId: 3, lat: 20.5215, lng: -100.8142, speed: 22, timestamp: "2026-05-22T06:25:00Z" },
  { positionId: 4, lat: 20.5212, lng: -100.8175, speed: 15, timestamp: "2026-05-22T06:38:00Z" },
  { positionId: 8, lat: 20.5111, lng: -100.9037, speed: 40, timestamp: "2026-05-22T07:40:00Z" },
];

export default function HomeScreen() {
  const [stepIndex, setStepIndex] = useState(0);
  const [data, setData] = useState<GpsUpdateResponse | null>(null);
  const [notifications, setNotifications] = useState<BackendNotification[]>([]);

  const fetchNext = async () => {
    const step = SIMULATION_STEPS[stepIndex % SIMULATION_STEPS.length];
    try {
      const res = await sendGpsUpdate({
        truckId: "101",
        routeId: "RUTA-01",
        status: "EN_RUTA",
        ...step,
      });
      setData(res);
      setNotifications((prev) => [...res.notifications, ...prev]);
      setStepIndex((i) => i + 1);
    } catch (err) {
      console.error(err);
    }
  };

  useEffect(() => { fetchNext(); }, []);

  return (
    <ScrollView refreshControl={<RefreshControl refreshing={false} onRefresh={fetchNext} />}>
      <EtaCard
        minutes={data?.eta.etaMinutes ?? 0}
        status={data?.eta.message ?? "Cargando..."}
      />
      <View>
        {/* Mapear notifications a <AlertItem /> aquí si quieren mostrarlas en la home también */}
      </View>
    </ScrollView>
  );
}
```

> **Truco del demo:** cada "pull to refresh" avanza el camión un paso. Sin tocar el backend, pueden mostrar las 5 fases del recorrido.

---

## 7. Pantalla de Alertas (`src/app/alerts.tsx`)

```tsx
import { FlatList, View } from "react-native";
import AlertItem from "../components/Alertltem";
import { notificationTypeToAlertType } from "../lib/notification-mapper";
import type { BackendNotification } from "../services/tracking.service";

type Props = { notifications: BackendNotification[] };

export default function AlertsScreen({ notifications }: Props) {
  return (
    <View style={{ flex: 1, padding: 16 }}>
      <FlatList
        data={notifications}
        keyExtractor={(_, i) => String(i)}
        renderItem={({ item }) => (
          <AlertItem
            title={item.title}
            description={item.body}
            time="ahora"
            type={notificationTypeToAlertType(item.type)}
          />
        )}
      />
    </View>
  );
}
```

> **Compartir notificaciones entre pantallas:** lo más simple para el demo es subirlas a un `Context` o Zustand. Si tienen tiempo. Para hackathon, pueden duplicar la lógica o pasarlas por params.

---

## 8. Cómo probar el flujo completo en el emulador de Android

### Pre-requisitos

- Backend corriendo: `npm run dev` dentro de `/backend`. Debe imprimir `Server is running on port 3000`.
- Android Studio con AVD (emulador) abierto.
- Frontend con dependencias instaladas: `npm install` dentro de `/frontend`.

### Pasos

1. **Levantar el frontend:**

   ```powershell
   cd frontend
   npm run android
   ```

   Eso abre Expo y lanza la app en el emulador.

2. **Crear un usuario de prueba** (sólo la primera vez). Desde Postman:

   ```
   POST http://localhost:3000/api/auth/register
   Content-Type: application/json

   { "name": "Ana López", "email": "ana@test.com", "password": "123456" }
   ```

   (Ana ya está en el mock con `routeId: "RUTA-01"`, así que recibirá las notificaciones del camión 101.)

3. **En la app del emulador**, iniciar sesión con:
   - Email: `ana@test.com`
   - Password: `123456`

4. **En la pantalla principal**, hacer **pull-to-refresh** repetidamente. Cada pull avanza el camión:

   | Paso | positionId | Notificación esperada |
   |---|---|---|
   | 1 | 1 | (ninguna) |
   | 2 | 2 | "¡Ruta Iniciada!" |
   | 3 | 3 | (ninguna) |
   | 4 | 4 | "Camión Cercano" |
   | 5 | 8 | "Servicio Finalizado" |

5. **Pantalla de alertas** debe mostrar las notificaciones acumuladas con los iconos y colores correctos.

---

## 9. Problemas comunes en Android emulator

| Síntoma | Causa | Solución |
|---|---|---|
| `Network request failed` | Apunta a `localhost` | Usar `http://10.0.2.2:3000` en `API_URL` |
| `TypeError: Failed to fetch` (en web) | Falta CORS | Avisar al del backend para que active `cors()` |
| 401 al hacer login | Usuario no existe | Registrar primero con Postman (sección 8, paso 2) |
| `notifications: []` aunque cambies positionId | El cache ya envió esa noti antes | Reiniciar el backend (`Ctrl+C` y `npm run dev`) |
| ETA negativo o raro | El `timestamp` que envías está atrás del target | Es normal, el mensaje sale como "El camión ya pasó..." |

---

## 10. Checklist mínimo

- [ ] `src/config/api.ts` con `http://10.0.2.2:3000`
- [ ] `src/lib/api.ts` (fetch wrapper)
- [ ] `src/services/auth.service.ts`
- [ ] `src/services/tracking.service.ts`
- [ ] `src/lib/notification-mapper.ts`
- [ ] Pantalla de login funcionando con `ana@test.com`
- [ ] Pantalla principal mostrando `EtaCard` con datos reales
- [ ] Pantalla de alertas mostrando `AlertItem` con notificaciones reales
- [ ] Probar las 5 fases del recorrido con pull-to-refresh

---

## 11. Lo que NO necesitan hacer

- ❌ Push notifications reales (Firebase / Expo Notifications) — sólo lista in-app
- ❌ Mapas con GPS real — los datos vienen del mock del backend
- ❌ AsyncStorage para el token — para el demo basta con estado en memoria
- ❌ Modificar nada del `/backend`
