Configurador de plan modular
Plan modular con casillas reales, desglose de extras y suma mensual; devuelve la selección y el total mediante callback.
- Plan base incluido
- Espacio de equipo
Puedes cambiar la selección antes de continuar.
Código completo
Es exactamente el archivo que se descarga. Cópialo a tu proyecto y úsalo.
"use client";
import { useState, type CSSProperties } from "react";
/** Configurador de plan modular. React + TypeScript, sin dependencias. Personaliza con variables --gczo-*. */
export type PlanModule = { id: string; label: string; price: number };
export type PreciosConfiguradorProps = {
basePrice?: number;
modules?: PlanModule[];
currency?: string;
onChoose?: (ids: string[], total: number) => void;
className?: string;
style?: CSSProperties;
};
const CSS = `
.gczo-root { box-sizing: border-box; min-width: 0; max-width: 100%; color: var(--gczo-fg, #202421); font: 400 15px/1.5 ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }
.gczo-root *, .gczo-root *::before, .gczo-root *::after { box-sizing: border-box; }
.gczo-root button, .gczo-root input, .gczo-root textarea, .gczo-root select { font: inherit; }
.gczo-root button, .gczo-root a { -webkit-tap-highlight-color: transparent; }
.gczo-root :focus-visible { outline: 2px solid var(--gczo-accent, #25724b); outline-offset: 4px; }
.gczo-root ::selection { background: var(--gczo-accent, #25724b); color: #fff; }
.gczo-root button:disabled { opacity: .5; cursor: not-allowed; }
.gczo-root svg { flex-shrink: 0; }
@media (prefers-reduced-motion: reduce) { .gczo-root *, .gczo-root *::before, .gczo-root *::after { animation: none !important; transition-duration: .01ms !important; scroll-behavior: auto !important; } }
.gczo-root { display:grid; grid-template-columns:1.2fr 1fr; width:100%; background:var(--gczo-bg,#ecf0f4); color:#263343; padding:44px clamp(24px,5vw,60px); gap:40px; }
.gczo-options h2 { font-size:42px; font-weight:600; line-height:1.05; letter-spacing:-.03em; margin:0 0 20px; }
.gczo-options>p { font-size:14px; color:#526174; margin:0 0 24px; }
.gczo-base,.gczo-options label { display:flex; align-items:center; gap:12px; padding:17px 0; border-bottom:1px solid #c6cfdb; font-size:13px; }
.gczo-base strong,.gczo-price { margin-left:auto; font-variant-numeric:tabular-nums; }
.gczo-options input { width:20px; height:20px; accent-color:var(--gczo-accent,#3c587b); cursor:pointer; }
.gczo-options label { min-height:54px; cursor:pointer; }
.gczo-summary { padding:28px; border-radius:12px; background:#fff; display:flex; flex-direction:column; align-items:flex-start; }
.gczo-summary>span { font-size:12px; color:#526174; }
.gczo-summary>strong { font-size:52px; line-height:1.2; letter-spacing:-.03em; font-weight:600; margin-top:12px; font-variant-numeric:tabular-nums; }
.gczo-summary small { font-size:11px; color:#526174; }
.gczo-summary ul { font-size:13px; padding-left:18px; margin:28px 0; }
.gczo-summary li { margin:8px 0; }
.gczo-summary button { width:100%; margin-top:auto; min-height:48px; border:0; border-radius:5px; background:#3c587b; color:#fff; padding:12px; cursor:pointer; }
.gczo-summary button:hover { background:#293f5b; }
.gczo-summary p { font-size:11px; color:#526174; margin:16px 0 0; }
@media(max-width:620px) { .gczo-root { grid-template-columns:1fr; gap:28px; } }`;
export function PreciosConfigurador({
basePrice = 19,
modules = [
{ id: "analytics", label: "Analítica avanzada", price: 9 },
{ id: "team", label: "Espacio de equipo", price: 15 },
{ id: "support", label: "Soporte prioritario", price: 12 },
],
currency = "USD",
onChoose,
className = "",
style,
}: PreciosConfiguradorProps) {
const [selected, setSelected] = useState<string[]>(["team"]);
const [chosen, setChosen] = useState(false);
const total =
basePrice +
modules
.filter((m) => selected.includes(m.id))
.reduce((sum, m) => sum + m.price, 0);
const fmt = new Intl.NumberFormat("es-MX", {
style: "currency",
currency,
maximumFractionDigits: 0,
});
return (
<section className={`gczo-root ${className}`} style={style}>
<style>{CSS}</style>
<div className="gczo-options">
<h2>
Tu plan.
<br />A tu medida.
</h2>
<p>Parte de lo esencial y añade lo que necesitas.</p>
<div className="gczo-base">
<span>Plan base</span>
<strong>{fmt.format(basePrice)}</strong>
</div>
{modules.map((module) => (
<label key={module.id}>
<span>{module.label}</span>
<span className="gczo-price">+{fmt.format(module.price)}</span>
<input
type="checkbox"
checked={selected.includes(module.id)}
onChange={(e) => {
setSelected(
e.target.checked
? [...selected, module.id]
: selected.filter((id) => id !== module.id),
);
setChosen(false);
}}
/>
</label>
))}
</div>
<div className="gczo-summary">
<span>Total mensual</span>
<strong>{fmt.format(total)}</strong>
<small>{currency} · precios de muestra</small>
<ul>
<li>Plan base incluido</li>
{modules
.filter((m) => selected.includes(m.id))
.map((m) => (
<li key={m.id}>{m.label}</li>
))}
</ul>
<button
type="button"
onClick={() => {
onChoose?.(
selected.filter((id) => modules.some((m) => m.id === id)),
total,
);
setChosen(true);
}}
>
Elegir configuración
</button>
<p role="status">
{chosen
? "Configuración seleccionada. No se realizó ningún cobro."
: "Puedes cambiar la selección antes de continuar."}
</p>
</div>
</section>
);
}
Úsalo en tu proyecto
Instálalo con la CLI de shadcn.
npx shadcn@latest add https://www.gassierconsulting.com/r/precios-configuradorEjemplo de uso de Configurador de plan modular
Después de copiar precios-configurador.tsx a tu proyecto, impórtalo y colócalo donde lo necesites.
import { PreciosConfigurador } from "./precios-configurador";
export default function Ejemplo() {
return <PreciosConfigurador currency="Ejemplo" />;
}Propiedades de PreciosConfigurador
PreciosConfigurador acepta 6 propiedades: todas son opcionales y tienen un valor por defecto, así que funciona sin configurar nada.
| Propiedad | Tipo | Descripción |
|---|---|---|
basePrice? | number | Opcional. |
modules? | PlanModule[] | Opcional. |
currency? | string | Opcional. |
onChoose? | (ids: string[], total: number) => void | Opcional. |
className? | string | Opcional. |
style? | CSSProperties | Opcional. |
Personaliza Configurador de plan modular 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.
| Variable | Valor por defecto |
|---|---|
--gczo-fg | #202421 |
--gczo-accent | #25724b |
--gczo-bg | #ecf0f4 |
Accesibilidad y rendimiento
El archivo tiene 133 líneas (6 KB) y no necesita @keyframes. Esto es lo que hace Configurador de plan modular 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: status.
- Es un componente de cliente de Next.js: incluye la directiva "use client".