Normalización de texto para interpretación

Overview

Este archivo define utilidades puras de normalización de texto para el léxico inmobiliario. Su objetivo principal es producir una forma comparable del texto y, al mismo tiempo, conservar el origen exacto de cada coincidencia dentro del texto original.

La lógica está pensada para coincidencias deterministas en español. Convierte a minúsculas, elimina diacríticos, colapsa espacios y aplica reemplazos puntuales como ² -> 2 y ³ -> 3. También mantiene un mapa de posiciones para poder reconstruir fragmentos exactos del texto original.

[!NOTE] La normalización conserva correspondencia por unidades UTF-16. Eso permite reportar fragmentos exactos aunque el texto contenga emojis, símbolos suplementarios o marcas combinantes.

How it works

flowchart TD
    Source[Texto de entrada] --> NormalizeForMatching[normalizeForMatching]
    NormalizeForMatching --> NormalizeCharacter[normalizeCharacter]
    NormalizeCharacter --> CharacterMap[Reemplazos y eliminacion de marcas]
    NormalizeForMatching --> PositionMap[Mapa starts y ends]
    PositionMap --> OriginalSpan[toOriginalSpan]
    NormalizeForMatching --> NormalizedText[Texto normalizado]
    NormalizedText --> NormalizeTerm[normalizeTerm]
    LetterCheck[isLetter] --> LetterClass[Letra Unicode]

El flujo principal entra por normalizeForMatching. Esa función recorre el texto original carácter por carácter, normaliza cada unidad y guarda la posición de origen en dos arreglos paralelos. Luego normalizeTerm reutiliza el mismo proceso, pero devuelve solo la forma normalizada.

Cuando se necesita recuperar un fragmento original, toOriginalSpan traduce un rango del texto normalizado al rango equivalente en el texto fuente. La función isLetter completa el módulo con una comprobación Unicode de letras.

Key components

{
    "title": "normalizeForMatching",
    "content": "[badge:public] Normaliza texto para comparaci\u00f3n y devuelve la cadena normalizada junto con los mapas de inicio y fin hacia el texto original.",
    "type": "function",
    "filePath": "services/api/src/modules/interpretation/text-normalization.js",
    "badges": [
        "public"
    ]
}
{
    "title": "normalizeTerm",
    "content": "[badge:public] Devuelve solo la forma normalizada de un t\u00e9rmino del l\u00e9xico, sin conservar el mapa de posiciones.",
    "type": "function",
    "filePath": "services/api/src/modules/interpretation/text-normalization.js",
    "badges": [
        "public"
    ]
}
{
    "title": "toOriginalSpan",
    "content": "[badge:public] Convierte un rango del texto normalizado en el rango equivalente del texto original usando los mapas de posiciones.",
    "type": "function",
    "filePath": "services/api/src/modules/interpretation/text-normalization.js",
    "badges": [
        "public"
    ]
}
{
    "title": "isLetter",
    "content": "[badge:public] Comprueba si un car\u00e1cter es una letra Unicode seg\u00fan la propiedad general \\p{L}.",
    "type": "function",
    "filePath": "services/api/src/modules/interpretation/text-normalization.js",
    "badges": [
        "public"
    ]
}

Data & fields

[
    {
        "name": "COMBINING_MARKS",
        "type": "RegExp",
        "required": true,
        "description": "Expresión regular que elimina marcas combinantes Unicode en el rango U+0300 a U+036F."
    },
    {
        "name": "WHITESPACE",
        "type": "RegExp",
        "required": true,
        "description": "Expresión regular que detecta cualquier espacio en blanco."
    },
    {
        "name": "LETTER",
        "type": "RegExp",
        "required": true,
        "description": "Expresión regular Unicode para detectar letras con la propiedad general \\p{L}."
    },
    {
        "name": "CHARACTER_REPLACEMENTS",
        "type": "Object",
        "required": true,
        "description": "Mapa inmutable de reemplazos puntuales antes de aplicar la normalización NFD."
    },
    {
        "name": "normalized",
        "type": "string",
        "required": true,
        "description": "Texto comparable resultante de la normalización."
    },
    {
        "name": "starts",
        "type": "number[]",
        "required": true,
        "description": "Posiciones de inicio, en el texto original, para cada unidad UTF-16 del texto normalizado."
    },
    {
        "name": "ends",
        "type": "number[]",
        "required": true,
        "description": "Posiciones de fin, en el texto original, para cada unidad UTF-16 del texto normalizado."
    }
]
[
    {
        "name": "normalized",
        "type": "string",
        "required": true,
        "description": "Cadena final normalizada. Está en minúsculas, sin diacríticos y con espacios colapsados."
    },
    {
        "name": "starts",
        "type": "number[]",
        "required": true,
        "description": "Cada posición apunta al índice de inicio en el texto original."
    },
    {
        "name": "ends",
        "type": "number[]",
        "required": true,
        "description": "Cada posición apunta al índice de fin exclusivo en el texto original."
    }
]

Behaviors

  • normalizeCharacter — Aplica reemplazos puntuales, descompone el carácter con NFD, elimina marcas combinantes y convierte a minúsculas. Devuelve la forma normalizada de ese carácter.
  • normalizeForMatching — Recorre el texto de entrada, colapsa espacios consecutivos, normaliza cada carácter y construye el mapa de posiciones hacia el original. Devuelve normalized, starts y ends.
  • normalizeTerm — Usa normalizeForMatching y devuelve solo normalized.
  • toOriginalSpan — Recibe un rango [inicio, fin) del texto normalizado y devuelve el rango equivalente en el texto original.
  • isLetter — Devuelve true cuando el carácter recibido es una letra Unicode no vacía.

[!IMPORTANT] toOriginalSpan usa normalizedEnd - 1 para leer el último índice del rango normalizado. Eso encaja con el esquema de mapeo por unidad UTF-16 del módulo.

How normalization behaves

1. Leer el texto fuente | El módulo recibe una cadena o un valor no cadena.
2. Convertir la entrada | Si no es string, se usa una cadena vacía.
3. Recorrer cada carácter | El texto se itera por unidades de texto originales.
4. Normalizar el carácter | Se aplica reemplazo, NFD, eliminación de marcas y minúsculas.
5. Colapsar espacios | Los espacios consecutivos se reducen a uno solo.
6. Registrar posiciones | Se guardan índices de inicio y fin por unidad de salida.
7. Ajustar el cierre | Se elimina un espacio final si quedó al final del resultado.
8. Exponer el resultado | Se devuelve el texto normalizado y su trazabilidad hacia el original.

Internal behavior details

normalizeCharacter

Esta función encapsula la normalización local de un solo carácter. Primero consulta CHARACTER_REPLACEMENTS, luego ejecuta normalize("NFD") y finalmente elimina marcas combinantes con COMBINING_MARKS.

normalizeForMatching

Esta función es el núcleo del módulo. Trata los espacios como separadores colapsables, y no como contenido relevante. Cuando una marca combinante se normaliza a cadena vacía, se absorbe dentro del carácter base anterior.

toOriginalSpan

La función traduce posiciones del texto comparable al texto original. Eso permite devolver fragmentos exactos del texto escrito por la persona, no solo la forma normalizada.

isLetter

La comprobación usa \p{L} con soporte Unicode. Eso permite distinguir letras de otros caracteres de forma consistente con idiomas que usan diacríticos o alfabetos extendidos.

Gotchas & edge cases

[!WARNING] La entrada no string se convierte en cadena vacía. El módulo no lanza error por ese caso.

[!WARNING] Los espacios iniciales se descartan y los espacios finales se eliminan al final del proceso.

[!NOTE] Las marcas combinantes se asocian al carácter base anterior. No se conservan como unidades independientes en el texto normalizado.

[!CAUTION] starts y ends operan por unidades UTF-16, no por puntos de código abstractos. Eso importa en símbolos suplementarios y emojis.

[!NOTE] isLetter solo comprueba letras Unicode. Los dígitos no son letras y quedan fuera de esa categoría.

Este módulo forma parte de services/api/src/modules/interpretation/ y apoya la comparación textual del léxico inmobiliario. Su salida está orientada a coincidencias deterministas y trazables, no a procesamiento con dependencias externas.

La lógica aquí declarada encaja con la intención indicada en el propio archivo: conservar procedencia textual del contrato y permitir que cualquier coincidencia se reporte como fragmento exacto del texto original.