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

# Configura tus apps de gestor de contraseñas

> Crea la cuenta de tu bóveda, guarda una primera contraseña y conecta las apps de Bitwarden de tu teléfono, navegador y ordenador a tu propia bóveda.

export const ClientSetupGuide = ({id, labels, locale = 'en', direction = 'ltr', meta = [], requirements = [], requirementsHelp, appsTitle = labels.appsTitle, vendor, apps = [], appsNote, stepsTitle = labels.stepsTitle, steps = [], help = [], helpTitle = labels.helpTitle, next = [], nextTitle = labels.nextTitle, attribution, sources}) => {
  const Anchor = 'a', Article = 'article', Details = 'details', Dialog = 'dialog', Figcaption = 'figcaption', Figure = 'figure', Footer = 'footer', Img = 'img', Summary = 'summary';
  const number = new Intl.NumberFormat(locale);
  const stepNumber = new Intl.NumberFormat(locale, {
    minimumIntegerDigits: 2
  });
  const message = (key, values = {}) => labels[key].replace(/\{(\w+)\}/g, (_, name) => String(values[name]));
  const count = steps.length;
  const last = count - 1;
  const storageKey = `kombify-client-guide:${id}:v1`;
  const rootRef = useRef(null);
  const zoomRef = useRef(null);
  const toastTimer = useRef(null);
  const focusTab = useRef(false);
  const [current, setCurrent] = useState(0);
  const [completed, setCompleted] = useState(() => steps.map(() => false));
  const [verified, setVerified] = useState(() => steps.map(step => (step.verify || []).map(() => false)));
  const [failed, setFailed] = useState({});
  const [zoom, setZoom] = useState(null);
  const [toast, setToast] = useState('');
  const isExternal = href => (/^https?:\/\//).test(href || '');
  const icon = name => {
    const paths = {
      apple: <path d="M16.7 1.8c0 2.1-1.7 4.4-3.7 4.2-.4-2.1 1.8-4.3 3.7-4.2ZM20.4 17.3c-.5 1.2-.8 1.7-1.5 2.7-1 1.5-2.4 3.3-4.2 3.3-1.6 0-2-1-4.1-.9-2.1 0-2.5 1-4.2.9-1.8-.1-3.1-1.7-4.1-3.2C-.5 15.9-.7 10.4 1 7.6c1.2-2 3.1-3.1 4.9-3.1 1.9 0 3.1 1 4.7 1 1.5 0 2.5-1 4.7-1 1.6 0 3.3.9 4.5 2.3-4 2.2-3.3 8 0 10.5Z" fill="currentColor" stroke="none" transform="translate(2 0) scale(.87)" />,
      android: <><path d="M5 12a7 7 0 0 1 14 0v6H5Zm2-6L5 3m12 3 2-3M2 12v5m20-5v5M8 18v4m8-4v4" /><path d="M8.5 9h.01M15.5 9h.01" /></>,
      browser: <><rect height="16" rx="2" width="20" x="2" y="4" /><path d="M2 9h20M6 6.5h.01M9 6.5h.01" /></>,
      desktop: <><rect height="14" rx="2" width="20" x="2" y="3" /><path d="M8 22h8M12 17v5" /></>,
      web: <><circle cx="12" cy="12" r="9" /><path d="M3 12h18M12 3a14 14 0 0 1 0 18M12 3a14 14 0 0 0 0 18" /></>,
      server: <><rect height="7" rx="2" width="18" x="3" y="3" /><rect height="7" rx="2" width="18" x="3" y="14" /><path d="M7 6.5h.01M7 17.5h.01M16 6.5h2M16 17.5h2" /></>,
      phone: <><rect height="20" rx="3" width="12" x="6" y="2" /><path d="M10 5h4M11 19h2" /></>,
      steps: <><rect height="7" rx="1.5" width="7" x="3" y="3" /><rect height="7" rx="1.5" width="7" x="14" y="3" /><rect height="7" rx="1.5" width="7" x="3" y="14" /><rect height="7" rx="1.5" width="7" x="14" y="14" /></>,
      book: <path d="M12 5v15M3 4c4-1 6 0 9 1 3-1 5-2 9-1v15c-4-1-6 0-9 1-3-1-5-2-9-1Z" />,
      clock: <><circle cx="12" cy="12" r="9" /><path d="M12 7v5l3 2" /></>,
      print: <><path d="M6 8V3h12v5M6 17H3V8h18v9h-3M6 14h12v7H6Z" /><path d="M17 11h.01" /></>,
      check: <path d="m5 12 4 4L19 6" />,
      arrow: <path d="M5 12h14M13 6l6 6-6 6" />,
      back: <path d="m15 5-7 7 7 7" />,
      external: <><path d="M14 4h6v6m0-6L10 14" /><path d="M10 4H5a1 1 0 0 0-1 1v14a1 1 0 0 0 1 1h14a1 1 0 0 0 1-1v-5" /></>,
      info: <><circle cx="12" cy="12" r="9" /><path d="M12 11v5M12 7h.01" /></>,
      ok: <><circle cx="12" cy="12" r="9" /><path d="m8 12 3 3 5-6" /></>,
      camera: <><path d="M3 7h4l2-3h6l2 3h4v13H3Z" /><circle cx="12" cy="13" r="4" /></>,
      zoom: <><circle cx="10.5" cy="10.5" r="6.5" /><path d="m16 16 5 5M7 10.5h7M10.5 7v7" /></>,
      shield: <><path d="M12 3 3 7v6c0 5 9 9 9 9s9-4 9-9V7Z" /><path d="m8 12 3 3 5-6" /></>,
      close: <path d="m6 6 12 12M6 18 18 6" />
    };
    return <svg aria-hidden="true" className="ico" fill="none" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.7" viewBox="0 0 24 24">{paths[name] || paths.info}</svg>;
  };
  const safeHref = href => (/^(https:\/\/|\/|#)/).test(href || '') ? href : '#';
  const link = (href, children, className, key) => isExternal(href) ? <Anchor className={className || ''} href={safeHref(href)} key={key} rel="noopener noreferrer" target="_blank">{children} {icon('external')}<span className="sr-only">{labels.opensInNewTab}</span></Anchor> : <Anchor className={className || ''} href={safeHref(href)} key={key} onClick={(href || '').startsWith('#') ? () => {
    zoomRef.current?.close();
    reveal(href.slice(1));
  } : undefined}>{children}</Anchor>;
  const rich = text => {
    if (!text || !text.split) return text;
    return text.split(/(\[\[[^\]]+\]\]|\*\*[^*]+\*\*|\[[^\]]+\]\([^)\s]+\))/g).map((part, i) => {
      if ((/^\[\[[^\]]+\]\]$/).test(part)) return <span className="key" key={i}>{part.slice(2, -2)}</span>;
      if ((/^\*\*[^*]+\*\*$/).test(part)) return <strong key={i}>{part.slice(2, -2)}</strong>;
      const match = (/^\[([^\]]+)\]\(([^)\s]+)\)$/).exec(part);
      if (match) return link(match[2], match[1], undefined, i);
      return part;
    });
  };
  useEffect(() => {
    try {
      const saved = JSON.parse(localStorage.getItem(storageKey) || 'null');
      if (Array.isArray(saved?.completed) && saved.completed.length === count) setCompleted(saved.completed.map(Boolean));
      if (Array.isArray(saved?.verified) && saved.verified.length === count) setVerified(saved.verified.map((list, i) => (steps[i].verify || []).map((_, j) => Boolean(list?.[j]))));
    } catch (_) {}
    const root = rootRef.current;
    const timer = setTimeout(() => {
      if (!root) return;
      const broken = {};
      root.querySelectorAll('.screen-image > img').forEach(image => {
        if (image.complete && !image.naturalWidth) broken[image.getAttribute('src')] = true;
      });
      if (Object.keys(broken).length) setFailed(old => ({
        ...old,
        ...broken
      }));
    }, 7000);
    const openAll = () => root?.querySelectorAll('details').forEach(node => {
      node.open = true;
    });
    window.addEventListener('beforeprint', openAll);
    return () => {
      clearTimeout(timer);
      clearTimeout(toastTimer.current);
      window.removeEventListener('beforeprint', openAll);
    };
  }, []);
  useEffect(() => {
    if (!focusTab.current) return;
    focusTab.current = false;
    rootRef.current?.querySelector(`[id="${id}-tab-${current}"]`)?.focus();
  }, [current]);
  const persist = (nextCompleted, nextVerified) => {
    try {
      localStorage.setItem(storageKey, JSON.stringify({
        completed: nextCompleted,
        verified: nextVerified
      }));
    } catch (_) {}
  };
  const say = text => {
    setToast('');
    setTimeout(() => setToast(text), 60);
    clearTimeout(toastTimer.current);
    toastTimer.current = setTimeout(() => setToast(''), 3800);
  };
  const select = (index, focus) => {
    const target = Math.max(0, Math.min(last, index));
    setCurrent(target);
    focusTab.current = Boolean(focus);
  };
  const onNext = () => {
    const checks = verified[current] || [];
    if (checks.length && !checks.every(Boolean)) {
      say(labels.verifyRequired);
      rootRef.current?.querySelector(`[id="${id}-verify-${current}-${checks.indexOf(false)}"]`)?.focus();
      return;
    }
    const nextCompleted = completed.map((done, i) => i === current ? true : done);
    setCompleted(nextCompleted);
    persist(nextCompleted, verified);
    if (current < last) select(current + 1, true); else {
      const done = nextCompleted.filter(Boolean).length;
      say(done === count ? labels.allDone : message('remainingSteps', {
        done: number.format(done),
        count: number.format(count)
      }));
      rootRef.current?.querySelector(`[id="${id}-more"], [id="${id}-help"]`)?.scrollIntoView({
        block: 'start'
      });
    }
  };
  const onVerify = (stepIndex, checkIndex, value) => {
    const nextVerified = verified.map((list, i) => i === stepIndex ? list.map((v, j) => j === checkIndex ? value : v) : list);
    const nextCompleted = completed.map((done, i) => i === stepIndex && !value ? false : done);
    setVerified(nextVerified);
    setCompleted(nextCompleted);
    persist(nextCompleted, nextVerified);
  };
  const onReset = () => {
    const nextCompleted = steps.map(() => false);
    const nextVerified = steps.map(step => (step.verify || []).map(() => false));
    setCompleted(nextCompleted);
    setVerified(nextVerified);
    persist(nextCompleted, nextVerified);
    select(0);
    say(labels.progressReset);
  };
  const onTabKey = event => {
    const moves = {
      ArrowRight: c => (c + 1) % count,
      ArrowLeft: c => (c - 1 + count) % count,
      Home: () => 0,
      End: () => last
    };
    const move = moves[event.key];
    if (!move) return;
    event.preventDefault();
    focusTab.current = true;
    setCurrent(move);
  };
  const openZoom = shot => {
    setZoom(shot);
    const dialog = zoomRef.current;
    if (dialog && !dialog.open) dialog.showModal();
  };
  const closeOnBackdrop = event => {
    const dialog = event.currentTarget;
    if (event.target !== dialog) return;
    const rect = dialog.getBoundingClientRect();
    if (event.clientX < rect.left || event.clientX > rect.right || event.clientY < rect.top || event.clientY > rect.bottom) dialog.close();
  };
  const reveal = targetId => {
    const node = rootRef.current?.querySelector(`[id="${targetId}"]`);
    const details = node && (node.tagName === 'DETAILS' ? node : node.querySelector('details'));
    if (details) details.open = true;
  };
  const renderShot = (shot, stepIndex) => {
    const unavailable = failed[shot.src];
    const label = shot.vendor ? labels.vendorScreenshotTag : labels.kombifyScreenshotTag;
    return <>
        <span className="screenshot-tag">{icon('camera')} {label}</span>
        <Figure className={`screen-figure${shot.frame === 'phone' ? ' phone' : ''}${unavailable ? ' image-unavailable' : ''}`}>
          <div className="screen-image">
            <Img alt={shot.alt} className={shot.srcDark ? 'only-light has-dark' : undefined} decoding="async" height={shot.height} loading={stepIndex === 0 ? 'eager' : 'lazy'} onError={() => setFailed(old => ({
      ...old,
      [shot.src]: true
    }))} referrerPolicy="no-referrer" src={shot.src} width={shot.width} />
            {shot.srcDark ? <Img alt={shot.alt} className="only-dark" decoding="async" height={shot.height} loading="lazy" onError={() => setFailed(old => ({
      ...old,
      [shot.src]: true
    }))} src={shot.srcDark} width={shot.width} /> : null}
            {(shot.vendor ? [] : shot.annotations || []).map((mark, i) => <span aria-hidden="true" className="annotation" data-n={mark.n ?? undefined} key={i} style={{
      top: `${mark.top}%`,
      left: `${mark.left}%`,
      width: `${mark.width}%`,
      height: `${mark.height}%`
    }} />)}
            <Anchor className="image-fallback" href={shot.vendor ? shot.creditHref || shot.src : shot.src} rel="noopener noreferrer" target="_blank">{icon('external')} {shot.vendor ? labels.openVendorPage : labels.openScreenshot}<span>{shot.vendor ? labels.vendorImageUnavailable : labels.imageUnavailable}</span></Anchor>
            <button aria-label={message('enlargeScreenshot', {
      description: shot.alt
    })} className="zoom-button" onClick={() => openZoom(shot)} type="button">{icon('zoom')}</button>
          </div>
          <Figcaption>
            {shot.caption}{shot.credit ? <> · {shot.creditHref ? link(shot.creditHref, shot.credit) : shot.credit}</> : null}
            {shot.version || shot.captured ? <span className="meta">{[shot.version, shot.captured ? message('captured', {
      date: shot.captured
    }) : null].filter(Boolean).join(' · ')}</span> : null}
          </Figcaption>
        </Figure>
      </>;
  };
  const renderLinkVisual = visual => <>
      <span className="screenshot-tag">{icon('external')} {visual.tag || labels.vendorGuideTag}</span>
      <div className="link-visual">
        {visual.title ? <strong>{visual.title}</strong> : null}
        {visual.text ? <p>{rich(visual.text)}</p> : null}
        {(visual.links || []).map((item, i) => <Anchor className="vendor-link" href={item.href} key={i} rel="noopener noreferrer" target="_blank">
            {icon(item.icon || 'book')}<span>{item.label}{item.detail ? <small>{item.detail}</small> : null}</span>{icon('external')}<span className="sr-only">{labels.opensInNewTab}</span>
          </Anchor>)}
      </div>
    </>;
  return <div className="kombify-client-guide not-prose" id={id} dir={direction} lang={locale} ref={rootRef} style={{
    '--kcg-steps': count
  }}>
      <div className="guide-content">
        <div className="page-top">
          <div className="intro-meta">{meta.map((item, i) => <span key={i}>{icon(item.icon)} {item.text}</span>)}</div>
          <div className="page-tools"><button aria-label={labels.printGuide} className="tool-button" onClick={() => window.print()} type="button">{icon('print')}</button></div>
        </div>
        {requirements.length ? <div className="ready-strip">
            <strong>{labels.requirementsTitle}</strong>
            {requirements.map((item, i) => <span key={i}>{icon('check')} {rich(item)}</span>)}
            {requirementsHelp ? <Anchor href={`#${requirementsHelp.target}`} onClick={() => reveal(requirementsHelp.target)}>{requirementsHelp.label} {icon('arrow')}</Anchor> : null}
          </div> : null}

        {apps.length ? <section aria-labelledby={`${id}-apps-title`} className="downloads" id={`${id}-apps`}>
            <div className="section-top">
              <h2 id={`${id}-apps-title`}>{appsTitle}</h2>
              {vendor ? link(vendor.href, vendor.label, 'small-link') : null}
            </div>
            <div className="downloads-grid">
              {apps.map((app, i) => <Anchor className="download-card" href={app.href} key={i} rel="noopener noreferrer" target="_blank">
                  <span className="platform-icon">{icon(app.platform)}</span>
                  <span className="download-text">
                    <strong>{app.label}</strong>
                    <small>{app.detail}</small>
                    <span className="store-name">{app.store} {icon('external')}</span>
                    <span className={`badge ${app.official ? 'official' : 'unofficial'}`}>{app.official ? message('officialApp', {
    vendor: app.vendor
  }) : message('thirdPartyApp', {
    vendor: app.vendor
  })}</span>
                  </span>
                  <span className="sr-only">{labels.opensInNewTab}</span>
                </Anchor>)}
            </div>
            {appsNote ? <p className="download-hint">{rich(appsNote)}</p> : null}
          </section> : null}

        <section aria-labelledby={`${id}-guide-title`} className="guide-section" id={`${id}-guide`}>
          <div className="guide-heading"><h2 id={`${id}-guide-title`}>{stepsTitle}</h2></div>
          <div className="wizard">
            <div aria-label={labels.setupSteps} className="step-tabs" onKeyDown={onTabKey} role="tablist">
              {steps.map((step, i) => <button aria-controls={`${id}-panel-${i}`} aria-selected={i === current} className={`step-tab${completed[i] ? ' done' : ''}`} id={`${id}-tab-${i}`} key={i} onClick={() => select(i)} role="tab" tabIndex={i === current ? 0 : -1} type="button">
                  <span className="step-dot">{completed[i] ? '✓' : number.format(i + 1)}</span><span>{step.tab}</span>
                </button>)}
            </div>
            {steps.map((step, i) => <Article aria-labelledby={`${id}-tab-${i}`} className={`guide-panel${i === current ? ' active' : ''}`} id={`${id}-panel-${i}`} key={i} role="tabpanel">
                <div className="panel-copy">
                  <div className="step-kicker">{message('stepPosition', {
    current: stepNumber.format(i + 1),
    count: stepNumber.format(count)
  })}</div>
                  <h3>{step.title}</h3>
                  {step.intro ? <p className="step-intro">{rich(step.intro)}</p> : null}
                  {step.clicks?.length ? <ol className="click-list">{step.clicks.map((click, j) => <li key={j}><span className="click-num">{number.format(j + 1)}</span><span>{rich(click)}</span></li>)}</ol> : null}
                  {step.verify?.length ? <div className="verify-list">
                      {step.verify.map((item, j) => <label key={j}><input checked={Boolean(verified[i]?.[j])} id={`${id}-verify-${i}-${j}`} onChange={event => onVerify(i, j, event.target.checked)} type="checkbox" /><span>{rich(item)}</span></label>)}
                    </div> : null}
                  {step.note ? <div className="micro-note">{icon('info')}<span>{rich(step.note)}</span></div> : null}
                  {(step.faq ? [].concat(step.faq) : []).map((faq, j) => <Details key={j}><Summary>{faq.q}</Summary><p>{rich(faq.a)}</p></Details>)}
                  {step.check ? <div className="step-check">{icon('ok')} {rich(step.check)}</div> : null}
                </div>
                <div className="panel-visual">
                  {step.screenshot ? renderShot(step.screenshot, i) : step.visual ? renderLinkVisual(step.visual) : null}
                </div>
              </Article>)}
            <div className="wizard-bottom">
              <div className="progress-note">
                <span>{message('progress', {
    done: number.format(completed.filter(Boolean).length),
    count: number.format(count)
  })}</span>
                <div aria-hidden="true" className="progress-meter">{steps.map((step, i) => <span className={completed[i] ? 'done' : undefined} key={i} />)}</div>
              </div>
              <button className="prev" disabled={current === 0} onClick={() => select(current - 1, true)} type="button">{icon('back')} {labels.back}</button>
              <button className="next" onClick={onNext} type="button"><span>{current === last ? labels.finish : labels.next}</span>{icon('arrow')}</button>
            </div>
          </div>
          {sources ? <p className="provenance-inline">{sources.summary} <Anchor href={`#${id}-sources`} onClick={() => reveal(`${id}-sources-detail`)}>{labels.imageSources}</Anchor></p> : null}
        </section>

        {next.length ? <section aria-labelledby={`${id}-more-title`} className="more-section" id={`${id}-more`}>
            <div className="section-top"><h2 id={`${id}-more-title`}>{nextTitle}</h2></div>
            <div className="more-grid">
              {next.map((item, i) => link(item.href, <>
                  <span className="more-icon">{icon(item.icon || 'arrow')}</span>
                  <span><strong>{item.title}</strong><small>{item.text}</small></span>
                </>, 'more-card', i))}
            </div>
          </section> : null}

        {help.length ? <section aria-labelledby={`${id}-help-title`} className="faq-section" id={`${id}-help`}>
            <h2 id={`${id}-help-title`}>{helpTitle}</h2>
            {help.map((item, i) => <Details className="faq" id={item.id} key={i}><Summary>{item.q}</Summary><div className="answer">{rich(item.a)}</div></Details>)}
          </section> : null}

        <Footer className="footline">
          <div className="attribution">{attribution ? rich(attribution) : null}</div>
          <button className="reset" onClick={onReset} type="button">{labels.resetProgress}</button>
        </Footer>

        {sources ? <section className="source-section" id={`${id}-sources`}>
            <Details id={`${id}-sources-detail`}>
              <Summary>{labels.sourcesTitle}</Summary>
              <div className="source-content">
                {(sources.paragraphs || []).map((text, i) => <p key={i}>{rich(text)}</p>)}
                <div className="source-links">{(sources.links || []).map((item, i) => link(item.href, item.label, undefined, i))}</div>
              </div>
            </Details>
            <div className="dim">{labels.ownershipNotice}</div>
          </section> : null}
      </div>

      <Dialog aria-labelledby={`${id}-zoom-title`} className="zoom-dialog" onClick={closeOnBackdrop} ref={zoomRef}>
        <div className="dialog-header">
          <h2 id={`${id}-zoom-title`}>{zoom?.vendor ? labels.vendorScreenshot : labels.screenshot}</h2>
          <button aria-label={labels.closeImage} className="dialog-close" onClick={() => zoomRef.current?.close()} type="button">{icon('close')}</button>
        </div>
        <div className="zoom-content">
          {zoom ? <Img alt={zoom.alt} src={zoom.src} /> : null}
          {zoom?.creditHref ? link(zoom.creditHref, zoom.vendor ? labels.openOriginal : labels.aboutScreenshot) : null}
        </div>
      </Dialog>
      <div aria-live="polite" className="sr-only" role="status">{toast}</div>
      <div aria-hidden="true" className={`toast${toast ? ' show' : ''}`}>{toast}</div>
    </div>;
};

export const clientSetupLabelsEs = {
  appsTitle: 'Primero, descarga la app.',
  stepsTitle: 'Conéctate paso a paso.',
  helpTitle: '¿Te has atascado?',
  nextTitle: 'Qué viene después.',
  opensInNewTab: ' (se abre en una pestaña nueva)',
  verifyRequired: 'Marca cada punto después de comprobarlo por tu cuenta.',
  allDone: 'Todos los pasos están marcados como completados en este navegador.',
  remainingSteps: '{done} de {count} pasos están marcados como completados. Abre las otras pestañas para terminarlos.',
  progressReset: 'Tu progreso en este navegador se ha restablecido.',
  vendorScreenshotTag: 'CAPTURA DEL FABRICANTE',
  kombifyScreenshotTag: 'CAPTURA DE KOMBIFY',
  openVendorPage: 'Abrir la página del fabricante',
  openScreenshot: 'Abrir la captura',
  vendorImageUnavailable: 'Para cargarla se necesita conexión a internet.',
  imageUnavailable: 'La imagen no se ha cargado.',
  enlargeScreenshot: 'Ampliar captura: {description}',
  captured: 'capturada el {date}',
  vendorGuideTag: 'GUÍA DEL FABRICANTE',
  printGuide: 'Imprimir la guía',
  requirementsTitle: 'Lo que necesitas',
  officialApp: 'Oficial · {vendor}',
  thirdPartyApp: 'De terceros · {vendor}',
  setupSteps: 'Pasos de configuración',
  stepPosition: 'PASO {current} / {count}',
  progress: '{done} de {count} pasos completados',
  back: 'Atrás',
  finish: 'Terminar',
  next: 'Hecho · siguiente',
  imageSources: 'Fuentes de las imágenes y fecha',
  resetProgress: 'Restablecer progreso',
  sourcesTitle: 'Fuentes de las imágenes, enlaces comprobados y fecha',
  ownershipNotice: 'Guía de kombify · Los nombres de producto, las capturas de las apps de los fabricantes y las marcas pertenecen a sus respectivos propietarios.',
  vendorScreenshot: 'Captura del fabricante',
  screenshot: 'Captura',
  closeImage: 'Cerrar imagen',
  openOriginal: 'Abrir el original del fabricante',
  aboutScreenshot: 'Sobre esta captura'
};

<ClientSetupGuide
  labels={clientSetupLabelsEs}
  locale="es"
  id="kcg-passwords"
  meta={[
{ icon: "phone", text: "Teléfono, navegador y ordenador" },
{ icon: "steps", text: "6 pasos cortos" },
{ icon: "book", text: "No hacen falta conocimientos previos" },
]}
  requirements={[
"La dirección de tu bóveda, por ejemplo https://vault.home.test",
"Una invitación para tu dirección de correo electrónico",
"Cada dispositivo preparado para tu bóveda doméstica por la persona que configuró kombify",
"Una contraseña maestra que puedas recordar",
]}
  requirementsHelp={{ label: "¿Aún no tienes invitación?", target: "kcg-passwords-no-invite" }}
  appsTitle="Primero, descarga la app de Bitwarden."
  vendor={{ label: "Todas las descargas de Bitwarden", href: "https://bitwarden.com/download/" }}
  apps={[
{ platform: "apple", label: "iPhone y iPad", detail: "Bitwarden Password Manager", store: "Abrir en la App Store", href: "https://apps.apple.com/app/bitwarden-password-manager/id1137397744", vendor: "Bitwarden", official: true },
{ platform: "android", label: "Android", detail: "Bitwarden Password Manager", store: "Abrir en Google Play", href: "https://play.google.com/store/apps/details?id=com.x8bit.bitwarden", vendor: "Bitwarden", official: true },
{ platform: "browser", label: "Extensión de navegador", detail: "Chrome, Firefox, Edge, Safari y más", store: "Abrir la página de descargas de Bitwarden", href: "https://bitwarden.com/download/", vendor: "Bitwarden", official: true },
{ platform: "desktop", label: "Ordenador", detail: "Windows, macOS y Linux", store: "Abrir la página de descargas de Bitwarden", href: "https://bitwarden.com/download/", vendor: "Bitwarden", official: true },
]}
  appsNote="Tiendas de navegadores: [Chrome](https://chromewebstore.google.com/detail/bitwarden-password-manager/nngceckbapebfimnlniiiahkandclblb) · [Firefox](https://addons.mozilla.org/firefox/addon/bitwarden-password-manager/) · [Edge](https://microsoftedge.microsoft.com/addons/detail/bitwarden-password-manager/jbkfoedolllekgbhcbcoahefnbanhhlh) · [Safari (Mac App Store)](https://apps.apple.com/app/bitwarden/id1352778147). Tu bóveda funciona con Vaultwarden, que es compatible con estas apps oficiales de Bitwarden. No necesitas una app de Vaultwarden aparte."
  stepsTitle="Configura tu bóveda paso a paso."
  steps={[
{
  tab: "Abrir la bóveda",
  title: "Abre tu bóveda en un navegador.",
  intro: "Empieza en un ordenador. Tu bóveda tiene su propia dirección web, como un sitio web que solo usa tu hogar.",
  clicks: [
    "Escribe la dirección de tu bóveda en la barra de direcciones del navegador, por ejemplo **https://vault.home.test**, y pulsa Intro.",
    "Debajo del cuadro de inicio de sesión, haz clic en [[Create account]].",
  ],
  note: "La dirección de esta guía es un ejemplo. Usa la dirección que te dio la persona que configuró kombify.",
  faq: { q: "¿Qué es la dirección de una bóveda?", a: "Es la dirección web de la bóveda de contraseñas de tu hogar. Suele empezar por **https://vault.** y terminar con el nombre de tu red doméstica. La persona que configuró kombify puede darte la dirección exacta." },
  check: "Ves el formulario [[Create account]].",
  screenshot: { src: "/images/guides/passwords/vaultwarden/web-01-open-vault.webp", width: 1088, height: 830, alt: "Página de inicio de sesión de Vaultwarden con el enlace Create account marcado", caption: "Página de inicio de sesión de la bóveda", credit: "Captura de kombify", creditHref: "#kcg-passwords-sources", version: "Vaultwarden 1.37.2 · bóveda web 2026.7.0", captured: "2026-09-24", annotations: [{ n: 2, top: 84.3, left: 38.3, width: 23.5, height: 7.2 }] },
},
{
  tab: "Crear cuenta",
  title: "Crea tu cuenta.",
  intro: "Tu bóveda solo acepta direcciones de correo electrónico invitadas. Usa la misma dirección a la que llegó la invitación.",
  clicks: [
    "En [[Email address]], introduce tu dirección de correo electrónico invitada.",
    "En [[Name]], introduce tu nombre. La persona que gestiona la bóveda y aquellos con quienes compartas contraseñas pueden verlo.",
    "Haz clic en [[Continue]].",
  ],
  faq: { q: "¿Es esta mi cuenta de kombify?", a: "No. Esta cuenta pertenece solo a tu bóveda de contraseñas. Tiene su propia dirección de correo electrónico y su propia contraseña maestra." },
  check: "La página te pide ahora [[Set a strong password]].",
  screenshot: { src: "/images/guides/passwords/vaultwarden/web-02-create-account.webp", width: 1088, height: 718, alt: "Formulario Create account con la dirección de correo alex@example.com y el nombre Alex Example", caption: "Crear cuenta", credit: "Captura de kombify", creditHref: "#kcg-passwords-sources", version: "Vaultwarden 1.37.2 · bóveda web 2026.7.0", captured: "2026-09-24", annotations: [{ n: 1, top: 37.6, left: 16.4, width: 65.1, height: 13.4 }, { n: 2, top: 61, left: 16.4, width: 65.1, height: 13.4 }, { n: 3, top: 76.3, left: 14, width: 72.1, height: 13.9 }] },
},
{
  tab: "Contraseña maestra",
  title: "Elige tu contraseña maestra.",
  intro: "La contraseña maestra es la única contraseña que aún tienes que recordar. Desbloquea todas las demás.",
  clicks: [
    "En [[Master password]], introduce una contraseña larga. Una frase corta de cuatro palabras o más funciona bien.",
    "Introduce la misma contraseña otra vez en [[Confirm master password]].",
    "Haz clic en [[Create account]]. Si la página sugiere la extensión de navegador, haz clic en [[Add it later]] y después en [[Skip to web app]]. Conectarás la extensión en el paso 5.",
  ],
  note: "**Nadie puede recuperar una contraseña maestra olvidada**, ni siquiera la persona que gestiona tu bóveda. Anótala y guarda la nota en un lugar seguro fuera de la bóveda.",
  faq: { q: "¿Qué significa la barra de seguridad?", a: "Estima lo difícil que es adivinar tu contraseña. Apunta a **Strong**. Es mejor una contraseña larga que una complicada." },
  check: "Tu bóveda vacía se abre con el título [[All vaults]].",
  screenshot: { src: "/images/guides/passwords/vaultwarden/web-03-master-password.webp", width: 1088, height: 1310, alt: "Página Set a strong password con la contraseña maestra introducida dos veces y valorada como Strong", caption: "Set a strong password", credit: "Captura de kombify", creditHref: "#kcg-passwords-sources", version: "Vaultwarden 1.37.2 · bóveda web 2026.7.0", captured: "2026-09-24", annotations: [{ n: 1, top: 25.5, left: 16.4, width: 60.7, height: 7.3 }, { n: 2, top: 52.2, left: 16.4, width: 60.7, height: 7.3 }, { n: 3, top: 87, left: 14, width: 72.1, height: 7.6 }] },
},
{
  tab: "Primera contraseña",
  title: "Guarda tu primera contraseña.",
  intro: "Guarda un inicio de sesión de prueba. Más adelante demuestra que tus apps están conectadas a tu bóveda.",
  clicks: [
    "Arriba a la derecha, haz clic en [[New]] y elige [[Login]]. Si primero aparece una ventana de bienvenida, haz clic en [[Skip]].",
    "En [[Item name]], introduce un nombre que reconozcas, por ejemplo el nombre de una tienda.",
    "Introduce tu [[Username]]. Junto a [[Password]], haz clic en las flechas circulares para generar una contraseña y después en [[Use this password]].",
    "Haz clic en [[Save]].",
  ],
  note: "Añade la dirección web de la tienda en [[Website (URI)]]. Entonces la extensión de navegador sugiere este inicio de sesión en ese sitio web.",
  check: "Aparece el mensaje [[Item added]] y tu inicio de sesión se muestra en la lista.",
  screenshot: { src: "/images/guides/passwords/vaultwarden/web-04-new-login.webp", width: 1472, height: 1664, alt: "Ventana New Login con el nombre de elemento Example Shop, un nombre de usuario y una contraseña generada", caption: "New Login", credit: "Captura de kombify", creditHref: "#kcg-passwords-sources", version: "Vaultwarden 1.37.2 · bóveda web 2026.7.0", captured: "2026-09-24", annotations: [{ n: 2, top: 12.1, left: 7.9, width: 82.6, height: 5.8 }, { n: 3, top: 41.8, left: 7.9, width: 79.3, height: 5.8 }, { n: 3, top: 52.3, left: 87.5, width: 5.7, height: 5 }, { n: 4, top: 92.1, left: 2.7, width: 10.4, height: 6 }] },
},
{
  tab: "Conectar apps",
  title: "Conecta las apps de Bitwarden.",
  intro: "Cada app de Bitwarden se conecta primero a los servidores del propio Bitwarden. Indícale una vez que use tu bóveda en su lugar.",
  clicks: [
    "Instala la app de Bitwarden de la lista de arriba y ábrela.",
    "En la pantalla de inicio de sesión, abre el menú [[Logging in on]] y elige [[Self-hosted]]. En la app de ordenador, el menú se llama [[Accessing]].",
    "En [[Server URL]], introduce la dirección de tu bóveda con **https://** al principio y pulsa [[Save]].",
    "Inicia sesión con tu dirección de correo electrónico y tu contraseña maestra.",
  ],
  note: "Repite estos pasos en cada teléfono, navegador y ordenador que uses. Cada uno necesita la dirección de tu bóveda una vez.",
  faq: { q: "¿Por qué aquí no hay ninguna imagen de la app?", a: "Las condiciones de Bitwarden no dan permiso para reutilizar sus imágenes. Su propia guía muestra cada pantalla, y las etiquetas de arriba coinciden con ella." },
  check: "La app muestra el inicio de sesión que guardaste en el paso 4.",
  visual: {
    tag: "GUÍA DE BITWARDEN",
    title: "Bitwarden muestra cada pantalla",
    text: "Elige tu dispositivo en la guía de Bitwarden. Busca la sección sobre un servidor **autoalojado**.",
    links: [
      { label: "Conectar una app a tu propio servidor", detail: "bitwarden.com · todos los dispositivos", href: "https://bitwarden.com/help/change-client-environment/", icon: "server" },
      { label: "Primeros pasos con la extensión de navegador", detail: "bitwarden.com", href: "https://bitwarden.com/help/getting-started-browserext/", icon: "browser" },
      { label: "Primeros pasos con la app móvil", detail: "bitwarden.com", href: "https://bitwarden.com/help/getting-started-mobile/", icon: "phone" },
    ],
  },
},
{
  tab: "Comprobación",
  title: "¿Está todo conectado?",
  intro: "Compruébalo en la bóveda web y en una app. Marca solo lo que hayas comprobado por tu cuenta.",
  verify: [
    "Puedo abrir la dirección de mi bóveda e iniciar sesión.",
    "El inicio de sesión que guardé aparece en una app de Bitwarden en mi teléfono, navegador u ordenador.",
    "Mi contraseña maestra está anotada y guardada fuera de la bóveda.",
  ],
  note: "Esta guía no lee tu bóveda. Tus marcas permanecen en este navegador.",
  check: "Ahora puedes trasladar tus otras contraseñas a la bóveda.",
  screenshot: { src: "/images/guides/passwords/vaultwarden/web-05-first-item.webp", width: 1430, height: 300, alt: "Lista All vaults que muestra el inicio de sesión guardado de Example Shop", caption: "Tu primer inicio de sesión en la bóveda", credit: "Captura de kombify", creditHref: "#kcg-passwords-sources", version: "Vaultwarden 1.37.2 · bóveda web 2026.7.0", captured: "2026-09-24", annotations: [{ n: null, top: 37.2, left: 1, width: 98, height: 58.1 }] },
},
]}
  nextTitle="Qué viene después."
  next={[
{ icon: "shield", title: "Activa el inicio de sesión en dos pasos", text: "Bitwarden explica cómo añadir un segundo paso al inicio de sesión.", href: "https://bitwarden.com/help/setup-two-step-login/" },
{ icon: "server", title: "Haz copia de seguridad de la bóveda", text: "Para la persona que gestiona kombify: protege los datos de la bóveda.", href: "/es/guides/stackkits/back-up-and-restore" },
]}
  help={[
{ id: "kcg-passwords-no-invite", q: "Todavía no tengo la dirección de una bóveda ni una invitación.", a: "Pregunta a la persona que configuró kombify en tu hogar. Ella añade el servicio Passwords e invita a tu dirección de correo electrónico. [Cómo funciona Passwords con kombify](/es/guides/stackkits/use-cases/passwords) · [Configuración del propietario](/es/stackkits/apps/vaultwarden)." },
{ q: "Create account dice «Registration not allowed or user already exists».", a: "Comprueba que usas exactamente la dirección de correo electrónico invitada. Si ya creaste tu cuenta, vuelve atrás e inicia sesión. En caso contrario, pide una nueva invitación." },
{ q: "La dirección no se abre o el navegador avisa sobre la conexión.", a: "Prueba primero la dirección en un dispositivo de tu red doméstica. Si tu bóveda solo funciona en casa, conéctate a tu Wi-Fi doméstico o a la app de conexión doméstica que te dieron. Además, cada dispositivo debe estar configurado para confiar en tu servidor doméstico; la persona que configuró kombify se encarga de ello. Si el navegador dice que la conexión no es privada, detente ahí y pregúntale. No ignores la advertencia. [Sobre las direcciones domésticas](/es/guides/stackkits/use-cases/network)." },
{ q: "He olvidado mi contraseña maestra.", a: "Nadie puede recuperarla. Tu bóveda está cifrada con ella. La persona que gestiona tu bóveda puede eliminar tu cuenta para que empieces de nuevo, pero las contraseñas guardadas en ella se pierden. [Bitwarden sobre las contraseñas maestras](https://bitwarden.com/help/master-password/)." },
{ q: "Mi teléfono Android no tiene Google Play.", a: "Bitwarden mantiene su propio repositorio de F-Droid. La dirección figura en la [página de descargas de Bitwarden](https://bitwarden.com/download/)." },
{ q: "¿Por qué dice Vaultwarden y Bitwarden?", a: "Vaultwarden es el programa que ejecuta tu bóveda. Es un servidor no oficial que funciona con las apps oficiales de Bitwarden. Vaultwarden no está asociado con Bitwarden, Inc. Las imágenes muestran Vaultwarden 1.37.2 en inglés; las versiones más recientes y otros idiomas pueden verse distintos." },
]}
  attribution="**Vaultwarden ejecuta tu bóveda y las apps de Bitwarden la abren. kombify lo instala por ti.** kombify no fabrica ninguna de las dos apps. [Vaultwarden](https://github.com/dani-garcia/vaultwarden) (AGPL-3.0) · bóveda web: [cliente web de Bitwarden compilado para Vaultwarden](https://github.com/dani-garcia/bw_web_builds) (GPL-3.0) · [Bitwarden](https://bitwarden.com/)"
  sources={{
summary: "Capturas de kombify de una instalación local de Vaultwarden · cuenta ficticia · menús en inglés.",
paragraphs: [
  "**Capturas · 24 de septiembre de 2026.** kombify tomó las imágenes de la bóveda web de una instalación de referencia local: Vaultwarden 1.37.2, la imagen que el catálogo de StackKits entrega para Passwords, con su bóveda web incluida 2026.7.0 (el cliente web de Bitwarden, GPL-3.0). La instalación usó la dirección de ejemplo https://vault.home.test y la cuenta ficticia alex@example.com. Es una instalación de referencia, no una prueba de despliegue de StackKits. Esta guía añadió los marcadores violeta.",
  "Las pantallas de las apps de Bitwarden no se muestran. Las [condiciones de Bitwarden](https://bitwarden.com/terms/) no dan permiso para reutilizar su contenido, así que el paso 5 enlaza a la propia guía de Bitwarden.",
  "Los enlaces de las tiendas y las páginas de ayuda se comprobaron el 24 de septiembre de 2026. Tu progreso permanece en este navegador. La guía no conecta ninguna cuenta ni pide contraseñas.",
],
links: [
  { label: "Bitwarden: conectar aplicaciones individuales", href: "https://bitwarden.com/help/change-client-environment/" },
  { label: "Descargas de Bitwarden", href: "https://bitwarden.com/download/" },
  { label: "Proyecto Vaultwarden", href: "https://github.com/dani-garcia/vaultwarden" },
],
}}
/>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.