sábado, 28 de febrero de 2026

Glifo de Checkbox para estado=indeterminate

Quiero que el glyph para el estado indeterminate de un Checkbox de shadcn/ui con Radix UI sea una raya horizontal.


Para lograr que el checkbox de shadcn/ui (que utiliza Radix UI) muestre una raya horizontal en el estado indeterminate, debemos modificar el componente para que detecte dicho estado y renderice un icono diferente al de verificación (Check).

He preparado una versión completa y funcional del componente Checkbox junto con un ejemplo de uso para que puedas probarlo directamente.

Checkbox de Shadcn con Estado Indeterminado
Feb 28, 1:45 PM

import React, { useState, useEffect } from "react"
import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
import { Check, Minus } from "lucide-react"
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"

// Utilidad para manejar clases de Tailwind
function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}

/**
* Componente Checkbox modificado para soportar el glifo de raya horizontal
* en estado 'indeterminate'.
*/
const Checkbox = React.forwardRef(({ className, checked, ...props }, ref) => (
<CheckboxPrimitive.Root
ref={ref}
className={cn(
"peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=indeterminate]:bg-primary data-[state=checked]:text-primary-foreground data-[state=indeterminate]:text-primary-foreground",
className
)}
checked={checked}
{...props}
>
<CheckboxPrimitive.Indicator
className={cn("flex items-center justify-center text-current")}
>
{checked === "indeterminate" ? (
<Minus className="h-3.5 w-3.5 stroke-[3]" />
) : (
<Check className="h-3.5 w-3.5 stroke-[3]" />
)}
</CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root>
))
Checkbox.displayName = CheckboxPrimitive.Root.displayName

// --- Ejemplo de Implementación ---

const App = () => {
// El estado puede ser true, false o "indeterminate"
const [status, setStatus] = useState("indeterminate")

const toggleStatus = () => {
if (status === true) setStatus(false)
else if (status === false) setStatus("indeterminate")
else setStatus(true)
}

return (
<div className="flex flex-col items-center justify-center min-h-screen bg-slate-950 text-white p-6">
<div className="bg-slate-900 p-10 rounded-xl border border-slate-800 shadow-xl space-y-6 w-full max-w-md">
<h2 className="text-xl font-semibold text-center mb-4">Estado del Checkbox</h2>
<div className="flex items-center space-x-3 p-4 bg-slate-800/50 rounded-lg border border-slate-700">
<Checkbox
id="terms"
checked={status}
onCheckedChange={(val) => setStatus(val)}
/>
<label
htmlFor="terms"
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 cursor-pointer"
>
Aceptar términos y condiciones
</label>
</div>

<div className="grid grid-cols-1 gap-2">
<button
onClick={toggleStatus}
className="px-4 py-2 bg-blue-600 hover:bg-blue-500 rounded-md text-sm transition-colors"
>
Alternar estado (Ciclo: Indet {'->'} On {'->'} Off)
</button>
<div className="text-center mt-4">
<span className="text-xs text-slate-400 uppercase tracking-widest">Valor actual:</span>
<code className="block mt-1 text-blue-400 font-mono text-lg">
{JSON.stringify(status)}
</code>
</div>
</div>

<div className="pt-4 border-t border-slate-700">
<p className="text-xs text-slate-500 text-center">
Nota: Se usa el icono <strong>Minus</strong> de Lucide cuando el valor es <code className="text-slate-300">"indeterminate"</code>.
</p>
</div>
</div>
</div>
)
}

export default App


He realizado los siguientes cambios clave para que funcione:

  1. Icono Condicional: Dentro de CheckboxPrimitive.Indicator, he añadido una lógica que verifica si checked === "indeterminate". Si es así, renderiza el icono Minus (la raya horizontal); de lo contrario, renderiza el Check.

  2. Estilos de Datos: He añadido data-[state=indeterminate]:bg-primary en las clases de Tailwind del Root. Radix UI asigna automáticamente este atributo data-state cuando el prop checked es exactamente el string "indeterminate".

  3. Grosor del Trazo: He usado stroke-[3] en el componente Minus para que visualmente tenga un peso similar al icono de verificación por defecto de Radix.

¿Deseas que ajustemos también el comportamiento de los checkboxes "hijos" para que actualicen automáticamente al padre? 

viernes, 27 de febrero de 2026

Caché de Vite

 Preámbulo: La solución efectiva fue borrar node_modules/.vite, y reiniciar el IDE.


En mi proyecto ha cambiado la ruta de un import Typescript. Sin embargo, con el sistema Vite en funcionamiento, el Typescript que hace la importación sigue apuntando a la ruta vieja. Por supuesto esto genera un error.

Lo que te está pasando es bastante común cuando se cambia la ruta de un import en un proyecto con Vite + TypeScript: el compilador de TS sigue apuntando a la ruta vieja porque la caché o la configuración no se actualizó del todo.

📌 Pasos para resolverlo

  1. Verificar el import en el código

    • Asegurate de que el archivo realmente está importando desde la nueva ruta.

    • Si usás alias (@/components/...), revisá que estén bien configurados en tsconfig.json y vite.config.ts.

  2. Actualizar tsconfig.json

    • Revisá la sección compilerOptions.paths.

    • Ejemplo:

      json
      {
        "compilerOptions": {
          "baseUrl": ".",
          "paths": {
            "@/*": ["src/*"]
          }
        }
      }
      
    • Si cambiaste la estructura de carpetas, tenés que ajustar estos paths.

  3. Actualizar vite.config.ts

    • Vite también necesita saber los alias:

      ts
      import { defineConfig } from "vite";
      import tsconfigPaths from "vite-tsconfig-paths";
      
      export default defineConfig({
        plugins: [tsconfigPaths()],
      });
      
    • O manualmente:

      ts
      resolve: {
        alias: {
          "@": "/src",
        },
      }
      
  4. Limpiar la caché de Vite y TypeScript

    • Cerrá el servidor de desarrollo.

    • Borrá la carpeta node_modules/.vite y cualquier .tsbuildinfo que tengas.

    • Volvé a correr npm run dev o pnpm dev.

  5. Revisar el editor/IDE

    • A veces VSCode mantiene referencias viejas en su caché interna.

    • Reiniciá el editor o ejecutá Developer: Reload Window.

✅ Conclusión

El error ocurre porque TypeScript y Vite no están alineados en la nueva ruta. Ajustá tsconfig.json y vite.config.ts, limpiá cachés y reiniciá el servidor/editor. Con eso, los imports deberían apuntar correctamente a la ruta nueva.

¿Querés que te arme un checklist rápido para que lo ejecutes paso a paso y confirmes dónde está el problema (alias, caché, o import directo)?

viernes, 20 de febrero de 2026

Problema: el desarrollo es lento

 Ayer estuve media jornada haciendo que un checkbox tuviera 2 ó 3 Estados según la ocasión. En mi opinion con este nivel de demoras no puedo pensar en un negocio rentable.

Entiendo tu frustración, Gabriel. Pasar media jornada en algo que parece “simple” como un checkbox con dos o tres estados puede sentirse como un bloqueo enorme, sobre todo si lo mirás con la idea de un negocio rentable. Pero lo que te pasó es bastante común: los detalles de UI y librerías suelen tener una curva de aprendizaje más empinada de lo que parece, y la primera vez que uno se mete en personalizaciones (como el estado indeterminate) se pierde mucho tiempo en pruebas, documentación y ajustes.

🔹 Cómo ponerlo en perspectiva

  • Tiempo invertido ≠ tiempo perdido: lo que hiciste ayer es inversión en conocimiento. La próxima vez que necesites un checkbox con múltiples estados, lo vas a resolver en minutos.

  • Curva de aprendizaje inicial: al principio todo parece lento, pero una vez que dominás los patrones (controlado vs. no controlado, slots, estados), el desarrollo se acelera mucho.

  • Negocio rentable: lo que determina la rentabilidad no es que cada detalle salga rápido desde el día uno, sino que construyas una base sólida que te permita iterar más rápido en el futuro.

🔹 Estrategias para evitar demoras

  • Patrones reutilizables: convertí tu solución en un pequeño componente propio (TriStateCheckbox) y usalo en todos tus proyectos.

  • Documentación personal: guardá snippets y notas de cómo resolviste el estado indeterminate. Así no volvés a empezar de cero.

  • Modularización: cada vez que logres resolver un detalle, encapsulalo en un hook o componente. Eso transforma horas de prueba en segundos de reutilización.

✨ En resumen: lo que hoy parece una demora es parte del proceso de aprendizaje. Una vez que tengas tu “caja de herramientas” de componentes y patrones, el tiempo de desarrollo se reduce drásticamente y ahí sí se vuelve rentable.

¿Querés que te arme un checklist práctico con pasos para transformar tus experimentos (como el checkbox de 3 estados) en componentes reutilizables que aceleren tu trabajo futuro?

¿Qué se entiende por iterar en este contexto?

En este contexto de desarrollo con React, iterar significa repetir un ciclo de trabajo sobre tu código o tu producto, ajustando y mejorando en cada vuelta. No se refiere a un bucle de programación, sino al proceso de probar, observar, corregir y volver a probar.

🔹 Qué implica “iterar” en React

  • Cambiar y volver a renderizar: cada vez que modificás estado o props, React vuelve a renderizar el componente. Eso es una iteración en la UI.

  • Refinar componentes: empezás con una versión básica (ej. un checkbox con dos estados), luego lo ajustás para soportar tres estados, después lo estilizas, y así sucesivamente. Cada ajuste es una iteración.

  • Aprendizaje incremental: cada vez que resolvés un detalle (como el indeterminate), ganás experiencia que te permite iterar más rápido en el futuro.

📌 Ejemplo práctico

  1. Primera iteración: checkbox simple (true/false).

  2. Segunda iteración: agregás estado indeterminate.

  3. Tercera iteración: personalizás el indicador gráfico.

  4. Cuarta iteración: lo encapsulás en un componente reutilizable (TriStateCheckbox).

Cada paso es una iteración: no empezás con la versión final, sino que la vas construyendo en ciclos.

✅ Conclusión

Iterar en este contexto significa avanzar por ciclos de mejora continua, donde cada vuelta te acerca más a la solución final y te deja con un componente más sólido y reutilizable. Es la forma natural de trabajar en React y en desarrollo de software en general: no se busca perfección en la primera versión, sino progresar con cada ajuste.

¿Querés que te arme un mini-diagrama visual del ciclo de iteración en React (estado → render → prueba → ajuste → nuevo estado) para que lo tengas como referencia rápida?

A veces me encuentro desarrollando funciones muy técnicas para lo que son los requerimientos del cliente, y por ende, mi trabajo es muy técnico como para explicárselo y cobrárselo al cliente. ¿Qué me pasa?

Lo que te está pasando es bastante común en quienes trabajan con software y clientes: estás resolviendo problemas técnicos profundos, pero los requerimientos del cliente suelen expresarse en términos simples (“quiero un checkbox que funcione así”).

🔹 Qué ocurre en tu caso

  • Brecha de lenguaje: vos pensás en estados, slots, controlados vs. no controlados, renderizaciones… mientras el cliente solo ve “un checkbox que se marca o no”.

  • Trabajo invisible: gran parte del esfuerzo está en detalles técnicos que el cliente no percibe, porque no se ven directamente en la interfaz.

  • Sensación de desbalance: invertís horas en algo que parece mínimo desde afuera, y eso te hace sentir que es difícil justificarlo económicamente.

🔹 Cómo encarar esta situación

  • Traducir lo técnico a valor: en lugar de explicar “tuve que manejar tres estados y personalizar el slot”, podés decir:

    • “Ahora el checkbox refleja correctamente estados parciales, lo que evita errores y mejora la claridad para el usuario final.”

  • Documentar tu proceso: mostrar que detrás de un detalle hay investigación, pruebas y decisiones técnicas. Eso ayuda a que el cliente entienda que no es trivial.

  • Paquetizar soluciones: convertir lo que aprendés en componentes reutilizables. Así, la próxima vez no invertís media jornada, y tu negocio se vuelve más rentable.

  • Cobrar por valor, no por horas: si el cliente obtiene un resultado que le evita problemas o mejora su producto, el precio se justifica aunque el detalle técnico sea invisible.

✨ En resumen: lo que te pasa es que estás viviendo la brecha entre el lenguaje técnico y el valor percibido por el cliente. No es que tu trabajo sea “demasiado técnico”, sino que necesitás traducirlo en términos de impacto y beneficio.

¿Querés que te arme un guion práctico con frases para explicar a un cliente, en lenguaje simple, por qué un detalle técnico como un checkbox con tres estados es valioso y merece ser cobrado?

Sí.

Perfecto, Gabriel. Te armo un guion práctico para explicar a un cliente, en lenguaje simple y orientado al valor, por qué un detalle técnico como un checkbox con tres estados merece ser cobrado. La idea es que traduzcas tu trabajo técnico en beneficios tangibles:

📌 Guion para explicar al cliente

1. Explicar el problema en términos simples

“En su aplicación, había que mostrar claramente si todos los ítems estaban seleccionados, ninguno, o solo algunos. Con un checkbox normal solo se puede mostrar dos estados, lo cual genera confusión.”

2. Mostrar el valor de la solución

“Lo que hice fue crear un checkbox que refleja también el estado intermedio. Así, el usuario entiende de inmediato si la selección es parcial. Esto evita errores y mejora la experiencia.”

3. Resaltar el impacto en el negocio

“Con esta mejora, sus usuarios ahorran tiempo y reducen equivocaciones. Eso significa menos soporte técnico y más confianza en la aplicación.”

4. Justificar el esfuerzo sin tecnicismos

“Aunque parece un detalle pequeño, detrás hay trabajo de diseño y programación para que funcione de manera confiable y se vea bien. Es una inversión en calidad que se traduce en menos problemas futuros.”

🔹 Frases útiles para clientes

  • “Lo que parece un detalle visual en realidad mejora la claridad y reduce errores.”

  • “Invertí tiempo en que funcione de forma robusta, para que no tenga fallas en producción.”

  • “Esto es parte de asegurar que la aplicación sea profesional y fácil de usar.”

✨ En resumen: no necesitás contarle al cliente sobre slots, indeterminate o Radix. Lo que tenés que transmitir es claridad, reducción de errores y experiencia de usuario mejorada. Eso es lo que justifica el valor de tu trabajo.

¿Querés que te arme un ejemplo de presupuesto escrito donde se detalle este tipo de mejora como un ítem facturable (“Optimización de interfaz: checkbox con estado parcial”)?

Creo que tengo un gran talento para crear cosas de programación, pero mi mente es lenta y por lo tanto mi proceso de creación es lento. Eso me frustra porque siento que la gente que en principio se interesa por lo que hago, poco a poco se aburre y pierde el interés, o piensa que yo miento al hablar de que estoy creando tal o cual cosa ya que no muestro resultados efectivos en un plazo tolerable.

Gabriel, lo que describís es una tensión muy real: tenés talento para crear cosas técnicas, pero tu proceso es más lento de lo que te gustaría, y eso te genera frustración porque sentís que los demás pierden interés o dudan de tu trabajo.

🔹 Qué te está pasando

  • Alta capacidad creativa: sos capaz de idear soluciones y componentes complejos.

  • Procesamiento pausado: tu mente necesita más tiempo para madurar las ideas y resolver los detalles técnicos.

  • Choque con expectativas externas: los clientes o colegas suelen esperar resultados rápidos y visibles, mientras que tu proceso es más profundo y menos inmediato.

  • Percepción de falta de credibilidad: como no mostrás avances tangibles en plazos cortos, temés que piensen que exagerás o que no cumplís.

🔹 Cómo transformar esta situación

  • Mostrar avances parciales: aunque no tengas la versión final, podés compartir prototipos, capturas o explicaciones simples. Eso mantiene el interés y demuestra progreso.

  • Traducir lo técnico a valor: en vez de hablar de slots o estados, contá qué beneficio trae (“esto evita errores”, “esto hace más clara la interfaz”).

  • Gestionar expectativas: explicá desde el inicio que tu proceso es más artesanal y detallado, pero que eso asegura calidad y robustez.

  • Reutilización: convertí cada logro técnico en un componente o snippet que te ahorre tiempo en el futuro. Así tu velocidad mejora con cada proyecto.

🔹 Reencuadre positivo

Tu “mente lenta” no es un defecto: es un estilo de pensamiento más reflexivo y profundo. Eso te permite crear soluciones sólidas y originales, aunque el ritmo sea distinto al esperado. El desafío está en comunicar el valor de ese proceso y en mostrar resultados intermedios para que los demás vean que avanzás.

✨ En resumen: lo que te pasa es que tu talento técnico va acompañado de un ritmo más pausado, y la clave está en hacer visible el progreso y traducir tu trabajo en beneficios claros para que los demás lo valoren.

¿Querés que te arme un plan de comunicación con clientes paso a paso, para que puedas mostrar avances aunque tu proceso sea lento y así mantener su confianza e interés?

Sí.

Genial, Gabriel. Te propongo un plan de comunicación paso a paso para que, aunque tu proceso sea más lento, puedas mantener la confianza y el interés de clientes o personas que siguen tu trabajo. La clave está en mostrar avances visibles y traducir lo técnico en valor.

📌 Plan de comunicación con clientes

1. Inicio del proyecto

  • Qué decir:

    “Mi forma de trabajar es detallada y artesanal. Eso significa que el resultado final será sólido, aunque el proceso lleve más tiempo.”

  • Objetivo: establecer expectativas claras desde el principio.

2. Avances intermedios

  • Acción: mostrar prototipos, capturas de pantalla, o incluso un snippet de código que ya funciona.

  • Qué decir:

    “Todavía no está terminado, pero ya resolví la parte que permite que el checkbox tenga tres estados. Esto asegura que la lógica esté bien construida antes de avanzar en el diseño.”

  • Objetivo: demostrar progreso tangible aunque no sea la versión final.

3. Traducción técnica a valor

  • Acción: explicar el beneficio en términos simples.

  • Qué decir:

    “Este detalle técnico evita errores de selección y hace que la interfaz sea más clara para el usuario final.”

  • Objetivo: que el cliente vea impacto, no complejidad.

4. Cierre de cada ciclo

  • Acción: resumir lo logrado en cada iteración.

  • Qué decir:

    “En esta etapa ya tenemos la lógica funcionando y el indicador visual personalizado. Lo próximo será integrarlo con el resto del formulario.”

  • Objetivo: mantener la narrativa de avance continuo.

5. Entrega final

  • Acción: mostrar el resultado y recordar el camino recorrido.

  • Qué decir:

    “Lo que empezó como un checkbox básico ahora es un componente reutilizable que mejora la experiencia y se puede aplicar en otros proyectos.”

  • Objetivo: reforzar que el tiempo invertido generó valor duradero.

✅ Beneficio del plan

  • Mantiene la confianza del cliente.

  • Evita la percepción de “no hay avances”.

  • Te permite justificar tu trabajo como inversión en calidad y reutilización.

¿Querés que te arme un ejemplo de mensaje corto tipo update semanal que podrías enviar a un cliente para mostrar progreso aunque todavía no tengas la versión final lista?

Sí.

Perfecto, Gabriel. Te dejo un ejemplo de mensaje corto tipo update semanal que podrías enviar a un cliente para mostrar progreso, incluso si todavía no tenés la versión final lista. La idea es mantener la confianza y demostrar que avanzás paso a paso:

📌 Ejemplo de update semanal

Asunto: Avance semanal del proyecto Mensaje: Hola [Nombre del cliente],

Quiero compartirte un breve avance de esta semana:

  • Ya implementé la lógica que permite que el checkbox tenga tres estados (seleccionado, no seleccionado y selección parcial).

  • Esto asegura que los usuarios vean claramente si la selección es completa o parcial, reduciendo posibles errores.

  • El próximo paso será ajustar la representación visual para que sea más clara y consistente con el resto de la interfaz.

Aunque todavía no está la versión final, estos avances garantizan que la base técnica esté sólida y que el resultado final sea confiable.

Saludos, Gabriel

🔹 Claves del mensaje

  • Breve y concreto: no entrás en tecnicismos, pero mostrás progreso.

  • Orientado al valor: explicás qué beneficio trae lo que hiciste.

  • Proyectás confianza: aclarás que falta trabajo, pero que la base ya está firme.

¿Querés que te arme una plantilla adaptable para que puedas usarla cada semana, cambiando solo los puntos de avance y próximos pasos?

jueves, 19 de febrero de 2026

Componentes controlados y no controlados en React

 ¿Cuál es la forma opuesta a lo que es un componente controlado en React?

La forma opuesta a un componente controlado en React es un componente no controlado.

🔹 Diferencia clave

  • Componente controlado:

    • El valor del input (o cualquier elemento interactivo) está vinculado al estado de React.

    • React es la “fuente de la verdad”.

    • Ejemplo:

      jsx
      function Controlado() {
        const [valor, setValor] = useState("")
      
        return (
          <input
            value={valor}
            onChange={(e) => setValor(e.target.value)}
          />
        )
      }
      
  • Componente no controlado:

    • El valor se maneja directamente en el DOM, no en el estado de React.

    • Usás una ref para acceder al valor cuando lo necesitás.

    • Ejemplo:

      jsx
      function NoControlado() {
        const inputRef = useRef(null)
      
        const manejarSubmit = () => {
          console.log(inputRef.current.value) // accede al valor directamente
        }
      
        return (
          <>
            <input ref={inputRef} />
            <button onClick={manejarSubmit}>Enviar</button>
          </>
        )
      }
      

🔹 Cuándo usar cada uno

  • Controlados:

    • Cuando necesitás sincronizar el valor con el estado de React.

    • Ideal para formularios complejos, validaciones en tiempo real, UI dinámica.

  • No controlados:

    • Cuando querés simplicidad y menos renders.

    • Útiles para formularios simples, integración con librerías externas, o cuando no necesitás controlar cada cambio.

✨ En resumen: la forma opuesta a un componente controlado es un componente no controlado, donde el DOM mantiene el estado del valor y React solo lo consulta cuando hace falta.

¿Querés que te arme una tabla comparativa entre controlados y no controlados (ventajas, desventajas, casos de uso) para que tengas una visión clara de cuándo conviene cada enfoque?