guía completa · ES

Construyendo un cuestionario
desde cero, celda por celda.

XLSForm es el estándar que convierte una planilla común en un formulario digital completo — con lógica de salto, validación, GPS, fotos y varios idiomas — listo para correr en ODK Collect, KoboToolbox o Enketo. Esta guía construye una encuesta de hogares paso a paso y documenta todos los tipos de pregunta y configuraciones, con ejemplos que puede copiar y convertir con rxform.

1 · Anatomía de un XLSForm

Un XLSForm es un archivo .xlsx (o .xls/.ods) con hasta tres hojas principales:

Reglas de oro para la columna name (preguntas, grupos y opciones):

Los nombres empiezan con letra o _ y contienen solo letras, números, -, _ y . — sin espacios ni acentos. Se convierten en los nombres de columna de su base de datos: prefiera ingreso_mensual a Ingreso Mensual ($). El label, en cambio, es texto libre: acentos, emoji, lo que haga falta.

2 · El primer formulario

El formulario útil más pequeño tiene tres filas en la hoja survey:

survey
typenamelabel
2textencuestadorNombre de quien aplica la encuesta
3integertamano_hogar¿Cuántas personas viven en el hogar?
4select_one sim_naotiene_agua¿El hogar tiene agua potable?

La pregunta de la fila 4 usa la lista si_no, definida en la hoja choices:

choices
list_namenamelabel
2si_nosi
3si_nonoNo

Y la hoja settings da identidad al formulario:

settings
form_titleform_idversion
2Encuesta de Hogares 2026enc_hogares_20262026081101

Convierta y listo:

$ rxform encuesta_hogares.xlsx
encuesta_hogares.xml

3 · Todos los tipos de pregunta

Texto y números

survey
typenamelabelhint
2textobservacionesObservaciones generalesTexto libre
3integeredadEdad (años cumplidos)Solo números enteros
4decimalingresoIngreso mensualUse punto para centavos
5rangesatisfaccionSatisfacción con el transporte0 = pésimo · 10 = excelente

Fechas y horas

survey
typenamelabelappearance
2datefecha_visitaFecha de la visita
3datemes_mudanza¿Cuándo se mudó?month-year
4timehora_inicioHora de inicio
5dateTimecitaAgendar retorno para

Apariencias útiles para date: month-year, year y no-calendar.

Opciones y listas

survey
typenamelabelappearance
2select_one escolaridadescolaridadEscolaridad del jefe de hogarminimal
3select_multiple serviciosservicios¿Qué servicios llegan a la calle?
4rank prioridadesprioridadesOrdene las prioridades del barrio

Se permiten columnas extra en la hoja choices, que se vuelven datos de la opción — la base de los filtros en cascada:

Selecciones en cascada (choice_filter)

choices
list_namenamelabeluf
2departamentoslmLima
3departamentoscsCusco
4distritosmirafloresMirafloreslm
5distritosbarrancoBarrancolm
6distritospisacPísaccs
survey
typenamelabelchoice_filter
2select_one departamentosdeptoDepartamento
3select_one distritosdistritoDistritodepto = ${depto}

En el filtro, las columnas de la lista aparecen por su nombre (depto) y las respuestas previas como ${depto}. Para barajar opciones: parameters = randomize=true (con seed=42 para un orden reproducible).

Ubicación

survey
typenamelabelparameters
2geopointpuntoUbicación del hogarcapture-accuracy=5 warning-accuracy=10
3geotracetrayectoTrayecto hasta la parada de bus
4geoshapeterrenoContorno del terreno

geopoint captura un punto (precisión objetivo en metros vía parameters); geotrace, una línea; geoshape, un polígono cerrado.

Medios y archivos

survey
typenamelabelappearanceparameters
2imagefoto_fachadaFoto de la fachadamax-pixels=1024
3imagefirmaFirma del encuestadosignature
4audiorelatoGrabe el relatoquality=voice-only
5videovideo_calleVideo de la calle
6filecomprobanteAdjunte el comprobante (PDF)
7barcodecodigoCódigo de barras del medidor

image con appearance = signature se vuelve campo de firma; draw, dibujo libre. max-pixels reduce el tamaño de las fotos en el dispositivo.

Notas y confirmaciones

survey
typenamelabel
2noteapertura¡Buenos días! Esta encuesta toma ~15 minutos. Las respuestas son confidenciales.
3acknowledgeconsentimientoLa persona acepta participar

Campos invisibles

survey
typenamecalculation
2calculateingreso_per_capita${ingreso} div ${tamano_hogar}
3hiddenversion_muestra

calculate computa un valor con XPath (funciones como if(), concat(), selected(), count(), round()…); hidden guarda un valor rellenable por default o integraciones.

Metadatos — recogidos solos

survey
typenamelabel / trigger
2startinicio
3endfin
4todayhoy
5deviceiddispositivo
6usernameusuario
7auditaudit
8start-geopointubicacion_inicio
9background-audiograbacion
10background-geopointubicacion_respuestatrigger: ${tiene_agua}

4 · Lógica del formulario

Toda la lógica usa ${nombre} para referenciar respuestas anteriores.

relevant — saltar preguntas

survey
typenamelabelrelevant
2select_one sim_naotrabaja¿Usted trabaja?
3textocupacion¿Cuál es su ocupación?${trabaja} = 'si'
4notenota_edadMódulo aplicable a mayores de edad.${edad} >= 18

La pregunta solo aparece cuando la expresión es verdadera. Combine condiciones con and/or; para selección múltiple use selected(${servicios}, 'agua').

constraint — validar respuestas

survey
typenamelabelconstraintconstraint_message
2integeredadEdad. >= 0 and . <= 120La edad debe estar entre 0 y 120.
3datenacimientoFecha de nacimiento. <= today()La fecha no puede ser futura.

El punto . es la propia respuesta. El mensaje aparece cuando la regla falla.

required, default, read_only

survey
typenamelabelrequiredrequired_messagedefault
2select_one si_notiene_agua¿Agua potable?yesEsta respuesta es obligatoria.
3datefecha_visitaFecha de la visitatoday()
4integersectorSector censal42

trigger — recalcular cuando algo cambia

survey
typenamelabeltriggercalculation
2integertamano_hogar¿Cuántos residentes?
3integerninos¿Cuántos niños?${tamano_hogar}

Con trigger, el campo se (re)define cada vez que cambia la pregunta referenciada — aquí, limpiando ninos cuando se edita tamano_hogar. Si hay calculation, corre en ese momento (en vez de continuamente).

5 · Grupos, repeticiones y loops

Grupos

survey
typenamelabelappearancerelevant
2begin_groupmodulo_aguaMódulo: Saneamientofield-list${tiene_agua} = 'si'
3select_one fuente_aguafuenteFuente del agua
4integerdias_sinDías sin agua en el mes
5end_group

Repeticiones

survey
typenamelabelrepeat_count
2begin_repeatresidenteDatos del residente${tamano_hogar}
3textnombreNombre
4integeredad_residenteEdad
5end_repeat

El bloque se repite una vez por residente. Sin repeat_count, el encuestador agrega repeticiones a mano; con una expresión (o un número), la cantidad es automática. Dentro de la repetición, use position(..) para el índice actual e indexed-repeat() para leer valores de otra repetición.

Loops sobre una lista

survey
typenamelabel
2begin loop over servicioseval_servicios
3select_one notasnota¿Cómo evalúa el servicio de %(label)s?
4end loop

Genera un bloque por opción de la lista servicios, sustituyendo %(label)s y %(name)s — una pregunta “¿Cómo evalúa el servicio de Agua?”, otra “…de Alcantarillado?”, etcétera.

6 · Varios idiomas

survey
typenamelabel::Español (es)label::English (en)hint::Español (es)
2integermoradores¿Cuántas personas viven aquí?How many people live here?Cuente a todos los residentes

Basta sufijar las columnas traducibles con ::Idioma (código): funciona para label, hint, guidance_hint, constraint_message, required_message, image, audio y video — en las hojas survey y choices. Defina el idioma inicial en settingsdefault_language = Español (es). La aplicación gana un menú de idiomas.

Medios por pregunta: las columnas image/audio/video asocian archivos (p. ej. tarjeta_respuesta.jpg) enviados junto con el formulario.

7 · Apariencias (appearance)

referencia
appearanceaplica aefecto
minimalselect_one/multiplemenú desplegable compacto
quickselect_oneavanza al seleccionar
likertselect_oneescala horizontal estilo Likert
columns / columns-nselectsopciones en columnas
autocompleteselect_onebusca mientras escribe
field-listgruposgrupo entero en una pantalla
table-listgruposmatriz de selects con la misma lista
multilinetextcaja de varias líneas
numbers / thousands-septextteclado numérico · separador de miles
month-year · year · no-calendardateprecisión reducida · sin calendario
signature · draw · annotateimagefirma · dibujo · anotar sobre foto
map · quick mapselect_one_from_file (geojson)elegir en el mapa
ratingrangeestrellas
label · list-nolabelselectspiezas para armar matrices manuales

8 · Parámetros (parameters)

La columna parameters recibe pares clave=valor separados por espacios:

referencia
tipoparámetros
rangestart=0 end=10 step=1
textrows=5
imagemax-pixels=1024
audio · background-audioquality=voice-only | low | normal
geopointcapture-accuracy=5 warning-accuracy=10
selectsrandomize=true seed=42
select_*_from_filevalue=coluna label=coluna
auditlocation-priority=balanced location-min-interval=60 location-max-age=120

9 · La hoja settings

referencia
columnapara qué
form_titletítulo mostrado en la aplicación
form_ididentificador único del formulario en el servidor
versionversión (use fecha: 2026081101); el servidor gestiona actualizaciones con ella
instance_namenombre de cada envío en los listados — ej. concat(${distrito}, '-', ${fecha_visita})
default_languageidioma inicial, ej. Español (es)
stylepages (una pantalla por grupo) · theme-grid
public_keyclave RSA para cifrar envíos de extremo a extremo
submission_url · auto_send · auto_deletedestino y política de envío
allow_choice_duplicatespermite name repetido en una lista
clean_text_valuesno preserva espacios múltiples en las celdas (por defecto se colapsan)
name · namespaces · attribute::x · prefix · delimiter · flat · omit_instanceIDajustes avanzados del XML generado

10 · Datos externos y entities

Listas grandes en archivo

survey
typenamelabelchoice_filter
2select_one_from_file distritos.csvdistritoDistritodepto = ${depto}
3select_one_from_file sectores.geojsonsectorSector (en el mapa)

El CSV necesita columnas name y label (o indique otras con parameters = value=codigo label=descripcion); GeoJSON usa id/title y habilita appearance = map. El archivo se sube al servidor junto con el formulario.

Consultas con pulldata()

survey
typenamecalculation
2calculatemeta_sectorpulldata('metas', 'meta', 'sector', ${sector})

Busca en metas.csv la columna meta de la fila donde sector = la respuesta. También existen ${last-saved#campo} (valor del último envío guardado) y la hoja entities, que permite al formulario crear y actualizar registros compartidos entre formularios (p. ej. registrar hogares en una visita y reencontrarlos en la siguiente) — con save_to en las preguntas y create_if/update_if/label en la hoja.

11 · Errores comunes — y cómo avisa rxform

12 · Convertir y publicar

# convertir
$ rxform encuesta_hogares.xlsx
encuesta_hogares.xml

# revisar errores de autoría es solo ejecutarlo — el mensaje señala la celda
$ rxform encuesta_hogares.xlsx --stdout > /dev/null
  1. KoboToolbox: suba el propio .xlsx (Kobo convierte en el servidor con pyxform — rxform genera exactamente el mismo XML, así que sirve como validación local instantánea) o despliegue el XML vía API.
  2. ODK Central: publique el .xlsx o el .xml generado; adjunte los CSV/GeoJSON de listas externas.
  3. Enketo: los formularios publicados reciben enlace web automáticamente.

Flujo recomendado: mantenga el .xlsx bajo control de versiones, corra rxform en CI para validar cada cambio (la conversión falla con un mensaje preciso si algo se rompe) y publique desde la versión validada.

guía · rxform — volver al inicio · referencia oficial xlsform.org