Skip to main content

Overview

This guide covers all configuration options for Checawaa, including email setup, user management, scheduler settings, security configuration, and customization options.
Most configuration is done through editing the main application file (app.py) and JSON data files. Changes typically require restarting the application to take effect.

Initial Setup

Prerequisites

Before configuring Checawaa, ensure you have:
1

Python environment

Python 3.7 or higher installed on your server
2

Required packages

Install dependencies from requirements.txt:
pip install flask flask-login flask-mail reportlab apscheduler
3

File permissions

Ensure the application has write access to the data directory for storing JSON files
4

Network access

Open ports for:
  • Port 5000 (or your chosen port) for HTTP access
  • Port 587 for SMTP email (if using Gmail)

First-Time Configuration Checklist

Default: 'clave_secreta_muy_segura'Action required: Set a unique, random secret key for session security.
Update SMTP credentials to your organization’s email server.
Replace default users with your actual employees.
Verify login, tracking, and email functionality.

Email Configuration

SMTP Settings

Checawaa uses Flask-Mail to send automated reminders and notifications. Configure these settings in app.py:
Current default configuration:
app.config['MAIL_SERVER'] = 'smtp.gmail.com'
app.config['MAIL_PORT'] = 587
app.config['MAIL_USE_TLS'] = True
app.config['MAIL_USERNAME'] = '[email protected]'
app.config['MAIL_PASSWORD'] = 'your-app-password'
Never use your actual Gmail password. Use an App Password instead.
Setting up Gmail App Password:
1

Enable 2-Factor Authentication

Go to Google Account Security settings and enable 2FA
2

Generate App Password

Visit: https://myaccount.google.com/apppasswordsSelect “Mail” and “Other” device
3

Copy the 16-character password

Format: xxxx xxxx xxxx xxxxUse this in MAIL_PASSWORD (remove spaces)

Email Sender Configuration

The sender email address appears in the “From” field:
# In app.py, function: enviar_recordatorio_automatizado
msg = Message("⚠️ Recordatorio Automático: Inicia tu Turno", 
              sender=app.config['MAIL_USERNAME'],  # Uses configured email
              recipients=destinatarios)
Use a professional, recognizable sender address like [email protected] or [email protected] for better email delivery rates.

Testing Email Configuration

After configuring email settings, test the functionality:
1

Start the application

python app.py
2

Log in as admin

Access the monitoring panel at /monitor
3

Click 'Notificar Faltantes'

This manually triggers the email reminder system
4

Check employee inboxes

Verify emails are received (check spam folders if not in inbox)

Troubleshooting Email Issues

Error: SMTPAuthenticationError or similarSolutions:
  • Verify username and password are correct
  • For Gmail: ensure you’re using an App Password, not regular password
  • Check if 2FA is required for your email service
  • Confirm “less secure app access” is enabled (if applicable)
Error: SMTPServerDisconnected or timeout errorsSolutions:
  • Verify MAIL_SERVER hostname is correct
  • Check firewall allows outbound traffic on port 587/465
  • Try alternate ports (587 for TLS, 465 for SSL)
  • Test with telnet smtp.gmail.com 587 to verify connectivity
Symptoms: Emails sent but employees don’t see themSolutions:
  • Check spam folders first
  • Add sender address to company’s email whitelist
  • Use a company domain email (not personal Gmail)
  • Configure SPF and DKIM records for your domain
  • Avoid excessive use of emojis in subject lines

Security Configuration

Secret Key

The Flask secret key is used to sign session cookies and protect against CSRF attacks: Current configuration:
app.secret_key = 'clave_secreta_muy_segura'
CRITICAL: Change this before deploying to production! This default key is publicly visible and insecure.

Generating a Secure Secret Key

import secrets
print(secrets.token_hex(32))
# Output: a1b2c3d4e5f6...  (64 characters)
Copy the output and use it as your secret key.
Update in app.py:
app.secret_key = 'your-generated-secret-key-here'
Instead of hardcoding credentials, use environment variables:
1

Create a .env file

# .env
SECRET_KEY=your-secret-key
MAIL_USERNAME=[email protected]
MAIL_PASSWORD=your-app-password
Add .env to your .gitignore to prevent committing secrets!
2

Install python-dotenv

pip install python-dotenv
3

Load environment variables in app.py

import os
from dotenv import load_dotenv

load_dotenv()

app.secret_key = os.getenv('SECRET_KEY')
app.config['MAIL_USERNAME'] = os.getenv('MAIL_USERNAME')
app.config['MAIL_PASSWORD'] = os.getenv('MAIL_PASSWORD')

Password Security

The current implementation stores passwords in plain text:
{
    "username": "empleado1",
    "pass": "123",  // Plain text - insecure!
    "email": "[email protected]"
}
Security Risk: Plain text passwords are visible to anyone with file access.
Recommended: Hash passwords using bcrypt
  1. Install bcrypt:
    pip install bcrypt
    
  2. Hash passwords when creating users:
    import bcrypt
    
    # When creating a user
    password = "123"
    hashed = bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt())
    
    # Store hashed password in usuarios.json
    {
        "username": "empleado1",
        "pass": hashed.decode('utf-8'),
        "email": "[email protected]"
    }
    
  3. Verify passwords on login:
    # In login route
    stored_hash = user['pass'].encode('utf-8')
    entered_password = request.form['password'].encode('utf-8')
    
    if bcrypt.checkpw(entered_password, stored_hash):
        login_user(User(user_input))
    

User Management

User Database Structure

Users are stored in data/usuarios.json:
[
    {
        "username": "admin",
        "pass": "secure-admin-password",
        "email": "[email protected]"
    },
    {
        "username": "empleado1",
        "pass": "employee-password",
        "email": "[email protected]"
    }
]

Adding Users

1

Determine user details

  • Username: Unique identifier (e.g., john.doe, employee123)
  • Password: Strong password (or hash if implementing hashing)
  • Email: Valid email for receiving reminders
2

Edit usuarios.json

Open data/usuarios.json and add the new user:
{
    "username": "john.doe",
    "pass": "SecurePass123!",
    "email": "[email protected]"
}
3

Validate JSON syntax

Ensure proper commas between entries and valid JSON structure.Use a JSON validator: https://jsonlint.com/
4

Test the new account

Try logging in with the new credentials to verify it works.

Bulk User Import

For adding many users at once:
import json
import csv

# Read from CSV
users = []
with open('employees.csv', 'r') as csvfile:
    reader = csv.DictReader(csvfile)
    for row in reader:
        users.append({
            "username": row['username'],
            "pass": row['password'],
            "email": row['email']
        })

# Write to usuarios.json
with open('data/usuarios.json', 'w') as jsonfile:
    json.dump(users, jsonfile, indent=4)

print(f"Imported {len(users)} users successfully!")
CSV format (employees.csv):
username,password,email
john.doe,Pass123,[email protected]
jane.smith,Pass456,[email protected]

Removing Users

Admin Account Configuration

The admin account is special - it has access to the monitoring panel:
# In app.py, login route
if user_input == 'admin':
    return redirect(url_for('monitor'))
The username must be exactly “admin” (case-sensitive). Any other username is treated as an employee account.
Creating multiple admin accounts: To give monitoring access to multiple people, modify the login logic:
ADMIN_USERS = ['admin', 'supervisor', 'hr.manager']

if user_input in ADMIN_USERS:
    return redirect(url_for('monitor'))

Scheduler Configuration

Automated Reminder Schedule

The automatic reminder system uses APScheduler to send emails at 8:00 AM daily: Current configuration (app.py):
from apscheduler.schedulers.background import BackgroundScheduler

scheduler = BackgroundScheduler(daemon=True)
scheduler.add_job(
    enviar_recordatorio_automatizado,
    'cron',
    hour=8,
    minute=0
)
scheduler.start()

Changing the Reminder Time

Example: 7:30 AM instead of 8:00 AM
scheduler.add_job(
    enviar_recordatorio_automatizado,
    'cron',
    hour=7,    # Changed from 8
    minute=30  # Changed from 0
)

Scheduler Troubleshooting

When running Flask in debug mode, the scheduler may execute twice due to the reloader. Always use use_reloader=False:
if __name__ == '__main__':
    app.run(host='0.0.0.0', 
            port=5000, 
            debug=True, 
            use_reloader=False)  # Critical!
Symptoms: Automated emails never sendCheck:
  • Verify scheduler is started: scheduler.start()
  • Check server logs for scheduler errors
  • Ensure the application stays running (doesn’t exit)
  • Confirm timezone settings are correct
Symptoms: Reminders arrive at unexpected timesSolutions:
  • Check server timezone: date or timedatectl
  • Set timezone explicitly in scheduler:
    from pytz import timezone
    scheduler.add_job(
        enviar_recordatorio_automatizado,
        'cron',
        hour=8,
        minute=0,
        timezone=timezone('America/New_York')
    )
    

Attendance Settings

Tardiness Threshold

The time that determines if an employee is late: Current setting: 8:30 AM
# In monitor route (app.py:149)
hora_reg = reg.get('hora', '00:00:00')
es_retardo = hora_reg > "08:30:00"
To change the threshold:
1

Update monitor route logic

# Change to 9:00 AM threshold
es_retardo = hora_reg > "09:00:00"
2

Update PDF report logic

# In reporte_pdf route (app.py:206)
estado = "RETARDO" if hora > "09:00:00" else "PUNTUAL"
3

Update reminder time

Adjust the scheduler to send reminders before the new threshold:
# If threshold is 9:00, remind at 8:30
scheduler.add_job(..., hour=8, minute=30)
Ensure consistency across all locations where the threshold is used: monitoring panel, PDF reports, and any custom analytics.

Location Update Frequency

How often employees send their location: Current setting: Every 50 minutes
// In templates/index.html:27
setInterval(enviarUbicacion, 50 * 60 * 1000);
Changing the frequency:

More Frequent (30 min)

setInterval(enviarUbicacion, 30 * 60 * 1000);
Pros: Better accuracy, more data points Cons: More battery usage, higher server load

Less Frequent (90 min)

setInterval(enviarUbicacion, 90 * 60 * 1000);
Pros: Better battery life, lower server load Cons: Less frequent verification
Consider your organization’s needs. If employees are highly mobile, more frequent updates provide better verification. For stationary office workers, less frequent updates may suffice.

Data Storage Configuration

File Locations

Checawaa stores data in JSON files:
DATA_DIR = 'data'
REGISTROS_FILE = os.path.join(DATA_DIR, 'registros.json')  # Attendance records
USUARIOS_FILE = os.path.join(DATA_DIR, 'usuarios.json')    # User accounts

Changing Storage Location

To use a different directory:
# Absolute path
DATA_DIR = '/var/lib/checawaa/data'

# Relative to user home
import os.path
DATA_DIR = os.path.expanduser('~/checawaa_data')

# Network drive (if mounted)
DATA_DIR = '/mnt/company_storage/checawaa'
Ensure the application has read/write permissions to the specified directory.

Database Migration (Moving to SQLite)

For larger deployments, consider migrating to a database:
import sqlite3

# Initialize database
conn = sqlite3.connect('data/checawaa.db')
cursor = conn.cursor()

# Create tables
cursor.execute('''
CREATE TABLE IF NOT EXISTS usuarios (
    id INTEGER PRIMARY KEY,
    username TEXT UNIQUE NOT NULL,
    password_hash TEXT NOT NULL,
    email TEXT NOT NULL
)
''')

cursor.execute('''
CREATE TABLE IF NOT EXISTS registros (
    id INTEGER PRIMARY KEY,
    usuario TEXT NOT NULL,
    lat REAL NOT NULL,
    lon REAL NOT NULL,
    fecha DATE NOT NULL,
    hora TIME NOT NULL
)
''')

conn.commit()
Benefits:
  • Better performance with large datasets
  • Atomic transactions
  • Query optimization
  • Concurrent access handling

Server Configuration

Development vs Production

Current configuration:
if __name__ == '__main__':
    app.run(host='0.0.0.0', 
            port=5000, 
            debug=True, 
            use_reloader=False)
  • debug=True: Shows detailed errors, auto-reloads on changes
  • host=‘0.0.0.0’: Accessible from any IP
  • port=5000: Default Flask port

Port Configuration

Change the port if 5000 is already in use:
app.run(host='0.0.0.0', 
        port=8080,  # Change to desired port
        debug=False)

HTTPS/SSL Configuration

For production, always use HTTPS:
1

Obtain SSL certificate

Options:
  • Let’s Encrypt (free): https://letsencrypt.org/
  • Commercial CA: Purchase from provider
  • Self-signed: For internal testing only
2

Use a reverse proxy

Nginx configuration:
server {
    listen 443 ssl;
    server_name attendance.company.com;
    
    ssl_certificate /path/to/cert.pem;
    ssl_certificate_key /path/to/key.pem;
    
    location / {
        proxy_pass http://localhost:5000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}
3

Redirect HTTP to HTTPS

server {
    listen 80;
    server_name attendance.company.com;
    return 301 https://$server_name$request_uri;
}

Customization Options

Branding and Appearance

Login page (templates/login.html:6):
<title>Iniciar Sesión - Tracker</title>
Change to:
<title>Login - Company Attendance System</title>

Email Template Customization

Reminder email content (app.py:81):
msg = Message("⚠️ Recordatorio Automático: Inicia tu Turno", 
              sender=app.config['MAIL_USERNAME'], 
              recipients=destinatarios)
msg.body = "Buenos días. Son las 8:00 AM y el sistema aún no detecta tu registro de hoy. Por favor inicia tu monitoreo."
Customize to your needs:
msg.body = """
Good morning / Buenos días,

It's 8:00 AM and the system has not detected your attendance record yet.
Please start your shift tracking.

Son las 8:00 AM y el sistema aún no detecta tu registro de hoy.
Por favor inicia tu monitoreo.

Thank you / Gracias,
Company Attendance System
"""
from flask_mail import Message

msg = Message("⚠️ Attendance Reminder", 
              sender=app.config['MAIL_USERNAME'], 
              recipients=destinatarios)

msg.html = """
<html>
<body style="font-family: Arial, sans-serif;">
    <h2 style="color: #dc3545;">Attendance Reminder</h2>
    <p>Good morning,</p>
    <p>It's 8:00 AM and we haven't received your attendance check-in yet.</p>
    <p><strong>Please log in and start your shift tracking.</strong></p>
    <hr>
    <p style="font-size: 12px; color: #666;">
        This is an automated message from the Company Attendance System.
    </p>
</body>
</html>
"""

PDF Report Customization

Report header (app.py:192):
p.drawString(100, 750, f"REPORTE DE ASISTENCIA - GENERADO: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
Customization options:
1

Company logo

from reportlab.lib.utils import ImageReader

logo = ImageReader('static/images/company_logo.png')
p.drawImage(logo, 50, 720, width=100, height=50)
2

Custom header text

p.setFont("Helvetica-Bold", 16)
p.drawString(100, 750, "COMPANY NAME")
p.setFont("Helvetica", 12)
p.drawString(100, 735, "Employee Attendance Report")
p.drawString(100, 720, f"Generated: {datetime.datetime.now().strftime('%B %d, %Y at %I:%M %p')}")
3

Additional columns

Add more data to the report table:
# Add location column
p.drawString(450, y, "Location")
# Later in loop:
p.drawString(450, y, f"({r['lat']:.4f}, {r['lon']:.4f})")

Performance Optimization

For Large Deployments

# Add indexes for faster queries
cursor.execute('CREATE INDEX idx_fecha ON registros(fecha)')
cursor.execute('CREATE INDEX idx_usuario ON registros(usuario)')
cursor.execute('CREATE INDEX idx_usuario_fecha ON registros(usuario, fecha)')
from functools import lru_cache
from datetime import datetime, timedelta

@lru_cache(maxsize=128)
def get_attendance_for_date(date_string):
    registros = leer_json(REGISTROS_FILE)
    return [r for r in registros if r['fecha'] == date_string]

# Clear cache after new records
def update_location():
    # ... save new record ...
    get_attendance_for_date.cache_clear()
# In monitor route, show only recent records
from datetime import datetime, timedelta

thirty_days_ago = (datetime.now() - timedelta(days=30)).strftime("%Y-%m-%d")

asistencia = [
    reg for reg in todos_registros 
    if reg.get('fecha', '') >= thirty_days_ago
]

Data Maintenance

Monthly archiving script:
import json
from datetime import datetime, timedelta

# Archive records older than 90 days
cutoff = (datetime.now() - timedelta(days=90)).strftime("%Y-%m-%d")

registros = leer_json(REGISTROS_FILE)
current = [r for r in registros if r['fecha'] >= cutoff]
archive = [r for r in registros if r['fecha'] < cutoff]

# Save current records
guardar_json(REGISTROS_FILE, current)

# Save archive
archive_file = f"data/archive_{cutoff}.json"
guardar_json(archive_file, archive)

print(f"Archived {len(archive)} records, kept {len(current)}")

Backup and Recovery

Automated Backup Strategy

1

Create backup script

#!/bin/bash
# backup.sh

BACKUP_DIR="/backups/checawaa"
DATE=$(date +%Y%m%d_%H%M%S)

mkdir -p "$BACKUP_DIR"

# Backup data directory
tar -czf "$BACKUP_DIR/checawaa_$DATE.tar.gz" data/

# Keep only last 30 days of backups
find "$BACKUP_DIR" -name "checawaa_*.tar.gz" -mtime +30 -delete

echo "Backup completed: checawaa_$DATE.tar.gz"
2

Schedule with cron

# Run daily at 2 AM
crontab -e

# Add line:
0 2 * * * /path/to/backup.sh
3

Test restoration

# Extract backup
tar -xzf checawaa_20260305_020000.tar.gz

# Restore files
cp -r data/* /path/to/checawaa/data/

Cloud Backup Integration

import boto3
from datetime import datetime

def backup_to_s3():
    s3 = boto3.client('s3')
    bucket_name = 'company-checawaa-backups'
    
    timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
    
    # Upload usuarios.json
    s3.upload_file(
        USUARIOS_FILE,
        bucket_name,
        f'backups/usuarios_{timestamp}.json'
    )
    
    # Upload registros.json
    s3.upload_file(
        REGISTROS_FILE,
        bucket_name,
        f'backups/registros_{timestamp}.json'
    )
    
    print(f"Backup uploaded to S3: {timestamp}")

Monitoring and Logging

Application Logging

Add comprehensive logging:
import logging

# Configure logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler('checawaa.log'),
        logging.StreamHandler()
    ]
)

logger = logging.getLogger(__name__)

# Use throughout application
logger.info(f"User {current_user.id} logged in")
logger.warning(f"Failed login attempt for user: {user_input}")
logger.error(f"Email sending failed: {str(e)}")

Health Check Endpoint

Add a health check for monitoring tools:
@app.route('/health')
def health_check():
    return jsonify({
        "status": "healthy",
        "timestamp": datetime.datetime.now().isoformat(),
        "scheduler_running": scheduler.running,
        "total_users": len(leer_json(USUARIOS_FILE)),
        "total_records": len(leer_json(REGISTROS_FILE))
    })

Troubleshooting

Common Configuration Issues

Check:
  • Python version (3.7+ required)
  • All dependencies installed: pip install -r requirements.txt
  • No syntax errors in app.py
  • Port 5000 not already in use: lsof -i :5000
  • Permissions on data directory
Causes:
  • File permission issues (can’t write to data/)
  • Disk space full
  • Invalid JSON syntax breaking file writes
  • Process doesn’t have write access
Solutions:
# Check permissions
ls -la data/

# Fix permissions
chmod 755 data/
chmod 644 data/*.json

# Check disk space
df -h
Debugging steps:
  1. Check logs for scheduler errors
  2. Verify scheduler.start() is called
  3. Confirm use_reloader=False
  4. Test manual trigger works: /send-reminders
  5. Check server timezone matches expected

Next Steps

After configuration:
  1. Test all functionality - Login, tracking, emails, reports
  2. Train administrators - Ensure they understand the admin panel
  3. Onboard employees - Provide training on using the system
  4. Monitor initially - Watch for issues in the first week
  5. Establish backup routine - Automate and test backups
  6. Document customizations - Keep notes on any changes made
For additional help with configuration, refer to the Administrator Guide for operational details or the Employee Guide for end-user information.

Build docs developers (and LLMs) love