import { useMemo, useState } from "react"; import type { Meta } from "./api"; import { useApi } from "./api"; import { COLOR_VEREDICTO, CuadrantesGestion, ETIQUETA_VEREDICTO, SerieGestion, nf, } from "./charts"; const NOMBRE_MES = [ "enero", "febrero", "marzo", "abril", "mayo", "junio", "julio", "agosto", "setiembre", "octubre", "noviembre", "diciembre", ]; const mesLegible = (p: string) => { const [a, m] = p.split("-"); return `${NOMBRE_MES[+m - 1]} ${a}`; }; /** * Traduce el veredicto a una frase con los tres números que lo sostienen. * Sin esto, un territorio que bajó de verdad puede parecer que subió, porque * el promedio del periodo heredado arranca bajo y el de la gestión arranca ya * en el pico. */ /** Una tendencia no significativa se muestra como ≈0: su valor crudo no es distinguible de cero, y enseñarlo contradice la etiqueta "plano". */ function tendTxt(t: any): string { if (t?.pct == null) return "—"; if (t.direccion === "estable") return "≈0%"; return `${t.pct > 0 ? "+" : ""}${Math.round(t.pct)}%`; } const DIRECCION: Record = { sube: "al alza", baja: "a la baja", estable: "plano", "sin datos": "sin datos suficientes", }; function explicar(t: any): string { if (t.recibido == null || t.actual == null || t.contrafactual == null) return ""; const d1 = DIRECCION[t.tend1.direccion] ?? t.tend1.direccion; const d2 = DIRECCION[t.tend2.direccion] ?? t.tend2.direccion; const pct1 = t.tend1.direccion === "estable" ? "" : ` (${tendTxt(t.tend1)} al año)`; const pct2 = t.tend2.direccion === "estable" ? "" : ` (${tendTxt(t.tend2)} al año)`; const verbo = t.actual < t.recibido ? "bajó a" : t.actual > t.recibido ? "subió a" : "se mantuvo en"; const base = `Heredó el territorio ${d1}${pct1}: en 2022 hubo ${t.recibido} homicidios. ` + `En los últimos 12 meses ${verbo} ${t.actual}, y la tendencia actual es ${d2}${pct2}.`; if (t.contrafactual > t.actual) return `${base} De haber seguido la tendencia que recibió, hoy serían ${t.contrafactual}: son ${t.contrafactual - t.actual} menos.`; if (t.contrafactual < t.actual) return `${base} Con esa misma tendencia habrían sido ${t.contrafactual}: son ${t.actual - t.contrafactual} más.`; return base; } /* Orden de lectura: primero lo que revirtió el alza, al final lo que la aceleró. */ const ORDEN = ["bajo", "freno", "sigue", "empeoro", "disparo"]; export default function Gestion({ meta }: { meta: Meta }) { const [nivel, setNivel] = useState<"departamento" | "provincia" | "distrito">("departamento"); const [tipo, setTipo] = useState("HOMICIDIO"); const [sexo, setSexo] = useState(""); const [lugar, setLugar] = useState(""); const [p1d, setP1d] = useState("2017-01"); const [p1h, setP1h] = useState("2022-12"); const [p2d, setP2d] = useState("2023-01"); const [minimo, setMinimo] = useState(10); const [sel, setSel] = useState(null); const [filtroVeredicto, setFiltroVeredicto] = useState(null); const p = new URLSearchParams({ nivel, p1_desde: p1d, p1_hasta: p1h, p2_desde: p2d, minimo: String(minimo), }); for (const [k, v] of Object.entries({ tipo, sexo, lugar })) if (v) p.set(k, v); const { data, error } = useApi(`/api/gestion?${p}`); const lista = useMemo(() => { if (!data) return []; const t = filtroVeredicto ? data.territorios.filter((x: any) => x.veredicto === filtroVeredicto) : data.territorios; return [...t].sort( (a, b) => ORDEN.indexOf(a.veredicto) - ORDEN.indexOf(b.veredicto) || b.n2 - a.n2 ); }, [data, filtroVeredicto]); const resumen = useMemo(() => { if (!data) return {}; const c: Record = {}; for (const t of data.territorios) c[t.veredicto] = (c[t.veredicto] ?? 0) + 1; return c; }, [data]); const detalle = useMemo( () => data?.territorios.find((t: any) => t.id === sel) ?? null, [data, sel] ); return ( <>
setP1d(e.target.value)} style={{ minWidth: 88 }} /> setP1h(e.target.value)} style={{ minWidth: 88 }} />
setP2d(e.target.value)} style={{ minWidth: 88 }} />
{error &&
{String(error)}
} {!data && !error &&
Calculando…
} {data && ( <>

{data.total} {nivel === "departamento" ? "regiones" : nivel === "provincia" ? "provincias" : "distritos"} · heredado{" "} {mesLegible(data.periodo1.desde)}–{mesLegible(data.periodo1.hasta)} · gestión{" "} {mesLegible(data.periodo2.desde)}–{mesLegible(data.periodo2.hasta)}

Haz clic en un veredicto para filtrar la tabla, o en un punto del gráfico para ver su serie completa.

{ORDEN.filter((v) => resumen[v]).map((v) => ( ))}

Qué recibió y qué dejó cada territorio

Eje horizontal: la tendencia del periodo heredado. Eje vertical: la de su gestión. El tamaño del punto es el volumen de casos. El cuadrante verde (derecha abajo) es el que importa: recibió subiendo y lo dejó bajando.

setSel(t)} />
{detalle && (

{detalle.territorio} {detalle.padre ? ` · ${detalle.padre}` : ""}

Línea gris punteada: tendencia del periodo heredado. Línea naranja: tendencia de la gestión actual.

{explicar(detalle)}

Lo heredó {DIRECCION[detalle.tend1.direccion]} {nf.format(detalle.recibido ?? 0)} casos en 2022 · tendencia previa {tendTxt(detalle.tend1)}/año
Últimos 12 meses · {DIRECCION[detalle.tend2.direccion]} 0 ? "up" : ""}`}> {nf.format(detalle.actual ?? 0)} {detalle.var_real >= 0 ? "+" : ""}{detalle.var_real}% vs 2022
Si nada hubiera cambiado {nf.format(detalle.contrafactual ?? 0)} {detalle.vs_contraf >= 0 ? "+" : ""}{detalle.vs_contraf}% vs ese escenario
Tendencia heredada {detalle.tend1.pct >= 0 ? "+" : ""}{Math.round(detalle.tend1.pct)}%/año {detalle.tend1.direccion}
Tendencia de su gestión {detalle.tend2.pct >= 0 ? "+" : ""}{Math.round(detalle.tend2.pct)}%/año {detalle.tend2.direccion}
Veredicto {ETIQUETA_VEREDICTO[detalle.veredicto]} {nf.format(detalle.n1)} → {nf.format(detalle.n2)} casos
)}

Tabla comparativa

2022 son los homicidios de ese año completo; últimos 12 meses, los del año móvil más reciente ya cerrado. Ambas ventanas son de doce meses, así que la estacionalidad se cancela y las cifras se pueden verificar una a una contra el registro. Si nada cambiaba proyecta el nivel de 2022 con la tendencia previa. El veredicto compara los últimos 12 meses contra esa proyección: por debajo del 85% es «frenó el alza» —o «bajó» si además la tendencia actual es descendente—; entre 85% y 115%, «sin cambio»; por encima de 115%, «empeoró», y pasado el 150%, «se disparó».

{nivel !== "departamento" && } {lista.map((t: any) => ( setSel(t.id)} style={{ cursor: "pointer" }} > {nivel !== "departamento" && ( )} ))}
Territorio{nivel === "provincia" ? "Departamento" : "Provincia · Departamento"}Lo heredó2022 Últimos 12m Si nada cambiabaVar. real Veredicto
{t.territorio}{t.padre} {DIRECCION[t.tend1.direccion] ?? "—"} {t.recibido == null ? "—" : nf.format(t.recibido)} {t.actual == null ? "—" : nf.format(t.actual)} {t.contrafactual == null ? "—" : nf.format(t.contrafactual)} 0 ? "var(--critical)" : "var(--good)" }}> {t.var_real == null ? "—" : `${t.var_real > 0 ? "+" : ""}${t.var_real}%`} {ETIQUETA_VEREDICTO[t.veredicto]} {/* La marca viene del backend: se calcula sobre el menor de los dos extremos, no solo sobre el nivel actual. */} {t.volumen_bajo && ( volumen bajo )}

La Policía Nacional depende del Ministerio del Interior, no de alcaldes ni gobernadores. Estos indicadores describen cómo evolucionó la violencia en cada territorio durante un periodo de gestión; no atribuyen causalidad a la autoridad local. Lima se separa en Lima Metropolitana —cuyo alcalde ejerce además funciones regionales— y Lima Provincias, que tiene gobernador propio; el Callao es región y provincia a la vez.

)} ); }