@cerca.red/mapa 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/README.md +133 -0
  2. package/dist/capas.d.ts +29 -0
  3. package/dist/capas.js +489 -0
  4. package/dist/capas.js.map +1 -0
  5. package/dist/doble/estiloFalso.d.ts +19 -0
  6. package/dist/doble/estiloFalso.js +33 -0
  7. package/dist/doble/estiloFalso.js.map +1 -0
  8. package/dist/doble/index.d.ts +11 -0
  9. package/dist/doble/index.js +12 -0
  10. package/dist/doble/index.js.map +1 -0
  11. package/dist/doble/mapaFalso.d.ts +75 -0
  12. package/dist/doble/mapaFalso.js +535 -0
  13. package/dist/doble/mapaFalso.js.map +1 -0
  14. package/dist/encuadre.d.ts +64 -0
  15. package/dist/encuadre.js +83 -0
  16. package/dist/encuadre.js.map +1 -0
  17. package/dist/estilo.d.ts +88 -0
  18. package/dist/estilo.js +23 -0
  19. package/dist/estilo.js.map +1 -0
  20. package/dist/eventos.d.ts +127 -0
  21. package/dist/eventos.js +69 -0
  22. package/dist/eventos.js.map +1 -0
  23. package/dist/identificadores.d.ts +109 -0
  24. package/dist/identificadores.js +104 -0
  25. package/dist/identificadores.js.map +1 -0
  26. package/dist/index.d.ts +37 -0
  27. package/dist/index.js +33 -0
  28. package/dist/index.js.map +1 -0
  29. package/dist/manifiesto.d.ts +97 -0
  30. package/dist/manifiesto.js +156 -0
  31. package/dist/manifiesto.js.map +1 -0
  32. package/dist/modelo.d.ts +154 -0
  33. package/dist/modelo.js +227 -0
  34. package/dist/modelo.js.map +1 -0
  35. package/dist/navegador/index.d.ts +70 -0
  36. package/dist/navegador/index.js +132 -0
  37. package/dist/navegador/index.js.map +1 -0
  38. package/dist/navegador/maplibre.d.ts +41 -0
  39. package/dist/navegador/maplibre.js +463 -0
  40. package/dist/navegador/maplibre.js.map +1 -0
  41. package/dist/navegador/protocolo.d.ts +4 -0
  42. package/dist/navegador/protocolo.js +55 -0
  43. package/dist/navegador/protocolo.js.map +1 -0
  44. package/dist/navegador/webgl.d.ts +24 -0
  45. package/dist/navegador/webgl.js +47 -0
  46. package/dist/navegador/webgl.js.map +1 -0
  47. package/dist/navegador/worker.d.ts +33 -0
  48. package/dist/navegador/worker.js +47 -0
  49. package/dist/navegador/worker.js.map +1 -0
  50. package/dist/paleta.d.ts +53 -0
  51. package/dist/paleta.js +53 -0
  52. package/dist/paleta.js.map +1 -0
  53. package/dist/proyecciones.d.ts +160 -0
  54. package/dist/proyecciones.js +2 -0
  55. package/dist/proyecciones.js.map +1 -0
  56. package/dist/puerto.d.ts +244 -0
  57. package/dist/puerto.js +2 -0
  58. package/dist/puerto.js.map +1 -0
  59. package/dist/sdk.d.ts +111 -0
  60. package/dist/sdk.js +552 -0
  61. package/dist/sdk.js.map +1 -0
  62. package/dist/sincronizacion.d.ts +40 -0
  63. package/dist/sincronizacion.js +144 -0
  64. package/dist/sincronizacion.js.map +1 -0
  65. package/dist/worker-de-maplibre.d.ts +36 -0
  66. package/dist/worker-de-maplibre.js +37 -0
  67. package/dist/worker-de-maplibre.js.map +1 -0
  68. package/package.json +59 -0
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Los identificadores de todo lo que este SDK mete en un estilo.
3
+ *
4
+ * Están aquí, en un solo archivo y como constantes, por una razón práctica:
5
+ * un identificador de capa escrito a mano en dos sitios es un `quitarCapa`
6
+ * que no quita nada, y `quitarCapa` de algo que no existe **no falla en
7
+ * MapLibre**. La capa se queda dibujada para siempre y nadie sabe por qué.
8
+ *
9
+ * El prefijo `cerca-` está para que se distingan a simple vista de las capas
10
+ * del mapa base, que vienen de Protomaps y no se tocan nunca.
11
+ */
12
+ /** Las fuentes de datos que el SDK crea y mantiene. */
13
+ export const FUENTES = {
14
+ negocios: "cerca-negocios",
15
+ /** Agregados entregados por el backend, nunca clusterizados por MapLibre. */
16
+ grupos: "cerca-grupos",
17
+ sedes: "cerca-sedes",
18
+ /**
19
+ * La selección va en su **propia fuente** y no en un estado de la fuente de
20
+ * negocios. No es duplicación: la clave de ordenación de símbolos de
21
+ * MapLibre no lee el estado de entidad, así que un negocio marcado como
22
+ * seleccionado seguiría dibujándose debajo de sus vecinos. Una fuente
23
+ * aparte, en una capa por encima, es la única forma de que lo elegido se
24
+ * vea encima de todo.
25
+ */
26
+ seleccion: "cerca-seleccion",
27
+ /** El halo de las ubicaciones aproximadas. Área, no alfiler. */
28
+ imprecision: "cerca-imprecision",
29
+ ubicacion: "cerca-ubicacion",
30
+ vehiculos: "cerca-vehiculos",
31
+ rutas: "cerca-rutas",
32
+ zonas: "cerca-zonas",
33
+ isocronas: "cerca-isocronas",
34
+ };
35
+ /** Las capas que el SDK dibuja, de abajo hacia arriba dentro de cada anclaje. */
36
+ export const CAPAS = {
37
+ isocronas: "cerca-isocronas-relleno",
38
+ isocronasBorde: "cerca-isocronas-borde",
39
+ zonas: "cerca-zonas-relleno",
40
+ zonasBorde: "cerca-zonas-borde",
41
+ imprecision: "cerca-imprecision-relleno",
42
+ imprecisionBorde: "cerca-imprecision-borde",
43
+ rutasAlternativas: "cerca-rutas-alternativas",
44
+ rutaPrincipalContorno: "cerca-ruta-principal-contorno",
45
+ rutaPrincipal: "cerca-ruta-principal",
46
+ ubicacionHalo: "cerca-ubicacion-halo",
47
+ ubicacionPunto: "cerca-ubicacion-punto",
48
+ gruposCirculo: "cerca-grupos-circulo",
49
+ gruposCantidad: "cerca-grupos-cantidad",
50
+ gruposToque: "cerca-grupos-toque",
51
+ gruposServidorCirculo: "cerca-grupos-servidor-circulo",
52
+ gruposServidorCantidad: "cerca-grupos-servidor-cantidad",
53
+ gruposServidorToque: "cerca-grupos-servidor-toque",
54
+ negociosPunto: "cerca-negocios-punto",
55
+ negociosSimbolo: "cerca-negocios-simbolo",
56
+ sedesPunto: "cerca-sedes-punto",
57
+ vehiculos: "cerca-vehiculos-simbolo",
58
+ seleccion: "cerca-seleccion-punto",
59
+ seleccionArea: "cerca-seleccion-area",
60
+ /**
61
+ * Las capas invisibles que reciben los toques.
62
+ *
63
+ * Un alfiler de 9 px de radio se ve bien y **no se puede tocar** en un
64
+ * teléfono: CLAUDE.md §1 pide objetivos de 44 px, o sea 22 de radio.
65
+ * Agrandar el círculo pintado sería taparlo todo, así que encima va otro
66
+ * círculo del tamaño tocable con opacidad cero.
67
+ *
68
+ * Funciona porque la consulta de entidades dibujadas de MapLibre lee el
69
+ * índice espacial y **sí devuelve capas con opacidad cero** — lo que no
70
+ * devuelve es capas con la visibilidad apagada, que es una cosa distinta.
71
+ * Es la diferencia entre «transparente» y «no está».
72
+ */
73
+ negociosToque: "cerca-negocios-toque",
74
+ sedesToque: "cerca-sedes-toque",
75
+ vehiculosToque: "cerca-vehiculos-toque",
76
+ rutasToque: "cerca-rutas-toque",
77
+ };
78
+ /**
79
+ * Los puntos de inserción que el estilo base deja preparados.
80
+ *
81
+ * Son capas vacías con identificador estable que solo existen para tener
82
+ * dónde insertar. La alternativa —referenciar una capa concreta de Protomaps,
83
+ * como `roads_labels_minor`— se rompe en cada actualización del upstream, y
84
+ * se rompe en silencio: `agregarCapa` con un `antesDe` que no existe lanza en
85
+ * MapLibre, pero un identificador que cambió de significado no lanza nada,
86
+ * solo deja la capa en el sitio equivocado.
87
+ *
88
+ * El orden, de abajo hacia arriba: mapa base sin rótulos → **áreas** →
89
+ * **líneas** → rótulos del mapa base → **puntos** → **selección**.
90
+ *
91
+ * Los rótulos del mapa base van en medio a propósito: el nombre de la calle
92
+ * se tiene que seguir leyendo por debajo de un polígono de cobertura, y los
93
+ * alfileres tienen que quedar por encima de todo. Un mapa que tapa los
94
+ * nombres de las calles no sirve para lo único que se le pide.
95
+ */
96
+ export const ANCLAS_POR_DEFECTO = {
97
+ areas: "cerca-ancla-areas",
98
+ lineas: "cerca-ancla-lineas",
99
+ puntos: "cerca-ancla-puntos",
100
+ seleccion: "cerca-ancla-seleccion",
101
+ };
102
+ /** El radio en píxeles que hace que un objetivo mida los 44 px que pide CLAUDE.md §1. */
103
+ export const RADIO_TACTIL_MINIMO_EN_PX = 22;
104
+ //# sourceMappingURL=identificadores.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"identificadores.js","sourceRoot":"","sources":["../src/identificadores.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,uDAAuD;AACvD,MAAM,CAAC,MAAM,OAAO,GAAG;IACrB,QAAQ,EAAE,gBAAgB;IAC1B,6EAA6E;IAC7E,MAAM,EAAE,cAAc;IACtB,KAAK,EAAE,aAAa;IACpB;;;;;;;OAOG;IACH,SAAS,EAAE,iBAAiB;IAC5B,gEAAgE;IAChE,WAAW,EAAE,mBAAmB;IAChC,SAAS,EAAE,iBAAiB;IAC5B,SAAS,EAAE,iBAAiB;IAC5B,KAAK,EAAE,aAAa;IACpB,KAAK,EAAE,aAAa;IACpB,SAAS,EAAE,iBAAiB;CACpB,CAAC;AAEX,iFAAiF;AACjF,MAAM,CAAC,MAAM,KAAK,GAAG;IACnB,SAAS,EAAE,yBAAyB;IACpC,cAAc,EAAE,uBAAuB;IACvC,KAAK,EAAE,qBAAqB;IAC5B,UAAU,EAAE,mBAAmB;IAC/B,WAAW,EAAE,2BAA2B;IACxC,gBAAgB,EAAE,yBAAyB;IAC3C,iBAAiB,EAAE,0BAA0B;IAC7C,qBAAqB,EAAE,+BAA+B;IACtD,aAAa,EAAE,sBAAsB;IACrC,aAAa,EAAE,sBAAsB;IACrC,cAAc,EAAE,uBAAuB;IACvC,aAAa,EAAE,sBAAsB;IACrC,cAAc,EAAE,uBAAuB;IACvC,WAAW,EAAE,oBAAoB;IACjC,qBAAqB,EAAE,+BAA+B;IACtD,sBAAsB,EAAE,gCAAgC;IACxD,mBAAmB,EAAE,6BAA6B;IAClD,aAAa,EAAE,sBAAsB;IACrC,eAAe,EAAE,wBAAwB;IACzC,UAAU,EAAE,mBAAmB;IAC/B,SAAS,EAAE,yBAAyB;IACpC,SAAS,EAAE,uBAAuB;IAClC,aAAa,EAAE,sBAAsB;IACrC;;;;;;;;;;;;OAYG;IACH,aAAa,EAAE,sBAAsB;IACrC,UAAU,EAAE,mBAAmB;IAC/B,cAAc,EAAE,uBAAuB;IACvC,UAAU,EAAE,mBAAmB;CACvB,CAAC;AAEX;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG;IAChC,KAAK,EAAE,mBAAmB;IAC1B,MAAM,EAAE,oBAAoB;IAC5B,MAAM,EAAE,oBAAoB;IAC5B,SAAS,EAAE,uBAAuB;CAC1B,CAAC;AASX,yFAAyF;AACzF,MAAM,CAAC,MAAM,yBAAyB,GAAG,EAAE,CAAC","sourcesContent":["/**\n * Los identificadores de todo lo que este SDK mete en un estilo.\n *\n * Están aquí, en un solo archivo y como constantes, por una razón práctica:\n * un identificador de capa escrito a mano en dos sitios es un `quitarCapa`\n * que no quita nada, y `quitarCapa` de algo que no existe **no falla en\n * MapLibre**. La capa se queda dibujada para siempre y nadie sabe por qué.\n *\n * El prefijo `cerca-` está para que se distingan a simple vista de las capas\n * del mapa base, que vienen de Protomaps y no se tocan nunca.\n */\n\n/** Las fuentes de datos que el SDK crea y mantiene. */\nexport const FUENTES = {\n negocios: \"cerca-negocios\",\n /** Agregados entregados por el backend, nunca clusterizados por MapLibre. */\n grupos: \"cerca-grupos\",\n sedes: \"cerca-sedes\",\n /**\n * La selección va en su **propia fuente** y no en un estado de la fuente de\n * negocios. No es duplicación: la clave de ordenación de símbolos de\n * MapLibre no lee el estado de entidad, así que un negocio marcado como\n * seleccionado seguiría dibujándose debajo de sus vecinos. Una fuente\n * aparte, en una capa por encima, es la única forma de que lo elegido se\n * vea encima de todo.\n */\n seleccion: \"cerca-seleccion\",\n /** El halo de las ubicaciones aproximadas. Área, no alfiler. */\n imprecision: \"cerca-imprecision\",\n ubicacion: \"cerca-ubicacion\",\n vehiculos: \"cerca-vehiculos\",\n rutas: \"cerca-rutas\",\n zonas: \"cerca-zonas\",\n isocronas: \"cerca-isocronas\",\n} as const;\n\n/** Las capas que el SDK dibuja, de abajo hacia arriba dentro de cada anclaje. */\nexport const CAPAS = {\n isocronas: \"cerca-isocronas-relleno\",\n isocronasBorde: \"cerca-isocronas-borde\",\n zonas: \"cerca-zonas-relleno\",\n zonasBorde: \"cerca-zonas-borde\",\n imprecision: \"cerca-imprecision-relleno\",\n imprecisionBorde: \"cerca-imprecision-borde\",\n rutasAlternativas: \"cerca-rutas-alternativas\",\n rutaPrincipalContorno: \"cerca-ruta-principal-contorno\",\n rutaPrincipal: \"cerca-ruta-principal\",\n ubicacionHalo: \"cerca-ubicacion-halo\",\n ubicacionPunto: \"cerca-ubicacion-punto\",\n gruposCirculo: \"cerca-grupos-circulo\",\n gruposCantidad: \"cerca-grupos-cantidad\",\n gruposToque: \"cerca-grupos-toque\",\n gruposServidorCirculo: \"cerca-grupos-servidor-circulo\",\n gruposServidorCantidad: \"cerca-grupos-servidor-cantidad\",\n gruposServidorToque: \"cerca-grupos-servidor-toque\",\n negociosPunto: \"cerca-negocios-punto\",\n negociosSimbolo: \"cerca-negocios-simbolo\",\n sedesPunto: \"cerca-sedes-punto\",\n vehiculos: \"cerca-vehiculos-simbolo\",\n seleccion: \"cerca-seleccion-punto\",\n seleccionArea: \"cerca-seleccion-area\",\n /**\n * Las capas invisibles que reciben los toques.\n *\n * Un alfiler de 9 px de radio se ve bien y **no se puede tocar** en un\n * teléfono: CLAUDE.md §1 pide objetivos de 44 px, o sea 22 de radio.\n * Agrandar el círculo pintado sería taparlo todo, así que encima va otro\n * círculo del tamaño tocable con opacidad cero.\n *\n * Funciona porque la consulta de entidades dibujadas de MapLibre lee el\n * índice espacial y **sí devuelve capas con opacidad cero** — lo que no\n * devuelve es capas con la visibilidad apagada, que es una cosa distinta.\n * Es la diferencia entre «transparente» y «no está».\n */\n negociosToque: \"cerca-negocios-toque\",\n sedesToque: \"cerca-sedes-toque\",\n vehiculosToque: \"cerca-vehiculos-toque\",\n rutasToque: \"cerca-rutas-toque\",\n} as const;\n\n/**\n * Los puntos de inserción que el estilo base deja preparados.\n *\n * Son capas vacías con identificador estable que solo existen para tener\n * dónde insertar. La alternativa —referenciar una capa concreta de Protomaps,\n * como `roads_labels_minor`— se rompe en cada actualización del upstream, y\n * se rompe en silencio: `agregarCapa` con un `antesDe` que no existe lanza en\n * MapLibre, pero un identificador que cambió de significado no lanza nada,\n * solo deja la capa en el sitio equivocado.\n *\n * El orden, de abajo hacia arriba: mapa base sin rótulos → **áreas** →\n * **líneas** → rótulos del mapa base → **puntos** → **selección**.\n *\n * Los rótulos del mapa base van en medio a propósito: el nombre de la calle\n * se tiene que seguir leyendo por debajo de un polígono de cobertura, y los\n * alfileres tienen que quedar por encima de todo. Un mapa que tapa los\n * nombres de las calles no sirve para lo único que se le pide.\n */\nexport const ANCLAS_POR_DEFECTO = {\n areas: \"cerca-ancla-areas\",\n lineas: \"cerca-ancla-lineas\",\n puntos: \"cerca-ancla-puntos\",\n seleccion: \"cerca-ancla-seleccion\",\n} as const;\n\nexport type Anclas = {\n readonly areas: string;\n readonly lineas: string;\n readonly puntos: string;\n readonly seleccion: string;\n};\n\n/** El radio en píxeles que hace que un objetivo mida los 44 px que pide CLAUDE.md §1. */\nexport const RADIO_TACTIL_MINIMO_EN_PX = 22;\n"]}
@@ -0,0 +1,37 @@
1
+ /**
2
+ * `@cerca.red/mapa` — el SDK del mapa de Cerca, **sin MapLibre**.
3
+ *
4
+ * ## Qué se puede importar desde aquí, y por qué importa
5
+ *
6
+ * Todo lo de este punto de entrada es SSR-seguro: no toca `window`, ni el
7
+ * DOM, ni WebGL, ni `maplibre-gl`. Se puede importar desde un Server
8
+ * Component de Next sobre Cloudflare Workers sin que el build reviente, y se
9
+ * puede importar desde un Worker para calcular una caja o traducir una
10
+ * proyección sin arrastrar 250 KB de motor de mapas.
11
+ *
12
+ * **El mapa de verdad entra por `@cerca.red/mapa/navegador`.** Es una subruta
13
+ * explícita a propósito: importarla se ve en el diff, y quien la escribe sabe
14
+ * que acaba de meter WebGL en ese archivo. Un solo punto de entrada obligaría
15
+ * a rodeos como el que la PWA ya tiene documentado en
16
+ * `Cerca-PWA/scripts/banco-motores.ts` por el `exports` único de `@cerca/ui`,
17
+ * y aquí sería peor, porque lo que se arrastraría es el motor entero.
18
+ *
19
+ * La prueba `sin-dom.test.ts` de este paquete importa este archivo bajo el
20
+ * runtime de Workers y falla si algo de esto deja de ser cierto. Si esa prueba
21
+ * se pone roja, se arregla el import — no la prueba.
22
+ *
23
+ * Y `@cerca.red/mapa/doble` da un motor falso con la geometría de verdad, para
24
+ * que una superficie pruebe sus pantallas sin levantar un contexto WebGL.
25
+ */
26
+ export { type MapaDeCerca, type OpcionesDeCrearMapa, type OpcionesDeSeleccion, type QueEncuadrar, crearMapaConMotor, } from "./sdk.ts";
27
+ export { type BandaDeIsocrona, type EnfasisEnMapa, type GrupoEnMapa, type NegocioEnMapa, type PrecisionDeUbicacion, type RutaEnMapa, type ResultadosEnMapa, type SedeEnMapa, type SimboloDeMapa, type UbicacionDeUsuarioEnMapa, type VehiculoEnMapa, type ZonaEnMapa, } from "./proyecciones.ts";
28
+ export { type CapaDelEstilo, type EspecificacionDeEstilo, type Expresion, type FabricaDeEstilo, type Tema, type TipoDeCapa, } from "./estilo.ts";
29
+ export { type Desuscribir, type DestinoDeCamara, type EntidadDibujada, type EventoDeCamara, type EventoDePuntero, type EventosDeCapa, type EventosNativos, type ImagenDeSimbolo, type MapaNativo, type Margenes, type OpcionesDeEncuadre, type OpcionesDeFuente, type OpcionesDeMovimiento, type OrigenDeMovimiento, type PuntoEnPantalla, type VistaDelMapa, } from "./puerto.ts";
30
+ export { type EventoDeCamaraPublico, type EventoDeClicEnMapa, type EventoDeError, type EventoDeGrupo, type EventoDeMapaListo, type EventoDeNegocio, type EventoDeNegocioEncima, type EventoDeRuta, type EventoDeSede, type EventoDeUbicacion, type EventoDeVehiculo, type EventosDelMapa, type TipoDeEvento, } from "./eventos.ts";
31
+ export { type ModeloDelMapa, cajaDeNegocio, cajaDelModelo, modeloInicial, } from "./modelo.ts";
32
+ export { MARGENES_POR_DEFECTO, ZOOM_DE_UN_PUNTO, type PlanDeEncuadre, planearEncuadre, } from "./encuadre.ts";
33
+ export { type PaletaDelMapa, PALETAS_POR_DEFECTO, resolverPaleta, } from "./paleta.ts";
34
+ export { ANCLAS_POR_DEFECTO, type Anclas, CAPAS, FUENTES, RADIO_TACTIL_MINIMO_EN_PX, } from "./identificadores.ts";
35
+ export { type CapaExtra } from "./sincronizacion.ts";
36
+ export { RUTA_DEL_WORKER_DE_MAPLIBRE, VERSION_DE_MAPLIBRE, } from "./worker-de-maplibre.ts";
37
+ export { type ArchivoDeTeselas, type Manifiesto, ESQUEMA_QUE_SE_ENTIENDE, archivoQueCubre, leerManifiesto, urlDeProtocolo, } from "./manifiesto.ts";
package/dist/index.js ADDED
@@ -0,0 +1,33 @@
1
+ /**
2
+ * `@cerca.red/mapa` — el SDK del mapa de Cerca, **sin MapLibre**.
3
+ *
4
+ * ## Qué se puede importar desde aquí, y por qué importa
5
+ *
6
+ * Todo lo de este punto de entrada es SSR-seguro: no toca `window`, ni el
7
+ * DOM, ni WebGL, ni `maplibre-gl`. Se puede importar desde un Server
8
+ * Component de Next sobre Cloudflare Workers sin que el build reviente, y se
9
+ * puede importar desde un Worker para calcular una caja o traducir una
10
+ * proyección sin arrastrar 250 KB de motor de mapas.
11
+ *
12
+ * **El mapa de verdad entra por `@cerca.red/mapa/navegador`.** Es una subruta
13
+ * explícita a propósito: importarla se ve en el diff, y quien la escribe sabe
14
+ * que acaba de meter WebGL en ese archivo. Un solo punto de entrada obligaría
15
+ * a rodeos como el que la PWA ya tiene documentado en
16
+ * `Cerca-PWA/scripts/banco-motores.ts` por el `exports` único de `@cerca/ui`,
17
+ * y aquí sería peor, porque lo que se arrastraría es el motor entero.
18
+ *
19
+ * La prueba `sin-dom.test.ts` de este paquete importa este archivo bajo el
20
+ * runtime de Workers y falla si algo de esto deja de ser cierto. Si esa prueba
21
+ * se pone roja, se arregla el import — no la prueba.
22
+ *
23
+ * Y `@cerca.red/mapa/doble` da un motor falso con la geometría de verdad, para
24
+ * que una superficie pruebe sus pantallas sin levantar un contexto WebGL.
25
+ */
26
+ export { crearMapaConMotor, } from "./sdk.js";
27
+ export { cajaDeNegocio, cajaDelModelo, modeloInicial, } from "./modelo.js";
28
+ export { MARGENES_POR_DEFECTO, ZOOM_DE_UN_PUNTO, planearEncuadre, } from "./encuadre.js";
29
+ export { PALETAS_POR_DEFECTO, resolverPaleta, } from "./paleta.js";
30
+ export { ANCLAS_POR_DEFECTO, CAPAS, FUENTES, RADIO_TACTIL_MINIMO_EN_PX, } from "./identificadores.js";
31
+ export { RUTA_DEL_WORKER_DE_MAPLIBRE, VERSION_DE_MAPLIBRE, } from "./worker-de-maplibre.js";
32
+ export { ESQUEMA_QUE_SE_ENTIENDE, archivoQueCubre, leerManifiesto, urlDeProtocolo, } from "./manifiesto.js";
33
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,EAKL,iBAAiB,GAClB,MAAM,UAAU,CAAC;AA6DlB,OAAO,EAEL,aAAa,EACb,aAAa,EACb,aAAa,GACd,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,oBAAoB,EACpB,gBAAgB,EAEhB,eAAe,GAChB,MAAM,eAAe,CAAC;AAEvB,OAAO,EAEL,mBAAmB,EACnB,cAAc,GACf,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,kBAAkB,EAElB,KAAK,EACL,OAAO,EACP,yBAAyB,GAC1B,MAAM,sBAAsB,CAAC;AAI9B,OAAO,EACL,2BAA2B,EAC3B,mBAAmB,GACpB,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EAGL,uBAAuB,EACvB,eAAe,EACf,cAAc,EACd,cAAc,GACf,MAAM,iBAAiB,CAAC","sourcesContent":["/**\n * `@cerca.red/mapa` — el SDK del mapa de Cerca, **sin MapLibre**.\n *\n * ## Qué se puede importar desde aquí, y por qué importa\n *\n * Todo lo de este punto de entrada es SSR-seguro: no toca `window`, ni el\n * DOM, ni WebGL, ni `maplibre-gl`. Se puede importar desde un Server\n * Component de Next sobre Cloudflare Workers sin que el build reviente, y se\n * puede importar desde un Worker para calcular una caja o traducir una\n * proyección sin arrastrar 250 KB de motor de mapas.\n *\n * **El mapa de verdad entra por `@cerca.red/mapa/navegador`.** Es una subruta\n * explícita a propósito: importarla se ve en el diff, y quien la escribe sabe\n * que acaba de meter WebGL en ese archivo. Un solo punto de entrada obligaría\n * a rodeos como el que la PWA ya tiene documentado en\n * `Cerca-PWA/scripts/banco-motores.ts` por el `exports` único de `@cerca/ui`,\n * y aquí sería peor, porque lo que se arrastraría es el motor entero.\n *\n * La prueba `sin-dom.test.ts` de este paquete importa este archivo bajo el\n * runtime de Workers y falla si algo de esto deja de ser cierto. Si esa prueba\n * se pone roja, se arregla el import — no la prueba.\n *\n * Y `@cerca.red/mapa/doble` da un motor falso con la geometría de verdad, para\n * que una superficie pruebe sus pantallas sin levantar un contexto WebGL.\n */\n\nexport {\n type MapaDeCerca,\n type OpcionesDeCrearMapa,\n type OpcionesDeSeleccion,\n type QueEncuadrar,\n crearMapaConMotor,\n} from \"./sdk.ts\";\n\nexport {\n type BandaDeIsocrona,\n type EnfasisEnMapa,\n type GrupoEnMapa,\n type NegocioEnMapa,\n type PrecisionDeUbicacion,\n type RutaEnMapa,\n type ResultadosEnMapa,\n type SedeEnMapa,\n type SimboloDeMapa,\n type UbicacionDeUsuarioEnMapa,\n type VehiculoEnMapa,\n type ZonaEnMapa,\n} from \"./proyecciones.ts\";\n\nexport {\n type CapaDelEstilo,\n type EspecificacionDeEstilo,\n type Expresion,\n type FabricaDeEstilo,\n type Tema,\n type TipoDeCapa,\n} from \"./estilo.ts\";\n\nexport {\n type Desuscribir,\n type DestinoDeCamara,\n type EntidadDibujada,\n type EventoDeCamara,\n type EventoDePuntero,\n type EventosDeCapa,\n type EventosNativos,\n type ImagenDeSimbolo,\n type MapaNativo,\n type Margenes,\n type OpcionesDeEncuadre,\n type OpcionesDeFuente,\n type OpcionesDeMovimiento,\n type OrigenDeMovimiento,\n type PuntoEnPantalla,\n type VistaDelMapa,\n} from \"./puerto.ts\";\n\nexport {\n type EventoDeCamaraPublico,\n type EventoDeClicEnMapa,\n type EventoDeError,\n type EventoDeGrupo,\n type EventoDeMapaListo,\n type EventoDeNegocio,\n type EventoDeNegocioEncima,\n type EventoDeRuta,\n type EventoDeSede,\n type EventoDeUbicacion,\n type EventoDeVehiculo,\n type EventosDelMapa,\n type TipoDeEvento,\n} from \"./eventos.ts\";\n\nexport {\n type ModeloDelMapa,\n cajaDeNegocio,\n cajaDelModelo,\n modeloInicial,\n} from \"./modelo.ts\";\n\nexport {\n MARGENES_POR_DEFECTO,\n ZOOM_DE_UN_PUNTO,\n type PlanDeEncuadre,\n planearEncuadre,\n} from \"./encuadre.ts\";\n\nexport {\n type PaletaDelMapa,\n PALETAS_POR_DEFECTO,\n resolverPaleta,\n} from \"./paleta.ts\";\n\nexport {\n ANCLAS_POR_DEFECTO,\n type Anclas,\n CAPAS,\n FUENTES,\n RADIO_TACTIL_MINIMO_EN_PX,\n} from \"./identificadores.ts\";\n\nexport { type CapaExtra } from \"./sincronizacion.ts\";\n\nexport {\n RUTA_DEL_WORKER_DE_MAPLIBRE,\n VERSION_DE_MAPLIBRE,\n} from \"./worker-de-maplibre.ts\";\n\nexport {\n type ArchivoDeTeselas,\n type Manifiesto,\n ESQUEMA_QUE_SE_ENTIENDE,\n archivoQueCubre,\n leerManifiesto,\n urlDeProtocolo,\n} from \"./manifiesto.ts\";\n"]}
@@ -0,0 +1,97 @@
1
+ import { type Caja, type Coordenada } from "@cerca.red/geo";
2
+ /**
3
+ * El manifiesto: de dónde sale la URL de un archivo de teselas.
4
+ *
5
+ * ## La regla que este archivo hace cumplir
6
+ *
7
+ * **La URL de un `.pmtiles` solo sale del manifiesto.** No se escribe en un
8
+ * componente, no se compone con plantillas, no se guarda en una variable de
9
+ * entorno de la superficie. ADR-024 puso URLs **absolutas** dentro del
10
+ * manifiesto exactamente para que quien las lea no tenga que construir nada, y
11
+ * ADR-027 lo llama «el punto de todo el documento».
12
+ *
13
+ * La razón no es orden: es que promover un puntero a una versión anterior
14
+ * **es** el rollback. Basta con que una superficie arme
15
+ * `.../colombia/2026-09-07.pmtiles` a mano para que revertir deje de revertir
16
+ * en esa superficie, y eso no falla — sigue funcionando, con el archivo malo.
17
+ *
18
+ * ## Dos formas, y por qué no son la misma
19
+ *
20
+ * Lo que **entra** es el documento que escribe el pipeline
21
+ * (`guiones/teselas/manifiesto.ts`, esquema 1): un objeto con `archivo` en
22
+ * singular, `zoom.minimo`, `caja`, `licencia.atribucion`. Es su formato y lo
23
+ * decide él; este paquete lo lee, no lo define.
24
+ *
25
+ * Lo que **sale** es una lista de archivos. No es capricho: ADR-024 ya dejó
26
+ * escrito qué pasa el día que Colombia crezca —el país a zoom bajo y archivos
27
+ * por ciudad a zoom alto, «seleccionados por el manifiesto según dónde esté
28
+ * mirando el usuario»—. Un tipo que hoy fuera un objeto suelto obligaría ese
29
+ * día a cambiar cada superficie que lo lee; con la lista de un elemento, el
30
+ * día que el manifiesto traiga dos, aquí no cambia nada.
31
+ *
32
+ * ## Por qué la validación es estricta y ruidosa
33
+ *
34
+ * Porque el manifiesto lo produce otro programa, en otro repositorio y en otro
35
+ * momento. Un documento con un campo renombrado no rompe nada al leerse:
36
+ * produce `undefined`, que se convierte en `"pmtiles://undefined"`, que
37
+ * MapLibre pide y falla como un error de red. La traza dice «no se pudo cargar
38
+ * la fuente» y no dice que el manifiesto cambió de forma.
39
+ *
40
+ * Así que se valida entero, al entrar, con el nombre del campo en el mensaje.
41
+ * Y `manifiesto.test.ts` lee el `cartografia/ejemplo-manifiesto.json` que
42
+ * genera el pipeline de verdad: si el formato cambia ahí, esta prueba se pone
43
+ * roja aquí, que es donde tiene que doler.
44
+ */
45
+ /**
46
+ * La versión de formato que este lector entiende.
47
+ *
48
+ * Un lector viejo tiene que poder decir «esto no lo entiendo» en vez de leer
49
+ * un campo que ahora significa otra cosa. El pipeline sube este número cuando
50
+ * un campo cambia de significado, no cuando añade uno nuevo — así que un
51
+ * manifiesto con esquema mayor se rechaza y uno con campos de más se acepta.
52
+ */
53
+ export declare const ESQUEMA_QUE_SE_ENTIENDE = 1;
54
+ /** Un archivo de teselas publicado, con todo lo que hace falta para pedirlo. */
55
+ export interface ArchivoDeTeselas {
56
+ /** URL absoluta. Se usa tal cual. */
57
+ readonly url: string;
58
+ readonly bytes: number;
59
+ /** La suma de verificación con la que se publicó. */
60
+ readonly sha256: string;
61
+ readonly zoomMinimo: number;
62
+ readonly zoomMaximo: number;
63
+ /** La caja que cubre este archivo. */
64
+ readonly caja: Caja;
65
+ /** Las capas que trae. El estilo espera exactamente estas. */
66
+ readonly capas: readonly string[];
67
+ }
68
+ export interface Manifiesto {
69
+ readonly esquema: number;
70
+ readonly region: string;
71
+ readonly version: string;
72
+ /** Los archivos de esta versión. Hoy siempre uno; ver arriba. */
73
+ readonly archivos: readonly ArchivoDeTeselas[];
74
+ /**
75
+ * La atribución, copiada de dentro del archivo de teselas.
76
+ *
77
+ * Es **texto legal** (CLAUDE.md §11): se muestra tal cual, no se recorta ni
78
+ * se reescribe. Viene de los metadatos del propio `.pmtiles`, que es lo que
79
+ * evita que una superficie se quede sin ella por olvido.
80
+ */
81
+ readonly atribucion: string;
82
+ /** A qué versión apuntaba antes. Existe para poder decir a qué se revirtió. */
83
+ readonly anterior: string | undefined;
84
+ }
85
+ /** La URL con el protocolo puesto, que es lo que espera MapLibre. */
86
+ export declare function urlDeProtocolo(archivo: ArchivoDeTeselas): string;
87
+ /**
88
+ * El archivo que cubre una coordenada, con **el mayor detalle disponible**.
89
+ *
90
+ * Cuando haya un archivo de país y uno de ciudad, los dos contienen el centro
91
+ * de Pereira; el que sirve es el de más zoom. Se elige por `zoomMaximo` y no
92
+ * por el orden de la lista porque el orden es una convención que nadie
93
+ * comprueba.
94
+ */
95
+ export declare function archivoQueCubre(manifiesto: Manifiesto, coordenada: Coordenada): ArchivoDeTeselas | undefined;
96
+ /** Valida un manifiesto recién descargado, o falla diciendo qué le falta. */
97
+ export declare function leerManifiesto(documento: unknown): Manifiesto;
@@ -0,0 +1,156 @@
1
+ import { cajaContiene, esCaja, } from "@cerca.red/geo";
2
+ /**
3
+ * El manifiesto: de dónde sale la URL de un archivo de teselas.
4
+ *
5
+ * ## La regla que este archivo hace cumplir
6
+ *
7
+ * **La URL de un `.pmtiles` solo sale del manifiesto.** No se escribe en un
8
+ * componente, no se compone con plantillas, no se guarda en una variable de
9
+ * entorno de la superficie. ADR-024 puso URLs **absolutas** dentro del
10
+ * manifiesto exactamente para que quien las lea no tenga que construir nada, y
11
+ * ADR-027 lo llama «el punto de todo el documento».
12
+ *
13
+ * La razón no es orden: es que promover un puntero a una versión anterior
14
+ * **es** el rollback. Basta con que una superficie arme
15
+ * `.../colombia/2026-09-07.pmtiles` a mano para que revertir deje de revertir
16
+ * en esa superficie, y eso no falla — sigue funcionando, con el archivo malo.
17
+ *
18
+ * ## Dos formas, y por qué no son la misma
19
+ *
20
+ * Lo que **entra** es el documento que escribe el pipeline
21
+ * (`guiones/teselas/manifiesto.ts`, esquema 1): un objeto con `archivo` en
22
+ * singular, `zoom.minimo`, `caja`, `licencia.atribucion`. Es su formato y lo
23
+ * decide él; este paquete lo lee, no lo define.
24
+ *
25
+ * Lo que **sale** es una lista de archivos. No es capricho: ADR-024 ya dejó
26
+ * escrito qué pasa el día que Colombia crezca —el país a zoom bajo y archivos
27
+ * por ciudad a zoom alto, «seleccionados por el manifiesto según dónde esté
28
+ * mirando el usuario»—. Un tipo que hoy fuera un objeto suelto obligaría ese
29
+ * día a cambiar cada superficie que lo lee; con la lista de un elemento, el
30
+ * día que el manifiesto traiga dos, aquí no cambia nada.
31
+ *
32
+ * ## Por qué la validación es estricta y ruidosa
33
+ *
34
+ * Porque el manifiesto lo produce otro programa, en otro repositorio y en otro
35
+ * momento. Un documento con un campo renombrado no rompe nada al leerse:
36
+ * produce `undefined`, que se convierte en `"pmtiles://undefined"`, que
37
+ * MapLibre pide y falla como un error de red. La traza dice «no se pudo cargar
38
+ * la fuente» y no dice que el manifiesto cambió de forma.
39
+ *
40
+ * Así que se valida entero, al entrar, con el nombre del campo en el mensaje.
41
+ * Y `manifiesto.test.ts` lee el `cartografia/ejemplo-manifiesto.json` que
42
+ * genera el pipeline de verdad: si el formato cambia ahí, esta prueba se pone
43
+ * roja aquí, que es donde tiene que doler.
44
+ */
45
+ /**
46
+ * La versión de formato que este lector entiende.
47
+ *
48
+ * Un lector viejo tiene que poder decir «esto no lo entiendo» en vez de leer
49
+ * un campo que ahora significa otra cosa. El pipeline sube este número cuando
50
+ * un campo cambia de significado, no cuando añade uno nuevo — así que un
51
+ * manifiesto con esquema mayor se rechaza y uno con campos de más se acepta.
52
+ */
53
+ export const ESQUEMA_QUE_SE_ENTIENDE = 1;
54
+ /** La URL con el protocolo puesto, que es lo que espera MapLibre. */
55
+ export function urlDeProtocolo(archivo) {
56
+ return `pmtiles://${archivo.url}`;
57
+ }
58
+ /**
59
+ * El archivo que cubre una coordenada, con **el mayor detalle disponible**.
60
+ *
61
+ * Cuando haya un archivo de país y uno de ciudad, los dos contienen el centro
62
+ * de Pereira; el que sirve es el de más zoom. Se elige por `zoomMaximo` y no
63
+ * por el orden de la lista porque el orden es una convención que nadie
64
+ * comprueba.
65
+ */
66
+ export function archivoQueCubre(manifiesto, coordenada) {
67
+ let elegido;
68
+ for (const archivo of manifiesto.archivos) {
69
+ if (!cajaContiene(archivo.caja, coordenada))
70
+ continue;
71
+ if (elegido === undefined || archivo.zoomMaximo > elegido.zoomMaximo) {
72
+ elegido = archivo;
73
+ }
74
+ }
75
+ return elegido;
76
+ }
77
+ /** Valida un manifiesto recién descargado, o falla diciendo qué le falta. */
78
+ export function leerManifiesto(documento) {
79
+ const raiz = objeto(documento, "el manifiesto");
80
+ const esquema = numero(raiz["esquema"], "el manifiesto: 'esquema'");
81
+ if (esquema > ESQUEMA_QUE_SE_ENTIENDE) {
82
+ throw new TypeError(`el manifiesto: es de esquema ${esquema} y este lector entiende hasta ` +
83
+ `el ${ESQUEMA_QUE_SE_ENTIENDE}. Un campo puede haber cambiado de ` +
84
+ `significado, así que leerlo sería peor que no leerlo. Hay que subir ` +
85
+ `la versión de @cerca.red/mapa en esta superficie.`);
86
+ }
87
+ const licencia = objeto(raiz["licencia"], "el manifiesto: 'licencia'");
88
+ const anterior = raiz["anterior"];
89
+ return {
90
+ esquema,
91
+ region: texto(raiz["region"], "el manifiesto: 'region'"),
92
+ version: texto(raiz["version"], "el manifiesto: 'version'"),
93
+ // El pipeline escribe `archivo` en singular. Aquí se normaliza a una
94
+ // lista de un elemento; ver el bloque de arriba.
95
+ archivos: [leerArchivo(raiz["archivo"], "el manifiesto: 'archivo'")],
96
+ atribucion: texto(licencia["atribucion"], "el manifiesto: 'licencia.atribucion'"),
97
+ anterior: typeof anterior === "string" ? anterior : undefined,
98
+ };
99
+ }
100
+ function leerArchivo(crudo, donde) {
101
+ const archivo = objeto(crudo, donde);
102
+ const url = texto(archivo["url"], `${donde}.url`);
103
+ if (!/^https?:\/\//.test(url)) {
104
+ throw new TypeError(`${donde}.url: se esperaba una URL absoluta y llegó "${url}". El ` +
105
+ `manifiesto las lleva absolutas a propósito (ADR-024): una relativa ` +
106
+ `obligaría a quien la lee a componerla, que es justo lo que deja el ` +
107
+ `rollback sin efecto.`);
108
+ }
109
+ const caja = archivo["caja"];
110
+ if (!esCaja(caja)) {
111
+ throw new TypeError(`${donde}.caja: se esperaba una caja [oeste, sur, este, norte] en ` +
112
+ `grados y llegó ${JSON.stringify(caja)}.`);
113
+ }
114
+ const zoom = objeto(archivo["zoom"], `${donde}.zoom`);
115
+ const zoomMinimo = numero(zoom["minimo"], `${donde}.zoom.minimo`);
116
+ const zoomMaximo = numero(zoom["maximo"], `${donde}.zoom.maximo`);
117
+ if (zoomMaximo < zoomMinimo) {
118
+ throw new TypeError(`${donde}.zoom: el máximo (${zoomMaximo}) es menor que el mínimo ` +
119
+ `(${zoomMinimo}). Con los dos cambiados el mapa no pide ninguna ` +
120
+ `tesela y se queda en blanco sin error.`);
121
+ }
122
+ const capas = archivo["capas"];
123
+ if (!Array.isArray(capas) || capas.some((c) => typeof c !== "string")) {
124
+ throw new TypeError(`${donde}.capas: se esperaba una lista de nombres de capa. Es lo que ` +
125
+ `dice qué trae el archivo, y sin ella no hay forma de saber si el ` +
126
+ `estilo espera algo que no está.`);
127
+ }
128
+ return {
129
+ url,
130
+ bytes: numero(archivo["bytes"], `${donde}.bytes`),
131
+ sha256: texto(archivo["sha256"], `${donde}.sha256`),
132
+ zoomMinimo,
133
+ zoomMaximo,
134
+ caja,
135
+ capas: capas,
136
+ };
137
+ }
138
+ function objeto(valor, donde) {
139
+ if (typeof valor !== "object" || valor === null || Array.isArray(valor)) {
140
+ throw new TypeError(`${donde}: se esperaba un objeto JSON y llegó ${JSON.stringify(valor)}.`);
141
+ }
142
+ return valor;
143
+ }
144
+ function texto(valor, donde) {
145
+ if (typeof valor !== "string" || valor === "") {
146
+ throw new TypeError(`${donde}: se esperaba un texto no vacío y llegó ${JSON.stringify(valor)}.`);
147
+ }
148
+ return valor;
149
+ }
150
+ function numero(valor, donde) {
151
+ if (typeof valor !== "number" || !Number.isFinite(valor)) {
152
+ throw new TypeError(`${donde}: se esperaba un número y llegó ${JSON.stringify(valor)}.`);
153
+ }
154
+ return valor;
155
+ }
156
+ //# sourceMappingURL=manifiesto.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifiesto.js","sourceRoot":"","sources":["../src/manifiesto.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n type Caja,\n cajaContiene,\n type Coordenada,\n esCaja,\n} from \"@cerca.red/geo\";\n\n/**\n * El manifiesto: de dónde sale la URL de un archivo de teselas.\n *\n * ## La regla que este archivo hace cumplir\n *\n * **La URL de un `.pmtiles` solo sale del manifiesto.** No se escribe en un\n * componente, no se compone con plantillas, no se guarda en una variable de\n * entorno de la superficie. ADR-024 puso URLs **absolutas** dentro del\n * manifiesto exactamente para que quien las lea no tenga que construir nada, y\n * ADR-027 lo llama «el punto de todo el documento».\n *\n * La razón no es orden: es que promover un puntero a una versión anterior\n * **es** el rollback. Basta con que una superficie arme\n * `.../colombia/2026-09-07.pmtiles` a mano para que revertir deje de revertir\n * en esa superficie, y eso no falla — sigue funcionando, con el archivo malo.\n *\n * ## Dos formas, y por qué no son la misma\n *\n * Lo que **entra** es el documento que escribe el pipeline\n * (`guiones/teselas/manifiesto.ts`, esquema 1): un objeto con `archivo` en\n * singular, `zoom.minimo`, `caja`, `licencia.atribucion`. Es su formato y lo\n * decide él; este paquete lo lee, no lo define.\n *\n * Lo que **sale** es una lista de archivos. No es capricho: ADR-024 ya dejó\n * escrito qué pasa el día que Colombia crezca —el país a zoom bajo y archivos\n * por ciudad a zoom alto, «seleccionados por el manifiesto según dónde esté\n * mirando el usuario»—. Un tipo que hoy fuera un objeto suelto obligaría ese\n * día a cambiar cada superficie que lo lee; con la lista de un elemento, el\n * día que el manifiesto traiga dos, aquí no cambia nada.\n *\n * ## Por qué la validación es estricta y ruidosa\n *\n * Porque el manifiesto lo produce otro programa, en otro repositorio y en otro\n * momento. Un documento con un campo renombrado no rompe nada al leerse:\n * produce `undefined`, que se convierte en `\"pmtiles://undefined\"`, que\n * MapLibre pide y falla como un error de red. La traza dice «no se pudo cargar\n * la fuente» y no dice que el manifiesto cambió de forma.\n *\n * Así que se valida entero, al entrar, con el nombre del campo en el mensaje.\n * Y `manifiesto.test.ts` lee el `cartografia/ejemplo-manifiesto.json` que\n * genera el pipeline de verdad: si el formato cambia ahí, esta prueba se pone\n * roja aquí, que es donde tiene que doler.\n */\n\n/**\n * La versión de formato que este lector entiende.\n *\n * Un lector viejo tiene que poder decir «esto no lo entiendo» en vez de leer\n * un campo que ahora significa otra cosa. El pipeline sube este número cuando\n * un campo cambia de significado, no cuando añade uno nuevo — así que un\n * manifiesto con esquema mayor se rechaza y uno con campos de más se acepta.\n */\nexport const ESQUEMA_QUE_SE_ENTIENDE = 1;\n\n/** Un archivo de teselas publicado, con todo lo que hace falta para pedirlo. */\nexport interface ArchivoDeTeselas {\n /** URL absoluta. Se usa tal cual. */\n readonly url: string;\n readonly bytes: number;\n /** La suma de verificación con la que se publicó. */\n readonly sha256: string;\n readonly zoomMinimo: number;\n readonly zoomMaximo: number;\n /** La caja que cubre este archivo. */\n readonly caja: Caja;\n /** Las capas que trae. El estilo espera exactamente estas. */\n readonly capas: readonly string[];\n}\n\nexport interface Manifiesto {\n readonly esquema: number;\n readonly region: string;\n readonly version: string;\n /** Los archivos de esta versión. Hoy siempre uno; ver arriba. */\n readonly archivos: readonly ArchivoDeTeselas[];\n /**\n * La atribución, copiada de dentro del archivo de teselas.\n *\n * Es **texto legal** (CLAUDE.md §11): se muestra tal cual, no se recorta ni\n * se reescribe. Viene de los metadatos del propio `.pmtiles`, que es lo que\n * evita que una superficie se quede sin ella por olvido.\n */\n readonly atribucion: string;\n /** A qué versión apuntaba antes. Existe para poder decir a qué se revirtió. */\n readonly anterior: string | undefined;\n}\n\n/** La URL con el protocolo puesto, que es lo que espera MapLibre. */\nexport function urlDeProtocolo(archivo: ArchivoDeTeselas): string {\n return `pmtiles://${archivo.url}`;\n}\n\n/**\n * El archivo que cubre una coordenada, con **el mayor detalle disponible**.\n *\n * Cuando haya un archivo de país y uno de ciudad, los dos contienen el centro\n * de Pereira; el que sirve es el de más zoom. Se elige por `zoomMaximo` y no\n * por el orden de la lista porque el orden es una convención que nadie\n * comprueba.\n */\nexport function archivoQueCubre(\n manifiesto: Manifiesto,\n coordenada: Coordenada,\n): ArchivoDeTeselas | undefined {\n let elegido: ArchivoDeTeselas | undefined;\n for (const archivo of manifiesto.archivos) {\n if (!cajaContiene(archivo.caja, coordenada)) continue;\n if (elegido === undefined || archivo.zoomMaximo > elegido.zoomMaximo) {\n elegido = archivo;\n }\n }\n return elegido;\n}\n\n/** Valida un manifiesto recién descargado, o falla diciendo qué le falta. */\nexport function leerManifiesto(documento: unknown): Manifiesto {\n const raiz = objeto(documento, \"el manifiesto\");\n\n const esquema = numero(raiz[\"esquema\"], \"el manifiesto: 'esquema'\");\n if (esquema > ESQUEMA_QUE_SE_ENTIENDE) {\n throw new TypeError(\n `el manifiesto: es de esquema ${esquema} y este lector entiende hasta ` +\n `el ${ESQUEMA_QUE_SE_ENTIENDE}. Un campo puede haber cambiado de ` +\n `significado, así que leerlo sería peor que no leerlo. Hay que subir ` +\n `la versión de @cerca.red/mapa en esta superficie.`,\n );\n }\n\n const licencia = objeto(raiz[\"licencia\"], \"el manifiesto: 'licencia'\");\n const anterior = raiz[\"anterior\"];\n\n return {\n esquema,\n region: texto(raiz[\"region\"], \"el manifiesto: 'region'\"),\n version: texto(raiz[\"version\"], \"el manifiesto: 'version'\"),\n // El pipeline escribe `archivo` en singular. Aquí se normaliza a una\n // lista de un elemento; ver el bloque de arriba.\n archivos: [leerArchivo(raiz[\"archivo\"], \"el manifiesto: 'archivo'\")],\n atribucion: texto(\n licencia[\"atribucion\"],\n \"el manifiesto: 'licencia.atribucion'\",\n ),\n anterior: typeof anterior === \"string\" ? anterior : undefined,\n };\n}\n\nfunction leerArchivo(crudo: unknown, donde: string): ArchivoDeTeselas {\n const archivo = objeto(crudo, donde);\n const url = texto(archivo[\"url\"], `${donde}.url`);\n\n if (!/^https?:\\/\\//.test(url)) {\n throw new TypeError(\n `${donde}.url: se esperaba una URL absoluta y llegó \"${url}\". El ` +\n `manifiesto las lleva absolutas a propósito (ADR-024): una relativa ` +\n `obligaría a quien la lee a componerla, que es justo lo que deja el ` +\n `rollback sin efecto.`,\n );\n }\n\n const caja = archivo[\"caja\"];\n if (!esCaja(caja)) {\n throw new TypeError(\n `${donde}.caja: se esperaba una caja [oeste, sur, este, norte] en ` +\n `grados y llegó ${JSON.stringify(caja)}.`,\n );\n }\n\n const zoom = objeto(archivo[\"zoom\"], `${donde}.zoom`);\n const zoomMinimo = numero(zoom[\"minimo\"], `${donde}.zoom.minimo`);\n const zoomMaximo = numero(zoom[\"maximo\"], `${donde}.zoom.maximo`);\n if (zoomMaximo < zoomMinimo) {\n throw new TypeError(\n `${donde}.zoom: el máximo (${zoomMaximo}) es menor que el mínimo ` +\n `(${zoomMinimo}). Con los dos cambiados el mapa no pide ninguna ` +\n `tesela y se queda en blanco sin error.`,\n );\n }\n\n const capas = archivo[\"capas\"];\n if (!Array.isArray(capas) || capas.some((c) => typeof c !== \"string\")) {\n throw new TypeError(\n `${donde}.capas: se esperaba una lista de nombres de capa. Es lo que ` +\n `dice qué trae el archivo, y sin ella no hay forma de saber si el ` +\n `estilo espera algo que no está.`,\n );\n }\n\n return {\n url,\n bytes: numero(archivo[\"bytes\"], `${donde}.bytes`),\n sha256: texto(archivo[\"sha256\"], `${donde}.sha256`),\n zoomMinimo,\n zoomMaximo,\n caja,\n capas: capas as readonly string[],\n };\n}\n\nfunction objeto(valor: unknown, donde: string): Record<string, unknown> {\n if (typeof valor !== \"object\" || valor === null || Array.isArray(valor)) {\n throw new TypeError(\n `${donde}: se esperaba un objeto JSON y llegó ${JSON.stringify(valor)}.`,\n );\n }\n return valor as Record<string, unknown>;\n}\n\nfunction texto(valor: unknown, donde: string): string {\n if (typeof valor !== \"string\" || valor === \"\") {\n throw new TypeError(\n `${donde}: se esperaba un texto no vacío y llegó ${JSON.stringify(valor)}.`,\n );\n }\n return valor;\n}\n\nfunction numero(valor: unknown, donde: string): number {\n if (typeof valor !== \"number\" || !Number.isFinite(valor)) {\n throw new TypeError(\n `${donde}: se esperaba un número y llegó ${JSON.stringify(valor)}.`,\n );\n }\n return valor;\n}\n"]}
@@ -0,0 +1,154 @@
1
+ import { type Caja, type ColeccionDeEntidades } from "@cerca.red/geo";
2
+ import type { Tema } from "./estilo.ts";
3
+ import type { BandaDeIsocrona, GrupoEnMapa, NegocioEnMapa, ResultadosEnMapa, RutaEnMapa, UbicacionDeUsuarioEnMapa, VehiculoEnMapa, ZonaEnMapa } from "./proyecciones.ts";
4
+ import type { Margenes } from "./puerto.ts";
5
+ /**
6
+ * El modelo: lo que el SDK sabe que hay que dibujar.
7
+ *
8
+ * ## La decisión que sostiene todo el paquete
9
+ *
10
+ * **El SDK tiene el modelo y el mapa es una proyección.** No al revés.
11
+ *
12
+ * La razón inmediata es que cambiar de tema o de idioma en MapLibre es
13
+ * reemplazar el estilo entero, y el estilo nuevo llega **sin** las fuentes ni
14
+ * las capas de Cerca. Si el estado viviera en el mapa, cambiar de tema
15
+ * perdería los negocios, la ruta y la selección — y no de forma ruidosa: el
16
+ * mapa se quedaría bonito y vacío.
17
+ *
18
+ * La razón de fondo es que hace fiel al doble de pruebas. Si el estado está
19
+ * aquí, en objetos planos y funciones puras, entonces lo que se prueba en
20
+ * Node es el comportamiento de verdad y no una imitación: la conversión de
21
+ * `NegocioEnMapa` a GeoJSON, qué caja hay que encuadrar, qué pasa al
22
+ * seleccionar algo que no existe. Lo que queda para WebGL es solo que el
23
+ * motor pinte lo que se le dio.
24
+ *
25
+ * ## Inmutable
26
+ *
27
+ * Cada operación devuelve un modelo nuevo. No es dogma: el SDK compara el
28
+ * modelo anterior con el nuevo para decidir qué fuentes tocar, y esa
29
+ * comparación es imposible si el objeto se modificó por debajo.
30
+ */
31
+ export interface ModeloDelMapa {
32
+ readonly resultados: ResultadosEnMapa;
33
+ readonly seleccionado: string | undefined;
34
+ readonly ubicacion: UbicacionDeUsuarioEnMapa | undefined;
35
+ readonly vehiculos: readonly VehiculoEnMapa[];
36
+ readonly rutas: readonly RutaEnMapa[];
37
+ readonly zonas: readonly ZonaEnMapa[];
38
+ readonly isocronas: readonly BandaDeIsocrona[];
39
+ readonly tema: Tema;
40
+ readonly idioma: string;
41
+ readonly margenes: Margenes;
42
+ /**
43
+ * Con cuánto radio se dibuja una ubicación marcada como aproximada.
44
+ *
45
+ * Vive aquí y **no** en `NegocioEnMapa` a propósito. Que una ubicación sea
46
+ * aproximada lo decide Cerca; con cuántos metros de área se representa esa
47
+ * imprecisión es una decisión de dibujo, y meterla en la proyección sería
48
+ * el primer campo de la acumulación que ADR-027 dice que mata la frontera.
49
+ */
50
+ readonly radioDeImprecisionEnMetros: number;
51
+ }
52
+ /** El modelo de un mapa recién creado. */
53
+ export declare function modeloInicial(parciales?: (Partial<Omit<ModeloDelMapa, "resultados">> & {
54
+ readonly resultados?: ResultadosEnMapa;
55
+ readonly negocios?: never;
56
+ }) | (Partial<Omit<ModeloDelMapa, "resultados">> & {
57
+ readonly resultados?: never;
58
+ readonly negocios?: readonly NegocioEnMapa[];
59
+ })): ModeloDelMapa;
60
+ /** Los negocios del modo actual; vacío mientras se muestran grupos del servidor. */
61
+ export declare function negociosDelModelo(modelo: ModeloDelMapa): readonly NegocioEnMapa[];
62
+ /** Los grupos explícitos del modo actual; vacío mientras se muestran negocios. */
63
+ export declare function gruposDelModelo(modelo: ModeloDelMapa): readonly GrupoEnMapa[];
64
+ /** Las propiedades con las que viaja un negocio a la fuente GeoJSON. */
65
+ interface PropiedadesDeNegocio extends Record<string, unknown> {
66
+ readonly id: string;
67
+ readonly enfasis: string;
68
+ readonly rotulo?: string;
69
+ readonly simbolo?: string;
70
+ }
71
+ /**
72
+ * Los negocios, como colección GeoJSON.
73
+ *
74
+ * El `id` va **dos veces**: como identificador de la entidad y como
75
+ * propiedad. No es descuido. El identificador de entidad es lo que necesita
76
+ * el estado de entidad —el resalte al pasar por encima— y tiene que ser
77
+ * numérico o una cadena; la propiedad es lo que se lee al consultar lo
78
+ * dibujado bajo el dedo, porque MapLibre **pierde el identificador de la
79
+ * entidad al agrupar** y solo conserva las propiedades.
80
+ */
81
+ export declare function coleccionDeNegocios(modelo: ModeloDelMapa): ColeccionDeEntidades<PropiedadesDeNegocio>;
82
+ interface PropiedadesDeGrupo extends Record<string, unknown> {
83
+ readonly id: string;
84
+ readonly cantidad: number;
85
+ readonly rotulo?: string;
86
+ }
87
+ /** Los agregados del servidor, en una fuente sin clustering local. */
88
+ export declare function coleccionDeGrupos(modelo: ModeloDelMapa): ColeccionDeEntidades<PropiedadesDeGrupo>;
89
+ interface PropiedadesDeSede extends Record<string, unknown> {
90
+ readonly id: string;
91
+ readonly negocio: string;
92
+ readonly rotulo?: string;
93
+ }
94
+ /** Las sedes de todos los negocios, aplanadas en una sola colección. */
95
+ export declare function coleccionDeSedes(modelo: ModeloDelMapa): ColeccionDeEntidades<PropiedadesDeSede>;
96
+ /**
97
+ * El negocio seleccionado, solo. En su propia fuente porque tiene que
98
+ * dibujarse **encima** de sus vecinos, y la clave de ordenación de símbolos
99
+ * no lee el estado de entidad.
100
+ */
101
+ export declare function coleccionDeSeleccion(modelo: ModeloDelMapa): ColeccionDeEntidades<PropiedadesDeNegocio>;
102
+ /**
103
+ * Las áreas de las ubicaciones aproximadas.
104
+ *
105
+ * Esta es la función que hace visible una decisión que en una lista es una
106
+ * etiqueta: «no se da por exacta una ubicación que nadie verificó». En un
107
+ * mapa, un alfiler afirma un punto. Si nadie verificó ese punto, el alfiler
108
+ * está afirmando algo que Cerca no sabe, y el usuario que camina hasta ahí
109
+ * descubre el error caminando.
110
+ *
111
+ * Un negocio aproximado sale **como área y sin punto**: aparece en esta
112
+ * colección y se filtra de la de negocios. Dibujar las dos cosas sería
113
+ * desdecirse en el mismo mapa.
114
+ */
115
+ export declare function coleccionDeImprecision(modelo: ModeloDelMapa): ColeccionDeEntidades<{
116
+ id: string;
117
+ }>;
118
+ /** Los negocios con ubicación exacta: los únicos que se dibujan como alfiler. */
119
+ export declare function negociosExactos(modelo: ModeloDelMapa): readonly NegocioEnMapa[];
120
+ /** La ubicación del usuario: el punto y, si se sabe, su área de incertidumbre. */
121
+ export declare function coleccionDeUbicacion(modelo: ModeloDelMapa): ColeccionDeEntidades<Record<string, unknown>>;
122
+ export declare function coleccionDeVehiculos(modelo: ModeloDelMapa): ColeccionDeEntidades<Record<string, unknown>>;
123
+ /**
124
+ * Las rutas, con `principal` resuelto.
125
+ *
126
+ * La regla de desempate está aquí y no en el filtro del estilo porque una
127
+ * expresión de MapLibre no puede decidir «la primera si ninguna se marcó»:
128
+ * con dos principales dibujaría las dos encima, y con cero no dibujaría
129
+ * ninguna en la capa gruesa — la ruta elegida se vería como una alternativa.
130
+ */
131
+ export declare function coleccionDeRutas(modelo: ModeloDelMapa): ColeccionDeEntidades<Record<string, unknown>>;
132
+ /** Cuál de las rutas es la principal: la primera marcada, o la primera a secas. */
133
+ export declare function indiceDeRutaPrincipal(rutas: readonly RutaEnMapa[]): number;
134
+ export declare function coleccionDeZonas(modelo: ModeloDelMapa): ColeccionDeEntidades<Record<string, unknown>>;
135
+ /**
136
+ * Las isócronas, **de mayor a menor**.
137
+ *
138
+ * El orden importa y no es estético: las bandas se solapan —la de 15 minutos
139
+ * contiene entera a la de 5— y en una capa de relleno se dibujan en el orden
140
+ * de la colección. Con la pequeña primero, la grande la tapa y el mapa
141
+ * enseña un solo polígono plano.
142
+ */
143
+ export declare function coleccionDeIsocronas(modelo: ModeloDelMapa): ColeccionDeEntidades<Record<string, unknown>>;
144
+ /**
145
+ * La caja de todo lo que hay dibujado, o de una parte.
146
+ *
147
+ * Recorre las sedes además de los negocios: una cadena con cinco locales
148
+ * encuadrada solo por su sede principal deja las otras cuatro fuera de
149
+ * pantalla, y el mapa parece correcto.
150
+ */
151
+ export declare function cajaDelModelo(modelo: ModeloDelMapa, que?: "todo" | "grupos" | "negocios" | "rutas" | "zonas"): Caja | undefined;
152
+ /** La caja de un negocio y sus sedes. Lo que se encuadra al seleccionarlo. */
153
+ export declare function cajaDeNegocio(negocio: NegocioEnMapa): Caja | undefined;
154
+ export {};