Sube la foto de un croquis a mano, un DXF de AutoCAD o un SVG y deja que se reconozcan las estancias; o dibújalas sobre la retícula. Ponles uso, acabados y puntos domóticos, y el 3D se levanta solo. Lo que exportas abajo es exactamente el modelo de datos que consume el resto de la plataforma.
La escala sale de aquí. Si el croquis lleva una cota escrita, ajústala a esa medida.
Súbela si el trazo va torcido; bájala si junta paredes que están separadas.
El DXF trae coordenadas reales: aquí no hay que calibrar nada.
Un muro en CAD son dos líneas paralelas. Esto las funde en un solo eje.
Descarta cotas, mobiliario y textos sueltos.
Las estancias entran como borrador: repásalas siempre antes de anclar puntos.
Lo de arriba es funcional: dibuja, mide, extruye, ancla y exporta. Es deliberadamente el recorrido más corto que ya cuenta la historia completa al cliente —del papel al gemelo operativo en un par de minutos— y sirve como banco de pruebas de la única métrica que decide si el negocio funciona: minutos por punto anclado.
Lo que hace el prototipo y el producto tendrá que hacer mejor:
| Capacidad | En el prototipo | En el MVP |
|---|---|---|
| Trazado | Contornos que mezclan tramos rectos y curvos (bulge como el DXF), retícula de 0,25 m e imán a vértice | Muros como polilíneas con detección automática de recintos (grafo planar), arcos incluidos |
| Visualización | Dos modos: la planta activa con todo el detalle, o el edificio entero por código de color con balizas de alarma | Secciones verticales, filtro por servicio y encuadre guardado por usuario |
| Importación | Foto o escaneado, DXF de AutoCAD y SVG | Añade PDF vectorial acotado; el DWG se resuelve pidiendo exportación a DXF |
| Acabados | Seis suelos y cinco muros generados por código, con color por estancia | Subir la foto del material real del cliente y biblioteca compartida entre edificios |
| Plano de fondo | Foto del croquis reconocida y usada como calco; escala por ancho total | PDF multipágina, calibrado por cota y corrección de perspectiva |
| Muros | Espesor único de 20 cm generado del contorno | Tipos de muro, medianeras compartidas, huecos de puerta y ventana |
| Anclaje | Al centroide de la estancia | Posición libre, importación del .knxproj y emparejamiento por nombre |
| Datos | Simulación local en el navegador | WebSocket contra el gateway del edificio |
| Persistencia | Ninguna: vive en memoria | PostgreSQL + PostGIS con versionado de modelo |
Todo el resto depende de que dos muros que deberían tocarse se toquen exactamente. El orden importa: primero se busca vértice existente dentro del radio de imán, y solo si no hay se redondea a retícula. Al revés se producen huecos de un cuarto de metro que luego rompen la detección de recintos.
function snap(p, vertices, opts = { grid: 0.25, magnet: 0.45 }) {
let best = null, bestD = opts.magnet;
for (const v of vertices) {
const d = Math.hypot(v.x - p.x, v.y - p.y);
if (d < bestD) { bestD = d; best = v; }
}
if (best) return { x: best.x, y: best.y, snapped: 'vertex' };
const g = opts.grid;
return { x: Math.round(p.x / g) * g, y: Math.round(p.y / g) * g, snapped: 'grid' };
}
Es la pieza que convierte un dibujo en un edificio, y la que el prototipo esquiva. La solución es de libro de geometría computacional y se implementa en dos o tres días: construir un grafo planar con los segmentos, partirlos en cada intersección, y recorrer las caras internas con el giro más a la izquierda en cada nodo.
space_id del anterior cuyo centroide esté más cerca y cuya área difiera menos de un 20 %. Sin esta regla, mover un muro borraría los anclajes de la estancia.Bibliotecas que evitan escribir lo peor: polygon-clipping para uniones y offsets, martinez-polygon-clipping como alternativa, y earcut para triangular el resultado.
El usuario sube un PDF o una foto del plano, hace clic en los dos extremos de una cota conocida y teclea su medida real. Con eso se resuelve la escala y todo lo que dibuje encima está en metros. Es una funcionalidad de media jornada y es la que convierte el editor en algo usable para un edificio existente sin documentación digital.
// px → metros a partir de dos puntos y una distancia conocida const pxDist = Math.hypot(b.x - a.x, b.y - a.y); const metersPerPixel = knownMeters / pxDist; // el PDF se rasteriza en cliente con pdf.js y se pinta como capa de fondo // la rotación se corrige alineando la cota con el eje X const angle = Math.atan2(b.y - a.y, b.x - a.x);
Tres formatos entran ya por el mismo botón, y los tres desembocan en el mismo sitio: dos familias de líneas —horizontales y verticales— que alimentan un único detector de recintos. Eso es lo que hace barato añadir formatos: el reconocimiento se escribe una vez.
| Formato | Estado | Escala | Qué hace falta |
|---|---|---|---|
| Foto o escaneado JPG · PNG | Funciona | Ancho real que teclea el usuario | Visión: umbral adaptativo y detección de líneas. Es el caso del croquis a mano y del plano en papel |
| DXF de AutoCAD LINE · LWPOLYLINE · POLYLINE | Funciona | Real: viene en el fichero | Parser de códigos de grupo y detección de unidades. Precisión milimétrica, sin calibrar nada |
| SVG vectorial line · rect · polyline · path | Funciona | Ancho real que teclea el usuario | El puente universal: cualquier CAD, Illustrator o Inkscape exporta a SVG |
| PDF vectorial acotado | MVP | Real si se lee la cota | pdf.js y su lista de operadores de dibujo: las líneas salen ya vectoriales, entran por la misma tubería. Un PDF escaneado, en cambio, es una imagen y va por la primera vía |
| DWG | No | — | Formato cerrado. Se pide al cliente que exporte a DXF, que es un clic en AutoCAD y no cuesta licencia |
| IFC | Fase 2 | Real | Otra tubería distinta: trae espacios y GUID, no hace falta detectar nada. Ver el documento de arquitectura |
En un DXF cada muro son dos líneas paralelas, no una. Si no se funden, entre ellas queda una celda de veinte centímetros que el detector leería como una habitación estrechísima. Por eso el panel tiene un «espesor máximo de muro»: agrupa los dos ejes en uno antes de construir la rejilla. Es un parámetro, no un algoritmo nuevo, y es la única concesión que ha hecho falta para que el mismo código sirva para una foto y para AutoCAD.
Corre entero en el navegador, sin servidor ni librerías externas. Son cinco pasos encadenados.
Esta ruta por rejilla evita el grafo planar completo de la sección anterior y, para croquis ortogonales —que es como dibuja a mano todo el mundo—, da el mismo resultado con una décima parte del código.
| Visión clásica en el navegador | Modelo de visión (VLM) | |
|---|---|---|
| Coste por foto | Cero | Céntimos, y recurrente |
| Latencia | Menos de un segundo | 3 a 10 segundos |
| Funciona sin conexión | Sí | No |
| Determinista | Sí: mismos parámetros, mismo resultado | No: dos llamadas iguales pueden diferir |
| Croquis torcido, curvo o sucio | Falla | Robusto |
| Lee lo escrito a mano | No | Sí: nombres de estancia y cotas |
| Deduce el uso de cada estancia | No | Sí |
Primero el clásico, porque resuelve gratis y al instante el croquis ortogonal sobre papel blanco, que es la mayoría. El VLM entra como segunda pasada, solo cuando el clásico no cierra ningún recinto o cuando el usuario pulsa «no es esto» —y ahí aporta lo que el otro no puede: leer «salón», «cocina» y «3,20» escritos a mano, y devolver nombres, usos y la cota de calibrado. Así el coste variable solo aparece en los croquis difíciles.
Con una condición: la geometría del VLM se regulariza siempre en local —ortogonalizar, cerrar polígonos, imantar a la retícula de 0,25 m— porque un modelo de lenguaje da coordenadas aproximadas, nunca exactas.
// Segunda pasada: contrato estricto de salida del VLM
{
"spaces": [
{ "name": "salón", "use": "living", "confidence": 0.86,
"polygon": [[0.06,0.09],[0.36,0.09],[0.36,0.58],[0.06,0.58]] } // normalizado 0-1
],
"dimensions": [
{ "text": "12,50 m", "value_m": 12.5, "spans": "ancho total" } // calibra la escala
],
"notes": "croquis ortogonal, 5 recintos, cota en el borde superior"
}
En el plan, la vía clásica cae en el sprint 3 —ya está validada aquí arriba— y la segunda pasada con VLM en fase 2, cuando haya croquis reales de clientes con los que medir cuántos la necesitan de verdad.
El editor no habla con la base de datos: publica un documento de modelo y el servidor lo valida, lo versiona y deriva de él la geometría 3D. Que el editor sea sustituible es intencionado —mañana el mismo documento puede venir de un IFC o de un escaneo.
POST /v1/buildings/:id/model-versions
{
"source": "sketch",
"editor_version": "0.4.1",
"storeys": [{
"ref": "p00", "name": "Planta baja", "level": 0, "height": 3.0,
"spaces": [{
"ref": "spc_recepcion", "name": "Recepción", "use": "reception",
"footprint": [[0,0],[7,0],[7,4],[0,4]], // metros, sentido antihorario
"wall_thickness": 0.2
}]
}],
"anchors": [
{ "point_ref": "knx:1/1/3", "space_ref": "spc_recepcion", "role": "actuator",
"representation": "luminaire", "position": [3.5, 2.7, 2.0] }
]
}
201 → { "model_version_id": "mv_01J8...", "status": "processing",
"spaces_created": 8, "anchors_bound": 6, "anchors_orphaned": 0 }
El worker responde por webhook cuando la geometría está teselada:
{ "event": "model.ready", "assets": [
{ "storey_ref": "p00", "lod": 0, "uri": "s3://.../p00_lod0.glb", "bytes": 184320 } ] }
El anclaje se guarda contra space_ref, y la posición [x,y,z] es opcional y local a la estancia. Si en la versión siguiente del modelo la estancia cambia de forma, el anclaje sobrevive; si desaparece, el anclaje pasa a una bandeja de huérfanos y el alta no se pierde. Esto es lo que permite que el croquis sea tosco y rehacible sin coste.
El botón Texturas está activo desde el arranque: cada estancia tiene un suelo y un acabado de muro, con valores por defecto según su uso —parquet y yeso en despachos, gres en pasillos, hidráulica y alicatado en aseos, hormigón visto en la sala técnica— y se cambian en el panel derecho, material y color, con un botón para aplicar a toda la planta.
Las texturas se dibujan por código en un canvas de 256 píxeles: vetas de mármol, juntas de gres, hiladas de ladrillo, fibra de moqueta. Ninguna se descarga.
Cuando esto llegue a producción, el siguiente escalón es permitir subir una foto del material real —el gres concreto que va a poner el cliente— y usarla como textura. Es lo mismo, con una imagen en vez de un canvas.
Al activar Estado en vivo las texturas se apagan solas y el suelo pasa a colorearse por temperatura o consumo. Es deliberado: un mapa de calor sobre un parquet no se lee, y un gemelo que intenta ser bonito y analítico a la vez falla en las dos cosas. Son dos vistas del mismo modelo —la de enseñar y la de operar— y el usuario alterna entre ellas con un clic.
Una estancia ha dejado de ser una lista de puntos: es una lista de nodos, y cada nodo lleva un bulge —la tangente de un cuarto del ángulo abarcado, exactamente la convención del DXF—. Cero es un tramo recto; cualquier otro valor es un arco. El polígono que consume el resto del editor —área, extrusión, muros, anclaje, JSON— se tesela de ese contorno, así que nada del código existente ha tenido que enterarse de que ahora hay curvas.
A pone en curvo el siguiente tramo. Clic en el final del arco, mueve el ratón para darle la flecha y clic para fijarla. Rectas y curvas se alternan en el mismo contorno sin cambiar de herramienta.outline con los bulges y footprint ya teselado. El servidor no necesita trigonometría y el editor no pierde la curva al recargar el modelo.La teselación es adaptativa —paso máximo de 12° y cuerda de 35 cm—, de modo que un muro curvo de tres metros son ocho cajas en el 3D y un ábside completo, veinticuatro. Sale más barato que una geometría de revolución y se lleva bien con el muro de espesor constante.
// del arrastre del tirador al bulge, que es lo que se guarda const h = (p.x - mid.x)*n.x + (p.y - mid.y)*n.y; // n = normal izquierda del tramo const bulge = 2*h / chord; // 0 = recto · 1 = semicírculo // y de vuelta, para teselar const R = (chord*chord/4 + h*h) / (2*Math.abs(h)); const steps = Math.ceil(Math.max(Math.abs(sweep)/0.21, R*Math.abs(sweep)/0.35));
Las plantas semitransparentes apiladas no se leen: el ojo no sabe decir si una mancha roja está en la tercera o en la quinta. Por eso el visor tiene ahora dos modos, y ninguno de los dos es un compromiso entre los otros.
Un clic sobre cualquier planta de la vista de edificio la convierte en la activa, así que el recorrido natural —ver la alarma arriba, bajar al detalle— cuesta un clic. Con el estado en vivo apagado, el color es el uso de la estancia; encendido, es la temperatura o el consumo, y las balizas marcan lo que se sale de rango o el sensor de humos o fuga que ha saltado.
Un edificio de ocho plantas con cuarenta puntos por planta son trescientos marcadores en pantalla: a esa densidad no se distingue ninguno. La vista de vigilancia renuncia a propósito a los elementos, a las texturas y a los rótulos, y se queda con lo único que hay que reconocer de un vistazo y desde lejos: qué planta, qué estancia y de qué color. El detalle está a un clic, que es exactamente donde tiene que estar.
| Pieza | Elección | Por qué esta y no otra |
|---|---|---|
| Lienzo 2D | Canvas 2D nativo | Un editor de plantas es dibujo vectorial simple; ni SVG ni una librería de diagramas aportan nada y ambas estorban al llegar a mil segmentos |
| Motor 3D | three.js | MIT, el mismo motor del visor de explotación: una sola escena que mantener |
| Geometría de polígonos | polygon-clipping | Uniones, diferencias y offsets robustos con coordenadas en punto flotante |
| Triangulación | earcut | Losas y techos; es lo que usa three.js por dentro, sin dependencia nueva real |
| Croquis desde foto | Código propio, sin dependencias | Umbral adaptativo, rejilla y union-find: 300 líneas de JavaScript, cero coste por uso y determinista. Segunda pasada con VLM solo para los croquis que fallen |
| DXF y SVG | Parsers propios | El DXF son pares código-valor: se lee sin librería. El SVG, con DOMParser del navegador. Ambos alimentan el mismo detector de recintos |
| Materiales | Canvas 2D → CanvasTexture | Texturas generadas por código: cero peso de red, cero licencias que auditar y tintado por parámetro |
| PDF vectorial | pdf.js | Su lista de operadores da las líneas ya vectoriales; el PDF escaneado va por la vía de imagen |
| Estado del editor | Zustand + patch inmutable | Deshacer y rehacer salen gratis si cada acción es un parche; imprescindible en un editor |
| Teselado servidor | Node + gltf-transform | Compresión Draco y Meshopt, generación de LOD y miniatura de planta |
| Persistencia | PostgreSQL + PostGIS | footprint como POLYGON permite consultas espaciales reales: qué hay dentro, qué toca con qué |
Un desarrollador con dedicación completa, más medio perfil de producto para las pruebas con usuario. Cada sprint termina con algo que se puede enseñar a un gestor de fincas.
.knxproj y descubrimiento ModbusNo es una lista de funcionalidades: es que un técnico vuestro, sin acompañamiento, dé de alta un edificio de cuatro plantas con cuarenta puntos en menos de tres horas, y que un gestor que nunca ha visto el producto encuentre la sala más caliente del edificio en menos de treinta segundos. Si eso se cumple, hay producto; si no, ninguna funcionalidad adicional lo arregla.