> ## Documentation Index
> Fetch the complete documentation index at: https://translations.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

> Este tutorial muestra cómo llamar a su API desde un dispositivo con capacidades de entrada limitadas mediante el Flujo de autorización de dispositivos.

# Flujo de autorización de dispositivos

export const AuthCodeGroup = ({children, dropdown}) => {
  const [processedChildren, setProcessedChildren] = useState(children);
  useEffect(() => {
    let unsubscribe = null;
    function init() {
      unsubscribe = window.autorun(() => {
        const processChildren = node => {
          if (typeof node === "string") {
            let processedNode = node;
            for (const [key, value] of window.rootStore.variableStore.values.entries()) {
              const escapedKey = key.replaceAll(/[.*+?^${}()|[\]\\]/g, (String.raw)`\$&`);
              processedNode = processedNode.replaceAll(new RegExp(escapedKey, "g"), value);
            }
            return processedNode;
          } else if (Array.isArray(node)) {
            return node.map(processChildren);
          } else if (node && node.props && node.props.children) {
            return {
              ...node,
              props: {
                ...node.props,
                children: processChildren(node.props.children)
              }
            };
          }
          return node;
        };
        setProcessedChildren(processChildren(children));
      });
    }
    if (window.rootStore) {
      init();
    } else {
      window.addEventListener("adu:storeReady", init);
    }
    return () => {
      window.removeEventListener("adu:storeReady", init);
      unsubscribe?.();
    };
  }, [children]);
  return <CodeGroup dropdown={dropdown}>{processedChildren}</CodeGroup>;
};

export const LoggedInForm = ({sampleApp}) => {
  const LS_APPS_KEY = "auth_demo_apps";
  const LS_APP_CFG_KEY = "auth_demo_app_cfg";
  const CHANNEL = "auth_flows_sync_v1";
  const mkChannel = () => new BroadcastChannel(CHANNEL);
  function uid() {
    return Math.random().toString(36).slice(2) + Date.now().toString(36);
  }
  function loadApps() {
    const raw = localStorage.getItem(LS_APPS_KEY);
    if (raw) return JSON.parse(raw);
    const seeded = [{
      id: "{yourClientId}",
      name: "Default App"
    }];
    localStorage.setItem(LS_APPS_KEY, JSON.stringify(seeded));
    return seeded;
  }
  function saveApps(apps) {
    localStorage.setItem(LS_APPS_KEY, JSON.stringify(apps));
  }
  function loadCfg() {
    const raw = localStorage.getItem(LS_APP_CFG_KEY);
    return raw ? JSON.parse(raw) : {};
  }
  function saveCfg(cfg) {
    localStorage.setItem(LS_APP_CFG_KEY, JSON.stringify(cfg));
  }
  const RightChevron = ({className = "w-5 h-5", ...props}) => <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke="currentColor" fill="none" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={className} {...props}>
      <polyline points="9 18 15 12 9 6" />
    </svg>;
  const LightningIcon = () => <svg width="24" height="24" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
      <path fillRule="evenodd" clipRule="evenodd" className="fill-[#3F59E4] dark:fill-[#99A7F1]" d="M24.971 30.152H7.088c-1.786 0-2.745-2.103-1.574-3.453l19.07-21.988c1.33-1.532 3.835-.4 3.569 1.607L24.97 30.152z" />
      <path fillRule="evenodd" clipRule="evenodd" className="fill-[#3F59E4] dark:fill-[#99A7F1]" d="M23.201 17.885h17.885c1.787 0 2.746 2.102 1.575 3.453l-19.073 21.99c-1.33 1.532-3.835.4-3.568-1.607L23.2 17.885z" />
    </svg>;
  const LayersIcon = () => <svg width="24" height="24" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
      <path className="fill-[#3F59E4] dark:fill-[#99A7F1]" d="M34.54 29.135l6.373 3.183c1.566.782 1.566 3.017 0 3.8l-14.815 7.396a4.623 4.623 0 01-4.125 0L7.174 36.12c-1.565-.782-1.565-3.017 0-3.798l6.532-3.214" />
      <path className="fill-[#AAB6F3] dark:fill-[#3449BA]" d="M34.54 18.86l6.373 3.183c1.566.782 1.566 3.016 0 3.8L26.098 33.24a4.623 4.623 0 01-4.125 0L7.174 25.843c-1.565-.781-1.565-3.016 0-3.798l6.33-3.164" />
      <path className="fill-[#CFD6F8] dark:fill-[#22307C]" d="M21.94 23.058L7.306 15.745c-1.62-.81-1.62-3.123 0-3.932l14.631-7.319a4.693 4.693 0 014.194 0l14.648 7.319c1.622.81 1.62 3.124 0 3.932L26.13 23.058c-1.321.66-2.873.66-4.191 0z" />
    </svg>;
  const GithubIcon = () => <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="w-5 h-5">
      <path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path>
    </svg>;
  function IconTile({children}) {
    return <div className="
          shrink-0 grid place-items-center w-10 h-10 rounded-lg
          bg-indigo-50 ring-1 ring-indigo-200/60
          dark:bg-indigo-950/40 dark:ring-white/10
        ">
        {children}
      </div>;
  }
  function Card({className = "", children}) {
    return <div className={`rounded-2xl shadow-sm ring-1 ring-zinc-200 dark:ring-zinc-800 ${className}`}>{children}</div>;
  }
  function Button({variant = "primary", type = "button", onClick, children}) {
    const base = "inline-flex items-center justify-center gap-2 h-10 px-4 rounded-xl font-medium transition";
    let styles = "";
    if (variant === "primary") {
      styles = "mint-bg-indigo-600 text-white hover:mint-bg-indigo-700";
    } else if (variant === "outline") {
      styles = "border border-zinc-300 dark:border-zinc-700 mint-bg-transparent hover:mint-bg-zinc-50 dark:hover:mint-bg-zinc-800";
    } else if (variant === "ghost") {
      styles = "hover:mint-bg-zinc-100 dark:hover:mint-bg-zinc-800";
    }
    return <button type={type} onClick={onClick} className={`${base} ${styles}`}>
        {children}
      </button>;
  }
  function Input({id, label, value, onChange, placeholder, name}) {
    return <label className="block space-y-1">
        <span className="text-sm text-zinc-700 dark:text-zinc-300">{label}</span>
        <input id={id} name={name} className="w-full h-11 px-3 rounded-xl border border-zinc-300 dark:border-zinc-700 bg-white dark:bg-zinc-900 text-zinc-900 dark:text-zinc-100 focus:outline-none focus:ring-2 focus:ring-indigo-500" placeholder={placeholder} value={value} onChange={e => onChange(e.target.value)} />
      </label>;
  }
  function Select({label, value, onChange, options}) {
    return <label className="block space-y-1 max-w-[300px]">
        <span className="text-sm text-zinc-700 dark:text-zinc-300">{label}</span>
        <div className="relative">
          <select className="w-full h-11 appearance-none px-3 pr-9 rounded-xl border border-zinc-300 dark:border-zinc-700 bg-white dark:bg-zinc-900 text-zinc-900 dark:text-zinc-100 focus:outline-none focus:ring-2 focus:ring-indigo-500" value={value} onChange={e => onChange(e.target.value)}>
            <optgroup label="Generic Applications">
              {options.map(o => <option key={o.id} value={o.id}>
                  {o.name}
                </option>)}
            </optgroup>
          </select>
          <svg className="pointer-events-none absolute right-3 top-1/2 -translate-y-1/2 w-5 h-5 text-zinc-500" viewBox="0 0 24 24">
            <path d="M7 10l5 5 5-5z" fill="currentColor" />
          </svg>
        </div>
      </label>;
  }
  function Toast({open, onClose, children}) {
    useEffect(() => {
      if (!open) return;
      const t = setTimeout(onClose, 2200);
      return () => clearTimeout(t);
    }, [open, onClose]);
    return <div className={`fixed right-4 top-4 z-50 transition ${open ? "opacity-100 translate-y-0" : "opacity-0 -translate-y-2 pointer-events-none"}`}>
        <div className="flex items-center gap-2 rounded-xl shadow ring-1 ring-emerald-200 bg-white dark:bg-zinc-900 px-4 py-2">
          <span className="w-1.5 h-8 rounded-l bg-emerald-500" />
          <svg className="w-5 h-5 text-emerald-600" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
            <path d="M20 6L9 17l-5-5" />
          </svg>
          <span className="text-sm text-zinc-900 dark:text-zinc-100">{children}</span>
        </div>
      </div>;
  }
  function Flows() {
    const [route, setRoute] = useState("menu");
    const [apps, setApps] = useState(loadApps());
    const [cfg, setCfg] = useState(loadCfg());
    const [selected, setSelected] = useState(apps[0]?.id || "");
    const [toast, setToast] = useState(false);
    const [bc] = useState(() => mkChannel());
    useEffect(() => {
      if (!apps.find(a => a.id === selected)) {
        setSelected(apps[0]?.id || "");
      }
    }, [apps, selected]);
    useEffect(() => {
      const onMsg = e => {
        const {type, payload} = e.data || ({});
        switch (type) {
          case "NAV":
            setRoute(payload.route);
            break;
          case "SELECT":
            setSelected(payload.appId);
            break;
          case "APPS_UPDATED":
            setApps(loadApps());
            break;
          case "CFG_UPDATED":
            setCfg(loadCfg());
            setToast(true);
            break;
          default:
            break;
        }
      };
      bc.addEventListener("message", onMsg);
      return () => bc.removeEventListener("message", onMsg);
    }, [bc]);
    const nav = nextRoute => {
      setRoute(nextRoute);
      bc.postMessage({
        type: "NAV",
        payload: {
          route: nextRoute
        }
      });
    };
    const selectApp = appId => {
      setSelected(appId);
      bc.postMessage({
        type: "SELECT",
        payload: {
          appId
        }
      });
    };
    const onCreate = name => {
      const id = uid();
      const next = [...apps, {
        id,
        name: name || "Untitled"
      }];
      setApps(next);
      saveApps(next);
      bc.postMessage({
        type: "APPS_UPDATED"
      });
      selectApp(id);
      nav("integrate");
    };
    const onSaveCfg = (appId, data) => {
      const next = {
        ...cfg,
        [appId]: data
      };
      setCfg(next);
      saveCfg(next);
      setToast(true);
      bc.postMessage({
        type: "CFG_UPDATED"
      });
    };
    return <div>
        {route === "menu" && <Menu onCreate={() => nav("create")} onIntegrate={() => nav("integrate")} />}

        {route === "create" && <CreateForm onCancel={() => nav("menu")} onSave={onCreate} />}

        {route === "integrate" && <IntegrateForm apps={apps} selected={selected} onSelect={selectApp} saved={cfg[selected]} onSave={data => onSaveCfg(selected, data)} onCancel={() => nav("menu")} />}

        <Toast open={toast} onClose={() => setToast(false)}>
          Successfully saved your changes.
        </Toast>
      </div>;
  }
  function Menu({onCreate, onIntegrate}) {
    return <ul className="space-y-4 list-none login_list">
        <li className="list-none !px-0">
          <button onClick={onCreate} className="w-full text-left">
            <Card className="p-5 hover:shadow-md transition">
              <div className="flex items-center justify-between">
                <div className="flex items-center gap-4">
                  <IconTile>
                    <LightningIcon />
                  </IconTile>
                  <h2 className="text-lg">Create a new application</h2>
                </div>
                <RightChevron className="w-4 h-4 text-zinc-500" />
              </div>
            </Card>
          </button>
        </li>
        <li className="list-none !px-0">
          <button onClick={onIntegrate} className="w-full text-left">
            <Card className="p-5 hover:shadow-md transition">
              <div className="flex items-center justify-between">
                <div className="flex items-center gap-4">
                  <IconTile>
                    <LayersIcon />
                  </IconTile>
                  <h2 className="text-lg">Integrate with an existing application</h2>
                </div>
                <RightChevron className="w-4 h-4 text-zinc-500" />
              </div>
            </Card>
          </button>
        </li>
        <li className="list-none !px-0">
          <a className="no_external_icon block" href={sampleApp ? sampleApp : "/"} target="_blank" rel="noreferrer">
            <Card className="p-5 hover:shadow-md transition">
              <div className="flex items-center justify-between">
                <div className="flex items-center gap-4">
                  <IconTile>
                    <GithubIcon />
                  </IconTile>
                  <h2 className="text-lg">View a sample application</h2>
                </div>
                <RightChevron className="w-4 h-4 text-zinc-500" />
              </div>
            </Card>
          </a>
        </li>
      </ul>;
  }
  function CreateForm({onSave, onCancel}) {
    const [name, setName] = useState("");
    return <div className="space-y-6">
        <Input id="app-name" label="Application Name" placeholder="My App" value={name} onChange={setName} />
        <p className="text-sm text-zinc-500">You can change this later in the application settings.</p>
        <div className="flex gap-3">
          <Button onClick={() => onSave(name)}>Guardar</Button>
          <Button variant="outline" onClick={onCancel}>
            Cancelar
          </Button>
        </div>
      </div>;
  }
  function IntegrateForm({apps, selected, onSelect, saved, onSave, onCancel}) {
    const [callbacks, setCallbacks] = useState(saved?.callbacks ?? "");
    const [logouts, setLogouts] = useState(saved?.logouts ?? "");
    const [origins, setOrigins] = useState(saved?.origins ?? "");
    useEffect(() => {
      setCallbacks(loadCfg()[selected]?.callbacks ?? "");
      setLogouts(loadCfg()[selected]?.logouts ?? "");
      setOrigins(loadCfg()[selected]?.origins ?? "");
    }, [selected]);
    return <div className="space-y-6">
        <div>
          <span className="block text-sm text-zinc-600 dark:text-zinc-300 mb-1">Selecciona tu aplicación</span>
          <Select label="" value={selected} onChange={onSelect} options={apps} />
        </div>

        <form className="space-y-4" onSubmit={e => {
      e.preventDefault();
      onSave({
        callbacks,
        logouts,
        origins
      });
    }}>
          <Input id="callbacks" name="callbacks" label="URLs de callback" placeholder="http://localhost:3000" value={callbacks} onChange={setCallbacks} />
          <Input id="logout" name="allowed_logout_urls" label="URLs de cierre de sesión" placeholder="http://localhost:3000" value={logouts} onChange={setLogouts} />
          <Input id="origins" name="web_origins" label="Orígenes web permitidos" placeholder="http://localhost:3000" value={origins} onChange={setOrigins} />

          <div className="flex gap-3 pt-2">
            <Button type="submit">Guardar</Button>
            <Button variant="outline" type="button" onClick={onCancel}>
              Cancelar
            </Button>
          </div>
        </form>
      </div>;
  }
  return <div className="w-full mx-auto py-8">
      <Flows />
    </div>;
};

export const SignUpForm = () => {
  const [isAuthenticated, setIsAuthenticated] = useState(false);
  const [storeReady, setStoreReady] = useState(false);
  useEffect(() => {
    let unsubscribe = null;
    function init() {
      setStoreReady(true);
      unsubscribe = window.autorun(() => {
        const authenticated = window.rootStore?.sessionStore?.isAuthenticated || false;
        setIsAuthenticated(authenticated);
      });
    }
    if (window.rootStore) {
      init();
    } else {
      window.addEventListener("adu:storeReady", init);
    }
    return () => {
      window.removeEventListener("adu:storeReady", init);
      unsubscribe?.();
    };
  }, []);
  function LoggedInForm({sampleApp}) {
    const LS_APPS_KEY = "auth_demo_apps";
    const LS_APP_CFG_KEY = "auth_demo_app_cfg";
    const CHANNEL = "auth_flows_sync_v1";
    const mkChannel = () => new BroadcastChannel(CHANNEL);
    function uid() {
      return Math.random().toString(36).slice(2) + Date.now().toString(36);
    }
    function loadApps() {
      const raw = localStorage.getItem(LS_APPS_KEY);
      if (raw) return JSON.parse(raw);
      const seeded = [{
        id: "{yourClientId}",
        name: "Aplicación predeterminada"
      }];
      localStorage.setItem(LS_APPS_KEY, JSON.stringify(seeded));
      return seeded;
    }
    function saveApps(apps) {
      localStorage.setItem(LS_APPS_KEY, JSON.stringify(apps));
    }
    function loadCfg() {
      const raw = localStorage.getItem(LS_APP_CFG_KEY);
      return raw ? JSON.parse(raw) : {};
    }
    function saveCfg(cfg) {
      localStorage.setItem(LS_APP_CFG_KEY, JSON.stringify(cfg));
    }
    const RightChevron = ({className = "w-5 h-5", ...props}) => <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke="currentColor" fill="none" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={className} {...props}>
        <polyline points="9 18 15 12 9 6" />
      </svg>;
    const LightningIcon = () => <svg width="24" height="24" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
        <path fillRule="evenodd" clipRule="evenodd" className="fill-[#3F59E4] dark:fill-[#99A7F1]" d="M24.971 30.152H7.088c-1.786 0-2.745-2.103-1.574-3.453l19.07-21.988c1.33-1.532 3.835-.4 3.569 1.607L24.97 30.152z" />
        <path fillRule="evenodd" clipRule="evenodd" className="fill-[#3F59E4] dark:fill-[#99A7F1]" d="M23.201 17.885h17.885c1.787 0 2.746 2.102 1.575 3.453l-19.073 21.99c-1.33 1.532-3.835.4-3.568-1.607L23.2 17.885z" />
      </svg>;
    const LayersIcon = () => <svg width="24" height="24" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
        <path className="fill-[#3F59E4] dark:fill-[#99A7F1]" d="M34.54 29.135l6.373 3.183c1.566.782 1.566 3.017 0 3.8l-14.815 7.396a4.623 4.623 0 01-4.125 0L7.174 36.12c-1.565-.782-1.565-3.017 0-3.798l6.532-3.214" />
        <path className="fill-[#AAB6F3] dark:fill-[#3449BA]" d="M34.54 18.86l6.373 3.183c1.566.782 1.566 3.016 0 3.8L26.098 33.24a4.623 4.623 0 01-4.125 0L7.174 25.843c-1.565-.781-1.565-3.016 0-3.798l6.33-3.164" />
        <path className="fill-[#CFD6F8] dark:fill-[#22307C]" d="M21.94 23.058L7.306 15.745c-1.62-.81-1.62-3.123 0-3.932l14.631-7.319a4.693 4.693 0 014.194 0l14.648 7.319c1.622.81 1.62 3.124 0 3.932L26.13 23.058c-1.321.66-2.873.66-4.191 0z" />
      </svg>;
    const GithubIcon = () => <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="w-5 h-5">
        <path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path>
      </svg>;
    function IconTile({children}) {
      return <div className="
            shrink-0 grid place-items-center w-10 h-10 rounded-lg
            bg-indigo-50 ring-1 ring-indigo-200/60
            dark:bg-indigo-950/40 dark:ring-white/10
          ">
          {children}
        </div>;
    }
    function Card({className = "", children}) {
      return <div className={`rounded-2xl shadow-sm ring-1 ring-zinc-200 dark:ring-zinc-800 ${className}`}>{children}</div>;
    }
    function Button({variant = "primary", type = "button", onClick, children}) {
      const base = "inline-flex items-center justify-center gap-2 h-10 px-4 rounded-xl font-medium transition";
      let styles = "";
      if (variant === "primary") {
        styles = "mint-bg-indigo-600 text-white hover:mint-bg-indigo-700";
      } else if (variant === "outline") {
        styles = "border border-zinc-300 dark:border-zinc-700 mint-bg-transparent hover:mint-bg-zinc-50 dark:hover:mint-bg-zinc-800";
      } else if (variant === "ghost") {
        styles = "hover:mint-bg-zinc-100 dark:hover:mint-bg-zinc-800";
      }
      return <button type={type} onClick={onClick} className={`${base} ${styles}`}>
          {children}
        </button>;
    }
    function Input({id, label, value, onChange, placeholder, name}) {
      return <label className="block space-y-1">
          <span className="text-sm text-zinc-700 dark:text-zinc-300">{label}</span>
          <input id={id} name={name} className="w-full h-11 px-3 rounded-xl border border-zinc-300 dark:border-zinc-700 bg-white dark:bg-zinc-900 text-zinc-900 dark:text-zinc-100 focus:outline-none focus:ring-2 focus:ring-indigo-500" placeholder={placeholder} value={value} onChange={e => onChange(e.target.value)} />
        </label>;
    }
    function Select({label, value, onChange, options}) {
      return <label className="block space-y-1 max-w-[300px]">
          <span className="text-sm text-zinc-700 dark:text-zinc-300">{label}</span>
          <div className="relative">
            <select className="w-full h-11 appearance-none px-3 pr-9 rounded-xl border border-zinc-300 dark:border-zinc-700 bg-white dark:bg-zinc-900 text-zinc-900 dark:text-zinc-100 focus:outline-none focus:ring-2 focus:ring-indigo-500" value={value} onChange={e => onChange(e.target.value)}>
              <optgroup label="Generic Applications">
                {options.map(o => <option key={o.id} value={o.id}>
                    {o.name}
                  </option>)}
              </optgroup>
            </select>
            <svg className="pointer-events-none absolute right-3 top-1/2 -translate-y-1/2 w-5 h-5 text-zinc-500" viewBox="0 0 24 24">
              <path d="M7 10l5 5 5-5z" fill="currentColor" />
            </svg>
          </div>
        </label>;
    }
    function Toast({open, onClose, children}) {
      useEffect(() => {
        if (!open) return;
        const t = setTimeout(onClose, 2200);
        return () => clearTimeout(t);
      }, [open, onClose]);
      return <div className={`fixed right-4 top-4 z-50 transition ${open ? "opacity-100 translate-y-0" : "opacity-0 -translate-y-2 pointer-events-none"}`}>
          <div className="flex items-center gap-2 rounded-xl shadow ring-1 ring-emerald-200 bg-white dark:bg-zinc-900 px-4 py-2">
            <span className="w-1.5 h-8 rounded-l bg-emerald-500" />
            <svg className="w-5 h-5 text-emerald-600" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
              <path d="M20 6L9 17l-5-5" />
            </svg>
            <span className="text-sm text-zinc-900 dark:text-zinc-100">{children}</span>
          </div>
        </div>;
    }
    function Flows() {
      const [route, setRoute] = useState("menu");
      const [apps, setApps] = useState(loadApps());
      const [cfg, setCfg] = useState(loadCfg());
      const [selected, setSelected] = useState(apps[0]?.id || "");
      const [toast, setToast] = useState(false);
      const [bc] = useState(() => mkChannel());
      useEffect(() => {
        if (!apps.find(a => a.id === selected)) {
          setSelected(apps[0]?.id || "");
        }
      }, [apps, selected]);
      useEffect(() => {
        const onMsg = e => {
          const {type, payload} = e.data || ({});
          switch (type) {
            case "NAV":
              setRoute(payload.route);
              break;
            case "SELECT":
              setSelected(payload.appId);
              break;
            case "APPS_UPDATED":
              setApps(loadApps());
              break;
            case "CFG_UPDATED":
              setCfg(loadCfg());
              setToast(true);
              break;
            default:
              break;
          }
        };
        bc.addEventListener("message", onMsg);
        return () => bc.removeEventListener("message", onMsg);
      }, [bc]);
      const nav = nextRoute => {
        setRoute(nextRoute);
        bc.postMessage({
          type: "NAV",
          payload: {
            route: nextRoute
          }
        });
      };
      const selectApp = appId => {
        setSelected(appId);
        bc.postMessage({
          type: "SELECT",
          payload: {
            appId
          }
        });
      };
      const onCreate = name => {
        const id = uid();
        const next = [...apps, {
          id,
          name: name || "Sin título"
        }];
        setApps(next);
        saveApps(next);
        bc.postMessage({
          type: "APPS_UPDATED"
        });
        selectApp(id);
        nav("integrate");
      };
      const onSaveCfg = (appId, data) => {
        const next = {
          ...cfg,
          [appId]: data
        };
        setCfg(next);
        saveCfg(next);
        setToast(true);
        bc.postMessage({
          type: "CFG_UPDATED"
        });
      };
      return <div>
          {route === "menu" && <Menu onCreate={() => nav("create")} onIntegrate={() => nav("integrate")} />}

          {route === "create" && <CreateForm onCancel={() => nav("menu")} onSave={onCreate} />}

          {route === "integrate" && <IntegrateForm apps={apps} selected={selected} onSelect={selectApp} saved={cfg[selected]} onSave={data => onSaveCfg(selected, data)} onCancel={() => nav("menu")} />}

          <Toast open={toast} onClose={() => setToast(false)}>
            Successfully saved your changes.
          </Toast>
        </div>;
    }
    function Menu({onCreate, onIntegrate}) {
      return <ul className="space-y-4 list-none login_list">
          <li className="list-none !px-0">
            <button onClick={onCreate} className="w-full text-left">
              <Card className="p-5 hover:shadow-md transition">
                <div className="flex items-center justify-between">
                  <div className="flex items-center gap-4">
                    <IconTile>
                      <LightningIcon />
                    </IconTile>
                    <h2 className="text-lg">Crear una nueva aplicación</h2>
                  </div>
                  <RightChevron className="w-4 h-4 text-zinc-500" />
                </div>
              </Card>
            </button>
          </li>
          <li className="list-none !px-0">
            <button onClick={onIntegrate} className="w-full text-left">
              <Card className="p-5 hover:shadow-md transition">
                <div className="flex items-center justify-between">
                  <div className="flex items-center gap-4">
                    <IconTile>
                      <LayersIcon />
                    </IconTile>
                    <h2 className="text-lg">Integrar con una aplicación existente</h2>
                  </div>
                  <RightChevron className="w-4 h-4 text-zinc-500" />
                </div>
              </Card>
            </button>
          </li>
          <li className="list-none !px-0">
            <a className="no_external_icon block" href={sampleApp ? sampleApp : "/"} target="_blank" rel="noreferrer">
              <Card className="p-5 hover:shadow-md transition">
                <div className="flex items-center justify-between">
                  <div className="flex items-center gap-4">
                    <IconTile>
                      <GithubIcon />
                    </IconTile>
                    <h2 className="text-lg">Ver una aplicación de ejemplo</h2>
                  </div>
                  <RightChevron className="w-4 h-4 text-zinc-500" />
                </div>
              </Card>
            </a>
          </li>
        </ul>;
    }
    function CreateForm({onSave, onCancel}) {
      const [name, setName] = useState("");
      return <div className="space-y-6">
          <Input id="app-name" label="Nombre de la aplicación" placeholder="Mi aplicación" value={name} onChange={setName} />
          <p className="text-sm text-zinc-500">Puedes cambiarlo más adelante en la configuración de la aplicación.</p>
          <div className="flex gap-3">
            <Button onClick={() => onSave(name)}>Guardar</Button>
            <Button variant="outline" onClick={onCancel}>
              Cancelar
            </Button>
          </div>
        </div>;
    }
    function IntegrateForm({apps, selected, onSelect, saved, onSave, onCancel}) {
      const [callbacks, setCallbacks] = useState(saved?.callbacks ?? "");
      const [logouts, setLogouts] = useState(saved?.logouts ?? "");
      const [origins, setOrigins] = useState(saved?.origins ?? "");
      useEffect(() => {
        setCallbacks(loadCfg()[selected]?.callbacks ?? "");
        setLogouts(loadCfg()[selected]?.logouts ?? "");
        setOrigins(loadCfg()[selected]?.origins ?? "");
      }, [selected]);
      return <div className="space-y-6">
          <div>
            <span className="block text-sm text-zinc-600 dark:text-zinc-300 mb-1">Selecciona tu aplicación</span>
            <Select label="" value={selected} onChange={onSelect} options={apps} />
          </div>

          <form className="space-y-4" onSubmit={e => {
        e.preventDefault();
        onSave({
          callbacks,
          logouts,
          origins
        });
      }}>
            <Input id="callbacks" name="callbacks" label="URLs de callback" placeholder="http://localhost:3000" value={callbacks} onChange={setCallbacks} />
            <Input id="logout" name="allowed_logout_urls" label="URLs de cierre de sesión" placeholder="http://localhost:3000" value={logouts} onChange={setLogouts} />
            <Input id="origins" name="web_origins" label="Orígenes web permitidos" placeholder="http://localhost:3000" value={origins} onChange={setOrigins} />

            <div className="flex gap-3 pt-2">
              <Button type="submit">Guardar</Button>
              <Button variant="outline" type="button" onClick={onCancel}>
                Cancelar
              </Button>
            </div>
          </form>
        </div>;
    }
    return <div className="w-full mx-auto py-8">
        <Flows />
      </div>;
  }
  function SignUpFormInternal() {
    return <div className="flex flex-col gap-2 items-center h-full">
        <img noZoom src="/docs/img/quickstarts/action_hero_dashboard.svg" alt="Regístrate para obtener una cuenta de Auth0" style={{
      width: "250px",
      height: "250px"
    }} />
        <span className="text-center" style={{
      width: "400px"
    }}>
          Regístrate para obtener una{" "}
          <a href="https://auth0.com/signup" target="_blank" rel="noopener noreferrer">
            cuenta de Auth0
          </a>{" "}
          o{" "}
          <span className="font-semibold text-primary dark:text-white cursor-pointer" onClick={() => console.log("log in")}>
            inicia sesión
          </span>{" "}
          en tu cuenta existente para integrarte directamente con tu propio tenant.
        </span>
        <button onClick={() => console.log("sign up")} className="bg-primary dark:bg-primary-light text-white dark:text-black px-4 py-2 rounded-md mt-4 font-medium" style={{
      width: "140px"
    }}>
          Regístrate
        </button>
      </div>;
  }
  return <></>;
};

export const SideMenuSectionItem = ({id, children}) => {
  return <div id={`side-menu-item-${id}`} className="recipe-side-menu-item flex flex-col w-full h-full">
      {children}
    </div>;
};

export const SideMenu = ({sections, children}) => {
  const [visibleSection, setVisibleSection] = useState(sections[0]?.id ?? null);
  const checkVisibility = () => {
    let currentVisible = null;
    const viewportHeight = window.innerHeight;
    const scrollY = window.scrollY;
    sections.forEach(({id}) => {
      const section = document.getElementById(id);
      if (section) {
        const rect = section.getBoundingClientRect();
        const sectionTop = rect.top + scrollY;
        const sectionBottom = sectionTop + rect.height;
        const multiplier = viewportHeight > 1600 ? 0.34 : 0.22;
        if (scrollY + viewportHeight * multiplier >= sectionTop && scrollY <= sectionBottom) {
          currentVisible = id;
        }
      }
    });
    if (currentVisible && currentVisible !== visibleSection) {
      setVisibleSection(currentVisible);
    }
  };
  useEffect(() => {
    const throttledCheck = () => {
      setTimeout(checkVisibility, 100);
    };
    checkVisibility();
    window.addEventListener("scroll", throttledCheck);
    return () => {
      window.removeEventListener("scroll", throttledCheck);
    };
  }, [sections, visibleSection]);
  useEffect(() => {
    sections.forEach(({id}) => {
      const section = document.getElementById(id);
      const sideMenuItem = document.getElementById(`side-menu-item-${id}`);
      if (section) {
        if (id === visibleSection) {
          section.classList.add("active-section");
        } else {
          section.classList.remove("active-section");
        }
      }
      if (sideMenuItem) {
        if (id === visibleSection) {
          sideMenuItem.classList.add("active-side-menu-item");
        } else {
          sideMenuItem.classList.remove("active-side-menu-item");
        }
      }
    });
  }, [visibleSection, sections]);
  return <div className="recipe-side-menu sticky px-2 py-1" style={{
    height: "calc(100vh - 7rem)",
    top: "7rem",
    scrollMarginTop: "var(--scroll-mt)"
  }}>
      {children.map(child => {
    if (child.props.id === visibleSection) {
      return child;
    }
    return null;
  })}
    </div>;
};

export const Section = ({id, title, stepNumber, children, isSingleColumn = false}) => {
  return <div id={id} className={`recipe-section flex flex-col transition-opacity duration-200`}>
      {}
      <Step title={title} stepNumber={stepNumber} titleSize="h3">
        {children}
      </Step>
    </div>;
};

export const Content = ({title, children}) => {
  return <div className="recipe-content flex flex-col">
      {title && <h1 className="text-3xl">{title}</h1>}
      {children}
    </div>;
};

export const Recipe = ({children, isSingleColumn = false}) => {
  return <div className={`pl-4 recipe-container mx-auto grid grid-cols-1 gap-10 relative ${isSingleColumn ? "md:grid-cols-1" : "md:grid-cols-2"}`}>
      {children}
    </div>;
};

export const sections = [{
  id: "prerequisites",
  title: "Requisitos previos"
}, {
  id: "request-device-code",
  title: "Solicitar code de dispositivo"
}, {
  id: "receive-device-code",
  title: "Recibir el code de dispositivo"
}, {
  id: "request-device-activation",
  title: "Solicitar la activación del dispositivo"
}, {
  id: "poll-the-token-endpoint",
  title: "Consultar periódicamente el endpoint de token"
}, {
  id: "user-authorization",
  title: "Autorización del usuario"
}, {
  id: "receive-tokens",
  title: "Recibir tokens"
}, {
  id: "call-your-api",
  title: "Llamar a tu API"
}, {
  id: "refresh-tokens",
  title: "Actualizar tokens"
}, {
  id: "troubleshooting",
  title: "Solución de problemas"
}, {
  id: "sample-implementations",
  title: "Implementaciones de ejemplo"
}, {
  id: "limitations",
  title: "Limitaciones"
}];

<Recipe>
  <Content>
    Este tutorial muestra cómo llamar a tu API desde un dispositivo con entrada restringida mediante el Flujo de Autorización de Dispositivos. Te recomendamos que inicies sesión para
    seguir este inicio rápido con ejemplos configurados para tu cuenta.

    Para una experiencia interactiva, puede utilizar el [entorno de pruebas de Device Flow](https://auth0.github.io/device-flow-playground/).

    <Section id={sections[0].id} title={sections[0].title} stepNumber="1">
      * Registre una aplicación nativa.
      * Asegúrese de que el interruptor **Conforme con OIDC** esté habilitado. Para obtener más información, consulte Autenticación conforme con OIDC.
      * Agregue **Código de dispositivo** a los tipos de concesión de la aplicación. Para obtener más información, consulte Actualizar tipos de concesión.
      * Agregue **Token de actualización** a los tipos de concesión de la aplicación si desea habilitar los tokens de actualización.
      * Configure y habilite al menos una conexión para la
        aplicación.
      * Registre su API en Auth0.
      * Habilite **Permitir acceso sin conexión** si usa tokens de actualización. Para obtener más información, consulte Configuración de la API.
      * Configure los ajustes del código de usuario del dispositivo para definir el
        conjunto de caracteres, el formato y la longitud del código de usuario generado aleatoriamente.
    </Section>

    <Section id={sections[1].id} title={sections[1].title} stepNumber="2">
      Cuando el usuario inicia la aplicación del dispositivo y desea autorizarla, su aplicación debe solicitar un code de dispositivo a la Authentication API de Auth0 para asociarlo con la sesión del usuario.

      Para obtener el código de dispositivo, su aplicación debe llamar al [Authorize endpoint](https://auth0.com/docs/api/authentication#-post-oauth-device-code-) del flujo de autorización de dispositivos de la API de autenticación:

      <AuthCodeGroup>
        ```bash cURL lines theme={null}
        curl --request post \
          --url 'https://{yourDomain}/oauth/device/code' \
          --header 'content-type: application/x-www-form-urlencoded'
        ```

        ```csharp C# lines theme={null}
        var client = new RestClient("https://{yourDomain}/oauth/device/code");
        var request = new RestRequest(Method.POST);
        request.AddHeader("content-type", "application/x-www-form-urlencoded");
        IRestResponse response = client.Execute(request);
        ```

        ```go Go lines  theme={null}
        package main
        import (
        "fmt"
        "net/http"
        "io/ioutil"
        )
        func main() {
        url := "https://{yourDomain}/oauth/device/code"
        req, _ := http.NewRequest("post", url, nil)
        req.Header.Add("content-type", "application/x-www-form-urlencoded")
        res, _ := http.DefaultClient.Do(req)
        defer res.Body.Close()
        body, _ := ioutil.ReadAll(res.Body)
        fmt.Println(res)
        fmt.Println(string(body))
        }
        ```

        ```java Java lines theme={null}
        HttpResponse<String> response = Unirest.post("https://{yourDomain}/oauth/device/code")
          .header("content-type", "application/x-www-form-urlencoded")
          .asString();
        ```

        ```javascript Node.JS lines theme={null}
        var axios = require("axios").default;
        var options = {
        method: 'post',
        url: 'https://{yourDomain}/oauth/device/code',
        headers: {'content-type': 'application/x-www-form-urlencoded'}
        };
        axios.request(options).then(function (response) {
        console.log(response.data);
        }).catch(function (error) {
        console.error(error);
        });
        ```

        ```objc Obj-C lines  theme={null}
        #import <Foundation/Foundation.h>
        NSDictionary *headers = @{ @"content-type": @"application/x-www-form-urlencoded" };
        NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"https://{yourDomain}/oauth/device/code"]
                                                          cachePolicy:NSURLRequestUseProtocolCachePolicy

                                                      timeoutInterval:10.0];

        [request setHTTPMethod:@"post"];
        [request setAllHTTPHeaderFields:headers];
        NSURLSession *session = [NSURLSession sharedSession];
        NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request
                                                completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {

                                                    if (error) {

                                                        NSLog(@&quot;%@&quot;, error);

                                                    } else {

                                                        NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *) response;

                                                        NSLog(@&quot;%@&quot;, httpResponse);

                                                    }

                                                }];

        [dataTask resume];
        ```

        ```php PHP lines  theme={null}
        $curl = curl_init();
        curl_setopt_array($curl, [
        CURLOPT_URL => "https://{yourDomain}/oauth/device/code",
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_ENCODING => "",
        CURLOPT_MAXREDIRS => 10,
        CURLOPT_TIMEOUT => 30,
        CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
        CURLOPT_CUSTOMREQUEST => "post",
        CURLOPT_HTTPHEADER => [
        &quot;content-type: application/x-www-form-urlencoded&quot;

        ],
        ]);
        $response = curl_exec($curl);
        $err = curl_error($curl);
        curl_close($curl);
        if ($err) {
        echo "cURL Error #:" . $err;
        } else {
        echo $response;
        }
        ```

        ```python Python lines theme={null}
        import http.client
        conn = http.client.HTTPSConnection("")
        headers = { 'content-type': "application/x-www-form-urlencoded" }
        conn.request("post", "/{yourDomain}/oauth/device/code", headers=headers)
        res = conn.getresponse()
        data = res.read()
        print(data.decode("utf-8"))
        ```

        ```ruby Ruby lines theme={null}
        require 'uri'
        require 'net/http'
        require 'openssl'
        url = URI("https://{yourDomain}/oauth/device/code")
        http = Net::HTTP.new(url.host, url.port)
        http.use_ssl = true
        http.verify_mode = OpenSSL::SSL::VERIFY_NONE
        request = Net::HTTP::Post.new(url)
        request["content-type"] = 'application/x-www-form-urlencoded'
        response = http.request(request)
        puts response.read_body
        ```

        ```swift Swift lines  theme={null}
        import Foundation
        let headers = ["content-type": "application/x-www-form-urlencoded"]
        let request = NSMutableURLRequest(url: NSURL(string: "https://{yourDomain}/oauth/device/code")! as URL,
                                            cachePolicy: .useProtocolCachePolicy,

                                        timeoutInterval: 10.0)

        request.httpMethod = "post"
        request.allHTTPHeaderFields = headers
        let session = URLSession.shared
        let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
        if (error != nil) {
        print(error)

        } else {
        let httpResponse = response as? HTTPURLResponse

        print(httpResponse)

        }
        })
        dataTask.resume()
        ```
      </AuthCodeGroup>
    </Section>

    <Section id={sections[2].id} title={sections[2].title} stepNumber="3">
      La aplicación del dispositivo debería recibir una respuesta HTTP 200 y una carga útil similar a esta:

      ```json lines theme={null}
      {
        "device_code": "GmRh...k9eS",
        "user_code": "WDJB-MJHT",
        "verification_uri": "https://my-tenant.auth0.com/device",
        "verification_uri_complete": "https://my-tenant.auth0.com/device?user_code=WDJB-MJHT",
        "expires_in": 900,
        "interval": 5
      }
      ```
    </Section>

    <Section id={sections[3].id} title={sections[3].title} stepNumber="4">
      Después de que la aplicación del dispositivo reciba `device_code` y `user_code`, debe
      indicarle al usuario que vaya a `verification_uri` e introduzca `user_code`.

      <Frame>
        <img src="https://mintcdn.com/translations/3nS3prIggmJG9TUI/docs/images/cdy7uua7fh8z/3Q9q41wocl6SojRoiGefXT/a98582a3c86740aaeb3d957f2dc4afe6/request-device-activation.png?fit=max&auto=format&n=3nS3prIggmJG9TUI&q=85&s=63ab39d633ebef6b8aa1ca47e28be708" width="1500" height="1082" data-path="docs/images/cdy7uua7fh8z/3Q9q41wocl6SojRoiGefXT/a98582a3c86740aaeb3d957f2dc4afe6/request-device-activation.png" />
      </Frame>

      <Info>
        `device_code` no está pensado para el usuario y no debe mostrarse durante
        la interacción para evitar confundirlo.
      </Info>

      <Info>
        Al crear una CLI, puede omitir este paso y abrir inmediatamente el navegador con
        `verification_uri_complete`.
      </Info>
    </Section>

    <Section id={sections[4].id} title={sections[4].title} stepNumber="5">
      Mientras la aplicación de su dispositivo espera a que el usuario la active, debe llamar de forma intermitente al [endpoint POST /oauth/token de la API de autenticación](https://auth0.com/docs/api/authentication#-post-oauth-token-)
      y gestionar la respuesta adecuadamente.

      <Info>
        Asegúrese de que la aplicación de su dispositivo espere la duración de `interval` (en segundos) o hasta
        que reciba una respuesta exitosa, lo que sea mayor, para evitar problemas de latencia de red.
      </Info>

      ```bash theme={null}
      curl --request POST \ 
        --url 'https://{yourDomain}/oauth/token' \
        --header 'content-type: application/x-www-form-urlencoded' \
        --data grant_type=urn:ietf:params:oauth:grant-type:device_code \
        --data device_code=AUTH0_SCOPES \
        --data 'client_id={yourClientId}'
      ```
    </Section>

    <Section id={sections[5].id} title={sections[5].title} stepNumber="6">
      El usuario escaneará el código QR o bien abrirá la página de activación e introducirá el code de usuario:

      <Frame>
        <img src="https://mintcdn.com/translations/c0RQ9V0YAcT0-8l5/docs/images/cdy7uua7fh8z/7IwrVX4s5a36CvfY95rKCo/dfc962ecde65502945741b1dc8b5c879/Device_Activation_-_English.png?fit=max&auto=format&n=c0RQ9V0YAcT0-8l5&q=85&s=4ca2f4ac3ad08e7629cae14f94870e23" width="493" height="669" data-path="docs/images/cdy7uua7fh8z/7IwrVX4s5a36CvfY95rKCo/dfc962ecde65502945741b1dc8b5c879/Device_Activation_-_English.png" />
      </Frame>

      Se mostrará una página de confirmación para que el usuario confirme que se trata del dispositivo correcto:

      <Frame>
        <img src="https://mintcdn.com/translations/MV7tE-x71x8RWRES/docs/images/cdy7uua7fh8z/5dwhOyM1HRNwfV3Co4Da2o/424a2bc1a1bb44a54c587a0bcddd8222/Device_Confirmation_-_English.png?fit=max&auto=format&n=MV7tE-x71x8RWRES&q=85&s=8abd58dfb1cb31c6930d7c0478226ead" width="495" height="672" data-path="docs/images/cdy7uua7fh8z/5dwhOyM1HRNwfV3Co4Da2o/424a2bc1a1bb44a54c587a0bcddd8222/Device_Confirmation_-_English.png" />
      </Frame>

      El usuario completará la transacción iniciando sesión. Este paso puede incluir uno o varios de los siguientes
      procesos:

      * Autenticar al usuario
      * Redirigir al usuario a un Proveedor de identidad para realizar la autenticación
      * Comprobar si hay sesiones de SSO activas
      * Obtener el consentimiento del usuario para el dispositivo, a menos que ya se haya otorgado previamente

      <Frame>
        <img src="https://mintcdn.com/translations/3nS3prIggmJG9TUI/docs/images/cdy7uua7fh8z/3GqqaNB7sjcAYTQiTnEEsn/7199a9b319283686057ca32c853f7a05/Login_screen_-_English.png?fit=max&auto=format&n=3nS3prIggmJG9TUI&q=85&s=3b8ce1c4a34fa7bacf72b53c69838400" width="497" height="925" data-path="docs/images/cdy7uua7fh8z/3GqqaNB7sjcAYTQiTnEEsn/7199a9b319283686057ca32c853f7a05/Login_screen_-_English.png" />
      </Frame>

      Tras la autenticación correcta y el consentimiento, se mostrará la pantalla de confirmación:

      <Frame>
        <img src="https://mintcdn.com/translations/Dcx0M11uuptU53TX/docs/images/cdy7uua7fh8z/2TsQpMa8fzifiojuEXLvDo/484a49b775ec5a6b3522983a425599fe/Success_message_-_English.png?fit=max&auto=format&n=Dcx0M11uuptU53TX&q=85&s=b44d9cdd65cd73d0bc1401e682e06038" width="497" height="672" data-path="docs/images/cdy7uua7fh8z/2TsQpMa8fzifiojuEXLvDo/484a49b775ec5a6b3522983a425599fe/Success_message_-_English.png" />
      </Frame>

      En este punto, el usuario se ha autenticado y el dispositivo ha sido autorizado.
    </Section>

    <Section id={sections[6].id} title={sections[6].title} stepNumber="7">
      Una vez que el usuario autoriza la aplicación del dispositivo, esta recibe una respuesta HTTP 200 y la siguiente carga:

      ```json lines theme={null}
      {
        "access_token": "eyJz93a...k4laUWw",
        "refresh_token": "GEbRxBN...edjnXbL",
        "id_token": "eyJ0XAi...4faeEoQ",
        "token_type": "Bearer",
        "expires_in": 86400
      }
      ```

      <Info>
        Debe validar sus tokens antes de guardarlos. Para saber cómo hacerlo, lea Validar tokens de acceso y Validar tokens de ID.
      </Info>

      Los tokens de acceso se usan para llamar al [endpoint Get User Info de la API de autenticación](https://auth0.com/docs/api/authentication#get-user-info) (si su aplicación del dispositivo
      solicitó el scope `openid`) o a la API especificada por el parámetro `audience`.
      Si llama a su propia API, su aplicación del dispositivo debe verificar el token de acceso antes de usarlo.

      Los tokens de ID contienen información del usuario que debe [decodificarse y extraerse](/es/docs/secure/tokens/id-tokens#id-token-payload). La API de autenticación
      solo devuelve un `id_token` si su aplicación del dispositivo solicitó el scope `openid`.

      Los tokens de actualización se usan para obtener un nuevo token de acceso o
      token de ID después de que haya expirado el anterior. La API de autenticación solo devuelve un `refresh_token` si
      la configuración **Allow Offline Access** está habilitada para la API especificada por el parámetro `audience`
      y su aplicación del dispositivo solicitó el scope `offline_access`.
    </Section>

    <Section id={sections[7].id} title={sections[7].title} stepNumber="8">
      Para llamar a tu API, la aplicación de tu dispositivo debe enviar el token de acceso como token Bearer en el
      encabezado `Authorization` de tu solicitud HTTP.

      ```bash lines theme={null}
      curl --request GET \
        --url https://myapi.com/api \
        --header 'authorization: Bearer AUTH0_API_ACCESS_TOKEN' \
        --header 'content-type: application/json'
      ```
    </Section>

    <Section id={sections[8].id} title={sections[8].title} stepNumber="9">
      Para obtener un nuevo token de acceso para un usuario, la aplicación del dispositivo puede llamar al [endpoint POST /oauth/token de la Authentication API](https://auth0.com/docs/api/authentication#-post-oauth-token-) con el
      parámetro `refresh_token`.

      ```bash lines theme={null}
      curl --request POST \
        --url 'https://{yourDomain}/oauth/token' \
        --header 'content-type: application/x-www-form-urlencoded' \
        --data grant_type=refresh_token \
        --data 'client_id={yourClientId}' \
        --data 'client_secret={yourClientSecret}' \
        --data refresh_token=AUTH0_REFRESH_TOKEN
      ```

      Si la solicitud se realiza correctamente, la aplicación de su dispositivo recibe una respuesta HTTP 200 con la siguiente carga útil:

      ```json lines theme={null}
      {
        "access_token": "eyJ...MoQ",
        "expires_in": 86400,
        "scope": "openid offline_access",
        "id_token": "eyJ...0NE",
        "token_type": "Bearer"
      }
      ```

      Para obtener más información sobre los tokens de actualización, consulta [Tokens de actualización](https://auth0.com/docs/secure/tokens/refresh-tokens).
    </Section>

    <Section id={sections[9].id} title={sections[9].title} stepNumber="10">
      Los registros del Tenant se crean para cualquier interacción que se produzca y pueden usarse para solucionar problemas.

      ### Respuestas del token

      Mientras esperas a que el usuario autorice el dispositivo, es posible que recibas varias respuestas HTTP 4xx.

      #### Autorización pendiente

      Verás este error mientras esperas a que el usuario realice una acción. Sigue consultando con el `interval` sugerido que recuperaste en el paso anterior de este tutorial.

      ```json lines theme={null}
      `HTTP 403`
      {
      "error": "authorization_pending",
      "error_description": "..."
      }
      ```

      #### Reduzca la frecuencia

      Está realizando sondeos con demasiada frecuencia. Reduzca la frecuencia y use el intervalo sugerido obtenido en el paso anterior de este
      tutorial. Para evitar este error debido a la latencia de la red, debe empezar a contar cada intervalo después de
      recibir la respuesta a la última solicitud de sondeo.

      ```json lines theme={null}
      `HTTP 429`
      {
      "error": "slow_down",
      "error_description": "..."
      }
      ```

      #### Token expirado

      El usuario no ha autorizado el dispositivo con la suficiente rapidez, por lo que `device_code` ha expirado. Su
      aplicación debe notificarle que el flujo ha expirado y pedirle que lo reinicie.

      <Info>
        El error `expired_token` se devuelve exactamente una vez. Después de eso, el endpoint devuelve el
        error `invalid_grant`.
      </Info>

      ```json lines theme={null}
      `HTTP 403`
      {
      "error": "expired_token",
      "error_description": "..."
      }
      ```

      #### Acceso denegado

      Si se deniega el acceso, recibirá:

      ```json lines theme={null}
      `HTTP 403`
      {
      "error": "access_denied",
      "error_description": "..."
      }
      ```

      Esto puede ocurrir por varios motivos, entre ellos:

      * El usuario rechazó autorizar el dispositivo.
      * El servidor de autorización denegó la transacción.
      * Una Action configurada denegó el acceso.
    </Section>

    <Section id={sections[10].id} title={sections[10].title} stepNumber="11">
      Consulta los ejemplos a continuación para aprender a implementar este flujo en aplicaciones del mundo real.

      * [Entorno de pruebas de autorización de dispositivos](https://auth0.github.io/device-flow-playground/)
      * [AppleTV (Swift)](https://github.com/pushpabrol/auth0-device-flow-appletv): Aplicación sencilla que muestra cómo se puede usar Auth0 con el
        flujo de autorización de dispositivos en un AppleTV.
      * [CLI (Node.js)](https://gist.github.com/panva/652c61e7d847e0ed99926c324fa91b36): Implementación de ejemplo de una CLI que usa el flujo de autorización
        de dispositivos en lugar del flujo de código de autorización. La principal diferencia es que tu CLI no necesita
        alojar un servidor web ni escuchar en un puerto.
    </Section>

    <Section id={sections[11].id} title={sections[11].title} stepNumber="12">
      Para usar el flujo de autorización de dispositivos, una aplicación para dispositivos debe:

      * Ser compatible con Server Name Indication (SNI)
      * Ser una aplicación nativa
      * Tener el [método de autenticación](/es/docs/secure/application-credentials#application-authentication-methods) configurado como **None**
      * Ser [conforme con OIDC](/es/docs/get-started/applications/application-settings#oauth)
      * No haberse creado mediante Dynamic Client Registration

      Además, el flujo de autorización de dispositivos no permite lo siguiente:

      * Conexiones sociales que usen claves de desarrollador de Auth0, a menos que utilice New Universal Login Experience
      * Acceder a parámetros de cadena de consulta desde una página de inicio de sesión alojada o Actions.
    </Section>

    ## Pasos siguientes

    ¡Excelente trabajo! Si llegaste hasta aquí, ya deberías tener el inicio de sesión, el cierre de sesión y la información del perfil de usuario funcionando en tu aplicación.

    Con esto concluye nuestro tutorial de inicio rápido, pero hay mucho más por explorar. Para saber más sobre lo que puedes hacer con Auth0, consulta:

    * [Auth0 Dashboard](https://manage.auth0.com/dashboard/us/dev-gja8kxz4ndtex3rq) - Aprende a configurar y administrar tu inquilino y tus aplicaciones de Auth0
    * [Auth0 Marketplace](https://marketplace.auth0.com/) - Descubra las integraciones que puede habilitar para ampliar las funcionalidades de Auth0
  </Content>

  <SideMenu sections={sections}>
    <SideMenuSectionItem id={sections[0].id}>
      <SignUpForm />
    </SideMenuSectionItem>

    <SideMenuSectionItem id={sections[1].id}>
      <SignUpForm />
    </SideMenuSectionItem>

    <SideMenuSectionItem id={sections[2].id}>
      <SignUpForm />
    </SideMenuSectionItem>

    <SideMenuSectionItem id={sections[3].id}>
      <SignUpForm />
    </SideMenuSectionItem>

    <SideMenuSectionItem id={sections[4].id}>
      <SignUpForm />
    </SideMenuSectionItem>

    <SideMenuSectionItem id={sections[5].id}>
      <SignUpForm />
    </SideMenuSectionItem>

    <SideMenuSectionItem id={sections[6].id}>
      <SignUpForm />
    </SideMenuSectionItem>

    <SideMenuSectionItem id={sections[7].id}>
      <SignUpForm />
    </SideMenuSectionItem>

    <SideMenuSectionItem id={sections[8].id}>
      <SignUpForm />
    </SideMenuSectionItem>

    <SideMenuSectionItem id={sections[9].id}>
      <SignUpForm />
    </SideMenuSectionItem>

    <SideMenuSectionItem id={sections[10].id}>
      <SignUpForm />
    </SideMenuSectionItem>

    <SideMenuSectionItem id={sections[11].id}>
      <SignUpForm />
    </SideMenuSectionItem>
  </SideMenu>
</Recipe>
