Prestakuntza-zentroaren aginte-panela (IX): Bezeroen ereduaren hedapena eta fluxu komertzial automatizatua

Fase honetan bezeroaren eredua zabaldu dugu, bezero finalak eta parte-hartzaileak gehituz. Harreman komertzialen fluxua hobetu dugu eta oroigarri- eta saio-zerbitzuak probatu ditugu test unitarioekin.

Bezeroen ereduaren hedapena eta fluxu komertzial automatizatua

Bederatzigarren fase honetan proiektuaren egitura nagusia hedatu dugu:
datu-eredua aberastu dugu bezero finalak, harremanetarako pertsonak eta prestakuntzetako parte-hartzaileak jasotzeko, eta aldi berean hobetu ditugu zerbitzuen eta errepositorioen arteko integrazioa, orain interakzioak, saioak eta oroigarri automatikoak sistema bakarrean uztartuta daudelarik.


1. Datu-ereduaren hedapena

Bezeroaren hasierako ereduari entitate berriak gehitu dizkiogu, enpresen arteko harreman errealak eta prestakuntza-prozesuaren kate osoa hobeto islatzeko.

Entitate berriak:

  • BezeroFinala (ClienteFinal): prestakuntza jasotzen duen enpresa edo entitatea (kontratugilea ez denean).
  • BezeroFinalarenKontaktua (ContactoClienteFinal): prestakuntzan inplikatutako pertsonak (arduraduna, koordinatzailea, parte-hartzailea…).
  • Parte-hartzailea (Participante): prestakuntzako ikasle edo parte-hartzaileak.

Gainera, kontratazio bakoitzak orain id_cliente_final eremua izan dezake, nahi izanez gero, eta horrek trazabilitate osoa ematen du harreman komertzialetik prestakuntzaren exekuzioraino.


2. Errepositorioen eguneraketa

2.1. interaccion_repo.py

Emaitzaren balio posibleetan “propuesta” egoera gehitu da, jarduera komertzialen egoera errealagoak jasotzeko eta oroigarri-zerbitzuarekin bateragarritasuna mantentzeko.

from planificador.data.db_manager import get_connection
import logging

logger = logging.getLogger(__name__)

class InteraccionRepository:

    @staticmethod
    def crear(id_cliente, fecha, tipo, descripcion=None,
              resultado="pendiente", proxima_accion=None,
              fecha_proxima_accion=None, crear_recordatorio=False):
        with get_connection() as conn:
            cur = conn.execute("""
                INSERT INTO InteraccionCliente (
                    id_cliente, fecha, tipo, descripcion, resultado,
                    proxima_accion, fecha_proxima_accion, crear_recordatorio
                )
                VALUES (?, ?, ?, ?, ?, ?, ?, ?)
            """, (id_cliente, fecha, tipo, descripcion, resultado,
                  proxima_accion, fecha_proxima_accion, int(crear_recordatorio)))
            conn.commit()
            logger.info(f"Bezeroarentzako interakzio berria sortu da {id_cliente} ({tipo})")
            return cur.lastrowid

    @staticmethod
    def listar_todas():
        with get_connection() as conn:
            cur = conn.execute("""
                SELECT * FROM InteraccionCliente ORDER BY fecha DESC
            """)
            columnas = [c[0] for c in cur.description]
            return [dict(zip(columnas, fila)) for fila in cur.fetchall()]

2.2. sesion_repo.py

Kodea garbitu eta logging sistema gehitu da. Ez du funtzionalitatea aldatzen, baina trazabilitatea eta egonkortasuna hobetzen ditu.

import logging
from planificador.data.db_manager import get_connection

logger = logging.getLogger(__name__)

class SesionRepository:

    @staticmethod
    def crear(id_contratacion, fecha=None, hora_inicio=None, hora_fin=None,
              direccion=None, enlace_vc=None, estado="propuesta", notas=None):
        """
        Saio berri bat sortzen du. Orain proposamenak erregistratzea ere onartzen du.
        """
        with get_connection() as conn:
            cur = conn.execute("""
                INSERT INTO Sesion (id_contratacion, fecha, hora_inicio, hora_fin,
                                    direccion, enlace_vc, estado, notas)
                VALUES (?, ?, ?, ?, ?, ?, ?, ?)
            """, (id_contratacion, fecha, hora_inicio, hora_fin, direccion, enlace_vc, estado, notas))
            conn.commit()
            logger.info(f"Saioa sortu da: egoera={estado}, kontratazioa={id_contratacion}, data={fecha or 'zehaztu gabe'}")
            return cur.lastrowid

    @staticmethod
    def listar_todas():
        with get_connection() as conn:
            cur = conn.execute("""
                SELECT id_sesion, id_contratacion, fecha, hora_inicio, hora_fin,
                    direccion, enlace_vc, estado, notas
                FROM Sesion
                ORDER BY fecha ASC, hora_inicio ASC
            """)
            columnas = [c[0] for c in cur.description]
            return [dict(zip(columnas, fila)) for fila in cur.fetchall()]

3. Oroigarri-zerbitzuaren eguneraketa

Oroigarriak sortzeko logika berrantolatu da, interakzio eta saio guztien arteko bateratze-sistema bakarra ezarriz.

import logging
from datetime import datetime, timedelta
from planificador.data.repositories.interaccion_repo import InteraccionRepository
from planificador.data.repositories.sesion_repo import SesionRepository

logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)

class ServicioRecordatorios:
    """
    Interakzio eta saioetatik abiatuta oroigarri automatikoak sortzen ditu
    eta datozen 24 orduetako abisuak egiaztatzen ditu.
    """

    @staticmethod
    def generar_desde_interacciones():
        interacciones = InteraccionRepository.listar_todas()
        recordatorios = []

        for inter in interacciones:
            if (
                inter["crear_recordatorio"]
                and inter["fecha_proxima_accion"]
                and datetime.strptime(inter["fecha_proxima_accion"], "%Y-%m-%d")
                >= datetime.now()
            ):
                recordatorio = {
                    "tipo": inter["tipo"],
                    "cliente": inter["id_cliente"],
                    "fecha": inter["fecha_proxima_accion"],
                    "descripcion": inter["proxima_accion"] or inter["descripcion"],
                }
                recordatorios.append(recordatorio)
                logger.info(f"Oroigarria sortu da interakziotik {inter['id_interaccion']}: {recordatorio}")

        logger.info(f"Guztira {len(recordatorios)} oroigarri sortu dira")
        return recordatorios

    @staticmethod
    def comprobar_sesiones_proximas(horas_anticipacion=24):
        sesiones = SesionRepository.listar_todas()
        ahora = datetime.now()
        margen = ahora + timedelta(hours=horas_anticipacion)

        proximas = [
            s for s in sesiones
            if datetime.strptime(s["fecha"] + " " + s["hora_inicio"], "%Y-%m-%d %H:%M") <= margen
            and datetime.strptime(s["fecha"] + " " + s["hora_inicio"], "%Y-%m-%d %H:%M") >= ahora
        ]

        logger.info(f"Hurrengo {horas_anticipacion} orduetan {len(proximas)} saio aurkitu dira")
        return proximas

    @staticmethod
    def avisar_proximos_eventos():
        recordatorios = ServicioRecordatorios.generar_desde_interacciones()
        sesiones = ServicioRecordatorios.comprobar_sesiones_proximas()

        avisos = {
            "recordatorios_interacciones": recordatorios,
            "sesiones_proximas": sesiones,
        }

        logger.info(f"Abisu konbinatuak sortu dira: {avisos}")
        return avisos

4. Test unitarioak

Test unitarioen bidez egiaztatu da interakzioak, saioak eta oroigarriak behar bezala sortzen direla, eta zerbitzu guztiak bateragarriak direla.

from datetime import datetime, timedelta
from planificador.data.repositories.interaccion_repo import InteraccionRepository
from planificador.data.repositories.sesion_repo import SesionRepository
from planificador.data.repositories.contratacion_repo import ContratacionRepository
from planificador.servicios.servicio_recordatorios import ServicioRecordatorios

def test_avisar_proximos_eventos(tmp_path):
    c_id = ContratacionRepository.crear(
        id_cliente=1,
        id_formacion_base=1,
        expediente="EXP-002",
        precio_hora=40,
        horas_previstas=8,
        modalidad="presencial"
    )

    InteraccionRepository.crear(
        id_cliente=1,
        fecha="2025-10-10",
        tipo="reunion",
        descripcion="Proposamenaren berrikuspena",
        resultado="propuesta",
        proxima_accion="Deitu baieztapena lortzeko",
        fecha_proxima_accion=(datetime.now() + timedelta(hours=12)).strftime("%Y-%m-%d"),
        crear_recordatorio=True,
    )

    SesionRepository.crear(
        id_contratacion=c_id,
        fecha=(datetime.now() + timedelta(hours=18)).strftime("%Y-%m-%d"),
        hora_inicio=(datetime.now() + timedelta(hours=18)).strftime("%H:%M"),
        hora_fin=(datetime.now() + timedelta(hours=20)).strftime("%H:%M"),
        direccion="Bulego Nagusia",
        estado="programada",
    )

    avisos = ServicioRecordatorios.avisar_proximos_eventos()

    assert "recordatorios_interacciones" in avisos
    assert "sesiones_proximas" in avisos
    assert len(avisos["sesiones_proximas"]) >= 1

Exekuzioaren emaitza:

pytest -v tests/servicios/test_servicio_recordatorios.py
3 passed in 0.15s

5. Ondorioak

IX. fasearen ostean, prestakuntzen planifikatzailea hurrengo puntuetara iritsi da:

  • Bezero-eredu zabala, enpresa nagusiak eta bezero finalak bereizita.
  • Kontaktuen eta parte-hartzaileen kudeaketa datu-geruzan integratuta.
  • Errepositorio eguneratuak trazabilitate eta egonkortasun handiagoarekin.
  • Oroigarri-zerbitzu bateratua, interakzio eta saioekin koherentea.
  • Test guztiak gaindituta, sistema guztiz funtzionala.

Sistema hau jada prestakuntzan espezializatutako mini-CRM bihurtzen ari da: planifikazioa, jarraipena eta automatizazioa tresna bakarrean integratuta.

Hurrengo urratsa

X. fasean, fakturazio-modulua garatuko dugu: kontratazio eta saioetatik abiatuta fakturak automatikoki sortzea, PDF formatuan esportatzea eta egoeren kudeaketa (zirriborroa, igorria, kobratua, baliogabetua). Administrazio-geruzaren amaiera izango da eta hurrengo faseetako analitika eta adimen funtzionalerako oinarriak ezarriko ditu.