Especificación del formato de schema
Todo lo que puede declarar un schema en /DataLab/config/upload_schemas/{OP_ID}.
Esta es la referencia para crear o endurecer operaciones. Los cambios se propagan a los wizards en vivo
(el loader usa onValue) — sin redeploy.
Regla de oro: el
op_id del nodo debe coincidir EXACTO con el id del catálogo.
Un schema escrito en una clave con typo (ej. ASIST_CTRL_TIEMP vs ASIST_CTRL_TIEMPOS) queda huérfano y la UI seguirá mostrando SCHEMA MIN.Estructura completa
{
"op_id": "MOV_INT_ADMIN", // = clave del nodo
"area_id": "ORI",
"title": "Movilidad internacional administrativos",
"description": "…",
// ── Hoja Excel ──
"sheetName": "P_Admin_int_sal", // pestaña REAL del template
"sheetAliases": ["p_admin_int_sal", "datos", …], // fuzzy match
"headerRow": 18, // fila (1-indexed) de las cabeceras
// ── Destino de datos ──
"dataArrayField": "movilidad_int_admin", // clave del array en el reporte
"dataPathTemplate": "DataLab/ciclos_de_carga/ori/{cicloKey}/data/movilidad_int_admin",
"configPath": "DataLab/config/ciclo_mov_int_admin",
"codigoPrefix": "MIA", // prefijo ÚNICO de certificados
// ── Ciclo ──
"defaultCicloKey": "2026-B", // semestral: AAAA-A|B · mensual: AAAA-MM · anual: AAAA
"cicloGranularity": "semestral", // semestral | mensual | anual
"reportNature": "acumulado", // opcional: reportes acumulados al corte
"reportNote": "…",
// ── Validación ──
"requiredColumns": ["semestre", "tipo_documento", …], // ⚠ KEYS CANÓNICAS
"criticalFields": ["semestre", "tipo_documento", …], // no pueden ir vacíos
"dateColumns": ["fecha_inicio_movilidad"], // reciben coerción de fecha
"fields": [ … ], // ver §Campos
"fieldValidations": [ … ], // ver §Validaciones
"controlledValues": { … }, // ver §Catálogos
"integrityRules": [ … ], // ver §Reglas
"additionalSheets": [ … ], // ver §Multi-hoja
"templateUrl": "https://…" // se PRESERVA al reescribir el schema
}
Campos (fields[])
{
"key": "numero_documento", // snake_case canónico — así viaja a RTDB
"label": "Número documento", // para UI
"type": "text", // text | number | date | currency | boolean
"currency": "COP", // solo type=currency (COP | USD)
"required": true, // el valor no puede faltar
"requiredColumn": true, // la COLUMNA debe existir en el Excel
"min": 0, "max": 500, // cotas para type=number
"aliases": ["NÚMERO DOCUMENTO", "NUMERO DOCUMENTO", "IDENTIFICACIÓN"]
}
Aliases: el matcher normaliza con
trim → lowercase → espacios→_ y prueba primero la key, luego cada alias.
Incluye SIEMPRE variantes con y sin tilde, y los typos literales del template oficial
(TPO DOCUMENTO, DEPENDENCIA ADMINSITRATIVA, FUENTE FINANACIACIÓN existen en producción y deben aliarse tal cual).Validaciones (fieldValidations[])
type: "pattern"
{ "key": "semestre", "type": "pattern",
"pattern": "^(20[2-3]\\d)\\s?-?\\s?(A|B|I|II|1|2|a|b|i|ii)$",
"msg": "Formato: 2026-A, 2026-B, …", // alineado con lo que el pattern acepta
"nullable": false } // true ⇒ vacío es válido
Patrones de la casa (copiar tal cual):
| Uso | Pattern |
|---|---|
| Semestre | ^(20[2-3]\d)\s?-?\s?(A|B|I|II|1|2|a|b|i|ii)$ |
| Año | ^20\d{2}$ |
| Documento | ^[1-9]\d{5,14}$ (6-15 dígitos, sin ceros a la izquierda) |
| Texto no vacío | ^\S.*\S$|^\S$ (permite 1 carácter, rechaza espacios en los bordes) |
| Moneda COP | ^\d{1,3}(\.\d{3})*(,\d{1,2})?$|^\d+([.,]\d{1,2})?$ |
| Entero ≥ 0 | ^(0|[1-9]\d*)$ |
| Hora 24h/12h | ^\s*(([01]?\d|2[0-3]):[0-5]\d|(0?[1-9]|1[0-2]):[0-5]\d\s?(AM|PM|am|pm|a\.m\.|p\.m\.))\s*$ |
| SNIES | ^\d{3,10}$ |
| Promedio 0–5 | ^(5([.,]0+)?|[0-4]([.,]\d{1,3})?)$ |
| Duración con unidad | ^\s*\d+([.,]\d+)?\s*(día(s)?|Día(s)?|…|año(s)?|AÑO(S)?)\s*$ (unidad OBLIGATORIA, variantes de caja explícitas) |
Los patterns son case-sensitive y sin flags: si necesitas tolerar “Semanas”, “SEMANAS” y “semanas”, enumera las variantes en el alternation. El validador NO aplica
i.type: "enum"
{ "key": "tipo_documento", "type": "enum",
"values": ["CC", "cc", "C.C.", "CÉDULA DE CIUDADANÍA", …],
"caseInsensitive": true,
"nullable": false,
"msg": "Tipo de documento debe estar en el catálogo" }
Antipatrón detectado en auditoría: definir
controlledValues sin su
fieldValidation type:"enum" deja el catálogo como documentación muerta — cualquier texto pasa.
Siempre en pareja.type: "date-range"
{ "key": "fecha_grado", "type": "date-range",
"min": "2010-01-01",
"max": "{today+30d}", // placeholders: {today} {today±Nd} {today±Ny}
"nullable": false, "msg": "…" }
Catálogos (controlledValues{})
Mapa key → valores permitidos. Convenciones:
- Variantes con y sin tilde, y de caja (MAYÚSCULA / Título / minúscula) para lo que los usuarios digitan.
- Booleanos SI/NO: incluir además
VERDADERO/FALSO,TRUE/FALSE,1/0,S/N(serializaciones de Excel). Nunca incluir el placeholder"SÍ / NO"del template. - Un enum modela UNA dimensión: no mezclar dirección + modalidad + propósito en el mismo catálogo (ej.
tipo_movilidad≠objeto_movilidad). - Siempre incluir escapes:
OTRO,N/A,NO APLICA.
Reglas de integridad (integrityRules[])
| kind | Semántica | Ejemplo |
|---|---|---|
cross-field-order | fieldA op fieldB | fecha_inicio < fecha_final |
cross-field-conditional | si condition entonces require | discapacidad=SI ⇒ tipo_discapacidad no vacío |
cross-field-sum | Σfields op target | cantidades desagregadas ≤ cantidad total |
cross-field-year-match | año de compareTo = field | anio ≙ año(fecha_formulacion) |
cross-field-vs-cicloKey | campo ≙ {cicloKey} de la ruta | anio = ciclo anual de carga |
Las reglas usan severity: warning|info — advierten sin bloquear la carga.
Multi-hoja (additionalSheets[])
Para operaciones cuyo template trae más de una pestaña de datos:
mode: "union"— misma entidad en dos hojas (ej. movilidad SALIENTE + ENTRANTE). Se declaradirectioneinjectField(columna sintética, ej.direccion_movilidad: "ENTRANTE"). Cada hoja adicional lleva sus propiosfields/validations/headerRow— pueden diferir de la principal (la hoja entrante suele añadir nombres/apellidos/sexo porque la persona es externa).joinKey(modo join) — hoja satélite que se une por clave a la principal (ej. beneficiarios porcodigo_cursoen EVENTOS_EDU_CONT).
Checklist para publicar un schema
- Confirmar
op_idEXACTO contra el catálogo (los ids largos se truncan en la UI). sheetNameyheaderRowtomados del template REAL (varían: 18, 19, 20).requiredColumnscon keys canónicas, todas presentes enfields[].key.- Cada
controlledValuescon su validaciónenum+caseInsensitive. - Solo columnas identificadoras como
required— el resto opcional connullable: true. - Preservar
templateUrlexistente al reescribir (y nunca escribirundefined: RTDB lo rechaza). - Quitar
__minimaly verificar que el chip pase a SCHEMA OK. - Agregar el
dataArrayFieldadataCandidatesenprocesar.html. codigoPrefixúnico en todo el catálogo.