Breaking

LightBlog

17 nov 2025

import React, { useState } from "react"; // Componente principal de la aplicación export default function App() { const [imageFile, setImageFile] = useState(null); const [base64ImageData, setBase64ImageData] = useState(null); const [prompt, setPrompt] = useState("Hacer que el hombre de la imagen se ponga de pie"); const [generatedImage, setGeneratedImage] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); // "edit" = editar imagen subida, "generate" = crear desde cero const [mode, setMode] = useState("edit"); /** * Maneja el cambio en el input de archivo. * Convierte la imagen seleccionada a base64. */ const handleFileChange = (event) => { const file = event.target.files[0]; if (file) { setImageFile(file); setError(null); setGeneratedImage(null); // Limpiar imagen generada anteriormente const reader = new FileReader(); reader.onloadend = () => { const base64String = reader.result .replace("data:", "") .replace(/^.+,/, ""); setBase64ImageData(base64String); }; reader.onerror = () => { setError("Error al leer el archivo de imagen."); }; reader.readAsDataURL(file); } }; /** * Llama a la API de Gemini para generar una imagen. * Ahora soporta: * - Modo "edit": prompt + imagen subida * - Modo "generate": solo prompt (imagen desde cero) * En ambos casos: estilo Pixar 3D, 4K, formato vertical 9:16 */ const handleGenerateImage = async () => { // Validaciones según el modo if (!prompt) { setError("Por favor, escribe una instrucción."); return; } if (mode === "edit" && !base64ImageData) { setError("En modo EDICIÓN debes subir una imagen."); return; } setLoading(true); setError(null); setGeneratedImage(null); // ⚠️ Pon aquí tu API key (idealmente vía variable de entorno) const apiKey = ""; if (!apiKey) { setError("Falta la API Key de Gemini. Configúrala en el código o en variables de entorno."); setLoading(false); return; } // Se recomienda usar el modelo estable gemini-2.5-flash-image const modelId = "gemini-2.5-flash-image"; const apiUrl = `https://generativelanguage.googleapis.com/v1beta/models/${modelId}:generateContent?key=${apiKey}`; // Añadimos siempre el estilo Pixar 3D, 4K, vertical const styleSuffix = " Estilo Pixar 3D, ultra detallado, calidad 4K, iluminación cinematográfica, formato vertical 9:16 para redes sociales."; const finalPrompt = `${prompt.trim()}. ${styleSuffix}`; // Construimos las "parts" según el modo const parts = [{ text: finalPrompt }]; if (mode === "edit" && base64ImageData && imageFile?.type) { parts.push({ inlineData: { mimeType: imageFile.type, // "image/jpeg" o "image/png" data: base64ImageData, }, }); } const payload = { contents: [ { parts, }, ], generationConfig: { // Solo queremos la imagen de vuelta responseModalities: ["IMAGE"], // Forzamos aspecto vertical (9:16) imageConfig: { aspectRatio: "9:16", }, }, }; try { let response = await fetch(apiUrl, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload), }); if (!response.ok) { const errorBody = await response.json().catch(() => ({})); throw new Error( `Error de la API: ${response.status} - ${ errorBody.error?.message || "Error desconocido" }` ); } const result = await response.json(); const base64Data = result?.candidates?.[0]?.content?.parts?.find( (p) => p.inlineData )?.inlineData?.data; if (base64Data) { const imageUrl = `data:image/png;base64,${base64Data}`; setGeneratedImage(imageUrl); } else { // A veces la respuesta puede ser texto (por ejemplo, si la solicitud no es segura) const textResponse = result?.candidates?.[0]?.content?.parts?.[0]?.text; if (textResponse) { setError( `La API devolvió un mensaje de texto en lugar de una imagen: ${textResponse}` ); } else { setError( "No se pudo generar la imagen. La respuesta de la API no contenía datos de imagen." ); console.error("Respuesta inesperada de la API:", result); } } } catch (err) { setError(`Error al generar la imagen: ${err.message}`); console.error(err); } finally { setLoading(false); } }; return (

Editor de Imagen AI (Pixar 3D · 4K · Vertical)

{/* Selector de modo */}
Modo de trabajo

En ambos modos se genera en estilo Pixar 3D, calidad 4K y formato vertical 9:16.

{/* Paso 1: Subir Imagen (solo relevante para modo edit) */}
{/* Previsualización de Imagen original */} {mode === "edit" && imageFile && base64ImageData && !generatedImage && (
Imagen subida
)} {/* Paso 2: Escribir Instrucción */}