🌿 NN Flora Suite

Cinco herramientas conectadas de forma segura con NN Flora Pro.

🩺 Doctor Visual

Usa una fotografía clara de la planta, sus hojas y su tallo.
Vista previa de la planta
Selecciona una fotografía.
Planta:
Estado:
Riego:
Luz:
Temperatura:
Abono:
Maceta:
Poda:
Plagas:
Señales observadas:
    Plan recomendado:
      Observación visual:

      🔍 Identificador Visual

      Fotografía hojas, tallo, flores o frutos con buena iluminación.
      Vista previa para identificar
      Selecciona una fotografía.
      Nombre:
      Nombre científico:
      Familia y tipo:
      Confianza orientativa:
      Descripción:
      Características visibles:
        Otras posibilidades:
          Origen:
          Hábitat:
          Calidad de la imagen:
          🪴 Cuidados
          Luz:
          Riego:
          Suelo:
          Temperatura:
          Floración:
          🥗 Alimentación y nutrición
          Uso alimentario:
          Partes tradicionalmente utilizadas:
            Usos culinarios conocidos:
              Propiedades nutricionales orientativas:
                Nutrientes destacados:
                  Preparación segura conocida:
                    Precauciones alimentarias:
                      🌿 Usos medicinales tradicionales
                      Situación:
                      Partes tradicionalmente utilizadas:
                        Usos tradicionales descritos:
                          Compuestos conocidos:
                            Nivel de evidencia:
                            Precauciones:
                              Contraindicaciones:
                                Interacciones:
                                  ⚠️ Seguridad
                                  Personas adultas:
                                  Niños:
                                  Embarazo y lactancia:
                                  Perros:
                                  Gatos:
                                  Contacto con la piel:
                                  Polinizadores:
                                    Curiosidades:

                                      🍃 HojaScan

                                      Fotografía la hoja por ambas caras si es posible.
                                      Vista previa de la hoja
                                      Selecciona una fotografía.
                                      Planta:
                                      Estado:
                                      Señales observadas:
                                        Necesidades:
                                          Consejos:
                                            Causas posibles:
                                              Calidad de imagen:

                                              🌡️ ClimaPlanta

                                              La fotografía es opcional y puede ayudar a contextualizar el estado visible.
                                              Vista previa para ClimaPlanta
                                              Introduce los datos y elige frío o calor.
                                              Ubicación:
                                              Planta:
                                              Evaluación:
                                              Orientación:
                                              Temperatura mínima orientativa:
                                              Temperatura máxima orientativa:
                                              Acción recomendada:
                                              Precauciones:
                                                Datos adicionales útiles:

                                                  🌱 PlantSense

                                                  Se combinará con los datos de humedad, hojas, tallo y raíces.
                                                  Vista previa para PlantSense
                                                  Completa los datos y pulsa analizar.
                                                  Planta:
                                                  Estado:
                                                  Necesidad principal:
                                                  Necesidades:
                                                    Señales:
                                                      Consejos:
                                                        Prioridad:
                                                        Explicación:

                                                        🔍 Identificador Visual

                                                        Fotografía hojas, tallo, flores o frutos con buena iluminación.
                                                        Vista previa para identificar
                                                        Selecciona una fotografía.
                                                        Nombre:
                                                        Nombre científico:
                                                        Familia y tipo:
                                                        Confianza orientativa:
                                                        Descripción:
                                                        Características visibles:
                                                          Otras posibilidades:
                                                            Origen:
                                                            Hábitat:
                                                            Calidad de la imagen:
                                                            🪴 Cuidados
                                                            Luz:
                                                            Riego:
                                                            Suelo:
                                                            Temperatura:
                                                            Floración:
                                                            🥗 Alimentación y nutrición
                                                            Uso alimentario:
                                                            Partes tradicionalmente utilizadas:
                                                              Usos culinarios conocidos:
                                                                Propiedades nutricionales orientativas:
                                                                  Nutrientes destacados:
                                                                    Preparación segura conocida:
                                                                      Precauciones alimentarias:
                                                                        🌿 Usos medicinales tradicionales
                                                                        Situación:
                                                                        Partes tradicionalmente utilizadas:
                                                                          Usos tradicionales descritos:
                                                                            Compuestos conocidos:
                                                                              Nivel de evidencia:
                                                                              Precauciones:
                                                                                Contraindicaciones:
                                                                                  Interacciones:
                                                                                    ⚠️ Seguridad
                                                                                    Personas adultas:
                                                                                    Niños:
                                                                                    Embarazo y lactancia:
                                                                                    Perros:
                                                                                    Gatos:
                                                                                    Contacto con la piel:
                                                                                    Polinizadores:
                                                                                      Curiosidades:

                                                                                        🍃 HojaScan

                                                                                        Fotografía la hoja por ambas caras si es posible.
                                                                                        Vista previa de la hoja
                                                                                        Selecciona una fotografía.
                                                                                        Planta:
                                                                                        Estado:
                                                                                        Señales observadas:
                                                                                          Necesidades:
                                                                                            Consejos:
                                                                                              Causas posibles:
                                                                                                Calidad de imagen:

                                                                                                🌡️ ClimaPlanta

                                                                                                La fotografía es opcional y puede ayudar a contextualizar el estado visible.
                                                                                                Vista previa para ClimaPlanta
                                                                                                Introduce los datos y elige frío o calor.
                                                                                                Ubicación:
                                                                                                Planta:
                                                                                                Evaluación:
                                                                                                Orientación:
                                                                                                Temperatura mínima orientativa:
                                                                                                Temperatura máxima orientativa:
                                                                                                Acción recomendada:
                                                                                                Precauciones:
                                                                                                  Datos adicionales útiles:

                                                                                                    🌱 PlantSense

                                                                                                    Se combinará con los datos de humedad, hojas, tallo y raíces.
                                                                                                    Vista previa para PlantSense
                                                                                                    Completa los datos y pulsa analizar.
                                                                                                    Planta:
                                                                                                    Estado:
                                                                                                    Necesidad principal:
                                                                                                    Necesidades:
                                                                                                      Señales:
                                                                                                        Consejos:
                                                                                                          Prioridad:
                                                                                                          Explicación:
                                                                                                          function setBusy( button, busy, normalText, busyText ) { button.disabled = Boolean(busy); button.textContent = busy ? busyText : normalText; } function validateImage( file ) { if (!file) { throw new Error( "Selecciona una fotografía." ); } const allowed = [ "image/jpeg", "image/png", "image/webp" ]; if ( file.type && allowed.indexOf( file.type ) === -1 ) { throw new Error( "La fotografía debe ser JPG, PNG o WebP." ); } if ( Number( file.size || 0 ) > MAX_FILE ) { throw new Error( "La fotografía supera el límite de 10 MB." ); } } function configurePhotoSource( prefix, cameraId, fileId, imageId, previewId, removeId, actionButtonId, statusId, required ) { const cameraInput = $(cameraId); const fileInput = $(fileId); const image = $(imageId); const preview = $(previewId); const removeButton = $(removeId); const actionButton = $(actionButtonId); const status = $(statusId); let currentUrl = ""; function clearPhoto() { selectedPhotos[prefix] = null; cameraInput.value = ""; fileInput.value = ""; if (currentUrl) { URL.revokeObjectURL( currentUrl ); currentUrl = ""; } image.removeAttribute( "src" ); preview.style.display = "none"; removeButton.style.display = "none"; if (required) { actionButton.disabled = true; status.textContent = "Selecciona una fotografía."; } else { actionButton.disabled = false; status.textContent = "Fotografía opcional no seleccionada."; } } function useFile( file, otherInput ) { if (!file) { return; } try { validateImage( file ); } catch (error) { status.textContent = error.message; return; } otherInput.value = ""; selectedPhotos[prefix] = file; if (currentUrl) { URL.revokeObjectURL( currentUrl ); } currentUrl = URL.createObjectURL( file ); image.onload = function() { preview.style.display = "block"; removeButton.style.display = "block"; actionButton.disabled = false; status.textContent = "Fotografía preparada: " + text( file.name, "imagen" ); }; image.onerror = function() { clearPhoto(); status.textContent = "No se pudo abrir la fotografía."; }; image.src = currentUrl; } cameraInput.addEventListener( "change", function() { useFile( cameraInput.files && cameraInput.files[0], fileInput ); } ); fileInput.addEventListener( "change", function() { useFile( fileInput.files && fileInput.files[0], cameraInput ); } ); removeButton.addEventListener( "click", clearPhoto ); if (!required) { actionButton.disabled = false; } } app.querySelectorAll( ".tab" ).forEach( function(button) { button.addEventListener( "click", function() { app.querySelectorAll( ".tab" ).forEach( function(tab) { tab.classList.remove( "on" ); } ); app.querySelectorAll( ".panel" ).forEach( function(panel) { panel.classList.remove( "on" ); } ); button.classList.add( "on" ); const panel = $( button.dataset.panel ); if (panel) { panel.classList.add( "on" ); } } ); } ); configurePhotoSource( "d", "dCamara", "dArchivo", "dImg", "dPrev", "dQuitar", "dBtn", "dStatus", true ); configurePhotoSource( "i", "iCamara", "iArchivo", "iImg", "iPrev", "iQuitar", "iBtn", "iStatus", true ); configurePhotoSource( "h", "hCamara", "hArchivo", "hImg", "hPrev", "hQuitar", "hBtn", "hStatus", true ); configurePhotoSource( "c", "cCamara", "cArchivo", "cImg", "cPrev", "cQuitar", "cFrio", "cStatus", false ); configurePhotoSource( "s", "sCamara", "sArchivo", "sImg", "sPrev", "sQuitar", "sBtn", "sStatus", false ); /* ==================================================== DOCTOR ==================================================== */ $("dBtn").addEventListener( "click", async function() { const button = $("dBtn"); clearError( "d" ); try { const file = selectedPhotos.d; validateImage( file ); setBusy( button, true, "Analizar planta", "Analizando..." ); $("dStatus").textContent = "Analizando la fotografía y los datos aportados..."; $("dResult").style.display = "none"; const form = new FormData(); form.append( "foto", file ); form.append( "nombre", $("dNombre").value ); form.append( "tipo", $("dTipo").value ); form.append( "tierra", $("dTierra").value ); form.append( "ultimoRiego", $("dRiego").value ); form.append( "luz", $("dLuz").value ); form.append( "hojas", $("dHojas").value ); form.append( "maceta", $("dMaceta").value ); form.append( "temperatura", $("dTemp").value ); const data = await send( "/flora/doctor", { method: "POST", body: form } ); const health = number( data.salud, 5, 98 ); $("drNombre").textContent = text( data.nombre, "Planta sin identificar" ); $("drNivel").textContent = text( data.nivel, "Evaluación orientativa" ) + " — " + health + "%"; $("drBar").style.width = health + "%"; $("drRiego").textContent = text( data.riego, "No determinado." ); $("drLuz").textContent = text( data.luz, "No determinada." ); $("drTemp").textContent = text( data.temperatura, "No determinada." ); $("drAbono").textContent = text( data.abono, "No determinado." ); $("drMaceta").textContent = text( data.maceta, "No determinada." ); $("drPoda").textContent = text( data.poda, "No determinada." ); $("drPlagas").textContent = text( data.plagas, "No determinadas." ); fillList( "drSenales", data.senales, "Sin señales adicionales." ); fillList( "drPlan", data.plan, "Sin pasos adicionales." ); $("drObservacion").textContent = text( data.observacionVisual, "Sin observaciones adicionales." ); $("drAdvertencia").textContent = text( data.advertencia, "Esta valoración es orientativa y no confirma enfermedades ni plagas." ); $("dResult").style.display = "block"; $("dStatus").textContent = "✅ Análisis terminado."; } catch (error) { showError( "d", error.message ); } finally { setBusy( button, false, "Analizar planta", "Analizando..." ); button.disabled = !selectedPhotos.d; } } ); /* ==================================================== IDENTIFICADOR ==================================================== */ $("iBtn").addEventListener( "click", async function() { const button = $("iBtn"); clearError( "i" ); try { const file = selectedPhotos.i; validateImage( file ); setBusy( button, true, "Identificar planta", "Identificando..." ); $("iStatus").textContent = "Analizando las características visibles..."; $("iResult").style.display = "none"; const form = new FormData(); form.append( "foto", file ); const data = await send( "/flora/identificar", { method: "POST", body: form } ); const confidence = number( data.confianza, 0, 100 ); const care = data.cuidados && typeof data.cuidados === "object" ? data.cuidados : {}; const food = data.alimentacion && typeof data.alimentacion === "object" ? data.alimentacion : {}; const medicinal = data.medicinal && typeof data.medicinal === "object" ? data.medicinal : {}; const toxicity = data.toxicidad && typeof data.toxicidad === "object" ? data.toxicidad : {}; $("irNombre").textContent = text( data.nombreComun, "Especie no determinada" ); $("irCientifico").textContent = text( data.nombreCientifico, "No determinado" ); $("irFamilia").textContent = [ text( data.familia ), text( data.tipo ) ] .filter(Boolean) .join(" · ") || "No determinado"; $("irConf").textContent = confidence + "%"; $("irBar").style.width = confidence + "%"; $("irDesc").textContent = text( data.descripcion, "Sin descripción disponible." ); fillList( "irCar", data.caracteristicasVisibles, "Sin características suficientes." ); const alternatives = normalizeArray( data.alternativas ).map( function(item) { if ( item && typeof item === "object" ) { const name = text( item.nombreComun || item.nombre, "Alternativa" ); const scientific = text( item.nombreCientifico ); const alternativeConfidence = number( item.confianza, 0, 100 ); const differences = text( item.diferencias ); return ( name + ( scientific ? " (" + scientific + ")" : "" ) + " — " + alternativeConfidence + "%" + ( differences ? ". " + differences : "" ) ); } return text( item ); } ); fillList( "irAlt", alternatives, "Sin alternativas suficientes." ); $("irOrigen").textContent = text( data.origen, "No indicado." ); $("irHabitat").textContent = text( data.habitat, "No indicado." ); $("irCalidad").textContent = text( data.calidadImagen, "No determinada." ); $("irLuz").textContent = text( care.luz, "No indicada." ); $("irRiego").textContent = text( care.riego, "No indicado." ); $("irSuelo").textContent = text( care.suelo, "No indicado." ); $("irTemperatura").textContent = text( care.temperatura, "No indicada." ); $("irFloracion").textContent = text( care.floracion, "No indicada." ); $("irAlimentacion").textContent = food.bloqueado ? "Identificación insuficiente para mostrar información alimentaria aplicable." : food.usoConocido ? "Se conocen usos alimentarios, pero la especie debe confirmarse antes de cualquier consumo." : "No consta un uso alimentario seguro."; fillList( "irPartesAlimentarias", food.bloqueado ? [] : food.partesUsadas, "Sin partes comestibles confirmadas." ); fillList( "irUsosCulinarios", food.bloqueado ? [] : food.usosCulinarios, "Sin usos culinarios confirmados." ); fillOptionalList( "irNutricionRow", "irNutricion", food.bloqueado ? [] : food.propiedadesNutricionales ); fillOptionalList( "irNutrientesRow", "irNutrientes", food.bloqueado ? [] : food.nutrientesDestacados ); fillOptionalList( "irPreparacionRow", "irPreparacion", food.bloqueado ? [] : food.preparacionSegura ); fillList( "irPrecaucionesAlimentarias", food.precauciones, "No consumir sin una identificación botánica fiable." ); $("irMedicinal").textContent = medicinal.bloqueado ? "Identificación insuficiente para mostrar usos medicinales aplicables." : medicinal.usoTradicionalConocido ? "Se describen usos tradicionales; no equivalen a tratamientos médicos." : "No consta un uso medicinal tradicional conocido."; fillList( "irPartesMedicinales", medicinal.bloqueado ? [] : medicinal.partesUsadas, "Sin partes de uso tradicional confirmadas." ); fillList( "irUsosMedicinales", medicinal.bloqueado ? [] : medicinal.usosTradicionales, "Sin usos tradicionales confirmados." ); fillList( "irCompuestos", medicinal.bloqueado ? [] : medicinal.compuestosConocidos, "Sin compuestos mostrados." ); $("irEvidencia").textContent = text( medicinal.nivelEvidencia, "No determinada." ); fillList( "irPrecaucionesMedicinales", medicinal.precauciones, "No utilizar con fines médicos sin asesoramiento sanitario." ); fillList( "irContraindicaciones", medicinal.contraindicaciones, "No determinadas." ); fillList( "irInteracciones", medicinal.interacciones, "No determinadas." ); $("irToxicidadPersonas").textContent = text( toxicity.personas, "No determinada." ); $("irNinos").textContent = text( toxicity.ninos, "No determinado." ); $("irEmbarazo").textContent = text( toxicity.embarazoLactancia, "No determinado." ); $("irPerros").textContent = text( toxicity.perros, "No determinado." ); $("irGatos").textContent = text( toxicity.gatos, "No determinado." ); $("irPiel").textContent = text( toxicity.contactoPiel, "No determinado." ); fillList( "irPolinizadores", data.polinizadores, "No indicados." ); fillList( "irCuriosidades", data.curiosidades, "Sin curiosidades adicionales." ); $("irAdvertencia").textContent = text( data.advertencia, "Nunca consumas ni uses medicinalmente una planta basándote únicamente en una fotografía." ); $("iResult").style.display = "block"; $("iStatus").textContent = "✅ Identificación terminada."; } catch (error) { showError( "i", error.message ); } finally { setBusy( button, false, "Identificar planta", "Identificando..." ); button.disabled = !selectedPhotos.i; } } ); /* ==================================================== HOJASCAN ==================================================== */ $("hBtn").addEventListener( "click", async function() { const button = $("hBtn"); clearError( "h" ); try { const file = selectedPhotos.h; validateImage( file ); setBusy( button, true, "Analizar hoja", "Analizando..." ); $("hStatus").textContent = "Analizando las características visibles de la hoja..."; $("hResult").style.display = "none"; const form = new FormData(); form.append( "foto", file ); form.append( "nombre", $("hNombre").value ); form.append( "tipoHoja", $("hTipo").value ); const data = await send( "/flora/hoja", { method: "POST", body: form } ); const health = number( data.salud, 5, 100 ); $("hrNombre").textContent = text( data.nombre, "Planta no identificada" ); $("hrEstado").textContent = text( data.estado, "Estado no determinado" ) + " — " + health + "%"; $("hrBar").style.width = health + "%"; fillList( "hrSen", data.senales, "Sin señales claras." ); fillList( "hrNec", data.necesidades, "Sin necesidades determinadas." ); fillList( "hrCon", data.consejos, "Sin consejos adicionales." ); fillList( "hrCau", data.posiblesCausas, "Sin causas suficientes." ); $("hrCal").textContent = text( data.calidadImagen, "No determinada." ); $("hrAdvertencia").textContent = text( data.advertencia, "Una fotografía no permite confirmar enfermedades." ); $("hResult").style.display = "block"; $("hStatus").textContent = "✅ Análisis terminado."; } catch (error) { showError( "h", error.message ); } finally { setBusy( button, false, "Analizar hoja", "Analizando..." ); button.disabled = !selectedPhotos.h; } } ); /* ==================================================== CLIMA ==================================================== */ async function analyzeClimate( choice ) { const coldButton = $("cFrio"); const heatButton = $("cCalor"); clearError( "c" ); try { coldButton.disabled = true; heatButton.disabled = true; $("cStatus").textContent = "Preparando una orientación térmica..."; $("cResult").style.display = "none"; let climateOptions; if (selectedPhotos.c) { const form = new FormData(); form.append( "foto", selectedPhotos.c ); form.append( "ciudad", $("cCiudad").value ); form.append( "planta", $("cPlanta").value ); form.append( "temperatura", $("cTemp").value ); form.append( "eleccion", choice ); climateOptions = { method: "POST", body: form }; } else { climateOptions = { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ ciudad: $("cCiudad").value, planta: $("cPlanta").value, temperatura: $("cTemp").value, eleccion: choice }) }; } const data = await send( "/flora/clima", climateOptions ); $("crCiudad").textContent = text( data.ciudad, "No indicada." ); $("crPlanta").textContent = text( data.planta, "No indicada." ); $("crEleccion").textContent = text( data.eleccion, choice ); $("crOri").textContent = text( data.orientacion, "Sin orientación suficiente." ); $("crMin").textContent = text( data.temperaturaMinimaOrientativa, "No determinada." ); $("crMax").textContent = text( data.temperaturaMaximaOrientativa, "No determinada." ); $("crAcc").textContent = text( data.accionRecomendada, "Comprueba la temperatura real antes de mover la planta." ); fillList( "crPre", data.precauciones, "Realiza cualquier cambio de ubicación de forma gradual." ); fillList( "crDatos", data.datosNecesarios, "Comprueba la temperatura y la humedad reales." ); $("crAdvertencia").textContent = text( data.advertencia, "Esta herramienta no consulta automáticamente el pronóstico meteorológico." ); $("cResult").style.display = "block"; $("cStatus").textContent = "✅ Orientación preparada."; } catch (error) { showError( "c", error.message ); } finally { coldButton.disabled = false; heatButton.disabled = false; } } $("cFrio").addEventListener( "click", function() { analyzeClimate( "frio" ); } ); $("cCalor").addEventListener( "click", function() { analyzeClimate( "calor" ); } ); /* ==================================================== PLANTSENSE ==================================================== */ $("sBtn").addEventListener( "click", async function() { const button = $("sBtn"); clearError( "s" ); try { setBusy( button, true, "Analizar datos", "Analizando..." ); $("sStatus").textContent = "Analizando los datos de cultivo..."; $("sResult").style.display = "none"; const body = { nombre: $("sNombre").value, humedad: Number( $("sHum").value ), hojas: $("sHojas").value, tallo: $("sTallo").value, raices: $("sRaices").value, horasLuz: Number( $("sLuz").value ), temperatura: Number( $("sTemp").value ), frecuenciaRiego: $("sRiego").value, plagas: $("sPlagas").value, observaciones: $("sObs").value }; let senseOptions; if (selectedPhotos.s) { const form = new FormData(); form.append( "foto", selectedPhotos.s ); Object.keys( body ).forEach( function(key) { form.append( key, body[key] == null ? "" : String( body[key] ) ); } ); senseOptions = { method: "POST", body: form }; } else { senseOptions = { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify( body ) }; } const data = await send( "/flora/sense", senseOptions ); const health = number( data.salud, 5, 100 ); $("srNombre").textContent = text( data.nombre, "Planta sin nombre" ); $("srEstado").textContent = text( data.estado, "Estado no determinado" ) + " — " + health + "%"; $("srBar").style.width = health + "%"; $("srPrincipal").textContent = text( data.necesidadPrincipal, "No determinada." ); fillList( "srNec", data.necesidades, "Sin necesidades determinadas." ); fillList( "srSen", data.senales, "Sin señales adicionales." ); fillList( "srCon", data.consejos, "Sin consejos adicionales." ); $("srPri").textContent = text( data.prioridad, "baja" ).toUpperCase(); $("srExp").textContent = text( data.explicacion, "Sin explicación adicional." ); $("srAdvertencia").textContent = text( data.advertencia, "Resultado orientativo basado únicamente en los datos introducidos." ); $("sResult").style.display = "block"; $("sStatus").textContent = "✅ Análisis terminado."; } catch (error) { showError( "s", error.message ); } finally { setBusy( button, false, "Analizar datos", "Analizando..." ); } } ); }());