Base de Datos¶
El Sistema A3 utiliza PostgreSQL 12.3+ como sistema de gestión de base de datos relacional. Esta página documenta el esquema, relaciones y consideraciones de diseño.
Esquema General¶
La base de datos está organizada en tablas correspondientes a las apps Django del sistema:
erDiagram
User ||--o| Perfil : tiene
User ||--o{ Casa : crea
User ||--o{ Apartado : gestiona
User ||--o{ Ticket : maneja
Casa ||--o{ AnexoCasa : tiene
Casa ||--o| Apartado : se_vende_en
Cliente ||--o{ Apartado : compra
Cliente ||--o{ AnexoCliente : tiene
Apartado ||--o{ Obra : requiere
Apartado ||--o| Garantia : tiene
Apartado ||--o| Cierre : finaliza
Ticket ||--o{ SubtareaTicket : contiene
Ticket ||--o{ ChatTicket : tiene
Comprobacion ||--o{ AnexoComprobacion : documenta
Comprobacion }|--|| User : aprueba
Perfil ||--o{ Horarios : tiene
Perfil ||--o{ Ausencia : registra
Perfil ||--o{ HorasExtra : acumula
Tablas Principales¶
auth_user (Django Auth)¶
Usuario base de Django, extendido por el modelo Perfil.
CREATE TABLE auth_user (
id SERIAL PRIMARY KEY,
username VARCHAR(150) UNIQUE NOT NULL,
first_name VARCHAR(150),
last_name VARCHAR(150),
email VARCHAR(254),
is_staff BOOLEAN DEFAULT FALSE,
is_active BOOLEAN DEFAULT TRUE,
is_superuser BOOLEAN DEFAULT FALSE,
password VARCHAR(128) NOT NULL,
last_login TIMESTAMP,
date_joined TIMESTAMP DEFAULT NOW()
);
users_perfil¶
Extensión del modelo User con información adicional.
CREATE TABLE users_perfil (
id SERIAL PRIMARY KEY,
user_id INTEGER UNIQUE REFERENCES auth_user(id) ON DELETE CASCADE,
tipo_perfil VARCHAR(20) NOT NULL, -- 'ASESOR' o 'CLIENTE'
plaza VARCHAR(50),
telefono VARCHAR(20),
fecha_nacimiento DATE,
direccion TEXT,
rfc VARCHAR(20),
curp VARCHAR(20),
nss VARCHAR(20),
-- Campos específicos para asesores
numero_empleado VARCHAR(20),
fecha_ingreso DATE,
salario DECIMAL(12, 2),
-- Campos de control
activo BOOLEAN DEFAULT TRUE,
fecha_creacion TIMESTAMP DEFAULT NOW(),
fecha_modificacion TIMESTAMP DEFAULT NOW()
);
CREATE INDEX idx_perfil_plaza ON users_perfil(plaza);
CREATE INDEX idx_perfil_tipo ON users_perfil(tipo_perfil);
inventario_casa¶
Catálogo de propiedades disponibles.
CREATE TABLE inventario_casa (
id SERIAL PRIMARY KEY,
clave VARCHAR(50) UNIQUE NOT NULL,
proyecto VARCHAR(100) NOT NULL,
tipo_vivienda VARCHAR(50),
modelo VARCHAR(50),
plaza VARCHAR(50) NOT NULL,
-- Ubicación
direccion TEXT,
colonia VARCHAR(100),
cp VARCHAR(10),
latitude DECIMAL(9, 6),
longitude DECIMAL(9, 6),
-- Características
recamaras INTEGER,
banos DECIMAL(3, 1),
superficie_terreno DECIMAL(10, 2),
superficie_construccion DECIMAL(10, 2),
-- Precios
precio_venta DECIMAL(12, 2),
precio_contado DECIMAL(12, 2),
enganche DECIMAL(12, 2),
-- Estado
estatus VARCHAR(50),
disponible BOOLEAN DEFAULT TRUE,
-- Auditoría
usuario_creacion_id INTEGER REFERENCES auth_user(id),
fecha_creacion TIMESTAMP DEFAULT NOW(),
fecha_modificacion TIMESTAMP DEFAULT NOW()
);
CREATE INDEX idx_casa_plaza ON inventario_casa(plaza);
CREATE INDEX idx_casa_estatus ON inventario_casa(estatus);
CREATE INDEX idx_casa_disponible ON inventario_casa(disponible);
clientes_cliente¶
Base de datos de clientes.
CREATE TABLE clientes_cliente (
id SERIAL PRIMARY KEY,
tipo_persona VARCHAR(20) NOT NULL, -- 'FISICA' o 'MORAL'
-- Identificación
rfc VARCHAR(20) UNIQUE NOT NULL,
nombre VARCHAR(100),
apellido_paterno VARCHAR(100),
apellido_materno VARCHAR(100),
razon_social VARCHAR(200),
-- Contacto
email VARCHAR(254),
telefono VARCHAR(20),
celular VARCHAR(20),
-- Dirección
calle TEXT,
numero_exterior VARCHAR(20),
numero_interior VARCHAR(20),
colonia VARCHAR(100),
municipio VARCHAR(100),
estado VARCHAR(50),
cp VARCHAR(10),
-- Metadata
plaza VARCHAR(50),
asesor_id INTEGER REFERENCES auth_user(id),
fecha_creacion TIMESTAMP DEFAULT NOW(),
activo BOOLEAN DEFAULT TRUE
);
CREATE INDEX idx_cliente_rfc ON clientes_cliente(rfc);
CREATE INDEX idx_cliente_asesor ON clientes_cliente(asesor_id);
apartados_apartado¶
Registros de ventas y reservaciones.
CREATE TABLE apartados_apartado (
id SERIAL PRIMARY KEY,
-- Relaciones
casa_id INTEGER REFERENCES inventario_casa(id),
cliente_id INTEGER REFERENCES clientes_cliente(id),
asesor_id INTEGER REFERENCES auth_user(id),
-- Tipo de venta
tipo_venta VARCHAR(20), -- 'CREDITO', 'CONTADO', 'C90'
plaza VARCHAR(50),
-- Precios finales
precio_final DECIMAL(12, 2),
enganche_pagado DECIMAL(12, 2),
monto_credito DECIMAL(12, 2),
-- Fechas
fecha_apartado DATE,
fecha_escrituracion DATE,
fecha_entrega DATE,
-- Estado
estatus VARCHAR(50),
activo BOOLEAN DEFAULT TRUE,
-- Comisiones
comision_asesor DECIMAL(12, 2),
comision_pagada BOOLEAN DEFAULT FALSE,
-- Auditoría
fecha_creacion TIMESTAMP DEFAULT NOW(),
fecha_modificacion TIMESTAMP DEFAULT NOW()
);
CREATE INDEX idx_apartado_casa ON apartados_apartado(casa_id);
CREATE INDEX idx_apartado_cliente ON apartados_apartado(cliente_id);
CREATE INDEX idx_apartado_asesor ON apartados_apartado(asesor_id);
CREATE INDEX idx_apartado_estatus ON apartados_apartado(estatus);
tickets_ticket¶
Sistema de tickets y tareas.
CREATE TABLE tickets_ticket (
id SERIAL PRIMARY KEY,
-- Información básica
titulo VARCHAR(200) NOT NULL,
descripcion TEXT,
servicio_id INTEGER REFERENCES tickets_servicio(id),
-- Asignación
creador_id INTEGER REFERENCES auth_user(id),
asignado_id INTEGER REFERENCES auth_user(id),
-- Prioridad y estado
prioridad VARCHAR(20) DEFAULT 'MEDIA',
estatus VARCHAR(20) DEFAULT 'PENDIENTE',
-- Fechas
fecha_limite DATE,
fecha_inicio TIMESTAMP,
fecha_fin TIMESTAMP,
-- Metadata
plaza VARCHAR(50),
etiquetas TEXT[], -- PostgreSQL array
-- Auditoría
fecha_creacion TIMESTAMP DEFAULT NOW(),
fecha_modificacion TIMESTAMP DEFAULT NOW()
);
CREATE INDEX idx_ticket_creador ON tickets_ticket(creador_id);
CREATE INDEX idx_ticket_asignado ON tickets_ticket(asignado_id);
CREATE INDEX idx_ticket_estatus ON tickets_ticket(estatus);
CREATE INDEX idx_ticket_fecha_limite ON tickets_ticket(fecha_limite);
comprobaciones_comprobacion¶
Gastos y comprobaciones de gastos.
CREATE TABLE comprobaciones_comprobacion (
id SERIAL PRIMARY KEY,
-- Identificación
folio VARCHAR(50) UNIQUE,
concepto TEXT NOT NULL,
-- Montos
monto_total DECIMAL(12, 2) NOT NULL,
monto_autorizado DECIMAL(12, 2),
-- Usuario
solicitante_id INTEGER REFERENCES auth_user(id),
plaza VARCHAR(50),
-- Aprobaciones
aprobado_contabilidad BOOLEAN DEFAULT FALSE,
aprobador_contabilidad_id INTEGER REFERENCES auth_user(id),
fecha_aprobacion_contabilidad TIMESTAMP,
aprobado_tesoreria BOOLEAN DEFAULT FALSE,
aprobador_tesoreria_id INTEGER REFERENCES auth_user(id),
fecha_aprobacion_tesoreria TIMESTAMP,
-- Estado
estatus VARCHAR(50) DEFAULT 'PENDIENTE',
-- Auditoría
fecha_creacion TIMESTAMP DEFAULT NOW(),
fecha_modificacion TIMESTAMP DEFAULT NOW()
);
CREATE INDEX idx_comprob_solicitante ON comprobaciones_comprobacion(solicitante_id);
CREATE INDEX idx_comprob_estatus ON comprobaciones_comprobacion(estatus);
ch_horarios¶
Horarios de trabajo de empleados.
CREATE TABLE ch_horarios (
id SERIAL PRIMARY KEY,
perfil_id INTEGER REFERENCES users_perfil(id),
-- Días de la semana
lunes_entrada TIME,
lunes_salida TIME,
martes_entrada TIME,
martes_salida TIME,
-- ... (resto de días)
-- Metadata
activo BOOLEAN DEFAULT TRUE,
fecha_creacion TIMESTAMP DEFAULT NOW()
);
Relaciones Clave¶
One-to-One¶
One-to-Many¶
User → Casas (creadas)
User → Apartados (gestionados)
User → Tickets (asignados)
Casa → AnexosCasa
Cliente → Apartados
Many-to-Many¶
Índices y Optimizaciones¶
Índices Implementados¶
-- Búsqueda por plaza (muy frecuente)
CREATE INDEX idx_plaza ON inventario_casa(plaza);
CREATE INDEX idx_apartado_plaza ON apartados_apartado(plaza);
CREATE INDEX idx_ticket_plaza ON tickets_ticket(plaza);
-- Búsqueda por estado/estatus
CREATE INDEX idx_casa_disponible ON inventario_casa(disponible);
CREATE INDEX idx_apartado_estatus ON apartados_apartado(estatus);
CREATE INDEX idx_ticket_estatus ON tickets_ticket(estatus);
-- Búsqueda por fechas
CREATE INDEX idx_ticket_fecha_limite ON tickets_ticket(fecha_limite);
CREATE INDEX idx_apartado_fecha ON apartados_apartado(fecha_apartado);
-- Foreign keys (automáticos en PostgreSQL con Django)
-- Se crean automáticamente en todas las ForeignKey
Partial Indexes¶
-- Solo casas disponibles (consulta muy frecuente)
CREATE INDEX idx_casas_disponibles
ON inventario_casa(plaza, precio_venta)
WHERE disponible = TRUE AND activo = TRUE;
-- Solo tickets pendientes
CREATE INDEX idx_tickets_pendientes
ON tickets_ticket(asignado_id, fecha_limite)
WHERE estatus IN ('PENDIENTE', 'EN_PROCESO');
Triggers y Constraints¶
Constraints de Negocio¶
-- El precio de contado debe ser menor al de venta
ALTER TABLE inventario_casa
ADD CONSTRAINT check_precio_contado
CHECK (precio_contado <= precio_venta);
-- El enganche no puede ser mayor al precio
ALTER TABLE apartados_apartado
ADD CONSTRAINT check_enganche
CHECK (enganche_pagado <= precio_final);
Triggers (Ejemplo conceptual)¶
-- Actualizar fecha_modificacion automáticamente
CREATE OR REPLACE FUNCTION update_modified_column()
RETURNS TRIGGER AS $$
BEGIN
NEW.fecha_modificacion = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER update_casa_modtime
BEFORE UPDATE ON inventario_casa
FOR EACH ROW
EXECUTE FUNCTION update_modified_column();
Vistas Materializadas (Futuro)¶
Para reportes y analytics:
-- Vista de ventas por plaza (ejemplo)
CREATE MATERIALIZED VIEW reporte_ventas_plaza AS
SELECT
plaza,
COUNT(*) as total_ventas,
SUM(precio_final) as monto_total,
AVG(precio_final) as precio_promedio,
DATE_TRUNC('month', fecha_apartado) as mes
FROM apartados_apartado
WHERE estatus = 'CERRADO'
GROUP BY plaza, mes;
-- Refrescar cada noche
CREATE INDEX ON reporte_ventas_plaza(plaza, mes);
Backups y Mantenimiento¶
Estrategia de Backup¶
- Backups automáticos diarios (Heroku Postgres)
- Retención: 7 días para plan Standard
- Backup manual antes de migraciones importantes
Mantenimiento¶
-- Vacuum para recuperar espacio
VACUUM ANALYZE;
-- Reindex para optimizar índices
REINDEX DATABASE sistema_a3;
-- Ver tamaño de tablas
SELECT
schemaname,
tablename,
pg_size_pretty(pg_total_relation_size(schemaname||'.'||tablename)) AS size
FROM pg_tables
WHERE schemaname = 'public'
ORDER BY pg_total_relation_size(schemaname||'.'||tablename) DESC;
Migraciones¶
Django maneja las migraciones automáticamente:
# Crear migraciones
python manage.py makemigrations
# Ver SQL de migración
python manage.py sqlmigrate inventario 0001
# Aplicar migraciones
python manage.py migrate
# Ver estado de migraciones
python manage.py showmigrations
Consideraciones de Performance¶
Queries N+1¶
Problema:
# Genera N+1 queries
casas = Casa.objects.all()
for casa in casas:
print(casa.usuario_creacion.username) # Query por cada casa
Solución:
# Una sola query con join
casas = Casa.objects.select_related('usuario_creacion').all()
for casa in casas:
print(casa.usuario_creacion.username)
Paginación¶
from django.core.paginator import Paginator
casas = Casa.objects.all()
paginator = Paginator(casas, 50) # 50 items por página
page = paginator.get_page(1)
Caché de Queries¶
from django.core.cache import cache
def get_casas_disponibles(plaza):
cache_key = f'casas_{plaza}'
casas = cache.get(cache_key)
if casas is None:
casas = list(Casa.objects.filter(plaza=plaza, disponible=True))
cache.set(cache_key, casas, 300) # 5 minutos
return casas
Para Más Información¶
- Modelos: Documentación detallada de cada modelo
- API: Cómo acceder a los datos vía API
- Backend: Lógica de negocio que usa estos modelos
La base de datos es el corazón del Sistema A3, diseñada para escalar con el negocio.