+server.js 4.55 KB
const API_BASE = 'http://136.112.29.74/api/rubro';

let cachedClasificador = null;
let cachedEstados = null;
let cachedTimestamp = 0;
const CACHE_TTL = 5 * 60 * 1000;

function getParent(code, nivel) {
  const s = String(code).padStart(5, '0');
  if (nivel === 'tipo') return null;
  // clase (ABC00) → tipo (AB000)
  if (nivel === 'clase') return s.substring(0, 2) + '000';
  // cuenta (ABCD0) → clase (ABC00)
  if (nivel === 'cuenta') return s.substring(0, 3) + '00';
  // sub_cuenta (ABCDE) → cuenta (ABCD0)
  if (nivel === 'sub_cuenta') return s.substring(0, 4) + '0';
  return null;
}

async function loadAndCacheAll() {
  const now = Date.now();
  if (cachedEstados && (now - cachedTimestamp) < CACHE_TTL) return;

  const clasRes = await fetch(`${API_BASE}/clasificador`);
  if (!clasRes.ok) throw new Error('Failed to fetch clasificador');
  cachedClasificador = await clasRes.json();

  const estados = new Map();
  const batchSize = 80;

  for (let i = 0; i < cachedClasificador.length; i += batchSize) {
    const batch = cachedClasificador.slice(i, i + batchSize);
    const results = await Promise.all(batch.map(async (item) => {
      try {
        const code = item.rubro;
        const res = await fetch(`${API_BASE}/${code}`);
        if (!res.ok) return null;
        const data = await res.json();
        return { code: String(code), estados: data.estados || [] };
      } catch { return null; }
    }));
    results.filter(Boolean).forEach(r => estados.set(r.code, r.estados));
  }

  cachedEstados = estados;
  cachedTimestamp = now;
}

export async function GET({ url }) {
  const gestion = parseInt(url.searchParams.get('gestion') || '2025');
  const entidad = url.searchParams.get('entidad') || '0';

  try {
    if (entidad === '0') {
      await loadAndCacheAll();

      const results = [];
      for (const item of cachedClasificador) {
        const code = String(item.rubro);
        const estados = cachedEstados.get(code);
        if (!estados) continue;
        const yearData = estados.find(d => d.gestion === gestion);
        if (!yearData || !yearData.total) continue;
        results.push({
          gestion,
          nivel: item.nivel || yearData.nivel || '',
          rubro: code,
          desc_rubro: item.desc_rubro || '',
          parent: getParent(code, item.nivel || yearData.nivel),
          devengado: yearData.total
        });
      }

      // Add missing parent nodes (tipos without estados)
      const ids = new Set(results.map(r => r.rubro));
      const missingParents = new Set();
      results.forEach(r => {
        if (r.parent && !ids.has(r.parent)) missingParents.add(r.parent);
      });
      for (const parentCode of missingParents) {
        const item = cachedClasificador.find(c => String(c.rubro) === parentCode);
        results.push({
          gestion,
          nivel: item?.nivel || 'tipo',
          rubro: parentCode,
          desc_rubro: item?.desc_rubro || parentCode,
          parent: getParent(parentCode, item?.nivel || 'tipo'),
          devengado: 0
        });
      }

      return new Response(JSON.stringify(results), {
        headers: { 'Content-Type': 'application/json' }
      });
    } else {
      if (!cachedClasificador) {
        const clasRes = await fetch(`${API_BASE}/clasificador`);
        cachedClasificador = await clasRes.json();
      }

      const results = [];
      const batchSize = 80;

      for (let i = 0; i < cachedClasificador.length; i += batchSize) {
        const batch = cachedClasificador.slice(i, i + batchSize);
        const batchResults = await Promise.all(batch.map(async (item) => {
          try {
            const code = String(item.rubro);
            const res = await fetch(`${API_BASE}/${code}/entidades/${entidad}`);
            if (!res.ok) return null;
            const data = await res.json();
            const yearData = data.find(d => d.gestion === gestion);
            if (!yearData || !yearData.monto) return null;
            return {
              gestion,
              nivel: item.nivel || '',
              rubro: code,
              desc_rubro: item.desc_rubro || '',
              parent: getParent(code, item.nivel),
              devengado: yearData.monto
            };
          } catch { return null; }
        }));
        results.push(...batchResults.filter(Boolean));
      }

      return new Response(JSON.stringify(results), {
        headers: { 'Content-Type': 'application/json' }
      });
    }
  } catch (err) {
    return new Response(JSON.stringify({ error: 'Failed to build treemap' }), {
      status: 500, headers: { 'Content-Type': 'application/json' }
    });
  }
}