# Cómo validar un campo de texto

> Restringir caracteres, limitar el tamaño y leer el valor limpio antes de enviar.

Source: https://crossfly.dev/es/docs/cookbook/forms/validar-campo — CrossFly documentation. Full docs as one file: https://crossfly.dev/llms-full.txt

# Cómo validar un campo de texto

**Propósito.** Garantizar que el usuario solo pueda escribir caracteres válidos, limitar el número de caracteres y obtener el valor sin máscara al momento del envío. En CrossFly la validación de entrada es declarativa (hecha en el estilo del campo), y la validación de negocio se hace al enviar.

## APIs usadas
- `campo.<TextFieldStyle>getStyle().validChars("0123456789")` — filtra los caracteres aceptados. `null` (por defecto) acepta todo. La comparación se hace en MAYÚSCULA: internamente `isCharValid(c)` hace `validChars.indexOf(Character.toUpperCase(c))`, por lo que informa las letras en mayúscula aunque el campo acepte minúsculas.
- `campo.<TextFieldStyle>getStyle().maxLength(11)` — limita el total de caracteres (`0` = ilimitado). Vale para la escritura y para pegar (`clipboardPaste`).
- `campo.<TextFieldStyle>getStyle().capitalise(Capitalise.UPPER)` — fuerza MAYÚSCULA/minúscula (`UPPER`, `LOWER`, `NORMAL`).
- `TextField.getText()` — texto como se muestra (con máscara, si la hay).
- `TextField.getTextWithoutMask()` — solo los dígitos/contenido bruto (ej.: `"12345678900"` en vez de `"123.456.789-00"`). Usa este en el envío.
- `TextInput.mapKeys(from, to)` — remapea teclas (ej.: sustituir coma por punto). `from` y `to` deben tener el mismo tamaño.

> No hay callback por tecla en CrossFly. No existe `onTextChanged` — lees `getText()`/`getTextWithoutMask()` en el momento del envío (o dentro del `onAction` del botón).

## Ejemplo completo (copiar, pegar y ejecutar)
```java
package com.exemplo;

import dev.crossfly.ui.*;
import dev.crossfly.ui.layout.*;
import dev.crossfly.ui.style.*;   // TextFieldStyle (config en el estilo)

/** Pantalla principal ejecutable. Apunta el launcher (classname) y corre: gradlew :lwjgl3:run */
public class MainWindow extends Window
{
   @Override
   public void onShow() throws Exception
   {
      super.onShow();
      setLayout(new VerticalLayout());

      TextField cpf = new TextField("");
      cpf.caption("CPF");
      cpf.<TextFieldStyle>getStyle()
         .validChars("0123456789")   // solo dígitos
         .maxLength(11);             // 11 dígitos

      TextField apelido = new TextField("");
      apelido.caption("Apelido");
      apelido.<TextFieldStyle>getStyle()
         .capitalise(Capitalise.LOWER)
         .maxLength(20);

      Label resultado = new Label("");

      Button enviar = new Button("Enviar");
      enviar.onAction(origin ->
      {
         String cpfLimpo = cpf.getTextWithoutMask();   // "12345678900"
         if (cpfLimpo.length() != 11)
         {
            Hint.show("CPF deve ter 11 dígitos", 2000);
            return;
         }
         resultado.setText("CPF: " + cpfLimpo + " · apelido: " + apelido.getText());
      });

      add(LAY_FILL, cpf);
      add(LAY_FILL, apelido);
      add(LAY_AUTO, enviar);
      add(LAY_FILL, resultado);
   }
}
```

> El fragmento de abajo muestra **solo el núcleo**. Para ejecutarlo, colócalo en el esqueleto `MainWindow` de arriba.

## Fragmento mínimo
```java
import dev.crossfly.ui.*;
import dev.crossfly.ui.style.*;
import static dev.crossfly.ui.UIConstants.Capitalise;

TextField cpf = new TextField("");
cpf.caption("CPF");
cpf.<TextFieldStyle>getStyle()
   .validChars("0123456789")   // solo dígitos
   .maxLength(11);             // 11 dígitos

TextField apelido = new TextField("");
apelido.caption("Apelido");
apelido.<TextFieldStyle>getStyle()
   .capitalise(Capitalise.LOWER)
   .maxLength(20);

Button enviar = new Button("Enviar");
enviar.onAction(origin ->
{
   String cpfLimpo = cpf.getTextWithoutMask();   // "12345678900"
   if (cpfLimpo.length() != 11)
   {
      new Alert("Erro", "CPF deve ter 11 dígitos").show();
      return;
   }
   // ... continúa con el envío
});
```

## Profundiza
- [Máscaras y teclados](/docs/ui/forms-and-input/masks-and-keyboards) · [Foco y validación](/docs/ui/forms-and-input/focus-and-validation)
- Recetas hermanas: [Máscara de CPF/CEP/fecha](/docs/cookbook/forms/mascara-cpf) · [Campo de contraseña](/docs/cookbook/forms/campo-de-senha)
- Fuente canónica: Capítulo 14 — Widgets básicos · [Enums (UIConstants)](/docs/reference/enums-uiconstants)
