La División GIS de nuestra ciudad necesitaba una forma para que los residentes reportaran baches, fugas de agua, grafitis, vertidos ilegales, problemas de código y preocupaciones similares, y para que el personal gestionara esos tickets sin un producto 311 de terceros. Lo construimos sobre el ArcGIS Enterprise que ya operamos, y ahora hemos publicado todo el sistema en GitHub, generalizado para que cualquier organización pueda implementarlo.
https://community.esri.com/home/leaving?allowTrusted=1&target=https%3A%2F%2Fgithub.com%2Fbrianmcleer%2Freport-a-concern
Esta publicación es un recorrido técnico de cómo está armado y por qué se tomaron algunas decisiones. El repositorio tiene el manual completo de despliegue.
Qué hay en la caja
Dos widgets Experience Builder 1.21 (asistente público de envío y gestor de tickets para el personal), un esquema de geodatabase empresarial con reglas de atributos, un pequeño proxy Flask que actúa como intermediario del servicio de entidades público, siete scripts Python que se ejecutan en el Programador de tareas de Windows, plantillas HTML accesibles para correos electrónicos, definiciones de trabajos del Programador de tareas y documentación. Nada en el código está ligado a nuestra organización. Los nombres de host, direcciones de correo electrónico, nombres de departamentos e IDs de ítems provienen de dos archivos de configuración ignorados por git y los paneles de configuración del widget.
El modelo de datos
Todo reside en una geodatabase empresarial SQL Server. El sistema principal es una clase de entidad puntual, Tickets, con un ID GUID del ticket, un número legible para humanos, categoría como subtipo (13 códigos), un campo texto subcategoría cuyo dominio cambia según el subtipo, estado, departamento asignado, el límite donde cayó el punto, campos de contacto del remitente y dos banderas que los scripts consultan: notification_sent y survey_sent. Los adjuntos están habilitados en Tickets para fotos.
Alrededor hay tablas relacionadas unidas por ID del ticket con clases de relación compuestas para que al eliminar un ticket se eliminen en cascada: Ticket_Comments (con una bandera is_public y otra email_sent), Ticket_Photos_Meta y Survey_Responses. Tres tablas de consulta controlan la ruta: Service_Boundaries (polígonos con bandera is_active), Category_Boundary_Lookup (qué categorías son válidas dentro de qué límite, más un mensaje redirigido para las no válidas) y Ticket_Routing (categoría, subcategoría opcional, ID del límite, departamento predeterminado, correo electrónico del departamento). Notification_Log es una tabla solo para agregar auditorías a la que escriben todos los scripts y el proxy.
Tickets, comentarios y respuestas a encuestas están versionados y archivados. Las tablas de consulta y Notification_Log no lo están a propósito, lo cual es importante más adelante.
Flujo de envío, completo
- El residente abre la app pública Experience Builder y coloca un pin. El widget de envío consulta Service_Boundaries del lado cliente para encontrar qué polígonos activos contienen el punto, luego consulta Category_Boundary_Lookup para que la lista de categorías solo muestre las válidas allí. No se puede presentar un ticket de agua dentro del distrito vecino; el residente ve la información de contacto del distrito en lugar de un callejón sin salida.
- El widget envía un applyEdits a una URL del mismo origen bajo la app, no directamente al servicio de entidades. IIS URL Rewrite (a nivel sitio, así una republicación Experience Builder no lo borra) reenvía esa ruta a un proxy Flask en localhost mediante Application Request Routing. Una segunda regla a nivel sitio devuelve 403 a cualquier POST directo al FeatureServer desde fuera.
- El proxy limita la tasa por IP cliente, rechaza más de una entidad por solicitud, verifica la geometría contra una caja delimitadora, valida categoría, longitud descripción, formato email y teléfono, y solo entonces reenvía al FeatureServer en localhost. Para fotos lee los primeros bytes y acepta solo contenido real JPEG, PNG, WebP y HEIC sin importar el tipo declarado, con límite de tamaño. Cada solicitud aceptada o rechazada se escribe en Notification_Log con la IP cliente para tener auditoría sin registro en archivo.
- Cinco reglas Arcade corren al insertar en la geodatabase: restricción geocercada (bloquea envíos fuera o categoría inválida con mensaje desde la consulta), cálculo de ruta que encuentra el límite e intenta coincidencia categoría + subcategoría + límite en Ticket_Routing o usa fila general para asignar departamento; cálculo número ticket desde secuencia SQL empezando en 10000; dos restricciones validan campos obligatorios y reglas comerciales. La ruta es dato no código: agregar o cambiar departamento es editar fila.
- Cada cinco minutos el script notificador busca tickets con notification_sent = 0, envía email confirmación al residente con enlace estado y correo aviso al departamento asignado con enlace profundo a la app gestor.
Lado personal
El widget gestor corre en app interna Experience Builder tras inicio Portal. Lee capa Tickets y tablas relacionadas desde mapa web sin configurar tokens ni URLs extra. El personal filtra por estado, categoría y distintivos departamentales; abre ticket; cambia estado (cambio requiere comentario; retroceder fecha resuelta escribe nota interna); añade comentarios públicos o internos; ve fotos en lightbox; ve respuesta encuesta si llegó. Comentarios públicos disparan email al residente vía script mailer comentarios. Hay exportación Excel con resumen y registros relacionados. Guía ayuda integrada siguiendo patrón usado en todos nuestros widgets.
Los scripts y dos errores iniciales
Siete scripts comparten módulo común para configuración, registro logs, alertas fallos, email, renderizado plantillas y escritura auditoría; cada script solo su lógica: notificador nuevos tickets; notificador reasignación (detecta cambio assigned_to y avisa nuevo departamento); mailer comentarios públicos; mailer invitación encuesta (al resolverse o cerrar ticket); extracción respuestas Survey123 (escribe Survey_Responses y avisa quien resolvió si pidió seguimiento); reporte directores días laborables tickets vencidos por categoría; reporte mensual todos departamentos. Cada fallo se recoge y envía alerta única al salir; proceso termina con código 1 para mostrar error en Programador Tareas.
Dos lecciones están integradas en código. Primero emails duplicados. El notificador original enviaba emails dentro sesión edición masiva única y confirmaba bandera al final. Si ese commit fallaba (un staff tenía abierto ticket en gestor causando conflicto versión), todos emails ya enviados se revertían las banderas; siguiente ejecución enviaba todo otra vez. Ahora bandera se confirma por ticket antes del email en operación corta independiente. Si falla commit no hay email; reintento seguro siguiente ejecución. Si commit ok pero envío falla se registra visible pero nunca reenvía.
Segundo tabla auditoría. Notification_Log empezó versionada; scripts escribiéndola con cursores eran lentos y dependían Compress. Ahora no está registrada versión; cada escritor inserta con SQL directo usando OBJECTID MAX+1 con reintento corto; así ninguna escritura depende contador fila geodatabase ni colisionan scripts. Relacionado: mailer encuesta leía tabla base sin ver resolución hasta siguiente Compress causando retraso ciclo encuesta. Ahora todos leen clase entidad versionada.
Ciclo encuesta
Al resolver ticket residente recibe enlace a formulario Survey123 con ID ticket en URL. Script extracción lee nuevas respuestas desde ArcGIS Online; escribe Survey_Responses en geodatabase; si pidió llamada avisa por email al staff que resolvió (resuelto según seguimiento editor con respaldo departamento). Widget gestor muestra calificación y comentarios del ticket.
Seguridad y privacidad
Usuarios anónimos tienen lectura vista pública y pueden crear solo vía proxy. Encabezados IIS sitio configuran HSTS, nosniff y opciones frame. Servicio entidades restringe tipos archivos subida y tamaño. Proxy nunca confía tipo contenido declarado. Retención manejada por tablas resumen que cuentan por mes, categoría y departamento sin IDs ni texto libre; así tickets antiguos con datos personales pueden purgarse programadamente mientras estadísticas permanecen.
Implementación
El manual docs/deployment.md es lista ordenada: construir esquema con script arcpy (primero prueba seca), publicar tres servicios (escritura pública, lectura pública, personal), añadir reglas atributos, colocar widgets en your-extensions y construir apps, poner reglas IIS a nivel sitio, levantar proxy en entorno Python clonado ArcGIS Pro, completar config.py y rac_secrets.py e importar trabajos Programador Tareas. Cada script viene modo prueba activado para no enviar emails reales hasta activar manualmente. Documento solución problemas es tabla síntoma-causa-solución creada migrando sistema entre servidores: error IIS HTML 500 indica proxy no escucha; 404 indica ARR no instalado; fallo CORS indica URL widget distinto origen página; error Programador Tareas Windows Server 2025 indica trabajo apunta Python base ArcGIS Pro no clonado.
Los zips del widget están adjuntos a cada release GitHub. Se aceptan issues y pull requests en el repositorio.