Bom dia
Sincronizado com a planilha online! (Última atualização: --:--)
Conhecimento da equipe, tecnologia por tecnologia
Níveis Alto, Médio e Baixo em 12 tecnologias e no curso interno, direto da planilha de colaboradores.
Conhecimento médio
0%
Colaboradores
0pessoas
Custo da operação
0
Nível Alto
0%
Conhecimento médio por tecnologia
Média do nível e participação do nível Alto (0 a 100%)
Distribuição dos níveis
Todas as respostas das 12 tecnologias
Maior conhecimento médio
Passe o mouse sobre o cargo para ver equipe, admissão e salário
Alto, Médio e Baixo por tecnologia
Quantidade de colaboradores em cada nível
Matriz de conhecimento
Clique em uma linha para abrir o perfil · A = Alto, M = Médio, B = Baixo
Psicológico médio
0%
Brilho nos olhos médio
0%
Alto nos dois critérios
0pessoas
Psicológico
Distribuição dos níveis na equipe
Brilho nos olhos
Distribuição dos níveis na equipe
Critérios por equipe
Média de cada critério em cada equipe (0 a 100%)
Colaborador por colaborador
Clique em uma linha para abrir o perfil
Aptos para promoção
0
Critério do painel: média das 12 tecnologias (Baixo 33, Médio 67, Alto 100) acima do mínimo e, se marcado, Curso interno em Alto. Ajuste o mínimo à política da empresa; a decisão final também considera cargo e tempo de casa.
Crescimento por tempo de empresa
Média de conhecimento e nível Alto por faixa de tempo
Curso interno
Nível dos colaboradores no curso interno
Conhecimento médio por equipe
A equipe com maior média aparece em destaque
Custo total da operação (soma do salário base)
–
Custo da equipe
0
Folha da lista
–Investimento manual
–Custo atual (planilha)
–Buscar colaboradores
Nome, código, cargo ou equipe. Adicione à lista ou remova.
Investimento
Valor manual somado ao custo da equipe
Digite o valor e o custo no topo é atualizado na hora.
Lista do investimento
/**
* ITS BRASIL | Central de Suporte - App Web (Google Apps Script)
* Projeto com 2 arquivos: Código.gs (este) + Index.html
*
* Lê a planilha "Colaboradores Suporte 2026" direto do Drive (aba "Folha Atual"),
* aceita arquivo .xlsx ou Planilha Google, e entrega os dados ao painel.
* O painel consulta a cada 30 s e só baixa de novo quando o arquivo é modificado.
*/
const CONFIG = {
FILE_ID: '1fXc-Gvm2B9_qWEc_xZ-auUX_6zpPlCVn', // arquivo no Drive
ABA: 'Folha Atual', // nome da aba
CACHE_SEGUNDOS: 600
};
/** Entrada do app web. */
function doGet() {
return HtmlService.createHtmlOutputFromFile('Index')
.setTitle('ITS BRASIL | Central de Suporte')
.addMetaTag('viewport', 'width=device-width, initial-scale=1')
.setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
}
/**
* Chamada pelo painel (google.script.run).
* Se a versão do cliente for a mesma do arquivo, responde sem reler nada.
*/
function getColaboradores(versaoCliente) {
const arquivo = DriveApp.getFileById(CONFIG.FILE_ID);
const versao = String(arquivo.getLastUpdated().getTime());
if (versaoCliente && String(versaoCliente) === versao) {
return { unchanged: true, version: versao };
}
const cache = CacheService.getScriptCache();
const chave = 'colab_' + versao;
const guardado = cache.get(chave);
if (guardado) return JSON.parse(guardado);
const rows = paraObjetos_(lerArquivo_(arquivo));
const total = rows.filter(function (r) { return String(r['CARGO'] || '').indexOf('CUSTO TOTAL') === 0; })[0];
const resposta = { version: versao, rows: rows, custo: total ? total['SALÁRIO BASE'] : '' };
try { cache.put(chave, JSON.stringify(resposta), CONFIG.CACHE_SEGUNDOS); } catch (e) {}
return resposta;
}
/** Informações da fonte, usadas pelo botão "Fonte" do painel. */
function getFonte() {
const f = DriveApp.getFileById(CONFIG.FILE_ID);
return {
nome: f.getName(),
atualizado: Utilities.formatDate(f.getLastUpdated(), Session.getScriptTimeZone(), 'dd/MM/yyyy HH:mm')
};
}
/** Execute uma vez no editor para autorizar o acesso ao Drive e testar a leitura. */
function testarLeitura() {
const r = getColaboradores();
Logger.log(r.rows.length + ' colaboradores lidos. Primeiro: ' + r.rows[0].NOME);
}
/* ---------------- leitura do arquivo ---------------- */
function lerArquivo_(arquivo) {
if (arquivo.getMimeType() === MimeType.GOOGLE_SHEETS) {
const ss = SpreadsheetApp.openById(arquivo.getId());
const aba = ss.getSheetByName(CONFIG.ABA) || ss.getSheets()[0];
return aba.getDataRange().getDisplayValues();
}
return lerXlsx_(arquivo.getBlob());
}
/** Lê um .xlsx sem serviços avançados: descompacta e interpreta o XML. */
function lerXlsx_(blob) {
const NS = XmlService.getNamespace('http://schemas.openxmlformats.org/spreadsheetml/2006/main');
const NSR = XmlService.getNamespace('r', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships');
const partes = {};
Utilities.unzip(blob.setContentType('application/zip')).forEach(function (p) { partes[p.getName()] = p; });
const xml = function (nome) { return XmlService.parse(partes[nome].getDataAsString('UTF-8')).getRootElement(); };
const textos = [];
if (partes['xl/sharedStrings.xml']) {
xml('xl/sharedStrings.xml').getChildren('si', NS).forEach(function (si) { textos.push(textoSi_(si, NS)); });
}
const abas = xml('xl/workbook.xml').getChild('sheets', NS).getChildren('sheet', NS);
const alvo = abas.filter(function (a) { return a.getAttribute('name').getValue() === CONFIG.ABA; })[0] || abas[0];
const rid = alvo.getAttribute('id', NSR).getValue();
const rel = xml('xl/_rels/workbook.xml.rels').getChildren().filter(function (r) {
return r.getAttribute('Id').getValue() === rid;
})[0];
let caminho = rel.getAttribute('Target').getValue();
caminho = caminho.charAt(0) === '/' ? caminho.slice(1) : 'xl/' + caminho;
const linhas = [];
xml(caminho).getChild('sheetData', NS).getChildren('row', NS).forEach(function (row) {
const celulas = [];
row.getChildren('c', NS).forEach(function (c) {
const col = indiceColuna_(c.getAttribute('r').getValue());
const tipo = c.getAttribute('t') ? c.getAttribute('t').getValue() : '';
const v = c.getChild('v', NS);
let valor = '';
if (tipo === 's' && v) valor = textos[parseInt(v.getText(), 10)];
else if (tipo === 'inlineStr') valor = textoSi_(c.getChild('is', NS), NS);
else if (v) valor = v.getText();
celulas[col] = valor;
});
linhas[parseInt(row.getAttribute('r').getValue(), 10) - 1] = celulas;
});
return linhas;
}
function textoSi_(si, NS) {
if (!si) return '';
const t = si.getChild('t', NS);
if (t) return t.getText();
return si.getChildren('r', NS).map(function (r) {
const x = r.getChild('t', NS);
return x ? x.getText() : '';
}).join('');
}
function indiceColuna_(ref) {
const letras = ref.replace(/[0-9]/g, '');
let n = 0;
for (let i = 0; i < letras.length; i++) n = n * 26 + (letras.charCodeAt(i) - 64);
return n - 1;
}
/** Primeira linha = cabeçalho. Devolve objetos { COD, NOME, 'ADMISSÃO', ..., Mikrotik, ... }. */
function paraObjetos_(linhas) {
const cheias = [];
for (let i = 0; i < linhas.length; i++) if (linhas[i]) cheias.push(linhas[i]);
const cab = [];
for (let i = 0; i < cheias[0].length; i++) cab.push(String(cheias[0][i] == null ? '' : cheias[0][i]).trim());
const saida = [];
for (let r = 1; r < cheias.length; r++) {
const linha = cheias[r], obj = {};
let vazio = true;
for (let i = 0; i < cab.length; i++) {
if (!cab[i]) continue;
let v = linha[i] == null ? '' : String(linha[i]).trim();
if (cab[i].indexOf('ADMISS') === 0 && /^\d{5}(\.\d+)?$/.test(v)) v = serialParaData_(parseFloat(v));
if (v) vazio = false;
obj[cab[i]] = v;
}
if (!vazio) saida.push(obj);
}
return saida;
}
function serialParaData_(n) {
const d = new Date(Math.round((n - 25569) * 86400000));
return Utilities.formatDate(d, 'UTC', 'dd/MM/yyyy');
}