# FaceID para React 18 — kit v2

Componente y hook **sin dependencias de terceros** (sólo `react`). No hay paquete npm:
se copian los archivos a tu proyecto.

| Archivo | Qué es |
|---|---|
| `faceid-capture.mjs` | Librería de cámara (módulo ES, idéntica a `../js/faceid-capture.mjs`) |
| `useFaceIDCapture.js` | Hook: estado, error, info de cámara, `start` / `stop` / `capture`, limpieza al desmontar |
| `FaceIDVerificar.jsx` | Componente listo: `<video>`, óvalo guía, selector de cámara, botón verificar, mensajes en español |

## Instalación

1. Copia los tres archivos a una carpeta de tu proyecto, por ejemplo `src/faceid/`.
2. Nada más: funcionan con Vite, Create React App, Next.js (componente cliente) y webpack.

```bash
curl -O https://faceapi.epunemi.gob.ec/static/faceid-kit/v2/faceid-kit-v2.zip
unzip faceid-kit-v2.zip
mkdir -p src/faceid && cp faceid-kit-v2/react/*.{js,mjs,jsx} src/faceid/
```

> **Next.js (App Router):** agrega `'use client';` como primera línea de `FaceIDVerificar.jsx`
> (y de `useFaceIDCapture.js` si lo usas directamente). Los archivos son seguros en SSR:
> no tocan `window` ni `navigator` al importarse.

## Uso del componente

```jsx
import FaceIDVerificar from './faceid/FaceIDVerificar.jsx';

export default function PaginaAsistencia({ csrfToken }) {
  return (
    <FaceIDVerificar
      urlVerificar="/faceid/verificar/"          // endpoint de TU backend, no de FaceID
      csrfToken={csrfToken}                       // si tu backend usa CSRF
      onVerificado={(aData) => console.log('Verificado', aData.similarity_score)}
      onFallo={(mensaje) => console.warn(mensaje)}
    />
  );
}
```

| Prop | Defecto | Descripción |
|---|---|---|
| `urlVerificar` | — (obligatoria) | URL de **tu** backend que recibe la foto (POST multipart) |
| `csrfToken` | — | Se envía en `X-CSRFToken` y `X-CSRF-TOKEN` |
| `headers` | — | Headers extra hacia tu backend (nunca la API key de FaceID) |
| `campoFoto` | `'foto'` | Nombre del campo multipart |
| `credentials` | `'same-origin'` | Envía la cookie de sesión de tu sistema |
| `onVerificado(aData, respuesta)` | — | Se llama si tu backend responde `isSuccess` + `is_same_person` |
| `onFallo(mensaje, respuesta?)` | — | Rostro distinto, sin rostro, sesión expirada, red… |
| `apagarAlVerificar` | `true` | Apaga la cámara después de verificar |
| `textoBoton` | `'Verificar mi identidad'` | |
| `opcionesCamara` | — | Opciones de `FaceIDCapture` (`ancho`, `alto`, `calidad`, `maxLado`, `espejo`, `recordar`, `brilloMinimo`) |

Tu backend debe responder con el formato de los ejemplos del kit:

```json
{"isSuccess": true, "message": "Identidad verificada.", "aData": {"is_same_person": true, "similarity_score": 0.71}}
```

**Importante:** `onVerificado` sirve para la interfaz (mostrar un mensaje, navegar). La acción real
(registrar asistencia, abrir el examen) la debe ejecutar tu backend en el mismo endpoint, cuando
FaceID le respondió `is_same_person = true`. Nunca confíes en lo que el navegador diga que pasó.

## Uso del hook (interfaz propia)

```jsx
import { useFaceIDCapture } from './faceid/useFaceIDCapture.js';

function MiCaptura() {
  const { videoRef, estado, error, info, camaras, soporte, start, stop, capture } = useFaceIDCapture();

  async function enviar() {
    const foto = await capture();                       // Blob JPEG
    const datos = new FormData();
    datos.append('foto', foto, 'captura.jpg');
    await fetch('/faceid/verificar/', { method: 'POST', body: datos, credentials: 'same-origin' });
  }

  return (
    <>
      <video ref={videoRef} playsInline muted />
      {!soporte.ok && soporte.error && <p role="alert">{soporte.error.message}</p>}
      {error && <p role="alert">{error.message}</p>}
      <button onClick={() => start()} disabled={estado === 'abriendo'}>Activar cámara</button>
      <button onClick={enviar} disabled={estado !== 'lista'}>Enviar</button>
    </>
  );
}
```

- `estado`: `'inactiva' | 'abriendo' | 'lista' | 'error'`.
- `error`: `Error` con mensaje en español; `error.name` indica la causa (`NotAllowedError`,
  `NotReadableError`, `BlackFeed`, `InAppBrowser`, `Disconnected`…).
- `start(deviceId?)` debe llamarse dentro de un click (requisito de iOS). Devuelve `true` si abrió.
- Al desmontar el componente la cámara se apaga sola (`destruir()`).
- Con `<React.StrictMode>` en desarrollo el efecto se monta dos veces: es normal, la cámara se crea
  recién en el primer `start()`.
