Más allá de la galería digital — convierte tus fotos favoritas en objetos únicos que duran generaciones.
Álbum impreso
Álbum Clásico
El álbum perfecto para guardar los recuerdos de tu sesión. Diseño personalizado, impresión de alta resolución y acabado profesional.
Formato 20×20 cm, capa dura
20 páginas en papel mate premium
Diseño personalizado incluido
Entrega en 2–3 semanas
Más popular
Álbum de lujo
Álbum Premium
El regalo perfecto. Capa en cuero sintético con grabado personalizado, papel de alta gramatura y presentación en caja regalo.
Formato 30×30 cm, capa cuero
40 páginas, papel fotográfico 300g
Grabado de nombre en la tapa
Caja regalo incluida
Entrega en 2–3 semanas
Impresiones
Pack de Fotos
Impresiones profesionales en papel fotográfico premium. Ideales para enmarcar y decorar tu hogar con momentos únicos.
10 fotos formato 15×20 cm
Papel fotográfico mate o brillante
Laminado protector incluido
Entrega en 1 semana
🖼
Foto enmarcada
Marco de madera · 20×30 cm
Desde$35
🗓
Calendario
12 meses · Fotos personalizadas
Desde$25
🃏
Tarjetas
Pack 20 und · Navidad / Cumpleaños
Desde$20
🪞
Canvas
Impresión en lienzo · 40×60 cm
Desde$55
Nuestra historia
Fotografía con alma
Soy Bruno Freitas, fotógrafo brasileño que encontró en Panamá el escenario perfecto para contar historias a través de la fotografía. Mi pasión es encontrar la luz perfecta y el instante auténtico que convierte cada imagen en un recuerdo eterno.
Bruno capturó cada detalle de nuestra sesión de maternidad con una sensibilidad increíble. Las fotos son un tesoro que guardaremos para siempre.
— María González · Maternidad
★★★★★
Las fotos de la graduación de mi hija quedaron espectaculares. Profesional, puntual y con un ojo artístico muy especial. 100% recomendado.
— Carlos & Ana Rodríguez · Graduación
★★★★★
Contratamos a PhotoLife Panama para las fotos de producto de nuestra tienda y el resultado superó todas las expectativas. Ventas aumentaron notablemente.
— Laura Martínez · Fotografia Comercial
¿Listo para empezar?
Reserva tu sesión
Contáctanos por WhatsApp o Instagram. Respondemos en menos de 24 horas y te asesoramos sin compromiso.
──
(function () {
'use strict';
// ── CONFIGURAÇÃO ─────────────────────────────────────────────
var SUPABASE_URL = 'https://tglzryglrdcwjfnbznow.supabase.co';
var SUPABASE_KEY = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InRnbHpyeWdsemRjd2pmbnpub3ciLCJyb2xlIjoiYW5vbiIsImlhdCI6MTc0OTM4MDUxMiwiZXhwIjoyMDY0OTU2NTEyfQ.JqyTNJmOJCUj0iANO2tC6_6r9_UhVbZ3VaK89LMc32g'; // anon key pública
// Inicializa o cliente Supabase
var db = null;
window.getDB = function getDB() {
if (!db) {
if (typeof supabase !== 'undefined') {
db = supabase.createClient(SUPABASE_URL, SUPABASE_KEY);
}
}
return db;
}
// ── REGISTRO DE CLIENTE ───────────────────────────────────────
//
// Esta função é chamada quando um novo usuário se registra.
// Substitui o setTimeout simulado no handleRegister() original.
//
window.supabaseRegistrarCliente = async function (nombre, correo, telefono) {
var client = getDB();
if (!client) { console.error('Supabase não inicializado'); return false; }
var { data, error } = await client
.from('clientes')
.insert({
nombre: nombre,
correo: correo,
telefono: telefono || null,
// fecha_nac, notas — opcionais, podem ser atualizados depois
})
.select()
.single();
if (error) {
// Erro de chave duplicada = cliente já existe (pode ser re-login)
if (error.code === '23505') {
console.log('Cliente já existe no banco:', correo);
return true; // não é falha, apenas atualização
}
console.error('Erro ao registrar cliente:', error.message);
return false;
}
console.log('Cliente registrado com sucesso:', data.id);
return true;
};
// ── BUSCAR SESSÕES DO CLIENTE NO SUPABASE ────────────────────
//
// Busca as sessões fotográficas do cliente logado.
// Retorna array no mesmo formato que PL_SESSIONS do site original.
//
window.supabaseBuscarSessoes = async function (correo) {
var client = getDB();
if (!client) return [];
// 1. Busca o cliente pelo e-mail
var { data: clienteData, error: clienteError } = await client
.from('clientes')
.select('id, nombre')
.eq('correo', correo.toLowerCase().trim())
.single();
if (clienteError || !clienteData) {
console.log('Cliente não encontrado no banco:', correo);
return [];
}
// 2. Busca as sessões vinculadas ao cliente
var { data: sessoes, error: sessoesError } = await client
.from('sesiones')
.select('*')
.eq('cliente_id', clienteData.id)
.order('fecha_sesion', { ascending: false });
if (sessoesError) {
console.error('Erro ao buscar sessões:', sessoesError.message);
return [];
}
// 3. Converte para o formato esperado pelo site (compatível com PL_SESSIONS)
return (sessoes || []).map(function (s) {
return {
id: s.id,
name: clienteData.nombre,
subtitle: s.tipo_sesion || '',
category: traduzirTipoSessao(s.tipo_sesion),
date: formatarData(s.fecha_sesion),
allowedEmails: [correo.toLowerCase().trim()],
photos: [], // as fotos vêm do Storage separadamente
enlaceGaleria: s.enlace_galeria || null,
estadoPago: s.estado_pago || 'pendiente',
notas: s.notas || '',
};
});
};
// ── BUSCAR URLs DAS FOTOS (Storage) ──────────────────────────
//
// Para cada sessão, busca os arquivos no bucket fotos-clientes/{cliente_id}/
// e gera URLs temporárias (válidas por 1 hora).
//
window.supabaseGetFotosSessao = async function (clienteId) {
var client = getDB();
if (!client) return [];
// Lista arquivos da pasta do cliente
var { data: arquivos, error } = await client.storage
.from('fotos-clientes')
.list(clienteId + '/', { limit: 500 });
if (error || !arquivos || arquivos.length === 0) return [];
// Filtra apenas imagens (não pastas)
var imgs = arquivos.filter(function (f) {
return f.name && /\.(jpg|jpeg|png|webp)$/i.test(f.name);
});
// Gera URLs assinadas (1 hora = 3600 segundos)
var urls = await Promise.all(imgs.map(async function (f) {
var caminho = clienteId + '/' + f.name;
var { data: signed } = await client.storage
.from('fotos-clientes')
.createSignedUrl(caminho, 3600);
return {
src: signed ? signed.signedUrl : '',
caption: f.name.replace(/\.[^.]+$/, '').replace(/[-_]/g, ' '),
date: f.created_at ? f.created_at.slice(0, 10) : '2025-01-01',
fav: false,
};
}));
return urls.filter(function (u) { return u.src; });
};
// ── UPLOAD DE FOTO (uso pelo admin/Bruno) ────────────────────
//
// Sobe uma foto para o bucket fotos-clientes/{clienteId}/
// Exemplo de uso no painel admin:
// await supabaseUploadFoto('uuid-do-cliente', arquivo);
//
window.supabaseUploadFoto = async function (clienteId, arquivo) {
var client = getDB();
if (!client) return null;
var caminho = clienteId + '/' + Date.now() + '_' + arquivo.name;
var { error } = await client.storage
.from('fotos-clientes')
.upload(caminho, arquivo, { upsert: false });
if (error) {
console.error('Erro no upload:', error.message);
return null;
}
return caminho;
};
// ── INTEGRAÇÃO COM O SITE EXISTENTE ──────────────────────────
//
// Intercepta o finishLogin() original para:
// 1. Registrar/confirmar o cliente no Supabase
// 2. Carregar as sessões do banco em vez do PL_SESSIONS hardcoded
//
var _originalFinishLogin = window.finishLogin;
window.finishLogin = async function (user) {
if (!user || !user.email) {
if (_originalFinishLogin) _originalFinishLogin(user);
return;
}
// Registra o cliente (se não existir) — sem bloquear o login
supabaseRegistrarCliente(
user.name || user.email.split('@')[0],
user.email,
null
).catch(function (e) { console.error('Registro silencioso falhou:', e); });
// Carrega as sessões do Supabase
try {
var sessoesDB = await supabaseBuscarSessoes(user.email);
if (sessoesDB && sessoesDB.length > 0) {
// Substitui temporariamente PL_SESSIONS pelas sessões do banco
window._supabaseSessoes = sessoesDB;
// Adiciona as fotos de cada sessão
for (var i = 0; i < sessoesDB.length; i++) {
var s = sessoesDB[i];
// Busca fotos somente se não houver enlace_galeria externo
if (!s.enlaceGaleria) {
try {
var fotos = await supabaseGetFotosSessao(s.id);
sessoesDB[i].photos = fotos;
} catch (e) {
// sem fotos — normal para sessões novas
}
}
}
// Substitui a função getSessionsForUser temporariamente
window._originalGetSessions = window.getSessionsForUser;
window.getSessionsForUser = function (email) {
if (!email) return [];
var lower = email.toLowerCase().trim();
if (lower === (window.ADMIN_EMAIL || 'brunofreitas.oessj@gmail.com').toLowerCase()) {
return window._supabaseSessoes || [];
}
return (window._supabaseSessoes || []).filter(function (s) {
return s.allowedEmails.some(function (e) {
return e.toLowerCase().trim() === lower;
});
});
};
}
} catch (e) {
console.log('Supabase não disponível, usando dados locais:', e.message);
}
// Continua o fluxo original de login
if (_originalFinishLogin) {
_originalFinishLogin(user);
}
};
// ── INTERCEPTA HANDLEREGISTER ─────────────────────────────────
//
// Substitui o setTimeout simulado por cadastro real no Supabase.
//
var _originalHandleRegister = window.handleRegister;
window.handleRegister = async function () {
var name = document.getElementById('reg-name') ? document.getElementById('reg-name').value.trim() : '';
var email = document.getElementById('reg-email') ? document.getElementById('reg-email').value.trim() : '';
var pw = document.getElementById('reg-password')? document.getElementById('reg-password').value : '';
var confirm = document.getElementById('reg-confirm') ? document.getElementById('reg-confirm').value : '';
var terms = document.getElementById('reg-terms') ? document.getElementById('reg-terms').checked : false;
// Validações básicas — mantém o comportamento original
var showMsg = window.showMsg || function(){};
if (!name) return showMsg('register-msg','error','Por favor ingresa tu nombre.');
if (!email || !email.includes('@')) return showMsg('register-msg','error','Ingresa un correo válido.');
if (pw.length < 8) return showMsg('register-msg','error','La contraseña debe tener al menos 8 caracteres.');
if (pw !== confirm) return showMsg('register-msg','error','Las contraseñas no coinciden.');
if (!terms) return showMsg('register-msg','error','Debes aceptar los términos y condiciones.');
var btn = document.querySelector('#form-register .btn-submit span');
if (btn) btn.textContent = 'Creando cuenta...';
// Registra no Supabase
var ok = await supabaseRegistrarCliente(name, email, null);
if (btn) btn.textContent = 'Crear mi cuenta';
if (ok) {
// Login automático após registro
var user = { name: name, email: email, picture: '' };
window.finishLogin(user);
} else {
showMsg('register-msg', 'error', 'Error al crear la cuenta. Intenta de nuevo.');
}
};
// ── HELPERS ──────────────────────────────────────────────────
function traduzirTipoSessao(tipo) {
var mapa = {
familia: 'Familia',
maternidad: 'Maternidad',
graduacion: 'Graduación',
comercial: 'Comercial',
};
return mapa[tipo] || (tipo ? tipo.charAt(0).toUpperCase() + tipo.slice(1) : 'Sesión');
}
function formatarData(dateStr) {
if (!dateStr) return '';
try {
var d = new Date(dateStr + 'T12:00:00');
return d.toLocaleDateString('es-PA', { day:'2-digit', month:'short', year:'numeric' });
} catch (e) {
return dateStr;
}
}
console.log('✅ Supabase PhotoLife: integração carregada.');
})();
// ═══════════════════════════════════════════════════════════════
// ONDE INSERIR NO SEU index.html:
//
// 1. No
, ANTES de qualquer outro
//
// 2. O conteúdo deste arquivo vai dentro de uma tag que fecha
// o bloco principal do site (perto da linha 3400 do seu HTML)
//
// Ficará assim:
// ... código existente ...
// function finishLogin(user) { ... }
//
//
//
//
//
// ═══════════════════════════════════════════════════════════════
// ═══════════════════════════════════════════════════════════════
// CORREÇÃO: guardarPerfil() — salva nome e telefone no Supabase
// Substitui a função original que só mostrava mensagem falsa.
// ═══════════════════════════════════════════════════════════════
window.guardarPerfil = async function () {
var msg = document.getElementById('perfil-msg');
var nombre = document.getElementById('perfil-nombre') ? document.getElementById('perfil-nombre').value.trim() : '';
var telefono = document.getElementById('perfil-tel') ? document.getElementById('perfil-tel').value.trim() : '';
var correo = document.getElementById('perfil-email') ? document.getElementById('perfil-email').value.trim() : '';
function showPerfilMsg(tipo, texto) {
if (!msg) return;
var cor = tipo === 'ok'
? 'background:rgba(112,192,144,0.1);border-left:2px solid #70c090;color:#70c090;'
: 'background:rgba(220,80,80,0.1);border-left:2px solid #dc5050;color:#dc5050;';
msg.style.cssText = 'display:block;' + cor + 'padding:10px 16px;font-size:12px;margin-top:16px;';
msg.textContent = texto;
if (tipo === 'ok') setTimeout(function () { msg.style.display = 'none'; }, 3000);
}
if (!nombre) return showPerfilMsg('erro', 'El nombre no puede estar vacío.');
if (!correo) return showPerfilMsg('erro', 'No se encontró el correo del usuario.');
var client = getDB ? getDB() : null;
if (!client) return showPerfilMsg('erro', 'Sin conexión con la base de datos.');
showPerfilMsg('ok', 'Guardando...');
var { error } = await client
.from('clientes')
.update({
nombre: nombre,
telefono: telefono || null,
})
.eq('correo', correo.toLowerCase().trim());
if (error) {
console.error('Erro ao salvar perfil:', error.message);
return showPerfilMsg('erro', 'Error al guardar. Intenta de nuevo.');
}
// Atualiza o nome exibido no dashboard sem recarregar
if (window.dbCurrentUser) {
window.dbCurrentUser.name = nombre;
var dbProfileName = document.getElementById('dbProfileName');
if (dbProfileName) dbProfileName.textContent = nombre;
var dbWelcomeName = document.getElementById('dbWelcomeName');
if (dbWelcomeName) dbWelcomeName.textContent = nombre.split(' ')[0];
var dbNameChip = document.getElementById('dbNameChip');
if (dbNameChip) dbNameChip.textContent = nombre.split(' ')[0];
var navName = document.getElementById('navUserDisplayName');
if (navName) navName.textContent = nombre.split(' ')[0];
}
showPerfilMsg('ok', '✓ Perfil actualizado correctamente.');
};
// ═══════════════════════════════════════════════════════════════
// CORREÇÃO: carrega o telefone do banco ao abrir o dashboard
// ═══════════════════════════════════════════════════════════════
var _originalDbRefreshUserUI = window.dbRefreshUserUI;
window.dbRefreshUserUI = async function () {
// Executa o refresh original primeiro
if (_originalDbRefreshUserUI) _originalDbRefreshUserUI();
// Busca telefone do banco e preenche o campo
if (!window.dbCurrentUser || !window.dbCurrentUser.email) return;
var client = window.getDB ? window.getDB() : null;
if (!client) return;
var { data } = await client
.from('clientes')
.select('telefono, nombre')
.eq('correo', window.dbCurrentUser.email.toLowerCase().trim())
.single();
if (data) {
var telField = document.getElementById('perfil-tel');
if (telField && data.telefono) telField.value = data.telefono;
// Atualiza nome se veio do banco (caso Google login)
var nomeField = document.getElementById('perfil-nombre');
if (nomeField && data.nombre) nomeField.value = data.nombre;
}
};
Deja un comentario a Bruno
¿Tienes alguna solicitud, retoque o simplemente quieres compartir tu experiencia? Bruno responderá en menos de 24 horas.