Ir al contenido
Evaluar mi proyecto
Componentes · PreciosDisponible

Precios por paquetes de créditos

Selector de paquetes con coste unitario y total actualizado; callback de elección sin realizar cobros ni simular compras.

Elige cuánto quieres crear.

Paquetes de muestra. Importes ilustrativos en USD.

USD 45.00USD 0.09 por crédito

Pago único · sin renovación automática en este ejemplo

Código completo

Es exactamente el archivo que se descarga. Cópialo a tu proyecto y úsalo.

precios-creditos.tsx120 líneas
Descargar
"use client";

import { useState, type CSSProperties } from "react";

/** Precios por paquetes de créditos. React + TypeScript, sin dependencias. Personaliza con variables --gczp-*. */
export type CreditPack = { credits: number; price: number; label: string };
export type PreciosCreditosProps = {
  packs?: CreditPack[];
  currency?: string;
  onChoose?: (pack: CreditPack) => void;
  className?: string;
  style?: CSSProperties;
};

const CSS = `
.gczp-root { box-sizing: border-box; min-width: 0; max-width: 100%; color: var(--gczp-fg, #202421); font: 400 15px/1.5 ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }
.gczp-root *, .gczp-root *::before, .gczp-root *::after { box-sizing: border-box; }
.gczp-root button, .gczp-root input, .gczp-root textarea, .gczp-root select { font: inherit; }
.gczp-root button, .gczp-root a { -webkit-tap-highlight-color: transparent; }
.gczp-root :focus-visible { outline: 2px solid var(--gczp-accent, #25724b); outline-offset: 4px; }
.gczp-root ::selection { background: var(--gczp-accent, #25724b); color: #fff; }
.gczp-root button:disabled { opacity: .5; cursor: not-allowed; }
.gczp-root svg { flex-shrink: 0; }
@media (prefers-reduced-motion: reduce) { .gczp-root *, .gczp-root *::before, .gczp-root *::after { animation: none !important; transition-duration: .01ms !important; scroll-behavior: auto !important; } }
.gczp-root { width:100%; padding:44px clamp(24px,5vw,64px); background:var(--gczp-bg,#f4f0e9); color:#343c30; }
.gczp-root h2 { font-size:clamp(30px,4vw,44px); line-height:1.1; font-weight:600; letter-spacing:-.025em; margin:0 0 16px; }
.gczp-intro { font-size:13px; color:#655f51; margin:0 0 30px; }
.gczp-options { display:flex; gap:12px; flex-wrap:wrap; }
.gczp-options button { flex:1; min-width:130px; text-align:left; padding:22px; border:1px solid #ccc8bc; border-radius:12px; color:#45533a; background:#fcfaf5; cursor:pointer; transition:background .2s,color .2s; }
.gczp-options button[aria-pressed="true"] { background:var(--gczp-accent,#334b31); color:#f5f3e8; border-color:transparent; }
.gczp-options span { display:block; font-size:12px; margin-bottom:22px; }
.gczp-options strong { display:block; font-size:36px; line-height:1; font-weight:600; font-variant-numeric:tabular-nums; }
.gczp-options small { display:block; margin-top:8px; font-size:12px; }
.gczp-total { display:flex; align-items:center; gap:24px; justify-content:space-between; margin:32px 0 0; flex-wrap:wrap; }
.gczp-total strong { display:block; font-size:34px; font-weight:600; letter-spacing:-.02em; font-variant-numeric:tabular-nums; }
.gczp-total span { display:block; color:#655f51; font-size:12px; }
.gczp-total button { border:0; border-radius:5px; background:#334b31; color:#fff; padding:12px 24px; min-height:48px; cursor:pointer; }
.gczp-total button:hover { background:#223820; }
.gczp-status { font-size:11px; color:#655f51; margin:24px 0 0; }`;

export function PreciosCreditos({
  packs = [
    { credits: 100, price: 12, label: "Para probar" },
    { credits: 500, price: 45, label: "Para crear" },
    { credits: 2000, price: 140, label: "Para equipos" },
  ],
  currency = "USD",
  onChoose,
  className = "",
  style,
}: PreciosCreditosProps) {
  const [selected, setSelected] = useState(1);
  const [confirmed, setConfirmed] = useState(false);
  const index = Math.min(selected, Math.max(0, packs.length - 1));
  const pack = packs[index];
  const fmt = new Intl.NumberFormat("es-MX", {
    style: "currency",
    currency,
    maximumFractionDigits: 2,
  });
  return (
    <section className={`gczp-root ${className}`} style={style}>
      <style>{CSS}</style>
      <h2>Elige cuánto quieres crear.</h2>
      <p className="gczp-intro">
        Paquetes de muestra. Importes ilustrativos en {currency}.
      </p>
      {packs.length ? (
        <>
          <div
            className="gczp-options"
            role="group"
            aria-label="Paquetes de créditos"
          >
            {packs.map((p, i) => (
              <button
                type="button"
                key={i}
                aria-pressed={index === i}
                onClick={() => {
                  setSelected(i);
                  setConfirmed(false);
                }}
              >
                <span>{p.label}</span>
                <strong>{p.credits.toLocaleString("es-MX")}</strong>
                <small>créditos</small>
              </button>
            ))}
          </div>
          <div className="gczp-total">
            <div>
              <strong>{fmt.format(pack.price)}</strong>
              <span>
                {fmt.format(pack.price / Math.max(1, pack.credits))} por crédito
              </span>
            </div>
            <button
              type="button"
              onClick={() => {
                onChoose?.(pack);
                setConfirmed(true);
              }}
            >
              Elegir paquete
            </button>
          </div>
          <p role="status" className="gczp-status">
            {confirmed
              ? `Paquete de ${pack.credits} créditos seleccionado. No se realizó ningún cobro.`
              : "Pago único · sin renovación automática en este ejemplo"}
          </p>
        </>
      ) : (
        <p>No hay paquetes disponibles.</p>
      )}
    </section>
  );
}

Úsalo en tu proyecto

Instálalo con la CLI de shadcn.

terminal.sh1 líneas
npx shadcn@latest add https://www.gassierconsulting.com/r/precios-creditos

Ejemplo de uso de Precios por paquetes de créditos

Después de copiar precios-creditos.tsx a tu proyecto, impórtalo y colócalo donde lo necesites.

ejemplo.tsx5 líneas
import { PreciosCreditos } from "./precios-creditos";

export default function Ejemplo() {
  return <PreciosCreditos currency="Ejemplo" />;
}

Propiedades de PreciosCreditos

PreciosCreditos acepta 5 propiedades: todas son opcionales y tienen un valor por defecto, así que funciona sin configurar nada.

PropiedadTipoDescripción
packs?CreditPack[]Opcional.
currency?stringOpcional.
onChoose?(pack: CreditPack) => voidOpcional.
className?stringOpcional.
style?CSSPropertiesOpcional.

Personaliza Precios por paquetes de créditos con variables CSS

Define estas variables en el contenedor o en tu hoja de estilos para cambiar los colores sin tocar el componente. Si no las defines, se usa el valor por defecto.

VariableValor por defecto
--gczp-fg#202421
--gczp-accent#25724b
--gczp-bg#f4f0e9

Accesibilidad y rendimiento

El archivo tiene 120 líneas (5 KB) y no necesita @keyframes. Esto es lo que hace Precios por paquetes de créditos por quien lo usa:

  • Detecta prefers-reduced-motion y baja o quita el movimiento cuando la persona lo pide.
  • Muestra un anillo de foco visible al navegar con teclado (:focus-visible).
  • Declara roles ARIA para lectores de pantalla: group, status.
  • Es un componente de cliente de Next.js: incluye la directiva "use client".
Licencia
MIT
Dependencias
React >=18
Compatibilidad
React 18 y 19 · Next.js (componente de cliente)