# Iniciativa de geometría regional por producto

> Estado: completada
> Fecha de planificación: 2026-08-20
> Aplicaciones: `apps/data`, `apps/sis` y `apps/tie`
> Backend legado excluido: `apps/api`

## GEO.1 Objetivo

Completar la región canónica del producto con geometrías administrables:

```text
Región política (cierre=false) ─┐
Ubicación final (cierre=true) ──┼─> producto.region_id ─> mapas SIS y TIE
Punto, polígono o ambos ────────┘

Importación ─> resuelve región ─> producto.region_id
                                  (no compra_producto.region_id)
```

El usuario puede crear una ubicación completa sin abandonar el formulario de producto, dibujar o editar MultiPolygon en SIS y ver la misma geometría canónica en los mapas de SIS y TIE.

## GEO.2 Decisiones cerradas

- `producto.region_id` es la única geografía canónica del producto.
- `cierre=true` representa una ubicación terminal real. Puede tener punto, polígono o ambos, no puede tener hijas y su nombre no añade un segmento a la ruta pública.
- `cierre=false` representa una región geográfica o política y puede ser padre.
- Un producto puede usar ambos tipos; SIS los distingue como ubicación exacta o región general.
- Si existen punto y polígono, el mapa dibuja el área y usa el punto como centro o etiqueta, sin pin adicional.
- El alta contextual desde producto ofrece el editor completo y crea siempre `cierre=true`.
- Las regiones existentes se editan en el módulo Región, porque pueden estar compartidas.
- Borrar geometría de una región enlazada se permite con confirmación y conteo de productos afectados.
- Mismo nombre normalizado y mismo padre reutiliza la región existente sin modificar su geometría.
- La compra manual solo escribe `compra_producto.region_id` si el usuario elige expresamente una ubicación operativa.
- La importación nunca escribe su región inferida en `compra_producto.region_id`.
- Importación: misma región reutiliza producto; una ficha histórica sin región se completa si es la única coincidencia; otra región crea producto nuevo desde el anuncio; múltiples históricos sin región producen ambigüedad.
- Sin dirección exacta se usa la región política inferida. Con dirección exacta se reutiliza o crea una ubicación terminal.
- Una geometría compartida se dibuja una sola vez y agrupa sus productos.
- No se modifica `manual-migration.sql` ni se regenera `database.sql`.

## GEO.3 Estado inicial verificado

- `region.poligono` ya es `geometry(MultiPolygon,4326)` y tiene índice GiST, pero no está soportado por la entidad ni el CRUD REST.
- El CRUD Región usa bodies `any`, tiene lecturas sin filtro completo de tenant y no persiste todos los campos editables.
- SIS solo edita puntos y duplica formularios/mapas de Región.
- Producto permite elegir región, pero no crearla desde el selector.
- Compra reutiliza el mismo `region_id` para la región canónica del producto y la ubicación operativa del ítem.
- La importación crea o resuelve una región, crea producto sin ella y luego la guarda en `compra_producto`.
- SIS aún dibuja ubicaciones desde `inputs`; TIE convierte polígonos de producto en pines `PointOnSurface`.
- TIE necesita diferenciar polígonos de producto de límites administrativos estáticos.

## GEO.4 Backlog

| ID | P | Estado | Entregable | Depende de |
|---|---|---|---|---|
| GEO-001 | P0 | hecha | Contrato seguro de Región en Data. | — |
| GEO-002 | P0 | hecha | Editor completo y reutilizable de Región en SIS. | GEO-001 |
| GEO-003 | P0 | hecha | Alta contextual de ubicación desde Producto. | GEO-002 |
| GEO-004 | P0 | hecha | Región operativa explícita en compra manual. | GEO-002 |
| GEO-005 | P0 | hecha | Importación con identidad regional de producto. | GEO-001 |
| GEO-006 | P0 | hecha | Contrato deduplicado de geometrías para mapas. | GEO-001, GEO-005 |
| GEO-007 | P0 | hecha | Mapa canónico de productos en SIS. | GEO-006 |
| GEO-008 | P0 | hecha | Polígonos públicos de producto en TIE. | GEO-006 |
| GEO-009 | P0 | hecha | Integridad, rendimiento y verificación integral. | GEO-003, GEO-004, GEO-005, GEO-007, GEO-008 |

## GEO.5 Detalle

### GEO-001 — Contrato seguro de Región en Data

- Mapear `poligono` como `geometry(MultiPolygon,4326)`.
- Crear DTOs explícitos para alta, edición y respuesta.
- Aceptar GeoJSON `Polygon`, `MultiPolygon` o `null`; normalizar mediante `ST_SetSRID` y `ST_Multi`.
- Validar rangos, anillos, tamaño, `ST_IsValid`, `ST_IsEmpty`, tenant, padre y ciclos.
- Un cierre requiere punto o polígono, no puede ser padre y no puede activarse si tiene hijos.
- Permitir punto y polígono simultáneos.
- Omitir `poligono` conserva; enviar `null` elimina expresamente.
- Devolver GeoJSON completo, bbox, punto representativo, tipo geométrico y cantidad de productos asociados.
- En duplicado por nombre normalizado y padre, devolver la región existente con `created=false`, conservando `id` en la raíz.
- Invalidar caché TIE cuando cambien geometría, punto, cierre, jerarquía o visibilidad.

**Aceptación:** round-trip GeoJSON conserva áreas y huecos; tenant y jerarquía están protegidos; una actualización parcial no borra campos omitidos.

### GEO-002 — Editor completo de Región en SIS

- Unificar alta, edición, detalle y diálogos en componentes Vuetify/OpenLayers reutilizables.
- Soportar punto, Polygon, MultiPolygon, varias áreas y anillos interiores.
- Incluir dibujar, modificar, deshacer, restaurar y limpiar con confirmación.
- Editar `cierre` separadamente y explicar ubicación terminal frente a región política.
- Mostrar como padres solo regiones `cierre=false`.
- Al limpiar geometría, mostrar cuántos productos dejarán de verse en mapas.
- Ajustar el viewport a la geometría y preservar todos los anillos al recargar.

**Aceptación:** crear, editar, recargar y limpiar geometrías funciona en página y diálogo, escritorio y móvil, sin markup duplicado.

### GEO-003 — Alta contextual desde Producto

- Añadir `Crear ubicación` a `productoRegionSelector.vue`.
- Abrir el editor completo con `cierre=true` fijo para la nueva ubicación.
- Permitir punto, polígono o ambos, nombre y padre político.
- Reutilizar y seleccionar una coincidencia por nombre normalizado y padre.
- Tras crear, refrescar y seleccionar el ID sin perder el formulario.
- Permitir seleccionar cierres o regiones políticas y mostrar claramente el tipo.
- Enlazar al módulo Región para editar una región existente.

**Aceptación:** alta y edición de producto crean y seleccionan una ubicación completa sin salir del flujo.

### GEO-004 — Región operativa explícita en compra manual

- Separar `producto.region_id` de un estado local `compra_region_id`.
- Nunca inicializar la ubicación operativa desde la región canónica.
- En compra nueva, enviar `NULL` hasta que el usuario seleccione expresamente.
- En edición, precargar la ubicación operativa existente; conservar si no cambia y limpiar solo por acción explícita.
- Reutilizar el editor de Región al crear una ubicación desde compra.
- Mantener el significado operativo de `compra_producto.region_id` y el portal proveedor.

**Aceptación:** crear una compra sin tocar Región deja el ítem en NULL; seleccionar o limpiar persiste exactamente la decisión del usuario.

### GEO-005 — Importación con identidad regional

- Resolver primero región política o terminal del anuncio.
- Buscar equivalencias priorizando la misma región.
- Reutilizar producto regional aunque cambie el proveedor.
- Asignar región a una única ficha histórica equivalente con `region_id IS NULL`.
- Crear producto nuevo desde nombre, clasificadores, precio y galería del anuncio cuando la equivalencia está en otra región.
- Si hay varias coincidencias históricas sin región, omitir y reportar `producto_ambiguo`.
- No enviar región a `CompraService.crear`; los nuevos `compra_producto.region_id` quedan NULL.
- Usar transacción por anuncio y savepoint por ítem; no dejar ubicaciones huérfanas si nada se importa.
- Reportar `producto_reutilizado`, `region_asignada`, `producto_regional_creado`, `producto_ambiguo` o `region_no_resuelta`.
- Permitir reintentar fallidos sin duplicar éxitos.

**Aceptación:** cada producto importado tiene la región decidida y ningún nuevo ítem recibe la región inferida como ubicación operativa.

### GEO-006 — Contrato geográfico para mapas

- Extender las fuentes SQL versionadas de SIS y TIE y desplegarlas con migraciones `CREATE OR REPLACE FUNCTION`.
- Mantener una referencia ligera en `producto.region`.
- Añadir `region_geometrias` deduplicado por `region_id`, con GeoJSON, bbox, centro, cierre, `product_ids` y cantidad.
- Administración usa geometría completa; catálogo simplifica preservando topología.
- Cierres conservan precisión aproximada de un metro; regiones políticas usan tolerancia progresiva por nivel.
- Semillas TIE usan `producto_id` y región canónica, nunca `compra_producto_id` ni `inputs`.
- Bbox, contadores, filtros y paginación permanecen basados en `producto.region_id`.

**Aceptación:** varios productos en una región devuelven una geometría y no existen coordenadas canónicas derivadas de compras.

### GEO-007 — Mapa canónico en SIS

- Consumir `producto.region` y `region_geometrias`; retirar coordenadas de `inputs`.
- Dibujar pin cuando solo existe punto y área cuando existe polígono.
- Agrupar puntos coincidentes y polígonos compartidos.
- Mostrar contador y productos navegables en el popup.
- Reemplazar features limpiamente al cambiar filtros.

**Aceptación:** cambiar `compra_producto.region_id` no mueve el producto; cambiar su región canónica sí.

### GEO-008 — Polígonos públicos en TIE

- Mantener OpenLayers en componentes cliente.
- Crear features con `feature_kind=product_region`, `region_id` y `product_ids`.
- Diferenciar áreas de producto y límites administrativos.
- Priorizar click de producto; uno abre tarjeta y varios abren lista.
- No dibujar pin `PointOnSurface` cuando existe polígono.
- Acumular y deduplicar geometrías durante infinite scroll.
- Deduplicar semillas y caché distrital por `producto_id`.

**Aceptación:** SSR/hidratación funcionan y las áreas compartidas son navegables sin activar el límite político subyacente.

### GEO-009 — Verificación integral

- Probar punto, Polygon, MultiPolygon, huecos, punto más polígono y limpieza.
- Probar cierre con hijos, padre cierre, ciclos y cruces de tenant.
- Probar todas las decisiones de importación y confirmar `compra_producto.region_id IS NULL`.
- Probar compra manual sin selección, con selección, conservación y limpieza.
- Auditar SRID, geometrías inválidas, cierres sin geometría y cierres con hijos.
- Medir tamaño JSON y `EXPLAIN (ANALYZE, BUFFERS)`.
- Compilar aisladamente SFC y verificar SSR/hidratación TIE.
- Confirmar invalidación de caché y representación coincidente en SIS/TIE.

**Aceptación:** todos los recorridos críticos pasan sin relaciones, geometrías o tenants inválidos y sin regresión significativa.

## GEO.6 Definition of Done

- Dependencias en `hecha` y estado `en_curso` antes de implementar.
- DTO, entidad, servicio, SQL y REST comparten el contrato.
- RLS/JWT, tenant, jerarquía, SRID y consultas parametrizadas se preservan.
- Omisión no borra geometría; limpiar requiere intención explícita.
- SIS usa Vuetify y composables autenticados; TIE usa Nuxt UI y helpers REST.
- Capas de mapa se deduplican, limpian con filtros y distinguen administración/producto.
- Importación registra decisiones sin sobrescrituras regionales.
- Verificación focalizada y evidencia en este archivo antes de marcar `hecha`.
- No se ejecutan builds globales por defecto.

## GEO.7 Fuera de alcance

- Eliminar `compra_producto.region_id`.
- Editar la región canónica desde TIE o tokens públicos.
- Geocodificación externa, shapefiles/KML o descarga de límites.
- Relación producto-región muchos-a-muchos o polígono propio en producto.
- Modificar `apps/api`, GraphQL, `manual-migration.sql` o `database.sql`.

## GEO.8 Protocolo

```text
Implementa ROADMAP::GEO-001.
```

El agente resuelve el ID, revisa dependencias y criterios, cambia a `en_curso`, implementa, verifica y registra evidencia antes de marcar `hecha`.

## GEO.9 Registro de ejecución

| Fecha | ID | Resultado | Evidencia | Verificación |
|---|---|---|---|---|
| 2026-08-20 | ROADMAP-GEO | Roadmap reemplazado; se retiró la iniciativa ya completada y se fijaron todas las decisiones de geometría, compra, importación y mapas. | `roadmap.md` y auditoría estática de Data/SIS/TIE. | 9 tareas sin dependencias ambiguas; GEO-001 abierto para implementación. |
| 2026-08-20 | GEO-001 | Contrato Región completo, tipado, multi-tenant y con GeoJSON MultiPolygon normalizado. | `Region.ts`, DTO, controller/service/module de Región. | TypeScript focal; round-trip MULTIPOLYGON SRID 4326, válido, con 1 hueco y actualización parcial conservada. |
| 2026-08-20 | GEO-002 | Editor OpenLayers reutilizable para punto, áreas múltiples y huecos; formularios de alta/edición/detalle consolidados. | `regionGeometryEditor.vue`, `regionForm.vue`, `regionEditorCard.vue`. | 12 SFC SIS compilados aisladamente. |
| 2026-08-20 | GEO-003 | Producto crea ubicaciones terminales dentro del selector, las selecciona y distingue ubicación exacta de región general. | `productoRegionSelector.vue`. | Compilación SFC aislada. |
| 2026-08-20 | GEO-004 | Compra manual separa la ubicación operativa de la región canónica y solo persiste selección explícita. | Store/componentes Compra y edición de compra desde Producto. | Auditoría de payload: estado local `compra_region_id`, alta predeterminada NULL. |
| 2026-08-20 | GEO-005 | Importador transaccional resuelve identidad regional del producto, reporta decisiones y no propaga región a ítems. | `CompraService.importarRegional`; ruta y helpers legados retirados. | TypeScript focal y auditoría sin asignación masiva de `compra_producto.region_id`. |
| 2026-08-20 | GEO-006 | Contrato `region_geometrias` deduplicado en administración/TIE y semillas canónicas versionadas. | Servicios Producto, fuente `filtros_disgregado2.sql`, migración 178509. | Migración aplicada; definición desplegada contiene `producto_id`/`region_id` y no `compra_producto_id` en semillas. |
| 2026-08-20 | GEO-007 | Mapa SIS usa región canónica, dibuja área o pin y agrupa productos por región. | Store Producto y `productosMapa.vue`. | Compilación SFC aislada; sin dependencia de `inputs` para features. |
| 2026-08-20 | GEO-008 | TIE muestra polígonos de producto diferenciados, navegables y deduplicados durante infinite scroll. | Store Producto, `IndexCategoria.vue`, `OlMapa.client.vue`. | 2 SFC TIE compilados aisladamente; popup de uno/lista y precedencia de feature verificadas estáticamente. |
| 2026-08-20 | GEO-009 | Integridad PostGIS saneada y verificaciones focales completadas. | Migración reversible 178510 con respaldo; auditoría SQL y controles de diff. | 0 geometrías inválidas, 0 cierres inválidos; JSON 140454 bytes; plan 0.345 ms; Prettier, TypeScript, 14 SFC y `git diff --check` correctos. |
