import React, { useState, useEffect, createContext, useContext } from 'react';
import { initializeApp } from 'firebase/app';
import { getAuth, signInAnonymously, onAuthStateChanged, createUserWithEmailAndPassword, signInWithEmailAndPassword, signOut } from 'firebase/auth';
import { getFirestore, doc, getDoc, setDoc, collection, query, where, onSnapshot, addDoc, updateDoc, deleteDoc, getDocs } from 'firebase/firestore';
import { Home, Users, Receipt, History, ListTodo, Menu, LogOut, Plus, Search, Edit, Trash2, Save, Calendar, DollarSign, Image as ImageIcon, X, Lightbulb, Globe } from 'lucide-react'; // Added Globe icon for language selection
// jsPDF and jspdf-autotable are assumed to be loaded globally via CDN in the HTML context.
// This is done to resolve "Could not resolve" errors in the compilation environment.
// For example, by including
// and
// in the HTML file that renders this React app.
// Your web app's Firebase configuration - PROVIDED BY USER
const firebaseConfig = {
apiKey: "AIzaSyANmX9hOkVLVLF_lgzJvP3xI2COS3XUqYs",
authDomain: "app-de-gestion-de-negocios.firebaseapp.com",
projectId: "app-de-gestion-de-negocios",
storageBucket: "app-de-gestion-de-negocios.firebasestorage.app",
messagingSenderId: "70853304594",
appId: "1:70853304594:web:a36c0ee73f3808173413cb",
measurementId: "G-ZVXV4N4CWB"
};
// Extract appId from the provided firebaseConfig
const appId = firebaseConfig.projectId; // Using projectId as appId for consistent collection paths
// Initialize Firebase
const app = initializeApp(firebaseConfig);
const auth = getAuth(app);
const db = getFirestore(app);
// Context for user and authentication state
const AuthContext = createContext(null);
// Utility function to generate a random UUID if userId is not available
const generateUUID = () => crypto.randomUUID();
// Translations object
const translations = {
es: {
appNamePro: "Pro",
appNameApp: "App",
loadingApp: "Cargando aplicación...",
error: "Error",
close: "Cerrar",
confirmation: "Confirmación",
confirm: "Confirmar",
cancel: "Cancelar",
authPageTitle: "ProApp",
authPageSubtitleRegister: "Crea tu cuenta y adapta la app a tu negocio",
authPageSubtitleLogin: "Inicia sesión en tu cuenta",
email: "Correo Electrónico",
password: "Contraseña",
registerNewAccount: "Registrar Nueva Cuenta",
login: "Iniciar Sesión",
alreadyHaveAccount: "¿Ya tienes una cuenta? Inicia Sesión",
noAccount: "¿No tienes una cuenta? Regístrate",
userId: "ID de Usuario:",
notAuthenticated: "No autenticado",
selectBusinessType: "Selecciona tu tipo de negocio",
gardener: "Jardinería y Paisajismo",
plumber: "Plomería",
painter: "Pintura",
cleaner: "Limpieza de Casas",
other: "Otro (General)",
pleaseSelectBusinessType: "Por favor, selecciona un tipo de negocio.",
authenticationError: "Error de autenticación:",
errorAuthSetup: "Error de configuración de autenticación inicial:",
errorFetchingProfile: "Error al cargar perfil de usuario:",
errorLoggingOut: "Error al cerrar sesión:",
dashboardWelcome: "Bienvenido a tu Dashboard,",
businessType: "Tipo de Negocio:",
totalClients: "Clientes Totales",
pendingInvoices: "Facturas Pendientes",
upcomingTasks: "Tareas Próximas",
clientManagement: "Gestión de Clientes",
clientManagementDesc: "Administra la información de tus clientes.",
invoicing: "Facturación",
invoicingDesc: "Crea y gestiona tus facturas profesionales.",
taskManagement: "Gestión de Tareas",
taskManagementDesc: "Organiza y sigue tus tareas pendientes.",
history: "Historial",
historyDesc: "Consulta el registro de actividades y transacciones.",
gardenerTipTitle: "Consejo de Jardinería",
gardenerTipContent: "Recuerda programar riegos profundos para céspedes en verano y fertilizar las plantas de temporada para un crecimiento óptimo.",
plumberTipTitle: "Consejo de Plomería",
plumberTipContent: "Inspecciona regularmente las tuberías en busca de fugas y aconseja a tus clientes sobre el mantenimiento preventivo de calentadores de agua.",
generalBusinessTipTitle: "Consejo General de Negocio",
generalBusinessTipContent: "Mantén tus registros al día y comunícate proactivamente con tus clientes para asegurar su satisfacción.",
searchClient: "Buscar cliente por nombre o dirección...",
addNewClient: "Agregar Nuevo Cliente",
noClientsRegistered: "No hay clientes registrados.",
editClient: "Editar Cliente",
addClient: "Agregar Nuevo Cliente",
fullName: "Nombre Completo",
address: "Dirección",
phone: "Teléfono",
photoUrl: "URL de Foto (Opcional)",
maintenanceFrequency: "Frecuencia de Mantenimiento",
weekly: "Semanal",
biWeekly: "Quincenal",
monthly: "Una vez al mes",
monthlyMaintenanceCost: "Costo Mensual de Mantenimiento ($)",
paymentsReceived: "Pagos Recibidos (Marcar Meses Pagados)",
totalPaid: "Total Pagado:",
totalRemaining: "Total Restante:",
lastServiceDate: "Fecha Último Servicio",
commonIssue: "Problema Común",
saveChanges: "Guardar Cambios",
clientUpdatedSuccess: "Cliente actualizado con éxito.",
clientAddedSuccess: "Cliente agregado con éxito.",
errorSavingClient: "Error al guardar cliente:",
deleteClientConfirm: "¿Estás seguro de que quieres eliminar este cliente? Esta acción no se puede deshacer.",
clientDeletedSuccess: "Cliente eliminado con éxito.",
errorDeletingClient: "Error al eliminar cliente:",
invoiceDesignSection: "Diseño de Factura (Plantilla Básica)",
invoiceDesignInfo: "Actualmente, la aplicación utiliza una plantilla de factura básica. En futuras actualizaciones, se podría implementar un editor visual para personalizar el diseño.",
invoiceIncludes: "La factura incluirá:",
yourBusinessInfo: "Tu información de negocio (placeholder)",
clientInfo: "Información del cliente",
maintenanceDetails: "Detalles de mantenimiento (si aplica)",
extraServicesAdded: "Servicios extra agregados",
totalCalculation: "Cálculo total",
createNewInvoice: "Crear Nueva Factura",
selectClient: "Seleccionar Cliente",
searchClientInvoice: "Buscar cliente...",
clientSelected: "Cliente seleccionado:",
includeMonthlyMaintenance: "Incluir Mantenimiento Mensual:",
selectMonth: "Selecciona Mes",
extraServices: "Servicios Extra",
serviceName: "Nombre del servicio",
cost: "Costo ($)",
addService: "Agregar Servicio",
enterValidServiceCost: "Por favor, ingresa un nombre y un costo válido para el servicio.",
addedServices: "Servicios Agregados:",
totalExtraServices: "Total de Servicios Extra:",
invoiceTotal: "Total de la Factura:",
generateInvoicePDF: "Generar Factura (PDF)",
saveInvoice: "Guardar Factura",
selectClientToGenerateInvoice: "Por favor, selecciona un cliente para generar la factura.",
jsPDFError: "Error: jsPDF no está cargado. Asegúrate de que las librerías jsPDF y jspdf-autotable estén incluidas en el HTML.",
invoiceSavedSuccess: "Factura guardada con éxito.",
errorSavingInvoice: "Error al guardar la factura:",
savedInvoices: "Facturas Guardadas",
noSavedInvoices: "No hay facturas guardadas aún.",
date: "Fecha:",
total: "Total:",
status: "Estado:",
pending: "Pendiente",
paid: "Pagada",
historyModuleTitle: "Historial de Actividad",
historyModuleContent: "Aquí se mostrará el historial de todas las actividades importantes, como la creación de clientes, generación de facturas, etc. (En desarrollo)",
taskManagementTitle: "Gestión de Tareas",
addNewTask: "Agregar Nueva Tarea",
taskDescription: "Descripción de la tarea...",
addTask: "Agregar Tarea",
taskCannotBeEmpty: "La descripción de la tarea no puede estar vacía.",
taskAddedSuccess: "Tarea agregada con éxito.",
errorAddingTask: "Error al agregar tarea:",
taskList: "Lista de Tareas",
noPendingTasks: "No hay tareas pendientes.",
taskStatusUpdated: "Estado de tarea actualizado.",
errorUpdatingTask: "Error al actualizar tarea:",
deleteTaskConfirm: "¿Estás seguro de que quieres eliminar este cliente? Esta acción no se puede deshacer.",
taskBreakdown: "Desglose de Tarea ✨",
originalTask: "Tarea original:",
generatingBreakdown: "Generando desglose...",
noBreakdownGenerated: "No se pudo generar un desglose. Intenta con una descripción de tarea diferente.",
errorCallingAI: "Error al conectar con la IA:",
months: [ // Added months array for iteration
{ value: 'jan', label: 'Enero' }, { value: 'feb', label: 'Febrero' }, { value: 'mar', label: 'Marzo' },
{ value: 'apr', label: 'Abril' }, { value: 'may', label: 'Mayo' }, { value: 'jun', label: 'Junio' },
{ value: 'jul', label: 'Julio' }, { value: 'aug', label: 'Agosto' }, { value: 'sep', label: 'Septiembre' },
{ value: 'oct', label: 'Octubre' }, { value: 'nov', label: 'Noviembre' }, { value: 'dec', label: 'Diciembre' },
],
deleteService: "Eliminar servicio",
taskBreakdownAI: "Desglosar Tarea con IA",
errorFirebaseNotInitialized: "Error: Firebase no está completamente inicializado. Inténtalo de nuevo.",
invoiceProApp: "ProApp Factura",
invoiceId: "Factura ID:",
billTo: "Facturar a:",
thanksForBusiness: "¡Gracias por tu negocio!",
description: "Descripción",
costTable: "Costo",
maintenanceGardening: "Mantenimiento de Jardinería",
monthNotSpecified: "Mes no especificado",
totalServices: "Total de Servicios Extra:",
selectService: "Selecciona un servicio",
logout: "Cerrar Sesión",
gardenNotes: "Notas del Jardín", // New translation
},
en: {
appNamePro: "Pro",
appNameApp: "App",
loadingApp: "Loading application...",
error: "Error",
close: "Close",
confirmation: "Confirmation",
confirm: "Confirm",
cancel: "Cancel",
authPageTitle: "ProApp",
authPageSubtitleRegister: "Create your account and adapt the app to your business",
authPageSubtitleLogin: "Log in to your account",
email: "Email",
password: "Password",
registerNewAccount: "Register New Account",
login: "Log In",
alreadyHaveAccount: "Already have an account? Log In",
noAccount: "Don't have an account? Register",
userId: "User ID:",
notAuthenticated: "Not Authenticated",
selectBusinessType: "Select your business type",
gardener: "Gardening and Landscaping",
plumber: "Plumbing",
painter: "Painting",
cleaner: "House Cleaning",
other: "Other (General)",
pleaseSelectBusinessType: "Please select a business type.",
authenticationError: "Authentication error:",
errorAuthSetup: "Initial authentication setup error:",
errorFetchingProfile: "Error fetching user profile:",
errorLoggingOut: "Error logging out:",
dashboardWelcome: "Welcome to your Dashboard,",
businessType: "Business Type:",
totalClients: "Total Clients",
pendingInvoices: "Pending Invoices",
upcomingTasks: "Upcoming Tasks",
clientManagement: "Client Management",
clientManagementDesc: "Manage your client information.",
invoicing: "Invoicing",
invoicingDesc: "Create and manage your professional invoices.",
taskManagement: "Task Management",
taskManagementDesc: "Organize and track your pending tasks.",
history: "History",
historyDesc: "View the activity and transaction log.",
gardenerTipTitle: "Gardening Tip",
gardenerTipContent: "Remember to schedule deep watering for lawns in summer and fertilize seasonal plants for optimal growth.",
plumberTipTitle: "Plumbing Tip",
plumberTipContent: "Regularly inspect pipes for leaks and advise your clients on preventive maintenance for water heaters.",
generalBusinessTipTitle: "General Business Tip",
generalBusinessTipContent: "Keep your records up to date and proactively communicate with your clients to ensure their satisfaction.",
searchClient: "Search client by name or address...",
addNewClient: "Add New Client",
noClientsRegistered: "No clients registered.",
editClient: "Edit Client",
addClient: "Add New Client",
fullName: "Full Name",
address: "Address",
phone: "Phone",
photoUrl: "Photo URL (Optional)",
maintenanceFrequency: "Maintenance Frequency",
weekly: "Weekly",
biWeekly: "Bi-Weekly",
monthly: "Once a Month",
monthlyMaintenanceCost: "Monthly Maintenance Cost ($)",
paymentsReceived: "Payments Received (Mark Paid Months)",
totalPaid: "Total Paid:",
totalRemaining: "Total Remaining:",
lastServiceDate: "Last Service Date",
commonIssue: "Common Issue",
saveChanges: "Save Changes",
clientUpdatedSuccess: "Client updated successfully.",
clientAddedSuccess: "Client added successfully.",
errorSavingClient: "Error saving client:",
deleteClientConfirm: "Are you sure you want to delete this client? This action cannot be undone.",
taskBreakdown: "Task Breakdown ✨",
originalTask: "Original task:",
generatingBreakdown: "Generating breakdown...",
noBreakdownGenerated: "Could not generate a breakdown. Try a different task description.",
errorCallingAI: "Error connecting with AI:",
months: [ // Added months array for iteration
{ value: 'jan', label: 'January' }, { value: 'feb', label: 'February' }, { value: 'mar', label: 'March' },
{ value: 'apr', label: 'April' }, { value: 'may', label: 'May' }, { value: 'jun', label: 'June' },
{ value: 'jul', label: 'July' }, { value: 'aug', label: 'August' }, { value: 'sep', label: 'September' },
{ value: 'oct', label: 'October' }, { value: 'nov', label: 'November' }, { value: 'dec', label: 'December' },
],
deleteService: "Delete service",
taskBreakdownAI: "Breakdown Task with AI",
errorFirebaseNotInitialized: "Error: Firebase is not fully initialized. Please try again.",
invoiceProApp: "ProApp Invoice",
invoiceId: "Invoice ID:",
billTo: "Bill To:",
thanksForBusiness: "Thank you for your business!",
description: "Description",
costTable: "Cost",
maintenanceGardening: "Gardening Maintenance",
monthNotSpecified: "Month not specified",
totalServices: "Total Extra Services:",
selectService: "Select a service",
logout: "Log Out",
gardenNotes: "Garden Notes", // New translation
},
};
// Confirmation Modal Component
function ConfirmationModal({ message, onConfirm, onCancel }) {
const { language } = useContext(AuthContext);
const t = translations[language];
return (
{t.confirmation}
{message}
);
}
// LLM Task Breakdown Modal Component
function TaskBreakdownModal({ taskDescription, breakdownContent, onClose, isLoading }) {
const { language } = useContext(AuthContext);
const t = translations[language];
return (
{t.taskBreakdown}
{t.originalTask} "{taskDescription}"
{isLoading ? (
{t.generatingBreakdown}
) : (
{breakdownContent || t.noBreakdownGenerated}
)}
);
}
// Main App Component
function App() {
const [user, setUser] = useState(null);
const [businessType, setBusinessType] = useState(null);
const [loadingAuth, setLoadingAuth] = useState(true);
const [showAuthModal, setShowAuthModal] = useState(false);
const [modalMessage, setModalMessage] = useState('');
const [showConfirmModal, setShowConfirmModal] = useState(false);
const [confirmModalMessage, setConfirmModalMessage] = useState('');
const [confirmAction, setConfirmAction] = useState(null); // Function to execute on confirm
const [language, setLanguage] = useState('es'); // Default language is Spanish
const t = translations[language];
useEffect(() => {
// Authenticate with custom token or anonymously
const authenticate = async () => {
try {
// We no longer rely on __initial_auth_token from the environment
// For a deployed app, you'd handle initial auth (e.g., check session, sign in anonymously if no user)
// For this environment, we'll just proceed with onAuthStateChanged
// If no user is signed in, AuthPage will handle it.
} catch (error) {
console.error("Error during initial authentication setup:", error);
setModalMessage(`${t.errorAuthSetup} ${error.message}`);
setShowAuthModal(true);
} finally {
// setLoadingAuth(false); // Let onAuthStateChanged handle this
}
};
authenticate();
// Listen for auth state changes
const unsubscribe = onAuthStateChanged(auth, async (currentUser) => {
setUser(currentUser);
if (currentUser) {
const userId = currentUser.uid;
// The path for user profiles now directly uses the projectId as appId
const userDocRef = doc(db, `artifacts/${appId}/users/${userId}/profile`, 'data');
try {
const userDocSnap = await getDoc(userDocRef);
if (userDocSnap.exists()) {
setBusinessType(userDocSnap.data().businessType);
} else {
// If user exists but no profile, prompt for business type or redirect to registration
console.log("User profile not found, prompting for registration details.");
setBusinessType(null); // Force user to register business type
}
} catch (error) {
console.error("Error fetching user profile:", error);
setModalMessage(`${t.errorFetchingProfile} ${error.message}`);
setShowAuthModal(true);
}
} else {
setBusinessType(null);
}
setLoadingAuth(false); // Set loading to false once auth state is determined
});
return () => unsubscribe();
}, [t]); // Added t to dependency array to re-run effects if translations change
const handleLogout = async () => {
try {
await signOut(auth);
setBusinessType(null);
} catch (error) {
console.error("Error logging out:", error);
setModalMessage(`${t.errorLoggingOut} ${error.message}`);
setShowAuthModal(true);
}
};
const showConfirmation = (message, action) => {
setConfirmModalMessage(message);
setConfirmAction(() => action); // Store the action to be executed
setShowConfirmModal(true);
};
const handleConfirm = () => {
if (confirmAction) {
confirmAction();
}
setShowConfirmModal(false);
setConfirmAction(null);
};
const handleCancelConfirm = () => {
setShowConfirmModal(false);
setConfirmAction(null);
};
if (loadingAuth) {
return (